From 64ba60eca35137081d06989083b270aa7ba041ea Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 26 May 2023 16:49:09 +1000 Subject: [PATCH 001/202] move test case --- test/e2e/e2e_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 1b9411895..3c5e68736 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -10,6 +10,7 @@ import ( "strconv" "github.com/babylonchain/babylon/test/e2e/initialization" + bbn "github.com/babylonchain/babylon/types" ct "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/stretchr/testify/require" ) @@ -25,6 +26,18 @@ func (s *IntegrationTestSuite) TestConnectIbc() { s.NoError(err) } +func (s *IntegrationTestSuite) TestBTCBaseHeader() { + hardcodedHeader, _ := bbn.NewBTCHeaderBytesFromHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000") + hardcodedHeaderHeight := uint64(0) + + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + baseHeader, err := nonValidatorNode.QueryBtcBaseHeader() + s.True(baseHeader.Hash.Eq(hardcodedHeader.Hash())) + s.Equal(hardcodedHeaderHeight, baseHeader.Height) +} + func (s *IntegrationTestSuite) TestIbcCheckpointing() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(35) From 31d8022e83663e974fccd1d37d00327cb00447b1 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 26 May 2023 17:22:44 +1000 Subject: [PATCH 002/202] init --- test/e2e/e2e_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 3c5e68736..1da2811d9 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -7,7 +7,9 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "math/rand" "strconv" + "time" "github.com/babylonchain/babylon/test/e2e/initialization" bbn "github.com/babylonchain/babylon/types" @@ -38,6 +40,23 @@ func (s *IntegrationTestSuite) TestBTCBaseHeader() { s.Equal(hardcodedHeaderHeight, baseHeader.Height) } +func (s *IntegrationTestSuite) TestSendTx() { + r := rand.New(rand.NewSource(time.Now().Unix())) + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + tip1, err := nonValidatorNode.QueryTip() + s.NoError(err) + + nonValidatorNode.InsertNewEmptyBtcHeader(r) + + tip2, err := nonValidatorNode.QueryTip() + s.NoError(err) + + s.Equal(tip1.Height+1, tip2.Height) +} + func (s *IntegrationTestSuite) TestIbcCheckpointing() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(35) From e43d884492aa34af9491b72e7bb99848d28fdea8 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 29 May 2023 16:17:45 +1000 Subject: [PATCH 003/202] chore: adding a flag for retrieving proofs of a FinalizedChainInfo (#391) --- x/zoneconcierge/client/cli/query.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x/zoneconcierge/client/cli/query.go b/x/zoneconcierge/client/cli/query.go index 270ad106e..2b0c5ad9c 100644 --- a/x/zoneconcierge/client/cli/query.go +++ b/x/zoneconcierge/client/cli/query.go @@ -57,9 +57,11 @@ func CmdFinalizedChainsInfo() *cobra.Command { Short: "retrieve the finalized info for a given list of chains", Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error { + prove, _ := cmd.Flags().GetBool("prove") + clientCtx := client.GetClientContextFromCmd(cmd) queryClient := types.NewQueryClient(clientCtx) - req := types.QueryFinalizedChainsInfoRequest{ChainIds: args} + req := types.QueryFinalizedChainsInfoRequest{ChainIds: args, Prove: prove} resp, err := queryClient.FinalizedChainsInfo(cmd.Context(), &req) if err != nil { return err @@ -69,7 +71,9 @@ func CmdFinalizedChainsInfo() *cobra.Command { }, } + cmd.Flags().Bool("prove", false, "whether to retrieve proofs for each FinalizedChainInfo") flags.AddQueryFlagsToCmd(cmd) + return cmd } From 3882cf38021ee1a206eac88bc6a9741947482da5 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 30 May 2023 15:52:52 +1000 Subject: [PATCH 004/202] rm code for integration test --- .circleci/config.yml | 6 - Makefile | 8 - test/integration_test.go | 375 -------------------------------------- test/utils.go | 385 --------------------------------------- 4 files changed, 774 deletions(-) delete mode 100644 test/integration_test.go delete mode 100644 test/utils.go diff --git a/.circleci/config.yml b/.circleci/config.yml index f2b451306..66edbf9fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,12 +32,6 @@ jobs: name: Run tests command: | make test - - run: # sudo is needed, so that integration test binary have proper access to nodes keyring - name: Run integration tests - command: | - sudo -E env "PATH=$PATH" make localnet-test-integration - # TODO: If CI tests will take to long consider having only this e2e test - # instead of separate integration tests and e2e tests. - run: name: Run e2e tests command: | diff --git a/Makefile b/Makefile index 272a682c1..da30688c6 100644 --- a/Makefile +++ b/Makefile @@ -228,10 +228,6 @@ endif .PHONY: run-tests test test-all $(TEST_TARGETS) -test-integration: - @echo "Running babylon integration test" - @go test github.com/babylonchain/babylon/test -v -count=1 --tags=integration -p 1 - test-e2e: go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e @@ -420,9 +416,6 @@ localnet-start: localnet-stop build-docker localnet-start-nodes localnet-stop: docker-compose down -# localnet-test-integration will spin up a localnet and run integration tests on it -localnet-test-integration: localnet-start test-integration localnet-stop - build-test-wasm: docker run --rm -v "$(WASM_DIR)":/code \ --mount type=volume,source="$(WASM_DIR_BASE_NAME)_cache",target=/code/target \ @@ -437,7 +430,6 @@ build-test-wasm: init-testnet-dirs \ localnet-start-nodes \ localnet-start \ -localnet-test-integration \ localnet-stop .PHONY: diagrams diff --git a/test/integration_test.go b/test/integration_test.go deleted file mode 100644 index c6b4a78bd..000000000 --- a/test/integration_test.go +++ /dev/null @@ -1,375 +0,0 @@ -//go:build integration -// +build integration - -package babylon_integration - -import ( - "context" - "encoding/hex" - "errors" - "fmt" - "math/rand" - "os" - "testing" - "time" - - appparams "github.com/babylonchain/babylon/app/params" - txformat "github.com/babylonchain/babylon/btctxformatter" - "github.com/babylonchain/babylon/testutil/datagen" - bbn "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - lightclient "github.com/babylonchain/babylon/x/btclightclient/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/btcsuite/btcd/chaincfg" - ref "github.com/cosmos/cosmos-sdk/client/grpc/reflection" - tm "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - "google.golang.org/grpc" -) - -// Addresses of all nodes in local testnet. -// TODO: instead of hardcoding them it would be nice to get them from env variables -// so docker-compose and this file would stay compatible -var addresses = []string{ - "localhost:9090", - "localhost:9091", - "localhost:9092", - "localhost:9093", -} - -var clients []*grpc.ClientConn - -func checkInterfacesWithRetries(client *grpc.ClientConn, maxTries int, sleepTime time.Duration) error { - tries := 0 - refClient := ref.NewReflectionServiceClient(client) - for { - _, err := refClient.ListAllInterfaces(context.Background(), &ref.ListAllInterfacesRequest{}) - - if err == nil { - // successful call to client, finish calling - return nil - } - - tries++ - - if tries > maxTries { - return errors.New("Failed to call client") - } - - <-time.After(sleepTime) - } -} - -func allClientsOverBlockNumber(clients []*grpc.ClientConn, blockNumber int64) bool { - for _, c := range clients { - latestResponse, err := tm.NewServiceClient(c).GetLatestBlock(context.Background(), &tm.GetLatestBlockRequest{}) - - if err != nil { - errorString := fmt.Sprintf("Integration tests failed, due to node failure. Erro: %v", err) - panic(errorString) - } - - if latestResponse.Block.Header.Height < blockNumber { - return false - } - } - // we iterated over all clients, and all of them were >= blockNumber - return true -} - -func waitForBlock(clients []*grpc.ClientConn, blockNumber int64) { - for { - allOver := allClientsOverBlockNumber(clients, blockNumber) - - if allOver { - return - } - - <-time.After(1 * time.Second) - } -} - -func getCurrentEpoch(conn *grpc.ClientConn) uint64 { - epochingClient := epochingtypes.NewQueryClient(conn) - - currentEpochResponse, err := epochingClient.CurrentEpoch( - context.Background(), - &epochingtypes.QueryCurrentEpochRequest{}, - ) - - if err != nil { - errorString := fmt.Sprintf("Query failed, testnet not running. Error: %v", err) - panic(errorString) - } - - return currentEpochResponse.CurrentEpoch -} - -func TestMain(m *testing.M) { - - // This is needed so that all address prefixes are in Babylon format - appparams.SetAddressPrefixes() - - for _, addr := range addresses { - grpcConn, err := grpc.Dial( - addr, // Or your gRPC server address. - grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanism. - ) - - if err != nil { - panic("Grpc connection failed cannot perform integration tests") - } - - clients = append(clients, grpcConn) - } - //runs all following tests - exitVal := m.Run() - - for _, c := range clients { - // close all connections after the tests - c.Close() - } - - os.Exit(exitVal) -} - -// This test serves as a waiting point for testnet to start, it is needed as -// docker compose is usually started in detached mode in CI, therefore tests -// are started even before all nodes are up. -// TODO: investigate starting testnet from golang test file. -func TestTestnetRuninng(t *testing.T) { - - for _, c := range clients { - err := checkInterfacesWithRetries(c, 40, 5*time.Second) - - if err != nil { - panic("Could not start integration tests. Testnet not running") - } - } -} - -// Check all nodes are properly initialized to genesis -// TODO ultimatly we would like to check genesis related to all modules here. -func TestBtcLightClientGenesis(t *testing.T) { - // The default testnet directory uses the simnet genesis header as its base - // with height 0. - hardcodedHeader, _ := bbn.NewBTCHeaderBytesFromHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000") - hardcodedHeaderHeight := uint64(0) - - for i, c := range clients { - lc := lightclient.NewQueryClient(c) - - res, err := lc.Tip(context.Background(), lightclient.NewQueryTipRequest()) - - if err != nil { - // this is fatal, as it means we most probably did not get any response and - // at least one of the nodes is down - t.Fatalf("Test failed due to client error: %v to node with address %s", err, addresses[i]) - } - - if res.Header.Height != hardcodedHeaderHeight || !res.Header.Hash.Eq(hardcodedHeader.Hash()) { - t.Errorf("Node with address %s started with unexpected header", addresses[i]) - } - } -} - -func TestNodeProgress(t *testing.T) { - // Waiting for block 7, as tests are configured to run with epoch interval = 5, - // which means that at block 7 all clients will surely be in second epoch or later - waitForBlock(clients, 7) - - for _, c := range clients { - currentEpoch := getCurrentEpoch(c) - if currentEpoch < 2 { - t.Errorf("Epoch after 7 blocks, should be at least larger or equal 2. Current epoch %d", currentEpoch) - } - } -} - -func TestSendTx(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - // TODO fix hard coded paths - node0dataPath := "../.testnets/node0/babylond" - node0genesisPath := "../.testnets/node0/babylond/config/genesis.json" - - sender, err := NewTestTxSender(node0dataPath, node0genesisPath, clients[0]) - - if err != nil { - panic("failed to init sender") - } - tip1 := sender.GetBtcTip() - - err = sender.insertNEmptyBTCHeaders(r, 1) - - if err != nil { - t.Fatalf("could not insert new btc header") - } - - tip2 := sender.GetBtcTip() - - if tip2.Height != tip1.Height+1 { - t.Fatalf("Light client should progress by 1 one block") - } -} - -func TestFailInvalidBTCTransactions(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - // TODO fix hard coded paths - node0dataPath := "../.testnets/node0/babylond" - node0genesisPath := "../.testnets/node0/babylond/config/genesis.json" - - sender, err := NewTestTxSender(node0dataPath, node0genesisPath, clients[0]) - - if err != nil { - panic("failed to init sender") - } - - hInfo := datagen.GenRandomBTCHeaderInfoWithInvalidHeader(r, chaincfg.SimNetParams.PowLimit) - - resp, err := sender.SendBtcHeadersTransaction([]bbn.BTCHeaderBytes{*hInfo.Header}) - - if err != nil { - t.Fatalf("could not insert new btc header") - } - - if resp.TxResponse.Code != 1105 || resp.TxResponse.Codespace != "btclightclient" { - t.Fatalf("submitting invalid header should result with error") - } - - currentTip := sender.GetBtcTip() - - // bogus submissions - firstSubmission := datagen.CreateBlockWithTransaction(r, currentTip.Header.ToBlockHeader(), []byte{1}) - - secondSubmission := datagen.CreateBlockWithTransaction(r, firstSubmission.HeaderBytes.ToBlockHeader(), []byte{1}) - - // At this point light client chain should be 3 long and inserting spv proofs - // should succeed - resp, _ = sender.insertSpvProof(firstSubmission.SpvProof, secondSubmission.SpvProof) - - if resp.TxResponse.Codespace != "btccheckpoint" || resp.TxResponse.Code != 1100 { - t.Fatalf("submitting invalid proof should result with error") - } -} - -func getCheckpoint(t *testing.T, conn *grpc.ClientConn, epoch uint64) *checkpointingtypes.RawCheckpointWithMeta { - queryCheckpoint := checkpointingtypes.NewQueryClient(conn) - - res, err := queryCheckpoint.RawCheckpoint( - context.Background(), - checkpointingtypes.NewQueryRawCheckpointRequest(epoch), - ) - - if err != nil { - t.Fatalf("Failed to retrieve epoch %d", epoch) - } - - return res.RawCheckpoint -} - -func TestSubmitCheckpoint(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - node0dataPath := "../.testnets/node0/babylond" - node0genesisPath := "../.testnets/node0/babylond/config/genesis.json" - - // We are at least on 2 epoch due to `TestNodeProgress` test. At this point - // checkpoint for epoch 1 should already be sealed - testEpoch := uint64(1) - - sender, err := NewTestTxSender(node0dataPath, node0genesisPath, clients[0]) - - if err != nil { - panic("failed to init sender") - } - - rawCheckpoint := getCheckpoint(t, clients[0], testEpoch) - - if rawCheckpoint.Status != checkpointingtypes.Sealed { - t.Fatalf("Expected checkpoint for epoch %d to be Sealed", testEpoch) - } - - rawBtcCheckpoint, err := checkpointingtypes.FromRawCkptToBTCCkpt( - rawCheckpoint.Ckpt, - sender.getSenderAddress().Bytes(), - ) - - if err != nil { - t.Fatalf("Could not create raw btc checkpoint from raw chekpoint") - } - - tagAsBytes, _ := hex.DecodeString(btcctypes.DefaultCheckpointTag) - - p1, p2 := txformat.MustEncodeCheckpointData( - txformat.BabylonTag(tagAsBytes), - txformat.CurrentVersion, - rawBtcCheckpoint, - ) - - currentTip := sender.GetBtcTip() - - firstSubmission := datagen.CreateBlockWithTransaction(r, currentTip.Header.ToBlockHeader(), p1) - - secondSubmission := datagen.CreateBlockWithTransaction(r, firstSubmission.HeaderBytes.ToBlockHeader(), p2) - - // first insert all headers - err = sender.insertBTCHeaders( - currentTip.Height, - []bbn.BTCHeaderBytes{firstSubmission.HeaderBytes, secondSubmission.HeaderBytes}, - ) - - if err != nil { - t.Fatalf("Could not insert two headers. Err: %s", err) - } - - // At this point light client chain should be 3 long and inserting spv proofs - // should succeed - checkPointInsertResponse, err := sender.insertSpvProof(firstSubmission.SpvProof, secondSubmission.SpvProof) - - if err != nil { - t.Log(checkPointInsertResponse.TxResponse) - t.Fatalf("failed to send spv proof") - } - - err = WaitForNextBlock(clients[0]) - - if err != nil { - t.Fatalf("failed to wait for next babylon block") - } - - rawCheckpoint = getCheckpoint(t, clients[0], testEpoch) - - if rawCheckpoint.Status != checkpointingtypes.Submitted { - t.Fatalf("Expected checkpoint for epoch %d to be submitted", testEpoch) - } -} - -func TestConfirmCheckpoint(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - node0dataPath := "../.testnets/node0/babylond" - node0genesisPath := "../.testnets/node0/babylond/config/genesis.json" - - // We are at least on 2 epoch due to `TestNodeProgress` test. At this point - // checkpoint for epoch 1 should already be sealed - testEpoch := uint64(1) - - sender, err := NewTestTxSender(node0dataPath, node0genesisPath, clients[0]) - - if err != nil { - panic("failed to init sender") - } - - err = sender.insertNEmptyBTCHeaders(r, 2) - - if err != nil { - t.Fatalf("Could not insert two headers. Err: %s", err) - } - - // Btc light client chain has been extended by 2 blocks, it means that our checkpoint - // should be confirmed at this point - rawCheckpoint := getCheckpoint(t, clients[0], testEpoch) - - if rawCheckpoint.Status != checkpointingtypes.Confirmed { - t.Fatalf("Expected checkpoint for epoch %d to be confirmed", testEpoch) - } - -} diff --git a/test/utils.go b/test/utils.go deleted file mode 100644 index 4223bc7d2..000000000 --- a/test/utils.go +++ /dev/null @@ -1,385 +0,0 @@ -package babylon_integration - -import ( - "context" - "errors" - "fmt" - "math/rand" - "time" - - tm "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - - "github.com/btcsuite/btcd/wire" - "github.com/cometbft/cometbft/types" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - ctypes "github.com/cosmos/cosmos-sdk/types" - txservice "github.com/cosmos/cosmos-sdk/types/tx" - acctypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "google.golang.org/grpc" - - "github.com/babylonchain/babylon/app" - appparams "github.com/babylonchain/babylon/app/params" - "github.com/babylonchain/babylon/testutil/datagen" - bbn "github.com/babylonchain/babylon/types" - btccheckpoint "github.com/babylonchain/babylon/x/btccheckpoint/types" - lightclient "github.com/babylonchain/babylon/x/btclightclient/types" -) - -type TestTxSender struct { - keyring keyring.Keyring - encConfig appparams.EncodingConfig - signerInfo *keyring.Record - chainId string - Conn *grpc.ClientConn -} - -func NewTestTxSender( - keyringPath string, - genesisPath string, - conn *grpc.ClientConn, -) (*TestTxSender, error) { - cfg := app.GetEncodingConfig() - - kb, err := keyring.New("babylond", "test", keyringPath, nil, cfg.Marshaler) - - if err != nil { - return nil, err - } - - genDoc, err := types.GenesisDocFromFile(genesisPath) - - if err != nil { - return nil, err - } - - signer, err := kb.Key("test-spending-key") - - if err != nil { - panic("test-spending-key should be defined for each node in integration test") - } - - signerInfo := signer - - return &TestTxSender{ - keyring: kb, - encConfig: cfg, - signerInfo: signerInfo, - chainId: genDoc.ChainID, - Conn: conn, - }, nil -} - -func (b *TestTxSender) getSenderAddress() ctypes.AccAddress { - addr, err := b.signerInfo.GetAddress() - - if err != nil { - panic("Getting address from sender should always succeed") - } - return addr -} - -func (b *TestTxSender) buildTx(fees string, gas uint64, seqNr uint64, accNumber uint64, msgs ...ctypes.Msg) []byte { - txFactory := tx.Factory{} - - txFactory = txFactory. - WithKeybase(b.keyring). - WithTxConfig(b.encConfig.TxConfig). - WithChainID(b.chainId). - WithFees(fees). - WithGas(gas). - WithSequence(seqNr). - WithAccountNumber(accNumber) - - txb1, _ := txFactory.BuildUnsignedTx(msgs...) - - if err := tx.Sign(txFactory, b.signerInfo.Name, txb1, true); err != nil { - panic("Tx should sign") - } - - txBytes, err := b.encConfig.TxConfig.TxEncoder()(txb1.GetTx()) - - if err != nil { - panic("Tx should encode") - } - - return txBytes -} - -func (b *TestTxSender) SendBtcHeadersTransaction(headers []bbn.BTCHeaderBytes) (*txservice.BroadcastTxResponse, error) { - if len(headers) == 0 { - return nil, errors.New("headers should not be empty") - } - - acc, err := b.getSelfAccount() - - if err != nil { - panic("retrieving sending account must succeed") - } - - address := b.getSenderAddress() - - var msgs []ctypes.Msg - var fees uint64 - var gas uint64 - - for _, header := range headers { - msg, err := lightclient.NewMsgInsertHeader(address, header.MarshalHex()) - - if err != nil { - panic("creating new header message must succeed ") - } - - msgs = append(msgs, msg) - - fees = fees + 3 - gas = gas + 300000 - } - - feesString := fmt.Sprintf("%d%s", fees, appparams.DefaultBondDenom) - - txBytes := b.buildTx(feesString, gas, acc.GetSequence(), acc.GetAccountNumber(), msgs...) - - req := txservice.BroadcastTxRequest{TxBytes: txBytes, Mode: txservice.BroadcastMode_BROADCAST_MODE_SYNC} - - sender := txservice.NewServiceClient(b.Conn) - - return sender.BroadcastTx(context.Background(), &req) -} - -func GenerateNEmptyHeaders(r *rand.Rand, tip *bbn.BTCHeaderBytes, n uint64) []bbn.BTCHeaderBytes { - var headers []bbn.BTCHeaderBytes - - if n == 0 { - return headers - } - - for i := uint64(0); i < n; i++ { - if i == 0 { - // first new header, need to use tip as base - headers = append(headers, generateEmptyChildHeaderBytes(r, tip)) - } else { - headers = append(headers, generateEmptyChildHeaderBytes(r, &headers[i-1])) - } - } - - return headers -} - -//nolint:unused -func (b *TestTxSender) insertSpvProof(p1 *btccheckpoint.BTCSpvProof, p2 *btccheckpoint.BTCSpvProof) (*txservice.BroadcastTxResponse, error) { - address := b.getSenderAddress() - - msg := btccheckpoint.MsgInsertBTCSpvProof{ - Submitter: address.String(), - Proofs: []*btccheckpoint.BTCSpvProof{p1, p2}, - } - - acc, err := b.getSelfAccount() - - if err != nil { - panic("retrieving sending account must succeed") - } - - fee := fmt.Sprintf("3000%s", appparams.BaseCoinUnit) - txBytes := b.buildTx(fee, 300000, acc.GetSequence(), acc.GetAccountNumber(), &msg) - - req := txservice.BroadcastTxRequest{TxBytes: txBytes, Mode: txservice.BroadcastMode_BROADCAST_MODE_SYNC} - - sender := txservice.NewServiceClient(b.Conn) - - return sender.BroadcastTx(context.Background(), &req) -} - -func (b *TestTxSender) GetBtcTip() *lightclient.BTCHeaderInfo { - lc := lightclient.NewQueryClient(b.Conn) - - res, err := lc.Tip(context.Background(), lightclient.NewQueryTipRequest()) - - if err != nil { - panic("should retrieve btc header") - } - - return res.Header -} - -func (b *TestTxSender) getAccount(addr ctypes.AccAddress) (acctypes.AccountI, error) { - queryClient := acctypes.NewQueryClient(b.Conn) - - res, _ := queryClient.Account( - context.Background(), - &acctypes.QueryAccountRequest{Address: addr.String()}, - ) - - var acc acctypes.AccountI - if err := b.encConfig.InterfaceRegistry.UnpackAny(res.Account, &acc); err != nil { - return nil, err - } - - return acc, nil -} - -func (b *TestTxSender) getSelfAccount() (acctypes.AccountI, error) { - return b.getAccount(b.getSenderAddress()) -} - -//nolint:unused -func (b *TestTxSender) insertBTCHeaders(currentTip uint64, headers []bbn.BTCHeaderBytes) error { - lenHeaders := len(headers) - - if lenHeaders == 0 { - return nil - } - - _, err := b.SendBtcHeadersTransaction(headers) - - if err != nil { - return err - } - - _, err = WaitBtcForHeight(b.Conn, currentTip+uint64(lenHeaders)) - - if err != nil { - return err - } - - return nil -} - -//nolint:unused -func (b *TestTxSender) insertNEmptyBTCHeaders(r *rand.Rand, n uint64) error { - currentTip := b.GetBtcTip() - headers := GenerateNEmptyHeaders(r, currentTip.Header, n) - - err := b.insertBTCHeaders(currentTip.Height, headers) - - if err != nil { - return err - } - - return nil -} - -func generateEmptyChildHeader(r *rand.Rand, bh *wire.BlockHeader) *wire.BlockHeader { - randHeader := datagen.GenRandomBtcdHeader(r) - - randHeader.Version = bh.Version - randHeader.PrevBlock = bh.BlockHash() - randHeader.Bits = bh.Bits - randHeader.Timestamp = bh.Timestamp.Add(50 * time.Second) - datagen.SolveBlock(randHeader) - - return randHeader -} - -func generateEmptyChildHeaderBytes(r *rand.Rand, bh *bbn.BTCHeaderBytes) bbn.BTCHeaderBytes { - childHeader := generateEmptyChildHeader(r, bh.ToBlockHeader()) - return bbn.NewBTCHeaderBytesFromBlockHeader(childHeader) -} - -// TODO following helpers could probably be generalized by taking function -// as param -// Tendermint blockchain helpers - -func LatestHeight(c *grpc.ClientConn) (int64, error) { - latestResponse, err := tm.NewServiceClient(c).GetLatestBlock(context.Background(), &tm.GetLatestBlockRequest{}) - if err != nil { - return 0, err - } - - return latestResponse.SdkBlock.Header.Height, nil //nolint:staticcheck // deprecated call, suggests to use sdk_block instead -} - -func WaitForHeight(c *grpc.ClientConn, h int64) (int64, error) { - return WaitForHeightWithTimeout(c, h, 15*time.Second) -} - -func WaitForHeightWithTimeout(c *grpc.ClientConn, h int64, t time.Duration) (int64, error) { - ticker := time.NewTicker(time.Second) - timeout := time.After(t) - - var latestHeight int64 - - for { - select { - case <-timeout: - ticker.Stop() - return latestHeight, errors.New("timeout exceeded waiting for block") - case <-ticker.C: - latestH, err := LatestHeight(c) - if err == nil { - latestHeight = latestH - if latestHeight >= h { - return latestHeight, nil - } - } - } - } -} - -func WaitForNextBlock(c *grpc.ClientConn) error { - lastBlock, err := LatestHeight(c) - if err != nil { - return err - } - - _, err = WaitForHeight(c, lastBlock+1) - - if err != nil { - return err - } - - return nil -} - -// Btc blockchain helpers - -func BtcLatestHeight(c *grpc.ClientConn) (uint64, error) { - latestResponse, err := lightclient.NewQueryClient(c).Tip(context.Background(), lightclient.NewQueryTipRequest()) - if err != nil { - return 0, err - } - - return latestResponse.Header.Height, nil -} - -func WaitForBtcHeightWithTimeout(c *grpc.ClientConn, h uint64, t time.Duration) (uint64, error) { - ticker := time.NewTicker(time.Second) - timeout := time.After(t) - var latestHeight uint64 - - for { - select { - case <-timeout: - ticker.Stop() - return latestHeight, errors.New("timeout exceeded waiting for btc block") - case <-ticker.C: - latestH, err := BtcLatestHeight(c) - if err == nil { - latestHeight = latestH - if latestHeight >= h { - return latestHeight, nil - } - } - } - } -} - -func WaitBtcForHeight(c *grpc.ClientConn, h uint64) (uint64, error) { - return WaitForBtcHeightWithTimeout(c, h, 15*time.Second) -} - -func WaitForNextBtcBlock(c *grpc.ClientConn) error { - lastBlock, err := BtcLatestHeight(c) - if err != nil { - return err - } - - _, err = WaitBtcForHeight(c, lastBlock+1) - - if err != nil { - return err - } - - return nil -} From 31c20611afbe0996d9f2fca7a8bf41e93f04198e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 31 May 2023 10:13:57 +1000 Subject: [PATCH 005/202] fix ci --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 66edbf9fa..2339772ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,6 +32,10 @@ jobs: name: Run tests command: | make test + - run: + name: Build docker image + command: | + make build-docker - run: name: Run e2e tests command: | From 258be0fc44c381a4032f9596a415ad390a459f80 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 31 May 2023 16:35:00 +1000 Subject: [PATCH 006/202] fix --- .circleci/config.yml | 4 ---- Makefile | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2339772ed..66edbf9fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,10 +32,6 @@ jobs: name: Run tests command: | make test - - run: - name: Build docker image - command: | - make build-docker - run: name: Run e2e tests command: | diff --git a/Makefile b/Makefile index da30688c6..39f00bccf 100644 --- a/Makefile +++ b/Makefile @@ -228,7 +228,7 @@ endif .PHONY: run-tests test test-all $(TEST_TARGETS) -test-e2e: +test-e2e: build-docker go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e test-sim-nondeterminism: From 9964a5a75601dbc20db24faec1827ab2ed6b9448 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Thu, 1 Jun 2023 12:43:23 +0200 Subject: [PATCH 007/202] Bump wasmd to stable (#396) --- go.mod | 10 +++++----- go.sum | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index b7f3020c7..c3bbd0c91 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ go 1.20 module github.com/babylonchain/babylon require ( - github.com/CosmWasm/wasmd v0.40.0-rc.2 + github.com/CosmWasm/wasmd v0.40.0 github.com/btcsuite/btcd v0.23.4 github.com/cometbft/cometbft v0.37.1 github.com/cometbft/cometbft-db v0.7.0 @@ -13,10 +13,10 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 // indirect - github.com/spf13/cast v1.5.0 + github.com/spf13/cast v1.5.1 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/supranational/blst v0.3.8 google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 google.golang.org/grpc v1.54.0 @@ -26,7 +26,7 @@ require ( require ( cosmossdk.io/api v0.3.1 cosmossdk.io/errors v1.0.0-beta.7 - cosmossdk.io/math v1.0.0 + cosmossdk.io/math v1.0.1 cosmossdk.io/tools/rosetta v0.2.1 github.com/CosmWasm/wasmvm v1.2.3 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d @@ -34,7 +34,7 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/gogoproto v1.4.8 - github.com/cosmos/ibc-go/v7 v7.0.0 + github.com/cosmos/ibc-go/v7 v7.0.1 github.com/golang/mock v1.6.0 github.com/jinzhu/copier v0.3.5 github.com/ory/dockertest/v3 v3.9.1 diff --git a/go.sum b/go.sum index 1ddfd2685..f48131951 100644 --- a/go.sum +++ b/go.sum @@ -196,8 +196,8 @@ cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/math v1.0.0 h1:ro9w7eKx23om2tZz/VM2Pf+z2WAbGX1yDQQOJ6iGeJw= -cosmossdk.io/math v1.0.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= +cosmossdk.io/math v1.0.1 h1:Qx3ifyOPaMLNH/89WeZFH268yCvU4xEcnPLu3sJqPPg= +cosmossdk.io/math v1.0.1/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -209,8 +209,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmd v0.40.0-rc.2 h1:UgOr8CaitJ8C8Y80viKLT6mL2Xh4yg2X4szCdTVr6xg= -github.com/CosmWasm/wasmd v0.40.0-rc.2/go.mod h1:l2s42GHKp1CHcR0N6J8P6p02b5RMWFCpcmRjyKMtqqg= +github.com/CosmWasm/wasmd v0.40.0 h1:3Vvq1m8dPQdvZR+QJc86VIx6QoWAEVnuHxqBPshJvyo= +github.com/CosmWasm/wasmd v0.40.0/go.mod h1:SuxskRBB7+bpwXGhUXaEfdpjg5WKpdxBy7Tm36VRMUU= github.com/CosmWasm/wasmvm v1.2.3 h1:OKYlobwmVGbl0eSn0mXoAAjE5hIuXnQCLPjbNd91sVY= github.com/CosmWasm/wasmvm v1.2.3/go.mod h1:vW/E3h8j9xBQs9bCoijDuawKo9kCtxOaS8N8J7KFtkc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -450,7 +450,7 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -952,8 +952,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= @@ -986,8 +986,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.8 h1:glwLF4oBRSJOTr05lRBgNwGQST0ndP2wg29fSeTRKCY= From 08bb69d1882a5c566e97787f3dc222201275a899 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 14 Jun 2023 11:07:11 +1000 Subject: [PATCH 008/202] btcstaking: add BTC staking tracker and finality tracker module (#1) --- app/app.go | 35 ++ go.mod | 24 +- go.sum | 46 +- proto/babylon/btcstaking/v1/genesis.proto | 12 + proto/babylon/btcstaking/v1/params.proto | 11 + proto/babylon/btcstaking/v1/query.proto | 25 + proto/babylon/btcstaking/v1/tx.proto | 7 + proto/babylon/finality/v1/genesis.proto | 12 + proto/babylon/finality/v1/params.proto | 11 + proto/babylon/finality/v1/query.proto | 25 + proto/babylon/finality/v1/tx.proto | 7 + testutil/keeper/btcstaking.go | 54 +++ testutil/keeper/finality.go | 55 +++ x/btcstaking/client/cli/query.go | 25 + x/btcstaking/client/cli/query_params.go | 36 ++ x/btcstaking/client/cli/tx.go | 27 ++ x/btcstaking/genesis.go | 20 + x/btcstaking/genesis_test.go | 25 + x/btcstaking/keeper/keeper.go | 54 +++ x/btcstaking/keeper/msg_server.go | 17 + x/btcstaking/keeper/msg_server_test.go | 23 + x/btcstaking/keeper/params.go | 16 + x/btcstaking/keeper/params_test.go | 18 + x/btcstaking/keeper/query.go | 7 + x/btcstaking/keeper/query_params.go | 19 + x/btcstaking/keeper/query_params_test.go | 21 + x/btcstaking/module.go | 147 ++++++ x/btcstaking/module_simulation.go | 57 +++ x/btcstaking/simulation/helpers.go | 15 + x/btcstaking/types/codec.go | 22 + x/btcstaking/types/errors.go | 10 + x/btcstaking/types/expected_keepers.go | 18 + x/btcstaking/types/genesis.go | 19 + x/btcstaking/types/genesis.pb.go | 323 +++++++++++++ x/btcstaking/types/genesis_test.go | 37 ++ x/btcstaking/types/keys.go | 19 + x/btcstaking/types/params.go | 39 ++ x/btcstaking/types/params.pb.go | 266 +++++++++++ x/btcstaking/types/query.pb.go | 535 ++++++++++++++++++++++ x/btcstaking/types/query.pb.gw.go | 153 +++++++ x/btcstaking/types/tx.pb.go | 81 ++++ x/btcstaking/types/types.go | 1 + x/finality/client/cli/query.go | 25 + x/finality/client/cli/query_params.go | 36 ++ x/finality/client/cli/tx.go | 27 ++ x/finality/genesis.go | 20 + x/finality/genesis_test.go | 26 ++ x/finality/keeper/keeper.go | 57 +++ x/finality/keeper/msg_server.go | 17 + x/finality/keeper/msg_server_test.go | 23 + x/finality/keeper/params.go | 16 + x/finality/keeper/params_test.go | 18 + x/finality/keeper/query.go | 7 + x/finality/keeper/query_params.go | 19 + x/finality/keeper/query_params_test.go | 21 + x/finality/module.go | 147 ++++++ x/finality/module_simulation.go | 57 +++ x/finality/simulation/helpers.go | 15 + x/finality/types/codec.go | 22 + x/finality/types/errors.go | 10 + x/finality/types/expected_keepers.go | 22 + x/finality/types/genesis.go | 19 + x/finality/types/genesis.pb.go | 321 +++++++++++++ x/finality/types/genesis_test.go | 37 ++ x/finality/types/keys.go | 19 + x/finality/types/params.go | 39 ++ x/finality/types/params.pb.go | 264 +++++++++++ x/finality/types/query.pb.go | 535 ++++++++++++++++++++++ x/finality/types/query.pb.gw.go | 153 +++++++ x/finality/types/tx.pb.go | 81 ++++ x/finality/types/types.go | 1 + 71 files changed, 4396 insertions(+), 32 deletions(-) create mode 100644 proto/babylon/btcstaking/v1/genesis.proto create mode 100644 proto/babylon/btcstaking/v1/params.proto create mode 100644 proto/babylon/btcstaking/v1/query.proto create mode 100644 proto/babylon/btcstaking/v1/tx.proto create mode 100644 proto/babylon/finality/v1/genesis.proto create mode 100644 proto/babylon/finality/v1/params.proto create mode 100644 proto/babylon/finality/v1/query.proto create mode 100644 proto/babylon/finality/v1/tx.proto create mode 100644 testutil/keeper/btcstaking.go create mode 100644 testutil/keeper/finality.go create mode 100644 x/btcstaking/client/cli/query.go create mode 100644 x/btcstaking/client/cli/query_params.go create mode 100644 x/btcstaking/client/cli/tx.go create mode 100644 x/btcstaking/genesis.go create mode 100644 x/btcstaking/genesis_test.go create mode 100644 x/btcstaking/keeper/keeper.go create mode 100644 x/btcstaking/keeper/msg_server.go create mode 100644 x/btcstaking/keeper/msg_server_test.go create mode 100644 x/btcstaking/keeper/params.go create mode 100644 x/btcstaking/keeper/params_test.go create mode 100644 x/btcstaking/keeper/query.go create mode 100644 x/btcstaking/keeper/query_params.go create mode 100644 x/btcstaking/keeper/query_params_test.go create mode 100644 x/btcstaking/module.go create mode 100644 x/btcstaking/module_simulation.go create mode 100644 x/btcstaking/simulation/helpers.go create mode 100644 x/btcstaking/types/codec.go create mode 100644 x/btcstaking/types/errors.go create mode 100644 x/btcstaking/types/expected_keepers.go create mode 100644 x/btcstaking/types/genesis.go create mode 100644 x/btcstaking/types/genesis.pb.go create mode 100644 x/btcstaking/types/genesis_test.go create mode 100644 x/btcstaking/types/keys.go create mode 100644 x/btcstaking/types/params.go create mode 100644 x/btcstaking/types/params.pb.go create mode 100644 x/btcstaking/types/query.pb.go create mode 100644 x/btcstaking/types/query.pb.gw.go create mode 100644 x/btcstaking/types/tx.pb.go create mode 100644 x/btcstaking/types/types.go create mode 100644 x/finality/client/cli/query.go create mode 100644 x/finality/client/cli/query_params.go create mode 100644 x/finality/client/cli/tx.go create mode 100644 x/finality/genesis.go create mode 100644 x/finality/genesis_test.go create mode 100644 x/finality/keeper/keeper.go create mode 100644 x/finality/keeper/msg_server.go create mode 100644 x/finality/keeper/msg_server_test.go create mode 100644 x/finality/keeper/params.go create mode 100644 x/finality/keeper/params_test.go create mode 100644 x/finality/keeper/query.go create mode 100644 x/finality/keeper/query_params.go create mode 100644 x/finality/keeper/query_params_test.go create mode 100644 x/finality/module.go create mode 100644 x/finality/module_simulation.go create mode 100644 x/finality/simulation/helpers.go create mode 100644 x/finality/types/codec.go create mode 100644 x/finality/types/errors.go create mode 100644 x/finality/types/expected_keepers.go create mode 100644 x/finality/types/genesis.go create mode 100644 x/finality/types/genesis.pb.go create mode 100644 x/finality/types/genesis_test.go create mode 100644 x/finality/types/keys.go create mode 100644 x/finality/types/params.go create mode 100644 x/finality/types/params.pb.go create mode 100644 x/finality/types/query.pb.go create mode 100644 x/finality/types/query.pb.gw.go create mode 100644 x/finality/types/tx.pb.go create mode 100644 x/finality/types/types.go diff --git a/app/app.go b/app/app.go index c878a001f..23ba0990d 100644 --- a/app/app.go +++ b/app/app.go @@ -108,12 +108,18 @@ import ( "github.com/babylonchain/babylon/x/btclightclient" btclightclientkeeper "github.com/babylonchain/babylon/x/btclightclient/keeper" btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking" + btcstakingkeeper "github.com/babylonchain/babylon/x/btcstaking/keeper" + btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/checkpointing" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching" epochingkeeper "github.com/babylonchain/babylon/x/epoching/keeper" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + "github.com/babylonchain/babylon/x/finality" + finalitykeeper "github.com/babylonchain/babylon/x/finality/keeper" + finalitytypes "github.com/babylonchain/babylon/x/finality/types" "github.com/babylonchain/babylon/x/monitor" monitorkeeper "github.com/babylonchain/babylon/x/monitor/keeper" monitortypes "github.com/babylonchain/babylon/x/monitor/types" @@ -318,6 +324,10 @@ type BabylonApp struct { TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers ZoneConciergeKeeper zckeeper.Keeper // for cross-chain fungible token transfers + // BTC staking related modules + BTCStakingKeeper btcstakingkeeper.Keeper + FinalityKeeper finalitykeeper.Keeper + WasmKeeper wasm.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -386,6 +396,10 @@ func NewBabylonApp( ibctransfertypes.StoreKey, ibcfeetypes.StoreKey, zctypes.StoreKey, + // BTC staking related modules + btcstakingtypes.StoreKey, + finalitytypes.StoreKey, + // WASM wasm.StoreKey, ) @@ -595,6 +609,15 @@ func NewBabylonApp( &btclightclientKeeper, ) + // set up BTC staking keeper + app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( + appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.GetSubspace(btcstakingtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + ) + app.FinalityKeeper = finalitykeeper.NewKeeper( + appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.GetSubspace(finalitytypes.ModuleName), app.AccountKeeper, app.BankKeeper, + app.BTCStakingKeeper, + ) + // add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter()) // make ZoneConcierge to subscribe to the epoching's hooks @@ -717,6 +740,9 @@ func NewBabylonApp( transfer.NewAppModule(app.TransferKeeper), zoneconcierge.NewAppModule(appCodec, app.ZoneConciergeKeeper, app.AccountKeeper, app.BankKeeper), ibcfee.NewAppModule(app.IBCFeeKeeper), + // BTC staking related modules + btcstaking.NewAppModule(appCodec, app.BTCStakingKeeper, app.AccountKeeper, app.BankKeeper), + finality.NewAppModule(appCodec, app.FinalityKeeper, app.AccountKeeper, app.BankKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -742,6 +768,9 @@ func NewBabylonApp( zctypes.ModuleName, ibcfeetypes.ModuleName, wasm.ModuleName, + // BTC staking related modules + btcstakingtypes.ModuleName, + finalitytypes.ModuleName, ) // TODO: there will be an architecture design on whether to modify slashing/evidence, specifically // - how many validators can we slash in a single epoch and @@ -767,6 +796,9 @@ func NewBabylonApp( zctypes.ModuleName, ibcfeetypes.ModuleName, wasm.ModuleName, + // BTC staking related modules + btcstakingtypes.ModuleName, + finalitytypes.ModuleName, ) // Babylon does not want EndBlock processing in staking app.mm.OrderEndBlockers = append(app.mm.OrderEndBlockers[:2], app.mm.OrderEndBlockers[2+1:]...) // remove stakingtypes.ModuleName @@ -794,6 +826,9 @@ func NewBabylonApp( zctypes.ModuleName, ibcfeetypes.ModuleName, wasm.ModuleName, + // BTC staking related modules + btcstakingtypes.ModuleName, + finalitytypes.ModuleName, ) // Uncomment if you want to set a custom migration order here. diff --git a/go.mod b/go.mod index c3bbd0c91..1ff140e14 100644 --- a/go.mod +++ b/go.mod @@ -18,9 +18,9 @@ require ( github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.3 github.com/supranational/blst v0.3.8 - google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 - google.golang.org/grpc v1.54.0 - gopkg.in/yaml.v2 v2.4.0 // indirect + google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/grpc v1.55.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -38,6 +38,7 @@ require ( github.com/golang/mock v1.6.0 github.com/jinzhu/copier v0.3.5 github.com/ory/dockertest/v3 v3.9.1 + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc google.golang.org/protobuf v1.30.0 ) @@ -113,8 +114,8 @@ require ( github.com/zondax/hid v0.9.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect golang.org/x/crypto v0.7.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/term v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect nhooyr.io/websocket v1.8.6 // indirect @@ -122,9 +123,9 @@ require ( require ( cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute v1.19.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/storage v1.29.0 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.3 // indirect @@ -161,7 +162,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -188,12 +189,13 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.110.0 // indirect + google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect pgregory.net/rapid v0.5.5 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index f48131951..b2c5eaf9c 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -111,13 +111,13 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -617,8 +617,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1177,8 +1177,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1204,8 +1204,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1325,13 +1325,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1470,8 +1470,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1589,8 +1589,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 h1:EfLuoKW5WfkgVdDy7dTK8qSbH37AX5mj/MFh+bGPz14= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1632,8 +1636,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/proto/babylon/btcstaking/v1/genesis.proto b/proto/babylon/btcstaking/v1/genesis.proto new file mode 100644 index 000000000..406cba75a --- /dev/null +++ b/proto/babylon/btcstaking/v1/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "gogoproto/gogo.proto"; +import "babylon/btcstaking/v1/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// GenesisState defines the btcstaking module's genesis state. +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto new file mode 100644 index 000000000..6cbf4ed44 --- /dev/null +++ b/proto/babylon/btcstaking/v1/params.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// Params defines the parameters for the module. +message Params { + option (gogoproto.goproto_stringer) = false; +} diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto new file mode 100644 index 000000000..1024a068f --- /dev/null +++ b/proto/babylon/btcstaking/v1/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "babylon/btcstaking/v1/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// Query defines the gRPC querier service. +service Query { + // Parameters queries the parameters of the module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/params"; + } +} + +// QueryParamsRequest is request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is response type for the Query/Params RPC method. +message QueryParamsResponse { + // params holds all the parameters of this module. + Params params = 1 [(gogoproto.nullable) = false]; +} \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto new file mode 100644 index 000000000..ed0673580 --- /dev/null +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// Msg defines the Msg service. +service Msg {} \ No newline at end of file diff --git a/proto/babylon/finality/v1/genesis.proto b/proto/babylon/finality/v1/genesis.proto new file mode 100644 index 000000000..f33e6ed15 --- /dev/null +++ b/proto/babylon/finality/v1/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package babylon.finality.v1; + +import "gogoproto/gogo.proto"; +import "babylon/finality/v1/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// GenesisState defines the finality module's genesis state. +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/babylon/finality/v1/params.proto b/proto/babylon/finality/v1/params.proto new file mode 100644 index 000000000..d317ece41 --- /dev/null +++ b/proto/babylon/finality/v1/params.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package babylon.finality.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// Params defines the parameters for the module. +message Params { + option (gogoproto.goproto_stringer) = false; +} diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto new file mode 100644 index 000000000..7c601cf6e --- /dev/null +++ b/proto/babylon/finality/v1/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package babylon.finality.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "babylon/finality/v1/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// Query defines the gRPC querier service. +service Query { + // Parameters queries the parameters of the module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/babylonchain/babylon/finality/v1/params"; + } +} + +// QueryParamsRequest is request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is response type for the Query/Params RPC method. +message QueryParamsResponse { + // params holds all the parameters of this module. + Params params = 1 [(gogoproto.nullable) = false]; +} \ No newline at end of file diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto new file mode 100644 index 000000000..3b6498403 --- /dev/null +++ b/proto/babylon/finality/v1/tx.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package babylon.finality.v1; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// Msg defines the Msg service. +service Msg {} \ No newline at end of file diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go new file mode 100644 index 000000000..23956f39f --- /dev/null +++ b/testutil/keeper/btcstaking.go @@ -0,0 +1,54 @@ +package keeper + +import ( + "testing" + + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + tmdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + typesparams "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/require" +) + +func BTCStakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { + storeKey := sdk.NewKVStoreKey(types.StoreKey) + memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + paramsSubspace := typesparams.NewSubspace(cdc, + types.Amino, + storeKey, + memStoreKey, + "BtcstakingParams", + ) + k := keeper.NewKeeper( + cdc, + storeKey, + memStoreKey, + paramsSubspace, + nil, + nil, + ) + + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + // Initialize params + k.SetParams(ctx, types.DefaultParams()) + + return &k, ctx +} diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go new file mode 100644 index 000000000..369930d68 --- /dev/null +++ b/testutil/keeper/finality.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "testing" + + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + tmdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + typesparams "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/require" +) + +func FinalityKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { + storeKey := sdk.NewKVStoreKey(types.StoreKey) + memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + paramsSubspace := typesparams.NewSubspace(cdc, + types.Amino, + storeKey, + memStoreKey, + "FinalityParams", + ) + k := keeper.NewKeeper( + cdc, + storeKey, + memStoreKey, + paramsSubspace, + nil, + nil, + nil, + ) + + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + // Initialize params + k.SetParams(ctx, types.DefaultParams()) + + return &k, ctx +} diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go new file mode 100644 index 000000000..754ae7bbc --- /dev/null +++ b/x/btcstaking/client/cli/query.go @@ -0,0 +1,25 @@ +package cli + +import ( + "fmt" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd(queryRoute string) *cobra.Command { + // Group btcstaking queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(CmdQueryParams()) + + return cmd +} diff --git a/x/btcstaking/client/cli/query_params.go b/x/btcstaking/client/cli/query_params.go new file mode 100644 index 000000000..1672274c1 --- /dev/null +++ b/x/btcstaking/client/cli/query_params.go @@ -0,0 +1,36 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/babylonchain/babylon/x/btcstaking/types" +) + +func CmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "shows the parameters of the module", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go new file mode 100644 index 000000000..0f60362c1 --- /dev/null +++ b/x/btcstaking/client/cli/tx.go @@ -0,0 +1,27 @@ +package cli + +import ( + "fmt" + "time" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +var ( + DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + return cmd +} diff --git a/x/btcstaking/genesis.go b/x/btcstaking/genesis.go new file mode 100644 index 000000000..26bfa29ca --- /dev/null +++ b/x/btcstaking/genesis.go @@ -0,0 +1,20 @@ +package btcstaking + +import ( + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.SetParams(ctx, genState.Params) +} + +// ExportGenesis returns the module's exported genesis +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + + return genesis +} diff --git a/x/btcstaking/genesis_test.go b/x/btcstaking/genesis_test.go new file mode 100644 index 000000000..72171ca49 --- /dev/null +++ b/x/btcstaking/genesis_test.go @@ -0,0 +1,25 @@ +package btcstaking_test + +import ( + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/testutil/nullify" + "github.com/babylonchain/babylon/x/btcstaking" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/stretchr/testify/require" +) + +func TestGenesis(t *testing.T) { + genesisState := types.GenesisState{ + Params: types.DefaultParams(), + } + + k, ctx := keepertest.BTCStakingKeeper(t) + btcstaking.InitGenesis(ctx, *k, genesisState) + got := btcstaking.ExportGenesis(ctx, *k) + require.NotNil(t, got) + + nullify.Fill(&genesisState) + nullify.Fill(got) +} diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go new file mode 100644 index 000000000..08ed06e3d --- /dev/null +++ b/x/btcstaking/keeper/keeper.go @@ -0,0 +1,54 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/babylonchain/babylon/x/btcstaking/types" +) + +type ( + Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey + paramstore paramtypes.Subspace + + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + } +) + +func NewKeeper( + cdc codec.BinaryCodec, + storeKey, + memKey storetypes.StoreKey, + ps paramtypes.Subspace, + + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper { + // set KeyTable if it has not already been set + if !ps.HasKeyTable() { + ps = ps.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + cdc: cdc, + storeKey: storeKey, + memKey: memKey, + paramstore: ps, + + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go new file mode 100644 index 000000000..720f56c04 --- /dev/null +++ b/x/btcstaking/keeper/msg_server.go @@ -0,0 +1,17 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btcstaking/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go new file mode 100644 index 000000000..aaef746f8 --- /dev/null +++ b/x/btcstaking/keeper/msg_server_test.go @@ -0,0 +1,23 @@ +package keeper_test + +import ( + "context" + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { + k, ctx := keepertest.BTCStakingKeeper(t) + return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) +} + +func TestMsgServer(t *testing.T) { + ms, ctx := setupMsgServer(t) + require.NotNil(t, ms) + require.NotNil(t, ctx) +} diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go new file mode 100644 index 000000000..2966f4e36 --- /dev/null +++ b/x/btcstaking/keeper/params.go @@ -0,0 +1,16 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetParams get all parameters as types.Params +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + return types.NewParams() +} + +// SetParams set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramstore.SetParamSet(ctx, ¶ms) +} diff --git a/x/btcstaking/keeper/params_test.go b/x/btcstaking/keeper/params_test.go new file mode 100644 index 000000000..706a65f14 --- /dev/null +++ b/x/btcstaking/keeper/params_test.go @@ -0,0 +1,18 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/stretchr/testify/require" +) + +func TestGetParams(t *testing.T) { + k, ctx := testkeeper.BTCStakingKeeper(t) + params := types.DefaultParams() + + k.SetParams(ctx, params) + + require.EqualValues(t, params, k.GetParams(ctx)) +} diff --git a/x/btcstaking/keeper/query.go b/x/btcstaking/keeper/query.go new file mode 100644 index 000000000..7a46601f8 --- /dev/null +++ b/x/btcstaking/keeper/query.go @@ -0,0 +1,7 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btcstaking/types" +) + +var _ types.QueryServer = Keeper{} diff --git a/x/btcstaking/keeper/query_params.go b/x/btcstaking/keeper/query_params.go new file mode 100644 index 000000000..1dda6409c --- /dev/null +++ b/x/btcstaking/keeper/query_params.go @@ -0,0 +1,19 @@ +package keeper + +import ( + "context" + + "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go new file mode 100644 index 000000000..ad75513ef --- /dev/null +++ b/x/btcstaking/keeper/query_params_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestParamsQuery(t *testing.T) { + keeper, ctx := testkeeper.BTCStakingKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + params := types.DefaultParams() + keeper.SetParams(ctx, params) + + response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + require.NoError(t, err) + require.Equal(t, &types.QueryParamsResponse{Params: params}, response) +} diff --git a/x/btcstaking/module.go b/x/btcstaking/module.go new file mode 100644 index 000000000..a7f09b949 --- /dev/null +++ b/x/btcstaking/module.go @@ -0,0 +1,147 @@ +package btcstaking + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/babylonchain/babylon/x/btcstaking/client/cli" + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns a default GenesisState for the module, marshalled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck // generally we don't handle errors here +} + +// GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to generate new transactions containing messages defined in the module +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd(types.StoreKey) +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the module's genesis initialization. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(gs, &genState) + + InitGenesis(ctx, am.keeper, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock contains the logic that is automatically triggered at the beginning of each block +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock contains the logic that is automatically triggered at the end of each block +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/btcstaking/module_simulation.go b/x/btcstaking/module_simulation.go new file mode 100644 index 000000000..b13d1bdf3 --- /dev/null +++ b/x/btcstaking/module_simulation.go @@ -0,0 +1,57 @@ +package btcstaking + +import ( + "math/rand" + + "github.com/babylonchain/babylon/testutil/sample" + btcstakingsimulation "github.com/babylonchain/babylon/x/btcstaking/simulation" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// avoid unused import issue +var ( + _ = sample.AccAddress + _ = btcstakingsimulation.FindAccount + _ = simulation.MsgEntryKind + _ = baseapp.Paramspace + _ = rand.Rand{} +) + +const () + +// GenerateGenesisState creates a randomized GenState of the module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + accs := make([]string, len(simState.Accounts)) + for i, acc := range simState.Accounts { + accs[i] = acc.Address.String() + } + btcstakingGenesis := types.GenesisState{ + Params: types.DefaultParams(), + } + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&btcstakingGenesis) +} + +// RegisterStoreDecoder registers a decoder. +func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} + +// ProposalContents doesn't return any content functions for governance proposals +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { + return nil +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + operations := make([]simtypes.WeightedOperation, 0) + + return operations +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{} +} diff --git a/x/btcstaking/simulation/helpers.go b/x/btcstaking/simulation/helpers.go new file mode 100644 index 000000000..92c437c0d --- /dev/null +++ b/x/btcstaking/simulation/helpers.go @@ -0,0 +1,15 @@ +package simulation + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// FindAccount find a specific address from an account list +func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { + creator, err := sdk.AccAddressFromBech32(address) + if err != nil { + panic(err) + } + return simtypes.FindAccount(accs, creator) +} diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go new file mode 100644 index 000000000..858010906 --- /dev/null +++ b/x/btcstaking/types/codec.go @@ -0,0 +1,22 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { + +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) +) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go new file mode 100644 index 000000000..1c04c70db --- /dev/null +++ b/x/btcstaking/types/errors.go @@ -0,0 +1,10 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// x/btcstaking module sentinel errors +var ( + ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") +) diff --git a/x/btcstaking/types/expected_keepers.go b/x/btcstaking/types/expected_keepers.go new file mode 100644 index 000000000..6aa6e9778 --- /dev/null +++ b/x/btcstaking/types/expected_keepers.go @@ -0,0 +1,18 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI + // Methods imported from account should be defined here +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + // Methods imported from bank should be defined here +} diff --git a/x/btcstaking/types/genesis.go b/x/btcstaking/types/genesis.go new file mode 100644 index 000000000..97cad7616 --- /dev/null +++ b/x/btcstaking/types/genesis.go @@ -0,0 +1,19 @@ +package types + +// DefaultIndex is the default global index +const DefaultIndex uint64 = 1 + +// DefaultGenesis returns the default genesis state +func DefaultGenesis() *GenesisState { + return &GenesisState{ + + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + + return gs.Params.Validate() +} diff --git a/x/btcstaking/types/genesis.pb.go b/x/btcstaking/types/genesis.pb.go new file mode 100644 index 000000000..2ccb1d1d9 --- /dev/null +++ b/x/btcstaking/types/genesis.pb.go @@ -0,0 +1,323 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the btcstaking module's genesis state. +type GenesisState struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_85d7b95fa5620238, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "babylon.btcstaking.v1.GenesisState") +} + +func init() { + proto.RegisterFile("babylon/btcstaking/v1/genesis.proto", fileDescriptor_85d7b95fa5620238) +} + +var fileDescriptor_85d7b95fa5620238 = []byte{ + // 202 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4e, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, + 0x12, 0x85, 0x2a, 0xd2, 0x43, 0x28, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, + 0xab, 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x94, 0xb0, 0x9b, 0x58, 0x90, 0x58, 0x94, 0x98, 0x0b, + 0x35, 0x50, 0xc9, 0x9b, 0x8b, 0xc7, 0x1d, 0x62, 0x43, 0x70, 0x49, 0x62, 0x49, 0xaa, 0x90, 0x35, + 0x17, 0x1b, 0x44, 0x5e, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x56, 0x0f, 0xab, 0x8d, 0x7a, + 0x01, 0x60, 0x45, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0xb5, 0x38, 0xf9, 0x9c, 0x78, + 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, + 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x51, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, + 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0xc0, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x18, 0x47, 0xbf, 0x02, + 0xd9, 0x91, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x17, 0x1a, 0x03, 0x02, 0x00, 0x00, + 0xff, 0xff, 0x64, 0xf3, 0x4d, 0xdf, 0x19, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go new file mode 100644 index 000000000..966ba2b6f --- /dev/null +++ b/x/btcstaking/types/genesis_test.go @@ -0,0 +1,37 @@ +package types_test + +import ( + "testing" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/stretchr/testify/require" +) + +func TestGenesisState_Validate(t *testing.T) { + tests := []struct { + desc string + genState *types.GenesisState + valid bool + }{ + { + desc: "default is valid", + genState: types.DefaultGenesis(), + valid: true, + }, + { + desc: "valid genesis state", + genState: &types.GenesisState{}, + valid: true, + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + err := tc.genState.Validate() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go new file mode 100644 index 000000000..9f5376768 --- /dev/null +++ b/x/btcstaking/types/keys.go @@ -0,0 +1,19 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "btcstaking" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // RouterKey defines the module's message routing key + RouterKey = ModuleName + + // MemStoreKey defines the in-memory store key + MemStoreKey = "mem_btcstaking" +) + +func KeyPrefix(p string) []byte { + return []byte(p) +} diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go new file mode 100644 index 000000000..357196ad6 --- /dev/null +++ b/x/btcstaking/types/params.go @@ -0,0 +1,39 @@ +package types + +import ( + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "gopkg.in/yaml.v2" +) + +var _ paramtypes.ParamSet = (*Params)(nil) + +// ParamKeyTable the param key table for launch module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// NewParams creates a new Params instance +func NewParams() Params { + return Params{} +} + +// DefaultParams returns a default set of parameters +func DefaultParams() Params { + return NewParams() +} + +// ParamSetPairs get the params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{} +} + +// Validate validates the set of params +func (p Params) Validate() error { + return nil +} + +// String implements the Stringer interface. +func (p Params) String() string { + out, _ := yaml.Marshal(p) + return string(out) +} diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go new file mode 100644 index 000000000..78a64c37c --- /dev/null +++ b/x/btcstaking/types/params.pb.go @@ -0,0 +1,266 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the module. +type Params struct { +} + +func (m *Params) Reset() { *m = Params{} } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_8d1392776a3e15b9, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Params)(nil), "babylon.btcstaking.v1.Params") +} + +func init() { + proto.RegisterFile("babylon/btcstaking/v1/params.proto", fileDescriptor_8d1392776a3e15b9) +} + +var fileDescriptor_8d1392776a3e15b9 = []byte{ + // 160 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, + 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, + 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0xf8, 0xb8, 0xd8, 0x02, 0xc0, 0x9a, 0xad, 0x58, 0x66, 0x2c, + 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, + 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xa3, + 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x0d, 0xc9, 0x19, 0x89, + 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0xb2, 0xa3, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, + 0x96, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xaf, 0xe9, 0x04, 0xb7, 0x00, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go new file mode 100644 index 000000000..39b43d5a3 --- /dev/null +++ b/x/btcstaking/types/query.pb.go @@ -0,0 +1,535 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params holds all the parameters of this module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btcstaking.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") +} + +func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } + +var fileDescriptor_74d49d26f7429697 = []byte{ + // 282 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, + 0x2a, 0xd1, 0x43, 0x28, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, 0xd0, + 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x64, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x13, 0x0b, 0x32, + 0xf5, 0x13, 0xf3, 0xf2, 0xf2, 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0xa1, 0xb2, 0x4a, 0xd8, + 0x6d, 0x2b, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0xaa, 0x51, 0x12, 0xe1, 0x12, 0x0a, 0x04, 0xd9, 0x1e, + 0x00, 0x16, 0x0c, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x51, 0x0a, 0xe2, 0x12, 0x46, 0x11, 0x2d, + 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0xb2, 0xe6, 0x62, 0x83, 0x68, 0x96, 0x60, 0x54, 0x60, 0xd4, + 0xe0, 0x36, 0x92, 0xd5, 0xc3, 0xea, 0x58, 0x3d, 0x88, 0x36, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, + 0x82, 0xa0, 0x5a, 0x8c, 0xe6, 0x32, 0x72, 0xb1, 0x82, 0x0d, 0x15, 0x9a, 0xcc, 0xc8, 0xc5, 0x06, + 0x51, 0x22, 0xa4, 0x89, 0xc3, 0x04, 0x4c, 0x37, 0x49, 0x69, 0x11, 0xa3, 0x14, 0xe2, 0x50, 0x25, + 0xa3, 0xa6, 0xcb, 0x4f, 0x26, 0x33, 0xe9, 0x08, 0x69, 0xe9, 0x43, 0xf5, 0x24, 0x67, 0x24, 0x66, + 0xe6, 0xe9, 0xe3, 0x0b, 0x0f, 0x27, 0x9f, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, + 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, + 0x88, 0x32, 0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xc5, 0x6e, 0x5e, 0x05, + 0xb2, 0x89, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xe0, 0x35, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x63, 0x6e, 0x35, 0xce, 0xf2, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Parameters queries the parameters of the module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Parameters queries the parameters of the module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.btcstaking.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/btcstaking/v1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go new file mode 100644 index 000000000..bed63f45f --- /dev/null +++ b/x/btcstaking/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: babylon/btcstaking/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go new file mode 100644 index 000000000..eddbdf6c8 --- /dev/null +++ b/x/btcstaking/types/tx.pb.go @@ -0,0 +1,81 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } + +var fileDescriptor_4baddb53e97f38f2 = []byte{ + // 137 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xca, 0xeb, 0x21, + 0xe4, 0xf5, 0xca, 0x0c, 0x8d, 0x58, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0x7c, 0x4e, 0x3c, 0x92, + 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, + 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x28, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, + 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x44, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x6c, + 0x63, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x4a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x8c, 0xcc, 0x21, 0x9b, 0x94, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.btcstaking.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/btcstaking/v1/tx.proto", +} diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go new file mode 100644 index 000000000..ab1254f4c --- /dev/null +++ b/x/btcstaking/types/types.go @@ -0,0 +1 @@ +package types diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go new file mode 100644 index 000000000..4df0208f9 --- /dev/null +++ b/x/finality/client/cli/query.go @@ -0,0 +1,25 @@ +package cli + +import ( + "fmt" + + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd(queryRoute string) *cobra.Command { + // Group finality queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(CmdQueryParams()) + + return cmd +} diff --git a/x/finality/client/cli/query_params.go b/x/finality/client/cli/query_params.go new file mode 100644 index 000000000..16ff79e70 --- /dev/null +++ b/x/finality/client/cli/query_params.go @@ -0,0 +1,36 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/babylonchain/babylon/x/finality/types" +) + +func CmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "shows the parameters of the module", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/finality/client/cli/tx.go b/x/finality/client/cli/tx.go new file mode 100644 index 000000000..ba0e1b4d9 --- /dev/null +++ b/x/finality/client/cli/tx.go @@ -0,0 +1,27 @@ +package cli + +import ( + "fmt" + "time" + + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" +) + +var ( + DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + return cmd +} diff --git a/x/finality/genesis.go b/x/finality/genesis.go new file mode 100644 index 000000000..109c08df4 --- /dev/null +++ b/x/finality/genesis.go @@ -0,0 +1,20 @@ +package finality + +import ( + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.SetParams(ctx, genState.Params) +} + +// ExportGenesis returns the module's exported genesis +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + + return genesis +} diff --git a/x/finality/genesis_test.go b/x/finality/genesis_test.go new file mode 100644 index 000000000..f988265b5 --- /dev/null +++ b/x/finality/genesis_test.go @@ -0,0 +1,26 @@ +package finality_test + +import ( + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/testutil/nullify" + "github.com/babylonchain/babylon/x/finality" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/stretchr/testify/require" +) + +func TestGenesis(t *testing.T) { + genesisState := types.GenesisState{ + Params: types.DefaultParams(), + } + + k, ctx := keepertest.FinalityKeeper(t) + finality.InitGenesis(ctx, *k, genesisState) + got := finality.ExportGenesis(ctx, *k) + require.NotNil(t, got) + + nullify.Fill(&genesisState) + nullify.Fill(got) + +} diff --git a/x/finality/keeper/keeper.go b/x/finality/keeper/keeper.go new file mode 100644 index 000000000..26366c2d4 --- /dev/null +++ b/x/finality/keeper/keeper.go @@ -0,0 +1,57 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/babylonchain/babylon/x/finality/types" +) + +type ( + Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey + paramstore paramtypes.Subspace + + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + BTCStakingKeeper types.BTCStakingKeeper + } +) + +func NewKeeper( + cdc codec.BinaryCodec, + storeKey, + memKey storetypes.StoreKey, + ps paramtypes.Subspace, + + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + BTCStakingKeeper types.BTCStakingKeeper, +) Keeper { + // set KeyTable if it has not already been set + if !ps.HasKeyTable() { + ps = ps.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + cdc: cdc, + storeKey: storeKey, + memKey: memKey, + paramstore: ps, + + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + BTCStakingKeeper: BTCStakingKeeper, + } +} + +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go new file mode 100644 index 000000000..23705d1ca --- /dev/null +++ b/x/finality/keeper/msg_server.go @@ -0,0 +1,17 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/finality/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go new file mode 100644 index 000000000..506ef0c68 --- /dev/null +++ b/x/finality/keeper/msg_server_test.go @@ -0,0 +1,23 @@ +package keeper_test + +import ( + "context" + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { + k, ctx := keepertest.FinalityKeeper(t) + return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) +} + +func TestMsgServer(t *testing.T) { + ms, ctx := setupMsgServer(t) + require.NotNil(t, ms) + require.NotNil(t, ctx) +} diff --git a/x/finality/keeper/params.go b/x/finality/keeper/params.go new file mode 100644 index 000000000..ed99e89b6 --- /dev/null +++ b/x/finality/keeper/params.go @@ -0,0 +1,16 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetParams get all parameters as types.Params +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + return types.NewParams() +} + +// SetParams set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramstore.SetParamSet(ctx, ¶ms) +} diff --git a/x/finality/keeper/params_test.go b/x/finality/keeper/params_test.go new file mode 100644 index 000000000..5d54522c2 --- /dev/null +++ b/x/finality/keeper/params_test.go @@ -0,0 +1,18 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/stretchr/testify/require" +) + +func TestGetParams(t *testing.T) { + k, ctx := testkeeper.FinalityKeeper(t) + params := types.DefaultParams() + + k.SetParams(ctx, params) + + require.EqualValues(t, params, k.GetParams(ctx)) +} diff --git a/x/finality/keeper/query.go b/x/finality/keeper/query.go new file mode 100644 index 000000000..8d23eb5b1 --- /dev/null +++ b/x/finality/keeper/query.go @@ -0,0 +1,7 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/finality/types" +) + +var _ types.QueryServer = Keeper{} diff --git a/x/finality/keeper/query_params.go b/x/finality/keeper/query_params.go new file mode 100644 index 000000000..6d3d896f0 --- /dev/null +++ b/x/finality/keeper/query_params.go @@ -0,0 +1,19 @@ +package keeper + +import ( + "context" + + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/x/finality/keeper/query_params_test.go b/x/finality/keeper/query_params_test.go new file mode 100644 index 000000000..d1a216d7b --- /dev/null +++ b/x/finality/keeper/query_params_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestParamsQuery(t *testing.T) { + keeper, ctx := testkeeper.FinalityKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + params := types.DefaultParams() + keeper.SetParams(ctx, params) + + response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + require.NoError(t, err) + require.Equal(t, &types.QueryParamsResponse{Params: params}, response) +} diff --git a/x/finality/module.go b/x/finality/module.go new file mode 100644 index 000000000..7993f2aa8 --- /dev/null +++ b/x/finality/module.go @@ -0,0 +1,147 @@ +package finality + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/babylonchain/babylon/x/finality/client/cli" + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns a default GenesisState for the module, marshalled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck // generally we don't handle errors here +} + +// GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to generate new transactions containing messages defined in the module +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd(types.StoreKey) +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the module's genesis initialization. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(gs, &genState) + + InitGenesis(ctx, am.keeper, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock contains the logic that is automatically triggered at the beginning of each block +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock contains the logic that is automatically triggered at the end of each block +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/finality/module_simulation.go b/x/finality/module_simulation.go new file mode 100644 index 000000000..e02b4f14a --- /dev/null +++ b/x/finality/module_simulation.go @@ -0,0 +1,57 @@ +package finality + +import ( + "math/rand" + + "github.com/babylonchain/babylon/testutil/sample" + finalitysimulation "github.com/babylonchain/babylon/x/finality/simulation" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// avoid unused import issue +var ( + _ = sample.AccAddress + _ = finalitysimulation.FindAccount + _ = simulation.MsgEntryKind + _ = baseapp.Paramspace + _ = rand.Rand{} +) + +const () + +// GenerateGenesisState creates a randomized GenState of the module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + accs := make([]string, len(simState.Accounts)) + for i, acc := range simState.Accounts { + accs[i] = acc.Address.String() + } + finalityGenesis := types.GenesisState{ + Params: types.DefaultParams(), + } + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&finalityGenesis) +} + +// RegisterStoreDecoder registers a decoder. +func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} + +// ProposalContents doesn't return any content functions for governance proposals +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { + return nil +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + operations := make([]simtypes.WeightedOperation, 0) + + return operations +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{} +} diff --git a/x/finality/simulation/helpers.go b/x/finality/simulation/helpers.go new file mode 100644 index 000000000..92c437c0d --- /dev/null +++ b/x/finality/simulation/helpers.go @@ -0,0 +1,15 @@ +package simulation + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// FindAccount find a specific address from an account list +func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { + creator, err := sdk.AccAddressFromBech32(address) + if err != nil { + panic(err) + } + return simtypes.FindAccount(accs, creator) +} diff --git a/x/finality/types/codec.go b/x/finality/types/codec.go new file mode 100644 index 000000000..858010906 --- /dev/null +++ b/x/finality/types/codec.go @@ -0,0 +1,22 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { + +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) +) diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go new file mode 100644 index 000000000..1c04c70db --- /dev/null +++ b/x/finality/types/errors.go @@ -0,0 +1,10 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// x/btcstaking module sentinel errors +var ( + ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") +) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go new file mode 100644 index 000000000..f9c613a5f --- /dev/null +++ b/x/finality/types/expected_keepers.go @@ -0,0 +1,22 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type BTCStakingKeeper interface { + // Methods imported from btcstaking should be defined here +} + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI + // Methods imported from account should be defined here +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + // Methods imported from bank should be defined here +} diff --git a/x/finality/types/genesis.go b/x/finality/types/genesis.go new file mode 100644 index 000000000..97cad7616 --- /dev/null +++ b/x/finality/types/genesis.go @@ -0,0 +1,19 @@ +package types + +// DefaultIndex is the default global index +const DefaultIndex uint64 = 1 + +// DefaultGenesis returns the default genesis state +func DefaultGenesis() *GenesisState { + return &GenesisState{ + + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + + return gs.Params.Validate() +} diff --git a/x/finality/types/genesis.pb.go b/x/finality/types/genesis.pb.go new file mode 100644 index 000000000..59249a222 --- /dev/null +++ b/x/finality/types/genesis.pb.go @@ -0,0 +1,321 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the finality module's genesis state. +type GenesisState struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_52dc577f74d797d1, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "babylon.finality.v1.GenesisState") +} + +func init() { proto.RegisterFile("babylon/finality/v1/genesis.proto", fileDescriptor_52dc577f74d797d1) } + +var fileDescriptor_52dc577f74d797d1 = []byte{ + // 200 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, + 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, + 0x2a, 0xd1, 0x83, 0x29, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, + 0x83, 0x58, 0x10, 0xa5, 0x52, 0x0a, 0xd8, 0x4c, 0x2b, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa6, + 0xe4, 0xc9, 0xc5, 0xe3, 0x0e, 0x31, 0x3d, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x92, 0x8b, 0x0d, + 0x22, 0x2f, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xad, 0x87, 0xc5, 0x36, 0xbd, 0x00, 0xb0, + 0x12, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xa0, 0x1a, 0x9c, 0xbc, 0x4e, 0x3c, 0x92, 0x63, + 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, + 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, + 0x3f, 0x57, 0x1f, 0x6a, 0x5c, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x70, 0x60, + 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x75, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x58, 0x11, 0xae, 0xb4, 0x0f, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/finality/types/genesis_test.go b/x/finality/types/genesis_test.go new file mode 100644 index 000000000..3036502e9 --- /dev/null +++ b/x/finality/types/genesis_test.go @@ -0,0 +1,37 @@ +package types_test + +import ( + "testing" + + "github.com/babylonchain/babylon/x/finality/types" + "github.com/stretchr/testify/require" +) + +func TestGenesisState_Validate(t *testing.T) { + tests := []struct { + desc string + genState *types.GenesisState + valid bool + }{ + { + desc: "default is valid", + genState: types.DefaultGenesis(), + valid: true, + }, + { + desc: "valid genesis state", + genState: &types.GenesisState{}, + valid: true, + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + err := tc.genState.Validate() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/finality/types/keys.go b/x/finality/types/keys.go new file mode 100644 index 000000000..7e07266bd --- /dev/null +++ b/x/finality/types/keys.go @@ -0,0 +1,19 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "finality" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // RouterKey defines the module's message routing key + RouterKey = ModuleName + + // MemStoreKey defines the in-memory store key + MemStoreKey = "mem_finality" +) + +func KeyPrefix(p string) []byte { + return []byte(p) +} diff --git a/x/finality/types/params.go b/x/finality/types/params.go new file mode 100644 index 000000000..357196ad6 --- /dev/null +++ b/x/finality/types/params.go @@ -0,0 +1,39 @@ +package types + +import ( + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "gopkg.in/yaml.v2" +) + +var _ paramtypes.ParamSet = (*Params)(nil) + +// ParamKeyTable the param key table for launch module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// NewParams creates a new Params instance +func NewParams() Params { + return Params{} +} + +// DefaultParams returns a default set of parameters +func DefaultParams() Params { + return NewParams() +} + +// ParamSetPairs get the params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{} +} + +// Validate validates the set of params +func (p Params) Validate() error { + return nil +} + +// String implements the Stringer interface. +func (p Params) String() string { + out, _ := yaml.Marshal(p) + return string(out) +} diff --git a/x/finality/types/params.pb.go b/x/finality/types/params.pb.go new file mode 100644 index 000000000..e967d01e6 --- /dev/null +++ b/x/finality/types/params.pb.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the module. +type Params struct { +} + +func (m *Params) Reset() { *m = Params{} } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_25539c9a61c72ee9, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Params)(nil), "babylon.finality.v1.Params") +} + +func init() { proto.RegisterFile("babylon/finality/v1/params.proto", fileDescriptor_25539c9a61c72ee9) } + +var fileDescriptor_25539c9a61c72ee9 = []byte{ + // 158 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, + 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xaa, + 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, + 0x58, 0x10, 0xa5, 0x4a, 0x7c, 0x5c, 0x6c, 0x01, 0x60, 0xad, 0x56, 0x2c, 0x33, 0x16, 0xc8, 0x33, + 0x38, 0x79, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, + 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x41, 0x7a, 0x66, + 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0xfc, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, + 0x18, 0x47, 0xbf, 0x02, 0xe1, 0xa0, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0x15, 0xc6, + 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x67, 0x01, 0xe8, 0x48, 0xb1, 0x00, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go new file mode 100644 index 000000000..2ddeab5e3 --- /dev/null +++ b/x/finality/types/query.pb.go @@ -0,0 +1,535 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params holds all the parameters of this module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "babylon.finality.v1.QueryParamsResponse") +} + +func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } + +var fileDescriptor_32bddab77af6fdae = []byte{ + // 279 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, + 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0x2a, 0xd0, + 0x83, 0x29, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, 0x58, + 0x10, 0xa5, 0x52, 0x32, 0xe9, 0xf9, 0xf9, 0xe9, 0x39, 0xa9, 0xfa, 0x89, 0x05, 0x99, 0xfa, 0x89, + 0x79, 0x79, 0xf9, 0x25, 0x89, 0x25, 0x99, 0xf9, 0x79, 0xc5, 0x50, 0x59, 0x05, 0x6c, 0x36, 0x15, + 0x24, 0x16, 0x25, 0xe6, 0x42, 0x55, 0x28, 0x89, 0x70, 0x09, 0x05, 0x82, 0x6c, 0x0e, 0x00, 0x0b, + 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x28, 0x05, 0x70, 0x09, 0xa3, 0x88, 0x16, 0x17, 0xe4, + 0xe7, 0x15, 0xa7, 0x0a, 0x59, 0x72, 0xb1, 0x41, 0x34, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, + 0x49, 0xeb, 0x61, 0x71, 0xa8, 0x1e, 0x44, 0x93, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, + 0x0d, 0x46, 0xd3, 0x19, 0xb9, 0x58, 0xc1, 0x46, 0x0a, 0xf5, 0x32, 0x72, 0xb1, 0x41, 0x94, 0x08, + 0xa9, 0x63, 0xd5, 0x8f, 0xe9, 0x1e, 0x29, 0x0d, 0xc2, 0x0a, 0x21, 0x4e, 0x54, 0x32, 0x68, 0xba, + 0xfc, 0x64, 0x32, 0x93, 0x96, 0x90, 0x86, 0x3e, 0x54, 0x47, 0x72, 0x46, 0x62, 0x66, 0x9e, 0x3e, + 0xee, 0x70, 0x70, 0xf2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, + 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x83, + 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xec, 0xa6, 0x55, 0x20, 0xcc, 0x2b, + 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, 0x07, 0xaa, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x7d, + 0xa8, 0x3f, 0x3a, 0xe2, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Parameters queries the parameters of the module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Parameters queries the parameters of the module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.finality.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/finality/v1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go new file mode 100644 index 000000000..430c89d5a --- /dev/null +++ b/x/finality/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: babylon/finality/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go new file mode 100644 index 000000000..fd355c32f --- /dev/null +++ b/x/finality/types/tx.pb.go @@ -0,0 +1,81 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } + +var fileDescriptor_2dd6da066b6baf1d = []byte{ + // 134 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, + 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xca, 0xea, 0xc1, 0x64, 0xf5, + 0xca, 0x0c, 0x8d, 0x58, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, + 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, + 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, + 0x57, 0x1f, 0x6a, 0x40, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0xb0, 0xad, 0xa4, + 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x6c, 0x9d, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x18, 0x3e, + 0x32, 0xee, 0x8e, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.finality.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/finality/v1/tx.proto", +} diff --git a/x/finality/types/types.go b/x/finality/types/types.go new file mode 100644 index 000000000..ab1254f4c --- /dev/null +++ b/x/finality/types/types.go @@ -0,0 +1 @@ +package types From dc94faba70274d36024e0526b4cf3f0a1d5c5658 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 16 Jun 2023 10:37:16 +1000 Subject: [PATCH 009/202] btcstaking: DB schema (#2) --- go.mod | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 84 + testutil/datagen/btc_schnorr.go | 15 + types/btc_schnorr_pk.go | 66 + types/btc_schnorr_pk_test.go | 40 + types/btc_schnorr_sig.go | 65 + types/btc_schnorr_sig_test.go | 41 + x/btcstaking/keeper/btc_delegations.go | 63 + x/btcstaking/keeper/btc_validators.go | 52 + x/btcstaking/types/btcstaking.pb.go | 1908 ++++++++++++++++++ x/btcstaking/types/errors.go | 3 +- x/btcstaking/types/keys.go | 5 + 12 files changed, 2342 insertions(+), 2 deletions(-) create mode 100644 proto/babylon/btcstaking/v1/btcstaking.proto create mode 100644 testutil/datagen/btc_schnorr.go create mode 100644 types/btc_schnorr_pk.go create mode 100644 types/btc_schnorr_pk_test.go create mode 100644 types/btc_schnorr_sig.go create mode 100644 types/btc_schnorr_sig_test.go create mode 100644 x/btcstaking/keeper/btc_delegations.go create mode 100644 x/btcstaking/keeper/btc_validators.go create mode 100644 x/btcstaking/types/btcstaking.pb.go diff --git a/go.mod b/go.mod index 1ff140e14..83fb12247 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( cosmossdk.io/tools/rosetta v0.2.1 github.com/CosmWasm/wasmvm v1.2.3 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/cosmos/cosmos-proto v1.0.0-beta.2 @@ -134,7 +135,6 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto new file mode 100644 index 000000000..c691d1164 --- /dev/null +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -0,0 +1,84 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "gogoproto/gogo.proto"; +import "cosmos/crypto/secp256k1/keys.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// BTCValidator defines a BTC validator +message BTCValidator { + // babylon_pk is the Babylon secp256k1 PK of this BTC validator + cosmos.crypto.secp256k1.PubKey babylon_pk = 1; + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // pop is the proof of possession of babylon_pk and btc_pk + ProofOfPossession pop = 3; +} + +// BTCDelegation defines a BTC delegation +message BTCDelegation { + // babylon_pk is the Babylon secp256k1 PK of this BTC delegation + cosmos.crypto.secp256k1.PubKey babylon_pk = 1; + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // pop is the proof of possession of babylon_pk and btc_pk + ProofOfPossession pop = 3; + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + bytes val_btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + uint64 start_height = 5; + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + uint64 end_height = 6; + // total_sat is the total amount of BTC stakes in this delegation + // quantified in satoshi + uint64 total_sat = 7; + // staking_tx is the staking tx + // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain + StakingTx staking_tx = 8; + // staking_tx_sig is the signature of the staking tx + // signed by SK corresponding to btc_pk + bytes staking_tx_sig = 9 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // slashing_tx is the slashing tx + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + SlashingTx slashing_tx = 10; + // slashing_tx_sig is the signature of the slashing tx + // signed by SK corresponding to btc_pk + bytes slashing_tx_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} + +// ProofOfPossession is the proof of possession that a Babylon secp256k1 +// secret key and a Bitcoin secp256k1 secret key are held by the same +// person +message ProofOfPossession { + // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) + bytes babylon_sig = 1; + // btc_sig is the signature generated via sign(sk_btc, babylon_sig) + // the signature follows encoding in BIP-340 spec + bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} + +// StakingTx defines a staking tx +message StakingTx { + // tx is the staking tx in bytes + bytes tx = 1; + // change_out_script is the output script of the change + bytes change_out_script = 2; + // slashing_out_script is the output script of the slashing path + bytes slashing_out_script = 3; +} + +// SlashingTx defines a slashing tx +message SlashingTx { + // tx is the slashing tx in bytes + bytes tx = 1; + // out_script is the output script of the slashing path + bytes out_script = 2; +} \ No newline at end of file diff --git a/testutil/datagen/btc_schnorr.go b/testutil/datagen/btc_schnorr.go new file mode 100644 index 000000000..c3a445b48 --- /dev/null +++ b/testutil/datagen/btc_schnorr.go @@ -0,0 +1,15 @@ +package datagen + +import ( + "math/rand" + + "github.com/btcsuite/btcd/btcec/v2" +) + +func GenRandomBTCKeyPair(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, error) { + sk, err := btcec.NewPrivateKey() + if err != nil { + return nil, nil, err + } + return sk, sk.PubKey(), nil +} diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go new file mode 100644 index 000000000..e8b26bb12 --- /dev/null +++ b/types/btc_schnorr_pk.go @@ -0,0 +1,66 @@ +package types + +import ( + "errors" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" +) + +type BIP340PubKey []byte + +const BIP340PubKeyLen = schnorr.PubKeyBytesLen + +func NewBIP340PubKey(data []byte) (BIP340PubKey, error) { + var pk BIP340PubKey + err := pk.Unmarshal(data) + return pk, err +} + +func NewBIP340PubKeyFromBTCPK(btcPK *btcec.PublicKey) BIP340PubKey { + pkBytes := schnorr.SerializePubKey(btcPK) + return BIP340PubKey(pkBytes) +} + +func (pk BIP340PubKey) ToBTCPK() (*btcec.PublicKey, error) { + return schnorr.ParsePubKey(pk) +} + +func (pk BIP340PubKey) Size() int { + return len(pk.MustMarshal()) +} + +func (pk BIP340PubKey) Marshal() ([]byte, error) { + return pk, nil +} + +func (pk BIP340PubKey) MustMarshal() []byte { + pkBytes, err := pk.Marshal() + if err != nil { + panic(err) + } + return pkBytes +} + +func (pk BIP340PubKey) MarshalTo(data []byte) (int, error) { + bz, err := pk.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (pk *BIP340PubKey) Unmarshal(data []byte) error { + newPK := BIP340PubKey(data) + + // ensure that the bytes can be transformed to a *btcec.PublicKey object + // this includes all format checks + _, err := newPK.ToBTCPK() + if err != nil { + return errors.New("bytes cannot be converted to a *btcec.PublicKey object") + } + + *pk = data + return nil +} diff --git a/types/btc_schnorr_pk_test.go b/types/btc_schnorr_pk_test.go new file mode 100644 index 000000000..49d905373 --- /dev/null +++ b/types/btc_schnorr_pk_test.go @@ -0,0 +1,40 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/types" + "github.com/stretchr/testify/require" +) + +func FuzzBIP340PubKey(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + _, btcPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + // btcPK -> BIP340PubKey -> btcPK + pk := types.NewBIP340PubKeyFromBTCPK(btcPK) + btcPK2, err := pk.ToBTCPK() + require.NoError(t, err) + // NOTE: we can only ensure they have the same x value. + // there could be 2 different y values for a given x on secp256k1 + // curve. The BIP340 encoding is compressed in the sense that + // it only contains x value but not y. pk.ToBTCPK() may choose a random + // one of the 2 possible y values. + require.Zero(t, btcPK.X().Cmp(btcPK2.X())) + + // pk -> bytes -> pk + pkBytes := pk.MustMarshal() + var pk2 types.BIP340PubKey + _, err = types.NewBIP340PubKey(pkBytes) + require.NoError(t, err) + err = pk2.Unmarshal(pkBytes) + require.NoError(t, err) + require.Equal(t, pk, pk2) + }) +} diff --git a/types/btc_schnorr_sig.go b/types/btc_schnorr_sig.go new file mode 100644 index 000000000..74b919be2 --- /dev/null +++ b/types/btc_schnorr_sig.go @@ -0,0 +1,65 @@ +package types + +import ( + "errors" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" +) + +type BIP340Signature []byte + +const BIP340SignatureLen = schnorr.SignatureSize + +func NewBIP340Signature(data []byte) (BIP340Signature, error) { + var sig BIP340Signature + err := sig.Unmarshal(data) + return sig, err +} + +func NewBIP340SignatureFromBTCSig(btcSig *schnorr.Signature) BIP340Signature { + sigBytes := btcSig.Serialize() + return BIP340Signature(sigBytes) +} + +func (sig BIP340Signature) ToBTCSig() (*schnorr.Signature, error) { + return schnorr.ParseSignature(sig) +} + +func (sig BIP340Signature) Size() int { + return len(sig.MustMarshal()) +} + +func (sig BIP340Signature) Marshal() ([]byte, error) { + return sig, nil +} + +func (sig BIP340Signature) MustMarshal() []byte { + sigBytes, err := sig.Marshal() + if err != nil { + panic(err) + } + return sigBytes +} + +func (sig BIP340Signature) MarshalTo(data []byte) (int, error) { + bz, err := sig.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (sig *BIP340Signature) Unmarshal(data []byte) error { + newSig := BIP340Signature(data) + + // ensure that the bytes can be transformed to a *schnorr.Signature object + // this includes all format checks + _, err := newSig.ToBTCSig() + if err != nil { + return errors.New("bytes cannot be converted to a *schnorr.Signature object") + } + + *sig = data + return nil +} diff --git a/types/btc_schnorr_sig_test.go b/types/btc_schnorr_sig_test.go new file mode 100644 index 000000000..e25e4a43b --- /dev/null +++ b/types/btc_schnorr_sig_test.go @@ -0,0 +1,41 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/stretchr/testify/require" +) + +func FuzzBIP340Signature(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + btcSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + // sign a random msg + msgHash := datagen.GenRandomBtcdHash(r) + btcSig, err := schnorr.Sign(btcSK, msgHash[:]) + require.NoError(t, err) + + // btcSig -> BIP340Signature -> btcSig + sig := types.NewBIP340SignatureFromBTCSig(btcSig) + btcSig2, err := sig.ToBTCSig() + require.NoError(t, err) + require.True(t, btcSig.IsEqual(btcSig2)) + + // BIP340Signature -> bytes -> BIP340Signature + sigBytes := sig.MustMarshal() + var sig2 types.BIP340Signature + _, err = types.NewBIP340Signature(sigBytes) + require.NoError(t, err) + err = sig2.Unmarshal(sigBytes) + require.NoError(t, err) + require.Equal(t, sig, sig2) + }) +} diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go new file mode 100644 index 000000000..ba837da87 --- /dev/null +++ b/x/btcstaking/keeper/btc_delegations.go @@ -0,0 +1,63 @@ +package keeper + +import ( + "fmt" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AddBTCDelegation verifies then adds the given BTC delegation to KVStore +func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { + // TODO: verify btcDel + k.setBTCDelegation(ctx, btcDel) + return nil +} + +// setBTCDelegation adds the given BTC delegation to KVStore +func (k Keeper) setBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { + store := k.btcDelegationStore(ctx, btcDel.ValBtcPk.MustMarshal()) + btcDelBytes := k.cdc.MustMarshal(btcDel) + store.Set(btcDel.BtcPk.MustMarshal(), btcDelBytes) +} + +// HasBTCDelegation checks if the given BTC delegation exists under a given BTC validator +func (k Keeper) HasBTCDelegation(ctx sdk.Context, valBTCPK []byte, delBTCPK []byte) bool { + if !k.HasBTCValidator(ctx, valBTCPK) { + return false + } + store := k.btcDelegationStore(ctx, valBTCPK) + return store.Has(delBTCPK) +} + +// GetBTCDelegation gets the BTC delegation with a given BTC PK under a given BTC validator +func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK []byte, delBTCPK []byte) (*types.BTCDelegation, error) { + // ensure the BTC validator exists + if !k.HasBTCValidator(ctx, valBTCPK) { + return nil, types.ErrBTCValNotFound + } + + store := k.btcDelegationStore(ctx, valBTCPK) + // ensure BTC delegation exists + if !store.Has(delBTCPK) { + return nil, types.ErrBTCDelNotFound + } + // get and unmarshal + btcDelBytes := store.Get(delBTCPK) + var btcDel types.BTCDelegation + if err := k.cdc.Unmarshal(btcDelBytes, &btcDel); err != nil { + return nil, fmt.Errorf("failed to unmarshal BTC delegation: %w", err) + } + return &btcDel, nil +} + +// btcDelegationStore returns the KVStore of the BTC delegations +// prefix: BTCDelegationKey || validator's Bitcoin secp256k1 PK +// key: delegation's Bitcoin secp256k1 PK +// value: BTCDelegation object +func (k Keeper) btcDelegationStore(ctx sdk.Context, valBTCPK []byte) prefix.Store { + store := ctx.KVStore(k.storeKey) + delegationStore := prefix.NewStore(store, types.BTCDelegationKey) + return prefix.NewStore(delegationStore, valBTCPK) +} diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go new file mode 100644 index 000000000..89f9708aa --- /dev/null +++ b/x/btcstaking/keeper/btc_validators.go @@ -0,0 +1,52 @@ +package keeper + +import ( + "fmt" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AddBTCValidator verifies and adds the given BTC validator to KVStore +func (k Keeper) AddBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) error { + // TODO: verify btcVal + k.setBTCValidator(ctx, btcVal) + return nil +} + +// setBTCValidator adds the given BTC validator to KVStore +func (k Keeper) setBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) { + store := k.btcValidatorStore(ctx) + btcValBytes := k.cdc.MustMarshal(btcVal) + store.Set(btcVal.BtcPk.MustMarshal(), btcValBytes) +} + +// HasBTCValidator checks if the BTC validator exists +func (k Keeper) HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool { + store := k.btcValidatorStore(ctx) + return store.Has(valBTCPK) +} + +// GetBTCValidator gets the BTC validator with the given validator Bitcoin PK +func (k Keeper) GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*types.BTCValidator, error) { + store := k.btcValidatorStore(ctx) + if !k.HasBTCValidator(ctx, valBTCPK) { + return nil, types.ErrBTCValNotFound + } + btcValBytes := store.Get(valBTCPK) + var btcVal types.BTCValidator + if err := k.cdc.Unmarshal(btcValBytes, &btcVal); err != nil { + return nil, fmt.Errorf("failed to unmarshal BTC validator: %w", err) + } + return &btcVal, nil +} + +// btcValidatorStore returns the KVStore of the BTC validator set +// prefix: BTCValidatorKey +// key: Bitcoin secp256k1 PK +// value: BTCValidator object +func (k Keeper) btcValidatorStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.BTCValidatorKey) +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go new file mode 100644 index 000000000..e71a6e298 --- /dev/null +++ b/x/btcstaking/types/btcstaking.pb.go @@ -0,0 +1,1908 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/btcstaking.proto + +package types + +import ( + fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// BTCValidator defines a BTC validator +type BTCValidator struct { + // babylon_pk is the Babylon secp256k1 PK of this BTC validator + BabylonPk *secp256k1.PubKey `protobuf:"bytes,1,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // pop is the proof of possession of babylon_pk and btc_pk + Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` +} + +func (m *BTCValidator) Reset() { *m = BTCValidator{} } +func (m *BTCValidator) String() string { return proto.CompactTextString(m) } +func (*BTCValidator) ProtoMessage() {} +func (*BTCValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{0} +} +func (m *BTCValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCValidator.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCValidator.Merge(m, src) +} +func (m *BTCValidator) XXX_Size() int { + return m.Size() +} +func (m *BTCValidator) XXX_DiscardUnknown() { + xxx_messageInfo_BTCValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCValidator proto.InternalMessageInfo + +func (m *BTCValidator) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *BTCValidator) GetPop() *ProofOfPossession { + if m != nil { + return m.Pop + } + return nil +} + +// BTCDelegation defines a BTC delegation +type BTCDelegation struct { + // babylon_pk is the Babylon secp256k1 PK of this BTC delegation + BabylonPk *secp256k1.PubKey `protobuf:"bytes,1,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // pop is the proof of possession of babylon_pk and btc_pk + Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + StartHeight uint64 `protobuf:"varint,5,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + EndHeight uint64 `protobuf:"varint,6,opt,name=end_height,json=endHeight,proto3" json:"end_height,omitempty"` + // total_sat is the total amount of BTC stakes in this delegation + // quantified in satoshi + TotalSat uint64 `protobuf:"varint,7,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` + // staking_tx is the staking tx + // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain + StakingTx *StakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + // staking_tx_sig is the signature of the staking tx + // signed by SK corresponding to btc_pk + StakingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,9,opt,name=staking_tx_sig,json=stakingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"staking_tx_sig,omitempty"` + // slashing_tx is the slashing tx + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + SlashingTx *SlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3" json:"slashing_tx,omitempty"` + // slashing_tx_sig is the signature of the slashing tx + // signed by SK corresponding to btc_pk + SlashingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=slashing_tx_sig,json=slashingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_tx_sig,omitempty"` +} + +func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } +func (m *BTCDelegation) String() string { return proto.CompactTextString(m) } +func (*BTCDelegation) ProtoMessage() {} +func (*BTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{1} +} +func (m *BTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegation.Merge(m, src) +} +func (m *BTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *BTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCDelegation proto.InternalMessageInfo + +func (m *BTCDelegation) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *BTCDelegation) GetPop() *ProofOfPossession { + if m != nil { + return m.Pop + } + return nil +} + +func (m *BTCDelegation) GetStartHeight() uint64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +func (m *BTCDelegation) GetEndHeight() uint64 { + if m != nil { + return m.EndHeight + } + return 0 +} + +func (m *BTCDelegation) GetTotalSat() uint64 { + if m != nil { + return m.TotalSat + } + return 0 +} + +func (m *BTCDelegation) GetStakingTx() *StakingTx { + if m != nil { + return m.StakingTx + } + return nil +} + +func (m *BTCDelegation) GetSlashingTx() *SlashingTx { + if m != nil { + return m.SlashingTx + } + return nil +} + +// ProofOfPossession is the proof of possession that a Babylon secp256k1 +// secret key and a Bitcoin secp256k1 secret key are held by the same +// person +type ProofOfPossession struct { + // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) + BabylonSig []byte `protobuf:"bytes,1,opt,name=babylon_sig,json=babylonSig,proto3" json:"babylon_sig,omitempty"` + // btc_sig is the signature generated via sign(sk_btc, babylon_sig) + // the signature follows encoding in BIP-340 spec + BtcSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=btc_sig,json=btcSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"btc_sig,omitempty"` +} + +func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } +func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } +func (*ProofOfPossession) ProtoMessage() {} +func (*ProofOfPossession) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{2} +} +func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ProofOfPossession) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ProofOfPossession.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ProofOfPossession) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProofOfPossession.Merge(m, src) +} +func (m *ProofOfPossession) XXX_Size() int { + return m.Size() +} +func (m *ProofOfPossession) XXX_DiscardUnknown() { + xxx_messageInfo_ProofOfPossession.DiscardUnknown(m) +} + +var xxx_messageInfo_ProofOfPossession proto.InternalMessageInfo + +func (m *ProofOfPossession) GetBabylonSig() []byte { + if m != nil { + return m.BabylonSig + } + return nil +} + +// StakingTx defines a staking tx +type StakingTx struct { + // tx is the staking tx in bytes + Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` + // change_out_script is the output script of the change + ChangeOutScript []byte `protobuf:"bytes,2,opt,name=change_out_script,json=changeOutScript,proto3" json:"change_out_script,omitempty"` + // slashing_out_script is the output script of the slashing path + SlashingOutScript []byte `protobuf:"bytes,3,opt,name=slashing_out_script,json=slashingOutScript,proto3" json:"slashing_out_script,omitempty"` +} + +func (m *StakingTx) Reset() { *m = StakingTx{} } +func (m *StakingTx) String() string { return proto.CompactTextString(m) } +func (*StakingTx) ProtoMessage() {} +func (*StakingTx) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{3} +} +func (m *StakingTx) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StakingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StakingTx.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *StakingTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_StakingTx.Merge(m, src) +} +func (m *StakingTx) XXX_Size() int { + return m.Size() +} +func (m *StakingTx) XXX_DiscardUnknown() { + xxx_messageInfo_StakingTx.DiscardUnknown(m) +} + +var xxx_messageInfo_StakingTx proto.InternalMessageInfo + +func (m *StakingTx) GetTx() []byte { + if m != nil { + return m.Tx + } + return nil +} + +func (m *StakingTx) GetChangeOutScript() []byte { + if m != nil { + return m.ChangeOutScript + } + return nil +} + +func (m *StakingTx) GetSlashingOutScript() []byte { + if m != nil { + return m.SlashingOutScript + } + return nil +} + +// SlashingTx defines a slashing tx +type SlashingTx struct { + // tx is the slashing tx in bytes + Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` + // out_script is the output script of the slashing path + OutScript []byte `protobuf:"bytes,2,opt,name=out_script,json=outScript,proto3" json:"out_script,omitempty"` +} + +func (m *SlashingTx) Reset() { *m = SlashingTx{} } +func (m *SlashingTx) String() string { return proto.CompactTextString(m) } +func (*SlashingTx) ProtoMessage() {} +func (*SlashingTx) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{4} +} +func (m *SlashingTx) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SlashingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SlashingTx.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SlashingTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_SlashingTx.Merge(m, src) +} +func (m *SlashingTx) XXX_Size() int { + return m.Size() +} +func (m *SlashingTx) XXX_DiscardUnknown() { + xxx_messageInfo_SlashingTx.DiscardUnknown(m) +} + +var xxx_messageInfo_SlashingTx proto.InternalMessageInfo + +func (m *SlashingTx) GetTx() []byte { + if m != nil { + return m.Tx + } + return nil +} + +func (m *SlashingTx) GetOutScript() []byte { + if m != nil { + return m.OutScript + } + return nil +} + +func init() { + proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") + proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") + proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") + proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") + proto.RegisterType((*SlashingTx)(nil), "babylon.btcstaking.v1.SlashingTx") +} + +func init() { + proto.RegisterFile("babylon/btcstaking/v1/btcstaking.proto", fileDescriptor_3851ae95ccfaf7db) +} + +var fileDescriptor_3851ae95ccfaf7db = []byte{ + // 606 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xad, 0xd3, 0x4f, 0x4f, 0xd2, 0x56, 0x5d, 0x40, 0x8a, 0x8a, 0x9a, 0xa4, 0x39, 0xa0, 0x88, + 0x83, 0x4d, 0x5b, 0x5a, 0x09, 0x90, 0x40, 0x72, 0x39, 0x80, 0x00, 0x35, 0x72, 0x22, 0x0e, 0x5c, + 0xac, 0xb5, 0xbb, 0x75, 0x56, 0x76, 0xbd, 0x96, 0x77, 0x12, 0x9c, 0x7f, 0xc0, 0x91, 0x9f, 0xc5, + 0xb1, 0xc7, 0xd2, 0x43, 0x85, 0xda, 0x3f, 0x82, 0xbc, 0xfe, 0x68, 0xa4, 0x52, 0x21, 0xd4, 0x1b, + 0xb7, 0xf1, 0xcc, 0x9b, 0x37, 0xef, 0x79, 0x67, 0x17, 0x9e, 0xb8, 0xd4, 0x9d, 0x86, 0x22, 0x32, + 0x5d, 0xf4, 0x24, 0xd2, 0x80, 0x47, 0xbe, 0x39, 0xd9, 0x99, 0xf9, 0x32, 0xe2, 0x44, 0xa0, 0x20, + 0x8f, 0x0a, 0x9c, 0x31, 0x53, 0x99, 0xec, 0x6c, 0x3e, 0xf4, 0x85, 0x2f, 0x14, 0xc2, 0xcc, 0xa2, + 0x1c, 0xbc, 0xd9, 0xf5, 0x84, 0x3c, 0x15, 0xd2, 0xf4, 0x92, 0x69, 0x8c, 0xc2, 0x94, 0xcc, 0x8b, + 0x77, 0xf7, 0x0f, 0x82, 0x1d, 0x33, 0x60, 0x53, 0x99, 0x63, 0xba, 0x3f, 0x35, 0x68, 0x58, 0xc3, + 0xc3, 0xcf, 0x34, 0xe4, 0xc7, 0x14, 0x45, 0x42, 0x5e, 0x03, 0x14, 0x33, 0x9c, 0x38, 0x68, 0x6a, + 0x1d, 0xad, 0x57, 0xdf, 0x6d, 0x1b, 0x39, 0x93, 0x91, 0x33, 0x19, 0x15, 0x93, 0xd1, 0x1f, 0xbb, + 0x1f, 0xd8, 0xd4, 0xd6, 0x8b, 0x96, 0x7e, 0x40, 0x3e, 0xc1, 0x92, 0x8b, 0x5e, 0xd6, 0x5b, 0xeb, + 0x68, 0xbd, 0x86, 0x75, 0x70, 0x71, 0xd9, 0xde, 0xf5, 0x39, 0x8e, 0xc6, 0xae, 0xe1, 0x89, 0x53, + 0xb3, 0x40, 0x7a, 0x23, 0xca, 0xa3, 0xf2, 0xc3, 0xc4, 0x69, 0xcc, 0xa4, 0x61, 0xbd, 0xef, 0xef, + 0x3d, 0x7f, 0x56, 0x50, 0x2e, 0xba, 0xe8, 0xf5, 0x03, 0xf2, 0x12, 0xe6, 0x63, 0x11, 0x37, 0xe7, + 0x95, 0x8e, 0x9e, 0xf1, 0x47, 0xfb, 0x46, 0x3f, 0x11, 0xe2, 0xe4, 0xe8, 0xa4, 0x2f, 0xa4, 0x64, + 0x52, 0x72, 0x11, 0xd9, 0x59, 0x53, 0xf7, 0x7c, 0x11, 0x56, 0xad, 0xe1, 0xe1, 0x5b, 0x16, 0x32, + 0x9f, 0x22, 0x17, 0xd1, 0x7f, 0x64, 0x8e, 0x0c, 0x01, 0x26, 0x34, 0x74, 0x0a, 0x39, 0x0b, 0xf7, + 0x92, 0xb3, 0x32, 0xa1, 0xa1, 0xa5, 0x14, 0x6d, 0x43, 0x43, 0x22, 0x4d, 0xd0, 0x19, 0x31, 0xee, + 0x8f, 0xb0, 0xb9, 0xd8, 0xd1, 0x7a, 0x0b, 0x76, 0x5d, 0xe5, 0xde, 0xa9, 0x14, 0xd9, 0x02, 0x60, + 0xd1, 0x71, 0x09, 0x58, 0x52, 0x00, 0x9d, 0x45, 0xc7, 0x45, 0xf9, 0x31, 0xe8, 0x28, 0x90, 0x86, + 0x8e, 0xa4, 0xd8, 0x5c, 0x56, 0xd5, 0x15, 0x95, 0x18, 0x50, 0x24, 0x6f, 0x00, 0x0a, 0x67, 0x0e, + 0xa6, 0xcd, 0x15, 0xe5, 0xbb, 0x73, 0x87, 0xef, 0x41, 0x1e, 0x0e, 0x53, 0x5b, 0x97, 0x65, 0x48, + 0x1c, 0x58, 0xbb, 0x21, 0x70, 0x24, 0xf7, 0x9b, 0xba, 0x72, 0xfe, 0xe2, 0xe2, 0xb2, 0xbd, 0xff, + 0x2f, 0xce, 0x07, 0xdc, 0x8f, 0x28, 0x8e, 0x13, 0x66, 0x37, 0x2a, 0xf6, 0x01, 0xf7, 0x89, 0x05, + 0x75, 0x19, 0x52, 0x39, 0x2a, 0x24, 0x82, 0x92, 0xb8, 0x7d, 0x97, 0xc4, 0x02, 0x39, 0x4c, 0x6d, + 0x90, 0x55, 0x4c, 0x28, 0xac, 0xcf, 0x70, 0x28, 0x95, 0xf5, 0xfb, 0xaa, 0x5c, 0xbd, 0xe1, 0x1f, + 0x70, 0xbf, 0xfb, 0x4d, 0x83, 0x8d, 0x5b, 0x8b, 0x41, 0xda, 0x50, 0x2f, 0xd7, 0x3b, 0x1b, 0x9a, + 0xed, 0x77, 0xc3, 0x2e, 0x37, 0x3e, 0x73, 0x67, 0xc3, 0x72, 0xb6, 0x30, 0x59, 0xb1, 0x76, 0x5f, + 0x45, 0xd9, 0x4d, 0xc8, 0xa4, 0x7c, 0x05, 0xbd, 0x3a, 0x2a, 0xb2, 0x06, 0x35, 0x4c, 0x8b, 0xc1, + 0x35, 0x4c, 0xc9, 0x53, 0xd8, 0xf0, 0x46, 0x34, 0xf2, 0x99, 0x23, 0xc6, 0xe8, 0x48, 0x2f, 0xe1, + 0x31, 0xe6, 0xa3, 0xed, 0xf5, 0xbc, 0x70, 0x34, 0xc6, 0x81, 0x4a, 0x13, 0x03, 0x1e, 0x54, 0xbf, + 0x6d, 0x06, 0x3d, 0xaf, 0xd0, 0x1b, 0x65, 0xa9, 0xc2, 0x77, 0x5f, 0x01, 0xdc, 0x1c, 0xc0, 0xad, + 0xc9, 0x5b, 0x00, 0xb7, 0x46, 0xea, 0xa2, 0x6c, 0xb6, 0x3e, 0xfe, 0xb8, 0x6a, 0x69, 0x67, 0x57, + 0x2d, 0xed, 0xd7, 0x55, 0x4b, 0xfb, 0x7e, 0xdd, 0x9a, 0x3b, 0xbb, 0x6e, 0xcd, 0x9d, 0x5f, 0xb7, + 0xe6, 0xbe, 0xfc, 0xf5, 0x02, 0xa5, 0xb3, 0x8f, 0xb4, 0xfa, 0x37, 0xee, 0x92, 0x7a, 0x4c, 0xf7, + 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0xb7, 0x85, 0x11, 0xd8, 0xc7, 0x05, 0x00, 0x00, +} + +func (m *BTCValidator) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pop != nil { + { + size, err := m.Pop.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *BTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SlashingTxSig != nil { + { + size := m.SlashingTxSig.Size() + i -= size + if _, err := m.SlashingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } + if m.SlashingTx != nil { + { + size, err := m.SlashingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + } + if m.StakingTxSig != nil { + { + size := m.StakingTxSig.Size() + i -= size + if _, err := m.StakingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + if m.StakingTx != nil { + { + size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + if m.TotalSat != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.TotalSat)) + i-- + dAtA[i] = 0x38 + } + if m.EndHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.EndHeight)) + i-- + dAtA[i] = 0x30 + } + if m.StartHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x28 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Pop != nil { + { + size, err := m.Pop.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ProofOfPossession) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProofOfPossession) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcSig != nil { + { + size := m.BtcSig.Size() + i -= size + if _, err := m.BtcSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.BabylonSig) > 0 { + i -= len(m.BabylonSig) + copy(dAtA[i:], m.BabylonSig) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.BabylonSig))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *StakingTx) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StakingTx) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StakingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.SlashingOutScript) > 0 { + i -= len(m.SlashingOutScript) + copy(dAtA[i:], m.SlashingOutScript) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.SlashingOutScript))) + i-- + dAtA[i] = 0x1a + } + if len(m.ChangeOutScript) > 0 { + i -= len(m.ChangeOutScript) + copy(dAtA[i:], m.ChangeOutScript) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.ChangeOutScript))) + i-- + dAtA[i] = 0x12 + } + if len(m.Tx) > 0 { + i -= len(m.Tx) + copy(dAtA[i:], m.Tx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SlashingTx) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SlashingTx) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SlashingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.OutScript) > 0 { + i -= len(m.OutScript) + copy(dAtA[i:], m.OutScript) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.OutScript))) + i-- + dAtA[i] = 0x12 + } + if len(m.Tx) > 0 { + i -= len(m.Tx) + copy(dAtA[i:], m.Tx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { + offset -= sovBtcstaking(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *BTCValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func (m *BTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.StartHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.StartHeight)) + } + if m.EndHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.EndHeight)) + } + if m.TotalSat != 0 { + n += 1 + sovBtcstaking(uint64(m.TotalSat)) + } + if m.StakingTx != nil { + l = m.StakingTx.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.StakingTxSig != nil { + l = m.StakingTxSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.SlashingTx != nil { + l = m.SlashingTx.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.SlashingTxSig != nil { + l = m.SlashingTxSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func (m *ProofOfPossession) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BabylonSig) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.BtcSig != nil { + l = m.BtcSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func (m *StakingTx) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Tx) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + l = len(m.ChangeOutScript) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + l = len(m.SlashingOutScript) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func (m *SlashingTx) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Tx) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + l = len(m.OutScript) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func sovBtcstaking(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozBtcstaking(x uint64) (n int) { + return sovBtcstaking(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *BTCValidator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EndHeight", wireType) + } + m.EndHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EndHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalSat", wireType) + } + m.TotalSat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalSat |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StakingTx == nil { + m.StakingTx = &StakingTx{} + } + if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.StakingTxSig = &v + if err := m.StakingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SlashingTx == nil { + m.SlashingTx = &SlashingTx{} + } + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.SlashingTxSig = &v + if err := m.SlashingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProofOfPossession: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProofOfPossession: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BabylonSig = append(m.BabylonSig[:0], dAtA[iNdEx:postIndex]...) + if m.BabylonSig == nil { + m.BabylonSig = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.BtcSig = &v + if err := m.BtcSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StakingTx) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StakingTx: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StakingTx: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) + if m.Tx == nil { + m.Tx = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeOutScript", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeOutScript = append(m.ChangeOutScript[:0], dAtA[iNdEx:postIndex]...) + if m.ChangeOutScript == nil { + m.ChangeOutScript = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingOutScript", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SlashingOutScript = append(m.SlashingOutScript[:0], dAtA[iNdEx:postIndex]...) + if m.SlashingOutScript == nil { + m.SlashingOutScript = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SlashingTx) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SlashingTx: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SlashingTx: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) + if m.Tx == nil { + m.Tx = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OutScript", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OutScript = append(m.OutScript[:0], dAtA[iNdEx:postIndex]...) + if m.OutScript == nil { + m.OutScript = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBtcstaking(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthBtcstaking + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupBtcstaking + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthBtcstaking + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthBtcstaking = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBtcstaking = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupBtcstaking = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 1c04c70db..455efb460 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,5 +6,6 @@ import ( // x/btcstaking module sentinel errors var ( - ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") + ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") ) diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 9f5376768..7785ed838 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -14,6 +14,11 @@ const ( MemStoreKey = "mem_btcstaking" ) +var ( + BTCValidatorKey = []byte{0x01} // key prefix for the BTC validators + BTCDelegationKey = []byte{0x02} // key prefix for the BTC delegations +) + func KeyPrefix(p string) []byte { return []byte(p) } From 16ced6f10a6dbfe3f4c9f63b3949c42b011010c2 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 19 Jun 2023 17:52:40 +1000 Subject: [PATCH 010/202] btcstaking: tx formats (#3) --- app/app.go | 7 +- client/docs/swagger-ui/swagger.yaml | 89 +- .../btccheckpoint/v1/btccheckpoint.proto | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 22 +- proto/babylon/btcstaking/v1/params.proto | 7 + proto/babylon/btcstaking/v1/tx.proto | 78 +- proto/babylon/zoneconcierge/v1/tx.proto | 2 +- testutil/keeper/btcstaking.go | 7 +- types/btc_slashing_tx.go | 37 + types/btc_staking_tx.go | 37 + x/btccheckpoint/types/btccheckpoint.pb.go | 2 +- x/btcstaking/genesis.go | 4 +- x/btcstaking/keeper/keeper.go | 5 + x/btcstaking/keeper/msg_server.go | 29 + x/btcstaking/keeper/params.go | 11 +- x/btcstaking/keeper/params_test.go | 3 +- x/btcstaking/keeper/query_params_test.go | 4 +- x/btcstaking/types/btcstaking.go | 27 + x/btcstaking/types/btcstaking.pb.go | 640 +----- x/btcstaking/types/codec.go | 13 +- x/btcstaking/types/msg.go | 95 + x/btcstaking/types/params.go | 8 +- x/btcstaking/types/params.pb.go | 129 +- x/btcstaking/types/tx.pb.go | 1803 ++++++++++++++++- x/epoching/types/codec.go | 12 - x/zoneconcierge/keeper/msg_server.go | 5 +- x/zoneconcierge/types/tx.pb.go | 4 +- 27 files changed, 2352 insertions(+), 730 deletions(-) create mode 100644 types/btc_slashing_tx.go create mode 100644 types/btc_staking_tx.go create mode 100644 x/btcstaking/types/btcstaking.go create mode 100644 x/btcstaking/types/msg.go diff --git a/app/app.go b/app/app.go index 23ba0990d..be63cd696 100644 --- a/app/app.go +++ b/app/app.go @@ -214,6 +214,10 @@ var ( transfer.AppModuleBasic{}, zoneconcierge.AppModuleBasic{}, ibcfee.AppModuleBasic{}, + + // BTC staking related + btcstaking.AppModuleBasic{}, + finality.AppModuleBasic{}, ) // module account permissions @@ -228,6 +232,7 @@ var ( ibcfeetypes.ModuleName: nil, // TODO: decide ZonConcierge's permissions here zctypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + // TODO: decide BTCStaking and Finality's permissiones here } ) @@ -611,7 +616,7 @@ func NewBabylonApp( // set up BTC staking keeper app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( - appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.GetSubspace(btcstakingtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.GetSubspace(btcstakingtypes.ModuleName), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.FinalityKeeper = finalitykeeper.NewKeeper( appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.GetSubspace(finalitytypes.ModuleName), app.AccountKeeper, app.BankKeeper, diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 14f146ca2..7d297243c 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -87,8 +87,7 @@ paths: valideated the proof? title: >- - TransactionInfo is the info of a tx that contains - Babylon checkpoint, + TransactionInfo is the info of a tx on Bitcoin, including @@ -404,16 +403,11 @@ paths: already processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains - Babylon checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: the BTC checkpoint transactions of the best submission best_submission_vigilante_address_list: @@ -8392,16 +8386,11 @@ paths: already processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains - Babylon checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: >- proof_epoch_submitted is the proof that the epoch's @@ -9437,8 +9426,7 @@ paths: valideated the proof? title: >- - TransactionInfo is the info of a tx that contains - Babylon checkpoint, + TransactionInfo is the info of a tx on Bitcoin, including @@ -10742,16 +10730,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: the BTC checkpoint transactions of the best submission best_submission_vigilante_address_list: @@ -10925,16 +10908,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: the BTC checkpoint transactions of the best submission best_submission_vigilante_address_list: @@ -11051,16 +11029,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: the BTC checkpoint transactions of the best submission best_submission_vigilante_address_list: @@ -11322,7 +11295,7 @@ definitions: valideated the proof? title: |- - TransactionInfo is the info of a tx that contains Babylon checkpoint, + TransactionInfo is the info of a tx on Bitcoin, including - the position of the tx on BTC blockchain - the full tx content @@ -16703,16 +16676,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: >- proof_epoch_submitted is the proof that the epoch's checkpoint is @@ -17303,16 +17271,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: >- proof_epoch_submitted is the proof that the epoch's checkpoint is @@ -18767,16 +18730,11 @@ definitions: processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: >- proof_epoch_submitted is the proof that the epoch's checkpoint is @@ -19564,16 +19522,11 @@ definitions: already processed and valideated the proof? - title: >- - TransactionInfo is the info of a tx that contains Babylon - checkpoint, - + title: |- + TransactionInfo is the info of a tx on Bitcoin, including - - the position of the tx on BTC blockchain - - the full tx content - - the Merkle proof that this tx is on the above position title: >- proof_epoch_submitted is the proof that the epoch's diff --git a/proto/babylon/btccheckpoint/v1/btccheckpoint.proto b/proto/babylon/btccheckpoint/v1/btccheckpoint.proto index 68126cc7b..c1fe3e6ec 100644 --- a/proto/babylon/btccheckpoint/v1/btccheckpoint.proto +++ b/proto/babylon/btccheckpoint/v1/btccheckpoint.proto @@ -71,7 +71,7 @@ enum BtcStatus { EPOCH_STATUS_FINALIZED = 2 [ (gogoproto.enumvalue_customname) = "Finalized" ]; } -// TransactionInfo is the info of a tx that contains Babylon checkpoint, +// TransactionInfo is the info of a tx on Bitcoin, // including // - the position of the tx on BTC blockchain // - the full tx content diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index c691d1164..37b8a6bbe 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -41,14 +41,14 @@ message BTCDelegation { uint64 total_sat = 7; // staking_tx is the staking tx // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - StakingTx staking_tx = 8; + bytes staking_tx = 8 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCStakingTx" ]; // staking_tx_sig is the signature of the staking tx // signed by SK corresponding to btc_pk bytes staking_tx_sig = 9 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - SlashingTx slashing_tx = 10; + bytes slashing_tx = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; // slashing_tx_sig is the signature of the slashing tx // signed by SK corresponding to btc_pk bytes slashing_tx_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; @@ -64,21 +64,3 @@ message ProofOfPossession { // the signature follows encoding in BIP-340 spec bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } - -// StakingTx defines a staking tx -message StakingTx { - // tx is the staking tx in bytes - bytes tx = 1; - // change_out_script is the output script of the change - bytes change_out_script = 2; - // slashing_out_script is the output script of the slashing path - bytes slashing_out_script = 3; -} - -// SlashingTx defines a slashing tx -message SlashingTx { - // tx is the slashing tx in bytes - bytes tx = 1; - // out_script is the output script of the slashing path - bytes out_script = 2; -} \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 6cbf4ed44..bba57ce49 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -8,4 +8,11 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; + + // jury_pk is the public key of jury + // the PK follows encoding in BIP-340 spec on Bitcoin + bytes jury_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // slashing address is the address that the slashed BTC goes to + // the address is on Bitcoin + bytes slashing_address = 2; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index ed0673580..fb9f82329 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -1,7 +1,83 @@ syntax = "proto3"; package babylon.btcstaking.v1; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; +import "cosmos/crypto/secp256k1/keys.proto"; +import "babylon/btcstaking/v1/params.proto"; +import "babylon/btcstaking/v1/btcstaking.proto"; +import "babylon/btccheckpoint/v1/btccheckpoint.proto"; + option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; // Msg defines the Msg service. -service Msg {} \ No newline at end of file +service Msg { + // CreateBTCValidator creates a new BTC validator + rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); + // CreateBTCDelegation creates a new BTC delegation + rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); + // UpdateParams updates the btcstaking module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgCreateBTCValidator is the message for creating a BTC validator +message MsgCreateBTCValidator { + string signer = 1; + // babylon_pk is the Babylon secp256k1 PK of this BTC validator + cosmos.crypto.secp256k1.PubKey babylon_pk = 2; + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // pop is the proof of possession of babylon_pk and btc_pk + ProofOfPossession pop = 4; +} +// MsgCreateBTCValidatorResponse is the response for MsgCreateBTCValidator +message MsgCreateBTCValidatorResponse {} + +// MsgCreateBTCDelegation is the message for creating a BTC delegation +message MsgCreateBTCDelegation { + string signer = 1; + // babylon_pk is the Babylon secp256k1 PK of this BTC delegation + cosmos.crypto.secp256k1.PubKey babylon_pk = 2; + // pop is the proof of possession of babylon_pk and btc_pk + ProofOfPossession pop = 3; + // staking_tx is the staking tx + // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain + bytes staking_tx = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCStakingTx" ]; + // staking_tx_sig is the signature of the staking tx + // signed by SK corresponding to btc_pk + bytes staking_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // staking_tx_info is the tx info of the staking tx, including the Merkle proof + babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 6; + // slashing_tx is the slashing tx + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + bytes slashing_tx = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; + // slashing_tx_sig is the signature of the slashing tx + // signed by SK corresponding to btc_pk + bytes slashing_tx_sig = 8 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} +// MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation +message MsgCreateBTCDelegationResponse {} + +// MsgUpdateParams defines a message for updating btcstaking module parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the epoching paramaeters parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +message MsgUpdateParamsResponse {} + + \ No newline at end of file diff --git a/proto/babylon/zoneconcierge/v1/tx.proto b/proto/babylon/zoneconcierge/v1/tx.proto index 4fc9a78b9..3c97ca6a8 100644 --- a/proto/babylon/zoneconcierge/v1/tx.proto +++ b/proto/babylon/zoneconcierge/v1/tx.proto @@ -11,7 +11,7 @@ option go_package = "github.com/babylonchain/babylon/x/zoneconcierge/types"; // Msg defines the Msg service. service Msg { - // UpdateParams updates the btccheckpoint module parameters. + // UpdateParams updates the zoneconcierge module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index 23956f39f..6365dff49 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -13,6 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" typesparams "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/stretchr/testify/require" ) @@ -43,12 +45,15 @@ func BTCStakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { paramsSubspace, nil, nil, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) // Initialize params - k.SetParams(ctx, types.DefaultParams()) + if err := k.SetParams(ctx, types.DefaultParams()); err != nil { + panic(err) + } return &k, ctx } diff --git a/types/btc_slashing_tx.go b/types/btc_slashing_tx.go new file mode 100644 index 000000000..bb2a84a15 --- /dev/null +++ b/types/btc_slashing_tx.go @@ -0,0 +1,37 @@ +package types + +type BTCSlashingTx []byte + +// TODO: constructors, verification rules, and util functions + +func (tx BTCSlashingTx) Marshal() ([]byte, error) { + return tx, nil +} + +func (tx BTCSlashingTx) MustMarshal() []byte { + txBytes, err := tx.Marshal() + if err != nil { + panic(err) + } + return txBytes +} + +func (tx BTCSlashingTx) MarshalTo(data []byte) (int, error) { + bz, err := tx.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (tx *BTCSlashingTx) Unmarshal(data []byte) error { + // TODO: verifications + + *tx = data + return nil +} + +func (tx *BTCSlashingTx) Size() int { + return len(tx.MustMarshal()) +} diff --git a/types/btc_staking_tx.go b/types/btc_staking_tx.go new file mode 100644 index 000000000..c186832c2 --- /dev/null +++ b/types/btc_staking_tx.go @@ -0,0 +1,37 @@ +package types + +type BTCStakingTx []byte + +// TODO: constructors, verification rules, and util functions + +func (tx BTCStakingTx) Marshal() ([]byte, error) { + return tx, nil +} + +func (tx BTCStakingTx) MustMarshal() []byte { + txBytes, err := tx.Marshal() + if err != nil { + panic(err) + } + return txBytes +} + +func (tx BTCStakingTx) MarshalTo(data []byte) (int, error) { + bz, err := tx.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (tx *BTCStakingTx) Unmarshal(data []byte) error { + // TODO: verifications + + *tx = data + return nil +} + +func (tx *BTCStakingTx) Size() int { + return len(tx.MustMarshal()) +} diff --git a/x/btccheckpoint/types/btccheckpoint.pb.go b/x/btccheckpoint/types/btccheckpoint.pb.go index 2c5545b37..e18ce0ac3 100644 --- a/x/btccheckpoint/types/btccheckpoint.pb.go +++ b/x/btccheckpoint/types/btccheckpoint.pb.go @@ -245,7 +245,7 @@ func (m *SubmissionKey) GetKey() []*TransactionKey { return nil } -// TransactionInfo is the info of a tx that contains Babylon checkpoint, +// TransactionInfo is the info of a tx on Bitcoin, // including // - the position of the tx on BTC blockchain // - the full tx content diff --git a/x/btcstaking/genesis.go b/x/btcstaking/genesis.go index 26bfa29ca..f8d5ff7cc 100644 --- a/x/btcstaking/genesis.go +++ b/x/btcstaking/genesis.go @@ -8,7 +8,9 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { - k.SetParams(ctx, genState.Params) + if err := k.SetParams(ctx, genState.Params); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 08ed06e3d..529739800 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -21,6 +21,9 @@ type ( accountKeeper types.AccountKeeper bankKeeper types.BankKeeper + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string } ) @@ -32,6 +35,7 @@ func NewKeeper( accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, + authority string, ) Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -46,6 +50,7 @@ func NewKeeper( accountKeeper: accountKeeper, bankKeeper: bankKeeper, + authority: authority, } } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 720f56c04..220b8e85f 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -1,7 +1,12 @@ package keeper import ( + "context" + + errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/btcstaking/types" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) type msgServer struct { @@ -15,3 +20,27 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { } var _ types.MsgServer = msgServer{} + +// UpdateParams updates the params +func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if ms.authority != req.Authority { + return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := ms.SetParams(ctx, req.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} + +// CreateBTCValidator creates a BTC validator +func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { + panic("TODO: implement me!") +} + +// CreateBTCDelegation creates a BTC delegation +func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { + panic("TODO: implement me!") +} diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index 2966f4e36..625014341 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -7,10 +7,17 @@ import ( // GetParams get all parameters as types.Params func (k Keeper) GetParams(ctx sdk.Context) types.Params { - return types.NewParams() + params := types.DefaultParams() + k.paramstore.GetParamSet(ctx, ¶ms) + return params } // SetParams set the params -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { + if err := params.Validate(); err != nil { + return err + } + k.paramstore.SetParamSet(ctx, ¶ms) + return nil } diff --git a/x/btcstaking/keeper/params_test.go b/x/btcstaking/keeper/params_test.go index 706a65f14..ce5b3e2cf 100644 --- a/x/btcstaking/keeper/params_test.go +++ b/x/btcstaking/keeper/params_test.go @@ -12,7 +12,8 @@ func TestGetParams(t *testing.T) { k, ctx := testkeeper.BTCStakingKeeper(t) params := types.DefaultParams() - k.SetParams(ctx, params) + err := k.SetParams(ctx, params) + require.NoError(t, err) require.EqualValues(t, params, k.GetParams(ctx)) } diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go index ad75513ef..998474150 100644 --- a/x/btcstaking/keeper/query_params_test.go +++ b/x/btcstaking/keeper/query_params_test.go @@ -13,7 +13,9 @@ func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.BTCStakingKeeper(t) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() - keeper.SetParams(ctx, params) + + err := keeper.SetParams(ctx, params) + require.NoError(t, err) response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) require.NoError(t, err) diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go new file mode 100644 index 000000000..5b88a8faf --- /dev/null +++ b/x/btcstaking/types/btcstaking.go @@ -0,0 +1,27 @@ +package types + +func (v *BTCValidator) ValidateBasic() error { + // TODO: validation rules + + if err := v.Pop.ValidateBasic(); err != nil { + return err + } + + return nil +} + +func (d *BTCDelegation) ValidateBasic() error { + // TODO: validation rules + + if err := d.Pop.ValidateBasic(); err != nil { + return err + } + + return nil +} + +func (p *ProofOfPossession) ValidateBasic() error { + // TODO: validation rules + + return nil +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index e71a6e298..d0fecdc08 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -107,14 +107,14 @@ type BTCDelegation struct { TotalSat uint64 `protobuf:"varint,7,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` // staking_tx is the staking tx // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - StakingTx *StakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + StakingTx *github_com_babylonchain_babylon_types.BTCStakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCStakingTx" json:"staking_tx,omitempty"` // staking_tx_sig is the signature of the staking tx // signed by SK corresponding to btc_pk StakingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,9,opt,name=staking_tx_sig,json=stakingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"staking_tx_sig,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - SlashingTx *SlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3" json:"slashing_tx,omitempty"` + SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` // slashing_tx_sig is the signature of the slashing tx // signed by SK corresponding to btc_pk SlashingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=slashing_tx_sig,json=slashingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_tx_sig,omitempty"` @@ -188,20 +188,6 @@ func (m *BTCDelegation) GetTotalSat() uint64 { return 0 } -func (m *BTCDelegation) GetStakingTx() *StakingTx { - if m != nil { - return m.StakingTx - } - return nil -} - -func (m *BTCDelegation) GetSlashingTx() *SlashingTx { - if m != nil { - return m.SlashingTx - } - return nil -} - // ProofOfPossession is the proof of possession that a Babylon secp256k1 // secret key and a Bitcoin secp256k1 secret key are held by the same // person @@ -253,131 +239,10 @@ func (m *ProofOfPossession) GetBabylonSig() []byte { return nil } -// StakingTx defines a staking tx -type StakingTx struct { - // tx is the staking tx in bytes - Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` - // change_out_script is the output script of the change - ChangeOutScript []byte `protobuf:"bytes,2,opt,name=change_out_script,json=changeOutScript,proto3" json:"change_out_script,omitempty"` - // slashing_out_script is the output script of the slashing path - SlashingOutScript []byte `protobuf:"bytes,3,opt,name=slashing_out_script,json=slashingOutScript,proto3" json:"slashing_out_script,omitempty"` -} - -func (m *StakingTx) Reset() { *m = StakingTx{} } -func (m *StakingTx) String() string { return proto.CompactTextString(m) } -func (*StakingTx) ProtoMessage() {} -func (*StakingTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{3} -} -func (m *StakingTx) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *StakingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_StakingTx.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *StakingTx) XXX_Merge(src proto.Message) { - xxx_messageInfo_StakingTx.Merge(m, src) -} -func (m *StakingTx) XXX_Size() int { - return m.Size() -} -func (m *StakingTx) XXX_DiscardUnknown() { - xxx_messageInfo_StakingTx.DiscardUnknown(m) -} - -var xxx_messageInfo_StakingTx proto.InternalMessageInfo - -func (m *StakingTx) GetTx() []byte { - if m != nil { - return m.Tx - } - return nil -} - -func (m *StakingTx) GetChangeOutScript() []byte { - if m != nil { - return m.ChangeOutScript - } - return nil -} - -func (m *StakingTx) GetSlashingOutScript() []byte { - if m != nil { - return m.SlashingOutScript - } - return nil -} - -// SlashingTx defines a slashing tx -type SlashingTx struct { - // tx is the slashing tx in bytes - Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` - // out_script is the output script of the slashing path - OutScript []byte `protobuf:"bytes,2,opt,name=out_script,json=outScript,proto3" json:"out_script,omitempty"` -} - -func (m *SlashingTx) Reset() { *m = SlashingTx{} } -func (m *SlashingTx) String() string { return proto.CompactTextString(m) } -func (*SlashingTx) ProtoMessage() {} -func (*SlashingTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{4} -} -func (m *SlashingTx) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *SlashingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_SlashingTx.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *SlashingTx) XXX_Merge(src proto.Message) { - xxx_messageInfo_SlashingTx.Merge(m, src) -} -func (m *SlashingTx) XXX_Size() int { - return m.Size() -} -func (m *SlashingTx) XXX_DiscardUnknown() { - xxx_messageInfo_SlashingTx.DiscardUnknown(m) -} - -var xxx_messageInfo_SlashingTx proto.InternalMessageInfo - -func (m *SlashingTx) GetTx() []byte { - if m != nil { - return m.Tx - } - return nil -} - -func (m *SlashingTx) GetOutScript() []byte { - if m != nil { - return m.OutScript - } - return nil -} - func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") - proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") - proto.RegisterType((*SlashingTx)(nil), "babylon.btcstaking.v1.SlashingTx") } func init() { @@ -385,45 +250,41 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 606 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0x4d, 0x6f, 0xd3, 0x40, - 0x10, 0xad, 0xd3, 0x4f, 0x4f, 0xd2, 0x56, 0x5d, 0x40, 0x8a, 0x8a, 0x9a, 0xa4, 0x39, 0xa0, 0x88, - 0x83, 0x4d, 0x5b, 0x5a, 0x09, 0x90, 0x40, 0x72, 0x39, 0x80, 0x00, 0x35, 0x72, 0x22, 0x0e, 0x5c, - 0xac, 0xb5, 0xbb, 0x75, 0x56, 0x76, 0xbd, 0x96, 0x77, 0x12, 0x9c, 0x7f, 0xc0, 0x91, 0x9f, 0xc5, - 0xb1, 0xc7, 0xd2, 0x43, 0x85, 0xda, 0x3f, 0x82, 0xbc, 0xfe, 0x68, 0xa4, 0x52, 0x21, 0xd4, 0x1b, - 0xb7, 0xf1, 0xcc, 0x9b, 0x37, 0xef, 0x79, 0x67, 0x17, 0x9e, 0xb8, 0xd4, 0x9d, 0x86, 0x22, 0x32, - 0x5d, 0xf4, 0x24, 0xd2, 0x80, 0x47, 0xbe, 0x39, 0xd9, 0x99, 0xf9, 0x32, 0xe2, 0x44, 0xa0, 0x20, - 0x8f, 0x0a, 0x9c, 0x31, 0x53, 0x99, 0xec, 0x6c, 0x3e, 0xf4, 0x85, 0x2f, 0x14, 0xc2, 0xcc, 0xa2, - 0x1c, 0xbc, 0xd9, 0xf5, 0x84, 0x3c, 0x15, 0xd2, 0xf4, 0x92, 0x69, 0x8c, 0xc2, 0x94, 0xcc, 0x8b, - 0x77, 0xf7, 0x0f, 0x82, 0x1d, 0x33, 0x60, 0x53, 0x99, 0x63, 0xba, 0x3f, 0x35, 0x68, 0x58, 0xc3, - 0xc3, 0xcf, 0x34, 0xe4, 0xc7, 0x14, 0x45, 0x42, 0x5e, 0x03, 0x14, 0x33, 0x9c, 0x38, 0x68, 0x6a, - 0x1d, 0xad, 0x57, 0xdf, 0x6d, 0x1b, 0x39, 0x93, 0x91, 0x33, 0x19, 0x15, 0x93, 0xd1, 0x1f, 0xbb, - 0x1f, 0xd8, 0xd4, 0xd6, 0x8b, 0x96, 0x7e, 0x40, 0x3e, 0xc1, 0x92, 0x8b, 0x5e, 0xd6, 0x5b, 0xeb, - 0x68, 0xbd, 0x86, 0x75, 0x70, 0x71, 0xd9, 0xde, 0xf5, 0x39, 0x8e, 0xc6, 0xae, 0xe1, 0x89, 0x53, - 0xb3, 0x40, 0x7a, 0x23, 0xca, 0xa3, 0xf2, 0xc3, 0xc4, 0x69, 0xcc, 0xa4, 0x61, 0xbd, 0xef, 0xef, - 0x3d, 0x7f, 0x56, 0x50, 0x2e, 0xba, 0xe8, 0xf5, 0x03, 0xf2, 0x12, 0xe6, 0x63, 0x11, 0x37, 0xe7, - 0x95, 0x8e, 0x9e, 0xf1, 0x47, 0xfb, 0x46, 0x3f, 0x11, 0xe2, 0xe4, 0xe8, 0xa4, 0x2f, 0xa4, 0x64, - 0x52, 0x72, 0x11, 0xd9, 0x59, 0x53, 0xf7, 0x7c, 0x11, 0x56, 0xad, 0xe1, 0xe1, 0x5b, 0x16, 0x32, - 0x9f, 0x22, 0x17, 0xd1, 0x7f, 0x64, 0x8e, 0x0c, 0x01, 0x26, 0x34, 0x74, 0x0a, 0x39, 0x0b, 0xf7, - 0x92, 0xb3, 0x32, 0xa1, 0xa1, 0xa5, 0x14, 0x6d, 0x43, 0x43, 0x22, 0x4d, 0xd0, 0x19, 0x31, 0xee, - 0x8f, 0xb0, 0xb9, 0xd8, 0xd1, 0x7a, 0x0b, 0x76, 0x5d, 0xe5, 0xde, 0xa9, 0x14, 0xd9, 0x02, 0x60, - 0xd1, 0x71, 0x09, 0x58, 0x52, 0x00, 0x9d, 0x45, 0xc7, 0x45, 0xf9, 0x31, 0xe8, 0x28, 0x90, 0x86, - 0x8e, 0xa4, 0xd8, 0x5c, 0x56, 0xd5, 0x15, 0x95, 0x18, 0x50, 0x24, 0x6f, 0x00, 0x0a, 0x67, 0x0e, - 0xa6, 0xcd, 0x15, 0xe5, 0xbb, 0x73, 0x87, 0xef, 0x41, 0x1e, 0x0e, 0x53, 0x5b, 0x97, 0x65, 0x48, - 0x1c, 0x58, 0xbb, 0x21, 0x70, 0x24, 0xf7, 0x9b, 0xba, 0x72, 0xfe, 0xe2, 0xe2, 0xb2, 0xbd, 0xff, - 0x2f, 0xce, 0x07, 0xdc, 0x8f, 0x28, 0x8e, 0x13, 0x66, 0x37, 0x2a, 0xf6, 0x01, 0xf7, 0x89, 0x05, - 0x75, 0x19, 0x52, 0x39, 0x2a, 0x24, 0x82, 0x92, 0xb8, 0x7d, 0x97, 0xc4, 0x02, 0x39, 0x4c, 0x6d, - 0x90, 0x55, 0x4c, 0x28, 0xac, 0xcf, 0x70, 0x28, 0x95, 0xf5, 0xfb, 0xaa, 0x5c, 0xbd, 0xe1, 0x1f, - 0x70, 0xbf, 0xfb, 0x4d, 0x83, 0x8d, 0x5b, 0x8b, 0x41, 0xda, 0x50, 0x2f, 0xd7, 0x3b, 0x1b, 0x9a, - 0xed, 0x77, 0xc3, 0x2e, 0x37, 0x3e, 0x73, 0x67, 0xc3, 0x72, 0xb6, 0x30, 0x59, 0xb1, 0x76, 0x5f, - 0x45, 0xd9, 0x4d, 0xc8, 0xa4, 0x7c, 0x05, 0xbd, 0x3a, 0x2a, 0xb2, 0x06, 0x35, 0x4c, 0x8b, 0xc1, - 0x35, 0x4c, 0xc9, 0x53, 0xd8, 0xf0, 0x46, 0x34, 0xf2, 0x99, 0x23, 0xc6, 0xe8, 0x48, 0x2f, 0xe1, - 0x31, 0xe6, 0xa3, 0xed, 0xf5, 0xbc, 0x70, 0x34, 0xc6, 0x81, 0x4a, 0x13, 0x03, 0x1e, 0x54, 0xbf, - 0x6d, 0x06, 0x3d, 0xaf, 0xd0, 0x1b, 0x65, 0xa9, 0xc2, 0x77, 0x5f, 0x01, 0xdc, 0x1c, 0xc0, 0xad, - 0xc9, 0x5b, 0x00, 0xb7, 0x46, 0xea, 0xa2, 0x6c, 0xb6, 0x3e, 0xfe, 0xb8, 0x6a, 0x69, 0x67, 0x57, - 0x2d, 0xed, 0xd7, 0x55, 0x4b, 0xfb, 0x7e, 0xdd, 0x9a, 0x3b, 0xbb, 0x6e, 0xcd, 0x9d, 0x5f, 0xb7, - 0xe6, 0xbe, 0xfc, 0xf5, 0x02, 0xa5, 0xb3, 0x8f, 0xb4, 0xfa, 0x37, 0xee, 0x92, 0x7a, 0x4c, 0xf7, - 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0xb7, 0x85, 0x11, 0xd8, 0xc7, 0x05, 0x00, 0x00, + // 540 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0xcf, 0x8f, 0xd2, 0x40, + 0x14, 0xa6, 0xfb, 0x83, 0x85, 0x07, 0xab, 0xb1, 0xd1, 0xa4, 0x59, 0x63, 0x41, 0x0e, 0x86, 0x53, + 0x2b, 0xe0, 0xae, 0xd1, 0x83, 0x87, 0xe2, 0x41, 0xa3, 0xc6, 0xa6, 0xad, 0xc6, 0x78, 0x69, 0xa6, + 0x65, 0x76, 0x98, 0xb4, 0xdb, 0x69, 0x3a, 0x03, 0x81, 0xbb, 0x07, 0x8f, 0xfe, 0x59, 0x1e, 0xf7, + 0xa8, 0x1c, 0x88, 0x81, 0x7f, 0xc4, 0x74, 0x28, 0x2c, 0x89, 0x26, 0xba, 0xe1, 0xb6, 0xb7, 0xd7, + 0xf7, 0xbe, 0xef, 0x7b, 0xdf, 0x6b, 0xde, 0x1b, 0x78, 0x14, 0xa0, 0x60, 0x1a, 0xb3, 0xc4, 0x0c, + 0x44, 0xc8, 0x05, 0x8a, 0x68, 0x42, 0xcc, 0x71, 0x67, 0xeb, 0xcb, 0x48, 0x33, 0x26, 0x98, 0x7a, + 0xaf, 0xc0, 0x19, 0x5b, 0x95, 0x71, 0xe7, 0xe4, 0x2e, 0x61, 0x84, 0x49, 0x84, 0x99, 0x47, 0x2b, + 0xf0, 0x49, 0x2b, 0x64, 0xfc, 0x82, 0x71, 0x33, 0xcc, 0xa6, 0xa9, 0x60, 0x26, 0xc7, 0x61, 0xda, + 0x3d, 0x3d, 0x8b, 0x3a, 0x66, 0x84, 0xa7, 0x7c, 0x85, 0x69, 0xfd, 0x54, 0xa0, 0x6e, 0x79, 0xfd, + 0x8f, 0x28, 0xa6, 0x03, 0x24, 0x58, 0xa6, 0xbe, 0x00, 0x28, 0x7a, 0xf8, 0x69, 0xa4, 0x29, 0x4d, + 0xa5, 0x5d, 0xeb, 0x36, 0x8c, 0x95, 0x92, 0xb1, 0x52, 0x32, 0x36, 0x4a, 0x86, 0x3d, 0x0a, 0xde, + 0xe0, 0xa9, 0x53, 0x2d, 0x28, 0x76, 0xa4, 0xbe, 0x83, 0x72, 0x20, 0xc2, 0x9c, 0xbb, 0xd7, 0x54, + 0xda, 0x75, 0xeb, 0x6c, 0x36, 0x6f, 0x74, 0x09, 0x15, 0xc3, 0x51, 0x60, 0x84, 0xec, 0xc2, 0x2c, + 0x90, 0xe1, 0x10, 0xd1, 0x64, 0xfd, 0x61, 0x8a, 0x69, 0x8a, 0xb9, 0x61, 0xbd, 0xb6, 0x7b, 0x4f, + 0x1e, 0x17, 0x92, 0x87, 0x81, 0x08, 0xed, 0x48, 0x7d, 0x0e, 0xfb, 0x29, 0x4b, 0xb5, 0x7d, 0xe9, + 0xa3, 0x6d, 0xfc, 0x75, 0x7c, 0xc3, 0xce, 0x18, 0x3b, 0x7f, 0x7f, 0x6e, 0x33, 0xce, 0x31, 0xe7, + 0x94, 0x25, 0x4e, 0x4e, 0x6a, 0x7d, 0x29, 0xc3, 0xb1, 0xe5, 0xf5, 0x5f, 0xe2, 0x18, 0x13, 0x24, + 0x28, 0x4b, 0x6e, 0xd0, 0x70, 0xaa, 0x07, 0x30, 0x46, 0xb1, 0x5f, 0xd8, 0x39, 0xd8, 0xc9, 0x4e, + 0x65, 0x8c, 0x62, 0x4b, 0x3a, 0x7a, 0x08, 0x75, 0x2e, 0x50, 0x26, 0xfc, 0x21, 0xa6, 0x64, 0x28, + 0xb4, 0xc3, 0xa6, 0xd2, 0x3e, 0x70, 0x6a, 0x32, 0xf7, 0x4a, 0xa6, 0xd4, 0x07, 0x00, 0x38, 0x19, + 0xac, 0x01, 0x65, 0x09, 0xa8, 0xe2, 0x64, 0x50, 0x94, 0xef, 0x43, 0x55, 0x30, 0x81, 0x62, 0x9f, + 0x23, 0xa1, 0x1d, 0xc9, 0x6a, 0x45, 0x26, 0x5c, 0x24, 0xd4, 0x0f, 0x00, 0xc5, 0x64, 0xbe, 0x98, + 0x68, 0x95, 0x6b, 0x9b, 0xf6, 0xfa, 0xee, 0x8a, 0xee, 0x4d, 0x9c, 0x2a, 0x5f, 0x87, 0xaa, 0x0f, + 0xb7, 0xae, 0x64, 0x7d, 0x4e, 0x89, 0x56, 0x95, 0xd2, 0xcf, 0x66, 0xf3, 0xc6, 0xe9, 0x75, 0xfe, + 0x87, 0x4b, 0x49, 0x82, 0xc4, 0x28, 0xc3, 0x4e, 0x7d, 0xa3, 0xee, 0x52, 0xa2, 0x7e, 0x82, 0x1a, + 0x8f, 0x11, 0x1f, 0x16, 0xc6, 0x41, 0xaa, 0x3f, 0x9d, 0xcd, 0x1b, 0xbd, 0xff, 0x37, 0x5e, 0xf0, + 0xbd, 0x89, 0x03, 0x7c, 0x13, 0xab, 0x08, 0x6e, 0x6f, 0x29, 0x4b, 0xef, 0xb5, 0x5d, 0xbd, 0x1f, + 0x5f, 0xe9, 0xbb, 0x94, 0xb4, 0xbe, 0x2a, 0x70, 0xe7, 0x8f, 0x25, 0x52, 0x1b, 0x50, 0x5b, 0x9f, + 0x42, 0xde, 0x34, 0xbf, 0x85, 0xba, 0xb3, 0xbe, 0x8e, 0x7c, 0x66, 0x07, 0x8e, 0xf2, 0xe5, 0xca, + 0x8b, 0x7b, 0xbb, 0x3a, 0xca, 0xaf, 0xc6, 0xa5, 0xc4, 0x7a, 0xfb, 0x7d, 0xa1, 0x2b, 0x97, 0x0b, + 0x5d, 0xf9, 0xb5, 0xd0, 0x95, 0x6f, 0x4b, 0xbd, 0x74, 0xb9, 0xd4, 0x4b, 0x3f, 0x96, 0x7a, 0xe9, + 0xf3, 0x3f, 0x37, 0x60, 0xb2, 0xfd, 0x34, 0xca, 0x2e, 0x41, 0x59, 0x3e, 0x61, 0xbd, 0xdf, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xd1, 0xe1, 0x97, 0xe1, 0x3d, 0x05, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -519,11 +380,11 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { } if m.SlashingTx != nil { { - size, err := m.SlashingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { + size := m.SlashingTx.Size() + i -= size + if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { return 0, err } - i -= size i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- @@ -543,11 +404,11 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { } if m.StakingTx != nil { { - size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { + size := m.StakingTx.Size() + i -= size + if _, err := m.StakingTx.MarshalTo(dAtA[i:]); err != nil { return 0, err } - i -= size i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- @@ -661,87 +522,6 @@ func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *StakingTx) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *StakingTx) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *StakingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.SlashingOutScript) > 0 { - i -= len(m.SlashingOutScript) - copy(dAtA[i:], m.SlashingOutScript) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.SlashingOutScript))) - i-- - dAtA[i] = 0x1a - } - if len(m.ChangeOutScript) > 0 { - i -= len(m.ChangeOutScript) - copy(dAtA[i:], m.ChangeOutScript) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.ChangeOutScript))) - i-- - dAtA[i] = 0x12 - } - if len(m.Tx) > 0 { - i -= len(m.Tx) - copy(dAtA[i:], m.Tx) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *SlashingTx) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SlashingTx) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *SlashingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.OutScript) > 0 { - i -= len(m.OutScript) - copy(dAtA[i:], m.OutScript) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.OutScript))) - i-- - dAtA[i] = 0x12 - } - if len(m.Tx) > 0 { - i -= len(m.Tx) - copy(dAtA[i:], m.Tx) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { offset -= sovBtcstaking(v) base := offset @@ -841,44 +621,6 @@ func (m *ProofOfPossession) Size() (n int) { return n } -func (m *StakingTx) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Tx) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - l = len(m.ChangeOutScript) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - l = len(m.SlashingOutScript) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - return n -} - -func (m *SlashingTx) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Tx) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - l = len(m.OutScript) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - return n -} - func sovBtcstaking(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1274,7 +1016,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -1284,24 +1026,23 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - if m.StakingTx == nil { - m.StakingTx = &StakingTx{} - } + var v github_com_babylonchain_babylon_types.BTCStakingTx + m.StakingTx = &v if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1345,7 +1086,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -1355,24 +1096,23 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - if m.SlashingTx == nil { - m.SlashingTx = &SlashingTx{} - } + var v github_com_babylonchain_babylon_types.BTCSlashingTx + m.SlashingTx = &v if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1552,276 +1292,6 @@ func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { } return nil } -func (m *StakingTx) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StakingTx: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StakingTx: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) - if m.Tx == nil { - m.Tx = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ChangeOutScript", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ChangeOutScript = append(m.ChangeOutScript[:0], dAtA[iNdEx:postIndex]...) - if m.ChangeOutScript == nil { - m.ChangeOutScript = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingOutScript", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SlashingOutScript = append(m.SlashingOutScript[:0], dAtA[iNdEx:postIndex]...) - if m.SlashingOutScript == nil { - m.SlashingOutScript = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBtcstaking(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBtcstaking - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SlashingTx) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SlashingTx: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SlashingTx: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) - if m.Tx == nil { - m.Tx = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field OutScript", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.OutScript = append(m.OutScript[:0], dAtA[iNdEx:postIndex]...) - if m.OutScript == nil { - m.OutScript = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBtcstaking(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBtcstaking - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipBtcstaking(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index 858010906..a2de41890 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -3,15 +3,24 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) func RegisterCodec(cdc *codec.LegacyAmino) { - + cdc.RegisterConcrete(&MsgCreateBTCValidator{}, "btcstaking/MsgCreateBTCValidator", nil) + cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) + cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + // Register messages + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgCreateBTCValidator{}, + &MsgCreateBTCDelegation{}, + &MsgUpdateParams{}, + ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go new file mode 100644 index 000000000..ac660dd98 --- /dev/null +++ b/x/btcstaking/types/msg.go @@ -0,0 +1,95 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ensure that these message types implement the sdk.Msg interface +var ( + _ sdk.Msg = &MsgUpdateParams{} + _ sdk.Msg = &MsgCreateBTCValidator{} + _ sdk.Msg = &MsgCreateBTCDelegation{} +) + +// GetSigners returns the expected signers for a MsgUpdateParams message. +func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(m.Authority) + return []sdk.AccAddress{addr} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgUpdateParams) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { + return errorsmod.Wrap(err, "invalid authority address") + } + + if err := m.Params.Validate(); err != nil { + return err + } + + return nil +} + +func (m *MsgCreateBTCValidator) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgCreateBTCValidator) ValidateBasic() error { + if m.BabylonPk == nil { + return fmt.Errorf("empty BabylonPk") + } + if m.BtcPk == nil { + return fmt.Errorf("empty BtcPk") + } + if m.Pop == nil { + return fmt.Errorf("empty Pop") + } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + return err + } + return m.Pop.ValidateBasic() +} + +func (m *MsgCreateBTCDelegation) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgCreateBTCDelegation) ValidateBasic() error { + if m.BabylonPk == nil { + return fmt.Errorf("empty BabylonPk") + } + if m.Pop == nil { + return fmt.Errorf("empty Pop") + } + if m.StakingTx == nil { + return fmt.Errorf("empty StakingTx") + } + if m.StakingTxSig == nil { + return fmt.Errorf("empty StakingTxSig") + } + if m.StakingTxInfo == nil { + return fmt.Errorf("empty StakingTxInfo") + } + if m.SlashingTx == nil { + return fmt.Errorf("empty SlashingTx") + } + if m.SlashingTxSig == nil { + return fmt.Errorf("empty SlashingTxSig") + } + + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + return err + } + return m.Pop.ValidateBasic() +} diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 357196ad6..7f0d4f59e 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -12,14 +12,10 @@ func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -// NewParams creates a new Params instance -func NewParams() Params { - return Params{} -} - // DefaultParams returns a default set of parameters +// TODO: decide default parameters? func DefaultParams() Params { - return NewParams() + return Params{} } // ParamSetPairs get the params.ParamSet diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 78a64c37c..5bc7e4c80 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -25,6 +26,12 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { + // jury_pk is the public key of jury + // the PK follows encoding in BIP-340 spec on Bitcoin + JuryPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=jury_pk,json=juryPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"jury_pk,omitempty"` + // slashing address is the address that the slashed BTC goes to + // the address is on Bitcoin + SlashingAddress []byte `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -59,6 +66,13 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo +func (m *Params) GetSlashingAddress() []byte { + if m != nil { + return m.SlashingAddress + } + return nil +} + func init() { proto.RegisterType((*Params)(nil), "babylon.btcstaking.v1.Params") } @@ -68,17 +82,22 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 160 bytes of a gzipped FileDescriptorProto + // 238 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, - 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0xf8, 0xb8, 0xd8, 0x02, 0xc0, 0x9a, 0xad, 0x58, 0x66, 0x2c, - 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, - 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xa3, - 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x0d, 0xc9, 0x19, 0x89, - 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0xb2, 0xa3, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, - 0x96, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xaf, 0xe9, 0x04, 0xb7, 0x00, 0x00, 0x00, + 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0xba, 0x18, 0xb9, 0xd8, 0x02, 0xc0, 0xba, 0x85, 0xfc, 0xb9, + 0xd8, 0xb3, 0x4a, 0x8b, 0x2a, 0xe3, 0x0b, 0xb2, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x9c, 0xcc, + 0x6e, 0xdd, 0x93, 0x37, 0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, + 0x9a, 0x9b, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x97, 0x54, 0x16, 0xa4, 0x16, 0xeb, 0x39, + 0x79, 0x06, 0x18, 0x9b, 0x18, 0x04, 0x94, 0x26, 0x79, 0xa7, 0x56, 0x06, 0xb1, 0x81, 0x8c, 0x09, + 0xc8, 0x16, 0xd2, 0xe4, 0x12, 0x28, 0xce, 0x49, 0x2c, 0xce, 0xc8, 0xcc, 0x4b, 0x8f, 0x4f, 0x4c, + 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x02, 0x99, 0x1c, 0xc4, 0x0f, 0x13, 0x77, 0x84, 0x08, + 0x5b, 0xb1, 0xcc, 0x58, 0x20, 0xcf, 0xe0, 0xe4, 0x73, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, + 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, + 0x72, 0x0c, 0x51, 0x04, 0x9d, 0x51, 0x81, 0x1c, 0x22, 0x60, 0x37, 0x25, 0xb1, 0x81, 0x7d, 0x68, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xba, 0xda, 0xfc, 0xdc, 0x34, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -101,6 +120,25 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.SlashingAddress) > 0 { + i -= len(m.SlashingAddress) + copy(dAtA[i:], m.SlashingAddress) + i = encodeVarintParams(dAtA, i, uint64(len(m.SlashingAddress))) + i-- + dAtA[i] = 0x12 + } + if m.JuryPk != nil { + { + size := m.JuryPk.Size() + i -= size + if _, err := m.JuryPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } return len(dAtA) - i, nil } @@ -121,6 +159,14 @@ func (m *Params) Size() (n int) { } var l int _ = l + if m.JuryPk != nil { + l = m.JuryPk.Size() + n += 1 + l + sovParams(uint64(l)) + } + l = len(m.SlashingAddress) + if l > 0 { + n += 1 + l + sovParams(uint64(l)) + } return n } @@ -159,6 +205,75 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JuryPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.JuryPk = &v + if err := m.JuryPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SlashingAddress = append(m.SlashingAddress[:0], dAtA[iNdEx:postIndex]...) + if m.SlashingAddress == nil { + m.SlashingAddress = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index eddbdf6c8..5a54ab04e 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -6,10 +6,20 @@ package types import ( context "context" fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + types "github.com/babylonchain/babylon/x/btccheckpoint/types" + _ "github.com/cosmos/cosmos-proto" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,19 +33,387 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// MsgCreateBTCValidator is the message for creating a BTC validator +type MsgCreateBTCValidator struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // babylon_pk is the Babylon secp256k1 PK of this BTC validator + BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // pop is the proof of possession of babylon_pk and btc_pk + Pop *ProofOfPossession `protobuf:"bytes,4,opt,name=pop,proto3" json:"pop,omitempty"` +} + +func (m *MsgCreateBTCValidator) Reset() { *m = MsgCreateBTCValidator{} } +func (m *MsgCreateBTCValidator) String() string { return proto.CompactTextString(m) } +func (*MsgCreateBTCValidator) ProtoMessage() {} +func (*MsgCreateBTCValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{0} +} +func (m *MsgCreateBTCValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateBTCValidator.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateBTCValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateBTCValidator.Merge(m, src) +} +func (m *MsgCreateBTCValidator) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateBTCValidator) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateBTCValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateBTCValidator proto.InternalMessageInfo + +func (m *MsgCreateBTCValidator) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgCreateBTCValidator) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *MsgCreateBTCValidator) GetPop() *ProofOfPossession { + if m != nil { + return m.Pop + } + return nil +} + +// MsgCreateBTCValidatorResponse is the response for MsgCreateBTCValidator +type MsgCreateBTCValidatorResponse struct { +} + +func (m *MsgCreateBTCValidatorResponse) Reset() { *m = MsgCreateBTCValidatorResponse{} } +func (m *MsgCreateBTCValidatorResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateBTCValidatorResponse) ProtoMessage() {} +func (*MsgCreateBTCValidatorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{1} +} +func (m *MsgCreateBTCValidatorResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateBTCValidatorResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateBTCValidatorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateBTCValidatorResponse.Merge(m, src) +} +func (m *MsgCreateBTCValidatorResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateBTCValidatorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateBTCValidatorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateBTCValidatorResponse proto.InternalMessageInfo + +// MsgCreateBTCDelegation is the message for creating a BTC delegation +type MsgCreateBTCDelegation struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // babylon_pk is the Babylon secp256k1 PK of this BTC delegation + BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // pop is the proof of possession of babylon_pk and btc_pk + Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` + // staking_tx is the staking tx + // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain + StakingTx *github_com_babylonchain_babylon_types.BTCStakingTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCStakingTx" json:"staking_tx,omitempty"` + // staking_tx_sig is the signature of the staking tx + // signed by SK corresponding to btc_pk + StakingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=staking_tx_sig,json=stakingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"staking_tx_sig,omitempty"` + // staking_tx_info is the tx info of the staking tx, including the Merkle proof + StakingTxInfo *types.TransactionInfo `protobuf:"bytes,6,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` + // slashing_tx is the slashing tx + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,7,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` + // slashing_tx_sig is the signature of the slashing tx + // signed by SK corresponding to btc_pk + SlashingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,8,opt,name=slashing_tx_sig,json=slashingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_tx_sig,omitempty"` +} + +func (m *MsgCreateBTCDelegation) Reset() { *m = MsgCreateBTCDelegation{} } +func (m *MsgCreateBTCDelegation) String() string { return proto.CompactTextString(m) } +func (*MsgCreateBTCDelegation) ProtoMessage() {} +func (*MsgCreateBTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{2} +} +func (m *MsgCreateBTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateBTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateBTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateBTCDelegation.Merge(m, src) +} +func (m *MsgCreateBTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateBTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateBTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateBTCDelegation proto.InternalMessageInfo + +func (m *MsgCreateBTCDelegation) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgCreateBTCDelegation) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *MsgCreateBTCDelegation) GetPop() *ProofOfPossession { + if m != nil { + return m.Pop + } + return nil +} + +func (m *MsgCreateBTCDelegation) GetStakingTxInfo() *types.TransactionInfo { + if m != nil { + return m.StakingTxInfo + } + return nil +} + +// MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation +type MsgCreateBTCDelegationResponse struct { +} + +func (m *MsgCreateBTCDelegationResponse) Reset() { *m = MsgCreateBTCDelegationResponse{} } +func (m *MsgCreateBTCDelegationResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateBTCDelegationResponse) ProtoMessage() {} +func (*MsgCreateBTCDelegationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{3} +} +func (m *MsgCreateBTCDelegationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateBTCDelegationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateBTCDelegationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateBTCDelegationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateBTCDelegationResponse.Merge(m, src) +} +func (m *MsgCreateBTCDelegationResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateBTCDelegationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateBTCDelegationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateBTCDelegationResponse proto.InternalMessageInfo + +// MsgUpdateParams defines a message for updating btcstaking module parameters. +type MsgUpdateParams struct { + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params defines the epoching paramaeters parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{4} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{5} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgCreateBTCValidator)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidator") + proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") + proto.RegisterType((*MsgCreateBTCDelegation)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegation") + proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") +} + func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 137 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, - 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xca, 0xeb, 0x21, - 0xe4, 0xf5, 0xca, 0x0c, 0x8d, 0x58, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0x7c, 0x4e, 0x3c, 0x92, - 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, - 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x28, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, - 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x44, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x6c, - 0x63, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x4a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x8c, 0xcc, 0x21, 0x9b, 0x94, 0x00, 0x00, 0x00, + // 723 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0xcb, 0x6e, 0xd3, 0x4c, + 0x14, 0xc7, 0xe3, 0xa6, 0xcd, 0xf7, 0x65, 0x7a, 0x93, 0x86, 0x5e, 0xd2, 0x48, 0x75, 0xa2, 0x2c, + 0xaa, 0x80, 0x5a, 0x9b, 0xa4, 0x17, 0x44, 0x91, 0x90, 0x48, 0xd9, 0x54, 0x10, 0x11, 0x9c, 0x14, + 0x21, 0x36, 0xd1, 0xd8, 0x99, 0x4c, 0x46, 0x49, 0x3c, 0x96, 0x67, 0x52, 0x25, 0x62, 0xc7, 0x13, + 0xb0, 0xe2, 0x39, 0x58, 0xf0, 0x10, 0x5d, 0x56, 0xac, 0x50, 0x17, 0x11, 0x6a, 0x91, 0x78, 0x01, + 0x56, 0xac, 0x50, 0xec, 0x49, 0x6c, 0x90, 0x2b, 0x1a, 0x10, 0xbb, 0x39, 0x39, 0xbf, 0xf3, 0x3f, + 0x97, 0x39, 0x19, 0x03, 0xd5, 0x44, 0xe6, 0xa0, 0xc3, 0x6c, 0xdd, 0x14, 0x16, 0x17, 0xa8, 0x4d, + 0x6d, 0xa2, 0x9f, 0x16, 0x74, 0xd1, 0xd7, 0x1c, 0x97, 0x09, 0x06, 0x57, 0xa5, 0x5f, 0x0b, 0xfc, + 0xda, 0x69, 0x21, 0xbd, 0x42, 0x18, 0x61, 0x1e, 0xa1, 0x8f, 0x4e, 0x3e, 0x9c, 0xde, 0xb0, 0x18, + 0xef, 0x32, 0x5e, 0xf7, 0x1d, 0xbe, 0x21, 0x5d, 0xeb, 0xbe, 0xa5, 0x77, 0xb9, 0xa7, 0xdf, 0xe5, + 0x44, 0x3a, 0x72, 0xd2, 0x61, 0xb9, 0x03, 0x47, 0x30, 0x9d, 0x63, 0xcb, 0x29, 0xee, 0x1f, 0xb4, + 0x0b, 0x7a, 0x1b, 0x0f, 0xc6, 0xc1, 0xb9, 0xe8, 0x22, 0x1d, 0xe4, 0xa2, 0xee, 0x98, 0xd9, 0x8a, + 0x66, 0x42, 0x65, 0xfb, 0xdc, 0x76, 0x88, 0xb3, 0x5a, 0xd8, 0x6a, 0x3b, 0x8c, 0xda, 0x42, 0xa2, + 0xc1, 0x0f, 0x3e, 0x9d, 0xfb, 0xae, 0x80, 0xd5, 0x32, 0x27, 0x47, 0x2e, 0x46, 0x02, 0x97, 0x6a, + 0x47, 0x2f, 0x50, 0x87, 0x36, 0x90, 0x60, 0x2e, 0x5c, 0x03, 0x09, 0x4e, 0x89, 0x8d, 0xdd, 0x94, + 0x92, 0x55, 0xf2, 0x49, 0x43, 0x5a, 0xf0, 0x21, 0x00, 0x32, 0x43, 0xdd, 0x69, 0xa7, 0x66, 0xb2, + 0x4a, 0x7e, 0xbe, 0x98, 0xd1, 0xe4, 0x2c, 0xfc, 0x26, 0xb5, 0x49, 0x93, 0x5a, 0xa5, 0x67, 0x3e, + 0xc1, 0x03, 0x23, 0x29, 0x43, 0x2a, 0x6d, 0x58, 0x06, 0x09, 0x53, 0x58, 0xa3, 0xd8, 0x78, 0x56, + 0xc9, 0x2f, 0x94, 0x0e, 0x2e, 0x86, 0x99, 0x22, 0xa1, 0xa2, 0xd5, 0x33, 0x35, 0x8b, 0x75, 0x75, + 0x49, 0x5a, 0x2d, 0x44, 0xed, 0xb1, 0xa1, 0x8b, 0x81, 0x83, 0xb9, 0x56, 0x3a, 0xae, 0xec, 0xee, + 0xdd, 0x95, 0x92, 0x73, 0xa6, 0xb0, 0x2a, 0x6d, 0x78, 0x08, 0xe2, 0x0e, 0x73, 0x52, 0xb3, 0x5e, + 0x1d, 0x79, 0x2d, 0xf2, 0x36, 0xb5, 0x8a, 0xcb, 0x58, 0xf3, 0x59, 0xb3, 0xc2, 0x38, 0xc7, 0x9c, + 0x53, 0x66, 0x1b, 0xa3, 0xa0, 0x5c, 0x06, 0x6c, 0x46, 0xf6, 0x6e, 0x60, 0xee, 0x30, 0x9b, 0xe3, + 0xdc, 0xb7, 0x59, 0xb0, 0x16, 0x26, 0x1e, 0xe3, 0x0e, 0x26, 0x48, 0x50, 0x66, 0xff, 0xb3, 0xf1, + 0xc8, 0x7e, 0xe2, 0x7f, 0xd0, 0x0f, 0x3c, 0x01, 0x40, 0x42, 0x75, 0xd1, 0xf7, 0x46, 0x32, 0xdd, + 0x78, 0x6b, 0x47, 0x55, 0x3f, 0xbc, 0xd6, 0x37, 0x92, 0x7c, 0x7c, 0x84, 0x75, 0xb0, 0x14, 0xc8, + 0xd6, 0x39, 0x25, 0xa9, 0x39, 0x4f, 0xfa, 0xfe, 0xc5, 0x30, 0xb3, 0x3f, 0xcd, 0xcd, 0x55, 0x29, + 0xb1, 0x91, 0xe8, 0xb9, 0xd8, 0x58, 0x98, 0xa8, 0x57, 0x29, 0x81, 0xcf, 0xc1, 0x72, 0x28, 0x01, + 0xb5, 0x9b, 0x2c, 0x95, 0xf0, 0xfa, 0xbf, 0x1d, 0xee, 0x3f, 0xb4, 0xbb, 0xa7, 0x05, 0xad, 0xe6, + 0x22, 0x9b, 0x23, 0x6b, 0x74, 0x17, 0xc7, 0x76, 0x93, 0x19, 0x8b, 0x13, 0xc5, 0x91, 0x09, 0x5f, + 0x82, 0x79, 0xde, 0x41, 0xbc, 0x25, 0x67, 0xf1, 0x9f, 0x57, 0xf0, 0xbd, 0x8b, 0x61, 0x66, 0xf7, + 0xe6, 0xb3, 0x90, 0xf1, 0xb5, 0xbe, 0x01, 0xf8, 0xe4, 0x0c, 0x11, 0x58, 0x0e, 0x29, 0x7b, 0xe3, + 0xf8, 0xff, 0x6f, 0xc7, 0xb1, 0x18, 0xe8, 0x57, 0x29, 0xc9, 0x65, 0x81, 0x1a, 0xbd, 0x75, 0x93, + 0xc5, 0x7c, 0xa7, 0x80, 0xe5, 0x32, 0x27, 0x27, 0x4e, 0x03, 0x09, 0x5c, 0xf1, 0x9e, 0x09, 0x78, + 0x00, 0x92, 0xa8, 0x27, 0x5a, 0xcc, 0xa5, 0x62, 0xe0, 0x2f, 0x65, 0x29, 0xf5, 0xf1, 0xc3, 0xce, + 0x8a, 0xdc, 0xbd, 0x47, 0x8d, 0x86, 0x8b, 0x39, 0xaf, 0x0a, 0x97, 0xda, 0xc4, 0x08, 0x50, 0xf8, + 0x00, 0x24, 0xfc, 0x87, 0x46, 0x6e, 0xeb, 0xe6, 0x75, 0x4b, 0xe7, 0x41, 0xa5, 0xd9, 0xb3, 0x61, + 0x26, 0x66, 0xc8, 0x90, 0xc3, 0xa5, 0x37, 0x5f, 0xdf, 0xdf, 0x09, 0xc4, 0x72, 0x1b, 0x60, 0xfd, + 0x97, 0xba, 0xc6, 0x35, 0x17, 0xbf, 0xcc, 0x80, 0x78, 0x99, 0x13, 0xd8, 0x07, 0x30, 0xe2, 0xb9, + 0xd9, 0xbe, 0x26, 0x6b, 0xe4, 0x1f, 0x34, 0xbd, 0x37, 0x0d, 0x3d, 0xae, 0x00, 0xbe, 0x06, 0xb7, + 0xa2, 0xfe, 0xca, 0x3b, 0x37, 0x10, 0x0b, 0xf0, 0xf4, 0xfe, 0x54, 0xf8, 0x24, 0x79, 0x13, 0x2c, + 0xfc, 0x74, 0x5d, 0x5b, 0xd7, 0xcb, 0x84, 0xb9, 0xb4, 0x76, 0x33, 0x6e, 0x9c, 0xa7, 0xf4, 0xf4, + 0xec, 0x52, 0x55, 0xce, 0x2f, 0x55, 0xe5, 0xf3, 0xa5, 0xaa, 0xbc, 0xbd, 0x52, 0x63, 0xe7, 0x57, + 0x6a, 0xec, 0xd3, 0x95, 0x1a, 0x7b, 0xf5, 0xdb, 0x67, 0xa0, 0x1f, 0xfe, 0xb6, 0x78, 0x9b, 0x6a, + 0x26, 0xbc, 0xcf, 0xc4, 0xee, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x51, 0x30, 0x8b, 0xd0, 0x47, + 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +428,12 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + // CreateBTCValidator creates a new BTC validator + CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) + // CreateBTCDelegation creates a new BTC delegation + CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) + // UpdateParams updates the btcstaking module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -60,22 +444,1415 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) { + out := new(MsgCreateBTCValidatorResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/CreateBTCValidator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) { + out := new(MsgCreateBTCDelegationResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/CreateBTCDelegation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { + // CreateBTCValidator creates a new BTC validator + CreateBTCValidator(context.Context, *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) + // CreateBTCDelegation creates a new BTC delegation + CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) + // UpdateParams updates the btcstaking module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) CreateBTCValidator(ctx context.Context, req *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateBTCValidator not implemented") +} +func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateBTCDelegation not implemented") +} +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_CreateBTCValidator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateBTCValidator) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreateBTCValidator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/CreateBTCValidator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreateBTCValidator(ctx, req.(*MsgCreateBTCValidator)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_CreateBTCDelegation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateBTCDelegation) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreateBTCDelegation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/CreateBTCDelegation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreateBTCDelegation(ctx, req.(*MsgCreateBTCDelegation)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btcstaking.v1.Msg", HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "babylon/btcstaking/v1/tx.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateBTCValidator", + Handler: _Msg_CreateBTCValidator_Handler, + }, + { + MethodName: "CreateBTCDelegation", + Handler: _Msg_CreateBTCDelegation_Handler, + }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/btcstaking/v1/tx.proto", +} + +func (m *MsgCreateBTCValidator) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil } + +func (m *MsgCreateBTCValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pop != nil { + { + size, err := m.Pop.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCreateBTCValidatorResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateBTCValidatorResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateBTCValidatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgCreateBTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateBTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SlashingTxSig != nil { + { + size := m.SlashingTxSig.Size() + i -= size + if _, err := m.SlashingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + if m.SlashingTx != nil { + { + size := m.SlashingTx.Size() + i -= size + if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.StakingTxInfo != nil { + { + size, err := m.StakingTxInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.StakingTxSig != nil { + { + size := m.StakingTxSig.Size() + i -= size + if _, err := m.StakingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.StakingTx != nil { + { + size := m.StakingTx.Size() + i -= size + if _, err := m.StakingTx.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Pop != nil { + { + size, err := m.Pop.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCreateBTCDelegationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateBTCDelegationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgCreateBTCValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCreateBTCValidatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgCreateBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.StakingTx != nil { + l = m.StakingTx.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.StakingTxSig != nil { + l = m.StakingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.StakingTxInfo != nil { + l = m.StakingTxInfo.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.SlashingTx != nil { + l = m.SlashingTx.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.SlashingTxSig != nil { + l = m.SlashingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCreateBTCDelegationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BTCStakingTx + m.StakingTx = &v + if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.StakingTxSig = &v + if err := m.StakingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StakingTxInfo == nil { + m.StakingTxInfo = &types.TransactionInfo{} + } + if err := m.StakingTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.SlashingTxSig = &v + if err := m.SlashingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/epoching/types/codec.go b/x/epoching/types/codec.go index cb32c4dae..ddaea8e65 100644 --- a/x/epoching/types/codec.go +++ b/x/epoching/types/codec.go @@ -20,21 +20,9 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { registry.RegisterImplementations( (*sdk.Msg)(nil), &MsgWrappedDelegate{}, - ) - registry.RegisterImplementations( - (*sdk.Msg)(nil), &MsgWrappedUndelegate{}, - ) - registry.RegisterImplementations( - (*sdk.Msg)(nil), &MsgWrappedBeginRedelegate{}, - ) - registry.RegisterImplementations( - (*sdk.Msg)(nil), &QueuedMessage{}, - ) - registry.RegisterImplementations( - (*sdk.Msg)(nil), &MsgUpdateParams{}, ) diff --git a/x/zoneconcierge/keeper/msg_server.go b/x/zoneconcierge/keeper/msg_server.go index 42d7a88fa..70709886b 100644 --- a/x/zoneconcierge/keeper/msg_server.go +++ b/x/zoneconcierge/keeper/msg_server.go @@ -21,10 +21,7 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} -// UpdateParams updates the params. -// TODO investigate when it is the best time to update the params. We can update them -// when the epoch changes, but we can also update them during the epoch and extend -// the epoch duration. +// UpdateParams updates the params func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) diff --git a/x/zoneconcierge/types/tx.pb.go b/x/zoneconcierge/types/tx.pb.go index c42d065d3..60eb3d932 100644 --- a/x/zoneconcierge/types/tx.pb.go +++ b/x/zoneconcierge/types/tx.pb.go @@ -171,7 +171,7 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // UpdateParams updates the btccheckpoint module parameters. + // UpdateParams updates the zoneconcierge module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -194,7 +194,7 @@ func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts // MsgServer is the server API for Msg service. type MsgServer interface { - // UpdateParams updates the btccheckpoint module parameters. + // UpdateParams updates the zoneconcierge module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } From aa171f7f43664192bcd02fedafc504e91111c5de Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 20 Jun 2023 19:53:49 +1000 Subject: [PATCH 011/202] btcstaking: pop and handler for `MsgCreateBTCValidator` (#4) --- testutil/datagen/account_balance.go | 6 +++ testutil/datagen/btcstaking.go | 40 ++++++++++++++ testutil/datagen/secp256k1.go | 15 ++++++ x/btcstaking/keeper/btc_validators.go | 7 --- x/btcstaking/keeper/msg_server.go | 20 ++++++- x/btcstaking/keeper/msg_server_test.go | 52 ++++++++++++++++-- x/btcstaking/types/btcstaking.go | 62 ++++++++++++++++++++- x/btcstaking/types/errors.go | 5 +- x/btcstaking/types/msg.go | 15 ++++-- x/btcstaking/types/pop.go | 69 ++++++++++++++++++++++++ x/btcstaking/types/pop_test.go | 74 ++++++++++++++++++++++++++ 11 files changed, 347 insertions(+), 18 deletions(-) create mode 100644 testutil/datagen/btcstaking.go create mode 100644 testutil/datagen/secp256k1.go create mode 100644 x/btcstaking/types/pop.go create mode 100644 x/btcstaking/types/pop_test.go diff --git a/testutil/datagen/account_balance.go b/testutil/datagen/account_balance.go index e7fd7a4d9..5055c4896 100644 --- a/testutil/datagen/account_balance.go +++ b/testutil/datagen/account_balance.go @@ -7,6 +7,12 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) +func GenRandomAccount() *authtypes.BaseAccount { + senderPrivKey := sec256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + return acc +} + func GenRandomAccWithBalance(n int) ([]authtypes.GenesisAccount, []banktypes.Balance) { accs := make([]authtypes.GenesisAccount, n) balances := make([]banktypes.Balance, n) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go new file mode 100644 index 000000000..18d802a44 --- /dev/null +++ b/testutil/datagen/btcstaking.go @@ -0,0 +1,40 @@ +package datagen + +import ( + "fmt" + "math/rand" + + bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" +) + +func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { + // key pairs + btcSK, btcPK, err := GenRandomBTCKeyPair(r) + if err != nil { + return nil, err + } + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + if err != nil { + return nil, err + } + bbnSK, bbnPK, err := GenRandomSecp256k1KeyPair(r) + if err != nil { + return nil, err + } + secp256k1PK, ok := bbnPK.(*secp256k1.PubKey) + if !ok { + return nil, fmt.Errorf("failed to assert bbnPK to *secp256k1.PubKey") + } + // pop + pop, err := bstypes.NewPoP(bbnSK, btcSK) + if err != nil { + return nil, err + } + return &bstypes.BTCValidator{ + BabylonPk: secp256k1PK, + BtcPk: &bip340PK, + Pop: pop, + }, nil +} diff --git a/testutil/datagen/secp256k1.go b/testutil/datagen/secp256k1.go new file mode 100644 index 000000000..3db651e22 --- /dev/null +++ b/testutil/datagen/secp256k1.go @@ -0,0 +1,15 @@ +package datagen + +import ( + "math/rand" + + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +func GenRandomSecp256k1KeyPair(r *rand.Rand) (cryptotypes.PrivKey, cryptotypes.PubKey, error) { + randBytes := GenRandomByteArray(r, 10) + sk := secp256k1.GenPrivKeyFromSecret(randBytes) + pk := sk.PubKey() + return sk, pk, nil +} diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 89f9708aa..d94003a3a 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -8,13 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// AddBTCValidator verifies and adds the given BTC validator to KVStore -func (k Keeper) AddBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) error { - // TODO: verify btcVal - k.setBTCValidator(ctx, btcVal) - return nil -} - // setBTCValidator adds the given BTC validator to KVStore func (k Keeper) setBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) { store := k.btcValidatorStore(ctx) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 220b8e85f..359a0ee59 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -37,7 +37,25 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara // CreateBTCValidator creates a BTC validator func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { - panic("TODO: implement me!") + // stateless checks, including PoP + if err := req.ValidateBasic(); err != nil { + return nil, err + } + // ensure the validator address does not exist before + ctx := sdk.UnwrapSDKContext(goCtx) + if ms.HasBTCValidator(ctx, *req.BtcPk) { + return nil, types.ErrDuplicatedBTCVal + } + + // all good, add this validator + btcVal := types.BTCValidator{ + BabylonPk: req.BabylonPk, + BtcPk: req.BtcPk, + Pop: req.Pop, + } + ms.setBTCValidator(ctx, &btcVal) + + return &types.MsgCreateBTCValidatorResponse{}, nil } // CreateBTCDelegation creates a BTC delegation diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index aaef746f8..c9425d029 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -2,8 +2,10 @@ package keeper_test import ( "context" + "math/rand" "testing" + "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -11,13 +13,57 @@ import ( "github.com/stretchr/testify/require" ) -func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { +func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { k, ctx := keepertest.BTCStakingKeeper(t) - return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) + return k, keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) } func TestMsgServer(t *testing.T) { - ms, ctx := setupMsgServer(t) + _, ms, ctx := setupMsgServer(t) require.NotNil(t, ms) require.NotNil(t, ctx) } + +func FuzzMsgCreateBTCValidator(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + keeper, ms, goCtx := setupMsgServer(t) + ctx := sdk.UnwrapSDKContext(goCtx) + + // generate new BTC validators + btcVals := []*types.BTCValidator{} + for i := 0; i < int(datagen.RandomInt(r, 10)); i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + msg := &types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, + } + _, err = ms.CreateBTCValidator(goCtx, msg) + require.NoError(t, err) + + btcVals = append(btcVals, btcVal) + } + // assert these validators exist in KVStore + for _, btcVal := range btcVals { + btcPK := *btcVal.BtcPk + require.True(t, keeper.HasBTCValidator(ctx, btcPK)) + } + + // duplicated BTC validators should not pass + for _, btcVal2 := range btcVals { + msg := &types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + BabylonPk: btcVal2.BabylonPk, + BtcPk: btcVal2.BtcPk, + Pop: btcVal2.Pop, + } + _, err := ms.CreateBTCValidator(goCtx, msg) + require.Error(t, err) + } + }) +} diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 5b88a8faf..345668714 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -1,27 +1,85 @@ package types +import "fmt" + func (v *BTCValidator) ValidateBasic() error { - // TODO: validation rules + // ensure fields are non-empty and well-formatted + if v.BabylonPk == nil { + return fmt.Errorf("empty BabylonPk") + } + if v.BtcPk == nil { + return fmt.Errorf("empty BtcPk") + } + if _, err := v.BtcPk.ToBTCPK(); err != nil { + return fmt.Errorf("BtcPk is not correctly formatted: %w", err) + } + if v.Pop == nil { + return fmt.Errorf("empty Pop") + } + // verify PoP if err := v.Pop.ValidateBasic(); err != nil { return err } + if err := v.Pop.Verify(v.BabylonPk, v.BtcPk); err != nil { + return err + } return nil } func (d *BTCDelegation) ValidateBasic() error { + if d.BabylonPk == nil { + return fmt.Errorf("empty BabylonPk") + } + if d.BtcPk == nil { + return fmt.Errorf("empty BabylonPk") + } + if d.Pop == nil { + return fmt.Errorf("empty Pop") + } + if d.ValBtcPk == nil { + return fmt.Errorf("empty ValBtcPk") + } + if d.StakingTx == nil { + return fmt.Errorf("empty StakingTx") + } + if d.StakingTxSig == nil { + return fmt.Errorf("empty StakingTxSig") + } + if d.StakingTx == nil { + return fmt.Errorf("empty StakingTxInfo") + } + if d.SlashingTx == nil { + return fmt.Errorf("empty SlashingTx") + } + if d.SlashingTxSig == nil { + return fmt.Errorf("empty SlashingTxSig") + } + // TODO: validation rules + // verify PoP if err := d.Pop.ValidateBasic(); err != nil { return err } + if err := d.Pop.Verify(d.BabylonPk, d.BtcPk); err != nil { + return err + } return nil } func (p *ProofOfPossession) ValidateBasic() error { - // TODO: validation rules + if len(p.BabylonSig) == 0 { + return fmt.Errorf("empty BabylonSig") + } + if p.BtcSig == nil { + return fmt.Errorf("empty BtcSig") + } + if _, err := p.BtcSig.ToBTCSig(); err != nil { + return fmt.Errorf("BtcSig is incorrectly formatted: %w", err) + } return nil } diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 455efb460..279f2dd30 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,6 +6,7 @@ import ( // x/btcstaking module sentinel errors var ( - ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") - ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") + ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") + ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index ac660dd98..c1ac8f743 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -54,7 +54,10 @@ func (m *MsgCreateBTCValidator) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } - return m.Pop.ValidateBasic() + if err := m.Pop.ValidateBasic(); err != nil { + return err + } + return m.Pop.Verify(m.BabylonPk, m.BtcPk) } func (m *MsgCreateBTCDelegation) GetSigners() []sdk.AccAddress { @@ -87,9 +90,15 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.SlashingTxSig == nil { return fmt.Errorf("empty SlashingTxSig") } - if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } - return m.Pop.ValidateBasic() + + // TODO: verification rules + + if err := m.Pop.ValidateBasic(); err != nil { + return err + } + // TODO: extract BTC PK and verify PoP + return nil } diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go new file mode 100644 index 000000000..40da6992a --- /dev/null +++ b/x/btcstaking/types/pop.go @@ -0,0 +1,69 @@ +package types + +import ( + "fmt" + + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/cometbft/cometbft/crypto/tmhash" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// NewPoP generates a new proof of possession that sk_Babylon and sk_BTC are held by the same person +// a proof of possession contains two signatures: +// - pop.BabylonSig = sign(sk_Babylon, pk_BTC) +// - pop.BtcSig = sign(sk_BTC, pop.BabylonSig) +func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPossession, error) { + pop := ProofOfPossession{} + + // generate pop.BabylonSig = sign(sk_Babylon, pk_BTC) + btcPK := btcSK.PubKey() + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + babylonSig, err := babylonSK.Sign(bip340PK) + if err != nil { + return nil, err + } + pop.BabylonSig = babylonSig + + // generate pop.BtcSig = sign(sk_BTC, pop.BabylonSig) + // NOTE: *schnorr.Sign has to take the hash of the message. + // So we have to hash babylonSig before signing + babylonSigHash := tmhash.Sum(pop.BabylonSig) + btcSig, err := schnorr.Sign(btcSK, babylonSigHash) + if err != nil { + return nil, err + } + bip340Sig := bbn.NewBIP340SignatureFromBTCSig(btcSig) + pop.BtcSig = &bip340Sig + + return &pop, nil +} + +// Verify verifies the validity of PoP +// 1. verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? +// 2. verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? +func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey) error { + // rule 1: verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? + btcSig, err := pop.BtcSig.ToBTCSig() + if err != nil { + return err + } + btcPK, err := bip340PK.ToBTCPK() + if err != nil { + return err + } + // NOTE: btcSig.Verify has to take hash of the message. + // So we have to hash babylonSig before verifying the signature + babylonSigHash := tmhash.Sum(pop.BabylonSig) + if !btcSig.Verify(babylonSigHash, btcPK) { + return fmt.Errorf("failed to verify babylonSig") + } + + // rule 2: verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? + if !babylonPK.VerifySignature(*bip340PK, pop.BabylonSig) { + return fmt.Errorf("failed to verify pop.BabylonSig") + } + + return nil +} diff --git a/x/btcstaking/types/pop_test.go b/x/btcstaking/types/pop_test.go new file mode 100644 index 000000000..bc2b979f8 --- /dev/null +++ b/x/btcstaking/types/pop_test.go @@ -0,0 +1,74 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/cometbft/cometbft/crypto/tmhash" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/stretchr/testify/require" +) + +func newInvalidPoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) *types.ProofOfPossession { + pop := types.ProofOfPossession{} + + randomNum := datagen.RandomInt(r, 2) // 0 or 1 + + btcPK := btcSK.PubKey() + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + babylonSig, err := babylonSK.Sign(bip340PK) + if err != nil { + panic(err) + } + + var babylonSigHash []byte + if randomNum == 0 { + pop.BabylonSig = babylonSig // correct sig + babylonSigHash = datagen.GenRandomByteArray(r, 32) // fake sig hash + } else { + pop.BabylonSig = datagen.GenRandomByteArray(r, uint64(len(babylonSig))) // fake sig + babylonSigHash = tmhash.Sum(pop.BabylonSig) // correct sig hash + } + + btcSig, err := schnorr.Sign(btcSK, babylonSigHash) + if err != nil { + panic(err) + } + bip340Sig := bbn.NewBIP340SignatureFromBTCSig(btcSig) + pop.BtcSig = &bip340Sig + + return &pop +} + +func FuzzPoP(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // generate BTC key pair + btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + + // generate Babylon key pair + babylonSK, babylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + require.NoError(t, err) + + // generate and verify PoP, correct case + pop, err := types.NewPoP(babylonSK, btcSK) + require.NoError(t, err) + err = pop.Verify(babylonPK, &bip340PK) + require.NoError(t, err) + + // generate and verify PoP, invalid case + invalidPoP := newInvalidPoP(r, babylonSK, btcSK) + err = invalidPoP.Verify(babylonPK, &bip340PK) + require.Error(t, err) + }) +} From bc57f1f3e9a43d7078c21a67d84d8c80ec19a4c6 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 21 Jun 2023 09:41:32 +0200 Subject: [PATCH 012/202] Initial btc logic (#6) * Initial btc logic --- btcstaking/staking.go | 423 +++++++++++++++++++++++++++++++++++++ btcstaking/staking_test.go | 286 +++++++++++++++++++++++++ 2 files changed, 709 insertions(+) create mode 100644 btcstaking/staking.go create mode 100644 btcstaking/staking_test.go diff --git a/btcstaking/staking.go b/btcstaking/staking.go new file mode 100644 index 000000000..d2173574a --- /dev/null +++ b/btcstaking/staking.go @@ -0,0 +1,423 @@ +package btcstaking + +import ( + "bytes" + "encoding/hex" + "fmt" + "math" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +const ( + // we expect signatures from 3 signers + expectedMultiSigSigners = 3 + + // Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + // using it as internal public key efectively disables taproot key spends + unspendableKeyPath = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +) + +// Following methods are copied from btcd. In most recent they are not exported. +// TODO: on btcd master those are already exported. Remove this copies +// when this will be released. +func isSmallInt(op byte) bool { + return op == txscript.OP_0 || (op >= txscript.OP_1 && op <= txscript.OP_16) +} + +func asSmallInt(op byte) int { + if op == txscript.OP_0 { + return 0 + } + + return int(op - (txscript.OP_1 - 1)) +} + +func checkMinimalDataEncoding(v []byte) error { + if len(v) == 0 { + return nil + } + + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, [0x80]. + if v[len(v)-1]&0x7f == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if len(v) == 1 || v[len(v)-2]&0x80 == 0 { + return fmt.Errorf("numeric value encoded as %x is "+ + "not minimally encoded", v) + } + } + + return nil +} + +func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (int64, error) { + // Interpreting data requires that it is not larger than + // the the passed scriptNumLen value. + if len(v) > scriptNumLen { + return 0, fmt.Errorf("numeric value encoded as %x is %d bytes "+ + "which exceeds the max allowed of %d", v, len(v), + scriptNumLen) + } + + // Enforce minimal encoded if requested. + if requireMinimal { + if err := checkMinimalDataEncoding(v); err != nil { + return 0, err + } + } + + // Zero is encoded as an empty byte slice. + if len(v) == 0 { + return 0, nil + } + + // Decode from little endian. + var result int64 + for i, val := range v { + result |= int64(val) << uint8(8*i) + } + + // When the most significant byte of the input bytes has the sign bit + // set, the result is negative. So, remove the sign bit from the result + // and make it negative. + if v[len(v)-1]&0x80 != 0 { + // The maximum length of v has already been determined to be 4 + // above, so uint8 is enough to cover the max possible shift + // value of 24. + result &= ^(int64(0x80) << uint8(8*(len(v)-1))) + return -result, nil + } + + return result, nil +} + +//End of copied methods + +// StakingScriptData is a struct that holds data parsed from staking script +type StakingScriptData struct { + StakerKey *btcec.PublicKey + ValidatorKey *btcec.PublicKey + JuryKey *btcec.PublicKey + StakingTime uint16 +} + +func NewStakingScriptData( + stakerKey, + validatorKey, + juryKey *btcec.PublicKey, + stakingTime uint16) (*StakingScriptData, error) { + + if stakerKey == nil || validatorKey == nil || juryKey == nil { + return nil, fmt.Errorf("staker, validator and jury keys cannot be nil") + } + + return &StakingScriptData{ + StakerKey: stakerKey, + ValidatorKey: validatorKey, + JuryKey: juryKey, + StakingTime: stakingTime, + }, nil +} + +// BuildStakingScript builds a staking script in the following format: +// OP_CHECKSIG +// OP_NOTIF +// +// OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL +// +// OP_ELSE +// +// OP_CHECKSEQUENCEVERIFY +// +// OP_ENDIF +func (sd *StakingScriptData) BuildStakingScript() ([]byte, error) { + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(sd.StakerKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_NOTIF) + builder.AddData(schnorr.SerializePubKey(sd.StakerKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddData(schnorr.SerializePubKey(sd.ValidatorKey)) + builder.AddOp(txscript.OP_CHECKSIGADD) + builder.AddData(schnorr.SerializePubKey(sd.JuryKey)) + builder.AddOp(txscript.OP_CHECKSIGADD) + builder.AddInt64(expectedMultiSigSigners) + builder.AddOp(txscript.OP_NUMEQUAL) + builder.AddOp(txscript.OP_ELSE) + builder.AddInt64(int64(sd.StakingTime)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_ENDIF) + return builder.Script() + +} + +// ParseStakingTransactionScript parses provided script. If script is not a valid staking script +// error is returned. If script is valid, StakingScriptData is returned, which contains all +// relevant data parsed from the script. +// Only stateless checks are performed. +func ParseStakingTransactionScript(version uint16, script []byte) (*StakingScriptData, error) { + // OP_CHECKSIG + // OP_NOTIF + // + // OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL + // + // OP_ELSE + // + // OP_CHECKSEQUENCEVERIFY + // + // OP_ENDIF + type templateMatch struct { + expectCanonicalInt bool + maxIntBytes int + opcode byte + extractedInt int64 + extractedData []byte + } + var template = [15]templateMatch{ + {opcode: txscript.OP_DATA_32}, + {opcode: txscript.OP_CHECKSIG}, + {opcode: txscript.OP_NOTIF}, + {opcode: txscript.OP_DATA_32}, + {opcode: txscript.OP_CHECKSIG}, + {opcode: txscript.OP_DATA_32}, + {opcode: txscript.OP_CHECKSIGADD}, + {opcode: txscript.OP_DATA_32}, + {opcode: txscript.OP_CHECKSIGADD}, + {expectCanonicalInt: true, maxIntBytes: 4}, + {opcode: txscript.OP_NUMEQUAL}, + {opcode: txscript.OP_ELSE}, + {expectCanonicalInt: true, maxIntBytes: 4}, + {opcode: txscript.OP_CHECKSEQUENCEVERIFY}, + {opcode: txscript.OP_ENDIF}, + } + + var templateOffset int + tokenizer := txscript.MakeScriptTokenizer(version, script) + for tokenizer.Next() { + // Not an staking script if it has more opcodes than expected in the + // template. + if templateOffset >= len(template) { + return nil, nil + } + + op := tokenizer.Opcode() + data := tokenizer.Data() + tplEntry := &template[templateOffset] + if tplEntry.expectCanonicalInt { + switch { + case data != nil: + val, err := makeScriptNum(data, true, tplEntry.maxIntBytes) + if err != nil { + return nil, err + } + tplEntry.extractedInt = int64(val) + + case isSmallInt(op): + tplEntry.extractedInt = int64(asSmallInt(op)) + + // Not an staking script if this is not int + default: + return nil, nil + } + } else { + if op != tplEntry.opcode { + return nil, nil + } + + tplEntry.extractedData = data + } + + templateOffset++ + } + if err := tokenizer.Err(); err != nil { + return nil, err + } + if !tokenizer.Done() || templateOffset != len(template) { + return nil, nil + } + + // At this point, the script appears to be an valid staking script. Extract relevant data and perform + // some initial validations. + + // Staker public key from the path without multisig i.e path where sats are locked + // for staking duration + stakerPk1, err := schnorr.ParsePubKey(template[0].extractedData) + if err != nil { + return nil, err + } + + // Staker public key from the path with multisig + if _, err := schnorr.ParsePubKey(template[3].extractedData); err != nil { + return nil, err + } + + if !bytes.Equal(template[0].extractedData, template[3].extractedData) { + return nil, fmt.Errorf("staker public key on lock path and multisig path are different") + } + + // Delegator public key + validatorPk, err := schnorr.ParsePubKey(template[5].extractedData) + + if err != nil { + return nil, err + } + + // Jury public key + juryPk, err := schnorr.ParsePubKey(template[7].extractedData) + + if err != nil { + return nil, err + } + + // validate number of mulitsig signers + if template[9].extractedInt != expectedMultiSigSigners { + return nil, fmt.Errorf("expected %d multisig signers, got %d", expectedMultiSigSigners, template[9].extractedInt) + } + + // validate staking time + if template[12].extractedInt < 0 || template[12].extractedInt > math.MaxUint16 { + return nil, fmt.Errorf("invalid staking time %d", template[12].extractedInt) + } + + // we do not need to check error here, as we already validated that all public keys are not nil + scriptData, _ := NewStakingScriptData(stakerPk1, validatorPk, juryPk, uint16(template[12].extractedInt)) + + return scriptData, nil +} + +func UnspendableKeyPathInternalPubKey() btcec.PublicKey { + // TODO: We could cache it in some cached private package variable if performance + // is necessary, as this returns always the same value. + keyBytes, _ := hex.DecodeString(unspendableKeyPath) + // We are using btcec here, as key is 33 byte compressed format. + pubKey, _ := btcec.ParsePubKey(keyBytes) + return *pubKey +} + +// TaprootAddressForScript returns a Taproot address commiting to the given pkScript +func TaprootAddressForScript( + pkScript []byte, + internalPubKey *btcec.PublicKey, + net *chaincfg.Params) (*btcutil.AddressTaproot, error) { + + tapLeaf := txscript.NewBaseTapLeaf(pkScript) + + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + + outputKey := txscript.ComputeTaprootOutputKey( + internalPubKey, tapScriptRootHash[:], + ) + + address, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(outputKey), net) + + if err != nil { + return nil, fmt.Errorf("error encoding Taproot address: %v", err) + } + + return address, nil +} + +// BuildStakingOutput builds out which is necessary for staking transaction to stake funds. +func BuildStakingOutput( + stakerKey, + validatorKey, + juryKey *btcec.PublicKey, + stTime uint16, + stAmount btcutil.Amount, + net *chaincfg.Params) (*wire.TxOut, []byte, error) { + + sd, err := NewStakingScriptData(stakerKey, validatorKey, juryKey, stTime) + + if err != nil { + return nil, nil, err + } + + script, err := sd.BuildStakingScript() + + if err != nil { + return nil, nil, err + } + + internalPubKey := UnspendableKeyPathInternalPubKey() + + address, err := TaprootAddressForScript(script, &internalPubKey, net) + + if err != nil { + return nil, nil, err + } + + pkScript, err := txscript.PayToAddrScript(address) + + if err != nil { + return nil, nil, err + } + + return wire.NewTxOut(int64(stAmount), pkScript), script, nil +} + +// BuildWitnessToSpendStakingOutput builds witness for spending staking as single staker +// Current assumptions: +// - staking output is the only input to the transaction +// - staking output is valid staking output +func BuildWitnessToSpendStakingOutput( + tx *wire.MsgTx, + stakingOutput *wire.TxOut, + stakingScript []byte, + privKey *btcec.PrivateKey) (wire.TxWitness, error) { + + internalPubKey := UnspendableKeyPathInternalPubKey() + + tapLeaf := txscript.NewBaseTapLeaf(stakingScript) + + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( + &internalPubKey, + ) + + ctrlBlockBytes, err := ctrlBlock.ToBytes() + + if err != nil { + return nil, err + } + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingOutput.PkScript, + stakingOutput.Value, + ) + + sigHashes := txscript.NewTxSigHashes(tx, inputFetcher) + + sig, err := txscript.RawTxInTapscriptSignature( + tx, sigHashes, 0, stakingOutput.Value, + stakingOutput.PkScript, tapLeaf, txscript.SigHashDefault, + privKey, + ) + + if err != nil { + return nil, err + } + + witnessStack := wire.TxWitness(make([][]byte, 3)) + witnessStack[0] = sig + witnessStack[1] = stakingScript + witnessStack[2] = ctrlBlockBytes + return witnessStack, nil +} diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go new file mode 100644 index 000000000..4130fa245 --- /dev/null +++ b/btcstaking/staking_test.go @@ -0,0 +1,286 @@ +package btcstaking_test + +import ( + "bytes" + "fmt" + "math" + "math/rand" + "testing" + "time" + + "github.com/babylonchain/babylon/btcstaking" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +func FuzzGeneratingParsingValidStakingScript(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + juryPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + stakingTime := uint16(r.Intn(math.MaxUint16)) + + _, stakerPublicKey := btcec.PrivKeyFromBytes(stakerPrivKeyBytes) + _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) + _, juryPublicKey := btcec.PrivKeyFromBytes(juryPrivKeyBytes) + + sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, juryPublicKey, stakingTime) + + script, err := sd.BuildStakingScript() + require.NoError(t, err) + parsedScript, err := btcstaking.ParseStakingTransactionScript(0, script) + require.NoError(t, err) + + require.Equal(t, parsedScript.StakingTime, stakingTime) + require.Equal(t, schnorr.SerializePubKey(stakerPublicKey), schnorr.SerializePubKey(parsedScript.StakerKey)) + require.Equal(t, schnorr.SerializePubKey(validatorPublicKey), schnorr.SerializePubKey(parsedScript.ValidatorKey)) + require.Equal(t, schnorr.SerializePubKey(juryPublicKey), schnorr.SerializePubKey(parsedScript.JuryKey)) + }) +} + +// Help function to assert the execution of a script engine. Copied from: +// https://github.com/lightningnetwork/lnd/blob/master/input/script_utils_test.go#L24 +func assertEngineExecution(t *testing.T, testNum int, valid bool, + newEngine func() (*txscript.Engine, error)) { + + t.Helper() + + // Get a new VM to execute. + vm, err := newEngine() + require.NoError(t, err, "unable to create engine") + + // Execute the VM, only go on to the step-by-step execution if + // it doesn't validate as expected. + vmErr := vm.Execute() + if valid == (vmErr == nil) { + return + } + + // Now that the execution didn't match what we expected, fetch a new VM + // to step through. + vm, err = newEngine() + require.NoError(t, err, "unable to create engine") + + // This buffer will trace execution of the Script, dumping out + // to stdout. + var debugBuf bytes.Buffer + + done := false + for !done { + dis, err := vm.DisasmPC() + if err != nil { + t.Fatalf("stepping (%v)\n", err) + } + debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) + + done, err = vm.Step() + if err != nil && valid { + t.Log(debugBuf.String()) + t.Fatalf("spend test case #%v failed, spend "+ + "should be valid: %v", testNum, err) + } else if err == nil && !valid && done { + t.Log(debugBuf.String()) + t.Fatalf("spend test case #%v succeed, spend "+ + "should be invalid: %v", testNum, err) + } + + debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) + debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + } + + // If we get to this point the unexpected case was not reached + // during step execution, which happens for some checks, like + // the clean-stack rule. + validity := "invalid" + if valid { + validity = "valid" + } + + t.Log(debugBuf.String()) + t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) +} + +func TestStakingScriptExecutionSingleStaker(t *testing.T) { + const ( + stakingValue = btcutil.Amount(2 * 10e8) + stakingTimeBlocks = 5 + ) + + r := rand.New(rand.NewSource(time.Now().Unix())) + + stakerPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + validatorPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + juryPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + + stakingOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + + stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( + stakerPrivKey.PubKey(), + validatorPrivKey.PubKey(), + juryPrivKey.PubKey(), + stakingTimeBlocks, + stakingValue, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + + spendStakeTx.AddTxIn(wire.NewTxIn(stakingOut, nil, nil)) + + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + // to spend tx as staker, we need to set the sequence number to be >= stakingTimeBlocks + spendStakeTx.TxIn[0].Sequence = stakingTimeBlocks + + witness, err := btcstaking.BuildWitnessToSpendStakingOutput( + spendStakeTx, + stakingOutput, + stakingScript, + stakerPrivKey, + ) + + require.NoError(t, err) + + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingOutput.PkScript, stakingOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingOutput.Value, + prevOutputFetcher, + ) + } + assertEngineExecution(t, 0, true, newEngine) +} + +func TestStakingScriptExecutionMulitSig(t *testing.T) { + const ( + stakingValue = btcutil.Amount(2 * 10e8) + stakingTimeBlocks = 5 + ) + + r := rand.New(rand.NewSource(time.Now().Unix())) + + stakerPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + validatorPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + juryPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + + stakingOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + + stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( + stakerPrivKey.PubKey(), + validatorPrivKey.PubKey(), + juryPrivKey.PubKey(), + stakingTimeBlocks, + stakingValue, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + + spendStakeTx.AddTxIn(wire.NewTxIn(stakingOut, nil, nil)) + + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + witnessStaker, err := btcstaking.BuildWitnessToSpendStakingOutput( + spendStakeTx, + stakingOutput, + stakingScript, + stakerPrivKey, + ) + require.NoError(t, err) + + witnessValidator, err := btcstaking.BuildWitnessToSpendStakingOutput( + spendStakeTx, + stakingOutput, + stakingScript, + validatorPrivKey, + ) + + require.NoError(t, err) + + witnessJury, err := btcstaking.BuildWitnessToSpendStakingOutput( + spendStakeTx, + stakingOutput, + stakingScript, + juryPrivKey, + ) + + require.NoError(t, err) + + // To Construct valid witness, for multisig case we need: + // - jury signature - witnessJury[0] + // - validator signature - witnessValidator[0] + // - staker signature - witnessStaker[0] + // - empty signature - which is just an empty byte array which signals we are going to use multisig. + // This must be signagure on top of the stack. + // - whole script - witnessStaker[1] (any other wittness[1] will work as well) + // - control block - witnessStaker[2] (any other wittness[2] will work as well) + spendStakeTx.TxIn[0].Witness = [][]byte{ + witnessJury[0], witnessValidator[0], witnessStaker[0], []byte{}, witnessStaker[1], witnessStaker[2], + } + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingOutput.PkScript, stakingOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingOutput.Value, + prevOutputFetcher, + ) + } + assertEngineExecution(t, 0, true, newEngine) +} From 250d454ad98373780ac1134813ced613b49594e9 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 21 Jun 2023 19:22:54 +1000 Subject: [PATCH 013/202] finality: KVStore schema for finality tracker (#5) --- app/app.go | 6 +- proto/babylon/btcstaking/v1/tx.proto | 2 +- proto/babylon/epoching/v1/tx.proto | 2 +- proto/babylon/finality/v1/finality.proto | 31 + proto/babylon/finality/v1/params.proto | 4 + proto/babylon/finality/v1/tx.proto | 28 +- proto/babylon/zoneconcierge/v1/tx.proto | 2 +- testutil/keeper/btcstaking.go | 8 - testutil/keeper/finality.go | 15 +- types/btc_schnorr_eots.go | 62 ++ types/btc_schnorr_eots_test.go | 35 ++ types/btc_schnorr_pub_rand.go | 62 ++ types/btc_schnorr_pub_rand_test.go | 35 ++ x/btcstaking/keeper/keeper.go | 21 +- x/btcstaking/keeper/params.go | 29 +- x/btcstaking/types/keys.go | 1 + x/btcstaking/types/tx.pb.go | 2 +- x/epoching/types/tx.pb.go | 2 +- x/finality/genesis.go | 4 +- x/finality/keeper/indexed_blocks.go | 55 ++ x/finality/keeper/keeper.go | 26 +- x/finality/keeper/msg_server.go | 19 + x/finality/keeper/params.go | 24 +- x/finality/keeper/params_test.go | 3 +- x/finality/keeper/public_randomness.go | 38 ++ x/finality/keeper/query_params_test.go | 3 +- x/finality/keeper/votes.go | 49 ++ x/finality/types/codec.go | 9 +- x/finality/types/errors.go | 8 +- x/finality/types/finality.go | 19 + x/finality/types/finality.pb.go | 725 +++++++++++++++++++++++ x/finality/types/keys.go | 7 + x/finality/types/msg.go | 30 + x/finality/types/params.go | 7 +- x/finality/types/params.pb.go | 53 +- x/finality/types/tx.pb.go | 534 ++++++++++++++++- x/zoneconcierge/types/tx.pb.go | 2 +- 37 files changed, 1857 insertions(+), 105 deletions(-) create mode 100644 proto/babylon/finality/v1/finality.proto create mode 100644 types/btc_schnorr_eots.go create mode 100644 types/btc_schnorr_eots_test.go create mode 100644 types/btc_schnorr_pub_rand.go create mode 100644 types/btc_schnorr_pub_rand_test.go create mode 100644 x/finality/keeper/indexed_blocks.go create mode 100644 x/finality/keeper/public_randomness.go create mode 100644 x/finality/keeper/votes.go create mode 100644 x/finality/types/finality.go create mode 100644 x/finality/types/finality.pb.go create mode 100644 x/finality/types/msg.go diff --git a/app/app.go b/app/app.go index be63cd696..dd2b58840 100644 --- a/app/app.go +++ b/app/app.go @@ -616,11 +616,11 @@ func NewBabylonApp( // set up BTC staking keeper app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( - appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.GetSubspace(btcstakingtypes.ModuleName), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.FinalityKeeper = finalitykeeper.NewKeeper( - appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.GetSubspace(finalitytypes.ModuleName), app.AccountKeeper, app.BankKeeper, - app.BTCStakingKeeper, + appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, + app.BTCStakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index fb9f82329..fff1bf13c 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -71,7 +71,7 @@ message MsgUpdateParams { // in cosmos-proto string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // params defines the epoching paramaeters parameters to update. + // params defines the finality parameters to update. // // NOTE: All parameters must be supplied. Params params = 2 [(gogoproto.nullable) = false]; diff --git a/proto/babylon/epoching/v1/tx.proto b/proto/babylon/epoching/v1/tx.proto index a9bc48604..7a25e34a4 100644 --- a/proto/babylon/epoching/v1/tx.proto +++ b/proto/babylon/epoching/v1/tx.proto @@ -76,7 +76,7 @@ message MsgUpdateParams { // in cosmos-proto string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // params defines the epoching paramaeters parameters to update. + // params defines the epoching parameters to update. // // NOTE: All parameters must be supplied. Params params = 2 [(gogoproto.nullable) = false]; diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto new file mode 100644 index 000000000..d659d469c --- /dev/null +++ b/proto/babylon/finality/v1/finality.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package babylon.finality.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// Vote is a vote to a block +message Vote { + // val_btc_pk is the BTC Pk of the validator that casts this vote + bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // block_height is the height of the voted block + uint64 block_height = 2; + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + bytes finality_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; +} + +// IndexedBlock is the block with some indexed info +message IndexedBlock { + // height is the height of the block + uint64 height = 1; + // hash is the hash of the block + bytes hash = 2; + // btc_height is the height of the BTC tip upon EndBlock of this block + uint64 btc_height = 3; + // btc_hash is the hash of the BTC tip upon EndBlock of this block + bytes btc_hash = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderHashBytes" ]; +} diff --git a/proto/babylon/finality/v1/params.proto b/proto/babylon/finality/v1/params.proto index d317ece41..137e8cd51 100644 --- a/proto/babylon/finality/v1/params.proto +++ b/proto/babylon/finality/v1/params.proto @@ -8,4 +8,8 @@ option go_package = "github.com/babylonchain/babylon/x/finality/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; + + // min_pub_rand is the minimum number of public randomoness each + // message should commit + uint64 min_pub_rand = 1; } diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index 3b6498403..a5dacf822 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -3,5 +3,31 @@ package babylon.finality.v1; option go_package = "github.com/babylonchain/babylon/x/finality/types"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; +import "babylon/finality/v1/params.proto"; + // Msg defines the Msg service. -service Msg {} \ No newline at end of file +service Msg { + // UpdateParams updates the finality module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgUpdateParams defines a message for updating finality module parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the finality parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +message MsgUpdateParamsResponse {} diff --git a/proto/babylon/zoneconcierge/v1/tx.proto b/proto/babylon/zoneconcierge/v1/tx.proto index 3c97ca6a8..922128e11 100644 --- a/proto/babylon/zoneconcierge/v1/tx.proto +++ b/proto/babylon/zoneconcierge/v1/tx.proto @@ -25,7 +25,7 @@ message MsgUpdateParams { // in cosmos-proto string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // params defines the epoching paramaeters parameters to update. + // params defines the zoneconcierge parameters to update. // // NOTE: All parameters must be supplied. Params params = 2 [(gogoproto.nullable) = false]; diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index 6365dff49..cdf015824 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -15,7 +15,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - typesparams "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/stretchr/testify/require" ) @@ -32,17 +31,10 @@ func BTCStakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { registry := codectypes.NewInterfaceRegistry() cdc := codec.NewProtoCodec(registry) - paramsSubspace := typesparams.NewSubspace(cdc, - types.Amino, - storeKey, - memStoreKey, - "BtcstakingParams", - ) k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - paramsSubspace, nil, nil, authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index 369930d68..c06da4e9f 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -13,7 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - typesparams "github.com/cosmos/cosmos-sdk/x/params/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/require" ) @@ -30,26 +31,22 @@ func FinalityKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { registry := codectypes.NewInterfaceRegistry() cdc := codec.NewProtoCodec(registry) - paramsSubspace := typesparams.NewSubspace(cdc, - types.Amino, - storeKey, - memStoreKey, - "FinalityParams", - ) k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - paramsSubspace, nil, nil, nil, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) // Initialize params - k.SetParams(ctx, types.DefaultParams()) + if err := k.SetParams(ctx, types.DefaultParams()); err != nil { + panic(err) + } return &k, ctx } diff --git a/types/btc_schnorr_eots.go b/types/btc_schnorr_eots.go new file mode 100644 index 000000000..235888d9d --- /dev/null +++ b/types/btc_schnorr_eots.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" +) + +type SchnorrEOTSSig []byte + +const SchnorrEOTSSigLen = 32 + +func NewSchnorrEOTSSig(data []byte) (*SchnorrEOTSSig, error) { + var sig SchnorrEOTSSig + err := sig.Unmarshal(data) + return &sig, err +} + +func NewSchnorrEOTSSigFromModNScalar(r *btcec.ModNScalar) *SchnorrEOTSSig { + prBytes := r.Bytes() + sig := SchnorrEOTSSig(prBytes[:]) + return &sig +} + +func (sig SchnorrEOTSSig) ToModNScalar() *btcec.ModNScalar { + var s btcec.ModNScalar + s.PutBytesUnchecked(sig) + return &s +} + +func (sig SchnorrEOTSSig) Size() int { + return len(sig.MustMarshal()) +} + +func (sig SchnorrEOTSSig) Marshal() ([]byte, error) { + return sig, nil +} + +func (sig SchnorrEOTSSig) MustMarshal() []byte { + prBytes, err := sig.Marshal() + if err != nil { + panic(err) + } + return prBytes +} + +func (sig SchnorrEOTSSig) MarshalTo(data []byte) (int, error) { + bz, err := sig.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (sig *SchnorrEOTSSig) Unmarshal(data []byte) error { + if len(data) != SchnorrEOTSSigLen { + return fmt.Errorf("invalid data length") + } + *sig = data + return nil +} diff --git a/types/btc_schnorr_eots_test.go b/types/btc_schnorr_eots_test.go new file mode 100644 index 000000000..151f93a24 --- /dev/null +++ b/types/btc_schnorr_eots_test.go @@ -0,0 +1,35 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +func FuzzSchnorrEOTSSig(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + randBytes := datagen.GenRandomByteArray(r, 32) + var modNScalar btcec.ModNScalar + modNScalar.PutBytesUnchecked(randBytes) + + // ModNScalar -> SchnorrEOTSSig -> ModNScalar + sig := types.NewSchnorrEOTSSigFromModNScalar(&modNScalar) + modNScalar2 := sig.ToModNScalar() + require.True(t, modNScalar.Equals(modNScalar2)) + + // SchnorrEOTSSig -> bytes -> SchnorrEOTSSig + randBytes2 := sig.MustMarshal() + sig2, err := types.NewSchnorrEOTSSig(randBytes) + require.NoError(t, err) + require.Equal(t, randBytes, randBytes2) + require.Equal(t, sig, sig2) + }) +} diff --git a/types/btc_schnorr_pub_rand.go b/types/btc_schnorr_pub_rand.go new file mode 100644 index 000000000..2afc96b69 --- /dev/null +++ b/types/btc_schnorr_pub_rand.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" +) + +type SchnorrPubRand []byte + +const SchnorrPubRandLen = 32 + +func NewSchnorrPubRand(data []byte) (*SchnorrPubRand, error) { + var pr SchnorrPubRand + err := pr.Unmarshal(data) + return &pr, err +} + +func NewSchnorrPubRandFromFieldVal(r *btcec.FieldVal) *SchnorrPubRand { + prBytes := r.Bytes() + pr := SchnorrPubRand(prBytes[:]) + return &pr +} + +func (pr SchnorrPubRand) ToFieldVal() *btcec.FieldVal { + var r btcec.FieldVal + r.PutBytesUnchecked(pr) + return &r +} + +func (pr SchnorrPubRand) Size() int { + return len(pr.MustMarshal()) +} + +func (pr SchnorrPubRand) Marshal() ([]byte, error) { + return pr, nil +} + +func (pr SchnorrPubRand) MustMarshal() []byte { + prBytes, err := pr.Marshal() + if err != nil { + panic(err) + } + return prBytes +} + +func (pr SchnorrPubRand) MarshalTo(data []byte) (int, error) { + bz, err := pr.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +func (pr *SchnorrPubRand) Unmarshal(data []byte) error { + if len(data) != SchnorrPubRandLen { + return fmt.Errorf("invalid data length") + } + *pr = data + return nil +} diff --git a/types/btc_schnorr_pub_rand_test.go b/types/btc_schnorr_pub_rand_test.go new file mode 100644 index 000000000..9a70dfb87 --- /dev/null +++ b/types/btc_schnorr_pub_rand_test.go @@ -0,0 +1,35 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +func FuzzSchnorrPubRand(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + randBytes := datagen.GenRandomByteArray(r, 32) + var fieldVal btcec.FieldVal + fieldVal.PutBytesUnchecked(randBytes) + + // FieldVal -> SchnorrPubRand -> FieldVal + pubRand := types.NewSchnorrPubRandFromFieldVal(&fieldVal) + fieldVal2 := pubRand.ToFieldVal() + require.True(t, fieldVal.Equals(fieldVal2)) + + // SchnorrPubRand -> bytes -> SchnorrPubRand + randBytes2 := pubRand.MustMarshal() + pubRand2, err := types.NewSchnorrPubRand(randBytes) + require.NoError(t, err) + require.Equal(t, randBytes, randBytes2) + require.Equal(t, pubRand, pubRand2) + }) +} diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 529739800..f1ca391db 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -7,17 +7,15 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/babylonchain/babylon/x/btcstaking/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - paramstore paramtypes.Subspace + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey accountKeeper types.AccountKeeper bankKeeper types.BankKeeper @@ -31,22 +29,15 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, - ps paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, authority string, ) Keeper { - // set KeyTable if it has not already been set - if !ps.HasKeyTable() { - ps = ps.WithKeyTable(types.ParamKeyTable()) - } - return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - paramstore: ps, + cdc: cdc, + storeKey: storeKey, + memKey: memKey, accountKeeper: accountKeeper, bankKeeper: bankKeeper, diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index 625014341..0ddaf62e1 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -5,19 +5,24 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) types.Params { - params := types.DefaultParams() - k.paramstore.GetParamSet(ctx, ¶ms) - return params -} - -// SetParams set the params -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { - if err := params.Validate(); err != nil { +// SetParams sets the x/btcstaking module parameters. +func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { + if err := p.Validate(); err != nil { return err } - - k.paramstore.SetParamSet(ctx, ¶ms) + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&p) + store.Set(types.ParamsKey, bz) return nil } + +// GetParams returns the current x/btcstaking module parameters. +func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.ParamsKey) + if bz == nil { + return p + } + k.cdc.MustUnmarshal(bz, &p) + return p +} diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 7785ed838..1b35435bf 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -17,6 +17,7 @@ const ( var ( BTCValidatorKey = []byte{0x01} // key prefix for the BTC validators BTCDelegationKey = []byte{0x02} // key prefix for the BTC delegations + ParamsKey = []byte{0x03} // key prefix for the parameters ) func KeyPrefix(p string) []byte { diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 5a54ab04e..4e3f4a551 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -265,7 +265,7 @@ type MsgUpdateParams struct { // for AddressString instead of string, but the functionality is not yet implemented // in cosmos-proto Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` - // params defines the epoching paramaeters parameters to update. + // params defines the finality parameters to update. // // NOTE: All parameters must be supplied. Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` diff --git a/x/epoching/types/tx.pb.go b/x/epoching/types/tx.pb.go index 56c8ce0b9..19605482e 100644 --- a/x/epoching/types/tx.pb.go +++ b/x/epoching/types/tx.pb.go @@ -266,7 +266,7 @@ type MsgUpdateParams struct { // for AddressString instead of string, but the functionality is not yet implemented // in cosmos-proto Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` - // params defines the epoching paramaeters parameters to update. + // params defines the epoching parameters to update. // // NOTE: All parameters must be supplied. Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` diff --git a/x/finality/genesis.go b/x/finality/genesis.go index 109c08df4..fc1a75ae0 100644 --- a/x/finality/genesis.go +++ b/x/finality/genesis.go @@ -8,7 +8,9 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { - k.SetParams(ctx, genState.Params) + if err := k.SetParams(ctx, genState.Params); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go new file mode 100644 index 000000000..afed54e25 --- /dev/null +++ b/x/finality/keeper/indexed_blocks.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "fmt" + + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) AddBlock(ctx sdk.Context, block *types.IndexedBlock) error { + if k.HasBlock(ctx, block.Height) { + block2, err := k.GetBlock(ctx, block.Height) + if err != nil { + panic(err) + } + if !block.Equal(block2) { + panic(fmt.Errorf("conflicting blocks detected at height %d", block.Height)) + } + return types.ErrDuplicatedBlock + } + k.setBlock(ctx, block) + return nil +} + +func (k Keeper) setBlock(ctx sdk.Context, block *types.IndexedBlock) { + store := k.blockStore(ctx) + blockBytes := k.cdc.MustMarshal(block) + store.Set(sdk.Uint64ToBigEndian(block.Height), blockBytes) +} + +func (k Keeper) HasBlock(ctx sdk.Context, height uint64) bool { + store := k.blockStore(ctx) + return store.Has(sdk.Uint64ToBigEndian(height)) +} + +func (k Keeper) GetBlock(ctx sdk.Context, height uint64) (*types.IndexedBlock, error) { + store := k.blockStore(ctx) + blockBytes := store.Get(sdk.Uint64ToBigEndian(height)) + if len(blockBytes) == 0 { + return nil, types.ErrBlockNotFound + } + var block types.IndexedBlock + k.cdc.MustUnmarshal(blockBytes, &block) + return &block, nil +} + +// blockStore returns the KVStore of the blocks +// prefix: BlockKey +// key: block height +// value: IndexedBlock +func (k Keeper) blockStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.BlockKey) +} diff --git a/x/finality/keeper/keeper.go b/x/finality/keeper/keeper.go index 26366c2d4..4c4a9f6dd 100644 --- a/x/finality/keeper/keeper.go +++ b/x/finality/keeper/keeper.go @@ -7,21 +7,22 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/babylonchain/babylon/x/finality/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - paramstore paramtypes.Subspace + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey accountKeeper types.AccountKeeper bankKeeper types.BankKeeper BTCStakingKeeper types.BTCStakingKeeper + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string } ) @@ -29,26 +30,21 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, - ps paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, BTCStakingKeeper types.BTCStakingKeeper, + authority string, ) Keeper { - // set KeyTable if it has not already been set - if !ps.HasKeyTable() { - ps = ps.WithKeyTable(types.ParamKeyTable()) - } - return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - paramstore: ps, + cdc: cdc, + storeKey: storeKey, + memKey: memKey, accountKeeper: accountKeeper, bankKeeper: bankKeeper, BTCStakingKeeper: BTCStakingKeeper, + authority: authority, } } diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 23705d1ca..a37212e7d 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -1,7 +1,12 @@ package keeper import ( + "context" + + errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) type msgServer struct { @@ -15,3 +20,17 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { } var _ types.MsgServer = msgServer{} + +// UpdateParams updates the params +func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if ms.authority != req.Authority { + return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := ms.SetParams(ctx, req.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/x/finality/keeper/params.go b/x/finality/keeper/params.go index ed99e89b6..f4e9ce5e4 100644 --- a/x/finality/keeper/params.go +++ b/x/finality/keeper/params.go @@ -5,12 +5,24 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) types.Params { - return types.NewParams() +// SetParams sets the x/finality module parameters. +func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { + if err := p.Validate(); err != nil { + return err + } + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&p) + store.Set(types.ParamsKey, bz) + return nil } -// SetParams set the params -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) +// GetParams returns the current x/finality module parameters. +func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.ParamsKey) + if bz == nil { + return p + } + k.cdc.MustUnmarshal(bz, &p) + return p } diff --git a/x/finality/keeper/params_test.go b/x/finality/keeper/params_test.go index 5d54522c2..84bad7e25 100644 --- a/x/finality/keeper/params_test.go +++ b/x/finality/keeper/params_test.go @@ -12,7 +12,8 @@ func TestGetParams(t *testing.T) { k, ctx := testkeeper.FinalityKeeper(t) params := types.DefaultParams() - k.SetParams(ctx, params) + err := k.SetParams(ctx, params) + require.NoError(t, err) require.EqualValues(t, params, k.GetParams(ctx)) } diff --git a/x/finality/keeper/public_randomness.go b/x/finality/keeper/public_randomness.go new file mode 100644 index 000000000..3add2d591 --- /dev/null +++ b/x/finality/keeper/public_randomness.go @@ -0,0 +1,38 @@ +package keeper + +import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +//nolint:unused +func (k Keeper) setPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64, pr *bbn.SchnorrPubRand) { + store := k.pubRandStore(ctx, valBtcPK) + store.Set(sdk.Uint64ToBigEndian(height), *pr) +} + +func (k Keeper) HasPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { + store := k.pubRandStore(ctx, valBtcPK) + return store.Has(sdk.Uint64ToBigEndian(height)) +} + +func (k Keeper) GetPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*bbn.SchnorrPubRand, error) { + store := k.pubRandStore(ctx, valBtcPK) + prBytes := store.Get(sdk.Uint64ToBigEndian(height)) + if len(prBytes) == 0 { + return nil, types.ErrPubRandNotFound + } + return bbn.NewSchnorrPubRand(prBytes) +} + +// pubRandStore returns the KVStore of the public randomness +// prefix: PubRandKey +// key: (BTC validator || PK block height) +// value: PublicRandomness +func (k Keeper) pubRandStore(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) prefix.Store { + store := ctx.KVStore(k.storeKey) + prefixedStore := prefix.NewStore(store, types.PubRandKey) + return prefix.NewStore(prefixedStore, *valBtcPK) +} diff --git a/x/finality/keeper/query_params_test.go b/x/finality/keeper/query_params_test.go index d1a216d7b..115745dc9 100644 --- a/x/finality/keeper/query_params_test.go +++ b/x/finality/keeper/query_params_test.go @@ -13,7 +13,8 @@ func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.FinalityKeeper(t) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() - keeper.SetParams(ctx, params) + err := keeper.SetParams(ctx, params) + require.NoError(t, err) response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) require.NoError(t, err) diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go new file mode 100644 index 000000000..b09bb3904 --- /dev/null +++ b/x/finality/keeper/votes.go @@ -0,0 +1,49 @@ +package keeper + +import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) AddVote(ctx sdk.Context, vote *types.Vote) error { + // TODO verification rules of vote + k.setVote(ctx, vote) + return nil +} + +func (k Keeper) setVote(ctx sdk.Context, vote *types.Vote) { + store := k.voteStore(ctx, vote.BlockHeight) + voteBytes := k.cdc.MustMarshal(vote) + store.Set(sdk.Uint64ToBigEndian(vote.BlockHeight), voteBytes) +} + +func (k Keeper) HasVote(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { + store := k.voteStore(ctx, height) + return store.Has(*valBtcPK) +} + +func (k Keeper) GetVote(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*types.Vote, error) { + if uint64(ctx.BlockHeight()) < height { + return nil, types.ErrHeightTooHigh + } + store := k.voteStore(ctx, height) + voteBytes := store.Get(*valBtcPK) + if len(voteBytes) == 0 { + return nil, types.ErrVoteNotFound + } + var vote types.Vote + k.cdc.MustUnmarshal(voteBytes, &vote) + return &vote, nil +} + +// voteStore returns the KVStore of the votes +// prefix: VoteKey +// key: (block height || BTC validator PK) +// value: Vote +func (k Keeper) voteStore(ctx sdk.Context, height uint64) prefix.Store { + store := ctx.KVStore(k.storeKey) + prefixedStore := prefix.NewStore(store, types.VoteKey) + return prefix.NewStore(prefixedStore, sdk.Uint64ToBigEndian(height)) +} diff --git a/x/finality/types/codec.go b/x/finality/types/codec.go index 858010906..c885f3ab7 100644 --- a/x/finality/types/codec.go +++ b/x/finality/types/codec.go @@ -3,15 +3,20 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) func RegisterCodec(cdc *codec.LegacyAmino) { - + cdc.RegisterConcrete(&MsgUpdateParams{}, "finality/MsgUpdateParams", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + // Register messages + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgUpdateParams{}, + ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 1c04c70db..6213efdad 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -4,7 +4,11 @@ import ( errorsmod "cosmossdk.io/errors" ) -// x/btcstaking module sentinel errors +// x/finality module sentinel errors var ( - ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") + ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") + ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") + ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") + ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") + ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") ) diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go new file mode 100644 index 000000000..1c1e2c37f --- /dev/null +++ b/x/finality/types/finality.go @@ -0,0 +1,19 @@ +package types + +import "bytes" + +func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { + if !bytes.Equal(ib.Hash, ib2.Hash) { + return false + } + if ib.Height != ib2.Height { + return false + } + if !ib.BtcHash.Eq(ib2.BtcHash) { + return false + } + if ib.BtcHeight != ib2.BtcHeight { + return false + } + return true +} diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go new file mode 100644 index 000000000..0b91286ad --- /dev/null +++ b/x/finality/types/finality.pb.go @@ -0,0 +1,725 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/finality.proto + +package types + +import ( + fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Vote is a vote to a block +type Vote struct { + // val_btc_pk is the BTC Pk of the validator that casts this vote + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // block_height is the height of the voted block + BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,3,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` +} + +func (m *Vote) Reset() { *m = Vote{} } +func (m *Vote) String() string { return proto.CompactTextString(m) } +func (*Vote) ProtoMessage() {} +func (*Vote) Descriptor() ([]byte, []int) { + return fileDescriptor_ca5b87e52e3e6d02, []int{0} +} +func (m *Vote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Vote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Vote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Vote) XXX_Merge(src proto.Message) { + xxx_messageInfo_Vote.Merge(m, src) +} +func (m *Vote) XXX_Size() int { + return m.Size() +} +func (m *Vote) XXX_DiscardUnknown() { + xxx_messageInfo_Vote.DiscardUnknown(m) +} + +var xxx_messageInfo_Vote proto.InternalMessageInfo + +func (m *Vote) GetBlockHeight() uint64 { + if m != nil { + return m.BlockHeight + } + return 0 +} + +// IndexedBlock is the block with some indexed info +type IndexedBlock struct { + // height is the height of the block + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // hash is the hash of the block + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + // btc_height is the height of the BTC tip upon EndBlock of this block + BtcHeight uint64 `protobuf:"varint,3,opt,name=btc_height,json=btcHeight,proto3" json:"btc_height,omitempty"` + // btc_hash is the hash of the BTC tip upon EndBlock of this block + BtcHash *github_com_babylonchain_babylon_types.BTCHeaderHashBytes `protobuf:"bytes,4,opt,name=btc_hash,json=btcHash,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderHashBytes" json:"btc_hash,omitempty"` +} + +func (m *IndexedBlock) Reset() { *m = IndexedBlock{} } +func (m *IndexedBlock) String() string { return proto.CompactTextString(m) } +func (*IndexedBlock) ProtoMessage() {} +func (*IndexedBlock) Descriptor() ([]byte, []int) { + return fileDescriptor_ca5b87e52e3e6d02, []int{1} +} +func (m *IndexedBlock) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IndexedBlock) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IndexedBlock.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IndexedBlock) XXX_Merge(src proto.Message) { + xxx_messageInfo_IndexedBlock.Merge(m, src) +} +func (m *IndexedBlock) XXX_Size() int { + return m.Size() +} +func (m *IndexedBlock) XXX_DiscardUnknown() { + xxx_messageInfo_IndexedBlock.DiscardUnknown(m) +} + +var xxx_messageInfo_IndexedBlock proto.InternalMessageInfo + +func (m *IndexedBlock) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *IndexedBlock) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *IndexedBlock) GetBtcHeight() uint64 { + if m != nil { + return m.BtcHeight + } + return 0 +} + +func init() { + proto.RegisterType((*Vote)(nil), "babylon.finality.v1.Vote") + proto.RegisterType((*IndexedBlock)(nil), "babylon.finality.v1.IndexedBlock") +} + +func init() { + proto.RegisterFile("babylon/finality/v1/finality.proto", fileDescriptor_ca5b87e52e3e6d02) +} + +var fileDescriptor_ca5b87e52e3e6d02 = []byte{ + // 368 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4f, 0x6b, 0xe2, 0x40, + 0x18, 0xc6, 0x9d, 0x35, 0xb8, 0xee, 0x98, 0xd3, 0xec, 0xb2, 0xc8, 0xc2, 0x46, 0xd7, 0x93, 0xa7, + 0x44, 0x57, 0x29, 0x1e, 0x7a, 0x4a, 0x29, 0x68, 0x7b, 0xa8, 0x24, 0xd2, 0x42, 0x7b, 0x08, 0x33, + 0x71, 0x9a, 0x19, 0x4c, 0x33, 0x92, 0x8c, 0xc1, 0x7c, 0x8b, 0x7e, 0x96, 0x7e, 0x8a, 0x1e, 0x3d, + 0x16, 0x29, 0x52, 0xf4, 0x8b, 0x94, 0x4c, 0xa3, 0x3d, 0xd6, 0xdb, 0xfb, 0xe7, 0xc9, 0xf3, 0xfc, + 0x78, 0x33, 0xb0, 0x45, 0x30, 0xc9, 0x42, 0x11, 0x59, 0xf7, 0x3c, 0xc2, 0x21, 0x97, 0x99, 0x95, + 0x76, 0x0f, 0xb5, 0x39, 0x8f, 0x85, 0x14, 0xe8, 0x67, 0xa1, 0x31, 0x0f, 0xf3, 0xb4, 0xfb, 0xe7, + 0x57, 0x20, 0x02, 0xa1, 0xf6, 0x56, 0x5e, 0x7d, 0x48, 0x5b, 0xaf, 0x00, 0x6a, 0xd7, 0x42, 0x52, + 0x34, 0x81, 0x30, 0xc5, 0xa1, 0x47, 0xa4, 0xef, 0xcd, 0x67, 0x75, 0xd0, 0x04, 0x6d, 0xdd, 0x3e, + 0x59, 0x6f, 0x1a, 0xff, 0x03, 0x2e, 0xd9, 0x82, 0x98, 0xbe, 0x78, 0xb0, 0x0a, 0x5b, 0x9f, 0x61, + 0x1e, 0xed, 0x1b, 0x4b, 0x66, 0x73, 0x9a, 0x98, 0xf6, 0x68, 0xdc, 0xeb, 0x77, 0xc6, 0x0b, 0x72, + 0x49, 0x33, 0xa7, 0x9a, 0xe2, 0xd0, 0x96, 0xfe, 0x78, 0x86, 0xfe, 0x41, 0x9d, 0x84, 0xc2, 0x9f, + 0x79, 0x8c, 0xf2, 0x80, 0xc9, 0xfa, 0xb7, 0x26, 0x68, 0x6b, 0x4e, 0x4d, 0xcd, 0x86, 0x6a, 0x84, + 0xee, 0xa0, 0xbe, 0xc7, 0xf4, 0x12, 0x1e, 0xd4, 0xcb, 0x2a, 0x7a, 0xb0, 0xde, 0x34, 0xfa, 0xc7, + 0x45, 0xbb, 0x3e, 0x8b, 0x44, 0x1c, 0x9f, 0x5f, 0x4d, 0x5c, 0x97, 0x07, 0x4e, 0x6d, 0xef, 0xe6, + 0xf2, 0xa0, 0xf5, 0x04, 0xa0, 0x3e, 0x8a, 0xa6, 0x74, 0x49, 0xa7, 0x76, 0x9e, 0x89, 0x7e, 0xc3, + 0x4a, 0x81, 0x02, 0x14, 0x4a, 0xd1, 0x21, 0x04, 0x35, 0x86, 0x13, 0xa6, 0x00, 0x75, 0x47, 0xd5, + 0xe8, 0x2f, 0x84, 0xf9, 0x39, 0x0a, 0x7d, 0x59, 0xe9, 0x7f, 0x10, 0xe9, 0x17, 0xe0, 0x37, 0xb0, + 0xaa, 0xd6, 0xf9, 0x67, 0x9a, 0x82, 0x3e, 0x5d, 0x6f, 0x1a, 0x83, 0x23, 0xef, 0x35, 0x39, 0x1b, + 0x52, 0x3c, 0xa5, 0xf1, 0x10, 0x27, 0xcc, 0xce, 0x24, 0x4d, 0x9c, 0xef, 0xb9, 0x75, 0xde, 0x5d, + 0x3c, 0x6f, 0x0d, 0xb0, 0xda, 0x1a, 0xe0, 0x6d, 0x6b, 0x80, 0xc7, 0x9d, 0x51, 0x5a, 0xed, 0x8c, + 0xd2, 0xcb, 0xce, 0x28, 0xdd, 0x76, 0xbe, 0x32, 0x5f, 0x7e, 0x3e, 0x0b, 0x95, 0x43, 0x2a, 0xea, + 0x37, 0xf7, 0xde, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5c, 0xc2, 0xbd, 0x0a, 0x37, 0x02, 0x00, 0x00, +} + +func (m *Vote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Vote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.FinalitySig != nil { + { + size := m.FinalitySig.Size() + i -= size + if _, err := m.FinalitySig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BlockHeight != 0 { + i = encodeVarintFinality(dAtA, i, uint64(m.BlockHeight)) + i-- + dAtA[i] = 0x10 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IndexedBlock) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IndexedBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcHash != nil { + { + size := m.BtcHash.Size() + i -= size + if _, err := m.BtcHash.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.BtcHeight != 0 { + i = encodeVarintFinality(dAtA, i, uint64(m.BtcHeight)) + i-- + dAtA[i] = 0x18 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0x12 + } + if m.Height != 0 { + i = encodeVarintFinality(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintFinality(dAtA []byte, offset int, v uint64) int { + offset -= sovFinality(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Vote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovFinality(uint64(l)) + } + if m.BlockHeight != 0 { + n += 1 + sovFinality(uint64(m.BlockHeight)) + } + if m.FinalitySig != nil { + l = m.FinalitySig.Size() + n += 1 + l + sovFinality(uint64(l)) + } + return n +} + +func (m *IndexedBlock) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovFinality(uint64(m.Height)) + } + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovFinality(uint64(l)) + } + if m.BtcHeight != 0 { + n += 1 + sovFinality(uint64(m.BtcHeight)) + } + if m.BtcHash != nil { + l = m.BtcHash.Size() + n += 1 + l + sovFinality(uint64(l)) + } + return n +} + +func sovFinality(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozFinality(x uint64) (n int) { + return sovFinality(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Vote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Vote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Vote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) + } + m.BlockHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FinalitySig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.SchnorrEOTSSig + m.FinalitySig = &v + if err := m.FinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFinality(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthFinality + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IndexedBlock) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IndexedBlock: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IndexedBlock: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcHeight", wireType) + } + m.BtcHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BtcHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BTCHeaderHashBytes + m.BtcHash = &v + if err := m.BtcHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFinality(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthFinality + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipFinality(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFinality + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFinality + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFinality + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthFinality + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupFinality + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthFinality + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthFinality = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowFinality = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupFinality = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/finality/types/keys.go b/x/finality/types/keys.go index 7e07266bd..76c6dd3c8 100644 --- a/x/finality/types/keys.go +++ b/x/finality/types/keys.go @@ -14,6 +14,13 @@ const ( MemStoreKey = "mem_finality" ) +var ( + BlockKey = []byte{0x01} // key prefix for blocks + VoteKey = []byte{0x02} // key prefix for votes + PubRandKey = []byte{0x03} // key prefix for public randomness + ParamsKey = []byte{0x04} // key prefix for the parameters +) + func KeyPrefix(p string) []byte { return []byte(p) } diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go new file mode 100644 index 000000000..fadaf538a --- /dev/null +++ b/x/finality/types/msg.go @@ -0,0 +1,30 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ensure that these message types implement the sdk.Msg interface +var ( + _ sdk.Msg = &MsgUpdateParams{} +) + +// GetSigners returns the expected signers for a MsgUpdateParams message. +func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(m.Authority) + return []sdk.AccAddress{addr} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgUpdateParams) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { + return errorsmod.Wrap(err, "invalid authority address") + } + + if err := m.Params.Validate(); err != nil { + return err + } + + return nil +} diff --git a/x/finality/types/params.go b/x/finality/types/params.go index 357196ad6..c25ddb70a 100644 --- a/x/finality/types/params.go +++ b/x/finality/types/params.go @@ -12,14 +12,9 @@ func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -// NewParams creates a new Params instance -func NewParams() Params { - return Params{} -} - // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams() + return Params{} // TODO: default params } // ParamSetPairs get the params.ParamSet diff --git a/x/finality/types/params.pb.go b/x/finality/types/params.pb.go index e967d01e6..1a26e045b 100644 --- a/x/finality/types/params.pb.go +++ b/x/finality/types/params.pb.go @@ -25,6 +25,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { + // min_pub_rand is the minimum number of public randomoness each + // message should commit + MinPubRand uint64 `protobuf:"varint,1,opt,name=min_pub_rand,json=minPubRand,proto3" json:"min_pub_rand,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -59,6 +62,13 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo +func (m *Params) GetMinPubRand() uint64 { + if m != nil { + return m.MinPubRand + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "babylon.finality.v1.Params") } @@ -66,17 +76,19 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/params.proto", fileDescriptor_25539c9a61c72ee9) } var fileDescriptor_25539c9a61c72ee9 = []byte{ - // 158 bytes of a gzipped FileDescriptorProto + // 192 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xaa, 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, - 0x58, 0x10, 0xa5, 0x4a, 0x7c, 0x5c, 0x6c, 0x01, 0x60, 0xad, 0x56, 0x2c, 0x33, 0x16, 0xc8, 0x33, - 0x38, 0x79, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, - 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x41, 0x7a, 0x66, - 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0xfc, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, - 0x18, 0x47, 0xbf, 0x02, 0xe1, 0xa0, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0x15, 0xc6, - 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x67, 0x01, 0xe8, 0x48, 0xb1, 0x00, 0x00, 0x00, + 0x58, 0x10, 0xa5, 0x4a, 0x06, 0x5c, 0x6c, 0x01, 0x60, 0xad, 0x42, 0x0a, 0x5c, 0x3c, 0xb9, 0x99, + 0x79, 0xf1, 0x05, 0xa5, 0x49, 0xf1, 0x45, 0x89, 0x79, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x2c, + 0x41, 0x5c, 0xb9, 0x99, 0x79, 0x01, 0xa5, 0x49, 0x41, 0x89, 0x79, 0x29, 0x56, 0x2c, 0x33, 0x16, + 0xc8, 0x33, 0x38, 0x79, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, + 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x41, + 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0x05, 0xc9, 0x19, 0x89, + 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0xc2, 0xc9, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, + 0x47, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc1, 0x3d, 0x78, 0xb7, 0xd3, 0x00, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -99,6 +111,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MinPubRand != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinPubRand)) + i-- + dAtA[i] = 0x8 + } return len(dAtA) - i, nil } @@ -119,6 +136,9 @@ func (m *Params) Size() (n int) { } var l int _ = l + if m.MinPubRand != 0 { + n += 1 + sovParams(uint64(m.MinPubRand)) + } return n } @@ -157,6 +177,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinPubRand", wireType) + } + m.MinPubRand = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinPubRand |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index fd355c32f..d08626598 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -6,10 +6,17 @@ package types import ( context "context" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,19 +30,132 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// MsgUpdateParams defines a message for updating finality module parameters. +type MsgUpdateParams struct { + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params defines the finality parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{0} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{1} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgUpdateParams)(nil), "babylon.finality.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.finality.v1.MsgUpdateParamsResponse") +} + func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } var fileDescriptor_2dd6da066b6baf1d = []byte{ - // 134 bytes of a gzipped FileDescriptorProto + // 318 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xca, 0xea, 0xc1, 0x64, 0xf5, - 0xca, 0x0c, 0x8d, 0x58, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, - 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, - 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, - 0x57, 0x1f, 0x6a, 0x40, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0xb0, 0xad, 0xa4, - 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x6c, 0x9d, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x18, 0x3e, - 0x32, 0xee, 0x8e, 0x00, 0x00, 0x00, + 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xf2, 0xfa, 0x20, 0x16, 0x44, 0xa9, 0x94, + 0x64, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x4a, 0x89, 0x43, 0x78, + 0xfa, 0xb9, 0xc5, 0xe9, 0x20, 0xd3, 0x73, 0x8b, 0xd3, 0xa1, 0x12, 0x0a, 0xd8, 0x2c, 0x2f, 0x48, + 0x2c, 0x4a, 0xcc, 0x85, 0x6a, 0x55, 0x9a, 0xc2, 0xc8, 0xc5, 0xef, 0x5b, 0x9c, 0x1e, 0x5a, 0x90, + 0x92, 0x58, 0x92, 0x1a, 0x00, 0x96, 0x11, 0x32, 0xe3, 0xe2, 0x4c, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, + 0xca, 0x2c, 0xa9, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x74, 0x92, 0xb8, 0xb4, 0x45, 0x57, 0x04, + 0x6a, 0xa7, 0x63, 0x4a, 0x4a, 0x51, 0x6a, 0x71, 0x71, 0x70, 0x49, 0x51, 0x66, 0x5e, 0x7a, 0x10, + 0x42, 0xa9, 0x90, 0x25, 0x17, 0x1b, 0xc4, 0x6c, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x69, + 0x3d, 0x2c, 0xbe, 0xd3, 0x83, 0x58, 0xe2, 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, 0x54, 0x83, + 0x15, 0x5f, 0xd3, 0xf3, 0x0d, 0x5a, 0x08, 0xa3, 0x94, 0x24, 0xb9, 0xc4, 0xd1, 0x5c, 0x15, 0x94, + 0x5a, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x6a, 0x94, 0xc9, 0xc5, 0xec, 0x5b, 0x9c, 0x2e, 0x94, 0xc4, + 0xc5, 0x83, 0xe2, 0x68, 0x15, 0xac, 0x96, 0xa1, 0x19, 0x22, 0xa5, 0x43, 0x8c, 0x2a, 0x98, 0x55, + 0x4e, 0x5e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, + 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, + 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x35, 0x31, 0x39, 0x23, 0x31, 0x33, 0x0f, + 0xc6, 0xd1, 0xaf, 0x40, 0x04, 0x79, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x38, 0xbc, 0x8d, + 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x43, 0xfa, 0x08, 0x60, 0x10, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +170,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + // UpdateParams updates the finality module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -60,22 +182,414 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { + // UpdateParams updates the finality module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.finality.v1.Msg", HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "babylon/finality/v1/tx.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/finality/v1/tx.proto", +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/zoneconcierge/types/tx.pb.go b/x/zoneconcierge/types/tx.pb.go index 60eb3d932..111da71a1 100644 --- a/x/zoneconcierge/types/tx.pb.go +++ b/x/zoneconcierge/types/tx.pb.go @@ -37,7 +37,7 @@ type MsgUpdateParams struct { // for AddressString instead of string, but the functionality is not yet implemented // in cosmos-proto Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` - // params defines the epoching paramaeters parameters to update. + // params defines the zoneconcierge parameters to update. // // NOTE: All parameters must be supplied. Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` From e96cfcc497dcffee331e6d89cb5245208b478e45 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 23 Jun 2023 07:16:07 +0200 Subject: [PATCH 014/202] Add functions related to slashing tx (#7) * Add functions related to slashing tx --- btcstaking/staking.go | 340 +++++++++++++++++++++++++++++++++++-- btcstaking/staking_test.go | 99 +++++++++-- 2 files changed, 411 insertions(+), 28 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index d2173574a..edee0eed6 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -115,6 +115,11 @@ type StakingScriptData struct { StakingTime uint16 } +type StakingOutputInfo struct { + StakingScriptData *StakingScriptData + StakingAmount btcutil.Amount +} + func NewStakingScriptData( stakerKey, validatorKey, @@ -169,7 +174,7 @@ func (sd *StakingScriptData) BuildStakingScript() ([]byte, error) { // error is returned. If script is valid, StakingScriptData is returned, which contains all // relevant data parsed from the script. // Only stateless checks are performed. -func ParseStakingTransactionScript(version uint16, script []byte) (*StakingScriptData, error) { +func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { // OP_CHECKSIG // OP_NOTIF // @@ -206,7 +211,7 @@ func ParseStakingTransactionScript(version uint16, script []byte) (*StakingScrip } var templateOffset int - tokenizer := txscript.MakeScriptTokenizer(version, script) + tokenizer := txscript.MakeScriptTokenizer(0, script) for tokenizer.Next() { // Not an staking script if it has more opcodes than expected in the // template. @@ -294,7 +299,11 @@ func ParseStakingTransactionScript(version uint16, script []byte) (*StakingScrip } // we do not need to check error here, as we already validated that all public keys are not nil - scriptData, _ := NewStakingScriptData(stakerPk1, validatorPk, juryPk, uint16(template[12].extractedInt)) + scriptData, err := NewStakingScriptData(stakerPk1, validatorPk, juryPk, uint16(template[12].extractedInt)) + + if err != nil { + panic(fmt.Sprintf("unexpected error: %v", err)) + } return scriptData, nil } @@ -308,13 +317,14 @@ func UnspendableKeyPathInternalPubKey() btcec.PublicKey { return *pubKey } -// TaprootAddressForScript returns a Taproot address commiting to the given pkScript +// TaprootAddressForScript returns a Taproot address commiting to the given script, built taproot tree +// has only one leaf node. func TaprootAddressForScript( - pkScript []byte, + script []byte, internalPubKey *btcec.PublicKey, net *chaincfg.Params) (*btcutil.AddressTaproot, error) { - tapLeaf := txscript.NewBaseTapLeaf(pkScript) + tapLeaf := txscript.NewBaseTapLeaf(script) tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) @@ -334,6 +344,26 @@ func TaprootAddressForScript( return address, nil } +// BuildUnspendableTaprootPkScript builds taproot pkScript which commits to the provided script with +// unspendable spending key path. +func BuildUnspendableTaprootPkScript(rawScript []byte, net *chaincfg.Params) ([]byte, error) { + internalPubKey := UnspendableKeyPathInternalPubKey() + + address, err := TaprootAddressForScript(rawScript, &internalPubKey, net) + + if err != nil { + return nil, err + } + + pkScript, err := txscript.PayToAddrScript(address) + + if err != nil { + return nil, err + } + + return pkScript, nil +} + // BuildStakingOutput builds out which is necessary for staking transaction to stake funds. func BuildStakingOutput( stakerKey, @@ -355,15 +385,7 @@ func BuildStakingOutput( return nil, nil, err } - internalPubKey := UnspendableKeyPathInternalPubKey() - - address, err := TaprootAddressForScript(script, &internalPubKey, net) - - if err != nil { - return nil, nil, err - } - - pkScript, err := txscript.PayToAddrScript(address) + pkScript, err := BuildUnspendableTaprootPkScript(script, net) if err != nil { return nil, nil, err @@ -421,3 +443,291 @@ func BuildWitnessToSpendStakingOutput( witnessStack[2] = ctrlBlockBytes return witnessStack, nil } + +// ValidateStakingOutputPkScript validates that: +// - provided output commits to the given script with unspendable spending key path +// - provided script is valid staking script +func ValidateStakingOutputPkScript( + output *wire.TxOut, + script []byte, + net *chaincfg.Params) (*StakingScriptData, error) { + if output == nil { + return nil, fmt.Errorf("provided output cannot be nil") + } + + pkScript, err := BuildUnspendableTaprootPkScript(script, net) + + if err != nil { + return nil, err + } + + if !bytes.Equal(output.PkScript, pkScript) { + return nil, fmt.Errorf("output does not commit to the given script") + } + + return ParseStakingTransactionScript(script) +} + +// BuildSlashingTxFromOutpoint builds valid slashing transaction, using provided: +// - stakingOutput - staking output +// - slashingAddress - address to which slashed funds will go +// - fee - fee for the transaction +// It does not attach script sig to the transaction nor the witness. +// It only validates that provided address is standard btc address and slashing value is larger than 0 +func BuildSlashingTxFromOutpoint( + stakingOutput wire.OutPoint, + slashingAddress btcutil.Address, + slashingValue int64) (*wire.MsgTx, error) { + + addrScript, err := txscript.PayToAddrScript(slashingAddress) + + if err != nil { + return nil, err + } + + if slashingValue <= 0 { + return nil, fmt.Errorf("slashing value cannot be smaller or equal 0") + } + + tx := wire.NewMsgTx(wire.TxVersion) + // TODO: this builds input with sequence number equal to MaxTxInSequenceNum, which + // means this tx is not replacable. + input := wire.NewTxIn(&stakingOutput, nil, nil) + tx.AddTxIn(input) + tx.AddTxOut(wire.NewTxOut(slashingValue, addrScript)) + return tx, nil +} + +func getPossibleStakingOutput( + stakingTx *wire.MsgTx, + stakingOutputIdx uint32, +) (*wire.TxOut, error) { + if stakingTx == nil { + return nil, fmt.Errorf("provided staking transaction must not be nil") + } + + if stakingOutputIdx >= uint32(len(stakingTx.TxOut)) { + return nil, fmt.Errorf("invalid staking output index %d, tx has %d outputs", stakingOutputIdx, len(stakingTx.TxOut)) + } + + stakingOutput := stakingTx.TxOut[stakingOutputIdx] + + if !txscript.IsPayToTaproot(stakingOutput.PkScript) { + return nil, fmt.Errorf("must be pay to taproot output") + } + + return stakingOutput, nil +} + +// BuildSlashingTxFromOutpoint builds valid slashing transaction, using provided: +// - stakingTx - staking trasaction +// - stakingOutputIdx - index of the output committing to staking script +// - slashingAddress - address to which slashed funds will go +// - fee - fee for the transaction +// It does not attach script sig to the transaction nor the witness. +// It validates: +// - stakingTx is not nil +// - staking tx has output at stakingOutputIdx +// - staking output at stakingOutputIdx is valid staking output i.e p2tr output +func BuildSlashingTxFromStakingTx( + stakingTx *wire.MsgTx, + stakingOutputIdx uint32, + slashingAddress btcutil.Address, + fee int64, +) (*wire.MsgTx, error) { + stakingOutput, err := getPossibleStakingOutput(stakingTx, stakingOutputIdx) + + if err != nil { + return nil, err + } + + stakingTxHash := stakingTx.TxHash() + + stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) + + return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee) +} + +// BuildSlashingTxFromStakingTxStrict builds valid slashing transaction, using provided: +// - stakingTx - staking trasaction +// - stakingOutputIdx - index of the output committing to staking script +// - slashingAddress - address to which slashed funds will go +// - fee - fee for the transaction +// - script - staking script to which staking output should commit +// - scriptVersion - version of the script +// - net - network on wchich transactions should take place +// It validates: +// - the same stuff as BuildSlashingTxFromStakingTx +// - wheter staking output commits to the provided script +// - whether provided script is valid staking script +func BuildSlashingTxFromStakingTxStrict( + stakingTx *wire.MsgTx, + stakingOutputIdx uint32, + slashingAddress btcutil.Address, + fee int64, + script []byte, + net *chaincfg.Params, +) (*wire.MsgTx, error) { + stakingOutput, err := getPossibleStakingOutput(stakingTx, stakingOutputIdx) + + if err != nil { + return nil, err + } + + if _, err := ValidateStakingOutputPkScript(stakingOutput, script, net); err != nil { + return nil, err + } + + stakingTxHash := stakingTx.TxHash() + + stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) + + return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee) +} + +// CheckSlashingTx perform basic checks on slashing transaction: +// - slashing transaction is not nil +// - slashing transaction has exactly one input +// - slashing transaction is not replacable +// - slashing transaction has exactly one output +// - slashing transaction locktime is 0 +// - slashing transaction output is simple pay to address script paying to provided slashing address +func CheckSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error { + + if slashingTx == nil { + return fmt.Errorf("provided slashing transaction must not be nil") + } + + if len(slashingTx.TxIn) != 1 { + return fmt.Errorf("slashing transaction must have exactly one input") + } + + if slashingTx.TxIn[0].Sequence != wire.MaxTxInSequenceNum { + return fmt.Errorf("slashing transaction must be not replacable") + } + + if len(slashingTx.TxOut) != 1 { + return fmt.Errorf("slashing transaction must have exactly one output") + } + + if slashingTx.LockTime != 0 { + return fmt.Errorf("slashing transaction locktime must be 0") + } + + pkScript, err := txscript.PayToAddrScript(slashingAddress) + + if err != nil { + return err + } + + if !bytes.Equal(slashingTx.TxOut[0].PkScript, pkScript) { + return fmt.Errorf("slashing transaction must pay to provided slashing address") + } + + return nil +} + +// GetIdxOutputCommitingToScript retrieves index of the output committing to the provided script. +// It returns error if: +// - tx is nil +// - tx does not have output committing to the provided script +// - tx has more than one output committing to the provided script +func GetIdxOutputCommitingToScript( + tx *wire.MsgTx, + script []byte, + net *chaincfg.Params) (int, error) { + + if tx == nil { + return -1, fmt.Errorf("provided staking transaction must not be nil") + } + + script, err := BuildUnspendableTaprootPkScript(script, net) + + if err != nil { + return -1, err + } + + var comittingOutputIdx int = -1 + for i, out := range tx.TxOut { + if bytes.Equal(out.PkScript, script) && comittingOutputIdx < 0 { + comittingOutputIdx = i + } else if bytes.Equal(out.PkScript, script) && comittingOutputIdx >= 0 { + return -1, fmt.Errorf("transaction has more than one output committing to the provided script") + } + } + + if comittingOutputIdx < 0 { + return -1, fmt.Errorf("transaction does not have output committing to the provided script") + } + return comittingOutputIdx, nil +} + +// CheckTransactions validates all relevant data of slashing and staking transaction: +// - slashing transaction is valid +// - staking transaction script is valid +// - staking transaction has output committing to the provided script +// - slashing transaction input is pointing to staking transaction staking output +// - that min fee for slashing tx is preserved +// In case of success, it returns data extracted from valid staking script and staking amount. +func CheckTransactions( + slashingTx *wire.MsgTx, + stakingTx *wire.MsgTx, + slashingTxMinFee int64, + slashingAddress btcutil.Address, + script []byte, + net *chaincfg.Params, +) (*StakingOutputInfo, error) { + if slashingTxMinFee <= 0 { + return nil, fmt.Errorf("slashing transaction min fee must be larger than 0") + } + + //1. Check slashing tx + if err := CheckSlashingTx(slashingTx, slashingAddress); err != nil { + return nil, err + } + + //2. Check staking script. + scriptData, err := ParseStakingTransactionScript(script) + + if err != nil { + return nil, err + } + + //3. Check that staking transaction has output committing to the provided script + stakingOutputIdx, err := GetIdxOutputCommitingToScript(stakingTx, script, net) + + if err != nil { + return nil, err + } + + //4. Check that slashing transaction input is pointing to staking transaction + stakingTxHash := stakingTx.TxHash() + if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { + return nil, fmt.Errorf("slashing transaction must spend staking output") + } + + //5. Check that index of the found output matches index of the input in slashing transaction + if slashingTx.TxIn[0].PreviousOutPoint.Index != uint32(stakingOutputIdx) { + return nil, fmt.Errorf("slashing transaction input must spend staking output") + } + + stakingOutput := stakingTx.TxOut[stakingOutputIdx] + + //6. Check fees + if slashingTx.TxOut[0].Value <= 0 || stakingOutput.Value <= 0 { + return nil, fmt.Errorf("values of slashing and staking transaction must be larger than 0") + } + + if stakingOutput.Value <= slashingTx.TxOut[0].Value { + return nil, fmt.Errorf("slashing transaction must not spend more than staking transaction") + } + + if stakingOutput.Value-slashingTx.TxOut[0].Value <= slashingTxMinFee { + return nil, fmt.Errorf("slashing transaction fee must be larger than %d", slashingTxMinFee) + } + + return &StakingOutputInfo{ + StakingScriptData: scriptData, + StakingAmount: btcutil.Amount(stakingOutput.Value), + }, nil +} diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 4130fa245..dd8c6664f 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -20,30 +20,103 @@ import ( "github.com/stretchr/testify/require" ) +func genValidStakingScriptData(t *testing.T, r *rand.Rand) *btcstaking.StakingScriptData { + stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + juryPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + stakingTime := uint16(r.Intn(math.MaxUint16)) + + _, stakerPublicKey := btcec.PrivKeyFromBytes(stakerPrivKeyBytes) + _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) + _, juryPublicKey := btcec.PrivKeyFromBytes(juryPrivKeyBytes) + + sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, juryPublicKey, stakingTime) + + return sd +} + func FuzzGeneratingParsingValidStakingScript(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) - validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) - juryPrivKeyBytes := datagen.GenRandomByteArray(r, 32) - stakingTime := uint16(r.Intn(math.MaxUint16)) - _, stakerPublicKey := btcec.PrivKeyFromBytes(stakerPrivKeyBytes) - _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) - _, juryPublicKey := btcec.PrivKeyFromBytes(juryPrivKeyBytes) + sd := genValidStakingScriptData(t, r) - sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, juryPublicKey, stakingTime) + script, err := sd.BuildStakingScript() + require.NoError(t, err) + parsedScript, err := btcstaking.ParseStakingTransactionScript(script) + require.NoError(t, err) + + require.Equal(t, parsedScript.StakingTime, sd.StakingTime) + require.Equal(t, schnorr.SerializePubKey(sd.StakerKey), schnorr.SerializePubKey(parsedScript.StakerKey)) + require.Equal(t, schnorr.SerializePubKey(sd.ValidatorKey), schnorr.SerializePubKey(parsedScript.ValidatorKey)) + require.Equal(t, schnorr.SerializePubKey(sd.JuryKey), schnorr.SerializePubKey(parsedScript.JuryKey)) + }) +} +func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + // we do not care for inputs in staking tx + stakingTx := wire.NewMsgTx(2) + stakingOutputIdx := r.Intn(5) + // always more outputs than stakingOutputIdx + stakingTxNumOutputs := r.Intn(5) + 10 + sd := genValidStakingScriptData(t, r) script, err := sd.BuildStakingScript() require.NoError(t, err) - parsedScript, err := btcstaking.ParseStakingTransactionScript(0, script) + minStakingValue := 5000 + minFee := 2000 + + slashingAddress, err := btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) + require.NoError(t, err) + + for i := 0; i < stakingTxNumOutputs; i++ { + if i == stakingOutputIdx { + stakingOutput, _, err := btcstaking.BuildStakingOutput( + sd.StakerKey, + sd.ValidatorKey, + sd.JuryKey, + sd.StakingTime, + btcutil.Amount(r.Intn(5000)+minStakingValue), + &chaincfg.MainNetParams, + ) + require.NoError(t, err) + stakingTx.AddTxOut(stakingOutput) + } else { + stakingTx.AddTxOut( + &wire.TxOut{ + PkScript: datagen.GenRandomByteArray(r, 32), + Value: int64(r.Intn(5000) + 1), + }, + ) + } + } + + fee := int64(r.Intn(1000) + minFee) + + slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( + stakingTx, + uint32(stakingOutputIdx), + slashingAddress, + fee, + script, + &chaincfg.MainNetParams, + ) + require.NoError(t, err) - require.Equal(t, parsedScript.StakingTime, stakingTime) - require.Equal(t, schnorr.SerializePubKey(stakerPublicKey), schnorr.SerializePubKey(parsedScript.StakerKey)) - require.Equal(t, schnorr.SerializePubKey(validatorPublicKey), schnorr.SerializePubKey(parsedScript.ValidatorKey)) - require.Equal(t, schnorr.SerializePubKey(juryPublicKey), schnorr.SerializePubKey(parsedScript.JuryKey)) + _, err = btcstaking.CheckTransactions( + slashingTx, + stakingTx, + int64(minFee), + slashingAddress, + script, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) }) } From 9bfe627ac7ba3268205c7208a1e22e92f9a6de8f Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 23 Jun 2023 17:05:10 +1000 Subject: [PATCH 015/202] btcstaking: handler for MsgCreateBTCDelegation (#8) --- Makefile | 1 + app/app.go | 6 +- btcstaking/staking.go | 8 +- proto/babylon/btcstaking/v1/btcstaking.proto | 27 +- proto/babylon/btcstaking/v1/params.proto | 4 +- proto/babylon/btcstaking/v1/tx.proto | 21 +- test/e2e/configurer/chain/commands.go | 6 +- testutil/datagen/btc_address.go | 17 + testutil/datagen/btc_transaction.go | 4 +- testutil/datagen/btcstaking.go | 78 +++- testutil/keeper/btcstaking.go | 8 +- types/btc_config.go | 43 +- types/btc_schnorr_pk.go | 14 +- types/btc_schnorr_pk_test.go | 2 +- types/btc_slashing_tx.go | 41 ++ types/btc_staking_tx.go | 37 -- x/btccheckpoint/types/btcutils.go | 2 +- x/btccheckpoint/types/types.go | 29 ++ x/btcstaking/genesis_test.go | 2 +- x/btcstaking/keeper/btc_delegations.go | 7 - x/btcstaking/keeper/keeper.go | 50 ++- x/btcstaking/keeper/msg_server.go | 86 +++- x/btcstaking/keeper/msg_server_test.go | 113 +++++- x/btcstaking/keeper/params_test.go | 2 +- x/btcstaking/keeper/query_params_test.go | 2 +- x/btcstaking/types/btcstaking.go | 99 ++++- x/btcstaking/types/btcstaking.pb.go | 401 +++++++++++++++---- x/btcstaking/types/btcstaking_test.go | 45 +++ x/btcstaking/types/expected_keepers.go | 15 + x/btcstaking/types/mocked_keepers.go | 193 +++++++++ x/btcstaking/types/msg.go | 26 +- x/btcstaking/types/params.pb.go | 34 +- x/btcstaking/types/pop.go | 2 +- x/btcstaking/types/pop_test.go | 6 +- x/btcstaking/types/tx.pb.go | 215 ++++------ 35 files changed, 1263 insertions(+), 383 deletions(-) create mode 100644 testutil/datagen/btc_address.go delete mode 100644 types/btc_staking_tx.go create mode 100644 x/btcstaking/types/btcstaking_test.go create mode 100644 x/btcstaking/types/mocked_keepers.go diff --git a/Makefile b/Makefile index 39f00bccf..982889e0e 100644 --- a/Makefile +++ b/Makefile @@ -149,6 +149,7 @@ mocks: $(MOCKS_DIR) $(mockgen_cmd) -source=x/checkpointing/types/expected_keepers.go -package mocks -destination testutil/mocks/checkpointing_expected_keepers.go $(mockgen_cmd) -source=x/checkpointing/keeper/bls_signer.go -package mocks -destination testutil/mocks/bls_signer.go $(mockgen_cmd) -source=x/zoneconcierge/types/expected_keepers.go -package types -destination x/zoneconcierge/types/mocked_keepers.go + $(mockgen_cmd) -source=x/btcstaking/types/expected_keepers.go -package types -destination x/btcstaking/types/mocked_keepers.go .PHONY: mocks $(MOCKS_DIR): diff --git a/app/app.go b/app/app.go index dd2b58840..2587106c9 100644 --- a/app/app.go +++ b/app/app.go @@ -373,6 +373,7 @@ func NewBabylonApp( // but this way it makes babylon app more testable btcConfig := bbn.ParseBtcOptionsFromConfig(appOpts) powLimit := btcConfig.PowLimit() + btcNetParams := btcConfig.NetParams() appCodec := encodingConfig.Marshaler legacyAmino := encodingConfig.Amino @@ -616,7 +617,10 @@ func NewBabylonApp( // set up BTC staking keeper app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( - appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], + app.BTCLightClientKeeper, app.BtcCheckpointKeeper, + btcNetParams, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.FinalityKeeper = finalitykeeper.NewKeeper( appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, diff --git a/btcstaking/staking.go b/btcstaking/staking.go index edee0eed6..af591d0c9 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -308,13 +308,13 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { return scriptData, nil } -func UnspendableKeyPathInternalPubKey() btcec.PublicKey { +func UnspendableKeyPathInternalPubKey() *btcec.PublicKey { // TODO: We could cache it in some cached private package variable if performance // is necessary, as this returns always the same value. keyBytes, _ := hex.DecodeString(unspendableKeyPath) // We are using btcec here, as key is 33 byte compressed format. pubKey, _ := btcec.ParsePubKey(keyBytes) - return *pubKey + return pubKey } // TaprootAddressForScript returns a Taproot address commiting to the given script, built taproot tree @@ -349,7 +349,7 @@ func TaprootAddressForScript( func BuildUnspendableTaprootPkScript(rawScript []byte, net *chaincfg.Params) ([]byte, error) { internalPubKey := UnspendableKeyPathInternalPubKey() - address, err := TaprootAddressForScript(rawScript, &internalPubKey, net) + address, err := TaprootAddressForScript(rawScript, internalPubKey, net) if err != nil { return nil, err @@ -411,7 +411,7 @@ func BuildWitnessToSpendStakingOutput( tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( - &internalPubKey, + internalPubKey, ) ctrlBlockBytes, err := ctrlBlock.ToBytes() diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 37b8a6bbe..c0156a347 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -40,18 +40,19 @@ message BTCDelegation { // quantified in satoshi uint64 total_sat = 7; // staking_tx is the staking tx - // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - bytes staking_tx = 8 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCStakingTx" ]; - // staking_tx_sig is the signature of the staking tx - // signed by SK corresponding to btc_pk - bytes staking_tx_sig = 9 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + StakingTx staking_tx = 8; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - bytes slashing_tx = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; - // slashing_tx_sig is the signature of the slashing tx - // signed by SK corresponding to btc_pk - bytes slashing_tx_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes slashing_tx = 9 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; + // delegator_sig is the signature on the slashing tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the staking tx output. + bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // jury_sig is the signature signature on the slashing tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It will be a part of the witness for the staking tx output. + bytes jury_sig = 12 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // ProofOfPossession is the proof of possession that a Babylon secp256k1 @@ -64,3 +65,11 @@ message ProofOfPossession { // the signature follows encoding in BIP-340 spec bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } + +// StakingTx is the tx for delegating BTC +message StakingTx { + // tx is the staking tx in bytes + bytes tx = 1; + // staking_script is the script for the staking tx's output + bytes staking_script = 2; +} \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index bba57ce49..5f1a23a3a 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -13,6 +13,6 @@ message Params { // the PK follows encoding in BIP-340 spec on Bitcoin bytes jury_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // slashing address is the address that the slashed BTC goes to - // the address is on Bitcoin - bytes slashing_address = 2; + // the address is in string on Bitcoin + string slashing_address = 2; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index fff1bf13c..4c4b18042 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -43,20 +43,17 @@ message MsgCreateBTCDelegation { // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; // staking_tx is the staking tx - // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - bytes staking_tx = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCStakingTx" ]; - // staking_tx_sig is the signature of the staking tx - // signed by SK corresponding to btc_pk - bytes staking_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + StakingTx staking_tx = 4; // staking_tx_info is the tx info of the staking tx, including the Merkle proof - babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 6; + babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 5; // slashing_tx is the slashing tx - // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. - bytes slashing_tx = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; - // slashing_tx_sig is the signature of the slashing tx - // signed by SK corresponding to btc_pk - bytes slashing_tx_sig = 8 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // Note that the tx itself does not contain signatures, which are off-chain. + bytes slashing_tx = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; + // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the staking tx output. + // The staking tx output further needs signatures from jury and validator in + // order to be spendable. + bytes delegator_sig = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation message MsgCreateBTCDelegationResponse {} diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index 945f85a20..df6f5a6e7 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -140,8 +140,10 @@ func (n *NodeConfig) FinalizeSealedEpochs(startEpoch uint64, lastEpoch uint64) { ) require.NoError(n.t, err) - opReturn1 := datagen.CreateBlockWithTransaction(r, currentBtcTip.Header.ToBlockHeader(), p1) - opReturn2 := datagen.CreateBlockWithTransaction(r, opReturn1.HeaderBytes.ToBlockHeader(), p2) + tx1 := datagen.CreatOpReturnTransaction(r, p1) + opReturn1 := datagen.CreateBlockWithTransaction(r, currentBtcTip.Header.ToBlockHeader(), tx1) + tx2 := datagen.CreatOpReturnTransaction(r, p2) + opReturn2 := datagen.CreateBlockWithTransaction(r, opReturn1.HeaderBytes.ToBlockHeader(), tx2) n.InsertHeader(&opReturn1.HeaderBytes) n.InsertHeader(&opReturn2.HeaderBytes) diff --git a/testutil/datagen/btc_address.go b/testutil/datagen/btc_address.go new file mode 100644 index 000000000..7de1ce836 --- /dev/null +++ b/testutil/datagen/btc_address.go @@ -0,0 +1,17 @@ +package datagen + +import ( + "math/rand" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" +) + +func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (string, error) { + pkHash := GenRandomByteArray(r, 20) + addr, err := btcutil.NewAddressPubKeyHash(pkHash, net) + if err != nil { + return "", err + } + return addr.EncodeAddress(), nil +} diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index b9e86c45a..20774fb17 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -303,13 +303,13 @@ type BtcHeaderWithProof struct { func CreateBlockWithTransaction( r *rand.Rand, ph *wire.BlockHeader, - babylonData []byte, + tx *wire.MsgTx, ) *BtcHeaderWithProof { var transactions []*wire.MsgTx // height does not matter here, as it is used only for calculation of reward transactions = append(transactions, createCoinbaseTx(int32(889), &chaincfg.SimNetParams)) - transactions = append(transactions, CreatOpReturnTransaction(r, babylonData)) + transactions = append(transactions, tx) randHeader := GenRandomBtcdHeader(r) randHeader.Version = ph.Version diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 18d802a44..ab7ac519f 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -1,24 +1,34 @@ package datagen import ( + "bytes" "fmt" "math/rand" + "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" ) func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { // key pairs - btcSK, btcPK, err := GenRandomBTCKeyPair(r) + btcSK, _, err := GenRandomBTCKeyPair(r) if err != nil { return nil, err } + return GenRandomBTCValidatorWithBTCPK(r, btcSK) +} + +func GenRandomBTCValidatorWithBTCPK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { + // key pairs + btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) - if err != nil { - return nil, err - } bbnSK, bbnPK, err := GenRandomSecp256k1KeyPair(r) if err != nil { return nil, err @@ -34,7 +44,65 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { } return &bstypes.BTCValidator{ BabylonPk: secp256k1PK, - BtcPk: &bip340PK, + BtcPk: bip340PK, Pop: pop, }, nil } + +func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validatorPK, juryPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string) (*bstypes.StakingTx, *bbn.BTCSlashingTx, error) { + btcNet := &chaincfg.SimNetParams + + stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( + stakerSK.PubKey(), + validatorPK, + juryPK, + stakingTimeBlocks, + btcutil.Amount(stakingValue), + btcNet, + ) + if err != nil { + return nil, nil, err + } + + tx := wire.NewMsgTx(2) + // an arbitrary input, doesn't matter + txid, err := chainhash.NewHash(GenRandomByteArray(r, 32)) + if err != nil { + return nil, nil, err + } + lastOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + tx.AddTxIn(wire.NewTxIn(lastOut, []byte{1, 2, 3}, [][]byte{[]byte{1, 2, 3}})) + // 2 outputs for changes and staking output + tx.AddTxOut(wire.NewTxOut(100, []byte{1, 2, 3})) // output for change, doesn't matter + tx.AddTxOut(stakingOutput) + + // construct staking tx + var buf bytes.Buffer + err = tx.Serialize(&buf) + if err != nil { + return nil, nil, err + } + stakingTx := &bstypes.StakingTx{ + Tx: buf.Bytes(), + StakingScript: stakingScript, + } + + // construct slashing tx + slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) + if err != nil { + return nil, nil, err + } + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 100, stakingScript, btcNet) + if err != nil { + return nil, nil, err + } + slashingTx, err := bbn.NewBTCSlashingTxFromMsgTx(slashingMsgTx) + if err != nil { + return nil, nil, err + } + + return stakingTx, slashingTx, nil +} diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index cdf015824..45c2af94a 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -5,6 +5,7 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg" tmdb "github.com/cometbft/cometbft-db" "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -18,7 +19,7 @@ import ( "github.com/stretchr/testify/require" ) -func BTCStakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +func BTCStakingKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -35,8 +36,9 @@ func BTCStakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { cdc, storeKey, memStoreKey, - nil, - nil, + btclcKeeper, + btccKeeper, + &chaincfg.SimNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/types/btc_config.go b/types/btc_config.go index ad24a4014..b565740f4 100644 --- a/types/btc_config.go +++ b/types/btc_config.go @@ -11,9 +11,7 @@ import ( type SupportedBtcNetwork string type BtcConfig struct { - powLimit *big.Int - retargetAdjustmentFactor int64 - reduceMinDifficulty bool + btcNetParams *chaincfg.Params } const ( @@ -23,7 +21,7 @@ const ( BtcRegtest SupportedBtcNetwork = "regtest" ) -func getParams(opts servertypes.AppOptions) chaincfg.Params { +func getParams(opts servertypes.AppOptions) *chaincfg.Params { valueInterface := opts.Get("btc-config.network") if valueInterface == nil { @@ -37,49 +35,36 @@ func getParams(opts servertypes.AppOptions) chaincfg.Params { } if network == string(BtcMainnet) { - return chaincfg.MainNetParams + return &chaincfg.MainNetParams } else if network == string(BtcTestnet) { - return chaincfg.TestNet3Params + return &chaincfg.TestNet3Params } else if network == string(BtcSimnet) { - return chaincfg.SimNetParams + return &chaincfg.SimNetParams } else if network == string(BtcRegtest) { - return chaincfg.RegressionNetParams + return &chaincfg.RegressionNetParams } else { panic("Bitcoin network should be one of [mainet, testnet, simnet, regtest]") } } -func parsePowLimit(opts servertypes.AppOptions) *big.Int { - return getParams(opts).PowLimit -} - -func parseRetargetAdjustmentFactor(opts servertypes.AppOptions) int64 { - return getParams(opts).RetargetAdjustmentFactor -} - -func parseReduceMinDifficulty(opts servertypes.AppOptions) bool { - return getParams(opts).ReduceMinDifficulty -} - func ParseBtcOptionsFromConfig(opts servertypes.AppOptions) BtcConfig { - powLimit := parsePowLimit(opts) - retargetAdjustmentFactor := parseRetargetAdjustmentFactor(opts) - reduceMinDifficulty := parseReduceMinDifficulty(opts) return BtcConfig{ - powLimit: powLimit, - retargetAdjustmentFactor: retargetAdjustmentFactor, - reduceMinDifficulty: reduceMinDifficulty, + btcNetParams: getParams(opts), } } +func (c *BtcConfig) NetParams() *chaincfg.Params { + return c.btcNetParams +} + func (c *BtcConfig) PowLimit() big.Int { - return *c.powLimit + return *c.btcNetParams.PowLimit } func (c *BtcConfig) RetargetAdjustmentFactor() int64 { - return c.retargetAdjustmentFactor + return c.btcNetParams.RetargetAdjustmentFactor } func (c *BtcConfig) ReduceMinDifficulty() bool { - return c.reduceMinDifficulty + return c.btcNetParams.ReduceMinDifficulty } diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index e8b26bb12..672e60ea2 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "errors" "github.com/btcsuite/btcd/btcec/v2" @@ -11,15 +12,16 @@ type BIP340PubKey []byte const BIP340PubKeyLen = schnorr.PubKeyBytesLen -func NewBIP340PubKey(data []byte) (BIP340PubKey, error) { +func NewBIP340PubKey(data []byte) (*BIP340PubKey, error) { var pk BIP340PubKey err := pk.Unmarshal(data) - return pk, err + return &pk, err } -func NewBIP340PubKeyFromBTCPK(btcPK *btcec.PublicKey) BIP340PubKey { +func NewBIP340PubKeyFromBTCPK(btcPK *btcec.PublicKey) *BIP340PubKey { pkBytes := schnorr.SerializePubKey(btcPK) - return BIP340PubKey(pkBytes) + pk := BIP340PubKey(pkBytes) + return &pk } func (pk BIP340PubKey) ToBTCPK() (*btcec.PublicKey, error) { @@ -64,3 +66,7 @@ func (pk *BIP340PubKey) Unmarshal(data []byte) error { *pk = data return nil } + +func (pk *BIP340PubKey) Equals(pk2 *BIP340PubKey) bool { + return bytes.Equal(*pk, *pk2) +} diff --git a/types/btc_schnorr_pk_test.go b/types/btc_schnorr_pk_test.go index 49d905373..6daf60629 100644 --- a/types/btc_schnorr_pk_test.go +++ b/types/btc_schnorr_pk_test.go @@ -35,6 +35,6 @@ func FuzzBIP340PubKey(f *testing.F) { require.NoError(t, err) err = pk2.Unmarshal(pkBytes) require.NoError(t, err) - require.Equal(t, pk, pk2) + require.Equal(t, *pk, pk2) }) } diff --git a/types/btc_slashing_tx.go b/types/btc_slashing_tx.go index bb2a84a15..2e4774bd6 100644 --- a/types/btc_slashing_tx.go +++ b/types/btc_slashing_tx.go @@ -1,9 +1,29 @@ package types +import ( + "bytes" + + "github.com/babylonchain/babylon/btcstaking" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" +) + type BTCSlashingTx []byte // TODO: constructors, verification rules, and util functions +func NewBTCSlashingTxFromMsgTx(msgTx *wire.MsgTx) (*BTCSlashingTx, error) { + var buf bytes.Buffer + err := msgTx.Serialize(&buf) + if err != nil { + return nil, err + } + + tx := BTCSlashingTx(buf.Bytes()) + return &tx, nil +} + func (tx BTCSlashingTx) Marshal() ([]byte, error) { return tx, nil } @@ -35,3 +55,24 @@ func (tx *BTCSlashingTx) Unmarshal(data []byte) error { func (tx *BTCSlashingTx) Size() int { return len(tx.MustMarshal()) } + +func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { + var msgTx wire.MsgTx + rbuf := bytes.NewReader(*tx) + if err := msgTx.Deserialize(rbuf); err != nil { + return nil, err + } + return &msgTx, nil +} + +func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) error { + msgTx, err := tx.ToMsgTx() + if err != nil { + return err + } + decodedAddr, err := btcutil.DecodeAddress(slashingAddress, net) + if err != nil { + return err + } + return btcstaking.CheckSlashingTx(msgTx, decodedAddr) +} diff --git a/types/btc_staking_tx.go b/types/btc_staking_tx.go deleted file mode 100644 index c186832c2..000000000 --- a/types/btc_staking_tx.go +++ /dev/null @@ -1,37 +0,0 @@ -package types - -type BTCStakingTx []byte - -// TODO: constructors, verification rules, and util functions - -func (tx BTCStakingTx) Marshal() ([]byte, error) { - return tx, nil -} - -func (tx BTCStakingTx) MustMarshal() []byte { - txBytes, err := tx.Marshal() - if err != nil { - panic(err) - } - return txBytes -} - -func (tx BTCStakingTx) MarshalTo(data []byte) (int, error) { - bz, err := tx.Marshal() - if err != nil { - return 0, err - } - copy(data, bz) - return len(data), nil -} - -func (tx *BTCStakingTx) Unmarshal(data []byte) error { - // TODO: verifications - - *tx = data - return nil -} - -func (tx *BTCStakingTx) Size() int { - return len(tx.MustMarshal()) -} diff --git a/x/btccheckpoint/types/btcutils.go b/x/btccheckpoint/types/btcutils.go index ce34fda7e..6d761e567 100644 --- a/x/btccheckpoint/types/btcutils.go +++ b/x/btccheckpoint/types/btcutils.go @@ -8,9 +8,9 @@ import ( "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/btcutil" ) const ( diff --git a/x/btccheckpoint/types/types.go b/x/btccheckpoint/types/types.go index bec28894e..d5b9b5c93 100644 --- a/x/btccheckpoint/types/types.go +++ b/x/btccheckpoint/types/types.go @@ -3,6 +3,7 @@ package types import ( "encoding/hex" "fmt" + "math/big" "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/types" @@ -174,6 +175,34 @@ func (ti *TransactionInfo) ValidateBasic() error { return nil } +// VerifyInclusion verifies the tx is included in a given BTC header that satisfies the given PoW limit +// TODO: given that TransactionInfo is now used in btcstaking module as well, +// probably we need to move it out from btccheckpoint +func (ti *TransactionInfo) VerifyInclusion(btcHeader *types.BTCHeaderBytes, powLimit *big.Int) error { + if err := ti.ValidateBasic(); err != nil { + return err + } + if !ti.Key.Hash.Eq(btcHeader.Hash()) { + return fmt.Errorf("the given btcHeader is different from that in TransactionInfo") + } + + tx, err := ParseTransaction(ti.Transaction) + if err != nil { + return err + } + + header := btcHeader.ToBlockHeader() + if err := types.ValidateBTCHeader(header, powLimit); err != nil { + return err + } + + if !verify(tx, &header.MerkleRoot, ti.Proof, ti.Key.Index) { + return fmt.Errorf("header failed validation due to failed proof") + } + + return nil +} + func NewSpvProofFromHexBytes(c codec.Codec, proof string) (*BTCSpvProof, error) { bytes, err := hex.DecodeString(proof) diff --git a/x/btcstaking/genesis_test.go b/x/btcstaking/genesis_test.go index 72171ca49..9b2dd0a14 100644 --- a/x/btcstaking/genesis_test.go +++ b/x/btcstaking/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx := keepertest.BTCStakingKeeper(t) + k, ctx := keepertest.BTCStakingKeeper(t, nil, nil) btcstaking.InitGenesis(ctx, *k, genesisState) got := btcstaking.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index ba837da87..a46b54f70 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -8,13 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// AddBTCDelegation verifies then adds the given BTC delegation to KVStore -func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { - // TODO: verify btcDel - k.setBTCDelegation(ctx, btcDel) - return nil -} - // setBTCDelegation adds the given BTC delegation to KVStore func (k Keeper) setBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { store := k.btcDelegationStore(ctx, btcDel.ValBtcPk.MustMarshal()) diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index f1ca391db..1b43070d4 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -3,12 +3,14 @@ package keeper import ( "fmt" + bbn "github.com/babylonchain/babylon/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/babylonchain/babylon/x/btcstaking/types" ) type ( @@ -17,8 +19,10 @@ type ( storeKey storetypes.StoreKey memKey storetypes.StoreKey - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper + btclcKeeper types.BTCLightClientKeeper + btccKeeper types.BtcCheckpointKeeper + + btcNet *chaincfg.Params // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -30,8 +34,10 @@ func NewKeeper( storeKey, memKey storetypes.StoreKey, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, + btclcKeeper types.BTCLightClientKeeper, + btccKeeper types.BtcCheckpointKeeper, + + btcNet *chaincfg.Params, authority string, ) Keeper { return Keeper{ @@ -39,12 +45,38 @@ func NewKeeper( storeKey: storeKey, memKey: memKey, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, - authority: authority, + btclcKeeper: btclcKeeper, + btccKeeper: btccKeeper, + + btcNet: btcNet, + authority: authority, } } func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } + +func (k Keeper) getHeaderAndDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (*btclctypes.BTCHeaderInfo, uint64, error) { + if headerHash == nil { + return nil, 0, fmt.Errorf("headerHash is nil") + } + // get the header + header := k.btclcKeeper.GetHeaderByHash(ctx, headerHash) + if header == nil { + return nil, 0, fmt.Errorf("header that includes the staking tx is not found") + } + // get the tip + tip := k.btclcKeeper.GetTipInfo(ctx) + // If the height of the requested header is larger than the tip, return -1 + if tip.Height < header.Height { + return nil, 0, fmt.Errorf("header is higher than the tip in BTC light client") + } + // The depth is the number of blocks that have been build on top of the header + // For example: + // Tip: 0-deep + // Tip height is 10, headerInfo height is 5: 5-deep etc. + headerDepth := tip.Height - header.Height + + return header, headerDepth, nil +} diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 359a0ee59..4fb54b43c 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -2,9 +2,14 @@ package keeper import ( "context" + "encoding/hex" + "fmt" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcutil" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -60,5 +65,84 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // CreateBTCDelegation creates a BTC delegation func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { - panic("TODO: implement me!") + // stateless checks + if err := req.ValidateBasic(); err != nil { + return nil, err + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + // extract staking script from staking tx + stakingOutputInfo, err := req.StakingTx.GetStakingOutputInfo(ms.btcNet) + if err != nil { + return nil, err + } + delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.StakerKey) + valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) + juryPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.JuryKey) + + // ensure the staking tx is not duplicated + // NOTE: it's okay that the same staker has multiple delegations + // the situation that we need to prevent here is that every staking tx + // can only correspond to a single BTC delegation + btcDel, err := ms.GetBTCDelegation(ctx, *valBTCPK, *delBTCPK) + if err == nil && btcDel.StakingTx.Equals(req.StakingTx) { + return nil, fmt.Errorf("the BTC staking tx is already used") + } + + // ensure staking tx is using correct jury PK + paramJuryPK := ms.GetParams(ctx).JuryPk + if !juryPK.Equals(paramJuryPK) { + return nil, fmt.Errorf("staking tx specifies a wrong jury PK %s (expected: %s)", hex.EncodeToString(*juryPK), hex.EncodeToString(*paramJuryPK)) + } + + // ensure staking tx is k-deep + stakingTxHeader, stakingTxDepth, err := ms.getHeaderAndDepth(ctx, req.StakingTxInfo.Key.Hash) + if err != nil { + return nil, err + } + kValue := ms.btccKeeper.GetParams(ctx).BtcConfirmationDepth + if stakingTxDepth < kValue { + return nil, fmt.Errorf("staking tx is not k-deep yet. k=%d, depth=%d", kValue, stakingTxDepth) + } + // verify staking tx info, i.e., inclusion proof + if err := req.StakingTxInfo.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { + return nil, err + } + + // check slashing tx and its consistency with staking tx + slashingMsgTx, err := req.SlashingTx.ToMsgTx() + if err != nil { + return nil, err + } + slashingAddr, err := btcutil.DecodeAddress(ms.GetParams(ctx).SlashingAddress, ms.btcNet) + if err != nil { + return nil, err + } + stakingMsgTx, err := req.StakingTx.ToMsgTx() + if err != nil { + return nil, err + } + // TODO: parameterise slash min fee + if _, err := btcstaking.CheckTransactions(slashingMsgTx, stakingMsgTx, 1, slashingAddr, req.StakingTx.StakingScript, ms.btcNet); err != nil { + return nil, err + } + + // all good, construct BTCDelegation and insert BTC delegation + newBTCDel := &types.BTCDelegation{ + BabylonPk: req.BabylonPk, + BtcPk: delBTCPK, + Pop: req.Pop, + ValBtcPk: valBTCPK, + StartHeight: stakingTxHeader.Height, + EndHeight: stakingTxHeader.Height + uint64(stakingOutputInfo.StakingScriptData.StakingTime), + TotalSat: uint64(stakingOutputInfo.StakingAmount), + StakingTx: req.StakingTx, + SlashingTx: req.SlashingTx, + DelegatorSig: req.DelegatorSig, + JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury + } + ms.setBTCDelegation(ctx, newBTCDel) + + return &types.MsgCreateBTCDelegationResponse{}, nil } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index c9425d029..78cc15328 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -1,20 +1,29 @@ package keeper_test import ( + "bytes" "context" "math/rand" "testing" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { - k, ctx := keepertest.BTCStakingKeeper(t) + k, ctx := keepertest.BTCStakingKeeper(t, nil, nil) return k, keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) } @@ -67,3 +76,105 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { } }) } + +func FuzzMsgCreateBTCDelegation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + ms := keeper.NewMsgServerImpl(*bsKeeper) + goCtx := sdk.WrapSDKContext(ctx) + + // set jury PK to params + _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + err = bsKeeper.SetParams(ctx, types.Params{ + JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + SlashingAddress: slashingAddr, + }) + require.NoError(t, err) + + // generate and insert new BTC validator + validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcVal, err := datagen.GenRandomBTCValidatorWithBTCPK(r, validatorSK) + require.NoError(t, err) + msgNewVal := types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, + } + _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) + require.NoError(t, err) + + // key pairs, staking tx and slashing tx + delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + stakingTimeBlocks := uint16(5) + stakingValue := int64(2 * 10e8) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + require.NoError(t, err) + // get msgTx + var stakingMsgTx wire.MsgTx + err = stakingMsgTx.Deserialize(bytes.NewReader(stakingTx.Tx)) + require.NoError(t, err) + + // random signer + signer := datagen.GenRandomAccount().Address + // random Babylon SK + delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + require.NoError(t, err) + // PoP + pop, err := types.NewPoP(delBabylonSK, delSK) + require.NoError(t, err) + // generate staking tx info + prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, &stakingMsgTx) + btcHeader := btcHeaderWithProof.HeaderBytes + txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) + // mock for testing k-deep stuff + btccKeeper.EXPECT().GetPowLimit().Return(chaincfg.SimNetParams.PowLimit).AnyTimes() + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) + + // TODO: generate proper delegator sig + randHash := datagen.GenRandomBtcdHash(r) + delegatorSchnorrSig, err := schnorr.Sign(delSK, randHash[:]) + require.NoError(t, err) + delegatorSig := bbn.NewBIP340SignatureFromBTCSig(delegatorSchnorrSig) + + msg := &types.MsgCreateBTCDelegation{ + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + Pop: pop, + StakingTx: stakingTx, + StakingTxInfo: txInfo, + SlashingTx: slashingTx, + DelegatorSig: &delegatorSig, + } + _, err = ms.CreateBTCDelegation(goCtx, msg) + require.NoError(t, err) + + // check existence + actualDel, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) + require.NoError(t, err) + require.Equal(t, msg.BabylonPk, actualDel.BabylonPk) + require.Equal(t, msg.Pop, actualDel.Pop) + require.Equal(t, msg.StakingTx, actualDel.StakingTx) + require.Equal(t, msg.SlashingTx, actualDel.SlashingTx) + // ensure the BTC delegation in DB is correctly formatted + err = actualDel.ValidateBasic() + require.NoError(t, err) + }) +} diff --git a/x/btcstaking/keeper/params_test.go b/x/btcstaking/keeper/params_test.go index ce5b3e2cf..adf55969b 100644 --- a/x/btcstaking/keeper/params_test.go +++ b/x/btcstaking/keeper/params_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.BTCStakingKeeper(t) + k, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) params := types.DefaultParams() err := k.SetParams(ctx, params) diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go index 998474150..ed94316a2 100644 --- a/x/btcstaking/keeper/query_params_test.go +++ b/x/btcstaking/keeper/query_params_test.go @@ -10,7 +10,7 @@ import ( ) func TestParamsQuery(t *testing.T) { - keeper, ctx := testkeeper.BTCStakingKeeper(t) + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 345668714..e4f51cad6 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -1,6 +1,14 @@ package types -import "fmt" +import ( + "bytes" + "fmt" + + "github.com/babylonchain/babylon/btcstaking" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" +) func (v *BTCValidator) ValidateBasic() error { // ensure fields are non-empty and well-formatted @@ -44,21 +52,20 @@ func (d *BTCDelegation) ValidateBasic() error { if d.StakingTx == nil { return fmt.Errorf("empty StakingTx") } - if d.StakingTxSig == nil { - return fmt.Errorf("empty StakingTxSig") - } - if d.StakingTx == nil { - return fmt.Errorf("empty StakingTxInfo") - } if d.SlashingTx == nil { return fmt.Errorf("empty SlashingTx") } - if d.SlashingTxSig == nil { - return fmt.Errorf("empty SlashingTxSig") + if d.DelegatorSig == nil { + return fmt.Errorf("empty DelegatorSig") } // TODO: validation rules + // ensure staking tx is correctly formatted + if err := d.StakingTx.ValidateBasic(); err != nil { + return err + } + // verify PoP if err := d.Pop.ValidateBasic(); err != nil { return err @@ -83,3 +90,77 @@ func (p *ProofOfPossession) ValidateBasic() error { return nil } + +func (tx *StakingTx) Equals(tx2 *StakingTx) bool { + return bytes.Equal(tx.Tx, tx2.Tx) && bytes.Equal(tx.StakingScript, tx2.StakingScript) +} + +func (tx *StakingTx) ValidateBasic() error { + // unmarshal tx bytes to MsgTx + var msgTx wire.MsgTx + rbuf := bytes.NewReader(tx.Tx) + if err := msgTx.Deserialize(rbuf); err != nil { + return err + } + + // parse staking script + if _, err := btcstaking.ParseStakingTransactionScript(tx.StakingScript); err != nil { + return err + } + + return nil +} + +func (tx *StakingTx) ToMsgTx() (*wire.MsgTx, error) { + var msgTx wire.MsgTx + rbuf := bytes.NewReader(tx.Tx) + if err := msgTx.Deserialize(rbuf); err != nil { + return nil, err + } + return &msgTx, nil +} + +func (tx *StakingTx) GetStakingScriptData() (*btcstaking.StakingScriptData, error) { + return btcstaking.ParseStakingTransactionScript(tx.StakingScript) +} + +func (tx *StakingTx) GetStakingOutputInfo(net *chaincfg.Params) (*btcstaking.StakingOutputInfo, error) { + var ( + scriptData *btcstaking.StakingScriptData + outValue int64 + err error + ) + + // unmarshal tx bytes to MsgTx + var msgTx wire.MsgTx + rbuf := bytes.NewReader(tx.Tx) + if err := msgTx.Deserialize(rbuf); err != nil { + return nil, err + } + + // parse staking script + scriptData, err = btcstaking.ParseStakingTransactionScript(tx.StakingScript) + if err != nil { + return nil, err + } + expectedPkScript, err := btcstaking.BuildUnspendableTaprootPkScript(tx.StakingScript, net) + if err != nil { + return nil, err + } + + // find the output that corresponds to the staking script + for _, txOut := range msgTx.TxOut { + if bytes.Equal(expectedPkScript, txOut.PkScript) { + outValue = txOut.Value + } + } + if outValue == 0 { + // not found + return nil, fmt.Errorf("the tx contains no StakingTransactionScript") + } + + return &btcstaking.StakingOutputInfo{ + StakingScriptData: scriptData, + StakingAmount: btcutil.Amount(outValue), + }, nil +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index d0fecdc08..67a35a50e 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -106,18 +106,19 @@ type BTCDelegation struct { // quantified in satoshi TotalSat uint64 `protobuf:"varint,7,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` // staking_tx is the staking tx - // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - StakingTx *github_com_babylonchain_babylon_types.BTCStakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCStakingTx" json:"staking_tx,omitempty"` - // staking_tx_sig is the signature of the staking tx - // signed by SK corresponding to btc_pk - StakingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,9,opt,name=staking_tx_sig,json=stakingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"staking_tx_sig,omitempty"` + StakingTx *StakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` - // slashing_tx_sig is the signature of the slashing tx - // signed by SK corresponding to btc_pk - SlashingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=slashing_tx_sig,json=slashingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_tx_sig,omitempty"` + SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` + // delegator_sig is the signature on the slashing tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the staking tx output. + DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` + // jury_sig is the signature signature on the slashing tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It will be a part of the witness for the staking tx output. + JurySig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,12,opt,name=jury_sig,json=jurySig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_sig,omitempty"` } func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } @@ -188,6 +189,13 @@ func (m *BTCDelegation) GetTotalSat() uint64 { return 0 } +func (m *BTCDelegation) GetStakingTx() *StakingTx { + if m != nil { + return m.StakingTx + } + return nil +} + // ProofOfPossession is the proof of possession that a Babylon secp256k1 // secret key and a Bitcoin secp256k1 secret key are held by the same // person @@ -239,10 +247,66 @@ func (m *ProofOfPossession) GetBabylonSig() []byte { return nil } +// StakingTx is the tx for delegating BTC +type StakingTx struct { + // tx is the staking tx in bytes + Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` + // staking_script is the script for the staking tx's output + StakingScript []byte `protobuf:"bytes,2,opt,name=staking_script,json=stakingScript,proto3" json:"staking_script,omitempty"` +} + +func (m *StakingTx) Reset() { *m = StakingTx{} } +func (m *StakingTx) String() string { return proto.CompactTextString(m) } +func (*StakingTx) ProtoMessage() {} +func (*StakingTx) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{3} +} +func (m *StakingTx) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StakingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StakingTx.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *StakingTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_StakingTx.Merge(m, src) +} +func (m *StakingTx) XXX_Size() int { + return m.Size() +} +func (m *StakingTx) XXX_DiscardUnknown() { + xxx_messageInfo_StakingTx.DiscardUnknown(m) +} + +var xxx_messageInfo_StakingTx proto.InternalMessageInfo + +func (m *StakingTx) GetTx() []byte { + if m != nil { + return m.Tx + } + return nil +} + +func (m *StakingTx) GetStakingScript() []byte { + if m != nil { + return m.StakingScript + } + return nil +} + func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") + proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") } func init() { @@ -250,41 +314,43 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 540 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0xcf, 0x8f, 0xd2, 0x40, - 0x14, 0xa6, 0xfb, 0x83, 0x85, 0x07, 0xab, 0xb1, 0xd1, 0xa4, 0x59, 0x63, 0x41, 0x0e, 0x86, 0x53, - 0x2b, 0xe0, 0xae, 0xd1, 0x83, 0x87, 0xe2, 0x41, 0xa3, 0xc6, 0xa6, 0xad, 0xc6, 0x78, 0x69, 0xa6, - 0x65, 0x76, 0x98, 0xb4, 0xdb, 0x69, 0x3a, 0x03, 0x81, 0xbb, 0x07, 0x8f, 0xfe, 0x59, 0x1e, 0xf7, - 0xa8, 0x1c, 0x88, 0x81, 0x7f, 0xc4, 0x74, 0x28, 0x2c, 0x89, 0x26, 0xba, 0xe1, 0xb6, 0xb7, 0xd7, - 0xf7, 0xbe, 0xef, 0x7b, 0xdf, 0x6b, 0xde, 0x1b, 0x78, 0x14, 0xa0, 0x60, 0x1a, 0xb3, 0xc4, 0x0c, - 0x44, 0xc8, 0x05, 0x8a, 0x68, 0x42, 0xcc, 0x71, 0x67, 0xeb, 0xcb, 0x48, 0x33, 0x26, 0x98, 0x7a, - 0xaf, 0xc0, 0x19, 0x5b, 0x95, 0x71, 0xe7, 0xe4, 0x2e, 0x61, 0x84, 0x49, 0x84, 0x99, 0x47, 0x2b, - 0xf0, 0x49, 0x2b, 0x64, 0xfc, 0x82, 0x71, 0x33, 0xcc, 0xa6, 0xa9, 0x60, 0x26, 0xc7, 0x61, 0xda, - 0x3d, 0x3d, 0x8b, 0x3a, 0x66, 0x84, 0xa7, 0x7c, 0x85, 0x69, 0xfd, 0x54, 0xa0, 0x6e, 0x79, 0xfd, - 0x8f, 0x28, 0xa6, 0x03, 0x24, 0x58, 0xa6, 0xbe, 0x00, 0x28, 0x7a, 0xf8, 0x69, 0xa4, 0x29, 0x4d, - 0xa5, 0x5d, 0xeb, 0x36, 0x8c, 0x95, 0x92, 0xb1, 0x52, 0x32, 0x36, 0x4a, 0x86, 0x3d, 0x0a, 0xde, - 0xe0, 0xa9, 0x53, 0x2d, 0x28, 0x76, 0xa4, 0xbe, 0x83, 0x72, 0x20, 0xc2, 0x9c, 0xbb, 0xd7, 0x54, - 0xda, 0x75, 0xeb, 0x6c, 0x36, 0x6f, 0x74, 0x09, 0x15, 0xc3, 0x51, 0x60, 0x84, 0xec, 0xc2, 0x2c, - 0x90, 0xe1, 0x10, 0xd1, 0x64, 0xfd, 0x61, 0x8a, 0x69, 0x8a, 0xb9, 0x61, 0xbd, 0xb6, 0x7b, 0x4f, - 0x1e, 0x17, 0x92, 0x87, 0x81, 0x08, 0xed, 0x48, 0x7d, 0x0e, 0xfb, 0x29, 0x4b, 0xb5, 0x7d, 0xe9, - 0xa3, 0x6d, 0xfc, 0x75, 0x7c, 0xc3, 0xce, 0x18, 0x3b, 0x7f, 0x7f, 0x6e, 0x33, 0xce, 0x31, 0xe7, - 0x94, 0x25, 0x4e, 0x4e, 0x6a, 0x7d, 0x29, 0xc3, 0xb1, 0xe5, 0xf5, 0x5f, 0xe2, 0x18, 0x13, 0x24, - 0x28, 0x4b, 0x6e, 0xd0, 0x70, 0xaa, 0x07, 0x30, 0x46, 0xb1, 0x5f, 0xd8, 0x39, 0xd8, 0xc9, 0x4e, - 0x65, 0x8c, 0x62, 0x4b, 0x3a, 0x7a, 0x08, 0x75, 0x2e, 0x50, 0x26, 0xfc, 0x21, 0xa6, 0x64, 0x28, - 0xb4, 0xc3, 0xa6, 0xd2, 0x3e, 0x70, 0x6a, 0x32, 0xf7, 0x4a, 0xa6, 0xd4, 0x07, 0x00, 0x38, 0x19, - 0xac, 0x01, 0x65, 0x09, 0xa8, 0xe2, 0x64, 0x50, 0x94, 0xef, 0x43, 0x55, 0x30, 0x81, 0x62, 0x9f, - 0x23, 0xa1, 0x1d, 0xc9, 0x6a, 0x45, 0x26, 0x5c, 0x24, 0xd4, 0x0f, 0x00, 0xc5, 0x64, 0xbe, 0x98, - 0x68, 0x95, 0x6b, 0x9b, 0xf6, 0xfa, 0xee, 0x8a, 0xee, 0x4d, 0x9c, 0x2a, 0x5f, 0x87, 0xaa, 0x0f, - 0xb7, 0xae, 0x64, 0x7d, 0x4e, 0x89, 0x56, 0x95, 0xd2, 0xcf, 0x66, 0xf3, 0xc6, 0xe9, 0x75, 0xfe, - 0x87, 0x4b, 0x49, 0x82, 0xc4, 0x28, 0xc3, 0x4e, 0x7d, 0xa3, 0xee, 0x52, 0xa2, 0x7e, 0x82, 0x1a, - 0x8f, 0x11, 0x1f, 0x16, 0xc6, 0x41, 0xaa, 0x3f, 0x9d, 0xcd, 0x1b, 0xbd, 0xff, 0x37, 0x5e, 0xf0, - 0xbd, 0x89, 0x03, 0x7c, 0x13, 0xab, 0x08, 0x6e, 0x6f, 0x29, 0x4b, 0xef, 0xb5, 0x5d, 0xbd, 0x1f, - 0x5f, 0xe9, 0xbb, 0x94, 0xb4, 0xbe, 0x2a, 0x70, 0xe7, 0x8f, 0x25, 0x52, 0x1b, 0x50, 0x5b, 0x9f, - 0x42, 0xde, 0x34, 0xbf, 0x85, 0xba, 0xb3, 0xbe, 0x8e, 0x7c, 0x66, 0x07, 0x8e, 0xf2, 0xe5, 0xca, - 0x8b, 0x7b, 0xbb, 0x3a, 0xca, 0xaf, 0xc6, 0xa5, 0xc4, 0x7a, 0xfb, 0x7d, 0xa1, 0x2b, 0x97, 0x0b, - 0x5d, 0xf9, 0xb5, 0xd0, 0x95, 0x6f, 0x4b, 0xbd, 0x74, 0xb9, 0xd4, 0x4b, 0x3f, 0x96, 0x7a, 0xe9, - 0xf3, 0x3f, 0x37, 0x60, 0xb2, 0xfd, 0x34, 0xca, 0x2e, 0x41, 0x59, 0x3e, 0x61, 0xbd, 0xdf, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xd1, 0xe1, 0x97, 0xe1, 0x3d, 0x05, 0x00, 0x00, + // 576 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0x4d, 0x6b, 0xd4, 0x40, + 0x18, 0x6e, 0xb6, 0x5f, 0xbb, 0xef, 0xa6, 0x05, 0x07, 0x85, 0x50, 0x31, 0xbb, 0x2e, 0x28, 0x7b, + 0x4a, 0x6c, 0x6b, 0x2b, 0x7a, 0x50, 0x48, 0x3d, 0x28, 0x2a, 0x86, 0x24, 0x88, 0x78, 0x30, 0x4c, + 0xb2, 0x69, 0x76, 0x4c, 0x9a, 0x09, 0x99, 0xd9, 0x25, 0xf9, 0x07, 0x1e, 0xfd, 0x59, 0x1e, 0x7b, + 0xd4, 0x1e, 0x4a, 0x69, 0xff, 0x88, 0x64, 0x32, 0x59, 0x0a, 0x2a, 0x2a, 0x7b, 0xf3, 0xf6, 0xce, + 0xfb, 0xf1, 0xbc, 0xcf, 0x93, 0x79, 0x32, 0x70, 0x3f, 0xc0, 0x41, 0x95, 0xd2, 0xcc, 0x0c, 0x78, + 0xc8, 0x38, 0x4e, 0x48, 0x16, 0x9b, 0xf3, 0xdd, 0x6b, 0x27, 0x23, 0x2f, 0x28, 0xa7, 0xe8, 0x96, + 0xec, 0x33, 0xae, 0x55, 0xe6, 0xbb, 0x3b, 0x37, 0x63, 0x1a, 0x53, 0xd1, 0x61, 0xd6, 0x51, 0xd3, + 0xbc, 0x33, 0x0a, 0x29, 0x3b, 0xa1, 0xcc, 0x0c, 0x8b, 0x2a, 0xe7, 0xd4, 0x64, 0x51, 0x98, 0xef, + 0x1d, 0x1c, 0x26, 0xbb, 0x66, 0x12, 0x55, 0xac, 0xe9, 0x19, 0x7d, 0x57, 0x40, 0xb5, 0xbc, 0xa3, + 0x77, 0x38, 0x25, 0x13, 0xcc, 0x69, 0x81, 0x9e, 0x02, 0xc8, 0x1d, 0x7e, 0x9e, 0x68, 0xca, 0x50, + 0x19, 0xf7, 0xf7, 0x06, 0x46, 0x83, 0x64, 0x34, 0x48, 0xc6, 0x02, 0xc9, 0xb0, 0x67, 0xc1, 0xab, + 0xa8, 0x72, 0x7a, 0x72, 0xc4, 0x4e, 0xd0, 0x1b, 0xd8, 0x08, 0x78, 0x58, 0xcf, 0x76, 0x86, 0xca, + 0x58, 0xb5, 0x0e, 0xcf, 0xce, 0x07, 0x7b, 0x31, 0xe1, 0xd3, 0x59, 0x60, 0x84, 0xf4, 0xc4, 0x94, + 0x9d, 0xe1, 0x14, 0x93, 0xac, 0x3d, 0x98, 0xbc, 0xca, 0x23, 0x66, 0x58, 0x2f, 0xed, 0xfd, 0x87, + 0x0f, 0x24, 0xe4, 0x7a, 0xc0, 0x43, 0x3b, 0x41, 0x4f, 0x60, 0x35, 0xa7, 0xb9, 0xb6, 0x2a, 0x78, + 0x8c, 0x8d, 0x5f, 0xca, 0x37, 0xec, 0x82, 0xd2, 0xe3, 0xb7, 0xc7, 0x36, 0x65, 0x2c, 0x62, 0x8c, + 0xd0, 0xcc, 0xa9, 0x87, 0x46, 0x17, 0xeb, 0xb0, 0x65, 0x79, 0x47, 0xcf, 0xa3, 0x34, 0x8a, 0x31, + 0x27, 0x34, 0xfb, 0x8f, 0xc4, 0x21, 0x0f, 0x60, 0x8e, 0x53, 0x5f, 0xd2, 0x59, 0x5b, 0x8a, 0x4e, + 0x77, 0x8e, 0x53, 0x4b, 0x30, 0xba, 0x0b, 0x2a, 0xe3, 0xb8, 0xe0, 0xfe, 0x34, 0x22, 0xf1, 0x94, + 0x6b, 0xeb, 0x43, 0x65, 0xbc, 0xe6, 0xf4, 0x45, 0xee, 0x85, 0x48, 0xa1, 0x3b, 0x00, 0x51, 0x36, + 0x69, 0x1b, 0x36, 0x44, 0x43, 0x2f, 0xca, 0x26, 0xb2, 0x7c, 0x1b, 0x7a, 0x9c, 0x72, 0x9c, 0xfa, + 0x0c, 0x73, 0x6d, 0x53, 0x54, 0xbb, 0x22, 0xe1, 0x62, 0x8e, 0x9e, 0x01, 0x48, 0x65, 0x3e, 0x2f, + 0xb5, 0xae, 0xd0, 0x3d, 0xfc, 0x8d, 0x6e, 0xb7, 0x09, 0xbd, 0xd2, 0xe9, 0xb1, 0x36, 0x44, 0xef, + 0xa1, 0xcf, 0x52, 0xcc, 0xa6, 0x12, 0xa1, 0x27, 0x64, 0x3f, 0x3a, 0x3b, 0x1f, 0xec, 0xff, 0xa5, + 0x6c, 0xef, 0xc8, 0x95, 0xf3, 0x5e, 0xe9, 0x00, 0x5b, 0xc4, 0xe8, 0x23, 0x6c, 0x4d, 0x1a, 0xa3, + 0xd0, 0xc2, 0x67, 0x24, 0xd6, 0x40, 0x60, 0x3f, 0x3e, 0x3b, 0x1f, 0x1c, 0xfc, 0xcb, 0x27, 0x75, + 0x49, 0x9c, 0x61, 0x3e, 0x2b, 0x22, 0x47, 0x5d, 0xe0, 0xb9, 0x24, 0x46, 0x1e, 0x74, 0x3f, 0xcd, + 0x8a, 0x4a, 0x40, 0xab, 0xcb, 0x42, 0x6f, 0xd6, 0x50, 0x2e, 0x89, 0x47, 0x9f, 0x15, 0xb8, 0xf1, + 0x93, 0x41, 0xd0, 0x00, 0xfa, 0xad, 0xcd, 0xeb, 0x75, 0xb5, 0xcf, 0x55, 0xa7, 0x75, 0x7e, 0x4d, + 0xc6, 0x81, 0xcd, 0xda, 0x38, 0x75, 0xb1, 0xb3, 0x2c, 0x97, 0xfa, 0x8f, 0xa8, 0xa9, 0x58, 0xd0, + 0x5b, 0x5c, 0x19, 0xda, 0x86, 0x0e, 0x2f, 0xe5, 0xe2, 0x0e, 0x2f, 0xd1, 0x3d, 0xd8, 0x6e, 0x2f, + 0x9e, 0x85, 0x05, 0xc9, 0x79, 0xb3, 0xd7, 0xd9, 0x92, 0x59, 0x57, 0x24, 0xad, 0xd7, 0x5f, 0x2f, + 0x75, 0xe5, 0xf4, 0x52, 0x57, 0x2e, 0x2e, 0x75, 0xe5, 0xcb, 0x95, 0xbe, 0x72, 0x7a, 0xa5, 0xaf, + 0x7c, 0xbb, 0xd2, 0x57, 0x3e, 0xfc, 0xd1, 0xd6, 0xe5, 0xf5, 0xa7, 0x53, 0x30, 0x0d, 0x36, 0xc4, + 0x13, 0xb7, 0xff, 0x23, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x9a, 0x80, 0x0d, 0x5d, 0x05, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -366,23 +432,23 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.SlashingTxSig != nil { + if m.JurySig != nil { { - size := m.SlashingTxSig.Size() + size := m.JurySig.Size() i -= size - if _, err := m.SlashingTxSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.JurySig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x5a + dAtA[i] = 0x62 } - if m.SlashingTx != nil { + if m.DelegatorSig != nil { { - size := m.SlashingTx.Size() + size := m.DelegatorSig.Size() i -= size - if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.DelegatorSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -390,11 +456,11 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x52 } - if m.StakingTxSig != nil { + if m.SlashingTx != nil { { - size := m.StakingTxSig.Size() + size := m.SlashingTx.Size() i -= size - if _, err := m.StakingTxSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -404,11 +470,11 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { } if m.StakingTx != nil { { - size := m.StakingTx.Size() - i -= size - if _, err := m.StakingTx.MarshalTo(dAtA[i:]); err != nil { + size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { return 0, err } + i -= size i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- @@ -522,6 +588,43 @@ func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *StakingTx) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StakingTx) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StakingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.StakingScript) > 0 { + i -= len(m.StakingScript) + copy(dAtA[i:], m.StakingScript) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.StakingScript))) + i-- + dAtA[i] = 0x12 + } + if len(m.Tx) > 0 { + i -= len(m.Tx) + copy(dAtA[i:], m.Tx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { offset -= sovBtcstaking(v) base := offset @@ -589,16 +692,16 @@ func (m *BTCDelegation) Size() (n int) { l = m.StakingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.StakingTxSig != nil { - l = m.StakingTxSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) - } if m.SlashingTx != nil { l = m.SlashingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.SlashingTxSig != nil { - l = m.SlashingTxSig.Size() + if m.DelegatorSig != nil { + l = m.DelegatorSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.JurySig != nil { + l = m.JurySig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } return n @@ -621,6 +724,23 @@ func (m *ProofOfPossession) Size() (n int) { return n } +func (m *StakingTx) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Tx) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + l = len(m.StakingScript) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + func sovBtcstaking(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1016,7 +1136,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -1026,30 +1146,31 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCStakingTx - m.StakingTx = &v + if m.StakingTx == nil { + m.StakingTx = &StakingTx{} + } if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 9: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1076,15 +1197,15 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.StakingTxSig = &v - if err := m.StakingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1111,15 +1232,15 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCSlashingTx - m.SlashingTx = &v - if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorSig = &v + if err := m.DelegatorSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 11: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTxSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field JurySig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1147,8 +1268,8 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.SlashingTxSig = &v - if err := m.SlashingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.JurySig = &v + if err := m.JurySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1292,6 +1413,124 @@ func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { } return nil } +func (m *StakingTx) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StakingTx: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StakingTx: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) + if m.Tx == nil { + m.Tx = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingScript", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingScript = append(m.StakingScript[:0], dAtA[iNdEx:postIndex]...) + if m.StakingScript == nil { + m.StakingScript = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBtcstaking(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go new file mode 100644 index 000000000..5662586a1 --- /dev/null +++ b/x/btcstaking/types/btcstaking_test.go @@ -0,0 +1,45 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/btcsuite/btcd/chaincfg" + "github.com/stretchr/testify/require" +) + +func FuzzStakingTx(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + stakerSK, stakerPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + stakingTimeBlocks := uint16(5) + stakingValue := int64(2 * 10e8) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + require.NoError(t, err) + + err = stakingTx.ValidateBasic() + require.NoError(t, err) + + // extract staking script and staked value + stakingOutputInfo, err := stakingTx.GetStakingOutputInfo(&chaincfg.SimNetParams) + require.NoError(t, err) + // NOTE: given that PK derived from SK has 2 possibilities on a curve, we can only compare x value but not y value + require.Equal(t, stakingOutputInfo.StakingScriptData.StakerKey.SerializeCompressed()[1:], stakerPK.SerializeCompressed()[1:]) + require.Equal(t, stakingOutputInfo.StakingScriptData.ValidatorKey.SerializeCompressed()[1:], validatorPK.SerializeCompressed()[1:]) + require.Equal(t, stakingOutputInfo.StakingScriptData.JuryKey.SerializeCompressed()[1:], juryPK.SerializeCompressed()[1:]) + require.Equal(t, stakingOutputInfo.StakingScriptData.StakingTime, stakingTimeBlocks) + require.Equal(t, int64(stakingOutputInfo.StakingAmount), stakingValue) + }) +} diff --git a/x/btcstaking/types/expected_keepers.go b/x/btcstaking/types/expected_keepers.go index 6aa6e9778..3d8748b39 100644 --- a/x/btcstaking/types/expected_keepers.go +++ b/x/btcstaking/types/expected_keepers.go @@ -1,6 +1,11 @@ package types import ( + "math/big" + + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" ) @@ -16,3 +21,13 @@ type BankKeeper interface { SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins // Methods imported from bank should be defined here } + +type BTCLightClientKeeper interface { + GetTipInfo(ctx sdk.Context) *btclctypes.BTCHeaderInfo + GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo +} + +type BtcCheckpointKeeper interface { + GetPowLimit() *big.Int + GetParams(ctx sdk.Context) (p btcctypes.Params) +} diff --git a/x/btcstaking/types/mocked_keepers.go b/x/btcstaking/types/mocked_keepers.go new file mode 100644 index 000000000..7d3ec29f7 --- /dev/null +++ b/x/btcstaking/types/mocked_keepers.go @@ -0,0 +1,193 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/btcstaking/types/expected_keepers.go + +// Package types is a generated GoMock package. +package types + +import ( + big "math/big" + reflect "reflect" + + types "github.com/babylonchain/babylon/types" + types0 "github.com/babylonchain/babylon/x/btccheckpoint/types" + types1 "github.com/babylonchain/babylon/x/btclightclient/types" + types2 "github.com/cosmos/cosmos-sdk/types" + types3 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx types2.Context, addr types2.AccAddress) types3.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types3.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// SpendableCoins mocks base method. +func (m *MockBankKeeper) SpendableCoins(ctx types2.Context, addr types2.AccAddress) types2.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) + ret0, _ := ret[0].(types2.Coins) + return ret0 +} + +// SpendableCoins indicates an expected call of SpendableCoins. +func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) +} + +// MockBTCLightClientKeeper is a mock of BTCLightClientKeeper interface. +type MockBTCLightClientKeeper struct { + ctrl *gomock.Controller + recorder *MockBTCLightClientKeeperMockRecorder +} + +// MockBTCLightClientKeeperMockRecorder is the mock recorder for MockBTCLightClientKeeper. +type MockBTCLightClientKeeperMockRecorder struct { + mock *MockBTCLightClientKeeper +} + +// NewMockBTCLightClientKeeper creates a new mock instance. +func NewMockBTCLightClientKeeper(ctrl *gomock.Controller) *MockBTCLightClientKeeper { + mock := &MockBTCLightClientKeeper{ctrl: ctrl} + mock.recorder = &MockBTCLightClientKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBTCLightClientKeeper) EXPECT() *MockBTCLightClientKeeperMockRecorder { + return m.recorder +} + +// GetHeaderByHash mocks base method. +func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx types2.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeaderByHash", ctx, hash) + ret0, _ := ret[0].(*types1.BTCHeaderInfo) + return ret0 +} + +// GetHeaderByHash indicates an expected call of GetHeaderByHash. +func (mr *MockBTCLightClientKeeperMockRecorder) GetHeaderByHash(ctx, hash interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeaderByHash", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetHeaderByHash), ctx, hash) +} + +// GetTipInfo mocks base method. +func (m *MockBTCLightClientKeeper) GetTipInfo(ctx types2.Context) *types1.BTCHeaderInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTipInfo", ctx) + ret0, _ := ret[0].(*types1.BTCHeaderInfo) + return ret0 +} + +// GetTipInfo indicates an expected call of GetTipInfo. +func (mr *MockBTCLightClientKeeperMockRecorder) GetTipInfo(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTipInfo", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetTipInfo), ctx) +} + +// MockBtcCheckpointKeeper is a mock of BtcCheckpointKeeper interface. +type MockBtcCheckpointKeeper struct { + ctrl *gomock.Controller + recorder *MockBtcCheckpointKeeperMockRecorder +} + +// MockBtcCheckpointKeeperMockRecorder is the mock recorder for MockBtcCheckpointKeeper. +type MockBtcCheckpointKeeperMockRecorder struct { + mock *MockBtcCheckpointKeeper +} + +// NewMockBtcCheckpointKeeper creates a new mock instance. +func NewMockBtcCheckpointKeeper(ctrl *gomock.Controller) *MockBtcCheckpointKeeper { + mock := &MockBtcCheckpointKeeper{ctrl: ctrl} + mock.recorder = &MockBtcCheckpointKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBtcCheckpointKeeper) EXPECT() *MockBtcCheckpointKeeperMockRecorder { + return m.recorder +} + +// GetParams mocks base method. +func (m *MockBtcCheckpointKeeper) GetParams(ctx types2.Context) types0.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types0.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockBtcCheckpointKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockBtcCheckpointKeeper)(nil).GetParams), ctx) +} + +// GetPowLimit mocks base method. +func (m *MockBtcCheckpointKeeper) GetPowLimit() *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPowLimit") + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetPowLimit indicates an expected call of GetPowLimit. +func (mr *MockBtcCheckpointKeeperMockRecorder) GetPowLimit() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPowLimit", reflect.TypeOf((*MockBtcCheckpointKeeper)(nil).GetPowLimit)) +} diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index c1ac8f743..9da24f2b3 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -4,6 +4,7 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" + bbn "github.com/babylonchain/babylon/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -78,27 +79,38 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.StakingTx == nil { return fmt.Errorf("empty StakingTx") } - if m.StakingTxSig == nil { - return fmt.Errorf("empty StakingTxSig") - } if m.StakingTxInfo == nil { return fmt.Errorf("empty StakingTxInfo") } if m.SlashingTx == nil { return fmt.Errorf("empty SlashingTx") } - if m.SlashingTxSig == nil { - return fmt.Errorf("empty SlashingTxSig") + if m.DelegatorSig == nil { + return fmt.Errorf("empty DelegatorSig") } if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } - // TODO: verification rules + // staking tx should be correctly formatted + if err := m.StakingTx.ValidateBasic(); err != nil { + return err + } + // TODO: verify delegator_sig + + // verify PoP if err := m.Pop.ValidateBasic(); err != nil { return err } - // TODO: extract BTC PK and verify PoP + stakingScriptData, err := m.StakingTx.GetStakingScriptData() + if err != nil { + return err + } + btcPK := bbn.NewBIP340PubKeyFromBTCPK(stakingScriptData.StakerKey) + if err := m.Pop.Verify(m.BabylonPk, btcPK); err != nil { + return err + } + return nil } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 5bc7e4c80..21cb23aa4 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -30,8 +30,8 @@ type Params struct { // the PK follows encoding in BIP-340 spec on Bitcoin JuryPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=jury_pk,json=juryPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"jury_pk,omitempty"` // slashing address is the address that the slashed BTC goes to - // the address is on Bitcoin - SlashingAddress []byte `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` + // the address is in string on Bitcoin + SlashingAddress string `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -66,11 +66,11 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo -func (m *Params) GetSlashingAddress() []byte { +func (m *Params) GetSlashingAddress() string { if m != nil { return m.SlashingAddress } - return nil + return "" } func init() { @@ -82,7 +82,7 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 238 bytes of a gzipped FileDescriptorProto + // 240 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, @@ -93,11 +93,11 @@ var fileDescriptor_8d1392776a3e15b9 = []byte{ 0x9a, 0x9b, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x97, 0x54, 0x16, 0xa4, 0x16, 0xeb, 0x39, 0x79, 0x06, 0x18, 0x9b, 0x18, 0x04, 0x94, 0x26, 0x79, 0xa7, 0x56, 0x06, 0xb1, 0x81, 0x8c, 0x09, 0xc8, 0x16, 0xd2, 0xe4, 0x12, 0x28, 0xce, 0x49, 0x2c, 0xce, 0xc8, 0xcc, 0x4b, 0x8f, 0x4f, 0x4c, - 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x02, 0x99, 0x1c, 0xc4, 0x0f, 0x13, 0x77, 0x84, 0x08, - 0x5b, 0xb1, 0xcc, 0x58, 0x20, 0xcf, 0xe0, 0xe4, 0x73, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, - 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, - 0x72, 0x0c, 0x51, 0x04, 0x9d, 0x51, 0x81, 0x1c, 0x22, 0x60, 0x37, 0x25, 0xb1, 0x81, 0x7d, 0x68, - 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xba, 0xda, 0xfc, 0xdc, 0x34, 0x01, 0x00, 0x00, + 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0c, 0xe2, 0x87, 0x89, 0x3b, + 0x42, 0x84, 0xad, 0x58, 0x66, 0x2c, 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, + 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, + 0xc6, 0x63, 0x39, 0x86, 0x28, 0x82, 0xce, 0xa8, 0x40, 0x0e, 0x11, 0xb0, 0x9b, 0x92, 0xd8, 0xc0, + 0x3e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x1b, 0x71, 0xf7, 0xdb, 0x34, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -244,7 +244,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingAddress", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -254,25 +254,23 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthParams } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthParams } if postIndex > l { return io.ErrUnexpectedEOF } - m.SlashingAddress = append(m.SlashingAddress[:0], dAtA[iNdEx:postIndex]...) - if m.SlashingAddress == nil { - m.SlashingAddress = []byte{} - } + m.SlashingAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index 40da6992a..c2653beec 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -20,7 +20,7 @@ func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPos // generate pop.BabylonSig = sign(sk_Babylon, pk_BTC) btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) - babylonSig, err := babylonSK.Sign(bip340PK) + babylonSig, err := babylonSK.Sign(*bip340PK) if err != nil { return nil, err } diff --git a/x/btcstaking/types/pop_test.go b/x/btcstaking/types/pop_test.go index bc2b979f8..d566cea0f 100644 --- a/x/btcstaking/types/pop_test.go +++ b/x/btcstaking/types/pop_test.go @@ -21,7 +21,7 @@ func newInvalidPoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.Pri btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) - babylonSig, err := babylonSK.Sign(bip340PK) + babylonSig, err := babylonSK.Sign(*bip340PK) if err != nil { panic(err) } @@ -63,12 +63,12 @@ func FuzzPoP(f *testing.F) { // generate and verify PoP, correct case pop, err := types.NewPoP(babylonSK, btcSK) require.NoError(t, err) - err = pop.Verify(babylonPK, &bip340PK) + err = pop.Verify(babylonPK, bip340PK) require.NoError(t, err) // generate and verify PoP, invalid case invalidPoP := newInvalidPoP(r, babylonSK, btcSK) - err = invalidPoP.Verify(babylonPK, &bip340PK) + err = invalidPoP.Verify(babylonPK, bip340PK) require.Error(t, err) }) } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 4e3f4a551..ddec07b99 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -144,20 +144,17 @@ type MsgCreateBTCDelegation struct { // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` // staking_tx is the staking tx - // It is signed by SK corresponding to btc_pk and is already on Bitcoin chain - StakingTx *github_com_babylonchain_babylon_types.BTCStakingTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCStakingTx" json:"staking_tx,omitempty"` - // staking_tx_sig is the signature of the staking tx - // signed by SK corresponding to btc_pk - StakingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=staking_tx_sig,json=stakingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"staking_tx_sig,omitempty"` + StakingTx *StakingTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // staking_tx_info is the tx info of the staking tx, including the Merkle proof - StakingTxInfo *types.TransactionInfo `protobuf:"bytes,6,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` + StakingTxInfo *types.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` // slashing_tx is the slashing tx - // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. - SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,7,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` - // slashing_tx_sig is the signature of the slashing tx - // signed by SK corresponding to btc_pk - SlashingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,8,opt,name=slashing_tx_sig,json=slashingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_tx_sig,omitempty"` + // Note that the tx itself does not contain signatures, which are off-chain. + SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` + // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the staking tx output. + // The staking tx output further needs signatures from jury and validator in + // order to be spendable. + DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,7,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` } func (m *MsgCreateBTCDelegation) Reset() { *m = MsgCreateBTCDelegation{} } @@ -214,6 +211,13 @@ func (m *MsgCreateBTCDelegation) GetPop() *ProofOfPossession { return nil } +func (m *MsgCreateBTCDelegation) GetStakingTx() *StakingTx { + if m != nil { + return m.StakingTx + } + return nil +} + func (m *MsgCreateBTCDelegation) GetStakingTxInfo() *types.TransactionInfo { if m != nil { return m.StakingTxInfo @@ -367,53 +371,52 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 723 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0xcb, 0x6e, 0xd3, 0x4c, - 0x14, 0xc7, 0xe3, 0xa6, 0xcd, 0xf7, 0x65, 0x7a, 0x93, 0x86, 0x5e, 0xd2, 0x48, 0x75, 0xa2, 0x2c, - 0xaa, 0x80, 0x5a, 0x9b, 0xa4, 0x17, 0x44, 0x91, 0x90, 0x48, 0xd9, 0x54, 0x10, 0x11, 0x9c, 0x14, - 0x21, 0x36, 0xd1, 0xd8, 0x99, 0x4c, 0x46, 0x49, 0x3c, 0x96, 0x67, 0x52, 0x25, 0x62, 0xc7, 0x13, - 0xb0, 0xe2, 0x39, 0x58, 0xf0, 0x10, 0x5d, 0x56, 0xac, 0x50, 0x17, 0x11, 0x6a, 0x91, 0x78, 0x01, - 0x56, 0xac, 0x50, 0xec, 0x49, 0x6c, 0x90, 0x2b, 0x1a, 0x10, 0xbb, 0x39, 0x39, 0xbf, 0xf3, 0x3f, - 0x97, 0x39, 0x19, 0x03, 0xd5, 0x44, 0xe6, 0xa0, 0xc3, 0x6c, 0xdd, 0x14, 0x16, 0x17, 0xa8, 0x4d, - 0x6d, 0xa2, 0x9f, 0x16, 0x74, 0xd1, 0xd7, 0x1c, 0x97, 0x09, 0x06, 0x57, 0xa5, 0x5f, 0x0b, 0xfc, - 0xda, 0x69, 0x21, 0xbd, 0x42, 0x18, 0x61, 0x1e, 0xa1, 0x8f, 0x4e, 0x3e, 0x9c, 0xde, 0xb0, 0x18, - 0xef, 0x32, 0x5e, 0xf7, 0x1d, 0xbe, 0x21, 0x5d, 0xeb, 0xbe, 0xa5, 0x77, 0xb9, 0xa7, 0xdf, 0xe5, - 0x44, 0x3a, 0x72, 0xd2, 0x61, 0xb9, 0x03, 0x47, 0x30, 0x9d, 0x63, 0xcb, 0x29, 0xee, 0x1f, 0xb4, - 0x0b, 0x7a, 0x1b, 0x0f, 0xc6, 0xc1, 0xb9, 0xe8, 0x22, 0x1d, 0xe4, 0xa2, 0xee, 0x98, 0xd9, 0x8a, - 0x66, 0x42, 0x65, 0xfb, 0xdc, 0x76, 0x88, 0xb3, 0x5a, 0xd8, 0x6a, 0x3b, 0x8c, 0xda, 0x42, 0xa2, - 0xc1, 0x0f, 0x3e, 0x9d, 0xfb, 0xae, 0x80, 0xd5, 0x32, 0x27, 0x47, 0x2e, 0x46, 0x02, 0x97, 0x6a, - 0x47, 0x2f, 0x50, 0x87, 0x36, 0x90, 0x60, 0x2e, 0x5c, 0x03, 0x09, 0x4e, 0x89, 0x8d, 0xdd, 0x94, - 0x92, 0x55, 0xf2, 0x49, 0x43, 0x5a, 0xf0, 0x21, 0x00, 0x32, 0x43, 0xdd, 0x69, 0xa7, 0x66, 0xb2, - 0x4a, 0x7e, 0xbe, 0x98, 0xd1, 0xe4, 0x2c, 0xfc, 0x26, 0xb5, 0x49, 0x93, 0x5a, 0xa5, 0x67, 0x3e, - 0xc1, 0x03, 0x23, 0x29, 0x43, 0x2a, 0x6d, 0x58, 0x06, 0x09, 0x53, 0x58, 0xa3, 0xd8, 0x78, 0x56, - 0xc9, 0x2f, 0x94, 0x0e, 0x2e, 0x86, 0x99, 0x22, 0xa1, 0xa2, 0xd5, 0x33, 0x35, 0x8b, 0x75, 0x75, - 0x49, 0x5a, 0x2d, 0x44, 0xed, 0xb1, 0xa1, 0x8b, 0x81, 0x83, 0xb9, 0x56, 0x3a, 0xae, 0xec, 0xee, - 0xdd, 0x95, 0x92, 0x73, 0xa6, 0xb0, 0x2a, 0x6d, 0x78, 0x08, 0xe2, 0x0e, 0x73, 0x52, 0xb3, 0x5e, - 0x1d, 0x79, 0x2d, 0xf2, 0x36, 0xb5, 0x8a, 0xcb, 0x58, 0xf3, 0x59, 0xb3, 0xc2, 0x38, 0xc7, 0x9c, - 0x53, 0x66, 0x1b, 0xa3, 0xa0, 0x5c, 0x06, 0x6c, 0x46, 0xf6, 0x6e, 0x60, 0xee, 0x30, 0x9b, 0xe3, - 0xdc, 0xb7, 0x59, 0xb0, 0x16, 0x26, 0x1e, 0xe3, 0x0e, 0x26, 0x48, 0x50, 0x66, 0xff, 0xb3, 0xf1, - 0xc8, 0x7e, 0xe2, 0x7f, 0xd0, 0x0f, 0x3c, 0x01, 0x40, 0x42, 0x75, 0xd1, 0xf7, 0x46, 0x32, 0xdd, - 0x78, 0x6b, 0x47, 0x55, 0x3f, 0xbc, 0xd6, 0x37, 0x92, 0x7c, 0x7c, 0x84, 0x75, 0xb0, 0x14, 0xc8, - 0xd6, 0x39, 0x25, 0xa9, 0x39, 0x4f, 0xfa, 0xfe, 0xc5, 0x30, 0xb3, 0x3f, 0xcd, 0xcd, 0x55, 0x29, - 0xb1, 0x91, 0xe8, 0xb9, 0xd8, 0x58, 0x98, 0xa8, 0x57, 0x29, 0x81, 0xcf, 0xc1, 0x72, 0x28, 0x01, - 0xb5, 0x9b, 0x2c, 0x95, 0xf0, 0xfa, 0xbf, 0x1d, 0xee, 0x3f, 0xb4, 0xbb, 0xa7, 0x05, 0xad, 0xe6, - 0x22, 0x9b, 0x23, 0x6b, 0x74, 0x17, 0xc7, 0x76, 0x93, 0x19, 0x8b, 0x13, 0xc5, 0x91, 0x09, 0x5f, - 0x82, 0x79, 0xde, 0x41, 0xbc, 0x25, 0x67, 0xf1, 0x9f, 0x57, 0xf0, 0xbd, 0x8b, 0x61, 0x66, 0xf7, - 0xe6, 0xb3, 0x90, 0xf1, 0xb5, 0xbe, 0x01, 0xf8, 0xe4, 0x0c, 0x11, 0x58, 0x0e, 0x29, 0x7b, 0xe3, - 0xf8, 0xff, 0x6f, 0xc7, 0xb1, 0x18, 0xe8, 0x57, 0x29, 0xc9, 0x65, 0x81, 0x1a, 0xbd, 0x75, 0x93, - 0xc5, 0x7c, 0xa7, 0x80, 0xe5, 0x32, 0x27, 0x27, 0x4e, 0x03, 0x09, 0x5c, 0xf1, 0x9e, 0x09, 0x78, - 0x00, 0x92, 0xa8, 0x27, 0x5a, 0xcc, 0xa5, 0x62, 0xe0, 0x2f, 0x65, 0x29, 0xf5, 0xf1, 0xc3, 0xce, - 0x8a, 0xdc, 0xbd, 0x47, 0x8d, 0x86, 0x8b, 0x39, 0xaf, 0x0a, 0x97, 0xda, 0xc4, 0x08, 0x50, 0xf8, - 0x00, 0x24, 0xfc, 0x87, 0x46, 0x6e, 0xeb, 0xe6, 0x75, 0x4b, 0xe7, 0x41, 0xa5, 0xd9, 0xb3, 0x61, - 0x26, 0x66, 0xc8, 0x90, 0xc3, 0xa5, 0x37, 0x5f, 0xdf, 0xdf, 0x09, 0xc4, 0x72, 0x1b, 0x60, 0xfd, - 0x97, 0xba, 0xc6, 0x35, 0x17, 0xbf, 0xcc, 0x80, 0x78, 0x99, 0x13, 0xd8, 0x07, 0x30, 0xe2, 0xb9, - 0xd9, 0xbe, 0x26, 0x6b, 0xe4, 0x1f, 0x34, 0xbd, 0x37, 0x0d, 0x3d, 0xae, 0x00, 0xbe, 0x06, 0xb7, - 0xa2, 0xfe, 0xca, 0x3b, 0x37, 0x10, 0x0b, 0xf0, 0xf4, 0xfe, 0x54, 0xf8, 0x24, 0x79, 0x13, 0x2c, - 0xfc, 0x74, 0x5d, 0x5b, 0xd7, 0xcb, 0x84, 0xb9, 0xb4, 0x76, 0x33, 0x6e, 0x9c, 0xa7, 0xf4, 0xf4, - 0xec, 0x52, 0x55, 0xce, 0x2f, 0x55, 0xe5, 0xf3, 0xa5, 0xaa, 0xbc, 0xbd, 0x52, 0x63, 0xe7, 0x57, - 0x6a, 0xec, 0xd3, 0x95, 0x1a, 0x7b, 0xf5, 0xdb, 0x67, 0xa0, 0x1f, 0xfe, 0xb6, 0x78, 0x9b, 0x6a, - 0x26, 0xbc, 0xcf, 0xc4, 0xee, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x51, 0x30, 0x8b, 0xd0, 0x47, - 0x07, 0x00, 0x00, + // 711 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xcd, 0x6e, 0xd3, 0x4a, + 0x14, 0xc7, 0xe3, 0xa6, 0xcd, 0x55, 0xa6, 0xed, 0xad, 0x34, 0xb7, 0x1f, 0x69, 0xa4, 0x3a, 0x51, + 0x16, 0x55, 0xee, 0x55, 0x6b, 0xdf, 0xa4, 0x1f, 0x88, 0x22, 0x81, 0x48, 0xd9, 0x54, 0x10, 0x11, + 0x9c, 0x80, 0x10, 0x0b, 0xa2, 0xb1, 0x33, 0x99, 0x8c, 0x9c, 0x78, 0x2c, 0xcf, 0xa4, 0x4a, 0xc4, + 0x8e, 0x27, 0x60, 0xc5, 0x73, 0xb0, 0xe0, 0x21, 0xba, 0xac, 0x58, 0xa1, 0x2e, 0x22, 0xd4, 0x22, + 0xf1, 0x0e, 0x5d, 0xa1, 0xd8, 0xe3, 0xc4, 0x20, 0x47, 0xb4, 0x48, 0xec, 0xe6, 0xf8, 0xfc, 0xce, + 0x7f, 0xce, 0x99, 0x73, 0x8e, 0x81, 0x6a, 0x22, 0x73, 0xd8, 0x65, 0x8e, 0x6e, 0x0a, 0x8b, 0x0b, + 0x64, 0x53, 0x87, 0xe8, 0xa7, 0x25, 0x5d, 0x0c, 0x34, 0xd7, 0x63, 0x82, 0xc1, 0x35, 0xe9, 0xd7, + 0xa6, 0x7e, 0xed, 0xb4, 0x94, 0x5d, 0x25, 0x8c, 0x30, 0x9f, 0xd0, 0xc7, 0xa7, 0x00, 0xce, 0x6e, + 0x5a, 0x8c, 0xf7, 0x18, 0x6f, 0x06, 0x8e, 0xc0, 0x90, 0xae, 0x8d, 0xc0, 0xd2, 0x7b, 0xdc, 0xd7, + 0xef, 0x71, 0x22, 0x1d, 0x05, 0xe9, 0xb0, 0xbc, 0xa1, 0x2b, 0x98, 0xce, 0xb1, 0xe5, 0x96, 0x0f, + 0x0e, 0xed, 0x92, 0x6e, 0xe3, 0x61, 0x18, 0x5c, 0x88, 0x4f, 0xd2, 0x45, 0x1e, 0xea, 0x85, 0xcc, + 0x76, 0x3c, 0x13, 0x49, 0x3b, 0xe0, 0x76, 0x22, 0x9c, 0xd5, 0xc1, 0x96, 0xed, 0x32, 0xea, 0x08, + 0x89, 0x4e, 0x3f, 0x04, 0x74, 0xe1, 0x5a, 0x01, 0x6b, 0x55, 0x4e, 0x8e, 0x3d, 0x8c, 0x04, 0xae, + 0x34, 0x8e, 0x5f, 0xa0, 0x2e, 0x6d, 0x21, 0xc1, 0x3c, 0xb8, 0x0e, 0x52, 0x9c, 0x12, 0x07, 0x7b, + 0x19, 0x25, 0xaf, 0x14, 0xd3, 0x86, 0xb4, 0xe0, 0x7d, 0x00, 0xe4, 0x0d, 0x4d, 0xd7, 0xce, 0xcc, + 0xe5, 0x95, 0xe2, 0x62, 0x39, 0xa7, 0xc9, 0xb7, 0x08, 0x8a, 0xd4, 0x26, 0x45, 0x6a, 0xb5, 0xbe, + 0xf9, 0x18, 0x0f, 0x8d, 0xb4, 0x0c, 0xa9, 0xd9, 0xb0, 0x0a, 0x52, 0xa6, 0xb0, 0xc6, 0xb1, 0xc9, + 0xbc, 0x52, 0x5c, 0xaa, 0x1c, 0x5e, 0x8c, 0x72, 0x65, 0x42, 0x45, 0xa7, 0x6f, 0x6a, 0x16, 0xeb, + 0xe9, 0x92, 0xb4, 0x3a, 0x88, 0x3a, 0xa1, 0xa1, 0x8b, 0xa1, 0x8b, 0xb9, 0x56, 0x39, 0xa9, 0xed, + 0xed, 0xff, 0x2f, 0x25, 0x17, 0x4c, 0x61, 0xd5, 0x6c, 0x78, 0x04, 0x92, 0x2e, 0x73, 0x33, 0xf3, + 0x7e, 0x1e, 0x45, 0x2d, 0xb6, 0x9b, 0x5a, 0xcd, 0x63, 0xac, 0xfd, 0xb4, 0x5d, 0x63, 0x9c, 0x63, + 0xce, 0x29, 0x73, 0x8c, 0x71, 0x50, 0x21, 0x07, 0xb6, 0x62, 0x6b, 0x37, 0x30, 0x77, 0x99, 0xc3, + 0x71, 0xe1, 0x3a, 0x09, 0xd6, 0xa3, 0xc4, 0x23, 0xdc, 0xc5, 0x04, 0x09, 0xca, 0x9c, 0x3f, 0xf6, + 0x3c, 0xb2, 0x9e, 0xe4, 0x6f, 0xd4, 0x03, 0x1f, 0x00, 0x20, 0xa1, 0xa6, 0x18, 0xc8, 0x27, 0xc9, + 0xcf, 0x90, 0xa8, 0x07, 0xc7, 0xc6, 0xc0, 0x48, 0xf3, 0xf0, 0x08, 0x9f, 0x81, 0x95, 0xa9, 0x40, + 0x93, 0x3a, 0x6d, 0x96, 0x59, 0xf0, 0x55, 0xfe, 0x8d, 0xaa, 0x44, 0x86, 0xe8, 0xb4, 0xa4, 0x35, + 0x3c, 0xe4, 0x70, 0x64, 0x8d, 0x1f, 0xe5, 0xc4, 0x69, 0x33, 0x63, 0x79, 0x22, 0x37, 0x36, 0xe1, + 0x4b, 0xb0, 0xc8, 0xbb, 0x88, 0x77, 0x64, 0x52, 0x29, 0xbf, 0xe7, 0x77, 0x2e, 0x46, 0xb9, 0xbd, + 0x1b, 0xf6, 0xbc, 0x71, 0x5c, 0x97, 0xf1, 0x8d, 0x81, 0x01, 0xf8, 0xe4, 0x0c, 0x5f, 0x83, 0xe5, + 0x56, 0xd0, 0x0f, 0xe6, 0x35, 0x39, 0x25, 0x99, 0xbf, 0x7c, 0xed, 0xbb, 0x17, 0xa3, 0xdc, 0xc1, + 0x6d, 0xe6, 0xa9, 0x4e, 0x89, 0x83, 0x44, 0xdf, 0xc3, 0xc6, 0xd2, 0x44, 0xaf, 0x4e, 0x49, 0x21, + 0x0f, 0xd4, 0xf8, 0xde, 0x4f, 0xc6, 0xe3, 0xbd, 0x02, 0x56, 0xaa, 0x9c, 0x3c, 0x77, 0x5b, 0x48, + 0xe0, 0x9a, 0xbf, 0xac, 0xf0, 0x10, 0xa4, 0x51, 0x5f, 0x74, 0x98, 0x47, 0xc5, 0x30, 0x18, 0x8d, + 0x4a, 0xe6, 0xd3, 0xc7, 0xdd, 0x55, 0x39, 0x01, 0x0f, 0x5b, 0x2d, 0x0f, 0x73, 0x5e, 0x17, 0x1e, + 0x75, 0x88, 0x31, 0x45, 0xe1, 0x3d, 0x90, 0x0a, 0xd6, 0x5d, 0xce, 0xcc, 0xd6, 0xac, 0xd6, 0xfb, + 0x50, 0x65, 0xfe, 0x6c, 0x94, 0x4b, 0x18, 0x32, 0xe4, 0xe8, 0xef, 0xb7, 0xdf, 0x3e, 0xfc, 0x37, + 0x15, 0x2b, 0x6c, 0x82, 0x8d, 0x9f, 0xf2, 0x0a, 0x73, 0x2e, 0x7f, 0x9d, 0x03, 0xc9, 0x2a, 0x27, + 0x70, 0x00, 0x60, 0xcc, 0xd2, 0xef, 0xcc, 0xb8, 0x35, 0x76, 0x4d, 0xb2, 0xfb, 0xb7, 0xa1, 0xc3, + 0x0c, 0xe0, 0x1b, 0xf0, 0x4f, 0xdc, 0x42, 0xed, 0xde, 0x40, 0x6c, 0x8a, 0x67, 0x0f, 0x6e, 0x85, + 0x4f, 0x2e, 0x6f, 0x83, 0xa5, 0x1f, 0xda, 0xb5, 0x3d, 0x5b, 0x26, 0xca, 0x65, 0xb5, 0x9b, 0x71, + 0xe1, 0x3d, 0x95, 0x27, 0x67, 0x97, 0xaa, 0x72, 0x7e, 0xa9, 0x2a, 0x5f, 0x2e, 0x55, 0xe5, 0xdd, + 0x95, 0x9a, 0x38, 0xbf, 0x52, 0x13, 0x9f, 0xaf, 0xd4, 0xc4, 0xab, 0x5f, 0xfe, 0xeb, 0x06, 0xd1, + 0x3f, 0xbc, 0x3f, 0xa8, 0x66, 0xca, 0xff, 0x59, 0xef, 0x7d, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x08, + 0x3f, 0xe8, 0xd7, 0xcd, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -683,17 +686,17 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l - if m.SlashingTxSig != nil { + if m.DelegatorSig != nil { { - size := m.SlashingTxSig.Size() + size := m.DelegatorSig.Size() i -= size - if _, err := m.SlashingTxSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.DelegatorSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x42 + dAtA[i] = 0x3a } if m.SlashingTx != nil { { @@ -705,7 +708,7 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x3a + dAtA[i] = 0x32 } if m.StakingTxInfo != nil { { @@ -717,27 +720,15 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 - } - if m.StakingTxSig != nil { - { - size := m.StakingTxSig.Size() - i -= size - if _, err := m.StakingTxSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0x2a } if m.StakingTx != nil { { - size := m.StakingTx.Size() - i -= size - if _, err := m.StakingTx.MarshalTo(dAtA[i:]); err != nil { + size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { return 0, err } + i -= size i = encodeVarintTx(dAtA, i, uint64(size)) } i-- @@ -930,10 +921,6 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.StakingTx.Size() n += 1 + l + sovTx(uint64(l)) } - if m.StakingTxSig != nil { - l = m.StakingTxSig.Size() - n += 1 + l + sovTx(uint64(l)) - } if m.StakingTxInfo != nil { l = m.StakingTxInfo.Size() n += 1 + l + sovTx(uint64(l)) @@ -942,8 +929,8 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.SlashingTx.Size() n += 1 + l + sovTx(uint64(l)) } - if m.SlashingTxSig != nil { - l = m.SlashingTxSig.Size() + if m.DelegatorSig != nil { + l = m.DelegatorSig.Size() n += 1 + l + sovTx(uint64(l)) } return n @@ -1364,7 +1351,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -1374,63 +1361,29 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCStakingTx - m.StakingTx = &v + if m.StakingTx == nil { + m.StakingTx = &StakingTx{} + } if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxSig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.StakingTxSig = &v - if err := m.StakingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTxInfo", wireType) } @@ -1466,7 +1419,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } @@ -1501,9 +1454,9 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 8: + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTxSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1531,8 +1484,8 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.SlashingTxSig = &v - if err := m.SlashingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.DelegatorSig = &v + if err := m.DelegatorSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex From c2392b0a1b88929fc3dfefd77545ba464c40db2e Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 26 Jun 2023 08:54:27 +0200 Subject: [PATCH 016/202] Add signing and sig verifing functions (#9) * Add signing and sig verifing functions --- btcstaking/staking.go | 234 ++++++++++++++++++++++++++++++++++--- btcstaking/staking_test.go | 40 +++++++ 2 files changed, 259 insertions(+), 15 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index af591d0c9..2d59156f5 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -115,9 +115,14 @@ type StakingScriptData struct { StakingTime uint16 } +// StakingOutputInfo holds info about whole staking output: +// - data derived from the script +// - staking amount in staking output +// - staking pk script type StakingOutputInfo struct { StakingScriptData *StakingScriptData StakingAmount btcutil.Amount + StakingPkScript []byte } func NewStakingScriptData( @@ -299,7 +304,12 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { } // we do not need to check error here, as we already validated that all public keys are not nil - scriptData, err := NewStakingScriptData(stakerPk1, validatorPk, juryPk, uint16(template[12].extractedInt)) + scriptData, err := NewStakingScriptData( + stakerPk1, + validatorPk, + juryPk, + uint16(template[12].extractedInt), + ) if err != nil { panic(fmt.Sprintf("unexpected error: %v", err)) @@ -420,25 +430,14 @@ func BuildWitnessToSpendStakingOutput( return nil, err } - inputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingOutput.PkScript, - stakingOutput.Value, - ) - - sigHashes := txscript.NewTxSigHashes(tx, inputFetcher) - - sig, err := txscript.RawTxInTapscriptSignature( - tx, sigHashes, 0, stakingOutput.Value, - stakingOutput.PkScript, tapLeaf, txscript.SigHashDefault, - privKey, - ) + sig, err := SignTxWithOneScriptSpendInputFromTapLeaf(tx, stakingOutput, privKey, tapLeaf) if err != nil { return nil, err } witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = sig + witnessStack[0] = sig.Serialize() witnessStack[1] = stakingScript witnessStack[2] = ctrlBlockBytes return witnessStack, nil @@ -706,7 +705,7 @@ func CheckTransactions( return nil, fmt.Errorf("slashing transaction must spend staking output") } - //5. Check that index of the found output matches index of the input in slashing transaction + //5. Check that index of the fund output matches index of the input in slashing transaction if slashingTx.TxIn[0].PreviousOutPoint.Index != uint32(stakingOutputIdx) { return nil, fmt.Errorf("slashing transaction input must spend staking output") } @@ -729,5 +728,210 @@ func CheckTransactions( return &StakingOutputInfo{ StakingScriptData: scriptData, StakingAmount: btcutil.Amount(stakingOutput.Value), + StakingPkScript: stakingOutput.PkScript, }, nil } + +func signTxWithOneScriptSpendInputFromTapLeafInternal( + txToSign *wire.MsgTx, + fundingOutput *wire.TxOut, + privKey *btcec.PrivateKey, + tapLeaf txscript.TapLeaf) (*schnorr.Signature, error) { + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + fundingOutput.PkScript, + fundingOutput.Value, + ) + + sigHashes := txscript.NewTxSigHashes(txToSign, inputFetcher) + + sig, err := txscript.RawTxInTapscriptSignature( + txToSign, sigHashes, 0, fundingOutput.Value, + fundingOutput.PkScript, tapLeaf, txscript.SigHashDefault, + privKey, + ) + + if err != nil { + return nil, err + } + + parsedSig, err := schnorr.ParseSignature(sig) + + if err != nil { + return nil, err + } + + return parsedSig, err +} + +// SignTxWithOneScriptSpendInputFromTapLeaf signs transaction with one input coming +// from script spend output. +// It does not do any validations, expect that txToSign has exactly one input. +func SignTxWithOneScriptSpendInputFromTapLeaf( + txToSign *wire.MsgTx, + fundingOutput *wire.TxOut, + privKey *btcec.PrivateKey, + tapLeaf txscript.TapLeaf, +) (*schnorr.Signature, error) { + if txToSign == nil { + return nil, fmt.Errorf("tx to sign must not be nil") + } + + if fundingOutput == nil { + return nil, fmt.Errorf("funding output must not be nil") + } + + if privKey == nil { + return nil, fmt.Errorf("private key must not be nil") + } + + if len(txToSign.TxIn) != 1 { + return nil, fmt.Errorf("tx to sign must have exactly one input") + } + + return signTxWithOneScriptSpendInputFromTapLeafInternal( + txToSign, + fundingOutput, + privKey, + tapLeaf, + ) +} + +// SignTxWithOneScriptSpendInputFromScript signs transaction with one input coming +// from script spend output with provided script. +// It does not do any validations, expect that txToSign has exactly one input. +func SignTxWithOneScriptSpendInputFromScript( + txToSign *wire.MsgTx, + fundingOutput *wire.TxOut, + privKey *btcec.PrivateKey, + script []byte, +) (*schnorr.Signature, error) { + tapLeaf := txscript.NewBaseTapLeaf(script) + return SignTxWithOneScriptSpendInputFromTapLeaf(txToSign, fundingOutput, privKey, tapLeaf) +} + +// SignTxWithOneScriptSpendInputStrict signs transaction with one input coming +// from script spend output with provided script. +// It checks: +// - txToSign is not nil +// - txToSign has exactly one input +// - fundingTx is not nil +// - fundingTx has one output committing to the provided script +// - txToSign input is pointing to the correct output in fundingTx +func SignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + privKey *btcec.PrivateKey, + script []byte, + net *chaincfg.Params, +) (*schnorr.Signature, error) { + + if txToSign == nil { + return nil, fmt.Errorf("tx to sign must not be nil") + } + + if len(txToSign.TxIn) != 1 { + return nil, fmt.Errorf("tx to sign must have exactly one input") + } + + scriptIdx, err := GetIdxOutputCommitingToScript(fundingTx, script, net) + + if err != nil { + return nil, err + } + + fundingTxHash := fundingTx.TxHash() + + if !txToSign.TxIn[0].PreviousOutPoint.Hash.IsEqual(&fundingTxHash) { + return nil, fmt.Errorf("txToSign must input point to fundingTx") + } + + if txToSign.TxIn[0].PreviousOutPoint.Index != uint32(scriptIdx) { + return nil, fmt.Errorf("txToSign inpunt index must point to output with provided script") + } + + fundingOutput := fundingTx.TxOut[scriptIdx] + + return SignTxWithOneScriptSpendInputFromScript(txToSign, fundingOutput, privKey, script) +} + +// VerifyTransactionSigWithOutput verifies that: +// - provided transaction has exactly one input +// - provided signature is valid schnorr BIP340 signature +// - provided signature is signing whole provided transaction (SigHashDefault) +func VerifyTransactionSigWithOutput( + transaction *wire.MsgTx, + fundingOutput *wire.TxOut, + script []byte, + pubKey *btcec.PublicKey, + signature []byte) error { + + if fundingOutput == nil { + return fmt.Errorf("funding output must not be nil") + } + + return VerifyTransactionSigWithOutputData( + transaction, + fundingOutput.PkScript, + fundingOutput.Value, + script, + pubKey, + signature, + ) +} + +// VerifyTransactionSigWithOutputData verifies that: +// - provided transaction has exactly one input +// - provided signature is valid schnorr BIP340 signature +// - provided signature is signing whole provided transaction (SigHashDefault) +func VerifyTransactionSigWithOutputData( + transaction *wire.MsgTx, + fundingOutputPkScript []byte, + fundingOutputValue int64, + script []byte, + pubKey *btcec.PublicKey, + signature []byte) error { + + if transaction == nil { + return fmt.Errorf("tx to verify not be nil") + } + + if len(transaction.TxIn) != 1 { + return fmt.Errorf("tx to sign must have exactly one input") + } + + if pubKey == nil { + return fmt.Errorf("public key must not be nil") + } + + tapLeaf := txscript.NewBaseTapLeaf(script) + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + fundingOutputPkScript, + fundingOutputValue, + ) + + sigHashes := txscript.NewTxSigHashes(transaction, inputFetcher) + + sigHash, err := txscript.CalcTapscriptSignaturehash( + sigHashes, txscript.SigHashDefault, transaction, 0, inputFetcher, tapLeaf, + ) + + if err != nil { + return err + } + + parsedSig, err := schnorr.ParseSignature(signature) + + if err != nil { + return err + } + + valid := parsedSig.Verify(sigHash, pubKey) + + if !valid { + return fmt.Errorf("signature is not valid") + } + + return nil +} diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index dd8c6664f..d0ecb59a7 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -120,6 +120,46 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { }) } +func FuzzGeneratingSignatureValidation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + pk, err := btcec.NewPrivateKey() + require.NoError(t, err) + inputHash, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + + tx := wire.NewMsgTx(2) + foundingOutput := wire.NewTxOut(int64(r.Intn(1000)), datagen.GenRandomByteArray(r, 32)) + tx.AddTxIn( + wire.NewTxIn(wire.NewOutPoint(inputHash, uint32(r.Intn(20))), nil, nil), + ) + tx.AddTxOut( + wire.NewTxOut(int64(r.Intn(1000)), datagen.GenRandomByteArray(r, 32)), + ) + script := datagen.GenRandomByteArray(r, 150) + + sig, err := btcstaking.SignTxWithOneScriptSpendInputFromScript( + tx, + foundingOutput, + pk, + script, + ) + + require.NoError(t, err) + + err = btcstaking.VerifyTransactionSigWithOutput( + tx, + foundingOutput, + script, + pk.PubKey(), + sig.Serialize(), + ) + + require.NoError(t, err) + }) +} + // Help function to assert the execution of a script engine. Copied from: // https://github.com/lightningnetwork/lnd/blob/master/input/script_utils_test.go#L24 func assertEngineExecution(t *testing.T, testNum int, valid bool, From e91756369c0cbf863c10a715aa9ecce362253ffd Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 27 Jun 2023 12:39:51 +1000 Subject: [PATCH 017/202] BTC staking: handle delegator signature (#10) --- proto/babylon/btcstaking/v1/btcstaking.proto | 4 +- proto/babylon/btcstaking/v1/tx.proto | 21 +- testutil/datagen/btcstaking.go | 4 +- x/btcstaking/keeper/msg_server.go | 67 +- x/btcstaking/keeper/msg_server_test.go | 77 ++- .../btcstaking/types}/btc_slashing_tx.go | 38 ++ x/btcstaking/types/btcstaking.go | 33 +- x/btcstaking/types/btcstaking.pb.go | 84 +-- x/btcstaking/types/codec.go | 2 + x/btcstaking/types/msg.go | 43 +- x/btcstaking/types/tx.pb.go | 598 ++++++++++++++++-- 11 files changed, 820 insertions(+), 151 deletions(-) rename {types => x/btcstaking/types}/btc_slashing_tx.go (58%) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index c0156a347..f9d17551e 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -44,7 +44,7 @@ message BTCDelegation { // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - bytes slashing_tx = 9 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; + bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. @@ -52,7 +52,7 @@ message BTCDelegation { // jury_sig is the signature signature on the slashing tx // by the jury (i.e., SK corresponding to jury_pk in params) // It will be a part of the witness for the staking tx output. - bytes jury_sig = 12 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // ProofOfPossession is the proof of possession that a Babylon secp256k1 diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 4c4b18042..870de7ed8 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -17,6 +17,9 @@ service Msg { rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); // CreateBTCDelegation creates a new BTC delegation rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); + // AddJurySig handles a signature from jury + rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse); + // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } @@ -48,7 +51,7 @@ message MsgCreateBTCDelegation { babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 5; // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. - bytes slashing_tx = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCSlashingTx" ]; + bytes slashing_tx = 6 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from jury and validator in @@ -58,6 +61,22 @@ message MsgCreateBTCDelegation { // MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation message MsgCreateBTCDelegationResponse {} +// MsgAddJurySig is the message for handling a signature from jury +message MsgAddJurySig { + string signer = 1; + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // sig is the signature of the jury + // the signature follows encoding in BIP-340 spec + bytes sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} +// MsgAddJurySigResponse is the response for MsgAddJurySig +message MsgAddJurySigResponse {} + // MsgUpdateParams defines a message for updating btcstaking module parameters. message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index ab7ac519f..b2b344250 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -49,7 +49,7 @@ func GenRandomBTCValidatorWithBTCPK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst }, nil } -func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validatorPK, juryPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string) (*bstypes.StakingTx, *bbn.BTCSlashingTx, error) { +func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validatorPK, juryPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { btcNet := &chaincfg.SimNetParams stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( @@ -99,7 +99,7 @@ func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validator if err != nil { return nil, nil, err } - slashingTx, err := bbn.NewBTCSlashingTxFromMsgTx(slashingMsgTx) + slashingTx, err := bstypes.NewBTCSlashingTxFromMsgTx(slashingMsgTx) if err != nil { return nil, nil, err } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 4fb54b43c..a95a2e7cf 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -42,10 +42,6 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara // CreateBTCValidator creates a BTC validator func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { - // stateless checks, including PoP - if err := req.ValidateBasic(); err != nil { - return nil, err - } // ensure the validator address does not exist before ctx := sdk.UnwrapSDKContext(goCtx) if ms.HasBTCValidator(ctx, *req.BtcPk) { @@ -65,11 +61,6 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // CreateBTCDelegation creates a BTC delegation func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { - // stateless checks - if err := req.ValidateBasic(); err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(goCtx) // extract staking script from staking tx @@ -85,6 +76,8 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // NOTE: it's okay that the same staker has multiple delegations // the situation that we need to prevent here is that every staking tx // can only correspond to a single BTC delegation + // TODO: the current impl does not support multiple delegations with the same (valPK, delPK) pair + // since a delegation is keyed by (valPK, delPK). Need to decide whether to support this btcDel, err := ms.GetBTCDelegation(ctx, *valBTCPK, *delBTCPK) if err == nil && btcDel.StakingTx.Equals(req.StakingTx) { return nil, fmt.Errorf("the BTC staking tx is already used") @@ -128,6 +121,18 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, err } + // verify delegator_sig + err = req.SlashingTx.VerifySignature( + stakingOutputInfo.StakingPkScript, + int64(stakingOutputInfo.StakingAmount), + req.StakingTx.StakingScript, + stakingOutputInfo.StakingScriptData.StakerKey, + req.DelegatorSig, + ) + if err != nil { + return nil, err + } + // all good, construct BTCDelegation and insert BTC delegation newBTCDel := &types.BTCDelegation{ BabylonPk: req.BabylonPk, @@ -146,3 +151,47 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return &types.MsgCreateBTCDelegationResponse{}, nil } + +// AddJurySig adds a signature from jury to a BTC delegation +func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) (*types.MsgAddJurySigResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // ensure BTC delegation exists + btcDel, err := ms.GetBTCDelegation(ctx, *req.ValPk, *req.DelPk) + if err != nil { + return nil, err + } + if btcDel.IsActivated() { + return nil, fmt.Errorf("the BTC delegation has already been signed by the jury") + } + + stakingOutputInfo, err := btcDel.StakingTx.GetStakingOutputInfo(ms.btcNet) + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) + } + + juryPK, err := ms.GetParams(ctx).JuryPk.ToBTCPK() + if err != nil { + // failing to cast a verified jury PK a programming error + panic(fmt.Errorf("failed to cast a verified jury public key")) + } + + // verify signature w.r.t. jury PK and signature + err = btcDel.SlashingTx.VerifySignature( + stakingOutputInfo.StakingPkScript, + int64(stakingOutputInfo.StakingAmount), + btcDel.StakingTx.StakingScript, + juryPK, + req.Sig, + ) + if err != nil { + return nil, err + } + + // all good, add signature to BTC delegation and set it back to KVStore + btcDel.JurySig = req.Sig + ms.setBTCDelegation(ctx, btcDel) + + return &types.MsgAddJurySigResponse{}, nil +} diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 78cc15328..bceb777ee 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "bytes" "context" "math/rand" "testing" @@ -13,9 +12,7 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -77,7 +74,7 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { }) } -func FuzzMsgCreateBTCDelegation(f *testing.F) { +func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -93,7 +90,7 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { goCtx := sdk.WrapSDKContext(ctx) // set jury PK to params - _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -103,7 +100,9 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { }) require.NoError(t, err) - // generate and insert new BTC validator + /* + generate and insert new BTC validator + */ validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcVal, err := datagen.GenRandomBTCValidatorWithBTCPK(r, validatorSK) @@ -117,6 +116,9 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) require.NoError(t, err) + /* + generate and insert new BTC delegation + */ // key pairs, staking tx and slashing tx delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) @@ -125,8 +127,7 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) require.NoError(t, err) // get msgTx - var stakingMsgTx wire.MsgTx - err = stakingMsgTx.Deserialize(bytes.NewReader(stakingTx.Tx)) + stakingMsgTx, err := stakingTx.ToMsgTx() require.NoError(t, err) // random signer @@ -139,7 +140,7 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { require.NoError(t, err) // generate staking tx info prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) - btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, &stakingMsgTx) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) btcHeader := btcHeaderWithProof.HeaderBytes txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) // mock for testing k-deep stuff @@ -148,33 +149,69 @@ func FuzzMsgCreateBTCDelegation(f *testing.F) { btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) - // TODO: generate proper delegator sig - randHash := datagen.GenRandomBtcdHash(r) - delegatorSchnorrSig, err := schnorr.Sign(delSK, randHash[:]) + // generate proper delegator sig + delegatorSig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.StakingScript, + delSK, + &chaincfg.SimNetParams, + ) require.NoError(t, err) - delegatorSig := bbn.NewBIP340SignatureFromBTCSig(delegatorSchnorrSig) - msg := &types.MsgCreateBTCDelegation{ + // all good, construct and send MsgCreateBTCDelegation message + msgCreateBTCDel := &types.MsgCreateBTCDelegation{ Signer: signer, BabylonPk: delBabylonPK.(*secp256k1.PubKey), Pop: pop, StakingTx: stakingTx, StakingTxInfo: txInfo, SlashingTx: slashingTx, - DelegatorSig: &delegatorSig, + DelegatorSig: delegatorSig, } - _, err = ms.CreateBTCDelegation(goCtx, msg) + _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) require.NoError(t, err) + /* + verify the new BTC delegation + */ // check existence actualDel, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) require.NoError(t, err) - require.Equal(t, msg.BabylonPk, actualDel.BabylonPk) - require.Equal(t, msg.Pop, actualDel.Pop) - require.Equal(t, msg.StakingTx, actualDel.StakingTx) - require.Equal(t, msg.SlashingTx, actualDel.SlashingTx) + require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) + require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) + require.Equal(t, msgCreateBTCDel.StakingTx, actualDel.StakingTx) + require.Equal(t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) // ensure the BTC delegation in DB is correctly formatted err = actualDel.ValidateBasic() require.NoError(t, err) + // delegation is not activated by jury yet + require.False(t, actualDel.IsActivated()) + + /* + generate and insert new jury signature + */ + jurySig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.StakingScript, + jurySK, + &chaincfg.SimNetParams, + ) + require.NoError(t, err) + msgAddJurySig := &types.MsgAddJurySig{ + Signer: signer, + ValPk: btcVal.BtcPk, + DelPk: actualDel.BtcPk, + Sig: jurySig, + } + _, err = ms.AddJurySig(ctx, msgAddJurySig) + require.NoError(t, err) + + /* + ensure jury sig is added successfully + */ + actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) + require.NoError(t, err) + require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) + require.True(t, actualDelWithJurySig.IsActivated()) }) } diff --git a/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go similarity index 58% rename from types/btc_slashing_tx.go rename to x/btcstaking/types/btc_slashing_tx.go index 2e4774bd6..3b85d10d0 100644 --- a/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -4,6 +4,8 @@ import ( "bytes" "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -76,3 +78,39 @@ func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) } return btcstaking.CheckSlashingTx(msgTx, decodedAddr) } + +// Sign generates a signature on the slashing tx signed by staker, validator or jury +func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk *btcec.PrivateKey, net *chaincfg.Params) (*bbn.BIP340Signature, error) { + msgTx, err := tx.ToMsgTx() + if err != nil { + return nil, err + } + schnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + msgTx, + stakingMsgTx, + sk, + stakingScript, + net, + ) + if err != nil { + return nil, err + } + delegatorSig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) + return &delegatorSig, nil +} + +// VerifySignature verifies a signature on the slashing tx signed by staker, validator or jury +func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { + msgTx, err := tx.ToMsgTx() + if err != nil { + return err + } + return btcstaking.VerifyTransactionSigWithOutputData( + msgTx, + stakingPkScript, + stakingAmount, + stakingScript, + pk, + *sig, + ) +} diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index e4f51cad6..7ced7b81a 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -13,16 +13,16 @@ import ( func (v *BTCValidator) ValidateBasic() error { // ensure fields are non-empty and well-formatted if v.BabylonPk == nil { - return fmt.Errorf("empty BabylonPk") + return fmt.Errorf("empty Babylon public key") } if v.BtcPk == nil { - return fmt.Errorf("empty BtcPk") + return fmt.Errorf("empty BTC public key") } if _, err := v.BtcPk.ToBTCPK(); err != nil { return fmt.Errorf("BtcPk is not correctly formatted: %w", err) } if v.Pop == nil { - return fmt.Errorf("empty Pop") + return fmt.Errorf("empty proof of possession") } // verify PoP @@ -38,29 +38,27 @@ func (v *BTCValidator) ValidateBasic() error { func (d *BTCDelegation) ValidateBasic() error { if d.BabylonPk == nil { - return fmt.Errorf("empty BabylonPk") + return fmt.Errorf("empty Babylon public key") } if d.BtcPk == nil { - return fmt.Errorf("empty BabylonPk") + return fmt.Errorf("empty BTC public key") } if d.Pop == nil { - return fmt.Errorf("empty Pop") + return fmt.Errorf("empty proof of possession") } if d.ValBtcPk == nil { - return fmt.Errorf("empty ValBtcPk") + return fmt.Errorf("empty Validator BTC public key") } if d.StakingTx == nil { - return fmt.Errorf("empty StakingTx") + return fmt.Errorf("empty staking tx") } if d.SlashingTx == nil { - return fmt.Errorf("empty SlashingTx") + return fmt.Errorf("empty slashing tx") } if d.DelegatorSig == nil { - return fmt.Errorf("empty DelegatorSig") + return fmt.Errorf("empty delegator signature") } - // TODO: validation rules - // ensure staking tx is correctly formatted if err := d.StakingTx.ValidateBasic(); err != nil { return err @@ -77,12 +75,18 @@ func (d *BTCDelegation) ValidateBasic() error { return nil } +// IsActivated returns whether a BTC delegation is activated or not +// a BTC delegation is activated when it receives a signature from jury +func (d *BTCDelegation) IsActivated() bool { + return d.JurySig != nil +} + func (p *ProofOfPossession) ValidateBasic() error { if len(p.BabylonSig) == 0 { - return fmt.Errorf("empty BabylonSig") + return fmt.Errorf("empty Babylon signature") } if p.BtcSig == nil { - return fmt.Errorf("empty BtcSig") + return fmt.Errorf("empty BTC signature") } if _, err := p.BtcSig.ToBTCSig(); err != nil { return fmt.Errorf("BtcSig is incorrectly formatted: %w", err) @@ -161,6 +165,7 @@ func (tx *StakingTx) GetStakingOutputInfo(net *chaincfg.Params) (*btcstaking.Sta return &btcstaking.StakingOutputInfo{ StakingScriptData: scriptData, + StakingPkScript: expectedPkScript, StakingAmount: btcutil.Amount(outValue), }, nil } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 67a35a50e..8793f3608 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -110,7 +110,7 @@ type BTCDelegation struct { // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. - SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. @@ -118,7 +118,7 @@ type BTCDelegation struct { // jury_sig is the signature signature on the slashing tx // by the jury (i.e., SK corresponding to jury_pk in params) // It will be a part of the witness for the staking tx output. - JurySig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,12,opt,name=jury_sig,json=jurySig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_sig,omitempty"` + JurySig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=jury_sig,json=jurySig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_sig,omitempty"` } func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } @@ -314,43 +314,43 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 576 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0x4d, 0x6b, 0xd4, 0x40, - 0x18, 0x6e, 0xb6, 0x5f, 0xbb, 0xef, 0xa6, 0x05, 0x07, 0x85, 0x50, 0x31, 0xbb, 0x2e, 0x28, 0x7b, - 0x4a, 0x6c, 0x6b, 0x2b, 0x7a, 0x50, 0x48, 0x3d, 0x28, 0x2a, 0x86, 0x24, 0x88, 0x78, 0x30, 0x4c, - 0xb2, 0x69, 0x76, 0x4c, 0x9a, 0x09, 0x99, 0xd9, 0x25, 0xf9, 0x07, 0x1e, 0xfd, 0x59, 0x1e, 0x7b, - 0xd4, 0x1e, 0x4a, 0x69, 0xff, 0x88, 0x64, 0x32, 0x59, 0x0a, 0x2a, 0x2a, 0x7b, 0xf3, 0xf6, 0xce, - 0xfb, 0xf1, 0xbc, 0xcf, 0x93, 0x79, 0x32, 0x70, 0x3f, 0xc0, 0x41, 0x95, 0xd2, 0xcc, 0x0c, 0x78, - 0xc8, 0x38, 0x4e, 0x48, 0x16, 0x9b, 0xf3, 0xdd, 0x6b, 0x27, 0x23, 0x2f, 0x28, 0xa7, 0xe8, 0x96, - 0xec, 0x33, 0xae, 0x55, 0xe6, 0xbb, 0x3b, 0x37, 0x63, 0x1a, 0x53, 0xd1, 0x61, 0xd6, 0x51, 0xd3, - 0xbc, 0x33, 0x0a, 0x29, 0x3b, 0xa1, 0xcc, 0x0c, 0x8b, 0x2a, 0xe7, 0xd4, 0x64, 0x51, 0x98, 0xef, - 0x1d, 0x1c, 0x26, 0xbb, 0x66, 0x12, 0x55, 0xac, 0xe9, 0x19, 0x7d, 0x57, 0x40, 0xb5, 0xbc, 0xa3, - 0x77, 0x38, 0x25, 0x13, 0xcc, 0x69, 0x81, 0x9e, 0x02, 0xc8, 0x1d, 0x7e, 0x9e, 0x68, 0xca, 0x50, - 0x19, 0xf7, 0xf7, 0x06, 0x46, 0x83, 0x64, 0x34, 0x48, 0xc6, 0x02, 0xc9, 0xb0, 0x67, 0xc1, 0xab, - 0xa8, 0x72, 0x7a, 0x72, 0xc4, 0x4e, 0xd0, 0x1b, 0xd8, 0x08, 0x78, 0x58, 0xcf, 0x76, 0x86, 0xca, - 0x58, 0xb5, 0x0e, 0xcf, 0xce, 0x07, 0x7b, 0x31, 0xe1, 0xd3, 0x59, 0x60, 0x84, 0xf4, 0xc4, 0x94, - 0x9d, 0xe1, 0x14, 0x93, 0xac, 0x3d, 0x98, 0xbc, 0xca, 0x23, 0x66, 0x58, 0x2f, 0xed, 0xfd, 0x87, - 0x0f, 0x24, 0xe4, 0x7a, 0xc0, 0x43, 0x3b, 0x41, 0x4f, 0x60, 0x35, 0xa7, 0xb9, 0xb6, 0x2a, 0x78, - 0x8c, 0x8d, 0x5f, 0xca, 0x37, 0xec, 0x82, 0xd2, 0xe3, 0xb7, 0xc7, 0x36, 0x65, 0x2c, 0x62, 0x8c, - 0xd0, 0xcc, 0xa9, 0x87, 0x46, 0x17, 0xeb, 0xb0, 0x65, 0x79, 0x47, 0xcf, 0xa3, 0x34, 0x8a, 0x31, - 0x27, 0x34, 0xfb, 0x8f, 0xc4, 0x21, 0x0f, 0x60, 0x8e, 0x53, 0x5f, 0xd2, 0x59, 0x5b, 0x8a, 0x4e, - 0x77, 0x8e, 0x53, 0x4b, 0x30, 0xba, 0x0b, 0x2a, 0xe3, 0xb8, 0xe0, 0xfe, 0x34, 0x22, 0xf1, 0x94, - 0x6b, 0xeb, 0x43, 0x65, 0xbc, 0xe6, 0xf4, 0x45, 0xee, 0x85, 0x48, 0xa1, 0x3b, 0x00, 0x51, 0x36, - 0x69, 0x1b, 0x36, 0x44, 0x43, 0x2f, 0xca, 0x26, 0xb2, 0x7c, 0x1b, 0x7a, 0x9c, 0x72, 0x9c, 0xfa, - 0x0c, 0x73, 0x6d, 0x53, 0x54, 0xbb, 0x22, 0xe1, 0x62, 0x8e, 0x9e, 0x01, 0x48, 0x65, 0x3e, 0x2f, - 0xb5, 0xae, 0xd0, 0x3d, 0xfc, 0x8d, 0x6e, 0xb7, 0x09, 0xbd, 0xd2, 0xe9, 0xb1, 0x36, 0x44, 0xef, - 0xa1, 0xcf, 0x52, 0xcc, 0xa6, 0x12, 0xa1, 0x27, 0x64, 0x3f, 0x3a, 0x3b, 0x1f, 0xec, 0xff, 0xa5, - 0x6c, 0xef, 0xc8, 0x95, 0xf3, 0x5e, 0xe9, 0x00, 0x5b, 0xc4, 0xe8, 0x23, 0x6c, 0x4d, 0x1a, 0xa3, - 0xd0, 0xc2, 0x67, 0x24, 0xd6, 0x40, 0x60, 0x3f, 0x3e, 0x3b, 0x1f, 0x1c, 0xfc, 0xcb, 0x27, 0x75, - 0x49, 0x9c, 0x61, 0x3e, 0x2b, 0x22, 0x47, 0x5d, 0xe0, 0xb9, 0x24, 0x46, 0x1e, 0x74, 0x3f, 0xcd, - 0x8a, 0x4a, 0x40, 0xab, 0xcb, 0x42, 0x6f, 0xd6, 0x50, 0x2e, 0x89, 0x47, 0x9f, 0x15, 0xb8, 0xf1, - 0x93, 0x41, 0xd0, 0x00, 0xfa, 0xad, 0xcd, 0xeb, 0x75, 0xb5, 0xcf, 0x55, 0xa7, 0x75, 0x7e, 0x4d, - 0xc6, 0x81, 0xcd, 0xda, 0x38, 0x75, 0xb1, 0xb3, 0x2c, 0x97, 0xfa, 0x8f, 0xa8, 0xa9, 0x58, 0xd0, - 0x5b, 0x5c, 0x19, 0xda, 0x86, 0x0e, 0x2f, 0xe5, 0xe2, 0x0e, 0x2f, 0xd1, 0x3d, 0xd8, 0x6e, 0x2f, - 0x9e, 0x85, 0x05, 0xc9, 0x79, 0xb3, 0xd7, 0xd9, 0x92, 0x59, 0x57, 0x24, 0xad, 0xd7, 0x5f, 0x2f, - 0x75, 0xe5, 0xf4, 0x52, 0x57, 0x2e, 0x2e, 0x75, 0xe5, 0xcb, 0x95, 0xbe, 0x72, 0x7a, 0xa5, 0xaf, - 0x7c, 0xbb, 0xd2, 0x57, 0x3e, 0xfc, 0xd1, 0xd6, 0xe5, 0xf5, 0xa7, 0x53, 0x30, 0x0d, 0x36, 0xc4, - 0x13, 0xb7, 0xff, 0x23, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x9a, 0x80, 0x0d, 0x5d, 0x05, 0x00, 0x00, + // 575 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xae, 0xd3, 0xbf, 0x64, 0x92, 0x56, 0xea, 0x0a, 0xa4, 0xa8, 0x08, 0x27, 0x44, 0x02, 0xe5, + 0x64, 0xd3, 0x94, 0x56, 0x82, 0x03, 0x48, 0x2e, 0x07, 0x10, 0x20, 0x2c, 0xdb, 0xe2, 0xc0, 0x01, + 0x6b, 0xed, 0xb8, 0xce, 0x62, 0xd7, 0x6b, 0x79, 0x37, 0x91, 0xfd, 0x06, 0x1c, 0x79, 0x1e, 0x9e, + 0x80, 0x63, 0x8f, 0xd0, 0x43, 0x85, 0xda, 0x17, 0x41, 0xbb, 0x5e, 0x47, 0x95, 0x00, 0x21, 0x94, + 0x1b, 0xb7, 0xf1, 0xcc, 0x37, 0xdf, 0x7c, 0xe3, 0xf9, 0xb4, 0xf0, 0x20, 0xc0, 0x41, 0x95, 0xd2, + 0xcc, 0x0c, 0x78, 0xc8, 0x38, 0x4e, 0x48, 0x16, 0x9b, 0x8b, 0x83, 0x1b, 0x5f, 0x46, 0x5e, 0x50, + 0x4e, 0xd1, 0x6d, 0x85, 0x33, 0x6e, 0x54, 0x16, 0x07, 0xfb, 0xb7, 0x62, 0x1a, 0x53, 0x89, 0x30, + 0x45, 0x54, 0x83, 0xf7, 0x47, 0x21, 0x65, 0x67, 0x94, 0x99, 0x61, 0x51, 0xe5, 0x9c, 0x9a, 0x2c, + 0x0a, 0xf3, 0xc9, 0xd1, 0x71, 0x72, 0x60, 0x26, 0x51, 0xc5, 0x6a, 0xcc, 0xe8, 0xbb, 0x06, 0x3d, + 0xcb, 0x3b, 0x79, 0x87, 0x53, 0x32, 0xc5, 0x9c, 0x16, 0xe8, 0x29, 0x80, 0x9a, 0xe1, 0xe7, 0x49, + 0x5f, 0x1b, 0x6a, 0xe3, 0xee, 0x64, 0x60, 0xd4, 0x4c, 0x46, 0xcd, 0x64, 0x2c, 0x99, 0x0c, 0x7b, + 0x1e, 0xbc, 0x8a, 0x2a, 0xa7, 0xa3, 0x5a, 0xec, 0x04, 0xbd, 0x81, 0xad, 0x80, 0x87, 0xa2, 0xb7, + 0x35, 0xd4, 0xc6, 0x3d, 0xeb, 0xf8, 0xe2, 0x72, 0x30, 0x89, 0x09, 0x9f, 0xcd, 0x03, 0x23, 0xa4, + 0x67, 0xa6, 0x42, 0x86, 0x33, 0x4c, 0xb2, 0xe6, 0xc3, 0xe4, 0x55, 0x1e, 0x31, 0xc3, 0x7a, 0x69, + 0x1f, 0x3e, 0x7a, 0xa8, 0x28, 0x37, 0x03, 0x1e, 0xda, 0x09, 0x7a, 0x02, 0xeb, 0x39, 0xcd, 0xfb, + 0xeb, 0x52, 0xc7, 0xd8, 0xf8, 0xed, 0xfa, 0x86, 0x5d, 0x50, 0x7a, 0xfa, 0xf6, 0xd4, 0xa6, 0x8c, + 0x45, 0x8c, 0x11, 0x9a, 0x39, 0xa2, 0x69, 0xf4, 0x65, 0x13, 0x76, 0x2c, 0xef, 0xe4, 0x79, 0x94, + 0x46, 0x31, 0xe6, 0x84, 0x66, 0xff, 0xd1, 0x72, 0xc8, 0x03, 0x58, 0xe0, 0xd4, 0x57, 0x72, 0x36, + 0x56, 0x92, 0xd3, 0x5e, 0xe0, 0xd4, 0x92, 0x8a, 0xee, 0x41, 0x8f, 0x71, 0x5c, 0x70, 0x7f, 0x16, + 0x91, 0x78, 0xc6, 0xfb, 0x9b, 0x43, 0x6d, 0xbc, 0xe1, 0x74, 0x65, 0xee, 0x85, 0x4c, 0xa1, 0xbb, + 0x00, 0x51, 0x36, 0x6d, 0x00, 0x5b, 0x12, 0xd0, 0x89, 0xb2, 0xa9, 0x2a, 0xdf, 0x81, 0x0e, 0xa7, + 0x1c, 0xa7, 0x3e, 0xc3, 0xbc, 0xbf, 0x2d, 0xab, 0x6d, 0x99, 0x70, 0x31, 0x47, 0xcf, 0x00, 0xd4, + 0x66, 0x3e, 0x2f, 0xfb, 0x6d, 0xb9, 0xf7, 0xf0, 0x0f, 0x7b, 0xbb, 0x75, 0xe8, 0x95, 0x4e, 0x87, + 0x35, 0x21, 0x9a, 0x40, 0x97, 0xa5, 0x98, 0xcd, 0x14, 0x43, 0x47, 0xae, 0xbd, 0x77, 0x71, 0x39, + 0x10, 0x87, 0x76, 0x55, 0xc5, 0x2b, 0x1d, 0x60, 0xcb, 0x18, 0x7d, 0x80, 0x9d, 0x69, 0x6d, 0x01, + 0x5a, 0xf8, 0x8c, 0xc4, 0x7d, 0x90, 0x5d, 0x8f, 0x2f, 0x2e, 0x07, 0x47, 0xff, 0xf2, 0xb3, 0x5c, + 0x12, 0x67, 0x98, 0xcf, 0x8b, 0xc8, 0xe9, 0x2d, 0xf9, 0x5c, 0x12, 0x23, 0x0f, 0xda, 0x1f, 0xe7, + 0x45, 0x25, 0xa9, 0xbb, 0xab, 0x52, 0x6f, 0x0b, 0x2a, 0x97, 0xc4, 0xa3, 0x4f, 0x1a, 0xec, 0xfd, + 0x72, 0x7a, 0x34, 0x80, 0x6e, 0x63, 0x60, 0x31, 0x4e, 0x38, 0xb8, 0xe7, 0x34, 0x9e, 0x16, 0x62, + 0x1c, 0xd8, 0x16, 0x96, 0x10, 0xc5, 0xd6, 0xaa, 0x5a, 0x84, 0xd7, 0x85, 0x14, 0x0b, 0x3a, 0xcb, + 0x63, 0xa0, 0x5d, 0x68, 0xf1, 0x52, 0x0d, 0x6e, 0xf1, 0x12, 0xdd, 0x87, 0xdd, 0xe6, 0xa4, 0x2c, + 0x2c, 0x48, 0xce, 0xeb, 0xb9, 0xce, 0x8e, 0xca, 0xba, 0x32, 0x69, 0xbd, 0xfe, 0x7a, 0xa5, 0x6b, + 0xe7, 0x57, 0xba, 0xf6, 0xe3, 0x4a, 0xd7, 0x3e, 0x5f, 0xeb, 0x6b, 0xe7, 0xd7, 0xfa, 0xda, 0xb7, + 0x6b, 0x7d, 0xed, 0xfd, 0x5f, 0x0d, 0x5b, 0xde, 0x7c, 0x14, 0xa5, 0xd2, 0x60, 0x4b, 0x3e, 0x5e, + 0x87, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x32, 0x65, 0xa2, 0x37, 0x05, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -442,7 +442,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x62 + dAtA[i] = 0x5a } if m.DelegatorSig != nil { { @@ -1197,7 +1197,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCSlashingTx + var v BTCSlashingTx m.SlashingTx = &v if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1238,7 +1238,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 12: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field JurySig", wireType) } diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index a2de41890..38af96f31 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -10,6 +10,7 @@ import ( func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgCreateBTCValidator{}, "btcstaking/MsgCreateBTCValidator", nil) cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) + cdc.RegisterConcrete(&MsgAddJurySig{}, "btcstaking/MsgAddJurySig", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) } @@ -19,6 +20,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgCreateBTCValidator{}, &MsgCreateBTCDelegation{}, + &MsgAddJurySig{}, &MsgUpdateParams{}, ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 9da24f2b3..cc9c3506d 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -13,6 +13,7 @@ var ( _ sdk.Msg = &MsgUpdateParams{} _ sdk.Msg = &MsgCreateBTCValidator{} _ sdk.Msg = &MsgCreateBTCDelegation{} + _ sdk.Msg = &MsgAddJurySig{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -44,13 +45,13 @@ func (m *MsgCreateBTCValidator) GetSigners() []sdk.AccAddress { func (m *MsgCreateBTCValidator) ValidateBasic() error { if m.BabylonPk == nil { - return fmt.Errorf("empty BabylonPk") + return fmt.Errorf("empty Babylon public key") } if m.BtcPk == nil { - return fmt.Errorf("empty BtcPk") + return fmt.Errorf("empty BTC public key") } if m.Pop == nil { - return fmt.Errorf("empty Pop") + return fmt.Errorf("empty proof of possession") } if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err @@ -71,22 +72,22 @@ func (m *MsgCreateBTCDelegation) GetSigners() []sdk.AccAddress { func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.BabylonPk == nil { - return fmt.Errorf("empty BabylonPk") + return fmt.Errorf("empty Babylon public key") } if m.Pop == nil { - return fmt.Errorf("empty Pop") + return fmt.Errorf("empty proof of possession") } if m.StakingTx == nil { - return fmt.Errorf("empty StakingTx") + return fmt.Errorf("empty staking tx") } if m.StakingTxInfo == nil { - return fmt.Errorf("empty StakingTxInfo") + return fmt.Errorf("empty staking tx info") } if m.SlashingTx == nil { - return fmt.Errorf("empty SlashingTx") + return fmt.Errorf("empty slashing tx") } if m.DelegatorSig == nil { - return fmt.Errorf("empty DelegatorSig") + return fmt.Errorf("empty delegator signature") } if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err @@ -97,8 +98,6 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { return err } - // TODO: verify delegator_sig - // verify PoP if err := m.Pop.ValidateBasic(); err != nil { return err @@ -114,3 +113,25 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { return nil } + +func (m *MsgAddJurySig) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgAddJurySig) ValidateBasic() error { + if m.ValPk == nil { + return fmt.Errorf("empty BTC validator public key") + } + if m.DelPk == nil { + return fmt.Errorf("empty BTC delegation public key") + } + if m.Sig == nil { + return fmt.Errorf("empty jury signature") + } + + return nil +} diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index ddec07b99..316750c5c 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -149,7 +149,7 @@ type MsgCreateBTCDelegation struct { StakingTxInfo *types.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. - SlashingTx *github_com_babylonchain_babylon_types.BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=github.com/babylonchain/babylon/types.BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from jury and validator in @@ -262,6 +262,97 @@ func (m *MsgCreateBTCDelegationResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCreateBTCDelegationResponse proto.InternalMessageInfo +// MsgAddJurySig is the message for handling a signature from jury +type MsgAddJurySig struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // sig is the signature of the jury + // the signature follows encoding in BIP-340 spec + Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` +} + +func (m *MsgAddJurySig) Reset() { *m = MsgAddJurySig{} } +func (m *MsgAddJurySig) String() string { return proto.CompactTextString(m) } +func (*MsgAddJurySig) ProtoMessage() {} +func (*MsgAddJurySig) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{4} +} +func (m *MsgAddJurySig) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddJurySig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddJurySig.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddJurySig) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddJurySig.Merge(m, src) +} +func (m *MsgAddJurySig) XXX_Size() int { + return m.Size() +} +func (m *MsgAddJurySig) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddJurySig.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddJurySig proto.InternalMessageInfo + +func (m *MsgAddJurySig) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +// MsgAddJurySigResponse is the response for MsgAddJurySig +type MsgAddJurySigResponse struct { +} + +func (m *MsgAddJurySigResponse) Reset() { *m = MsgAddJurySigResponse{} } +func (m *MsgAddJurySigResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddJurySigResponse) ProtoMessage() {} +func (*MsgAddJurySigResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{5} +} +func (m *MsgAddJurySigResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddJurySigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddJurySigResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddJurySigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddJurySigResponse.Merge(m, src) +} +func (m *MsgAddJurySigResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAddJurySigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddJurySigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddJurySigResponse proto.InternalMessageInfo + // MsgUpdateParams defines a message for updating btcstaking module parameters. type MsgUpdateParams struct { // authority is the address of the governance account. @@ -279,7 +370,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{4} + return fileDescriptor_4baddb53e97f38f2, []int{6} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -330,7 +421,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{5} + return fileDescriptor_4baddb53e97f38f2, []int{7} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -364,6 +455,8 @@ func init() { proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") proto.RegisterType((*MsgCreateBTCDelegation)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegation") proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") + proto.RegisterType((*MsgAddJurySig)(nil), "babylon.btcstaking.v1.MsgAddJurySig") + proto.RegisterType((*MsgAddJurySigResponse)(nil), "babylon.btcstaking.v1.MsgAddJurySigResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") } @@ -371,52 +464,57 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 711 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xcd, 0x6e, 0xd3, 0x4a, - 0x14, 0xc7, 0xe3, 0xa6, 0xcd, 0x55, 0xa6, 0xed, 0xad, 0x34, 0xb7, 0x1f, 0x69, 0xa4, 0x3a, 0x51, - 0x16, 0x55, 0xee, 0x55, 0x6b, 0xdf, 0xa4, 0x1f, 0x88, 0x22, 0x81, 0x48, 0xd9, 0x54, 0x10, 0x11, - 0x9c, 0x80, 0x10, 0x0b, 0xa2, 0xb1, 0x33, 0x99, 0x8c, 0x9c, 0x78, 0x2c, 0xcf, 0xa4, 0x4a, 0xc4, - 0x8e, 0x27, 0x60, 0xc5, 0x73, 0xb0, 0xe0, 0x21, 0xba, 0xac, 0x58, 0xa1, 0x2e, 0x22, 0xd4, 0x22, - 0xf1, 0x0e, 0x5d, 0xa1, 0xd8, 0xe3, 0xc4, 0x20, 0x47, 0xb4, 0x48, 0xec, 0xe6, 0xf8, 0xfc, 0xce, - 0x7f, 0xce, 0x99, 0x73, 0x8e, 0x81, 0x6a, 0x22, 0x73, 0xd8, 0x65, 0x8e, 0x6e, 0x0a, 0x8b, 0x0b, - 0x64, 0x53, 0x87, 0xe8, 0xa7, 0x25, 0x5d, 0x0c, 0x34, 0xd7, 0x63, 0x82, 0xc1, 0x35, 0xe9, 0xd7, - 0xa6, 0x7e, 0xed, 0xb4, 0x94, 0x5d, 0x25, 0x8c, 0x30, 0x9f, 0xd0, 0xc7, 0xa7, 0x00, 0xce, 0x6e, - 0x5a, 0x8c, 0xf7, 0x18, 0x6f, 0x06, 0x8e, 0xc0, 0x90, 0xae, 0x8d, 0xc0, 0xd2, 0x7b, 0xdc, 0xd7, - 0xef, 0x71, 0x22, 0x1d, 0x05, 0xe9, 0xb0, 0xbc, 0xa1, 0x2b, 0x98, 0xce, 0xb1, 0xe5, 0x96, 0x0f, - 0x0e, 0xed, 0x92, 0x6e, 0xe3, 0x61, 0x18, 0x5c, 0x88, 0x4f, 0xd2, 0x45, 0x1e, 0xea, 0x85, 0xcc, - 0x76, 0x3c, 0x13, 0x49, 0x3b, 0xe0, 0x76, 0x22, 0x9c, 0xd5, 0xc1, 0x96, 0xed, 0x32, 0xea, 0x08, - 0x89, 0x4e, 0x3f, 0x04, 0x74, 0xe1, 0x5a, 0x01, 0x6b, 0x55, 0x4e, 0x8e, 0x3d, 0x8c, 0x04, 0xae, - 0x34, 0x8e, 0x5f, 0xa0, 0x2e, 0x6d, 0x21, 0xc1, 0x3c, 0xb8, 0x0e, 0x52, 0x9c, 0x12, 0x07, 0x7b, - 0x19, 0x25, 0xaf, 0x14, 0xd3, 0x86, 0xb4, 0xe0, 0x7d, 0x00, 0xe4, 0x0d, 0x4d, 0xd7, 0xce, 0xcc, - 0xe5, 0x95, 0xe2, 0x62, 0x39, 0xa7, 0xc9, 0xb7, 0x08, 0x8a, 0xd4, 0x26, 0x45, 0x6a, 0xb5, 0xbe, - 0xf9, 0x18, 0x0f, 0x8d, 0xb4, 0x0c, 0xa9, 0xd9, 0xb0, 0x0a, 0x52, 0xa6, 0xb0, 0xc6, 0xb1, 0xc9, - 0xbc, 0x52, 0x5c, 0xaa, 0x1c, 0x5e, 0x8c, 0x72, 0x65, 0x42, 0x45, 0xa7, 0x6f, 0x6a, 0x16, 0xeb, - 0xe9, 0x92, 0xb4, 0x3a, 0x88, 0x3a, 0xa1, 0xa1, 0x8b, 0xa1, 0x8b, 0xb9, 0x56, 0x39, 0xa9, 0xed, - 0xed, 0xff, 0x2f, 0x25, 0x17, 0x4c, 0x61, 0xd5, 0x6c, 0x78, 0x04, 0x92, 0x2e, 0x73, 0x33, 0xf3, - 0x7e, 0x1e, 0x45, 0x2d, 0xb6, 0x9b, 0x5a, 0xcd, 0x63, 0xac, 0xfd, 0xb4, 0x5d, 0x63, 0x9c, 0x63, - 0xce, 0x29, 0x73, 0x8c, 0x71, 0x50, 0x21, 0x07, 0xb6, 0x62, 0x6b, 0x37, 0x30, 0x77, 0x99, 0xc3, - 0x71, 0xe1, 0x3a, 0x09, 0xd6, 0xa3, 0xc4, 0x23, 0xdc, 0xc5, 0x04, 0x09, 0xca, 0x9c, 0x3f, 0xf6, - 0x3c, 0xb2, 0x9e, 0xe4, 0x6f, 0xd4, 0x03, 0x1f, 0x00, 0x20, 0xa1, 0xa6, 0x18, 0xc8, 0x27, 0xc9, - 0xcf, 0x90, 0xa8, 0x07, 0xc7, 0xc6, 0xc0, 0x48, 0xf3, 0xf0, 0x08, 0x9f, 0x81, 0x95, 0xa9, 0x40, - 0x93, 0x3a, 0x6d, 0x96, 0x59, 0xf0, 0x55, 0xfe, 0x8d, 0xaa, 0x44, 0x86, 0xe8, 0xb4, 0xa4, 0x35, - 0x3c, 0xe4, 0x70, 0x64, 0x8d, 0x1f, 0xe5, 0xc4, 0x69, 0x33, 0x63, 0x79, 0x22, 0x37, 0x36, 0xe1, - 0x4b, 0xb0, 0xc8, 0xbb, 0x88, 0x77, 0x64, 0x52, 0x29, 0xbf, 0xe7, 0x77, 0x2e, 0x46, 0xb9, 0xbd, - 0x1b, 0xf6, 0xbc, 0x71, 0x5c, 0x97, 0xf1, 0x8d, 0x81, 0x01, 0xf8, 0xe4, 0x0c, 0x5f, 0x83, 0xe5, - 0x56, 0xd0, 0x0f, 0xe6, 0x35, 0x39, 0x25, 0x99, 0xbf, 0x7c, 0xed, 0xbb, 0x17, 0xa3, 0xdc, 0xc1, - 0x6d, 0xe6, 0xa9, 0x4e, 0x89, 0x83, 0x44, 0xdf, 0xc3, 0xc6, 0xd2, 0x44, 0xaf, 0x4e, 0x49, 0x21, - 0x0f, 0xd4, 0xf8, 0xde, 0x4f, 0xc6, 0xe3, 0xbd, 0x02, 0x56, 0xaa, 0x9c, 0x3c, 0x77, 0x5b, 0x48, - 0xe0, 0x9a, 0xbf, 0xac, 0xf0, 0x10, 0xa4, 0x51, 0x5f, 0x74, 0x98, 0x47, 0xc5, 0x30, 0x18, 0x8d, - 0x4a, 0xe6, 0xd3, 0xc7, 0xdd, 0x55, 0x39, 0x01, 0x0f, 0x5b, 0x2d, 0x0f, 0x73, 0x5e, 0x17, 0x1e, - 0x75, 0x88, 0x31, 0x45, 0xe1, 0x3d, 0x90, 0x0a, 0xd6, 0x5d, 0xce, 0xcc, 0xd6, 0xac, 0xd6, 0xfb, - 0x50, 0x65, 0xfe, 0x6c, 0x94, 0x4b, 0x18, 0x32, 0xe4, 0xe8, 0xef, 0xb7, 0xdf, 0x3e, 0xfc, 0x37, - 0x15, 0x2b, 0x6c, 0x82, 0x8d, 0x9f, 0xf2, 0x0a, 0x73, 0x2e, 0x7f, 0x9d, 0x03, 0xc9, 0x2a, 0x27, - 0x70, 0x00, 0x60, 0xcc, 0xd2, 0xef, 0xcc, 0xb8, 0x35, 0x76, 0x4d, 0xb2, 0xfb, 0xb7, 0xa1, 0xc3, - 0x0c, 0xe0, 0x1b, 0xf0, 0x4f, 0xdc, 0x42, 0xed, 0xde, 0x40, 0x6c, 0x8a, 0x67, 0x0f, 0x6e, 0x85, - 0x4f, 0x2e, 0x6f, 0x83, 0xa5, 0x1f, 0xda, 0xb5, 0x3d, 0x5b, 0x26, 0xca, 0x65, 0xb5, 0x9b, 0x71, - 0xe1, 0x3d, 0x95, 0x27, 0x67, 0x97, 0xaa, 0x72, 0x7e, 0xa9, 0x2a, 0x5f, 0x2e, 0x55, 0xe5, 0xdd, - 0x95, 0x9a, 0x38, 0xbf, 0x52, 0x13, 0x9f, 0xaf, 0xd4, 0xc4, 0xab, 0x5f, 0xfe, 0xeb, 0x06, 0xd1, - 0x3f, 0xbc, 0x3f, 0xa8, 0x66, 0xca, 0xff, 0x59, 0xef, 0x7d, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x08, - 0x3f, 0xe8, 0xd7, 0xcd, 0x06, 0x00, 0x00, + // 789 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0x4d, 0x6f, 0xe3, 0x44, + 0x18, 0x8e, 0x9b, 0x36, 0x28, 0xd3, 0x86, 0x0a, 0xd3, 0x8f, 0x34, 0x52, 0x9d, 0xc8, 0x42, 0x55, + 0x40, 0xad, 0x4d, 0xd2, 0x0f, 0x89, 0x22, 0x81, 0x9a, 0x72, 0x29, 0x25, 0x22, 0x38, 0x81, 0x03, + 0x07, 0xc2, 0xd8, 0x9e, 0x4c, 0xac, 0x24, 0x1e, 0xcb, 0x33, 0x89, 0x12, 0x71, 0xe3, 0x17, 0x20, + 0x0e, 0xfc, 0x0e, 0x0e, 0xfc, 0x06, 0xd4, 0x63, 0xb5, 0xda, 0xc3, 0xaa, 0x87, 0x68, 0xd5, 0x1e, + 0xf6, 0x3f, 0xec, 0x69, 0x65, 0x7b, 0xfc, 0xb1, 0x2b, 0x47, 0xdb, 0x6e, 0x77, 0x6f, 0x33, 0x7e, + 0x9f, 0xf7, 0x99, 0xf7, 0x7d, 0xde, 0x0f, 0x03, 0x49, 0x87, 0xfa, 0x6c, 0x48, 0x6c, 0x55, 0x67, + 0x06, 0x65, 0x70, 0x60, 0xd9, 0x58, 0x9d, 0xd4, 0x54, 0x36, 0x55, 0x1c, 0x97, 0x30, 0x22, 0x6e, + 0x72, 0xbb, 0x12, 0xdb, 0x95, 0x49, 0xad, 0xb4, 0x81, 0x09, 0x26, 0x3e, 0x42, 0xf5, 0x4e, 0x01, + 0xb8, 0xb4, 0x63, 0x10, 0x3a, 0x22, 0xb4, 0x1b, 0x18, 0x82, 0x0b, 0x37, 0x6d, 0x07, 0x37, 0x75, + 0x44, 0x7d, 0xfe, 0x11, 0xc5, 0xdc, 0x20, 0x73, 0x83, 0xe1, 0xce, 0x1c, 0x46, 0x54, 0x8a, 0x0c, + 0xa7, 0x7e, 0x7c, 0x32, 0xa8, 0xa9, 0x03, 0x34, 0x0b, 0x9d, 0xe5, 0xf4, 0x20, 0x1d, 0xe8, 0xc2, + 0x51, 0x88, 0xd9, 0x4b, 0xc7, 0x24, 0xc2, 0x0e, 0x70, 0xfb, 0x09, 0x9c, 0xd1, 0x47, 0xc6, 0xc0, + 0x21, 0x96, 0xcd, 0x38, 0x34, 0xfe, 0x10, 0xa0, 0xe5, 0x97, 0x02, 0xd8, 0x6c, 0x52, 0x7c, 0xee, + 0x22, 0xc8, 0x50, 0xa3, 0x73, 0xfe, 0x0b, 0x1c, 0x5a, 0x26, 0x64, 0xc4, 0x15, 0xb7, 0x40, 0x8e, + 0x5a, 0xd8, 0x46, 0x6e, 0x51, 0xa8, 0x08, 0xd5, 0xbc, 0xc6, 0x6f, 0xe2, 0x37, 0x00, 0xf0, 0x17, + 0xba, 0xce, 0xa0, 0xb8, 0x54, 0x11, 0xaa, 0xab, 0xf5, 0xb2, 0xc2, 0xb5, 0x08, 0x92, 0x54, 0xa2, + 0x24, 0x95, 0xd6, 0x58, 0xbf, 0x44, 0x33, 0x2d, 0xcf, 0x5d, 0x5a, 0x03, 0xb1, 0x09, 0x72, 0x3a, + 0x33, 0x3c, 0xdf, 0x6c, 0x45, 0xa8, 0xae, 0x35, 0x4e, 0x6e, 0xe6, 0xe5, 0x3a, 0xb6, 0x58, 0x7f, + 0xac, 0x2b, 0x06, 0x19, 0xa9, 0x1c, 0x69, 0xf4, 0xa1, 0x65, 0x87, 0x17, 0x95, 0xcd, 0x1c, 0x44, + 0x95, 0xc6, 0x45, 0xeb, 0xf0, 0xe8, 0x4b, 0x4e, 0xb9, 0xa2, 0x33, 0xa3, 0x35, 0x10, 0x4f, 0x41, + 0xd6, 0x21, 0x4e, 0x71, 0xd9, 0x8f, 0xa3, 0xaa, 0xa4, 0x56, 0x53, 0x69, 0xb9, 0x84, 0xf4, 0x7e, + 0xec, 0xb5, 0x08, 0xa5, 0x88, 0x52, 0x8b, 0xd8, 0x9a, 0xe7, 0x24, 0x97, 0xc1, 0x6e, 0x6a, 0xee, + 0x1a, 0xa2, 0x0e, 0xb1, 0x29, 0x92, 0x9f, 0x66, 0xc1, 0x56, 0x12, 0xf1, 0x1d, 0x1a, 0x22, 0x0c, + 0x99, 0x45, 0xec, 0x0f, 0x26, 0x0f, 0xcf, 0x27, 0xfb, 0x0e, 0xf9, 0x88, 0xdf, 0x02, 0xc0, 0x41, + 0x5d, 0x36, 0xe5, 0x92, 0x54, 0x16, 0x50, 0xb4, 0x83, 0x63, 0x67, 0xaa, 0xe5, 0x69, 0x78, 0x14, + 0x7f, 0x02, 0xeb, 0x31, 0x41, 0xd7, 0xb2, 0x7b, 0xa4, 0xb8, 0xe2, 0xb3, 0x7c, 0x9e, 0x64, 0x49, + 0x34, 0xd1, 0xa4, 0xa6, 0x74, 0x5c, 0x68, 0x53, 0x68, 0x78, 0xa2, 0x5c, 0xd8, 0x3d, 0xa2, 0x15, + 0x22, 0x3a, 0xef, 0x2a, 0xd6, 0xc1, 0x2a, 0x1d, 0x42, 0xda, 0xe7, 0x41, 0xe5, 0xfc, 0x9a, 0x7f, + 0x72, 0x33, 0x2f, 0x17, 0x1a, 0x9d, 0xf3, 0x36, 0xb7, 0x74, 0xa6, 0x1a, 0xa0, 0xd1, 0x59, 0xfc, + 0x0d, 0x14, 0xcc, 0x40, 0x69, 0xe2, 0x76, 0xa9, 0x85, 0x8b, 0x1f, 0xf9, 0x5e, 0x5f, 0xdd, 0xcc, + 0xcb, 0xc7, 0x0f, 0xe9, 0x94, 0xb6, 0x85, 0x6d, 0xc8, 0xc6, 0x2e, 0xd2, 0xd6, 0x22, 0xbe, 0xb6, + 0x85, 0xe5, 0x0a, 0x90, 0xd2, 0xab, 0x1a, 0x15, 0xfe, 0xef, 0x25, 0x50, 0x68, 0x52, 0x7c, 0x66, + 0x9a, 0xdf, 0x8f, 0xdd, 0x59, 0xdb, 0xc2, 0x0b, 0xeb, 0xdd, 0x04, 0xb9, 0x09, 0x1c, 0x86, 0xb5, + 0x7e, 0x44, 0x3b, 0x4f, 0xe0, 0x30, 0x98, 0x0e, 0x13, 0x0d, 0xdf, 0xc3, 0x74, 0x98, 0xc8, 0xa3, + 0xbb, 0x04, 0x59, 0x4f, 0xbf, 0xe5, 0xc7, 0xea, 0xe7, 0xb1, 0xc8, 0xdb, 0xfe, 0xaa, 0x88, 0x35, + 0x89, 0xd4, 0xfa, 0x47, 0x00, 0xeb, 0x4d, 0x8a, 0x7f, 0x76, 0x4c, 0xc8, 0x50, 0xcb, 0x5f, 0x5a, + 0xe2, 0x09, 0xc8, 0xc3, 0x31, 0xeb, 0x13, 0xd7, 0x62, 0xb3, 0x40, 0xb2, 0x46, 0xf1, 0xc9, 0x7f, + 0x07, 0x1b, 0x7c, 0x12, 0xce, 0x4c, 0xd3, 0x45, 0x94, 0xb6, 0x99, 0x6b, 0xd9, 0x58, 0x8b, 0xa1, + 0xe2, 0xd7, 0x20, 0x17, 0xac, 0x3d, 0x3e, 0x3b, 0xbb, 0x8b, 0x46, 0xc0, 0x07, 0x35, 0x96, 0xaf, + 0xe6, 0xe5, 0x8c, 0xc6, 0x5d, 0x4e, 0x3f, 0xfe, 0xf3, 0xc5, 0xbf, 0x5f, 0xc4, 0x64, 0xf2, 0x0e, + 0xd8, 0x7e, 0x23, 0xae, 0x30, 0xe6, 0xfa, 0xff, 0x59, 0x90, 0x6d, 0x52, 0x2c, 0x4e, 0x81, 0x98, + 0xb2, 0xfc, 0xf6, 0x17, 0xbc, 0x9a, 0xba, 0x2e, 0x4a, 0x47, 0x0f, 0x41, 0x87, 0x11, 0x88, 0x7f, + 0x80, 0x4f, 0xd3, 0x16, 0xcb, 0xc1, 0x3d, 0xc8, 0x62, 0x78, 0xe9, 0xf8, 0x41, 0xf0, 0xe8, 0xf1, + 0xdf, 0x01, 0x48, 0x34, 0xf7, 0x67, 0x8b, 0x49, 0x62, 0x54, 0x69, 0xff, 0x3e, 0xa8, 0xe8, 0x85, + 0x1e, 0x58, 0x7b, 0xad, 0x21, 0xf6, 0x16, 0x7b, 0x27, 0x71, 0x25, 0xe5, 0x7e, 0xb8, 0xf0, 0x9d, + 0xc6, 0x0f, 0x57, 0xb7, 0x92, 0x70, 0x7d, 0x2b, 0x09, 0xcf, 0x6f, 0x25, 0xe1, 0xaf, 0x3b, 0x29, + 0x73, 0x7d, 0x27, 0x65, 0x9e, 0xdd, 0x49, 0x99, 0x5f, 0xdf, 0x3a, 0x37, 0xd3, 0xe4, 0xbf, 0xd4, + 0x6f, 0x7c, 0x3d, 0xe7, 0xff, 0x16, 0x0f, 0x5f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1d, 0xcd, 0x67, + 0xc1, 0x37, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -435,6 +533,9 @@ type MsgClient interface { CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) + // AddJurySig handles a signature from jury + AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) + // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -465,6 +566,15 @@ func (c *msgClient) CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDel return out, nil } +func (c *msgClient) AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) { + out := new(MsgAddJurySigResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddJurySig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) @@ -480,6 +590,9 @@ type MsgServer interface { CreateBTCValidator(context.Context, *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) + // AddJurySig handles a signature from jury + AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) + // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } @@ -494,6 +607,9 @@ func (*UnimplementedMsgServer) CreateBTCValidator(ctx context.Context, req *MsgC func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateBTCDelegation not implemented") } +func (*UnimplementedMsgServer) AddJurySig(ctx context.Context, req *MsgAddJurySig) (*MsgAddJurySigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddJurySig not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -538,6 +654,24 @@ func _Msg_CreateBTCDelegation_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Msg_AddJurySig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddJurySig) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AddJurySig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/AddJurySig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AddJurySig(ctx, req.(*MsgAddJurySig)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -568,6 +702,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "CreateBTCDelegation", Handler: _Msg_CreateBTCDelegation_Handler, }, + { + MethodName: "AddJurySig", + Handler: _Msg_AddJurySig_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -791,6 +929,95 @@ func (m *MsgCreateBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } +func (m *MsgAddJurySig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAddJurySig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddJurySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sig != nil { + { + size := m.Sig.Size() + i -= size + if _, err := m.Sig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.DelPk != nil { + { + size := m.DelPk.Size() + i -= size + if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ValPk != nil { + { + size := m.ValPk.Size() + i -= size + if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAddJurySigResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAddJurySigResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddJurySigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -945,6 +1172,40 @@ func (m *MsgCreateBTCDelegationResponse) Size() (n int) { return n } +func (m *MsgAddJurySig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ValPk != nil { + l = m.ValPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.DelPk != nil { + l = m.DelPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Sig != nil { + l = m.Sig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAddJurySigResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgUpdateParams) Size() (n int) { if m == nil { return 0 @@ -1448,7 +1709,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCSlashingTx + var v BTCSlashingTx m.SlashingTx = &v if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1560,6 +1821,243 @@ func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAddJurySig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddJurySig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValPk = &v + if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.DelPk = &v + if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.Sig = &v + if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAddJurySigResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddJurySigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From ab21136133ad36011f941d39db62994cb9ba4949 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 27 Jun 2023 15:11:26 +1000 Subject: [PATCH 018/202] BTC staking: events for BTC validator/delegation programs (#11) --- proto/babylon/btcstaking/v1/events.proto | 18 + x/btcstaking/keeper/msg_server.go | 18 + x/btcstaking/types/events.pb.go | 692 +++++++++++++++++++++++ 3 files changed, 728 insertions(+) create mode 100644 proto/babylon/btcstaking/v1/events.proto create mode 100644 x/btcstaking/types/events.pb.go diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto new file mode 100644 index 000000000..80dc48df8 --- /dev/null +++ b/proto/babylon/btcstaking/v1/events.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "babylon/btcstaking/v1/btcstaking.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// EventNewBTCValidator is the event emitted when a BTC validator is created +message EventNewBTCValidator { BTCValidator btc_val = 1; } + +// EventNewBTCDelegation is the event emitted when a BTC delegation is created +// NOTE: the BTC delegation is not active thus does not have voting power yet +// only after it receives a jury signature it becomes activated and has voting power +message EventNewBTCDelegation { BTCDelegation btc_del = 1; } + +// EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation +// such that the BTC delegation starts to have voting power in its timelock period +message EventActivateBTCDelegation { BTCDelegation btc_del = 1; } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index a95a2e7cf..05178ca16 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -56,6 +56,11 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea } ms.setBTCValidator(ctx, &btcVal) + // notify subscriber + if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCValidator{BtcVal: &btcVal}); err != nil { + return nil, err + } + return &types.MsgCreateBTCValidatorResponse{}, nil } @@ -134,6 +139,9 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } // all good, construct BTCDelegation and insert BTC delegation + // NOTE: the BTC delegation does not have voting power yet. It will + // have voting power only when 1) its corresponding staking tx is k-deep, + // and 2) it receives a jury signature newBTCDel := &types.BTCDelegation{ BabylonPk: req.BabylonPk, BtcPk: delBTCPK, @@ -149,6 +157,11 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } ms.setBTCDelegation(ctx, newBTCDel) + // notify subscriber + if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { + return nil, err + } + return &types.MsgCreateBTCDelegationResponse{}, nil } @@ -193,5 +206,10 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) btcDel.JurySig = req.Sig ms.setBTCDelegation(ctx, btcDel) + // notify subscriber + if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { + return nil, err + } + return &types.MsgAddJurySigResponse{}, nil } diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go new file mode 100644 index 000000000..d862e1bcf --- /dev/null +++ b/x/btcstaking/types/events.pb.go @@ -0,0 +1,692 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/events.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// EventNewBTCValidator is the event emitted when a BTC validator is created +type EventNewBTCValidator struct { + BtcVal *BTCValidator `protobuf:"bytes,1,opt,name=btc_val,json=btcVal,proto3" json:"btc_val,omitempty"` +} + +func (m *EventNewBTCValidator) Reset() { *m = EventNewBTCValidator{} } +func (m *EventNewBTCValidator) String() string { return proto.CompactTextString(m) } +func (*EventNewBTCValidator) ProtoMessage() {} +func (*EventNewBTCValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{0} +} +func (m *EventNewBTCValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventNewBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventNewBTCValidator.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventNewBTCValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventNewBTCValidator.Merge(m, src) +} +func (m *EventNewBTCValidator) XXX_Size() int { + return m.Size() +} +func (m *EventNewBTCValidator) XXX_DiscardUnknown() { + xxx_messageInfo_EventNewBTCValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_EventNewBTCValidator proto.InternalMessageInfo + +func (m *EventNewBTCValidator) GetBtcVal() *BTCValidator { + if m != nil { + return m.BtcVal + } + return nil +} + +// EventNewBTCDelegation is the event emitted when a BTC delegation is created +type EventNewBTCDelegation struct { + BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` +} + +func (m *EventNewBTCDelegation) Reset() { *m = EventNewBTCDelegation{} } +func (m *EventNewBTCDelegation) String() string { return proto.CompactTextString(m) } +func (*EventNewBTCDelegation) ProtoMessage() {} +func (*EventNewBTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{1} +} +func (m *EventNewBTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventNewBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventNewBTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventNewBTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventNewBTCDelegation.Merge(m, src) +} +func (m *EventNewBTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *EventNewBTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_EventNewBTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_EventNewBTCDelegation proto.InternalMessageInfo + +func (m *EventNewBTCDelegation) GetBtcDel() *BTCDelegation { + if m != nil { + return m.BtcDel + } + return nil +} + +// EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation +type EventActivateBTCDelegation struct { + BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` +} + +func (m *EventActivateBTCDelegation) Reset() { *m = EventActivateBTCDelegation{} } +func (m *EventActivateBTCDelegation) String() string { return proto.CompactTextString(m) } +func (*EventActivateBTCDelegation) ProtoMessage() {} +func (*EventActivateBTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{2} +} +func (m *EventActivateBTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventActivateBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventActivateBTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventActivateBTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventActivateBTCDelegation.Merge(m, src) +} +func (m *EventActivateBTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *EventActivateBTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_EventActivateBTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_EventActivateBTCDelegation proto.InternalMessageInfo + +func (m *EventActivateBTCDelegation) GetBtcDel() *BTCDelegation { + if m != nil { + return m.BtcDel + } + return nil +} + +func init() { + proto.RegisterType((*EventNewBTCValidator)(nil), "babylon.btcstaking.v1.EventNewBTCValidator") + proto.RegisterType((*EventNewBTCDelegation)(nil), "babylon.btcstaking.v1.EventNewBTCDelegation") + proto.RegisterType((*EventActivateBTCDelegation)(nil), "babylon.btcstaking.v1.EventActivateBTCDelegation") +} + +func init() { + proto.RegisterFile("babylon/btcstaking/v1/events.proto", fileDescriptor_74118427820fff75) +} + +var fileDescriptor_74118427820fff75 = []byte{ + // 250 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, + 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x52, 0xc3, 0xae, 0x15, 0x49, 0x11, 0x58, + 0xbb, 0x52, 0x08, 0x97, 0x88, 0x2b, 0xc8, 0x38, 0xbf, 0xd4, 0x72, 0xa7, 0x10, 0xe7, 0xb0, 0xc4, + 0x9c, 0xcc, 0x94, 0xc4, 0x92, 0xfc, 0x22, 0x21, 0x1b, 0x2e, 0xf6, 0xa4, 0x92, 0xe4, 0xf8, 0xb2, + 0xc4, 0x1c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x65, 0x3d, 0xac, 0x16, 0xe9, 0x21, 0xeb, + 0x0a, 0x62, 0x4b, 0x2a, 0x49, 0x0e, 0x4b, 0xcc, 0x51, 0x0a, 0xe3, 0x12, 0x45, 0x32, 0xd5, 0x25, + 0x35, 0x27, 0x35, 0x3d, 0xb1, 0x24, 0x33, 0x3f, 0x4f, 0xc8, 0x16, 0x62, 0x6c, 0x4a, 0x2a, 0xcc, + 0x58, 0x15, 0xdc, 0xc6, 0x22, 0xb4, 0x81, 0xcd, 0x75, 0x49, 0xcd, 0x51, 0x8a, 0xe6, 0x92, 0x02, + 0x9b, 0xeb, 0x98, 0x5c, 0x92, 0x59, 0x96, 0x58, 0x92, 0x4a, 0x4d, 0xc3, 0x9d, 0x7c, 0x4e, 0x3c, + 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, + 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x28, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, + 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x62, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, + 0x1c, 0xcc, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xf0, 0x35, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x88, 0x0d, 0x1d, 0x71, 0xc4, 0x01, 0x00, 0x00, +} + +func (m *EventNewBTCValidator) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventNewBTCValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventNewBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcVal != nil { + { + size, err := m.BtcVal.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventNewBTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventNewBTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventNewBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcDel != nil { + { + size, err := m.BtcDel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventActivateBTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventActivateBTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventActivateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcDel != nil { + { + size, err := m.BtcDel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { + offset -= sovEvents(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EventNewBTCValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcVal != nil { + l = m.BtcVal.Size() + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventNewBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcDel != nil { + l = m.BtcDel.Size() + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventActivateBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcDel != nil { + l = m.BtcDel.Size() + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func sovEvents(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvents(x uint64) (n int) { + return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EventNewBTCValidator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventNewBTCValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventNewBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcVal", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BtcVal == nil { + m.BtcVal = &BTCValidator{} + } + if err := m.BtcVal.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventNewBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventNewBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventNewBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BtcDel == nil { + m.BtcDel = &BTCDelegation{} + } + if err := m.BtcDel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventActivateBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventActivateBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventActivateBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BtcDel == nil { + m.BtcDel = &BTCDelegation{} + } + if err := m.BtcDel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipEvents(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthEvents + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupEvents + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthEvents + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthEvents = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowEvents = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupEvents = fmt.Errorf("proto: unexpected end of group") +) From 982da21bf1cbb09d1ec44b26058938ffb1cf0d69 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 28 Jun 2023 10:25:21 +1000 Subject: [PATCH 019/202] BTC staking: voting power table for each Babylon height (#12) --- app/app.go | 2 +- testutil/datagen/btcstaking.go | 39 +++++++++ x/btcstaking/abci.go | 24 ++++++ x/btcstaking/keeper/btc_delegations.go | 4 +- x/btcstaking/keeper/btc_validators.go | 4 +- x/btcstaking/keeper/msg_server.go | 6 +- x/btcstaking/keeper/voting_power_table.go | 72 ++++++++++++++++ .../keeper/voting_power_table_test.go | 86 +++++++++++++++++++ x/btcstaking/module.go | 10 +-- x/btcstaking/types/btcstaking.go | 14 +++ x/btcstaking/types/btcstaking_test.go | 38 ++++++++ x/btcstaking/types/keys.go | 7 +- 12 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 x/btcstaking/abci.go create mode 100644 x/btcstaking/keeper/voting_power_table.go create mode 100644 x/btcstaking/keeper/voting_power_table_test.go diff --git a/app/app.go b/app/app.go index 2587106c9..502e9cd44 100644 --- a/app/app.go +++ b/app/app.go @@ -618,7 +618,7 @@ func NewBabylonApp( // set up BTC staking keeper app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], - app.BTCLightClientKeeper, app.BtcCheckpointKeeper, + &btclightclientKeeper, &btcCheckpointKeeper, btcNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index b2b344250..e48de5697 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -9,6 +9,7 @@ import ( bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -49,6 +50,44 @@ func GenRandomBTCValidatorWithBTCPK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst }, nil } +func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { + // key pairs + btcSK, btcPK, err := GenRandomBTCKeyPair(r) + if err != nil { + return nil, err + } + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + bbnSK, bbnPK, err := GenRandomSecp256k1KeyPair(r) + if err != nil { + return nil, err + } + secp256k1PK, ok := bbnPK.(*secp256k1.PubKey) + if !ok { + return nil, fmt.Errorf("failed to assert bbnPK to *secp256k1.PubKey") + } + // pop + pop, err := bstypes.NewPoP(bbnSK, btcSK) + if err != nil { + return nil, err + } + // TODO: generate legitimate jury signature and staking/slashing tx + jurySchnorrSig, err := schnorr.Sign(btcSK, GenRandomByteArray(r, 32)) + if err != nil { + return nil, err + } + jurySig := bbn.NewBIP340SignatureFromBTCSig(jurySchnorrSig) + return &bstypes.BTCDelegation{ + BabylonPk: secp256k1PK, + BtcPk: bip340PK, + Pop: pop, + ValBtcPk: valBTCPK, + StartHeight: startHeight, + EndHeight: endHeight, + TotalSat: totalSat, + JurySig: &jurySig, + }, nil +} + func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validatorPK, juryPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { btcNet := &chaincfg.SimNetParams diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go new file mode 100644 index 000000000..495b0e1d8 --- /dev/null +++ b/x/btcstaking/abci.go @@ -0,0 +1,24 @@ +package btcstaking + +import ( + "time" + + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) +} + +func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + // update BTC validator power table + k.RecordVotingPowerTable(ctx) + + return []abci.ValidatorUpdate{} +} diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index a46b54f70..bf19926e8 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// setBTCDelegation adds the given BTC delegation to KVStore -func (k Keeper) setBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { +// SetBTCDelegation adds the given BTC delegation to KVStore +func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { store := k.btcDelegationStore(ctx, btcDel.ValBtcPk.MustMarshal()) btcDelBytes := k.cdc.MustMarshal(btcDel) store.Set(btcDel.BtcPk.MustMarshal(), btcDelBytes) diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index d94003a3a..2863cf531 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// setBTCValidator adds the given BTC validator to KVStore -func (k Keeper) setBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) { +// SetBTCValidator adds the given BTC validator to KVStore +func (k Keeper) SetBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) { store := k.btcValidatorStore(ctx) btcValBytes := k.cdc.MustMarshal(btcVal) store.Set(btcVal.BtcPk.MustMarshal(), btcValBytes) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 05178ca16..c857b8319 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -54,7 +54,7 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea BtcPk: req.BtcPk, Pop: req.Pop, } - ms.setBTCValidator(ctx, &btcVal) + ms.SetBTCValidator(ctx, &btcVal) // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCValidator{BtcVal: &btcVal}); err != nil { @@ -155,7 +155,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre DelegatorSig: req.DelegatorSig, JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury } - ms.setBTCDelegation(ctx, newBTCDel) + ms.SetBTCDelegation(ctx, newBTCDel) // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { @@ -204,7 +204,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) // all good, add signature to BTC delegation and set it back to KVStore btcDel.JurySig = req.Sig - ms.setBTCDelegation(ctx, btcDel) + ms.SetBTCDelegation(ctx, btcDel) // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go new file mode 100644 index 000000000..bd1f9d82d --- /dev/null +++ b/x/btcstaking/keeper/voting_power_table.go @@ -0,0 +1,72 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RecordVotingPowerTable computes the voting power table at the current block height +// and saves the power table to KVStore +// triggered upon each EndBlock +func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { + // tip of Babylon and Bitcoin + babylonTipHeight := uint64(ctx.BlockHeight()) + btcTip := k.btclcKeeper.GetTipInfo(ctx) + if btcTip == nil { + return + } + btcTipHeight := btcTip.Height + wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + // iterate all BTC validators + btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) + defer btcValIter.Close() + for ; btcValIter.Valid(); btcValIter.Next() { + valBTCPK := btcValIter.Key() + valPower := uint64(0) + + // iterate all BTC delegations under this validator + // to calculate this validator's total voting power + btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) + for ; btcDelIter.Valid(); btcDelIter.Next() { + var btcDel types.BTCDelegation + k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDel) + valPower += btcDel.VotingPower(btcTipHeight, wValue) + } + btcDelIter.Close() + + if valPower > 0 { + k.setVotingPower(ctx, valBTCPK, babylonTipHeight, valPower) + } + } +} + +// setVotingPower sets the voting power of a given BTC validator at a given Babylon height +func (k Keeper) setVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64, power uint64) { + store := k.votingPowerStore(ctx, height) + store.Set(valBTCPK, sdk.Uint64ToBigEndian(power)) +} + +// GetVotingPower gets the voting power of a given BTC validator at a given Babylon height +func (k Keeper) GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 { + if !k.HasBTCValidator(ctx, valBTCPK) { + return 0 + } + store := k.votingPowerStore(ctx, height) + powerBytes := store.Get(valBTCPK) + if len(powerBytes) == 0 { + return 0 + } + return sdk.BigEndianToUint64(powerBytes) +} + +// votingPowerStore returns the KVStore of the BTC validators' voting power +// prefix: (VotingPowerKey || Babylon block height) +// key: Bitcoin secp256k1 PK +// value: voting power quantified in Satoshi +func (k Keeper) votingPowerStore(ctx sdk.Context, height uint64) prefix.Store { + store := ctx.KVStore(k.storeKey) + votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) + return prefix.NewStore(votingPowerStore, sdk.Uint64ToBigEndian(height)) +} diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go new file mode 100644 index 000000000..51bf04f0b --- /dev/null +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -0,0 +1,86 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + keepertest "github.com/babylonchain/babylon/testutil/keeper" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzVotingPowerTable(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // generate a random batch of validators + btcVals := []*types.BTCValidator{} + numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 1 + numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) + for i := uint64(0); i < numBTCVals; i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + btcVals = append(btcVals, btcVal) + } + + // for the first numBTCValsWithVotingPower validators, generate a random number of BTC delegations + numBTCDels := datagen.RandomInt(r, 10) + 1 + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + valBTCPK := btcVals[i].BtcPk + for j := uint64(0); j < numBTCDels; j++ { + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, 1, 1000, 1) // timelock period: 1-1000 + require.NoError(t, err) + keeper.SetBTCDelegation(ctx, btcDel) + } + } + + // assert none of validators has voting power (since BTC height is 0) + babylonHeight := uint64(1) + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) + keeper.RecordVotingPowerTable(ctx) + for _, btcVal := range btcVals { + power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) + require.Zero(t, power) + } + + // move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power + babylonHeight = uint64(2) + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.RecordVotingPowerTable(ctx) + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + require.Equal(t, uint64(numBTCDels), power) + } + for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + require.Zero(t, power) + } + + // move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + babylonHeight = uint64(3) + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) + keeper.RecordVotingPowerTable(ctx) + for _, btcVal := range btcVals { + power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) + require.Zero(t, power) + } + }) +} diff --git a/x/btcstaking/module.go b/x/btcstaking/module.go index a7f09b949..1d263739a 100644 --- a/x/btcstaking/module.go +++ b/x/btcstaking/module.go @@ -138,10 +138,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -// BeginBlock contains the logic that is automatically triggered at the beginning of each block -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper, req) +} -// EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return EndBlocker(ctx, am.keeper) } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7ced7b81a..14a12a1eb 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -81,6 +81,20 @@ func (d *BTCDelegation) IsActivated() bool { return d.JurySig != nil } +// VotingPower returns the voting power of the BTC delegation at a given BTC height +// and a given w value +// The BTC delegation d has voting power iff the following holds +// - d has a jury signature +// - d's timelock start height <= the given BTC height +// - the given BTC height <= d's timelock end height - w +func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { + if d.IsActivated() && d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { + return d.TotalSat + } else { + return 0 + } +} + func (p *ProofOfPossession) ValidateBasic() error { if len(p.BabylonSig) == 0 { return fmt.Errorf("empty Babylon signature") diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 5662586a1..f934b1ed0 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/require" ) @@ -43,3 +45,39 @@ func FuzzStakingTx(f *testing.F) { require.Equal(t, int64(stakingOutputInfo.StakingAmount), stakingValue) }) } + +func FuzzBTCDelegation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 100) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + btcDel := &types.BTCDelegation{} + // randomise voting power + btcDel.TotalSat = datagen.RandomInt(r, 100000) + + // randomise jury sig + hasJurySig := datagen.RandomInt(r, 2) == 0 + if hasJurySig { + jurySig := bbn.BIP340Signature([]byte{1, 2, 3}) + btcDel.JurySig = &jurySig + } + + // randomise start height and end height + btcDel.StartHeight = datagen.RandomInt(r, 100) + btcDel.EndHeight = btcDel.StartHeight + datagen.RandomInt(r, 100) + + // randomise BTC tip and w + btcHeight := btcDel.StartHeight + datagen.RandomInt(r, 50) + w := datagen.RandomInt(r, 50) + + // test expected voting power + hasVotingPower := hasJurySig && btcDel.StartHeight <= btcHeight && btcHeight+w <= btcDel.EndHeight + actualVotingPower := btcDel.VotingPower(btcHeight, w) + if hasVotingPower { + require.Equal(t, btcDel.TotalSat, actualVotingPower) + } else { + require.Equal(t, uint64(0), actualVotingPower) + } + }) +} diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 1b35435bf..40ca35a80 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -15,9 +15,10 @@ const ( ) var ( - BTCValidatorKey = []byte{0x01} // key prefix for the BTC validators - BTCDelegationKey = []byte{0x02} // key prefix for the BTC delegations - ParamsKey = []byte{0x03} // key prefix for the parameters + ParamsKey = []byte{0x01} // key prefix for the parameters + BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators + BTCDelegationKey = []byte{0x03} // key prefix for the BTC delegations + VotingPowerKey = []byte{0x04} // key prefix for the voting power ) func KeyPrefix(p string) []byte { From 42e65f70c8b82ef163f820ab75be32ad39c668c5 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 30 Jun 2023 17:06:48 +1000 Subject: [PATCH 020/202] finality tracker: index blocks upon `EndBlock` and tx formats (#13) --- .circleci/config.yml | 47 +- go.mod | 9 +- go.sum | 10 + proto/babylon/finality/v1/finality.proto | 22 +- proto/babylon/finality/v1/tx.proto | 42 + testutil/datagen/finality.go | 62 ++ types/btc_schnorr_eots.go | 6 +- types/btc_schnorr_eots_test.go | 3 +- types/btc_schnorr_pub_rand.go | 2 +- types/btc_schnorr_pub_rand_test.go | 2 +- x/btcstaking/keeper/msg_server.go | 2 +- x/btcstaking/types/events.pb.go | 3 + x/finality/abci.go | 26 + x/finality/keeper/indexed_blocks.go | 23 +- x/finality/keeper/msg_server.go | 10 + x/finality/keeper/public_randomness.go | 2 +- x/finality/keeper/votes.go | 36 +- x/finality/module.go | 10 +- x/finality/types/codec.go | 4 + x/finality/types/finality.go | 7 +- x/finality/types/finality.pb.go | 389 +------ x/finality/types/msg.go | 107 ++ x/finality/types/msg_test.go | 49 + x/finality/types/tx.pb.go | 1212 ++++++++++++++++++++-- 24 files changed, 1569 insertions(+), 516 deletions(-) create mode 100644 testutil/datagen/finality.go create mode 100644 x/finality/abci.go create mode 100644 x/finality/types/msg_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 66edbf9fa..5581c82bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,6 +19,7 @@ jobs: - run: name: Print Go environment command: "go env" + - add_ssh_keys - go/load-cache: key: go-mod-v6-{{ checksum "go.sum" }} - go/mod-download @@ -32,10 +33,12 @@ jobs: name: Run tests command: | make test - - run: - name: Run e2e tests - command: | - make test-e2e + # TODO: re-enable after EOTS library becomes public. Currently building Babylon-private + # requires retrieving the private EOTS library, which is challenging in Circle CI + # - run: + # name: Run e2e tests + # command: | + # make test-e2e lint: machine: image: ubuntu-2204:2022.10.1 @@ -44,6 +47,7 @@ jobs: - go/install: version: "1.20" - checkout + - add_ssh_keys - run: name: Lint proto files command: make proto-lint @@ -59,6 +63,7 @@ jobs: resource_class: large steps: - checkout + - add_ssh_keys - aws-ecr/build-image: push-image: false dockerfile: Dockerfile @@ -106,19 +111,21 @@ workflows: lint: jobs: - lint - docker: - jobs: - - build_docker: - filters: - tags: - only: /.*/ - - push_docker: - requires: - - build_docker - filters: - tags: - only: /.*/ - branches: - only: - - main - - dev + # TODO: re-enable after EOTS library becomes public. Currently building Babylon-private + # requires retrieving the private EOTS library, which is challenging in Circle CI + # docker: + # jobs: + # - build_docker: + # filters: + # tags: + # only: /.*/ + # - push_docker: + # requires: + # - build_docker + # filters: + # tags: + # only: /.*/ + # branches: + # only: + # - main + # - dev diff --git a/go.mod b/go.mod index 83fb12247..dd5329519 100644 --- a/go.mod +++ b/go.mod @@ -31,8 +31,8 @@ require ( github.com/CosmWasm/wasmvm v1.2.3 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d github.com/btcsuite/btcd/btcec/v2 v2.3.2 - github.com/btcsuite/btcd/btcutil v1.1.2 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/gogoproto v1.4.8 github.com/cosmos/ibc-go/v7 v7.0.1 @@ -134,6 +134,7 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect + github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -145,8 +146,8 @@ require ( github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect github.com/creachadair/taskgroup v0.4.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect - github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/cli v20.10.21+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v20.10.24+incompatible // indirect diff --git a/go.sum b/go.sum index b2c5eaf9c..753a2e03e 100644 --- a/go.sum +++ b/go.sum @@ -250,6 +250,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1 h1:nJDhs+WYMF9cFUPWSagchCLJifEL4DZlRm8URrGL+h8= +github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1/go.mod h1:P/P4Psf9o0V4yO7CgjUEXrbPs6ROfUorsZ2EYEZxzoI= github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf h1:NJU3YuruPqV8w6/y45Zsb8FudcWSkTBugdpTT7kJmjw= github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf/go.mod h1:BFh8nKWjr5zeR2OZfhkzdgDzj1+KjRn3aJLpwapStj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -276,9 +278,13 @@ github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9Ur github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -391,9 +397,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index d659d469c..65f2de81f 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -1,31 +1,15 @@ syntax = "proto3"; package babylon.finality.v1; -import "gogoproto/gogo.proto"; - option go_package = "github.com/babylonchain/babylon/x/finality/types"; -// Vote is a vote to a block -message Vote { - // val_btc_pk is the BTC Pk of the validator that casts this vote - bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // block_height is the height of the voted block - uint64 block_height = 2; - // finality_sig is the finality signature to this block - // where finality signature is an EOTS signature, i.e., - // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator - bytes finality_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; -} - // IndexedBlock is the block with some indexed info message IndexedBlock { // height is the height of the block uint64 height = 1; // hash is the hash of the block bytes hash = 2; - // btc_height is the height of the BTC tip upon EndBlock of this block - uint64 btc_height = 3; - // btc_hash is the hash of the BTC tip upon EndBlock of this block - bytes btc_hash = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderHashBytes" ]; + // finalized indicates whether the IndexedBlock is finalised by 2/3 + // BTC validators or not + bool finalized = 3; } diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index a5dacf822..f28802ed3 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -10,10 +10,52 @@ import "babylon/finality/v1/params.proto"; // Msg defines the Msg service. service Msg { + // AddVote adds a vote to a given block + rpc AddVote(MsgAddVote) returns (MsgAddVoteResponse); + // CommitPubRand commits a list of public randomness for EOTS + rpc CommitPubRand(MsgCommitPubRand) returns (MsgCommitPubRandResponse); + // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } +// MsgAddVote defines a message for adding a vote +message MsgAddVote { + string signer = 1; + // val_btc_pk is the BTC Pk of the validator that casts this vote + bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // block_height is the height of the voted block + uint64 block_height = 3; + // block_hash is the hash of the voted block + bytes block_hash = 4; + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + bytes finality_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; +} +// MsgAddVoteResponse is the response to the MsgAddVote message +message MsgAddVoteResponse{} + +// MsgCommitPubRand defines a message for committing a list of public randomness for EOTS +message MsgCommitPubRand { + string signer = 1; + // val_btc_pk is the BTC Pk of the validator that commits the public randomness + bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // start_height is the start block height of the list of public randomness + uint64 start_height = 3; + // pub_rand_list is the list of public randomness + repeated bytes pub_rand_list = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; + // sig is the signature on (start_height || pub_rand_list) signed by + // SK corresponding to val_btc_pk. This prevents others to commit public + // randomness on behalf of val_btc_pk + // TODO: another option is to restrict signer to correspond to val_btc_pk. This restricts + // the tx submitter to be the holder of val_btc_pk. Decide this later + bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} +// MsgCommitPubRandResponse is the response to the MsgCommitPubRand message +message MsgCommitPubRandResponse{} + // MsgUpdateParams defines a message for updating finality module parameters. message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go new file mode 100644 index 000000000..a06a1a6a0 --- /dev/null +++ b/testutil/datagen/finality.go @@ -0,0 +1,62 @@ +package datagen + +import ( + "math/rand" + + bbn "github.com/babylonchain/babylon/types" + ftypes "github.com/babylonchain/babylon/x/finality/types" + "github.com/babylonchain/eots" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" +) + +func GenRandomMsgAddVote(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgAddVote, *bbn.SchnorrPubRand, error) { + msg := &ftypes.MsgAddVote{ + Signer: GenRandomAccount().Address, + ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + BlockHeight: RandomInt(r, 100), + BlockHash: GenRandomByteArray(r, 32), + } + msgToSign := msg.MsgToSign() + sr, pr, err := eots.RandGen(r) + if err != nil { + return nil, nil, err + } + sig, err := eots.Sign(sk, sr, msgToSign) + if err != nil { + return nil, nil, err + } + msg.FinalitySig = bbn.NewSchnorrEOTSSigFromModNScalar(sig) + + return msg, bbn.NewSchnorrPubRandFromFieldVal(pr), nil +} + +func GenRandomMsgCommitPubRand(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgCommitPubRand, error) { + prList := []bbn.SchnorrPubRand{} + for i := 0; i < 1000; i++ { + prBytes := GenRandomByteArray(r, bbn.SchnorrPubRandLen) + pr, err := bbn.NewSchnorrPubRand(prBytes) + if err != nil { + return nil, err + } + prList = append(prList, *pr) + } + + msg := &ftypes.MsgCommitPubRand{ + Signer: GenRandomAccount().Address, + ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + StartHeight: RandomInt(r, 100), + PubRandList: prList, + } + hash, err := msg.HashToSign() + if err != nil { + return nil, err + } + schnorrSig, err := schnorr.Sign(sk, hash) + if err != nil { + return nil, err + } + sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) + msg.Sig = &sig + return msg, nil +} diff --git a/types/btc_schnorr_eots.go b/types/btc_schnorr_eots.go index 235888d9d..746766003 100644 --- a/types/btc_schnorr_eots.go +++ b/types/btc_schnorr_eots.go @@ -16,15 +16,15 @@ func NewSchnorrEOTSSig(data []byte) (*SchnorrEOTSSig, error) { return &sig, err } -func NewSchnorrEOTSSigFromModNScalar(r *btcec.ModNScalar) *SchnorrEOTSSig { - prBytes := r.Bytes() +func NewSchnorrEOTSSigFromModNScalar(s *btcec.ModNScalar) *SchnorrEOTSSig { + prBytes := s.Bytes() sig := SchnorrEOTSSig(prBytes[:]) return &sig } func (sig SchnorrEOTSSig) ToModNScalar() *btcec.ModNScalar { var s btcec.ModNScalar - s.PutBytesUnchecked(sig) + s.SetByteSlice(sig) return &s } diff --git a/types/btc_schnorr_eots_test.go b/types/btc_schnorr_eots_test.go index 151f93a24..d6fe38575 100644 --- a/types/btc_schnorr_eots_test.go +++ b/types/btc_schnorr_eots_test.go @@ -18,7 +18,8 @@ func FuzzSchnorrEOTSSig(f *testing.F) { randBytes := datagen.GenRandomByteArray(r, 32) var modNScalar btcec.ModNScalar - modNScalar.PutBytesUnchecked(randBytes) + overflowed := modNScalar.SetByteSlice(randBytes) + require.False(t, overflowed) // ModNScalar -> SchnorrEOTSSig -> ModNScalar sig := types.NewSchnorrEOTSSigFromModNScalar(&modNScalar) diff --git a/types/btc_schnorr_pub_rand.go b/types/btc_schnorr_pub_rand.go index 2afc96b69..d0bd52603 100644 --- a/types/btc_schnorr_pub_rand.go +++ b/types/btc_schnorr_pub_rand.go @@ -24,7 +24,7 @@ func NewSchnorrPubRandFromFieldVal(r *btcec.FieldVal) *SchnorrPubRand { func (pr SchnorrPubRand) ToFieldVal() *btcec.FieldVal { var r btcec.FieldVal - r.PutBytesUnchecked(pr) + r.SetByteSlice(pr) return &r } diff --git a/types/btc_schnorr_pub_rand_test.go b/types/btc_schnorr_pub_rand_test.go index 9a70dfb87..06e22387e 100644 --- a/types/btc_schnorr_pub_rand_test.go +++ b/types/btc_schnorr_pub_rand_test.go @@ -18,7 +18,7 @@ func FuzzSchnorrPubRand(f *testing.F) { randBytes := datagen.GenRandomByteArray(r, 32) var fieldVal btcec.FieldVal - fieldVal.PutBytesUnchecked(randBytes) + fieldVal.SetByteSlice(randBytes) // FieldVal -> SchnorrPubRand -> FieldVal pubRand := types.NewSchnorrPubRandFromFieldVal(&fieldVal) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index c857b8319..31aa1be25 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -83,7 +83,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // can only correspond to a single BTC delegation // TODO: the current impl does not support multiple delegations with the same (valPK, delPK) pair // since a delegation is keyed by (valPK, delPK). Need to decide whether to support this - btcDel, err := ms.GetBTCDelegation(ctx, *valBTCPK, *delBTCPK) + btcDel, err := ms.GetBTCDelegation(ctx, valBTCPK.MustMarshal(), delBTCPK.MustMarshal()) if err == nil && btcDel.StakingTx.Equals(req.StakingTx) { return nil, fmt.Errorf("the BTC staking tx is already used") } diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index d862e1bcf..71a57cb8c 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -68,6 +68,8 @@ func (m *EventNewBTCValidator) GetBtcVal() *BTCValidator { } // EventNewBTCDelegation is the event emitted when a BTC delegation is created +// NOTE: the BTC delegation is not active thus does not have voting power yet +// only after it receives a jury signature it becomes activated and has voting power type EventNewBTCDelegation struct { BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` } @@ -113,6 +115,7 @@ func (m *EventNewBTCDelegation) GetBtcDel() *BTCDelegation { } // EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation +// such that the BTC delegation starts to have voting power in its timelock period type EventActivateBTCDelegation struct { BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` } diff --git a/x/finality/abci.go b/x/finality/abci.go new file mode 100644 index 000000000..3860f7797 --- /dev/null +++ b/x/finality/abci.go @@ -0,0 +1,26 @@ +package finality + +import ( + "time" + + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) +} + +func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + // index the current block + k.IndexBlock(ctx) + + // TODO: tally all non-finalised blocks + + return []abci.ValidatorUpdate{} +} diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go index afed54e25..bbc280623 100644 --- a/x/finality/keeper/indexed_blocks.go +++ b/x/finality/keeper/indexed_blocks.go @@ -1,26 +1,21 @@ package keeper import ( - "fmt" - "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) AddBlock(ctx sdk.Context, block *types.IndexedBlock) error { - if k.HasBlock(ctx, block.Height) { - block2, err := k.GetBlock(ctx, block.Height) - if err != nil { - panic(err) - } - if !block.Equal(block2) { - panic(fmt.Errorf("conflicting blocks detected at height %d", block.Height)) - } - return types.ErrDuplicatedBlock +// IndexBlock indexes the current block, saves the corresponding indexed block +// to KVStore +func (k Keeper) IndexBlock(ctx sdk.Context) { + header := ctx.BlockHeader() + ib := &types.IndexedBlock{ + Height: uint64(header.Height), + Hash: header.LastCommitHash, + Finalized: false, } - k.setBlock(ctx, block) - return nil + k.setBlock(ctx, ib) } func (k Keeper) setBlock(ctx sdk.Context, block *types.IndexedBlock) { diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index a37212e7d..bf38e5b8c 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -34,3 +34,13 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara return &types.MsgUpdateParamsResponse{}, nil } + +// AddVote adds a new vote to a given block +func (ms msgServer) AddVote(goCtx context.Context, req *types.MsgAddVote) (*types.MsgAddVoteResponse, error) { + panic("TODO: implement me") +} + +// CommitPubRand commits a list of EOTS public randomness +func (ms msgServer) CommitPubRand(goCtx context.Context, req *types.MsgCommitPubRand) (*types.MsgCommitPubRandResponse, error) { + panic("TODO: implement me") +} diff --git a/x/finality/keeper/public_randomness.go b/x/finality/keeper/public_randomness.go index 3add2d591..84451ca7e 100644 --- a/x/finality/keeper/public_randomness.go +++ b/x/finality/keeper/public_randomness.go @@ -34,5 +34,5 @@ func (k Keeper) GetPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height u func (k Keeper) pubRandStore(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) prefix.Store { store := ctx.KVStore(k.storeKey) prefixedStore := prefix.NewStore(store, types.PubRandKey) - return prefix.NewStore(prefixedStore, *valBtcPK) + return prefix.NewStore(prefixedStore, valBtcPK.MustMarshal()) } diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index b09bb3904..c9aa5daa2 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -1,47 +1,45 @@ package keeper import ( + "fmt" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) AddVote(ctx sdk.Context, vote *types.Vote) error { - // TODO verification rules of vote - k.setVote(ctx, vote) - return nil -} - -func (k Keeper) setVote(ctx sdk.Context, vote *types.Vote) { - store := k.voteStore(ctx, vote.BlockHeight) - voteBytes := k.cdc.MustMarshal(vote) - store.Set(sdk.Uint64ToBigEndian(vote.BlockHeight), voteBytes) +//nolint:unused +func (k Keeper) setSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { + store := k.voteStore(ctx, height) + store.Set(valBtcPK.MustMarshal(), sig.MustMarshal()) } -func (k Keeper) HasVote(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { +func (k Keeper) HasSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { store := k.voteStore(ctx, height) - return store.Has(*valBtcPK) + return store.Has(valBtcPK.MustMarshal()) } -func (k Keeper) GetVote(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*types.Vote, error) { +func (k Keeper) GetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { if uint64(ctx.BlockHeight()) < height { return nil, types.ErrHeightTooHigh } store := k.voteStore(ctx, height) - voteBytes := store.Get(*valBtcPK) - if len(voteBytes) == 0 { + sigBytes := store.Get(valBtcPK.MustMarshal()) + if len(sigBytes) == 0 { return nil, types.ErrVoteNotFound } - var vote types.Vote - k.cdc.MustUnmarshal(voteBytes, &vote) - return &vote, nil + sig, err := bbn.NewSchnorrEOTSSig(sigBytes) + if err != nil { + panic(fmt.Errorf("failed to unmarshal EOTS signature: %w", err)) + } + return sig, nil } // voteStore returns the KVStore of the votes // prefix: VoteKey // key: (block height || BTC validator PK) -// value: Vote +// value: EOTS sig func (k Keeper) voteStore(ctx sdk.Context, height uint64) prefix.Store { store := ctx.KVStore(k.storeKey) prefixedStore := prefix.NewStore(store, types.VoteKey) diff --git a/x/finality/module.go b/x/finality/module.go index 7993f2aa8..0a64bc245 100644 --- a/x/finality/module.go +++ b/x/finality/module.go @@ -138,10 +138,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -// BeginBlock contains the logic that is automatically triggered at the beginning of each block -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper, req) +} -// EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return EndBlocker(ctx, am.keeper) } diff --git a/x/finality/types/codec.go b/x/finality/types/codec.go index c885f3ab7..456450c81 100644 --- a/x/finality/types/codec.go +++ b/x/finality/types/codec.go @@ -8,6 +8,8 @@ import ( ) func RegisterCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgAddVote{}, "finality/MsgAddVote", nil) + cdc.RegisterConcrete(&MsgCommitPubRand{}, "finality/MsgCommitPubRand", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "finality/MsgUpdateParams", nil) } @@ -15,6 +17,8 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { // Register messages registry.RegisterImplementations( (*sdk.Msg)(nil), + &MsgAddVote{}, + &MsgCommitPubRand{}, &MsgUpdateParams{}, ) diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go index 1c1e2c37f..eff20e269 100644 --- a/x/finality/types/finality.go +++ b/x/finality/types/finality.go @@ -9,11 +9,6 @@ func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { if ib.Height != ib2.Height { return false } - if !ib.BtcHash.Eq(ib2.BtcHash) { - return false - } - if ib.BtcHeight != ib2.BtcHeight { - return false - } + // NOTE: we don't compare finalisation status here return true } diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 0b91286ad..8800dcffe 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -5,8 +5,6 @@ package types import ( fmt "fmt" - github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" - _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -24,76 +22,22 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// Vote is a vote to a block -type Vote struct { - // val_btc_pk is the BTC Pk of the validator that casts this vote - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` - // block_height is the height of the voted block - BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // finality_sig is the finality signature to this block - // where finality signature is an EOTS signature, i.e., - // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator - FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,3,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` -} - -func (m *Vote) Reset() { *m = Vote{} } -func (m *Vote) String() string { return proto.CompactTextString(m) } -func (*Vote) ProtoMessage() {} -func (*Vote) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{0} -} -func (m *Vote) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Vote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Vote.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Vote) XXX_Merge(src proto.Message) { - xxx_messageInfo_Vote.Merge(m, src) -} -func (m *Vote) XXX_Size() int { - return m.Size() -} -func (m *Vote) XXX_DiscardUnknown() { - xxx_messageInfo_Vote.DiscardUnknown(m) -} - -var xxx_messageInfo_Vote proto.InternalMessageInfo - -func (m *Vote) GetBlockHeight() uint64 { - if m != nil { - return m.BlockHeight - } - return 0 -} - // IndexedBlock is the block with some indexed info type IndexedBlock struct { // height is the height of the block Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` // hash is the hash of the block Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` - // btc_height is the height of the BTC tip upon EndBlock of this block - BtcHeight uint64 `protobuf:"varint,3,opt,name=btc_height,json=btcHeight,proto3" json:"btc_height,omitempty"` - // btc_hash is the hash of the BTC tip upon EndBlock of this block - BtcHash *github_com_babylonchain_babylon_types.BTCHeaderHashBytes `protobuf:"bytes,4,opt,name=btc_hash,json=btcHash,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderHashBytes" json:"btc_hash,omitempty"` + // finalized indicates whether the IndexedBlock is finalised by 2/3 + // BTC validators or not + Finalized bool `protobuf:"varint,3,opt,name=finalized,proto3" json:"finalized,omitempty"` } func (m *IndexedBlock) Reset() { *m = IndexedBlock{} } func (m *IndexedBlock) String() string { return proto.CompactTextString(m) } func (*IndexedBlock) ProtoMessage() {} func (*IndexedBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_ca5b87e52e3e6d02, []int{1} + return fileDescriptor_ca5b87e52e3e6d02, []int{0} } func (m *IndexedBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -136,15 +80,14 @@ func (m *IndexedBlock) GetHash() []byte { return nil } -func (m *IndexedBlock) GetBtcHeight() uint64 { +func (m *IndexedBlock) GetFinalized() bool { if m != nil { - return m.BtcHeight + return m.Finalized } - return 0 + return false } func init() { - proto.RegisterType((*Vote)(nil), "babylon.finality.v1.Vote") proto.RegisterType((*IndexedBlock)(nil), "babylon.finality.v1.IndexedBlock") } @@ -153,82 +96,20 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 368 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4f, 0x6b, 0xe2, 0x40, - 0x18, 0xc6, 0x9d, 0x35, 0xb8, 0xee, 0x98, 0xd3, 0xec, 0xb2, 0xc8, 0xc2, 0x46, 0xd7, 0x93, 0xa7, - 0x44, 0x57, 0x29, 0x1e, 0x7a, 0x4a, 0x29, 0x68, 0x7b, 0xa8, 0x24, 0xd2, 0x42, 0x7b, 0x08, 0x33, - 0x71, 0x9a, 0x19, 0x4c, 0x33, 0x92, 0x8c, 0xc1, 0x7c, 0x8b, 0x7e, 0x96, 0x7e, 0x8a, 0x1e, 0x3d, - 0x16, 0x29, 0x52, 0xf4, 0x8b, 0x94, 0x4c, 0xa3, 0x3d, 0xd6, 0xdb, 0xfb, 0xe7, 0xc9, 0xf3, 0xfc, - 0x78, 0x33, 0xb0, 0x45, 0x30, 0xc9, 0x42, 0x11, 0x59, 0xf7, 0x3c, 0xc2, 0x21, 0x97, 0x99, 0x95, - 0x76, 0x0f, 0xb5, 0x39, 0x8f, 0x85, 0x14, 0xe8, 0x67, 0xa1, 0x31, 0x0f, 0xf3, 0xb4, 0xfb, 0xe7, - 0x57, 0x20, 0x02, 0xa1, 0xf6, 0x56, 0x5e, 0x7d, 0x48, 0x5b, 0xaf, 0x00, 0x6a, 0xd7, 0x42, 0x52, - 0x34, 0x81, 0x30, 0xc5, 0xa1, 0x47, 0xa4, 0xef, 0xcd, 0x67, 0x75, 0xd0, 0x04, 0x6d, 0xdd, 0x3e, - 0x59, 0x6f, 0x1a, 0xff, 0x03, 0x2e, 0xd9, 0x82, 0x98, 0xbe, 0x78, 0xb0, 0x0a, 0x5b, 0x9f, 0x61, - 0x1e, 0xed, 0x1b, 0x4b, 0x66, 0x73, 0x9a, 0x98, 0xf6, 0x68, 0xdc, 0xeb, 0x77, 0xc6, 0x0b, 0x72, - 0x49, 0x33, 0xa7, 0x9a, 0xe2, 0xd0, 0x96, 0xfe, 0x78, 0x86, 0xfe, 0x41, 0x9d, 0x84, 0xc2, 0x9f, - 0x79, 0x8c, 0xf2, 0x80, 0xc9, 0xfa, 0xb7, 0x26, 0x68, 0x6b, 0x4e, 0x4d, 0xcd, 0x86, 0x6a, 0x84, - 0xee, 0xa0, 0xbe, 0xc7, 0xf4, 0x12, 0x1e, 0xd4, 0xcb, 0x2a, 0x7a, 0xb0, 0xde, 0x34, 0xfa, 0xc7, - 0x45, 0xbb, 0x3e, 0x8b, 0x44, 0x1c, 0x9f, 0x5f, 0x4d, 0x5c, 0x97, 0x07, 0x4e, 0x6d, 0xef, 0xe6, - 0xf2, 0xa0, 0xf5, 0x04, 0xa0, 0x3e, 0x8a, 0xa6, 0x74, 0x49, 0xa7, 0x76, 0x9e, 0x89, 0x7e, 0xc3, - 0x4a, 0x81, 0x02, 0x14, 0x4a, 0xd1, 0x21, 0x04, 0x35, 0x86, 0x13, 0xa6, 0x00, 0x75, 0x47, 0xd5, - 0xe8, 0x2f, 0x84, 0xf9, 0x39, 0x0a, 0x7d, 0x59, 0xe9, 0x7f, 0x10, 0xe9, 0x17, 0xe0, 0x37, 0xb0, - 0xaa, 0xd6, 0xf9, 0x67, 0x9a, 0x82, 0x3e, 0x5d, 0x6f, 0x1a, 0x83, 0x23, 0xef, 0x35, 0x39, 0x1b, - 0x52, 0x3c, 0xa5, 0xf1, 0x10, 0x27, 0xcc, 0xce, 0x24, 0x4d, 0x9c, 0xef, 0xb9, 0x75, 0xde, 0x5d, - 0x3c, 0x6f, 0x0d, 0xb0, 0xda, 0x1a, 0xe0, 0x6d, 0x6b, 0x80, 0xc7, 0x9d, 0x51, 0x5a, 0xed, 0x8c, - 0xd2, 0xcb, 0xce, 0x28, 0xdd, 0x76, 0xbe, 0x32, 0x5f, 0x7e, 0x3e, 0x0b, 0x95, 0x43, 0x2a, 0xea, - 0x37, 0xf7, 0xde, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5c, 0xc2, 0xbd, 0x0a, 0x37, 0x02, 0x00, 0x00, -} - -func (m *Vote) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Vote) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.FinalitySig != nil { - { - size := m.FinalitySig.Size() - i -= size - if _, err := m.FinalitySig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintFinality(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.BlockHeight != 0 { - i = encodeVarintFinality(dAtA, i, uint64(m.BlockHeight)) - i-- - dAtA[i] = 0x10 - } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintFinality(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil + // 195 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0x84, + 0xb3, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x84, 0xa1, 0x6a, 0xf4, 0xe0, 0xe2, 0x65, 0x86, + 0x4a, 0x11, 0x5c, 0x3c, 0x9e, 0x79, 0x29, 0xa9, 0x15, 0xa9, 0x29, 0x4e, 0x39, 0xf9, 0xc9, 0xd9, + 0x42, 0x62, 0x5c, 0x6c, 0x19, 0xa9, 0x99, 0xe9, 0x19, 0x25, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x2c, + 0x41, 0x50, 0x9e, 0x90, 0x10, 0x17, 0x4b, 0x46, 0x62, 0x71, 0x86, 0x04, 0x93, 0x02, 0xa3, 0x06, + 0x4f, 0x10, 0x98, 0x2d, 0x24, 0xc3, 0xc5, 0x09, 0x31, 0xaa, 0x2a, 0x35, 0x45, 0x82, 0x59, 0x81, + 0x51, 0x83, 0x23, 0x08, 0x21, 0xe0, 0xe4, 0x75, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, + 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, + 0x0c, 0x51, 0x06, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0x37, + 0x25, 0x67, 0x24, 0x66, 0xe6, 0xc1, 0x38, 0xfa, 0x15, 0x08, 0x6f, 0x94, 0x54, 0x16, 0xa4, 0x16, + 0x27, 0xb1, 0x81, 0x7d, 0x60, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x7c, 0x59, 0x57, 0xe7, + 0x00, 0x00, 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { @@ -251,20 +132,13 @@ func (m *IndexedBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.BtcHash != nil { - { - size := m.BtcHash.Size() - i -= size - if _, err := m.BtcHash.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintFinality(dAtA, i, uint64(size)) - } + if m.Finalized { i-- - dAtA[i] = 0x22 - } - if m.BtcHeight != 0 { - i = encodeVarintFinality(dAtA, i, uint64(m.BtcHeight)) + if m.Finalized { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } i-- dAtA[i] = 0x18 } @@ -294,26 +168,6 @@ func encodeVarintFinality(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *Vote) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovFinality(uint64(l)) - } - if m.BlockHeight != 0 { - n += 1 + sovFinality(uint64(m.BlockHeight)) - } - if m.FinalitySig != nil { - l = m.FinalitySig.Size() - n += 1 + l + sovFinality(uint64(l)) - } - return n -} - func (m *IndexedBlock) Size() (n int) { if m == nil { return 0 @@ -327,12 +181,8 @@ func (m *IndexedBlock) Size() (n int) { if l > 0 { n += 1 + l + sovFinality(uint64(l)) } - if m.BtcHeight != 0 { - n += 1 + sovFinality(uint64(m.BtcHeight)) - } - if m.BtcHash != nil { - l = m.BtcHash.Size() - n += 1 + l + sovFinality(uint64(l)) + if m.Finalized { + n += 2 } return n } @@ -343,145 +193,6 @@ func sovFinality(x uint64) (n int) { func sozFinality(x uint64) (n int) { return sovFinality(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Vote) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Vote: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Vote: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) - } - m.BlockHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.BlockHeight |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FinalitySig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.SchnorrEOTSSig - m.FinalitySig = &v - if err := m.FinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipFinality(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthFinality - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *IndexedBlock) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -566,28 +277,9 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 3: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcHeight", wireType) - } - m.BtcHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowFinality - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.BtcHeight |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } + return fmt.Errorf("proto: wrong wireType = %d for field Finalized", wireType) } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcHash", wireType) - } - var byteLen int + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFinality @@ -597,27 +289,12 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return ErrInvalidLengthFinality - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthFinality - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BTCHeaderHashBytes - m.BtcHash = &v - if err := m.BtcHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex + m.Finalized = bool(v != 0) default: iNdEx = preIndex skippy, err := skipFinality(dAtA[iNdEx:]) diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index fadaf538a..d3fd52396 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -1,13 +1,20 @@ package types import ( + "fmt" + errorsmod "cosmossdk.io/errors" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/eots" + "github.com/cometbft/cometbft/crypto/tmhash" sdk "github.com/cosmos/cosmos-sdk/types" ) // ensure that these message types implement the sdk.Msg interface var ( _ sdk.Msg = &MsgUpdateParams{} + _ sdk.Msg = &MsgAddVote{} + _ sdk.Msg = &MsgCommitPubRand{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -28,3 +35,103 @@ func (m *MsgUpdateParams) ValidateBasic() error { return nil } + +// GetSigners returns the expected signers for a MsgAddVote message. +func (m *MsgAddVote) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgAddVote) ValidateBasic() error { + if m.ValBtcPk == nil { + return fmt.Errorf("empty validator BTC PK") + } + if len(m.BlockHash) != tmhash.Size { + return fmt.Errorf("malformed block hash") + } + if m.FinalitySig == nil { + return fmt.Errorf("empty finality signature") + } + + return nil +} + +// MsgToSign returns (block_height || block_hash) +// The EOTS signature in MsgAddVote will be on this msg +func (m *MsgAddVote) MsgToSign() []byte { + msgToSign := []byte{} + msgToSign = append(msgToSign, sdk.Uint64ToBigEndian(m.BlockHeight)...) + msgToSign = append(msgToSign, m.BlockHash...) + return msgToSign +} + +func (m *MsgAddVote) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { + msgToSign := m.MsgToSign() + pk, err := m.ValBtcPk.ToBTCPK() + if err != nil { + return err + } + + return eots.Verify(pk, pubRand.ToFieldVal(), msgToSign, m.FinalitySig.ToModNScalar()) +} + +// GetSigners returns the expected signers for a MsgCommitPubRand message. +func (m *MsgCommitPubRand) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgCommitPubRand) ValidateBasic() error { + if m.ValBtcPk == nil { + return fmt.Errorf("empty validator BTC PK") + } + if len(m.PubRandList) == 0 { + return fmt.Errorf("empty list of public randomness") + } + if m.Sig == nil { + return fmt.Errorf("empty signature") + } + return m.verifySig() +} + +// HashToSign returns a 32-byte hash of (start_height || pub_rand_list) +// The signature in MsgCommitPubRand will be on this hash +func (m *MsgCommitPubRand) HashToSign() ([]byte, error) { + hasher := tmhash.New() + if _, err := hasher.Write(sdk.Uint64ToBigEndian(m.StartHeight)); err != nil { + return nil, err + } + for _, pr := range m.PubRandList { + if _, err := hasher.Write(pr.MustMarshal()); err != nil { + return nil, err + } + } + return hasher.Sum(nil), nil +} + +func (m *MsgCommitPubRand) verifySig() error { + msgHash, err := m.HashToSign() + if err != nil { + return err + } + pk, err := m.ValBtcPk.ToBTCPK() + if err != nil { + return err + } + schnorrSig, err := m.Sig.ToBTCSig() + if err != nil { + return err + } + if !schnorrSig.Verify(msgHash, pk) { + return fmt.Errorf("failed to verify signature") + } + return nil +} diff --git a/x/finality/types/msg_test.go b/x/finality/types/msg_test.go new file mode 100644 index 000000000..389151d4b --- /dev/null +++ b/x/finality/types/msg_test.go @@ -0,0 +1,49 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/eots" + "github.com/stretchr/testify/require" +) + +func FuzzMsgAddVote(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + sk, err := eots.KeyGen(r) + require.NoError(t, err) + + msg, pr, err := datagen.GenRandomMsgAddVote(r, sk) + require.NoError(t, err) + + // basic sanity checks + err = msg.ValidateBasic() + require.NoError(t, err) + // verify msg's EOTS sig against the given public randomness + err = msg.VerifyEOTSSig(pr) + require.NoError(t, err) + }) +} + +func FuzzMsgCommitPubRand(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + sk, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + msg, err := datagen.GenRandomMsgCommitPubRand(r, sk) + require.NoError(t, err) + + // sanity checks, including verifying signature + err = msg.ValidateBasic() + require.NoError(t, err) + }) +} diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index d08626598..bf4cd3c5f 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -6,6 +6,7 @@ package types import ( context "context" fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/cosmos-proto" _ "github.com/cosmos/cosmos-sdk/types/msgservice" _ "github.com/cosmos/gogoproto/gogoproto" @@ -30,6 +31,214 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// MsgAddVote defines a message for adding a vote +type MsgAddVote struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // val_btc_pk is the BTC Pk of the validator that casts this vote + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // block_height is the height of the voted block + BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // block_hash is the hash of the voted block + BlockHash []byte `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,5,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` +} + +func (m *MsgAddVote) Reset() { *m = MsgAddVote{} } +func (m *MsgAddVote) String() string { return proto.CompactTextString(m) } +func (*MsgAddVote) ProtoMessage() {} +func (*MsgAddVote) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{0} +} +func (m *MsgAddVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddVote.Merge(m, src) +} +func (m *MsgAddVote) XXX_Size() int { + return m.Size() +} +func (m *MsgAddVote) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddVote.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddVote proto.InternalMessageInfo + +func (m *MsgAddVote) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgAddVote) GetBlockHeight() uint64 { + if m != nil { + return m.BlockHeight + } + return 0 +} + +func (m *MsgAddVote) GetBlockHash() []byte { + if m != nil { + return m.BlockHash + } + return nil +} + +// MsgAddVoteResponse is the response to the MsgAddVote message +type MsgAddVoteResponse struct { +} + +func (m *MsgAddVoteResponse) Reset() { *m = MsgAddVoteResponse{} } +func (m *MsgAddVoteResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddVoteResponse) ProtoMessage() {} +func (*MsgAddVoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{1} +} +func (m *MsgAddVoteResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddVoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddVoteResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddVoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddVoteResponse.Merge(m, src) +} +func (m *MsgAddVoteResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAddVoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddVoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddVoteResponse proto.InternalMessageInfo + +// MsgCommitPubRand defines a message for committing a list of public randomness for EOTS +type MsgCommitPubRand struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // val_btc_pk is the BTC Pk of the validator that commits the public randomness + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // start_height is the start block height of the list of public randomness + StartHeight uint64 `protobuf:"varint,3,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // pub_rand_list is the list of public randomness + PubRandList []github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,4,rep,name=pub_rand_list,json=pubRandList,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand_list,omitempty"` + // sig is the signature on (start_height || pub_rand_list) signed by + // SK corresponding to val_btc_pk. This prevents others to commit public + // randomness on behalf of val_btc_pk + // TODO: another option is to restrict signer to correspond to val_btc_pk. This restricts + // the tx submitter to be the holder of val_btc_pk. Decide this later + Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` +} + +func (m *MsgCommitPubRand) Reset() { *m = MsgCommitPubRand{} } +func (m *MsgCommitPubRand) String() string { return proto.CompactTextString(m) } +func (*MsgCommitPubRand) ProtoMessage() {} +func (*MsgCommitPubRand) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{2} +} +func (m *MsgCommitPubRand) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCommitPubRand) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCommitPubRand.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCommitPubRand) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCommitPubRand.Merge(m, src) +} +func (m *MsgCommitPubRand) XXX_Size() int { + return m.Size() +} +func (m *MsgCommitPubRand) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCommitPubRand.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCommitPubRand proto.InternalMessageInfo + +func (m *MsgCommitPubRand) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgCommitPubRand) GetStartHeight() uint64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +// MsgCommitPubRandResponse is the response to the MsgCommitPubRand message +type MsgCommitPubRandResponse struct { +} + +func (m *MsgCommitPubRandResponse) Reset() { *m = MsgCommitPubRandResponse{} } +func (m *MsgCommitPubRandResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCommitPubRandResponse) ProtoMessage() {} +func (*MsgCommitPubRandResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_2dd6da066b6baf1d, []int{3} +} +func (m *MsgCommitPubRandResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCommitPubRandResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCommitPubRandResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCommitPubRandResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCommitPubRandResponse.Merge(m, src) +} +func (m *MsgCommitPubRandResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCommitPubRandResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCommitPubRandResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCommitPubRandResponse proto.InternalMessageInfo + // MsgUpdateParams defines a message for updating finality module parameters. type MsgUpdateParams struct { // authority is the address of the governance account. @@ -47,7 +256,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_2dd6da066b6baf1d, []int{0} + return fileDescriptor_2dd6da066b6baf1d, []int{4} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -98,7 +307,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_2dd6da066b6baf1d, []int{1} + return fileDescriptor_2dd6da066b6baf1d, []int{5} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -128,6 +337,10 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo func init() { + proto.RegisterType((*MsgAddVote)(nil), "babylon.finality.v1.MsgAddVote") + proto.RegisterType((*MsgAddVoteResponse)(nil), "babylon.finality.v1.MsgAddVoteResponse") + proto.RegisterType((*MsgCommitPubRand)(nil), "babylon.finality.v1.MsgCommitPubRand") + proto.RegisterType((*MsgCommitPubRandResponse)(nil), "babylon.finality.v1.MsgCommitPubRandResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.finality.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.finality.v1.MsgUpdateParamsResponse") } @@ -135,27 +348,46 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } var fileDescriptor_2dd6da066b6baf1d = []byte{ - // 318 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, - 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xca, 0xea, 0xc1, 0x64, 0xf5, - 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xf2, 0xfa, 0x20, 0x16, 0x44, 0xa9, 0x94, - 0x64, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x4a, 0x89, 0x43, 0x78, - 0xfa, 0xb9, 0xc5, 0xe9, 0x20, 0xd3, 0x73, 0x8b, 0xd3, 0xa1, 0x12, 0x0a, 0xd8, 0x2c, 0x2f, 0x48, - 0x2c, 0x4a, 0xcc, 0x85, 0x6a, 0x55, 0x9a, 0xc2, 0xc8, 0xc5, 0xef, 0x5b, 0x9c, 0x1e, 0x5a, 0x90, - 0x92, 0x58, 0x92, 0x1a, 0x00, 0x96, 0x11, 0x32, 0xe3, 0xe2, 0x4c, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, - 0xca, 0x2c, 0xa9, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x74, 0x92, 0xb8, 0xb4, 0x45, 0x57, 0x04, - 0x6a, 0xa7, 0x63, 0x4a, 0x4a, 0x51, 0x6a, 0x71, 0x71, 0x70, 0x49, 0x51, 0x66, 0x5e, 0x7a, 0x10, - 0x42, 0xa9, 0x90, 0x25, 0x17, 0x1b, 0xc4, 0x6c, 0x09, 0x26, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x69, - 0x3d, 0x2c, 0xbe, 0xd3, 0x83, 0x58, 0xe2, 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, 0x54, 0x83, - 0x15, 0x5f, 0xd3, 0xf3, 0x0d, 0x5a, 0x08, 0xa3, 0x94, 0x24, 0xb9, 0xc4, 0xd1, 0x5c, 0x15, 0x94, - 0x5a, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x6a, 0x94, 0xc9, 0xc5, 0xec, 0x5b, 0x9c, 0x2e, 0x94, 0xc4, - 0xc5, 0x83, 0xe2, 0x68, 0x15, 0xac, 0x96, 0xa1, 0x19, 0x22, 0xa5, 0x43, 0x8c, 0x2a, 0x98, 0x55, - 0x4e, 0x5e, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, - 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x90, 0x9e, 0x59, - 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x35, 0x31, 0x39, 0x23, 0x31, 0x33, 0x0f, - 0xc6, 0xd1, 0xaf, 0x40, 0x04, 0x79, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x38, 0xbc, 0x8d, - 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x43, 0xfa, 0x08, 0x60, 0x10, 0x02, 0x00, 0x00, + // 620 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcb, 0x6e, 0xd3, 0x40, + 0x14, 0x8d, 0x93, 0x50, 0xe8, 0x24, 0x05, 0x34, 0x54, 0xd4, 0x35, 0xe0, 0x84, 0x08, 0x44, 0x85, + 0xa8, 0xdd, 0x17, 0x15, 0x65, 0x57, 0x23, 0x24, 0xa0, 0x44, 0x44, 0x76, 0x61, 0x01, 0x48, 0xd6, + 0xf8, 0xc1, 0x78, 0x54, 0xdb, 0x63, 0x79, 0xc6, 0x51, 0xb3, 0xe5, 0x0b, 0x58, 0xb0, 0xe1, 0x03, + 0xd8, 0xb3, 0xe0, 0x23, 0xba, 0x42, 0x15, 0x2b, 0xd4, 0x45, 0x84, 0xda, 0x05, 0xbf, 0x81, 0xe2, + 0x47, 0x4d, 0xab, 0x56, 0x0d, 0x2c, 0xd8, 0xf9, 0xce, 0x3d, 0xf7, 0x9e, 0x3b, 0xc7, 0xe7, 0x0e, + 0xb8, 0x6e, 0x21, 0x6b, 0xe0, 0xd3, 0x50, 0x7d, 0x47, 0x42, 0xe4, 0x13, 0x3e, 0x50, 0xfb, 0x8b, + 0x2a, 0xdf, 0x56, 0xa2, 0x98, 0x72, 0x0a, 0xaf, 0xe4, 0x59, 0xa5, 0xc8, 0x2a, 0xfd, 0x45, 0x69, + 0x1a, 0x53, 0x4c, 0xd3, 0xbc, 0x3a, 0xfa, 0xca, 0xa0, 0xd2, 0xac, 0x4d, 0x59, 0x40, 0x99, 0x99, + 0x25, 0xb2, 0x20, 0x4f, 0xcd, 0x64, 0x91, 0x1a, 0x30, 0x3c, 0xea, 0x1e, 0x30, 0x9c, 0x27, 0xda, + 0x27, 0x91, 0x47, 0x28, 0x46, 0x41, 0x5e, 0xda, 0xf9, 0x54, 0x05, 0xa0, 0xcb, 0xf0, 0xba, 0xe3, + 0xbc, 0xa2, 0xdc, 0x85, 0x57, 0xc1, 0x04, 0x23, 0x38, 0x74, 0x63, 0x51, 0x68, 0x0b, 0x73, 0x93, + 0x7a, 0x1e, 0xc1, 0x4d, 0x00, 0xfa, 0xc8, 0x37, 0x2d, 0x6e, 0x9b, 0xd1, 0x96, 0x58, 0x6d, 0x0b, + 0x73, 0x4d, 0x6d, 0x75, 0x6f, 0xd8, 0x5a, 0xc2, 0x84, 0x7b, 0x89, 0xa5, 0xd8, 0x34, 0x50, 0x73, + 0x2e, 0xdb, 0x43, 0x24, 0x2c, 0x02, 0x95, 0x0f, 0x22, 0x97, 0x29, 0xda, 0xd3, 0xde, 0xf2, 0xca, + 0x42, 0x2f, 0xb1, 0x36, 0xdc, 0x81, 0x7e, 0xa1, 0x8f, 0x7c, 0x8d, 0xdb, 0xbd, 0x2d, 0x78, 0x13, + 0x34, 0x2d, 0x9f, 0xda, 0x5b, 0xa6, 0xe7, 0x12, 0xec, 0x71, 0xb1, 0xd6, 0x16, 0xe6, 0xea, 0x7a, + 0x23, 0x3d, 0x7b, 0x92, 0x1e, 0xc1, 0x1b, 0x00, 0xe4, 0x10, 0xc4, 0x3c, 0xb1, 0x3e, 0x22, 0xd6, + 0x27, 0x33, 0x00, 0x62, 0x1e, 0x7c, 0x03, 0x9a, 0xc5, 0xd5, 0x4c, 0x46, 0xb0, 0x78, 0x2e, 0x9d, + 0xec, 0xc1, 0xde, 0xb0, 0xb5, 0x32, 0xde, 0x64, 0x86, 0xed, 0x85, 0x34, 0x8e, 0x1f, 0xbf, 0xd8, + 0x34, 0x0c, 0x82, 0xf5, 0x46, 0xd1, 0xcd, 0x20, 0xb8, 0x33, 0x0d, 0x60, 0x29, 0x8d, 0xee, 0xb2, + 0x88, 0x86, 0xcc, 0xed, 0x7c, 0xab, 0x82, 0xcb, 0x5d, 0x86, 0x1f, 0xd1, 0x20, 0x20, 0xbc, 0x97, + 0x58, 0x3a, 0x0a, 0x9d, 0xff, 0xaf, 0x1b, 0xe3, 0x28, 0xe6, 0xc7, 0x74, 0x4b, 0xcf, 0x72, 0xdd, + 0xde, 0x82, 0xa9, 0x28, 0xb1, 0xcc, 0x18, 0x85, 0x8e, 0xe9, 0x13, 0xc6, 0xc5, 0x7a, 0xbb, 0xf6, + 0x4f, 0xca, 0xe4, 0x37, 0xd4, 0x1b, 0x51, 0xf6, 0xf1, 0x9c, 0x30, 0x0e, 0x37, 0x40, 0xad, 0x54, + 0x7b, 0x6d, 0x6f, 0xd8, 0xba, 0xff, 0x37, 0xf7, 0x31, 0x08, 0x0e, 0x11, 0x4f, 0x62, 0x57, 0x1f, + 0x75, 0xe9, 0x48, 0x40, 0x3c, 0xae, 0xe7, 0xa1, 0xd8, 0x1f, 0x05, 0x70, 0xa9, 0xcb, 0xf0, 0xcb, + 0xc8, 0x41, 0xdc, 0xed, 0xa5, 0xc6, 0x85, 0xab, 0x60, 0x12, 0x25, 0xdc, 0xa3, 0x31, 0xe1, 0x83, + 0x4c, 0x6e, 0x4d, 0xfc, 0xfe, 0x75, 0x7e, 0x3a, 0x5f, 0x89, 0x75, 0xc7, 0x89, 0x5d, 0xc6, 0x0c, + 0x1e, 0x93, 0x10, 0xeb, 0x25, 0x14, 0xae, 0x81, 0x89, 0xcc, 0xfa, 0xe9, 0x7f, 0x68, 0x2c, 0x5d, + 0x53, 0x4e, 0x58, 0x3e, 0x25, 0x23, 0xd1, 0xea, 0x3b, 0xc3, 0x56, 0x45, 0xcf, 0x0b, 0x1e, 0x5e, + 0x7c, 0xff, 0xeb, 0xcb, 0xdd, 0xb2, 0x55, 0x67, 0x16, 0xcc, 0x1c, 0x9b, 0xaa, 0x98, 0x78, 0xe9, + 0x73, 0x15, 0xd4, 0xba, 0x0c, 0x43, 0x03, 0x9c, 0x2f, 0x96, 0xaa, 0x75, 0x22, 0x51, 0x69, 0x2d, + 0xe9, 0xce, 0x19, 0x80, 0xa2, 0x39, 0x74, 0xc1, 0xd4, 0x51, 0xdf, 0xdd, 0x3e, 0xad, 0xf2, 0x08, + 0x4c, 0x9a, 0x1f, 0x0b, 0x76, 0x48, 0x63, 0x81, 0xe6, 0x11, 0xc5, 0x6f, 0x9d, 0x56, 0xfe, 0x27, + 0x4a, 0xba, 0x37, 0x0e, 0xaa, 0xe0, 0xd0, 0x9e, 0xed, 0xec, 0xcb, 0xc2, 0xee, 0xbe, 0x2c, 0xfc, + 0xdc, 0x97, 0x85, 0x0f, 0x07, 0x72, 0x65, 0xf7, 0x40, 0xae, 0xfc, 0x38, 0x90, 0x2b, 0xaf, 0x17, + 0xce, 0xf2, 0xd2, 0x76, 0xf9, 0x9c, 0xa5, 0xb6, 0xb2, 0x26, 0xd2, 0xb7, 0x6c, 0xf9, 0x77, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc1, 0xb9, 0xb9, 0xd7, 0x6c, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -170,6 +402,11 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + // AddVote adds a vote to a given block + AddVote(ctx context.Context, in *MsgAddVote, opts ...grpc.CallOption) (*MsgAddVoteResponse, error) + // CommitPubRand commits a list of public randomness for EOTS + CommitPubRand(ctx context.Context, in *MsgCommitPubRand, opts ...grpc.CallOption) (*MsgCommitPubRandResponse, error) + // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -182,6 +419,24 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) AddVote(ctx context.Context, in *MsgAddVote, opts ...grpc.CallOption) (*MsgAddVoteResponse, error) { + out := new(MsgAddVoteResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/AddVote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) CommitPubRand(ctx context.Context, in *MsgCommitPubRand, opts ...grpc.CallOption) (*MsgCommitPubRandResponse, error) { + out := new(MsgCommitPubRandResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/CommitPubRand", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/UpdateParams", in, out, opts...) @@ -193,6 +448,11 @@ func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts // MsgServer is the server API for Msg service. type MsgServer interface { + // AddVote adds a vote to a given block + AddVote(context.Context, *MsgAddVote) (*MsgAddVoteResponse, error) + // CommitPubRand commits a list of public randomness for EOTS + CommitPubRand(context.Context, *MsgCommitPubRand) (*MsgCommitPubRandResponse, error) + // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } @@ -201,6 +461,12 @@ type MsgServer interface { type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) AddVote(ctx context.Context, req *MsgAddVote) (*MsgAddVoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddVote not implemented") +} +func (*UnimplementedMsgServer) CommitPubRand(ctx context.Context, req *MsgCommitPubRand) (*MsgCommitPubRandResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CommitPubRand not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -209,6 +475,42 @@ func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_AddVote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddVote) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AddVote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Msg/AddVote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AddVote(ctx, req.(*MsgAddVote)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_CommitPubRand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCommitPubRand) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CommitPubRand(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Msg/CommitPubRand", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CommitPubRand(ctx, req.(*MsgCommitPubRand)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -231,6 +533,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.finality.v1.Msg", HandlerType: (*MsgServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "AddVote", + Handler: _Msg_AddVote_Handler, + }, + { + MethodName: "CommitPubRand", + Handler: _Msg_CommitPubRand_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -240,7 +550,7 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Metadata: "babylon/finality/v1/tx.proto", } -func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { +func (m *MsgAddVote) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -250,37 +560,63 @@ func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddVote) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.FinalitySig != nil { + { + size := m.FinalitySig.Size() + i -= size + if _, err := m.FinalitySig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a } - i-- - dAtA[i] = 0x12 - if len(m.Authority) > 0 { - i -= len(m.Authority) - copy(dAtA[i:], m.Authority) - i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + if len(m.BlockHash) > 0 { + i -= len(m.BlockHash) + copy(dAtA[i:], m.BlockHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.BlockHash))) + i-- + dAtA[i] = 0x22 + } + if m.BlockHeight != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.BlockHeight)) + i-- + dAtA[i] = 0x18 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgAddVoteResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -290,12 +626,12 @@ func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddVoteResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -303,47 +639,793 @@ func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *MsgCommitPubRand) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *MsgUpdateParams) Size() (n int) { - if m == nil { - return 0 - } + +func (m *MsgCommitPubRand) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCommitPubRand) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Authority) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) + if m.Sig != nil { + { + size := m.Sig.Size() + i -= size + if _, err := m.Sig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a } - l = m.Params.Size() - n += 1 + l + sovTx(uint64(l)) - return n + if len(m.PubRandList) > 0 { + for iNdEx := len(m.PubRandList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.PubRandList[iNdEx].Size() + i -= size + if _, err := m.PubRandList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if m.StartHeight != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x18 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } -func (m *MsgUpdateParamsResponse) Size() (n int) { - if m == nil { - return 0 +func (m *MsgCommitPubRandResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } + return dAtA[:n], nil +} + +func (m *MsgCommitPubRandResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCommitPubRandResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - return n + return len(dAtA) - i, nil } -func sovTx(x uint64) (n int) { +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgAddVote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.BlockHeight != 0 { + n += 1 + sovTx(uint64(m.BlockHeight)) + } + l = len(m.BlockHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.FinalitySig != nil { + l = m.FinalitySig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAddVoteResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgCommitPubRand) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.StartHeight != 0 { + n += 1 + sovTx(uint64(m.StartHeight)) + } + if len(m.PubRandList) > 0 { + for _, e := range m.PubRandList { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + if m.Sig != nil { + l = m.Sig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCommitPubRandResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *MsgAddVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAddVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) + } + m.BlockHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BlockHash = append(m.BlockHash[:0], dAtA[iNdEx:postIndex]...) + if m.BlockHash == nil { + m.BlockHash = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FinalitySig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.SchnorrEOTSSig + m.FinalitySig = &v + if err := m.FinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAddVoteResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAddVoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddVoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCommitPubRand) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCommitPubRand: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCommitPubRand: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PubRandList", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.SchnorrPubRand + m.PubRandList = append(m.PubRandList, v) + if err := m.PubRandList[len(m.PubRandList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.Sig = &v + if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCommitPubRandResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCommitPubRandResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCommitPubRandResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From dff6bbb29ea24d68bc0c1a32f781175943b24809 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 3 Jul 2023 16:22:54 +1000 Subject: [PATCH 021/202] chore: merge EOTS and re-enable e2e (#15) --- .circleci/config.yml | 44 ++-- crypto/eots/README.md | 3 + crypto/eots/eots.go | 231 +++++++++++++++++++ crypto/eots/eots_test.go | 124 ++++++++++ crypto/eots/error.go | 20 ++ crypto/eots/examples/btc-testnet-txs/main.go | 182 +++++++++++++++ go.mod | 4 +- go.sum | 14 +- testutil/datagen/finality.go | 6 +- x/finality/types/msg.go | 2 +- x/finality/types/msg_test.go | 2 +- 11 files changed, 593 insertions(+), 39 deletions(-) create mode 100644 crypto/eots/README.md create mode 100644 crypto/eots/eots.go create mode 100644 crypto/eots/eots_test.go create mode 100644 crypto/eots/error.go create mode 100644 crypto/eots/examples/btc-testnet-txs/main.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 5581c82bd..758aaa2d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,12 +33,10 @@ jobs: name: Run tests command: | make test - # TODO: re-enable after EOTS library becomes public. Currently building Babylon-private - # requires retrieving the private EOTS library, which is challenging in Circle CI - # - run: - # name: Run e2e tests - # command: | - # make test-e2e + - run: + name: Run e2e tests + command: | + make test-e2e lint: machine: image: ubuntu-2204:2022.10.1 @@ -111,21 +109,19 @@ workflows: lint: jobs: - lint - # TODO: re-enable after EOTS library becomes public. Currently building Babylon-private - # requires retrieving the private EOTS library, which is challenging in Circle CI - # docker: - # jobs: - # - build_docker: - # filters: - # tags: - # only: /.*/ - # - push_docker: - # requires: - # - build_docker - # filters: - # tags: - # only: /.*/ - # branches: - # only: - # - main - # - dev + docker: + jobs: + - build_docker: + filters: + tags: + only: /.*/ + - push_docker: + requires: + - build_docker + filters: + tags: + only: /.*/ + branches: + only: + - main + - dev diff --git a/crypto/eots/README.md b/crypto/eots/README.md new file mode 100644 index 000000000..e2ff53a68 --- /dev/null +++ b/crypto/eots/README.md @@ -0,0 +1,3 @@ +# EOTS + +This module implements extractable one-time signature (EOTS). The code is copied from https://github.com/babylonchain/eots. \ No newline at end of file diff --git a/crypto/eots/eots.go b/crypto/eots/eots.go new file mode 100644 index 000000000..f94e66786 --- /dev/null +++ b/crypto/eots/eots.go @@ -0,0 +1,231 @@ +package eots + +import ( + "crypto/sha256" + "errors" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +type ModNScalar = btcec.ModNScalar +type PrivateKey = secp256k1.PrivateKey +type PublicKey = secp256k1.PublicKey +type PrivateRand = secp256k1.ModNScalar +type PublicRand = secp256k1.FieldVal + +// The Signature is only the S part of the BEP-340 Schnorr signatures. +type Signature = ModNScalar + +// KeyGen generates private key from a randomness source +func KeyGen(randSource io.Reader) (*PrivateKey, error) { + return secp256k1.GeneratePrivateKeyFromRand(randSource) +} + +// PubGen returns the associated public key from a private key. +func PubGen(k *PrivateKey) *PublicKey { + return k.PubKey() +} + +// RandGen returns the value to be used as random value when signing, and the associated public value. +func RandGen(randSource io.Reader) (*PrivateRand, *PublicRand, error) { + pk, err := KeyGen(randSource) + if err != nil { + return nil, nil, err + } + var j secp256k1.JacobianPoint + pk.PubKey().AsJacobian(&j) + return &pk.Key, &j.X, nil +} + +// hash function is used for hashing the message input for all functions of the library. +// Wrapper around sha256 in order to change only one function if the input hashing function is changed. +func hash(message []byte) [32]byte { + return sha256.Sum256(message) +} + +// Sign returns an extractable Schnorr signature for a message, signed with a private key and private randomness value. +// Note that the Signature is only the second (S) part of the typical bitcoin signature, the first (R) can be deduced from +// the public randomness value and the message. +func Sign(sk *PrivateKey, privateRand *PrivateRand, message []byte) (*Signature, error) { + h := hash(message) + return signHash(sk, privateRand, h) +} + +// signHash returns an extractable Schnorr signature for a hashed message. +// The caller MUST ensure that hash is the output of a cryptographically secure hash function. +// Based on unexported schnorrSign of btcd. +func signHash(sk *PrivateKey, privateRand *PrivateRand, hash [32]byte) (*Signature, error) { + if sk.Key.IsZero() { + str := "private key is zero" + return nil, signatureError(ecdsa_schnorr.ErrPrivateKeyIsZero, str) + } + + // d' = int(d) + var privKeyScalar ModNScalar + privKeyScalar.Set(&sk.Key) + + pubKey := PubGen(sk) + + // Negate d if P.y is odd. + pubKeyBytes := pubKey.SerializeCompressed() + if pubKeyBytes[0] == secp256k1.PubKeyFormatCompressedOdd { + privKeyScalar.Negate() + } + + k := new(ModNScalar).Set(privateRand) + + // R = kG + var R btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(k, &R) + + // Negate nonce k if R.y is odd (R.y is the y coordinate of the point R) + // + // Note that R must be in affine coordinates for this check. + R.ToAffine() + if R.Y.IsOdd() { + k.Negate() + } + + // e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n + var rBytes [32]byte + r := &R.X + r.PutBytesUnchecked(rBytes[:]) + pBytes := pubKey.SerializeCompressed()[1:] + + commitment := chainhash.TaggedHash(chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash[:]) + + var e ModNScalar + if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 { + k.Zero() + str := "hash of (r || P || m) too big" + return nil, signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str) + } + + // s = k + e*d mod n + sig := new(ModNScalar).Mul2(&e, &privKeyScalar).Add(k) + + // If Verify(bytes(P), m, sig) fails, abort. + // optional + + // Return s + return sig, nil +} + +// Verify verifies that the signature is valid for this message, public key and random value. +func Verify(pubKey *PublicKey, r *PublicRand, message []byte, sig *Signature) error { + h := hash(message) + return verifyHash(pubKey, r, h, sig) +} + +// Verify verifies that the signature is valid for this hashed message, public key and random value. +// Based on unexported schnorrVerify of btcd. +func verifyHash(pubKey *PublicKey, r *PublicRand, hash [32]byte, sig *Signature) error { + // Fail if P is not a point on the curve + if !pubKey.IsOnCurve() { + str := "pubkey point is not on curve" + return signatureError(ecdsa_schnorr.ErrPubKeyNotOnCurve, str) + } + + // Fail if r >= p is already handled by the fact r is a field element. + // Fail if s >= n is already handled by the fact s is a mod n scalar. + + // e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n. + var rBytes [32]byte + r.PutBytesUnchecked(rBytes[:]) + pBytes := pubKey.SerializeCompressed()[1:] + + commitment := chainhash.TaggedHash(chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash[:]) + + var e ModNScalar + if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 { + str := "hash of (r || P || m) too big" + return signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str) + } + + // Negate e here so we can use AddNonConst below to subtract the s*G + // point from e*P. + e.Negate() + + // R = s*G - e*P + var P, R, sG, eP btcec.JacobianPoint + pubKey.AsJacobian(&P) + btcec.ScalarBaseMultNonConst(sig, &sG) + btcec.ScalarMultNonConst(&e, &P, &eP) + btcec.AddNonConst(&sG, &eP, &R) + + // Fail if R is the point at infinity + if (R.X.IsZero() && R.Y.IsZero()) || R.Z.IsZero() { + str := "calculated R point is the point at infinity" + return signatureError(ecdsa_schnorr.ErrSigRNotOnCurve, str) + } + + // Fail if R.y is odd + // + // Note that R must be in affine coordinates for this check. + R.ToAffine() + if R.Y.IsOdd() { + str := "calculated R y-value is odd" + return signatureError(ecdsa_schnorr.ErrSigRYIsOdd, str) + } + + // verify signed with the right k random value + if !r.Equals(&R.X) { + str := "calculated R point was not given R" + return signatureError(ecdsa_schnorr.ErrUnequalRValues, str) + } + + return nil +} + +// Extract extracts the private key from a public key and signatures for two distinct hashes messages. +func Extract(pubKey *PublicKey, r *PublicRand, message1 []byte, sig1 *Signature, message2 []byte, sig2 *Signature) (*PrivateKey, error) { + h1 := hash(message1) + h2 := hash(message2) + return extractFromHashes(pubKey, r, h1, sig1, h2, sig2) +} + +// extractFromHashes extracts the private key from hashes, instead of the non-hashed message directly as Extract does. +func extractFromHashes(pubKey *PublicKey, r *PublicRand, hash1 [32]byte, sig1 *Signature, hash2 [32]byte, sig2 *Signature) (*PrivateKey, error) { + var rBytes [32]byte + r.PutBytesUnchecked(rBytes[:]) + pBytes := pubKey.SerializeCompressed()[1:] + + if sig1.Equals(sig2) { + return nil, errors.New("The two signatures need to be different in order to extract") + } + + commitment1 := chainhash.TaggedHash(chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash1[:]) + var e1 ModNScalar + if overflow := e1.SetBytes((*[32]byte)(commitment1)); overflow != 0 { + str := "hash of (r || P || m1) too big" + return nil, signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str) + } + + commitment2 := chainhash.TaggedHash(chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash2[:]) + var e2 ModNScalar + if overflow := e2.SetBytes((*[32]byte)(commitment2)); overflow != 0 { + str := "hash of (r || P || m2) too big" + return nil, signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str) + } + + // x = (s1 - s2) / (e1 - e2) + var x, denom ModNScalar + denom.Add2(&e1, e2.Negate()) + x.Add2(sig1, sig2.Negate()).Mul(denom.InverseNonConst()) + + pubKeyBytes := pubKey.SerializeCompressed() + if pubKeyBytes[0] == secp256k1.PubKeyFormatCompressedOdd { + x.Negate() + } + + privKey := secp256k1.NewPrivateKey(&x) + if privKey.PubKey().IsEqual(pubKey) { + return privKey, nil + } else { + return privKey, errors.New("Extracted private key does not match public key") + } +} diff --git a/crypto/eots/eots_test.go b/crypto/eots/eots_test.go new file mode 100644 index 000000000..9adc8df8f --- /dev/null +++ b/crypto/eots/eots_test.go @@ -0,0 +1,124 @@ +package eots + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/vulpine-io/io-test/v1/pkg/iotest" +) + +// TODO: possible improvements +// test KeyGen, PubGen, RandGen give consistent results with deterministic randomness source +// test compare signatures against btcec + +func FuzzSignAndVerify(f *testing.F) { + randSource := new(iotest.ReadCloser) + + sk, err := KeyGen(randSource) + if err != nil { + f.Fatal(err) + } + k, publicK, err := RandGen(rand.Reader) + if err != nil { + f.Fatal(err) + } + + for _, seed := range [][]byte{[]byte("hello"), []byte("1234567890!@#$%^&*()")} { + f.Add(seed) + } + f.Fuzz(func(t *testing.T, message []byte) { + sig, err := Sign(sk, k, message) + if err != nil { + t.Fatal(err) + } + err = Verify(PubGen(sk), publicK, message, sig) + if err != nil { + t.Fatal(err) + } + }) +} + +func TestSignAndInvalidVerify(t *testing.T) { + randSource := new(iotest.ReadCloser) + sk, err := KeyGen(randSource) + if err != nil { + t.Fatal(err) + } + k, publicK, err := RandGen(randSource) + if err != nil { + t.Fatal(err) + } + + message := []byte("hello") + + sig, err := Sign(sk, k, message) + if err != nil { + t.Fatal(err) + } + + invalidK := new(secp256k1.FieldVal).Set(publicK).AddInt(1) + + err = Verify(PubGen(sk), invalidK, message, sig) + if err == nil { + t.Fatal("Expected verify to fail with wrong k value") + } + + err = Verify(PubGen(sk), publicK, message, new(Signature)) + if err == nil { + t.Fatal("Expected verify to fail with wrong signature for the hash") + } + + messageInvalid := []byte("bye") + err = Verify(PubGen(sk), publicK, messageInvalid, sig) + if err == nil { + t.Fatal("Expected verify to fail with wrong signature for the hash") + } +} + +func FuzzExtract(f *testing.F) { + randSource := new(iotest.ReadCloser) + sk, err := KeyGen(randSource) + if err != nil { + f.Fatal(err) + } + k, publicK, err := RandGen(rand.Reader) + if err != nil { + f.Fatal(err) + } + + type tc struct { + m1 []byte + m2 []byte + } + + for _, seed := range []tc{{[]byte("hello"), []byte("bye")}, {[]byte("1234567890"), []byte("!@#$%^&*()")}} { + f.Add(seed.m1, seed.m2) + } + + f.Fuzz(func(t *testing.T, message1, message2 []byte) { + if bytes.Equal(message1, message2) { + t.Skip() + } + + sig1, err := Sign(sk, k, message1) + if err != nil { + t.Fatal(err) + } + + sig2, err := Sign(sk, k, message2) + if err != nil { + t.Fatal(err) + } + + sk2, err := Extract(sk.PubKey(), publicK, message1, sig1, message2, sig2) + if err != nil { + t.Fatal(err) + } + + if !sk.Key.Equals(&sk2.Key) { + t.Fatal("Unexpected extracted private key") + } + }) +} diff --git a/crypto/eots/error.go b/crypto/eots/error.go new file mode 100644 index 000000000..831587a2c --- /dev/null +++ b/crypto/eots/error.go @@ -0,0 +1,20 @@ +package eots + +import ( + ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +// ErrorKind identifies a kind of error. It has full support for errors.Is +// and errors.As, so the caller can directly check against an error kind +// when determining the reason for an error. +type ErrorKind = ecdsa_schnorr.ErrorKind + +// Error identifies an error related to a schnorr signature. It has full +// support for errors.Is and errors.As, so the caller can ascertain the +// specific reason for the error by checking the underlying error. +type Error = ecdsa_schnorr.Error + +// signatureError creates an Error given a set of arguments. +func signatureError(kind ErrorKind, desc string) Error { + return Error{Err: kind, Description: desc} +} diff --git a/crypto/eots/examples/btc-testnet-txs/main.go b/crypto/eots/examples/btc-testnet-txs/main.go new file mode 100644 index 000000000..2ddec8443 --- /dev/null +++ b/crypto/eots/examples/btc-testnet-txs/main.go @@ -0,0 +1,182 @@ +package main + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "flag" + "fmt" + "os" + + "github.com/babylonchain/babylon/crypto/eots" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +func main() { + initialSecretKey, amount, unspentTxHash, outIndex, fees := parseFlags() + + // generate keys for first tx + fmt.Println("Generating extractable keys (address A)") + secretKey, publicKey, privateRand, publicRand, wif1, address1 := generateKeys() + fmt.Println("Address A: ", address1.EncodeAddress()) + fmt.Println("Address A private key (WIF):", wif1.String()) + + // generate a transaction + fmt.Println("\nGenerating first transaction spendable by extractable key...") + txHash1, tx1, err := generateTx(initialSecretKey, amount, fees, address1, unspentTxHash, outIndex) + if err != nil { + panic(err) + } + fmt.Println("First tx hash: ", txHash1) + fmt.Println("First tx encoded: ", tx1) + + h1, s1, h2, s2 := generateSignatures(secretKey, privateRand, "1", "2") + + // the secret key is no longer used + secretKey = nil + + fmt.Println("---") + fmt.Println("Extracting key...") + + extractedKey, _ := eots.Extract(publicKey, publicRand, h1, s1, h2, s2) + + fmt.Println("Key extracted") + + fmt.Println("Generating keys for address B") + _, _, _, _, wif2, address2 := generateKeys() + fmt.Println("Address B: ", address2.EncodeAddress()) + fmt.Println("Private key for address B: ", wif2.String()) + + fmt.Println("\nGenerating transaction that spends BTC using the extracted key...") + txHash2, tx2, err := generateTx(extractedKey, amount-fees, fees, address2, txHash1, 0) + if err != nil { + panic(err) + } + fmt.Println("Second tx hash:", txHash2) + fmt.Println("Second tx encoded:", tx2) +} + +func generateKeys() (*eots.PrivateKey, *eots.PublicKey, *eots.PrivateRand, *eots.PublicRand, *btcutil.WIF, *btcutil.AddressPubKeyHash) { + secretKey, err := eots.KeyGen(rand.Reader) + if err != nil { + panic(err) + } + + publicKey := eots.PubGen(secretKey) + + privateRand, publicRand, err := eots.RandGen(rand.Reader) + if err != nil { + panic(err) + } + + wif, err := btcutil.NewWIF(secretKey, &chaincfg.TestNet3Params, true) + if err != nil { + panic(err) + } + + address, err := btcutil.NewAddressPubKeyHash(btcutil.Hash160(publicKey.SerializeCompressed()), &chaincfg.TestNet3Params) + if err != nil { + panic(err) + } + + return secretKey, publicKey, privateRand, publicRand, wif, address +} + +func generateSignatures(secretKey *eots.PrivateKey, privateRand *eots.PrivateRand, message1, message2 string) ([]byte, *eots.Signature, []byte, *eots.Signature) { + h1 := chainhash.HashB([]byte(message1)) + s1, _ := eots.Sign(secretKey, privateRand, h1) + + h2 := chainhash.HashB([]byte(message2)) + s2, _ := eots.Sign(secretKey, privateRand, h2) + + return h1, s1, h2, s2 +} + +func generateTx(senderPk *eots.PrivateKey, amount int64, fees int64, rcvAddress *btcutil.AddressPubKeyHash, unspentTxId *chainhash.Hash, outIndex uint32) (*chainhash.Hash, string, error) { + recTx := wire.NewMsgTx(wire.TxVersion) + + outPoint := wire.NewOutPoint(unspentTxId, outIndex) + txIn := wire.NewTxIn(outPoint, nil, nil) + recTx.AddTxIn(txIn) + + rcvScript2, err := txscript.PayToAddrScript(rcvAddress) + if err != nil { + return nil, "", err + } + outCoin := int64(amount - fees) + txOut := wire.NewTxOut(outCoin, rcvScript2) + recTx.AddTxOut(txOut) + + senderAddress, err := btcutil.NewAddressPubKeyHash(btcutil.Hash160(senderPk.PubKey().SerializeCompressed()), &chaincfg.TestNet3Params) + rcvScript, err := txscript.PayToAddrScript(senderAddress) + if err != nil { + return nil, "", err + } + + scriptSig, err := txscript.SignatureScript( + recTx, + 0, + rcvScript, + txscript.SigHashAll, + senderPk, + true) + if err != nil { + return nil, "", err + } + recTx.TxIn[0].SignatureScript = scriptSig + + buf := bytes.NewBuffer(make([]byte, 0, recTx.SerializeSize())) + recTx.Serialize(buf) + + // verify transaction + vm, err := txscript.NewEngine(rcvScript, recTx, 0, txscript.StandardVerifyFlags, nil, nil, amount, nil) + if err != nil { + return nil, "", err + } + if err := vm.Execute(); err != nil { + return nil, "", err + } + + hash := recTx.TxHash() + return &hash, hex.EncodeToString(buf.Bytes()), nil +} + +func parseFlags() (*eots.PrivateKey, int64, *chainhash.Hash, uint32, int64) { + keyWif := flag.String("key", "", "Private key used for spending utxo") + + amount := flag.Int64("amount", 0, "Amount available to spend (including fees)") + + unspentTxId := flag.String("tx", "", "Hash of unspent transaction") + + outIndex := flag.Int("outIndex", 0, "Output index of unspent transaction to spend (default 0)") + + fees := flag.Int64("fees", 1000, "Amount to be left for fees in each of the two transactions (default: 1000 satoshi)") + + flag.Parse() + + if *keyWif == "" { + fmt.Println("Please provide key (-key=WIF_ENCODED_KEY)") + os.Exit(1) + } + if *amount == 0 { + fmt.Println("Please provide amount (-amount=AMOUNT)") + os.Exit(1) + } + if *unspentTxId == "" { + fmt.Println("Please provide unspent transaction hash (-tx=HASH)") + os.Exit(1) + } + + wif, err := btcutil.DecodeWIF(*keyWif) + + unspentTxHash, err := chainhash.NewHashFromStr(*unspentTxId) + if err != nil { + panic(err) + } + + return wif.PrivKey, *amount, unspentTxHash, uint32(*outIndex), *fees +} diff --git a/go.mod b/go.mod index dd5329519..36346ef0d 100644 --- a/go.mod +++ b/go.mod @@ -36,9 +36,11 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/gogoproto v1.4.8 github.com/cosmos/ibc-go/v7 v7.0.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/golang/mock v1.6.0 github.com/jinzhu/copier v0.3.5 github.com/ory/dockertest/v3 v3.9.1 + github.com/vulpine-io/io-test v1.0.0 google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc google.golang.org/protobuf v1.30.0 ) @@ -134,7 +136,6 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect - github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -147,7 +148,6 @@ require ( github.com/creachadair/taskgroup v0.4.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/cli v20.10.21+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v20.10.24+incompatible // indirect diff --git a/go.sum b/go.sum index 753a2e03e..beedb10c9 100644 --- a/go.sum +++ b/go.sum @@ -250,8 +250,6 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1 h1:nJDhs+WYMF9cFUPWSagchCLJifEL4DZlRm8URrGL+h8= -github.com/babylonchain/eots v0.0.0-20230524134443-91d51839f8f1/go.mod h1:P/P4Psf9o0V4yO7CgjUEXrbPs6ROfUorsZ2EYEZxzoI= github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf h1:NJU3YuruPqV8w6/y45Zsb8FudcWSkTBugdpTT7kJmjw= github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf/go.mod h1:BFh8nKWjr5zeR2OZfhkzdgDzj1+KjRn3aJLpwapStj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -276,12 +274,9 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= -github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -395,13 +390,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= @@ -631,6 +623,7 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -737,6 +730,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -951,7 +945,9 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -1025,6 +1021,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vulpine-io/io-test v1.0.0 h1:Ot8vMh+ssm1VWDAwJ3U4C5qG9aRnr5YfQFZPNZBAUGI= +github.com/vulpine-io/io-test v1.0.0/go.mod h1:X1I+p5GCxVX9m4nFd1HBtr2bVX9v1ZE6x8w+Obt36AU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index a06a1a6a0..1265c055b 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -3,19 +3,19 @@ package datagen import ( "math/rand" + "github.com/babylonchain/babylon/crypto/eots" bbn "github.com/babylonchain/babylon/types" ftypes "github.com/babylonchain/babylon/x/finality/types" - "github.com/babylonchain/eots" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" ) -func GenRandomMsgAddVote(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgAddVote, *bbn.SchnorrPubRand, error) { +func GenRandomMsgAddVote(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgAddVote, *bbn.SchnorrPubRand, error) { msg := &ftypes.MsgAddVote{ Signer: GenRandomAccount().Address, ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), BlockHeight: RandomInt(r, 100), - BlockHash: GenRandomByteArray(r, 32), + BlockHash: GenRandomByteArray(r, 32), } msgToSign := msg.MsgToSign() sr, pr, err := eots.RandGen(r) diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index d3fd52396..ca41b72b9 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -4,8 +4,8 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/crypto/eots" bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/eots" "github.com/cometbft/cometbft/crypto/tmhash" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/finality/types/msg_test.go b/x/finality/types/msg_test.go index 389151d4b..c3fe77348 100644 --- a/x/finality/types/msg_test.go +++ b/x/finality/types/msg_test.go @@ -4,8 +4,8 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/testutil/datagen" - "github.com/babylonchain/eots" "github.com/stretchr/testify/require" ) From 27549e3f1b4bacf8bbb4e520037c063cd2846b29 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 4 Jul 2023 16:59:15 +1000 Subject: [PATCH 022/202] finality tracker: handler for `MsgCommitPubRand` and `MsgAddVote` (#16) --- Makefile | 1 + proto/babylon/finality/v1/finality.proto | 4 +- proto/babylon/finality/v1/tx.proto | 28 +- testutil/datagen/btcstaking.go | 4 +- testutil/datagen/finality.go | 43 +-- testutil/keeper/finality.go | 4 +- types/btc_schnorr_eots.go | 5 + x/btcstaking/keeper/btc_validators.go | 6 +- x/btcstaking/keeper/msg_server_test.go | 2 +- x/finality/genesis_test.go | 2 +- x/finality/keeper/indexed_blocks.go | 6 +- x/finality/keeper/msg_server.go | 102 ++++++- x/finality/keeper/msg_server_test.go | 166 ++++++++++- x/finality/keeper/params_test.go | 2 +- x/finality/keeper/public_randomness.go | 37 +++ x/finality/keeper/query_params_test.go | 2 +- x/finality/types/codec.go | 8 +- x/finality/types/errors.go | 1 + x/finality/types/expected_keepers.go | 3 +- x/finality/types/finality.go | 21 +- x/finality/types/finality.pb.go | 47 +-- x/finality/types/mocked_keepers.go | 138 +++++++++ x/finality/types/msg.go | 53 ++-- x/finality/types/msg_test.go | 19 +- x/finality/types/params.go | 4 +- x/finality/types/tx.pb.go | 363 ++++++++++++----------- 26 files changed, 761 insertions(+), 310 deletions(-) create mode 100644 x/finality/types/mocked_keepers.go diff --git a/Makefile b/Makefile index 982889e0e..8de00a1aa 100644 --- a/Makefile +++ b/Makefile @@ -150,6 +150,7 @@ mocks: $(MOCKS_DIR) $(mockgen_cmd) -source=x/checkpointing/keeper/bls_signer.go -package mocks -destination testutil/mocks/bls_signer.go $(mockgen_cmd) -source=x/zoneconcierge/types/expected_keepers.go -package types -destination x/zoneconcierge/types/mocked_keepers.go $(mockgen_cmd) -source=x/btcstaking/types/expected_keepers.go -package types -destination x/btcstaking/types/mocked_keepers.go + $(mockgen_cmd) -source=x/finality/types/expected_keepers.go -package types -destination x/finality/types/mocked_keepers.go .PHONY: mocks $(MOCKS_DIR): diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index 65f2de81f..304fca71f 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -7,8 +7,8 @@ option go_package = "github.com/babylonchain/babylon/x/finality/types"; message IndexedBlock { // height is the height of the block uint64 height = 1; - // hash is the hash of the block - bytes hash = 2; + // last_commit_hash is the last_commit_hash of the block + bytes last_commit_hash = 2; // finalized indicates whether the IndexedBlock is finalised by 2/3 // BTC validators or not bool finalized = 3; diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index f28802ed3..a7a1e3c64 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -10,35 +10,35 @@ import "babylon/finality/v1/params.proto"; // Msg defines the Msg service. service Msg { - // AddVote adds a vote to a given block - rpc AddVote(MsgAddVote) returns (MsgAddVoteResponse); - // CommitPubRand commits a list of public randomness for EOTS - rpc CommitPubRand(MsgCommitPubRand) returns (MsgCommitPubRandResponse); + // AddFinalitySig adds a finality signature to a given block + rpc AddFinalitySig(MsgAddFinalitySig) returns (MsgAddFinalitySigResponse); + // CommitPubRandList commits a list of public randomness for EOTS + rpc CommitPubRandList(MsgCommitPubRandList) returns (MsgCommitPubRandListResponse); // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } -// MsgAddVote defines a message for adding a vote -message MsgAddVote { +// MsgAddFinalitySig defines a message for adding a vote +message MsgAddFinalitySig { string signer = 1; // val_btc_pk is the BTC Pk of the validator that casts this vote bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // block_height is the height of the voted block uint64 block_height = 3; - // block_hash is the hash of the voted block - bytes block_hash = 4; + // block_last_commit_hash is the last_commit_hash of the voted block + bytes block_last_commit_hash = 4; // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` // `r` is the public randomness that is already committed by the validator bytes finality_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; } -// MsgAddVoteResponse is the response to the MsgAddVote message -message MsgAddVoteResponse{} +// MsgAddFinalitySigResponse is the response to the MsgAddFinalitySig message +message MsgAddFinalitySigResponse{} -// MsgCommitPubRand defines a message for committing a list of public randomness for EOTS -message MsgCommitPubRand { +// MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS +message MsgCommitPubRandList { string signer = 1; // val_btc_pk is the BTC Pk of the validator that commits the public randomness bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; @@ -53,8 +53,8 @@ message MsgCommitPubRand { // the tx submitter to be the holder of val_btc_pk. Decide this later bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// MsgCommitPubRandResponse is the response to the MsgCommitPubRand message -message MsgCommitPubRandResponse{} +// MsgCommitPubRandListResponse is the response to the MsgCommitPubRandList message +message MsgCommitPubRandListResponse{} // MsgUpdateParams defines a message for updating finality module parameters. message MsgUpdateParams { diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index e48de5697..c1b6b6574 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -23,10 +23,10 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { if err != nil { return nil, err } - return GenRandomBTCValidatorWithBTCPK(r, btcSK) + return GenRandomBTCValidatorWithBTCSK(r, btcSK) } -func GenRandomBTCValidatorWithBTCPK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { +func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { // key pairs btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index 1265c055b..fa5bbc3a8 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -10,53 +10,34 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" ) -func GenRandomMsgAddVote(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgAddVote, *bbn.SchnorrPubRand, error) { - msg := &ftypes.MsgAddVote{ - Signer: GenRandomAccount().Address, - ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), - BlockHeight: RandomInt(r, 100), - BlockHash: GenRandomByteArray(r, 32), - } - msgToSign := msg.MsgToSign() - sr, pr, err := eots.RandGen(r) - if err != nil { - return nil, nil, err - } - sig, err := eots.Sign(sk, sr, msgToSign) - if err != nil { - return nil, nil, err - } - msg.FinalitySig = bbn.NewSchnorrEOTSSigFromModNScalar(sig) - - return msg, bbn.NewSchnorrPubRandFromFieldVal(pr), nil -} - -func GenRandomMsgCommitPubRand(r *rand.Rand, sk *btcec.PrivateKey) (*ftypes.MsgCommitPubRand, error) { +func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeight uint64, numPubRand uint64) ([]*eots.PrivateRand, *ftypes.MsgCommitPubRandList, error) { + srList := []*eots.PrivateRand{} prList := []bbn.SchnorrPubRand{} - for i := 0; i < 1000; i++ { - prBytes := GenRandomByteArray(r, bbn.SchnorrPubRandLen) - pr, err := bbn.NewSchnorrPubRand(prBytes) + for i := uint64(0); i < numPubRand; i++ { + eotsSR, eotsPR, err := eots.RandGen(r) if err != nil { - return nil, err + return nil, nil, err } + pr := bbn.NewSchnorrPubRandFromFieldVal(eotsPR) + srList = append(srList, eotsSR) prList = append(prList, *pr) } - msg := &ftypes.MsgCommitPubRand{ + msg := &ftypes.MsgCommitPubRandList{ Signer: GenRandomAccount().Address, ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), - StartHeight: RandomInt(r, 100), + StartHeight: startHeight, PubRandList: prList, } hash, err := msg.HashToSign() if err != nil { - return nil, err + return nil, nil, err } schnorrSig, err := schnorr.Sign(sk, hash) if err != nil { - return nil, err + return nil, nil, err } sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) msg.Sig = &sig - return msg, nil + return srList, msg, nil } diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index c06da4e9f..a058d941d 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -18,7 +18,7 @@ import ( "github.com/stretchr/testify/require" ) -func FinalityKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -37,7 +37,7 @@ func FinalityKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { memStoreKey, nil, nil, - nil, + bsKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/types/btc_schnorr_eots.go b/types/btc_schnorr_eots.go index 746766003..acbdcdb91 100644 --- a/types/btc_schnorr_eots.go +++ b/types/btc_schnorr_eots.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -60,3 +61,7 @@ func (sig *SchnorrEOTSSig) Unmarshal(data []byte) error { *sig = data return nil } + +func (sig *SchnorrEOTSSig) Equals(sig2 *SchnorrEOTSSig) bool { + return bytes.Equal(sig.MustMarshal(), sig2.MustMarshal()) +} diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 2863cf531..87b0a17c4 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -29,9 +27,7 @@ func (k Keeper) GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*types.BTCVal } btcValBytes := store.Get(valBTCPK) var btcVal types.BTCValidator - if err := k.cdc.Unmarshal(btcValBytes, &btcVal); err != nil { - return nil, fmt.Errorf("failed to unmarshal BTC validator: %w", err) - } + k.cdc.MustUnmarshal(btcValBytes, &btcVal) return &btcVal, nil } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index bceb777ee..86a67fa45 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -105,7 +105,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { */ validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcVal, err := datagen.GenRandomBTCValidatorWithBTCPK(r, validatorSK) + btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) require.NoError(t, err) msgNewVal := types.MsgCreateBTCValidator{ Signer: datagen.GenRandomAccount().Address, diff --git a/x/finality/genesis_test.go b/x/finality/genesis_test.go index f988265b5..f3ebab922 100644 --- a/x/finality/genesis_test.go +++ b/x/finality/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx := keepertest.FinalityKeeper(t) + k, ctx := keepertest.FinalityKeeper(t, nil) finality.InitGenesis(ctx, *k, genesisState) got := finality.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go index bbc280623..440f30c41 100644 --- a/x/finality/keeper/indexed_blocks.go +++ b/x/finality/keeper/indexed_blocks.go @@ -11,9 +11,9 @@ import ( func (k Keeper) IndexBlock(ctx sdk.Context) { header := ctx.BlockHeader() ib := &types.IndexedBlock{ - Height: uint64(header.Height), - Hash: header.LastCommitHash, - Finalized: false, + Height: uint64(header.Height), + LastCommitHash: header.LastCommitHash, + Finalized: false, } k.setBlock(ctx, ib) } diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index bf38e5b8c..44e0bd1cf 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -1,9 +1,12 @@ package keeper import ( + "bytes" "context" + "fmt" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -35,12 +38,99 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara return &types.MsgUpdateParamsResponse{}, nil } -// AddVote adds a new vote to a given block -func (ms msgServer) AddVote(goCtx context.Context, req *types.MsgAddVote) (*types.MsgAddVoteResponse, error) { - panic("TODO: implement me") +// AddFinalitySig adds a new vote to a given block +func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinalitySig) (*types.MsgAddFinalitySigResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // ensure the BTC validator has voting power at this height + valPK := req.ValBtcPk + if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { + return nil, fmt.Errorf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) + } + + // ensure the BTC validator has not casted the same vote yet + existingSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) + if err == nil && existingSig.Equals(req.FinalitySig) { + return nil, fmt.Errorf("the BTC validator %v has casted the same vote before", valPK.MustMarshal()) + } + + // ensure the BTC validator has committed public randomness + pubRand, err := ms.GetPubRand(ctx, valPK, req.BlockHeight) + if err != nil { + return nil, err + } + + // verify EOTS signature w.r.t. public randomness + valBTCPK, err := valPK.ToBTCPK() + if err != nil { + return nil, err + } + if err := eots.Verify(valBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar()); err != nil { + return nil, err + } + + // verify whether the voted block is a fork or not + indexedBlock, err := ms.GetBlock(ctx, req.BlockHeight) + if err != nil { + return nil, err + } + if !bytes.Equal(indexedBlock.LastCommitHash, req.BlockLastCommitHash) { + // the BTC validator votes for a fork! + sig2, err := ms.GetSig(ctx, req.BlockHeight, valPK) + if err != nil { + return nil, fmt.Errorf("the BTC validator %v votes for a fork, but does not vote for the canonical block", valPK.MustMarshal()) + } + // the BTC validator votes for a fork AND the canonical block + // slash it via extracting its secret key + btcSK, err := eots.Extract(valBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar(), indexedBlock.MsgToSign(), sig2.ToModNScalar()) + if err != nil { + panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) + } + return nil, fmt.Errorf("the BTC validator %v votes two conflicting blocks! extracted secret key: %v", valPK.MustMarshal(), btcSK.Serialize()) + // TODO: what to do with the extracted secret key? e.g., have a KVStore that stores extracted SKs/forked blocks + } + // TODO: it's also possible that the validator votes for a fork first, then vote for canonical + // block. We need to save the signatures on the fork, and add a detection here + + // all good, add vote to DB + ms.setSig(ctx, req.BlockHeight, valPK, req.FinalitySig) + return &types.MsgAddFinalitySigResponse{}, nil } -// CommitPubRand commits a list of EOTS public randomness -func (ms msgServer) CommitPubRand(goCtx context.Context, req *types.MsgCommitPubRand) (*types.MsgCommitPubRandResponse, error) { - panic("TODO: implement me") +// CommitPubRandList commits a list of EOTS public randomness +func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommitPubRandList) (*types.MsgCommitPubRandListResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // ensure the request contains enough number of public randomness + minPubRand := ms.GetParams(ctx).MinPubRand + givenPubRand := len(req.PubRandList) + if uint64(givenPubRand) < minPubRand { + return nil, fmt.Errorf("the request contains too few public randomness (required minimum: %d, actual: %d)", minPubRand, givenPubRand) + } + + // ensure the BTC validator is registered + valBTCPKBytes := req.ValBtcPk.MustMarshal() + if !ms.BTCStakingKeeper.HasBTCValidator(ctx, valBTCPKBytes) { + return nil, fmt.Errorf("the validator with BTC PK %v is not registered", valBTCPKBytes) + } + + // this BTC validator has not commit any public randomness, + // commit the given public randomness list and return + if ms.IsFirstPubRand(ctx, req.ValBtcPk) { + ms.setPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + return &types.MsgCommitPubRandListResponse{}, nil + } + + // ensure height and req.StartHeight do not overlap, i.e., height < req.StartHeight + height, _, err := ms.GetLastPubRand(ctx, req.ValBtcPk) + if err != nil { + return nil, err + } + if height >= req.StartHeight { + return nil, fmt.Errorf("the start height (%d) has overlap with the height of the highest public randomness (%d)", req.StartHeight, height) + } + + // all good, commit the given public randomness list + ms.setPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + return &types.MsgCommitPubRandListResponse{}, nil } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 506ef0c68..665c1f8f0 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -2,22 +2,180 @@ package keeper_test import ( "context" + "math/rand" "testing" + "github.com/babylonchain/babylon/crypto/eots" + "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) -func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := keepertest.FinalityKeeper(t) - return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) +func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { + fKeeper, ctx := keepertest.FinalityKeeper(t, nil) + return fKeeper, keeper.NewMsgServerImpl(*fKeeper), sdk.WrapSDKContext(ctx) } func TestMsgServer(t *testing.T) { - ms, ctx := setupMsgServer(t) + _, ms, ctx := setupMsgServer(t) require.NotNil(t, ms) require.NotNil(t, ctx) } + +func FuzzCommitPubRandList(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + bsKeeper := types.NewMockBTCStakingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper) + ms := keeper.NewMsgServerImpl(*fKeeper) + + // create a random BTC validator + btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + valBTCPKBytes := valBTCPK.MustMarshal() + + // Case 1: fail if the BTC validator is not registered + bsKeeper.EXPECT().HasBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(false).Times(1) + startHeight := datagen.RandomInt(r, 10) + numPubRand := uint64(200) + _, msg, err := datagen.GenRandomMsgCommitPubRandList(r, btcSK, startHeight, numPubRand) + require.NoError(t, err) + _, err = ms.CommitPubRandList(ctx, msg) + require.Error(t, err) + // register the BTC validator + bsKeeper.EXPECT().HasBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(true).AnyTimes() + + // Case 2: commit a list of 0 { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.Hash))) + if len(m.LastCommitHash) > 0 { + i -= len(m.LastCommitHash) + copy(dAtA[i:], m.LastCommitHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.LastCommitHash))) i-- dAtA[i] = 0x12 } @@ -177,7 +178,7 @@ func (m *IndexedBlock) Size() (n int) { if m.Height != 0 { n += 1 + sovFinality(uint64(m.Height)) } - l = len(m.Hash) + l = len(m.LastCommitHash) if l > 0 { n += 1 + l + sovFinality(uint64(l)) } @@ -243,7 +244,7 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -270,9 +271,9 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} + m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.LastCommitHash == nil { + m.LastCommitHash = []byte{} } iNdEx = postIndex case 3: diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go new file mode 100644 index 000000000..1c8159247 --- /dev/null +++ b/x/finality/types/mocked_keepers.go @@ -0,0 +1,138 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/finality/types/expected_keepers.go + +// Package types is a generated GoMock package. +package types + +import ( + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockBTCStakingKeeper is a mock of BTCStakingKeeper interface. +type MockBTCStakingKeeper struct { + ctrl *gomock.Controller + recorder *MockBTCStakingKeeperMockRecorder +} + +// MockBTCStakingKeeperMockRecorder is the mock recorder for MockBTCStakingKeeper. +type MockBTCStakingKeeperMockRecorder struct { + mock *MockBTCStakingKeeper +} + +// NewMockBTCStakingKeeper creates a new mock instance. +func NewMockBTCStakingKeeper(ctrl *gomock.Controller) *MockBTCStakingKeeper { + mock := &MockBTCStakingKeeper{ctrl: ctrl} + mock.recorder = &MockBTCStakingKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBTCStakingKeeper) EXPECT() *MockBTCStakingKeeperMockRecorder { + return m.recorder +} + +// GetVotingPower mocks base method. +func (m *MockBTCStakingKeeper) GetVotingPower(ctx types.Context, valBTCPK []byte, height uint64) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVotingPower", ctx, valBTCPK, height) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetVotingPower indicates an expected call of GetVotingPower. +func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, valBTCPK, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPower", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPower), ctx, valBTCPK, height) +} + +// HasBTCValidator mocks base method. +func (m *MockBTCStakingKeeper) HasBTCValidator(ctx types.Context, valBTCPK []byte) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasBTCValidator", ctx, valBTCPK) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasBTCValidator indicates an expected call of HasBTCValidator. +func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).HasBTCValidator), ctx, valBTCPK) +} + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) types0.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types0.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// SpendableCoins mocks base method. +func (m *MockBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) + ret0, _ := ret[0].(types.Coins) + return ret0 +} + +// SpendableCoins indicates an expected call of SpendableCoins. +func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) +} diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index ca41b72b9..f0489079c 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -6,6 +6,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/crypto/eots" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/cometbft/cometbft/crypto/tmhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,8 +14,8 @@ import ( // ensure that these message types implement the sdk.Msg interface var ( _ sdk.Msg = &MsgUpdateParams{} - _ sdk.Msg = &MsgAddVote{} - _ sdk.Msg = &MsgCommitPubRand{} + _ sdk.Msg = &MsgAddFinalitySig{} + _ sdk.Msg = &MsgCommitPubRandList{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -36,8 +37,25 @@ func (m *MsgUpdateParams) ValidateBasic() error { return nil } -// GetSigners returns the expected signers for a MsgAddVote message. -func (m *MsgAddVote) GetSigners() []sdk.AccAddress { +func NewMsgAddFinalitySig(signer string, sk *btcec.PrivateKey, sr *eots.PrivateRand, blockHeight uint64, blockHash []byte) (*MsgAddFinalitySig, error) { + msg := &MsgAddFinalitySig{ + Signer: signer, + ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + BlockHeight: blockHeight, + BlockLastCommitHash: blockHash, + } + msgToSign := msg.MsgToSign() + sig, err := eots.Sign(sk, sr, msgToSign) + if err != nil { + return nil, err + } + msg.FinalitySig = bbn.NewSchnorrEOTSSigFromModNScalar(sig) + + return msg, nil +} + +// GetSigners returns the expected signers for a MsgAddFinalitySig message. +func (m *MsgAddFinalitySig) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Signer) if err != nil { panic(err) @@ -46,11 +64,11 @@ func (m *MsgAddVote) GetSigners() []sdk.AccAddress { } // ValidateBasic does a sanity check on the provided data. -func (m *MsgAddVote) ValidateBasic() error { +func (m *MsgAddFinalitySig) ValidateBasic() error { if m.ValBtcPk == nil { return fmt.Errorf("empty validator BTC PK") } - if len(m.BlockHash) != tmhash.Size { + if len(m.BlockLastCommitHash) != tmhash.Size { return fmt.Errorf("malformed block hash") } if m.FinalitySig == nil { @@ -60,16 +78,11 @@ func (m *MsgAddVote) ValidateBasic() error { return nil } -// MsgToSign returns (block_height || block_hash) -// The EOTS signature in MsgAddVote will be on this msg -func (m *MsgAddVote) MsgToSign() []byte { - msgToSign := []byte{} - msgToSign = append(msgToSign, sdk.Uint64ToBigEndian(m.BlockHeight)...) - msgToSign = append(msgToSign, m.BlockHash...) - return msgToSign +func (m *MsgAddFinalitySig) MsgToSign() []byte { + return MsgToSignForVote(m.BlockHeight, m.BlockLastCommitHash) } -func (m *MsgAddVote) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { +func (m *MsgAddFinalitySig) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { msgToSign := m.MsgToSign() pk, err := m.ValBtcPk.ToBTCPK() if err != nil { @@ -79,8 +92,8 @@ func (m *MsgAddVote) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { return eots.Verify(pk, pubRand.ToFieldVal(), msgToSign, m.FinalitySig.ToModNScalar()) } -// GetSigners returns the expected signers for a MsgCommitPubRand message. -func (m *MsgCommitPubRand) GetSigners() []sdk.AccAddress { +// GetSigners returns the expected signers for a MsgCommitPubRandList message. +func (m *MsgCommitPubRandList) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Signer) if err != nil { panic(err) @@ -89,7 +102,7 @@ func (m *MsgCommitPubRand) GetSigners() []sdk.AccAddress { } // ValidateBasic does a sanity check on the provided data. -func (m *MsgCommitPubRand) ValidateBasic() error { +func (m *MsgCommitPubRandList) ValidateBasic() error { if m.ValBtcPk == nil { return fmt.Errorf("empty validator BTC PK") } @@ -103,8 +116,8 @@ func (m *MsgCommitPubRand) ValidateBasic() error { } // HashToSign returns a 32-byte hash of (start_height || pub_rand_list) -// The signature in MsgCommitPubRand will be on this hash -func (m *MsgCommitPubRand) HashToSign() ([]byte, error) { +// The signature in MsgCommitPubRandList will be on this hash +func (m *MsgCommitPubRandList) HashToSign() ([]byte, error) { hasher := tmhash.New() if _, err := hasher.Write(sdk.Uint64ToBigEndian(m.StartHeight)); err != nil { return nil, err @@ -117,7 +130,7 @@ func (m *MsgCommitPubRand) HashToSign() ([]byte, error) { return hasher.Sum(nil), nil } -func (m *MsgCommitPubRand) verifySig() error { +func (m *MsgCommitPubRandList) verifySig() error { msgHash, err := m.HashToSign() if err != nil { return err diff --git a/x/finality/types/msg_test.go b/x/finality/types/msg_test.go index c3fe77348..27abb3624 100644 --- a/x/finality/types/msg_test.go +++ b/x/finality/types/msg_test.go @@ -6,10 +6,12 @@ import ( "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" "github.com/stretchr/testify/require" ) -func FuzzMsgAddVote(f *testing.F) { +func FuzzMsgAddFinalitySig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -17,20 +19,25 @@ func FuzzMsgAddVote(f *testing.F) { sk, err := eots.KeyGen(r) require.NoError(t, err) + sr, pr, err := eots.RandGen(r) + require.NoError(t, err) + blockHeight := datagen.RandomInt(r, 10) + blockHash := datagen.GenRandomByteArray(r, 32) - msg, pr, err := datagen.GenRandomMsgAddVote(r, sk) + signer := datagen.GenRandomAccount().Address + msg, err := types.NewMsgAddFinalitySig(signer, sk, sr, blockHeight, blockHash) require.NoError(t, err) // basic sanity checks err = msg.ValidateBasic() require.NoError(t, err) // verify msg's EOTS sig against the given public randomness - err = msg.VerifyEOTSSig(pr) + err = msg.VerifyEOTSSig(bbn.NewSchnorrPubRandFromFieldVal(pr)) require.NoError(t, err) }) } -func FuzzMsgCommitPubRand(f *testing.F) { +func FuzzMsgCommitPubRandList(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -39,7 +46,9 @@ func FuzzMsgCommitPubRand(f *testing.F) { sk, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - msg, err := datagen.GenRandomMsgCommitPubRand(r, sk) + startHeight := datagen.RandomInt(r, 10) + numPubRand := datagen.RandomInt(r, 100) + 1 + _, msg, err := datagen.GenRandomMsgCommitPubRandList(r, sk, startHeight, numPubRand) require.NoError(t, err) // sanity checks, including verifying signature diff --git a/x/finality/types/params.go b/x/finality/types/params.go index c25ddb70a..00413293d 100644 --- a/x/finality/types/params.go +++ b/x/finality/types/params.go @@ -14,7 +14,9 @@ func ParamKeyTable() paramtypes.KeyTable { // DefaultParams returns a default set of parameters func DefaultParams() Params { - return Params{} // TODO: default params + return Params{ + MinPubRand: 100, + } } // ParamSetPairs get the params.ParamSet diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index bf4cd3c5f..a3df9f579 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -31,15 +31,15 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// MsgAddVote defines a message for adding a vote -type MsgAddVote struct { +// MsgAddFinalitySig defines a message for adding a vote +type MsgAddFinalitySig struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // val_btc_pk is the BTC Pk of the validator that casts this vote ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` // block_height is the height of the voted block BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // block_hash is the hash of the voted block - BlockHash []byte `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + // block_last_commit_hash is the last_commit_hash of the voted block + BlockLastCommitHash []byte `protobuf:"bytes,4,opt,name=block_last_commit_hash,json=blockLastCommitHash,proto3" json:"block_last_commit_hash,omitempty"` // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` @@ -47,18 +47,18 @@ type MsgAddVote struct { FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,5,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` } -func (m *MsgAddVote) Reset() { *m = MsgAddVote{} } -func (m *MsgAddVote) String() string { return proto.CompactTextString(m) } -func (*MsgAddVote) ProtoMessage() {} -func (*MsgAddVote) Descriptor() ([]byte, []int) { +func (m *MsgAddFinalitySig) Reset() { *m = MsgAddFinalitySig{} } +func (m *MsgAddFinalitySig) String() string { return proto.CompactTextString(m) } +func (*MsgAddFinalitySig) ProtoMessage() {} +func (*MsgAddFinalitySig) Descriptor() ([]byte, []int) { return fileDescriptor_2dd6da066b6baf1d, []int{0} } -func (m *MsgAddVote) XXX_Unmarshal(b []byte) error { +func (m *MsgAddFinalitySig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddFinalitySig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddVote.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddFinalitySig.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -68,55 +68,55 @@ func (m *MsgAddVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *MsgAddVote) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddVote.Merge(m, src) +func (m *MsgAddFinalitySig) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddFinalitySig.Merge(m, src) } -func (m *MsgAddVote) XXX_Size() int { +func (m *MsgAddFinalitySig) XXX_Size() int { return m.Size() } -func (m *MsgAddVote) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddVote.DiscardUnknown(m) +func (m *MsgAddFinalitySig) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddFinalitySig.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddVote proto.InternalMessageInfo +var xxx_messageInfo_MsgAddFinalitySig proto.InternalMessageInfo -func (m *MsgAddVote) GetSigner() string { +func (m *MsgAddFinalitySig) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgAddVote) GetBlockHeight() uint64 { +func (m *MsgAddFinalitySig) GetBlockHeight() uint64 { if m != nil { return m.BlockHeight } return 0 } -func (m *MsgAddVote) GetBlockHash() []byte { +func (m *MsgAddFinalitySig) GetBlockLastCommitHash() []byte { if m != nil { - return m.BlockHash + return m.BlockLastCommitHash } return nil } -// MsgAddVoteResponse is the response to the MsgAddVote message -type MsgAddVoteResponse struct { +// MsgAddFinalitySigResponse is the response to the MsgAddFinalitySig message +type MsgAddFinalitySigResponse struct { } -func (m *MsgAddVoteResponse) Reset() { *m = MsgAddVoteResponse{} } -func (m *MsgAddVoteResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddVoteResponse) ProtoMessage() {} -func (*MsgAddVoteResponse) Descriptor() ([]byte, []int) { +func (m *MsgAddFinalitySigResponse) Reset() { *m = MsgAddFinalitySigResponse{} } +func (m *MsgAddFinalitySigResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddFinalitySigResponse) ProtoMessage() {} +func (*MsgAddFinalitySigResponse) Descriptor() ([]byte, []int) { return fileDescriptor_2dd6da066b6baf1d, []int{1} } -func (m *MsgAddVoteResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgAddFinalitySigResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddVoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddFinalitySigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddVoteResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddFinalitySigResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -126,20 +126,20 @@ func (m *MsgAddVoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, return b[:n], nil } } -func (m *MsgAddVoteResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddVoteResponse.Merge(m, src) +func (m *MsgAddFinalitySigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddFinalitySigResponse.Merge(m, src) } -func (m *MsgAddVoteResponse) XXX_Size() int { +func (m *MsgAddFinalitySigResponse) XXX_Size() int { return m.Size() } -func (m *MsgAddVoteResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddVoteResponse.DiscardUnknown(m) +func (m *MsgAddFinalitySigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddFinalitySigResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddVoteResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgAddFinalitySigResponse proto.InternalMessageInfo -// MsgCommitPubRand defines a message for committing a list of public randomness for EOTS -type MsgCommitPubRand struct { +// MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS +type MsgCommitPubRandList struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // val_btc_pk is the BTC Pk of the validator that commits the public randomness ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` @@ -155,18 +155,18 @@ type MsgCommitPubRand struct { Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` } -func (m *MsgCommitPubRand) Reset() { *m = MsgCommitPubRand{} } -func (m *MsgCommitPubRand) String() string { return proto.CompactTextString(m) } -func (*MsgCommitPubRand) ProtoMessage() {} -func (*MsgCommitPubRand) Descriptor() ([]byte, []int) { +func (m *MsgCommitPubRandList) Reset() { *m = MsgCommitPubRandList{} } +func (m *MsgCommitPubRandList) String() string { return proto.CompactTextString(m) } +func (*MsgCommitPubRandList) ProtoMessage() {} +func (*MsgCommitPubRandList) Descriptor() ([]byte, []int) { return fileDescriptor_2dd6da066b6baf1d, []int{2} } -func (m *MsgCommitPubRand) XXX_Unmarshal(b []byte) error { +func (m *MsgCommitPubRandList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgCommitPubRand) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgCommitPubRandList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgCommitPubRand.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgCommitPubRandList.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -176,48 +176,48 @@ func (m *MsgCommitPubRand) XXX_Marshal(b []byte, deterministic bool) ([]byte, er return b[:n], nil } } -func (m *MsgCommitPubRand) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCommitPubRand.Merge(m, src) +func (m *MsgCommitPubRandList) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCommitPubRandList.Merge(m, src) } -func (m *MsgCommitPubRand) XXX_Size() int { +func (m *MsgCommitPubRandList) XXX_Size() int { return m.Size() } -func (m *MsgCommitPubRand) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCommitPubRand.DiscardUnknown(m) +func (m *MsgCommitPubRandList) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCommitPubRandList.DiscardUnknown(m) } -var xxx_messageInfo_MsgCommitPubRand proto.InternalMessageInfo +var xxx_messageInfo_MsgCommitPubRandList proto.InternalMessageInfo -func (m *MsgCommitPubRand) GetSigner() string { +func (m *MsgCommitPubRandList) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgCommitPubRand) GetStartHeight() uint64 { +func (m *MsgCommitPubRandList) GetStartHeight() uint64 { if m != nil { return m.StartHeight } return 0 } -// MsgCommitPubRandResponse is the response to the MsgCommitPubRand message -type MsgCommitPubRandResponse struct { +// MsgCommitPubRandListResponse is the response to the MsgCommitPubRandList message +type MsgCommitPubRandListResponse struct { } -func (m *MsgCommitPubRandResponse) Reset() { *m = MsgCommitPubRandResponse{} } -func (m *MsgCommitPubRandResponse) String() string { return proto.CompactTextString(m) } -func (*MsgCommitPubRandResponse) ProtoMessage() {} -func (*MsgCommitPubRandResponse) Descriptor() ([]byte, []int) { +func (m *MsgCommitPubRandListResponse) Reset() { *m = MsgCommitPubRandListResponse{} } +func (m *MsgCommitPubRandListResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCommitPubRandListResponse) ProtoMessage() {} +func (*MsgCommitPubRandListResponse) Descriptor() ([]byte, []int) { return fileDescriptor_2dd6da066b6baf1d, []int{3} } -func (m *MsgCommitPubRandResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgCommitPubRandListResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgCommitPubRandResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgCommitPubRandListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgCommitPubRandResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgCommitPubRandListResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -227,17 +227,17 @@ func (m *MsgCommitPubRandResponse) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *MsgCommitPubRandResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCommitPubRandResponse.Merge(m, src) +func (m *MsgCommitPubRandListResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCommitPubRandListResponse.Merge(m, src) } -func (m *MsgCommitPubRandResponse) XXX_Size() int { +func (m *MsgCommitPubRandListResponse) XXX_Size() int { return m.Size() } -func (m *MsgCommitPubRandResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCommitPubRandResponse.DiscardUnknown(m) +func (m *MsgCommitPubRandListResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCommitPubRandListResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgCommitPubRandResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgCommitPubRandListResponse proto.InternalMessageInfo // MsgUpdateParams defines a message for updating finality module parameters. type MsgUpdateParams struct { @@ -337,10 +337,10 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo func init() { - proto.RegisterType((*MsgAddVote)(nil), "babylon.finality.v1.MsgAddVote") - proto.RegisterType((*MsgAddVoteResponse)(nil), "babylon.finality.v1.MsgAddVoteResponse") - proto.RegisterType((*MsgCommitPubRand)(nil), "babylon.finality.v1.MsgCommitPubRand") - proto.RegisterType((*MsgCommitPubRandResponse)(nil), "babylon.finality.v1.MsgCommitPubRandResponse") + proto.RegisterType((*MsgAddFinalitySig)(nil), "babylon.finality.v1.MsgAddFinalitySig") + proto.RegisterType((*MsgAddFinalitySigResponse)(nil), "babylon.finality.v1.MsgAddFinalitySigResponse") + proto.RegisterType((*MsgCommitPubRandList)(nil), "babylon.finality.v1.MsgCommitPubRandList") + proto.RegisterType((*MsgCommitPubRandListResponse)(nil), "babylon.finality.v1.MsgCommitPubRandListResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.finality.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.finality.v1.MsgUpdateParamsResponse") } @@ -348,46 +348,47 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } var fileDescriptor_2dd6da066b6baf1d = []byte{ - // 620 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcb, 0x6e, 0xd3, 0x40, - 0x14, 0x8d, 0x93, 0x50, 0xe8, 0x24, 0x05, 0x34, 0x54, 0xd4, 0x35, 0xe0, 0x84, 0x08, 0x44, 0x85, - 0xa8, 0xdd, 0x17, 0x15, 0x65, 0x57, 0x23, 0x24, 0xa0, 0x44, 0x44, 0x76, 0x61, 0x01, 0x48, 0xd6, - 0xf8, 0xc1, 0x78, 0x54, 0xdb, 0x63, 0x79, 0xc6, 0x51, 0xb3, 0xe5, 0x0b, 0x58, 0xb0, 0xe1, 0x03, - 0xd8, 0xb3, 0xe0, 0x23, 0xba, 0x42, 0x15, 0x2b, 0xd4, 0x45, 0x84, 0xda, 0x05, 0xbf, 0x81, 0xe2, - 0x47, 0x4d, 0xab, 0x56, 0x0d, 0x2c, 0xd8, 0xf9, 0xce, 0x3d, 0xf7, 0x9e, 0x3b, 0xc7, 0xe7, 0x0e, - 0xb8, 0x6e, 0x21, 0x6b, 0xe0, 0xd3, 0x50, 0x7d, 0x47, 0x42, 0xe4, 0x13, 0x3e, 0x50, 0xfb, 0x8b, - 0x2a, 0xdf, 0x56, 0xa2, 0x98, 0x72, 0x0a, 0xaf, 0xe4, 0x59, 0xa5, 0xc8, 0x2a, 0xfd, 0x45, 0x69, - 0x1a, 0x53, 0x4c, 0xd3, 0xbc, 0x3a, 0xfa, 0xca, 0xa0, 0xd2, 0xac, 0x4d, 0x59, 0x40, 0x99, 0x99, - 0x25, 0xb2, 0x20, 0x4f, 0xcd, 0x64, 0x91, 0x1a, 0x30, 0x3c, 0xea, 0x1e, 0x30, 0x9c, 0x27, 0xda, - 0x27, 0x91, 0x47, 0x28, 0x46, 0x41, 0x5e, 0xda, 0xf9, 0x54, 0x05, 0xa0, 0xcb, 0xf0, 0xba, 0xe3, - 0xbc, 0xa2, 0xdc, 0x85, 0x57, 0xc1, 0x04, 0x23, 0x38, 0x74, 0x63, 0x51, 0x68, 0x0b, 0x73, 0x93, - 0x7a, 0x1e, 0xc1, 0x4d, 0x00, 0xfa, 0xc8, 0x37, 0x2d, 0x6e, 0x9b, 0xd1, 0x96, 0x58, 0x6d, 0x0b, - 0x73, 0x4d, 0x6d, 0x75, 0x6f, 0xd8, 0x5a, 0xc2, 0x84, 0x7b, 0x89, 0xa5, 0xd8, 0x34, 0x50, 0x73, - 0x2e, 0xdb, 0x43, 0x24, 0x2c, 0x02, 0x95, 0x0f, 0x22, 0x97, 0x29, 0xda, 0xd3, 0xde, 0xf2, 0xca, - 0x42, 0x2f, 0xb1, 0x36, 0xdc, 0x81, 0x7e, 0xa1, 0x8f, 0x7c, 0x8d, 0xdb, 0xbd, 0x2d, 0x78, 0x13, - 0x34, 0x2d, 0x9f, 0xda, 0x5b, 0xa6, 0xe7, 0x12, 0xec, 0x71, 0xb1, 0xd6, 0x16, 0xe6, 0xea, 0x7a, - 0x23, 0x3d, 0x7b, 0x92, 0x1e, 0xc1, 0x1b, 0x00, 0xe4, 0x10, 0xc4, 0x3c, 0xb1, 0x3e, 0x22, 0xd6, - 0x27, 0x33, 0x00, 0x62, 0x1e, 0x7c, 0x03, 0x9a, 0xc5, 0xd5, 0x4c, 0x46, 0xb0, 0x78, 0x2e, 0x9d, - 0xec, 0xc1, 0xde, 0xb0, 0xb5, 0x32, 0xde, 0x64, 0x86, 0xed, 0x85, 0x34, 0x8e, 0x1f, 0xbf, 0xd8, - 0x34, 0x0c, 0x82, 0xf5, 0x46, 0xd1, 0xcd, 0x20, 0xb8, 0x33, 0x0d, 0x60, 0x29, 0x8d, 0xee, 0xb2, - 0x88, 0x86, 0xcc, 0xed, 0x7c, 0xab, 0x82, 0xcb, 0x5d, 0x86, 0x1f, 0xd1, 0x20, 0x20, 0xbc, 0x97, - 0x58, 0x3a, 0x0a, 0x9d, 0xff, 0xaf, 0x1b, 0xe3, 0x28, 0xe6, 0xc7, 0x74, 0x4b, 0xcf, 0x72, 0xdd, - 0xde, 0x82, 0xa9, 0x28, 0xb1, 0xcc, 0x18, 0x85, 0x8e, 0xe9, 0x13, 0xc6, 0xc5, 0x7a, 0xbb, 0xf6, - 0x4f, 0xca, 0xe4, 0x37, 0xd4, 0x1b, 0x51, 0xf6, 0xf1, 0x9c, 0x30, 0x0e, 0x37, 0x40, 0xad, 0x54, - 0x7b, 0x6d, 0x6f, 0xd8, 0xba, 0xff, 0x37, 0xf7, 0x31, 0x08, 0x0e, 0x11, 0x4f, 0x62, 0x57, 0x1f, - 0x75, 0xe9, 0x48, 0x40, 0x3c, 0xae, 0xe7, 0xa1, 0xd8, 0x1f, 0x05, 0x70, 0xa9, 0xcb, 0xf0, 0xcb, - 0xc8, 0x41, 0xdc, 0xed, 0xa5, 0xc6, 0x85, 0xab, 0x60, 0x12, 0x25, 0xdc, 0xa3, 0x31, 0xe1, 0x83, - 0x4c, 0x6e, 0x4d, 0xfc, 0xfe, 0x75, 0x7e, 0x3a, 0x5f, 0x89, 0x75, 0xc7, 0x89, 0x5d, 0xc6, 0x0c, - 0x1e, 0x93, 0x10, 0xeb, 0x25, 0x14, 0xae, 0x81, 0x89, 0xcc, 0xfa, 0xe9, 0x7f, 0x68, 0x2c, 0x5d, - 0x53, 0x4e, 0x58, 0x3e, 0x25, 0x23, 0xd1, 0xea, 0x3b, 0xc3, 0x56, 0x45, 0xcf, 0x0b, 0x1e, 0x5e, - 0x7c, 0xff, 0xeb, 0xcb, 0xdd, 0xb2, 0x55, 0x67, 0x16, 0xcc, 0x1c, 0x9b, 0xaa, 0x98, 0x78, 0xe9, - 0x73, 0x15, 0xd4, 0xba, 0x0c, 0x43, 0x03, 0x9c, 0x2f, 0x96, 0xaa, 0x75, 0x22, 0x51, 0x69, 0x2d, - 0xe9, 0xce, 0x19, 0x80, 0xa2, 0x39, 0x74, 0xc1, 0xd4, 0x51, 0xdf, 0xdd, 0x3e, 0xad, 0xf2, 0x08, - 0x4c, 0x9a, 0x1f, 0x0b, 0x76, 0x48, 0x63, 0x81, 0xe6, 0x11, 0xc5, 0x6f, 0x9d, 0x56, 0xfe, 0x27, - 0x4a, 0xba, 0x37, 0x0e, 0xaa, 0xe0, 0xd0, 0x9e, 0xed, 0xec, 0xcb, 0xc2, 0xee, 0xbe, 0x2c, 0xfc, - 0xdc, 0x97, 0x85, 0x0f, 0x07, 0x72, 0x65, 0xf7, 0x40, 0xae, 0xfc, 0x38, 0x90, 0x2b, 0xaf, 0x17, - 0xce, 0xf2, 0xd2, 0x76, 0xf9, 0x9c, 0xa5, 0xb6, 0xb2, 0x26, 0xd2, 0xb7, 0x6c, 0xf9, 0x77, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xc1, 0xb9, 0xb9, 0xd7, 0x6c, 0x05, 0x00, 0x00, + // 636 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0x93, 0x7c, 0xd5, 0xd7, 0x49, 0x28, 0xaa, 0x5b, 0xb5, 0x6e, 0x5a, 0x39, 0x21, 0x42, + 0xa8, 0x20, 0xb0, 0xfb, 0x47, 0x45, 0xd9, 0xd5, 0x08, 0x54, 0x68, 0x23, 0x22, 0xbb, 0x6c, 0x00, + 0xc9, 0x1a, 0xff, 0x74, 0x3c, 0xaa, 0xed, 0x31, 0x9e, 0x71, 0xd4, 0x6c, 0x79, 0x02, 0x16, 0x3c, + 0x08, 0x0b, 0xc4, 0x23, 0xa0, 0x2e, 0xab, 0xae, 0x50, 0x17, 0x11, 0x4a, 0x16, 0xbc, 0x06, 0x8a, + 0x7f, 0x9a, 0x92, 0xa4, 0x22, 0xb0, 0x60, 0xe7, 0x3b, 0xe7, 0xf8, 0x9e, 0x7b, 0xcf, 0xdc, 0x3b, + 0x60, 0xc5, 0x80, 0x46, 0xdb, 0x25, 0xbe, 0x7c, 0x84, 0x7d, 0xe8, 0x62, 0xd6, 0x96, 0x5b, 0xeb, + 0x32, 0x3b, 0x91, 0x82, 0x90, 0x30, 0xc2, 0xcf, 0xa5, 0xa8, 0x94, 0xa1, 0x52, 0x6b, 0xbd, 0x32, + 0x8f, 0x08, 0x22, 0x31, 0x2e, 0xf7, 0xbf, 0x12, 0x6a, 0x65, 0xc9, 0x24, 0xd4, 0x23, 0x54, 0x4f, + 0x80, 0x24, 0x48, 0xa1, 0xc5, 0x24, 0x92, 0x3d, 0x8a, 0xfa, 0xd9, 0x3d, 0x8a, 0x52, 0xa0, 0x36, + 0x4e, 0x3c, 0x80, 0x21, 0xf4, 0xd2, 0x5f, 0xeb, 0x5f, 0xf2, 0x60, 0xb6, 0x41, 0xd1, 0xae, 0x65, + 0x3d, 0x4b, 0x29, 0x1a, 0x46, 0xfc, 0x02, 0x98, 0xa2, 0x18, 0xf9, 0x76, 0x28, 0x70, 0x35, 0x6e, + 0x75, 0x5a, 0x4d, 0x23, 0xfe, 0x10, 0x80, 0x16, 0x74, 0x75, 0x83, 0x99, 0x7a, 0x70, 0x2c, 0xe4, + 0x6b, 0xdc, 0x6a, 0x59, 0xd9, 0xbe, 0xe8, 0x54, 0x37, 0x10, 0x66, 0x4e, 0x64, 0x48, 0x26, 0xf1, + 0xe4, 0x54, 0xd2, 0x74, 0x20, 0xf6, 0xb3, 0x40, 0x66, 0xed, 0xc0, 0xa6, 0x92, 0xf2, 0xbc, 0xb9, + 0xb9, 0xb5, 0xd6, 0x8c, 0x8c, 0x7d, 0xbb, 0xad, 0xfe, 0xdf, 0x82, 0xae, 0xc2, 0xcc, 0xe6, 0x31, + 0x7f, 0x0b, 0x94, 0x0d, 0x97, 0x98, 0xc7, 0xba, 0x63, 0x63, 0xe4, 0x30, 0xa1, 0x50, 0xe3, 0x56, + 0x8b, 0x6a, 0x29, 0x3e, 0xdb, 0x8b, 0x8f, 0xf8, 0x4d, 0xb0, 0x90, 0x50, 0x5c, 0x48, 0x99, 0x6e, + 0x12, 0xcf, 0xc3, 0x4c, 0x77, 0x20, 0x75, 0x84, 0x62, 0xbf, 0x08, 0x75, 0x2e, 0x46, 0x0f, 0x20, + 0x65, 0x4f, 0x62, 0x6c, 0x0f, 0x52, 0x87, 0x7f, 0x03, 0xca, 0x59, 0xdf, 0x3a, 0xc5, 0x48, 0xf8, + 0x2f, 0xae, 0xf7, 0xd1, 0x45, 0xa7, 0xba, 0x35, 0x59, 0xbd, 0x9a, 0xe9, 0xf8, 0x24, 0x0c, 0x9f, + 0xbe, 0x3c, 0xd4, 0x34, 0x8c, 0xd4, 0xd2, 0xd1, 0xc0, 0xa2, 0xfa, 0x32, 0x58, 0x1a, 0xf1, 0x4d, + 0xb5, 0x69, 0x40, 0x7c, 0x6a, 0xd7, 0xcf, 0xf3, 0x60, 0xbe, 0x41, 0x51, 0x52, 0x4b, 0x33, 0x32, + 0x54, 0xe8, 0x5b, 0x07, 0x98, 0xb2, 0x7f, 0x6f, 0x2c, 0x65, 0x30, 0x64, 0x43, 0xc6, 0xc6, 0x67, + 0xa9, 0xb1, 0x6f, 0xc1, 0x8d, 0x20, 0x32, 0xf4, 0x10, 0xfa, 0x96, 0xee, 0x62, 0xca, 0x84, 0x62, + 0xad, 0xf0, 0x57, 0x26, 0xa5, 0x5d, 0xaa, 0xa5, 0xe0, 0x4a, 0xbb, 0xfb, 0xa0, 0x30, 0x30, 0x7e, + 0xe7, 0xa2, 0x53, 0x7d, 0xf8, 0x27, 0xfd, 0x68, 0x18, 0xf9, 0x90, 0x45, 0xa1, 0xad, 0xf6, 0xb3, + 0xd4, 0x45, 0xb0, 0x32, 0xce, 0xd3, 0x4b, 0xd3, 0x3f, 0x72, 0xe0, 0x66, 0x83, 0xa2, 0x57, 0x81, + 0x05, 0x99, 0xdd, 0x8c, 0x87, 0x9c, 0xdf, 0x06, 0xd3, 0x30, 0x62, 0x0e, 0x09, 0x31, 0x6b, 0x27, + 0x96, 0x2b, 0xc2, 0xf9, 0xe7, 0x07, 0xf3, 0xe9, 0xfa, 0xec, 0x5a, 0x56, 0x68, 0x53, 0xaa, 0xb1, + 0x10, 0xfb, 0x48, 0x1d, 0x50, 0xf9, 0x1d, 0x30, 0x95, 0xac, 0x49, 0x7c, 0x17, 0xa5, 0x8d, 0x65, + 0x69, 0xcc, 0xa2, 0x4a, 0x89, 0x88, 0x52, 0x3c, 0xed, 0x54, 0x73, 0x6a, 0xfa, 0xc3, 0xe3, 0x99, + 0xf7, 0x3f, 0x3e, 0xdd, 0x1b, 0xa4, 0xaa, 0x2f, 0x81, 0xc5, 0xa1, 0xaa, 0xb2, 0x8a, 0x37, 0xbe, + 0xe6, 0x41, 0xa1, 0x41, 0x11, 0xef, 0x80, 0x99, 0xa1, 0x05, 0xbc, 0x33, 0x56, 0x6f, 0x64, 0xe0, + 0x2a, 0xd2, 0x64, 0xbc, 0x4c, 0x91, 0x7f, 0x07, 0x66, 0x47, 0x87, 0xf2, 0xee, 0x75, 0x49, 0x46, + 0xa8, 0x95, 0xf5, 0x89, 0xa9, 0x97, 0x92, 0x06, 0x28, 0xff, 0x72, 0x25, 0xb7, 0xaf, 0x4b, 0x71, + 0x95, 0x55, 0xb9, 0x3f, 0x09, 0x2b, 0xd3, 0x50, 0x5e, 0x9c, 0x76, 0x45, 0xee, 0xac, 0x2b, 0x72, + 0xdf, 0xbb, 0x22, 0xf7, 0xa1, 0x27, 0xe6, 0xce, 0x7a, 0x62, 0xee, 0x5b, 0x4f, 0xcc, 0xbd, 0x5e, + 0xfb, 0xdd, 0xc0, 0x9d, 0x0c, 0xde, 0xc6, 0x78, 0xf6, 0x8c, 0xa9, 0xf8, 0x61, 0xdc, 0xfc, 0x19, + 0x00, 0x00, 0xff, 0xff, 0x75, 0xe4, 0x6f, 0x66, 0xb9, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -402,10 +403,10 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // AddVote adds a vote to a given block - AddVote(ctx context.Context, in *MsgAddVote, opts ...grpc.CallOption) (*MsgAddVoteResponse, error) - // CommitPubRand commits a list of public randomness for EOTS - CommitPubRand(ctx context.Context, in *MsgCommitPubRand, opts ...grpc.CallOption) (*MsgCommitPubRandResponse, error) + // AddFinalitySig adds a finality signature to a given block + AddFinalitySig(ctx context.Context, in *MsgAddFinalitySig, opts ...grpc.CallOption) (*MsgAddFinalitySigResponse, error) + // CommitPubRandList commits a list of public randomness for EOTS + CommitPubRandList(ctx context.Context, in *MsgCommitPubRandList, opts ...grpc.CallOption) (*MsgCommitPubRandListResponse, error) // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) @@ -419,18 +420,18 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } -func (c *msgClient) AddVote(ctx context.Context, in *MsgAddVote, opts ...grpc.CallOption) (*MsgAddVoteResponse, error) { - out := new(MsgAddVoteResponse) - err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/AddVote", in, out, opts...) +func (c *msgClient) AddFinalitySig(ctx context.Context, in *MsgAddFinalitySig, opts ...grpc.CallOption) (*MsgAddFinalitySigResponse, error) { + out := new(MsgAddFinalitySigResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/AddFinalitySig", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *msgClient) CommitPubRand(ctx context.Context, in *MsgCommitPubRand, opts ...grpc.CallOption) (*MsgCommitPubRandResponse, error) { - out := new(MsgCommitPubRandResponse) - err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/CommitPubRand", in, out, opts...) +func (c *msgClient) CommitPubRandList(ctx context.Context, in *MsgCommitPubRandList, opts ...grpc.CallOption) (*MsgCommitPubRandListResponse, error) { + out := new(MsgCommitPubRandListResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Msg/CommitPubRandList", in, out, opts...) if err != nil { return nil, err } @@ -448,10 +449,10 @@ func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts // MsgServer is the server API for Msg service. type MsgServer interface { - // AddVote adds a vote to a given block - AddVote(context.Context, *MsgAddVote) (*MsgAddVoteResponse, error) - // CommitPubRand commits a list of public randomness for EOTS - CommitPubRand(context.Context, *MsgCommitPubRand) (*MsgCommitPubRandResponse, error) + // AddFinalitySig adds a finality signature to a given block + AddFinalitySig(context.Context, *MsgAddFinalitySig) (*MsgAddFinalitySigResponse, error) + // CommitPubRandList commits a list of public randomness for EOTS + CommitPubRandList(context.Context, *MsgCommitPubRandList) (*MsgCommitPubRandListResponse, error) // TODO: msg for evidence of equivocation. this is not specified yet // UpdateParams updates the finality module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) @@ -461,11 +462,11 @@ type MsgServer interface { type UnimplementedMsgServer struct { } -func (*UnimplementedMsgServer) AddVote(ctx context.Context, req *MsgAddVote) (*MsgAddVoteResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddVote not implemented") +func (*UnimplementedMsgServer) AddFinalitySig(ctx context.Context, req *MsgAddFinalitySig) (*MsgAddFinalitySigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddFinalitySig not implemented") } -func (*UnimplementedMsgServer) CommitPubRand(ctx context.Context, req *MsgCommitPubRand) (*MsgCommitPubRandResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CommitPubRand not implemented") +func (*UnimplementedMsgServer) CommitPubRandList(ctx context.Context, req *MsgCommitPubRandList) (*MsgCommitPubRandListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CommitPubRandList not implemented") } func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") @@ -475,38 +476,38 @@ func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } -func _Msg_AddVote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddVote) +func _Msg_AddFinalitySig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddFinalitySig) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).AddVote(ctx, in) + return srv.(MsgServer).AddFinalitySig(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.finality.v1.Msg/AddVote", + FullMethod: "/babylon.finality.v1.Msg/AddFinalitySig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddVote(ctx, req.(*MsgAddVote)) + return srv.(MsgServer).AddFinalitySig(ctx, req.(*MsgAddFinalitySig)) } return interceptor(ctx, in, info, handler) } -func _Msg_CommitPubRand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgCommitPubRand) +func _Msg_CommitPubRandList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCommitPubRandList) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).CommitPubRand(ctx, in) + return srv.(MsgServer).CommitPubRandList(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.finality.v1.Msg/CommitPubRand", + FullMethod: "/babylon.finality.v1.Msg/CommitPubRandList", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).CommitPubRand(ctx, req.(*MsgCommitPubRand)) + return srv.(MsgServer).CommitPubRandList(ctx, req.(*MsgCommitPubRandList)) } return interceptor(ctx, in, info, handler) } @@ -534,12 +535,12 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ HandlerType: (*MsgServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "AddVote", - Handler: _Msg_AddVote_Handler, + MethodName: "AddFinalitySig", + Handler: _Msg_AddFinalitySig_Handler, }, { - MethodName: "CommitPubRand", - Handler: _Msg_CommitPubRand_Handler, + MethodName: "CommitPubRandList", + Handler: _Msg_CommitPubRandList_Handler, }, { MethodName: "UpdateParams", @@ -550,7 +551,7 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Metadata: "babylon/finality/v1/tx.proto", } -func (m *MsgAddVote) Marshal() (dAtA []byte, err error) { +func (m *MsgAddFinalitySig) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -560,12 +561,12 @@ func (m *MsgAddVote) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddVote) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddFinalitySig) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddFinalitySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -582,10 +583,10 @@ func (m *MsgAddVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - if len(m.BlockHash) > 0 { - i -= len(m.BlockHash) - copy(dAtA[i:], m.BlockHash) - i = encodeVarintTx(dAtA, i, uint64(len(m.BlockHash))) + if len(m.BlockLastCommitHash) > 0 { + i -= len(m.BlockLastCommitHash) + copy(dAtA[i:], m.BlockLastCommitHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.BlockLastCommitHash))) i-- dAtA[i] = 0x22 } @@ -616,7 +617,7 @@ func (m *MsgAddVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgAddVoteResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgAddFinalitySigResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -626,12 +627,12 @@ func (m *MsgAddVoteResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddVoteResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddFinalitySigResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddFinalitySigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -639,7 +640,7 @@ func (m *MsgAddVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgCommitPubRand) Marshal() (dAtA []byte, err error) { +func (m *MsgCommitPubRandList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -649,12 +650,12 @@ func (m *MsgCommitPubRand) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgCommitPubRand) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgCommitPubRandList) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgCommitPubRand) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgCommitPubRandList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -712,7 +713,7 @@ func (m *MsgCommitPubRand) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgCommitPubRandResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgCommitPubRandListResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -722,12 +723,12 @@ func (m *MsgCommitPubRandResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgCommitPubRandResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgCommitPubRandListResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgCommitPubRandResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgCommitPubRandListResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -809,7 +810,7 @@ func encodeVarintTx(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MsgAddVote) Size() (n int) { +func (m *MsgAddFinalitySig) Size() (n int) { if m == nil { return 0 } @@ -826,7 +827,7 @@ func (m *MsgAddVote) Size() (n int) { if m.BlockHeight != 0 { n += 1 + sovTx(uint64(m.BlockHeight)) } - l = len(m.BlockHash) + l = len(m.BlockLastCommitHash) if l > 0 { n += 1 + l + sovTx(uint64(l)) } @@ -837,7 +838,7 @@ func (m *MsgAddVote) Size() (n int) { return n } -func (m *MsgAddVoteResponse) Size() (n int) { +func (m *MsgAddFinalitySigResponse) Size() (n int) { if m == nil { return 0 } @@ -846,7 +847,7 @@ func (m *MsgAddVoteResponse) Size() (n int) { return n } -func (m *MsgCommitPubRand) Size() (n int) { +func (m *MsgCommitPubRandList) Size() (n int) { if m == nil { return 0 } @@ -876,7 +877,7 @@ func (m *MsgCommitPubRand) Size() (n int) { return n } -func (m *MsgCommitPubRandResponse) Size() (n int) { +func (m *MsgCommitPubRandListResponse) Size() (n int) { if m == nil { return 0 } @@ -915,7 +916,7 @@ func sovTx(x uint64) (n int) { func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MsgAddVote) Unmarshal(dAtA []byte) error { +func (m *MsgAddFinalitySig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -938,10 +939,10 @@ func (m *MsgAddVote) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddVote: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddFinalitySig: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddVote: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddFinalitySig: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1032,7 +1033,7 @@ func (m *MsgAddVote) Unmarshal(dAtA []byte) error { } case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BlockLastCommitHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1059,9 +1060,9 @@ func (m *MsgAddVote) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BlockHash = append(m.BlockHash[:0], dAtA[iNdEx:postIndex]...) - if m.BlockHash == nil { - m.BlockHash = []byte{} + m.BlockLastCommitHash = append(m.BlockLastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.BlockLastCommitHash == nil { + m.BlockLastCommitHash = []byte{} } iNdEx = postIndex case 5: @@ -1120,7 +1121,7 @@ func (m *MsgAddVote) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddVoteResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddFinalitySigResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1143,10 +1144,10 @@ func (m *MsgAddVoteResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddVoteResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddFinalitySigResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddVoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddFinalitySigResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1170,7 +1171,7 @@ func (m *MsgAddVoteResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCommitPubRand) Unmarshal(dAtA []byte) error { +func (m *MsgCommitPubRandList) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1193,10 +1194,10 @@ func (m *MsgCommitPubRand) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCommitPubRand: wiretype end group for non-group") + return fmt.Errorf("proto: MsgCommitPubRandList: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCommitPubRand: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCommitPubRandList: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1376,7 +1377,7 @@ func (m *MsgCommitPubRand) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCommitPubRandResponse) Unmarshal(dAtA []byte) error { +func (m *MsgCommitPubRandListResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1399,10 +1400,10 @@ func (m *MsgCommitPubRandResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCommitPubRandResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgCommitPubRandListResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCommitPubRandResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCommitPubRandListResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: From 17855d830a19fc8d5f0c65a8737a903cb5e673ff Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 6 Jul 2023 18:20:17 +1000 Subject: [PATCH 023/202] finality: tallying blocks upon `EndBlock` (#17) --- testutil/datagen/btc_schnorr.go | 14 +- types/btc_schnorr_pk.go | 15 ++ x/btcstaking/keeper/voting_power_table.go | 45 +++++ .../keeper/voting_power_table_test.go | 29 +++- x/btcstaking/types/errors.go | 7 +- x/finality/abci.go | 12 +- x/finality/keeper/indexed_blocks.go | 4 +- x/finality/keeper/msg_server.go | 2 +- x/finality/keeper/tallying.go | 94 ++++++++++ x/finality/keeper/tallying_test.go | 163 ++++++++++++++++++ x/finality/keeper/votes.go | 31 +++- x/finality/types/expected_keepers.go | 2 + x/finality/types/mocked_keepers.go | 29 ++++ 13 files changed, 428 insertions(+), 19 deletions(-) create mode 100644 x/finality/keeper/tallying.go create mode 100644 x/finality/keeper/tallying_test.go diff --git a/testutil/datagen/btc_schnorr.go b/testutil/datagen/btc_schnorr.go index c3a445b48..63bed5eb4 100644 --- a/testutil/datagen/btc_schnorr.go +++ b/testutil/datagen/btc_schnorr.go @@ -3,13 +3,25 @@ package datagen import ( "math/rand" + bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" ) func GenRandomBTCKeyPair(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, error) { - sk, err := btcec.NewPrivateKey() + sk, err := secp256k1.GeneratePrivateKeyFromRand(r) if err != nil { return nil, nil, err } return sk, sk.PubKey(), nil } + +func GenRandomBIP340PubKey(r *rand.Rand) (*bbn.BIP340PubKey, error) { + sk, err := secp256k1.GeneratePrivateKeyFromRand(r) + if err != nil { + return nil, err + } + pk := sk.PubKey() + btcPK := bbn.NewBIP340PubKeyFromBTCPK(pk) + return btcPK, nil +} diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index 672e60ea2..ad20c6034 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "encoding/hex" "errors" "github.com/btcsuite/btcd/btcec/v2" @@ -18,6 +19,16 @@ func NewBIP340PubKey(data []byte) (*BIP340PubKey, error) { return &pk, err } +func NewBIP340PubKeyFromHex(hexStr string) (*BIP340PubKey, error) { + pkBytes, err := hex.DecodeString(hexStr) + if err != nil { + return nil, err + } + var pk BIP340PubKey + err = pk.Unmarshal(pkBytes) + return &pk, err +} + func NewBIP340PubKeyFromBTCPK(btcPK *btcec.PublicKey) *BIP340PubKey { pkBytes := schnorr.SerializePubKey(btcPK) pk := BIP340PubKey(pkBytes) @@ -28,6 +39,10 @@ func (pk BIP340PubKey) ToBTCPK() (*btcec.PublicKey, error) { return schnorr.ParsePubKey(pk) } +func (pk *BIP340PubKey) ToHex() string { + return hex.EncodeToString(pk.MustMarshal()) +} + func (pk BIP340PubKey) Size() int { return len(pk.MustMarshal()) } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index bd1f9d82d..06c4ac408 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -1,6 +1,9 @@ package keeper import ( + "fmt" + + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -61,6 +64,48 @@ func (k Keeper) GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) return sdk.BigEndianToUint64(powerBytes) } +// GetVotingPowerTable gets the voting power table, i.e., validator set at a given height +func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 { + store := k.votingPowerStore(ctx, height) + iter := store.Iterator(nil, nil) + defer iter.Close() + + // if no validator at this height, return nil + if !iter.Valid() { + return nil + } + + // get all validators at this height + valSet := map[string]uint64{} + for ; iter.Valid(); iter.Next() { + valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + if err != nil { + // failing to unmarshal validator BTC PK in KVStore is a programming error + panic(fmt.Errorf("failed to unmarshal validator BTC PK: %w", err)) + } + valSet[valBTCPK.ToHex()] = sdk.BigEndianToUint64(iter.Value()) + } + + return valSet +} + +// GetBTCStakingActivatedHeight returns the height when the BTC staking protocol is activated +// i.e., the first height where a BTC validator has voting power +// otherwise, we return -1 here +// Before the BTC staking protocol is activated, we don't index or tally any block +func (k Keeper) GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) { + store := ctx.KVStore(k.storeKey) + votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) + iter := votingPowerStore.Iterator(nil, nil) + defer iter.Close() + // if the iterator is valid, then there exists a height that has a BTC validator with voting power + if iter.Valid() { + return sdk.BigEndianToUint64(iter.Key()), nil + } else { + return 0, types.ErrBTCStakingNotActivated + } +} + // votingPowerStore returns the KVStore of the BTC validators' voting power // prefix: (VotingPowerKey || Babylon block height) // key: Bitcoin secp256k1 PK diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 51bf04f0b..a8d3a9add 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -49,8 +49,8 @@ func FuzzVotingPowerTable(f *testing.F) { } } - // assert none of validators has voting power (since BTC height is 0) - babylonHeight := uint64(1) + // Case 1: assert none of validators has voting power (since BTC height is 0) + babylonHeight := datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) keeper.RecordVotingPowerTable(ctx) @@ -59,8 +59,12 @@ func FuzzVotingPowerTable(f *testing.F) { require.Zero(t, power) } - // move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power - babylonHeight = uint64(2) + // since there is no BTC validator with BTC delegation, the BTC staking protocol is not activated yet + _, err := keeper.GetBTCStakingActivatedHeight(ctx) + require.Error(t, err) + + // Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power + babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.RecordVotingPowerTable(ctx) @@ -73,8 +77,20 @@ func FuzzVotingPowerTable(f *testing.F) { require.Zero(t, power) } - // move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) - babylonHeight = uint64(3) + // also, get voting power table and assert consistency + powerTable := keeper.GetVotingPowerTable(ctx, babylonHeight) + require.NotNil(t, powerTable) + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + require.Equal(t, powerTable[btcVals[i].BtcPk.ToHex()], power) + } + // the activation height should be the current Babylon height as well + activatedHeight, err := keeper.GetBTCStakingActivatedHeight(ctx) + require.NoError(t, err) + require.Equal(t, babylonHeight, activatedHeight) + + // Case 3: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) keeper.RecordVotingPowerTable(ctx) @@ -82,5 +98,6 @@ func FuzzVotingPowerTable(f *testing.F) { power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) require.Zero(t, power) } + }) } diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 279f2dd30..679f66f60 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,7 +6,8 @@ import ( // x/btcstaking module sentinel errors var ( - ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") - ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") - ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") + ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") + ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") + ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1103, "the BTC staking protocol is not activated yet") ) diff --git a/x/finality/abci.go b/x/finality/abci.go index 3860f7797..32faa3ceb 100644 --- a/x/finality/abci.go +++ b/x/finality/abci.go @@ -17,10 +17,14 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - // index the current block - k.IndexBlock(ctx) - - // TODO: tally all non-finalised blocks + // if the BTC staking protocol is activated, i.e., there exists a height where a BTC validator + // has voting power, start indexing and tallying blocks + if _, err := k.BTCStakingKeeper.GetBTCStakingActivatedHeight(ctx); err == nil { + // index the current block + k.IndexBlock(ctx) + // tally all non-finalised blocks + k.TallyBlocks(ctx) + } return []abci.ValidatorUpdate{} } diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go index 440f30c41..bb7465aaa 100644 --- a/x/finality/keeper/indexed_blocks.go +++ b/x/finality/keeper/indexed_blocks.go @@ -15,10 +15,10 @@ func (k Keeper) IndexBlock(ctx sdk.Context) { LastCommitHash: header.LastCommitHash, Finalized: false, } - k.setBlock(ctx, ib) + k.SetBlock(ctx, ib) } -func (k Keeper) setBlock(ctx sdk.Context, block *types.IndexedBlock) { +func (k Keeper) SetBlock(ctx sdk.Context, block *types.IndexedBlock) { store := k.blockStore(ctx) blockBytes := k.cdc.MustMarshal(block) store.Set(sdk.Uint64ToBigEndian(block.Height), blockBytes) diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 44e0bd1cf..52d84bf2a 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -93,7 +93,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // block. We need to save the signatures on the fork, and add a detection here // all good, add vote to DB - ms.setSig(ctx, req.BlockHeight, valPK, req.FinalitySig) + ms.SetSig(ctx, req.BlockHeight, valPK, req.FinalitySig) return &types.MsgAddFinalitySigResponse{}, nil } diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go new file mode 100644 index 000000000..6c7938542 --- /dev/null +++ b/x/finality/keeper/tallying.go @@ -0,0 +1,94 @@ +package keeper + +import ( + "fmt" + + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TallyBlocks tries to finalise all blocks that are non-finalised AND have a non-nil +// BTC validator set, from earliest to the latest. +// +// This function is invoked upon each `EndBlock` *after* the BTC staking protocol is activated +// It ensures that at height `h`, the ancestor chain `[activated_height, h-1]` contains either +// - finalised blocks (i.e., block with validator set AND QC of this validator set) +// - non-finalisable blocks (i.e., block with no active validator) +// but without block that has validator set AND does not receive QC +func (k Keeper) TallyBlocks(ctx sdk.Context) { + // blocksToFinalize is the set of blocks to finalise within this tallying attempt + blocksToFinalize := []*types.IndexedBlock{} + // valSets is the BTC validator set at each height with a non-finalised block + valSets := map[uint64]map[string]uint64{} + + activatedHeight, err := k.BTCStakingKeeper.GetBTCStakingActivatedHeight(ctx) + if err != nil { + // invoking TallyBlocks when BTC staking protocol is not activated is a programming error + panic(fmt.Errorf("cannot tally a block when the BTC staking protocol hasn't been activated yet, current height: %v, activated height: %v", + ctx.BlockHeight(), activatedHeight)) + } + + // find all blocks that are non-finalised AND have validator set, from latest to the earliest activated height + // There are 4 different scenarios as follows + // - has validators, non-finalised: can finalise, add to blocksToFinalize + // - does not have validators, non-finalised: non-finalisable, skip + // - has validators, finalised, break here + // - does not have validators, finalised: impossible to happen, panic + // After this for loop, the blocks since earliest activated height are either finalised or non-finalisable + blockRevIter := k.blockStore(ctx).ReverseIterator(sdk.Uint64ToBigEndian(uint64(activatedHeight)), nil) + defer blockRevIter.Close() + for ; blockRevIter.Valid(); blockRevIter.Next() { + // get the indexed block + ibBytes := blockRevIter.Value() + var ib types.IndexedBlock + k.cdc.MustUnmarshal(ibBytes, &ib) + // get the validator set of this block + valSet := k.BTCStakingKeeper.GetVotingPowerTable(ctx, ib.Height) + + if valSet != nil && !ib.Finalized { + // has validators, non-finalised: can finalise, add block and valset + blocksToFinalize = append(blocksToFinalize, &ib) + valSets[ib.Height] = valSet + } else if valSet == nil && !ib.Finalized { + // does not have validators, non-finalised: not finalisable, skip + continue + } else if valSet != nil && ib.Finalized { + // has validators and the block has finalised + // this means that the entire prefix has been finalised, break here + break + } else if valSet == nil && ib.Finalized { + // does not have validators, finalised: impossible to happen, panic + panic(fmt.Errorf("block %d is finalized, but does not have a validator set", ib.Height)) + } + } + + // for each of these blocks from earliest to latest, tally the block w.r.t. existing votes + for i := len(blocksToFinalize) - 1; i >= 0; i-- { + blockToFinalize := blocksToFinalize[i] + sigSet := k.GetSigSet(ctx, blockToFinalize.Height) + valSet := valSets[blockToFinalize.Height] + if tally(valSet, sigSet) { + // if this block gets >2/3 votes, finalise it + blockToFinalize.Finalized = true + k.SetBlock(ctx, blockToFinalize) + } else { + // if not, then this block and all subsequent blocks should not be finalised + // thus, we need to break here + break + } + } +} + +// tally checks whether a block with the given validator set and votes reaches a quorum or not +func tally(valSet map[string]uint64, sigSet map[string]*bbn.SchnorrEOTSSig) bool { + totalPower := uint64(0) + votedPower := uint64(0) + for pkStr, power := range valSet { + totalPower += power + if _, ok := sigSet[pkStr]; ok { + votedPower += power + } + } + return votedPower > totalPower*2/3 +} diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go new file mode 100644 index 000000000..5b68414e4 --- /dev/null +++ b/x/finality/keeper/tallying_test.go @@ -0,0 +1,163 @@ +package keeper_test + +import ( + "encoding/hex" + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/babylonchain/babylon/x/finality/keeper" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzTallying(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + bsKeeper := types.NewMockBTCStakingKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper) + + // Case 1: expect to panic if tallying upon BTC staking protocol is not activated + bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(0), bstypes.ErrBTCStakingNotActivated).Times(1) + require.Panics(t, func() { fKeeper.TallyBlocks(ctx) }) + + // Case 2: expect to panic if finalised block with nil validator set + fKeeper.SetBlock(ctx, &types.IndexedBlock{ + Height: 1, + LastCommitHash: datagen.GenRandomByteArray(r, 32), + Finalized: true, + }) + // activate BTC staking protocol at height 1 + bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(1), nil).Times(1) + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(uint64(1))).Return(nil).Times(1) + require.Panics(t, func() { fKeeper.TallyBlocks(ctx) }) + + // activate BTC staking protocol at a random height + activatedHeight := datagen.RandomInt(r, 10) + 1 + + // Case 3: index a list of blocks, don't give them QCs, and tally them + // Expect they are not finalised + for i := activatedHeight; i < activatedHeight+10; i++ { + // index blocks + fKeeper.SetBlock(ctx, &types.IndexedBlock{ + Height: i, + LastCommitHash: datagen.GenRandomByteArray(r, 32), + Finalized: false, + }) + // 1 vote + votedValPK, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + fKeeper.SetSig(ctx, i, votedValPK, votedSig) + // 4 BTC vals + valSet := map[string]uint64{ + hex.EncodeToString(votedValPK.MustMarshal()): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + } + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(i)).Return(valSet).Times(1) + } + // add mock queries to GetBTCStakingActivatedHeight + bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) + // tally blocks and none of them should be finalised + fKeeper.TallyBlocks(ctx) + for i := activatedHeight; i < activatedHeight+10; i++ { + ib, err := fKeeper.GetBlock(ctx, i) + require.NoError(t, err) + require.False(t, ib.Finalized) + } + + // Case 4: index a list of blocks, give some of them QCs, and tally them. + // Expect they are all finalised + numWithQCs := datagen.RandomInt(r, 5) + 1 + for i := activatedHeight; i < activatedHeight+10; i++ { + // index blocks + fKeeper.SetBlock(ctx, &types.IndexedBlock{ + Height: i, + LastCommitHash: datagen.GenRandomByteArray(r, 32), + Finalized: false, + }) + if i < activatedHeight+numWithQCs { + // this block has QC + err := giveQCToHeight(r, ctx, bsKeeper, fKeeper, i) + require.NoError(t, err) + } else { + // this block does not have QC + err := giveNoQCToHeight(r, ctx, bsKeeper, fKeeper, i) + require.NoError(t, err) + } + } + // add mock queries to GetBTCStakingActivatedHeight + bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) + // tally blocks and none of them should be finalised + fKeeper.TallyBlocks(ctx) + for i := activatedHeight; i < activatedHeight+10; i++ { + ib, err := fKeeper.GetBlock(ctx, i) + require.NoError(t, err) + if i < activatedHeight+numWithQCs { + require.True(t, ib.Finalized) + } else { + require.False(t, ib.Finalized) + } + } + }) +} + +func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakingKeeper, fKeeper *keeper.Keeper, height uint64) error { + // 4 BTC vals + valSet := map[string]uint64{} + // 3 votes + for i := 0; i < 3; i++ { + votedValPK, err := datagen.GenRandomBIP340PubKey(r) + if err != nil { + return err + } + votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) + if err != nil { + return err + } + fKeeper.SetSig(ctx, height, votedValPK, votedSig) + // add val + valSet[votedValPK.ToHex()] = 1 + } + // the rest val that does not vote + valSet[hex.EncodeToString(datagen.GenRandomByteArray(r, 32))] = 1 + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).Times(1) + + return nil +} + +func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakingKeeper, fKeeper *keeper.Keeper, height uint64) error { + // 1 vote + votedValPK, err := datagen.GenRandomBIP340PubKey(r) + if err != nil { + return err + } + votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) + if err != nil { + return err + } + fKeeper.SetSig(ctx, height, votedValPK, votedSig) + // 4 BTC vals + valSet := map[string]uint64{ + votedValPK.ToHex(): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, + } + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).Times(1) + + return nil +} diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index c9aa5daa2..2a9f65b9b 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -9,8 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -//nolint:unused -func (k Keeper) setSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { +func (k Keeper) SetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { store := k.voteStore(ctx, height) store.Set(valBtcPK.MustMarshal(), sig.MustMarshal()) } @@ -36,6 +35,34 @@ func (k Keeper) GetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKe return sig, nil } +// GetSigSet gets all EOTS signatures at a given height +func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.SchnorrEOTSSig { + store := k.voteStore(ctx, height) + iter := store.Iterator(nil, nil) + defer iter.Close() + + // if there is no vote on this height, return nil + if !iter.Valid() { + return nil + } + + sigs := map[string]*bbn.SchnorrEOTSSig{} + for ; iter.Valid(); iter.Next() { + valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + if err != nil { + // failing to unmarshal validator BTC PK in KVStore is a programming error + panic(fmt.Errorf("failed to unmarshal validator BTC PK: %w", err)) + } + sig, err := bbn.NewSchnorrEOTSSig(iter.Value()) + if err != nil { + // failing to unmarshal EOTS sig in KVStore is a programming error + panic(fmt.Errorf("failed to unmarshal EOTS signature: %w", err)) + } + sigs[valBTCPK.ToHex()] = sig + } + return sigs +} + // voteStore returns the KVStore of the votes // prefix: VoteKey // key: (block height || BTC validator PK) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index 4b0261c93..6fdbf70f8 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -8,6 +8,8 @@ import ( type BTCStakingKeeper interface { HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 + GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 + GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) } // AccountKeeper defines the expected account keeper used for simulations (noalias) diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index 1c8159247..ed32cec4e 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -35,6 +35,21 @@ func (m *MockBTCStakingKeeper) EXPECT() *MockBTCStakingKeeperMockRecorder { return m.recorder } +// GetBTCStakingActivatedHeight mocks base method. +func (m *MockBTCStakingKeeper) GetBTCStakingActivatedHeight(ctx types.Context) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBTCStakingActivatedHeight", ctx) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBTCStakingActivatedHeight indicates an expected call of GetBTCStakingActivatedHeight. +func (mr *MockBTCStakingKeeperMockRecorder) GetBTCStakingActivatedHeight(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCStakingActivatedHeight", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCStakingActivatedHeight), ctx) +} + // GetVotingPower mocks base method. func (m *MockBTCStakingKeeper) GetVotingPower(ctx types.Context, valBTCPK []byte, height uint64) uint64 { m.ctrl.T.Helper() @@ -49,6 +64,20 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, valBTCPK, height return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPower", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPower), ctx, valBTCPK, height) } +// GetVotingPowerTable mocks base method. +func (m *MockBTCStakingKeeper) GetVotingPowerTable(ctx types.Context, height uint64) map[string]uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVotingPowerTable", ctx, height) + ret0, _ := ret[0].(map[string]uint64) + return ret0 +} + +// GetVotingPowerTable indicates an expected call of GetVotingPowerTable. +func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPowerTable(ctx, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPowerTable", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPowerTable), ctx, height) +} + // HasBTCValidator mocks base method. func (m *MockBTCStakingKeeper) HasBTCValidator(ctx types.Context, valBTCPK []byte) bool { m.ctrl.T.Helper() From c46b30fbdc2a26962a3c8926b1d06add532fe474 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 11 Jul 2023 15:17:49 +1000 Subject: [PATCH 024/202] finality: evidence storage and handler (#18) --- proto/babylon/finality/v1/events.proto | 20 + proto/babylon/finality/v1/finality.proto | 21 + x/finality/keeper/evidence.go | 42 ++ x/finality/keeper/msg_server.go | 59 ++- x/finality/keeper/msg_server_test.go | 27 +- x/finality/types/errors.go | 13 +- x/finality/types/events.go | 15 + x/finality/types/events.pb.go | 507 +++++++++++++++++++++++ x/finality/types/finality.go | 37 +- x/finality/types/finality.pb.go | 365 +++++++++++++++- x/finality/types/keys.go | 9 +- x/finality/types/msg.go | 2 +- 12 files changed, 1065 insertions(+), 52 deletions(-) create mode 100644 proto/babylon/finality/v1/events.proto create mode 100644 x/finality/keeper/evidence.go create mode 100644 x/finality/types/events.go create mode 100644 x/finality/types/events.pb.go diff --git a/proto/babylon/finality/v1/events.proto b/proto/babylon/finality/v1/events.proto new file mode 100644 index 000000000..68b06f431 --- /dev/null +++ b/proto/babylon/finality/v1/events.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package babylon.finality.v1; + +import "gogoproto/gogo.proto"; +import "babylon/finality/v1/finality.proto"; + +option go_package = "github.com/babylonchain/babylon/x/finality/types"; + +// EventSlashedBTCValidator is the event emitted when a BTC validator is slashed +// due to signing two conflicting blocks +message EventSlashedBTCValidator { + // val_btc_pk is the BTC validator's BTC PK + bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // indexed_block is the canonical block at this height + IndexedBlock indexed_block = 2; + // evidence is the evidence that the BTC validator double signs + Evidence evidence = 3; + // extracted_btc_sk is the extracted BTC SK of this BTC validator + bytes extracted_btc_sk = 4; +} diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index 304fca71f..256b3334d 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -3,6 +3,8 @@ package babylon.finality.v1; option go_package = "github.com/babylonchain/babylon/x/finality/types"; +import "gogoproto/gogo.proto"; + // IndexedBlock is the block with some indexed info message IndexedBlock { // height is the height of the block @@ -13,3 +15,22 @@ message IndexedBlock { // BTC validators or not bool finalized = 3; } + +// Evidence is the evidence that a BTC validator has signed a finality +// signature with correct public randomness on a fork header +// It includes all fields of MsgAddFinalitySig, such that anyone seeing +// the evidence and a signature on the canonical fork can extract the +// BTC validator's BTC secret key. +message Evidence { + // val_btc_pk is the BTC Pk of the validator that casts this vote + bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // block_height is the height of the voted block + uint64 block_height = 2; + // block_last_commit_hash is the last_commit_hash of the voted block + bytes block_last_commit_hash = 3; + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + bytes finality_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; +} \ No newline at end of file diff --git a/x/finality/keeper/evidence.go b/x/finality/keeper/evidence.go new file mode 100644 index 000000000..249af7817 --- /dev/null +++ b/x/finality/keeper/evidence.go @@ -0,0 +1,42 @@ +package keeper + +import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) SetEvidence(ctx sdk.Context, evidence *types.Evidence) { + store := k.evidenceStore(ctx, evidence.BlockHeight) + store.Set(evidence.ValBtcPk.MustMarshal(), k.cdc.MustMarshal(evidence)) +} + +func (k Keeper) HasEvidence(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { + store := k.evidenceStore(ctx, height) + return store.Has(valBtcPK.MustMarshal()) +} + +func (k Keeper) GetEvidence(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*types.Evidence, error) { + if uint64(ctx.BlockHeight()) < height { + return nil, types.ErrHeightTooHigh + } + store := k.evidenceStore(ctx, height) + evidenceBytes := store.Get(valBtcPK.MustMarshal()) + if len(evidenceBytes) == 0 { + return nil, types.ErrEvidenceNotFound + } + var evidence types.Evidence + k.cdc.MustUnmarshal(evidenceBytes, &evidence) + return &evidence, nil +} + +// evidenceStore returns the KVStore of the evidences +// prefix: EvidenceKey +// key: (block height || BTC validator PK) +// value: Evidence +func (k Keeper) evidenceStore(ctx sdk.Context, height uint64) prefix.Store { + store := ctx.KVStore(k.storeKey) + prefixedStore := prefix.NewStore(store, types.EvidenceKey) + return prefix.NewStore(prefixedStore, sdk.Uint64ToBigEndian(height)) +} diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 52d84bf2a..ff6a1ebe7 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -76,24 +76,61 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal } if !bytes.Equal(indexedBlock.LastCommitHash, req.BlockLastCommitHash) { // the BTC validator votes for a fork! - sig2, err := ms.GetSig(ctx, req.BlockHeight, valPK) + + // construct and save evidence + evidence := &types.Evidence{ + ValBtcPk: req.ValBtcPk, + BlockHeight: req.BlockHeight, + BlockLastCommitHash: req.BlockLastCommitHash, + FinalitySig: req.FinalitySig, + } + ms.SetEvidence(ctx, evidence) + + // if this BTC validator has also signed canonical block, extract its secret key and emit an event + canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) + if err == nil { + btcSK, err := evidence.ExtractBTCSK(indexedBlock, pubRand, canonicalSig) + if err != nil { + panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) + } + + eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) + if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { + return nil, fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err) + } + } + + // NOTE: we should NOT return error here, otherwise the state change triggered in this tx + // (including the evidence) will be rolled back + return &types.MsgAddFinalitySigResponse{}, nil + } + + // this signature is good, add vote to DB + ms.SetSig(ctx, req.BlockHeight, valPK, req.FinalitySig) + + // if this BTC validator has signed the canonical block before, + // slash it via extracting its secret key, and emit an event + if ms.HasEvidence(ctx, req.BlockHeight, req.ValBtcPk) { + // the BTC validator has voted for a fork before! + + // get evidence + evidence, err := ms.GetEvidence(ctx, req.BlockHeight, req.ValBtcPk) if err != nil { - return nil, fmt.Errorf("the BTC validator %v votes for a fork, but does not vote for the canonical block", valPK.MustMarshal()) + panic(fmt.Errorf("failed to get evidence despite HasEvidence returns true")) } - // the BTC validator votes for a fork AND the canonical block - // slash it via extracting its secret key - btcSK, err := eots.Extract(valBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar(), indexedBlock.MsgToSign(), sig2.ToModNScalar()) + + // extract its SK + btcSK, err := evidence.ExtractBTCSK(indexedBlock, pubRand, req.FinalitySig) if err != nil { panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) } - return nil, fmt.Errorf("the BTC validator %v votes two conflicting blocks! extracted secret key: %v", valPK.MustMarshal(), btcSK.Serialize()) - // TODO: what to do with the extracted secret key? e.g., have a KVStore that stores extracted SKs/forked blocks + + eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) + if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { + return nil, fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err) + } } - // TODO: it's also possible that the validator votes for a fork first, then vote for canonical - // block. We need to save the signatures on the fork, and add a detection here - // all good, add vote to DB - ms.SetSig(ctx, req.BlockHeight, valPK, req.FinalitySig) return &types.MsgAddFinalitySigResponse{}, nil } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 665c1f8f0..4791d9e49 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -5,7 +5,6 @@ import ( "math/rand" "testing" - "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -166,16 +165,30 @@ func FuzzAddFinalitySig(f *testing.F) { _, err = ms.AddFinalitySig(ctx, msg) require.Error(t, err) - // Case 5: fail if the BTC validator has voted for a fork + // Case 5: the BTC validator is slashed if the BTC validator votes for a fork blockHash2 := datagen.GenRandomByteArray(r, 32) msg2, err := types.NewMsgAddFinalitySig(signer, btcSK, sr, blockHeight, blockHash2) require.NoError(t, err) + // NOTE: even though this BTC validator is slashed, the msg should be successful + // Otherwise the saved evidence will be rolled back _, err = ms.AddFinalitySig(ctx, msg2) - require.Error(t, err) - // Also, this BTC validator can be slashed - // extract the SK and assert the extracted SK is correct - btcSK2, err := eots.Extract(btcPK, pr.ToFieldVal(), msg.MsgToSign(), sig.ToModNScalar(), msg2.MsgToSign(), msg2.FinalitySig.ToModNScalar()) require.NoError(t, err) - require.Equal(t, btcSK.Serialize(), btcSK2.Serialize()) + // ensure the evidence has been stored + evidence, err := fKeeper.GetEvidence(ctx, blockHeight, valBTCPK) + require.NoError(t, err) + require.Equal(t, msg2.BlockHeight, evidence.BlockHeight) + require.Equal(t, msg2.ValBtcPk.MustMarshal(), evidence.ValBtcPk.MustMarshal()) + require.Equal(t, msg2.BlockLastCommitHash, evidence.BlockLastCommitHash) + require.Equal(t, msg2.FinalitySig.MustMarshal(), evidence.FinalitySig.MustMarshal()) + // extract the SK and assert the extracted SK is correct + indexedBlock := &types.IndexedBlock{Height: blockHeight, LastCommitHash: blockHash} + btcSK2, err := evidence.ExtractBTCSK(indexedBlock, &pr, msg.FinalitySig) + require.NoError(t, err) + // ensure btcSK and btcSK2 correspond to the same PK + // NOTE: it's possible that different SKs derive to the same PK + // In this scenario, signature of any of these SKs can be verified with this PK + // exclude the first byte here since it denotes the y axis of PubKey, which does + // not affect verification + require.Equal(t, btcSK.PubKey().SerializeCompressed()[1:], btcSK2.PubKey().SerializeCompressed()[1:]) }) } diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 3303083ca..83fa6e380 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -6,10 +6,11 @@ import ( // x/finality module sentinel errors var ( - ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") - ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") - ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") - ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") - ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") - ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") + ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") + ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") + ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") + ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") + ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") + ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") + ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1107, "evidence is not found") ) diff --git a/x/finality/types/events.go b/x/finality/types/events.go new file mode 100644 index 000000000..d2337a65c --- /dev/null +++ b/x/finality/types/events.go @@ -0,0 +1,15 @@ +package types + +import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" +) + +func NewEventSlashedBTCValidator(valBTCPK *bbn.BIP340PubKey, indexedBlock *IndexedBlock, evidence *Evidence, btcSK *btcec.PrivateKey) *EventSlashedBTCValidator { + return &EventSlashedBTCValidator{ + ValBtcPk: valBTCPK, + IndexedBlock: indexedBlock, + Evidence: evidence, + ExtractedBtcSk: btcSK.Serialize(), + } +} diff --git a/x/finality/types/events.pb.go b/x/finality/types/events.pb.go new file mode 100644 index 000000000..b5f1358b1 --- /dev/null +++ b/x/finality/types/events.pb.go @@ -0,0 +1,507 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/finality/v1/events.proto + +package types + +import ( + fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// EventSlashedBTCValidator is the event emitted when a BTC validator is slashed +// due to signing two conflicting blocks +type EventSlashedBTCValidator struct { + // val_btc_pk is the BTC validator's BTC PK + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // indexed_block is the canonical block at this height + IndexedBlock *IndexedBlock `protobuf:"bytes,2,opt,name=indexed_block,json=indexedBlock,proto3" json:"indexed_block,omitempty"` + // evidence is the evidence that the BTC validator double signs + Evidence *Evidence `protobuf:"bytes,3,opt,name=evidence,proto3" json:"evidence,omitempty"` + // extracted_btc_sk is the extracted BTC SK of this BTC validator + ExtractedBtcSk []byte `protobuf:"bytes,4,opt,name=extracted_btc_sk,json=extractedBtcSk,proto3" json:"extracted_btc_sk,omitempty"` +} + +func (m *EventSlashedBTCValidator) Reset() { *m = EventSlashedBTCValidator{} } +func (m *EventSlashedBTCValidator) String() string { return proto.CompactTextString(m) } +func (*EventSlashedBTCValidator) ProtoMessage() {} +func (*EventSlashedBTCValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_c34c03aae5e3e6bf, []int{0} +} +func (m *EventSlashedBTCValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventSlashedBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventSlashedBTCValidator.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventSlashedBTCValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSlashedBTCValidator.Merge(m, src) +} +func (m *EventSlashedBTCValidator) XXX_Size() int { + return m.Size() +} +func (m *EventSlashedBTCValidator) XXX_DiscardUnknown() { + xxx_messageInfo_EventSlashedBTCValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSlashedBTCValidator proto.InternalMessageInfo + +func (m *EventSlashedBTCValidator) GetIndexedBlock() *IndexedBlock { + if m != nil { + return m.IndexedBlock + } + return nil +} + +func (m *EventSlashedBTCValidator) GetEvidence() *Evidence { + if m != nil { + return m.Evidence + } + return nil +} + +func (m *EventSlashedBTCValidator) GetExtractedBtcSk() []byte { + if m != nil { + return m.ExtractedBtcSk + } + return nil +} + +func init() { + proto.RegisterType((*EventSlashedBTCValidator)(nil), "babylon.finality.v1.EventSlashedBTCValidator") +} + +func init() { proto.RegisterFile("babylon/finality/v1/events.proto", fileDescriptor_c34c03aae5e3e6bf) } + +var fileDescriptor_c34c03aae5e3e6bf = []byte{ + // 330 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xcf, 0x4a, 0xc3, 0x30, + 0x1c, 0xc7, 0xd7, 0x29, 0x32, 0xe2, 0x14, 0xa9, 0x1e, 0xca, 0xc0, 0x3a, 0x77, 0xda, 0x29, 0xdd, + 0x1f, 0x11, 0xbc, 0x46, 0x26, 0x4c, 0x2f, 0xa3, 0x1b, 0x1e, 0xbc, 0x94, 0x24, 0x8d, 0x5b, 0x68, + 0x6c, 0xc6, 0xfa, 0x5b, 0x58, 0xdf, 0xc2, 0x17, 0xf0, 0x7d, 0x3c, 0xee, 0x28, 0x1e, 0x44, 0xb6, + 0x17, 0x91, 0x76, 0x7f, 0xf4, 0x30, 0xf0, 0x96, 0x5f, 0xf2, 0xc9, 0x37, 0xf9, 0x7e, 0x50, 0x95, + 0x51, 0x96, 0x2a, 0x1d, 0x7b, 0xcf, 0x32, 0xa6, 0x4a, 0x42, 0xea, 0x99, 0xa6, 0x27, 0x8c, 0x88, + 0x21, 0xc1, 0xe3, 0x89, 0x06, 0x6d, 0x9f, 0xae, 0x09, 0xbc, 0x21, 0xb0, 0x69, 0x56, 0xce, 0x86, + 0x7a, 0xa8, 0xf3, 0x73, 0x2f, 0x5b, 0xad, 0xd0, 0x4a, 0x6d, 0x57, 0xd8, 0xf6, 0x5a, 0xce, 0xd4, + 0xde, 0x8a, 0xc8, 0xe9, 0x64, 0xf9, 0x7d, 0x45, 0x93, 0x91, 0x08, 0xc9, 0xe0, 0xf6, 0x91, 0x2a, + 0x19, 0x52, 0xd0, 0x13, 0x7b, 0x80, 0x90, 0xa1, 0x2a, 0x60, 0xc0, 0x83, 0x71, 0xe4, 0x58, 0x55, + 0xab, 0x5e, 0x26, 0xd7, 0x9f, 0x5f, 0x17, 0xad, 0xa1, 0x84, 0xd1, 0x94, 0x61, 0xae, 0x5f, 0xbc, + 0xf5, 0x1b, 0x7c, 0x44, 0x65, 0xbc, 0x19, 0x3c, 0x48, 0xc7, 0x22, 0xc1, 0xa4, 0xdb, 0x6b, 0x5f, + 0x35, 0x7a, 0x53, 0xf6, 0x20, 0x52, 0xbf, 0x64, 0xa8, 0x22, 0xc0, 0x7b, 0x91, 0x7d, 0x87, 0x8e, + 0x64, 0x1c, 0x8a, 0x99, 0x08, 0x03, 0xa6, 0x34, 0x8f, 0x9c, 0x62, 0xd5, 0xaa, 0x1f, 0xb6, 0x2e, + 0xf1, 0x8e, 0x66, 0xb8, 0xbb, 0x22, 0x49, 0x06, 0xfa, 0x65, 0xf9, 0x67, 0xb2, 0x6f, 0x50, 0x49, + 0x18, 0x19, 0x8a, 0x98, 0x0b, 0x67, 0x2f, 0x8f, 0x38, 0xdf, 0x19, 0xd1, 0x59, 0x43, 0xfe, 0x16, + 0xb7, 0xeb, 0xe8, 0x44, 0xcc, 0x60, 0x42, 0x39, 0x64, 0x9f, 0x00, 0x1e, 0x24, 0x91, 0xb3, 0x9f, + 0xd5, 0xf3, 0x8f, 0xb7, 0xfb, 0x04, 0x78, 0x3f, 0x22, 0xf7, 0xef, 0x0b, 0xd7, 0x9a, 0x2f, 0x5c, + 0xeb, 0x7b, 0xe1, 0x5a, 0xaf, 0x4b, 0xb7, 0x30, 0x5f, 0xba, 0x85, 0x8f, 0xa5, 0x5b, 0x78, 0x6a, + 0xfc, 0x27, 0x61, 0xf6, 0xeb, 0x3d, 0xf7, 0xc1, 0x0e, 0x72, 0xe5, 0xed, 0x9f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x90, 0xd8, 0x29, 0xfa, 0xe5, 0x01, 0x00, 0x00, +} + +func (m *EventSlashedBTCValidator) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventSlashedBTCValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventSlashedBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ExtractedBtcSk) > 0 { + i -= len(m.ExtractedBtcSk) + copy(dAtA[i:], m.ExtractedBtcSk) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ExtractedBtcSk))) + i-- + dAtA[i] = 0x22 + } + if m.Evidence != nil { + { + size, err := m.Evidence.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.IndexedBlock != nil { + { + size, err := m.IndexedBlock.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { + offset -= sovEvents(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EventSlashedBTCValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovEvents(uint64(l)) + } + if m.IndexedBlock != nil { + l = m.IndexedBlock.Size() + n += 1 + l + sovEvents(uint64(l)) + } + if m.Evidence != nil { + l = m.Evidence.Size() + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.ExtractedBtcSk) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func sovEvents(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvents(x uint64) (n int) { + return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventSlashedBTCValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventSlashedBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IndexedBlock", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IndexedBlock == nil { + m.IndexedBlock = &IndexedBlock{} + } + if err := m.IndexedBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExtractedBtcSk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExtractedBtcSk = append(m.ExtractedBtcSk[:0], dAtA[iNdEx:postIndex]...) + if m.ExtractedBtcSk == nil { + m.ExtractedBtcSk = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipEvents(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthEvents + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupEvents + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthEvents + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthEvents = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowEvents = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupEvents = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go index 00026d05c..5fe688819 100644 --- a/x/finality/types/finality.go +++ b/x/finality/types/finality.go @@ -3,9 +3,21 @@ package types import ( "bytes" + "github.com/babylonchain/babylon/crypto/eots" + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" sdk "github.com/cosmos/cosmos-sdk/types" ) +// msgToSignForVote returns the message for an EOTS signature +// The EOTS signature on a block will be (blockHeight || blockHash) +func msgToSignForVote(blockHeight uint64, blockHash []byte) []byte { + msgToSign := []byte{} + msgToSign = append(msgToSign, sdk.Uint64ToBigEndian(blockHeight)...) + msgToSign = append(msgToSign, blockHash...) + return msgToSign +} + func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { if !bytes.Equal(ib.LastCommitHash, ib2.LastCommitHash) { return false @@ -18,14 +30,23 @@ func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { } func (ib *IndexedBlock) MsgToSign() []byte { - return MsgToSignForVote(ib.Height, ib.LastCommitHash) + return msgToSignForVote(ib.Height, ib.LastCommitHash) } -// MsgToSignForVote returns the message for an EOTS signature -// The EOTS signature on a block will be (blockHeight || blockHash) -func MsgToSignForVote(blockHeight uint64, blockHash []byte) []byte { - msgToSign := []byte{} - msgToSign = append(msgToSign, sdk.Uint64ToBigEndian(blockHeight)...) - msgToSign = append(msgToSign, blockHash...) - return msgToSign +func (e *Evidence) MsgToSign() []byte { + return msgToSignForVote(e.BlockHeight, e.BlockLastCommitHash) +} + +// ExtractBTCSK extracts the BTC SK given the canonical block, public randomness +// and EOTS signature on the canonical block +func (e *Evidence) ExtractBTCSK(indexedBlock *IndexedBlock, pubRand *bbn.SchnorrPubRand, sig *bbn.SchnorrEOTSSig) (*btcec.PrivateKey, error) { + btcPK, err := e.ValBtcPk.ToBTCPK() + if err != nil { + return nil, err + } + return eots.Extract( + btcPK, pubRand.ToFieldVal(), + indexedBlock.MsgToSign(), sig.ToModNScalar(), // msg and sig for canonical block + e.MsgToSign(), e.FinalitySig.ToModNScalar(), // msg and sig for fork block + ) } diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 229d17da1..c268622d7 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -5,6 +5,8 @@ package types import ( fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -87,8 +89,75 @@ func (m *IndexedBlock) GetFinalized() bool { return false } +// Evidence is the evidence that a BTC validator has signed a finality +// signature with correct public randomness on a fork header +// It includes all fields of MsgAddFinalitySig, such that anyone seeing +// the evidence and a signature on the canonical fork can extract the +// BTC validator's BTC secret key. +type Evidence struct { + // val_btc_pk is the BTC Pk of the validator that casts this vote + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // block_height is the height of the voted block + BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // block_last_commit_hash is the last_commit_hash of the voted block + BlockLastCommitHash []byte `protobuf:"bytes,3,opt,name=block_last_commit_hash,json=blockLastCommitHash,proto3" json:"block_last_commit_hash,omitempty"` + // finality_sig is the finality signature to this block + // where finality signature is an EOTS signature, i.e., + // the `s` in a Schnorr signature `(r, s)` + // `r` is the public randomness that is already committed by the validator + FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,4,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` +} + +func (m *Evidence) Reset() { *m = Evidence{} } +func (m *Evidence) String() string { return proto.CompactTextString(m) } +func (*Evidence) ProtoMessage() {} +func (*Evidence) Descriptor() ([]byte, []int) { + return fileDescriptor_ca5b87e52e3e6d02, []int{1} +} +func (m *Evidence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Evidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Evidence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Evidence) XXX_Merge(src proto.Message) { + xxx_messageInfo_Evidence.Merge(m, src) +} +func (m *Evidence) XXX_Size() int { + return m.Size() +} +func (m *Evidence) XXX_DiscardUnknown() { + xxx_messageInfo_Evidence.DiscardUnknown(m) +} + +var xxx_messageInfo_Evidence proto.InternalMessageInfo + +func (m *Evidence) GetBlockHeight() uint64 { + if m != nil { + return m.BlockHeight + } + return 0 +} + +func (m *Evidence) GetBlockLastCommitHash() []byte { + if m != nil { + return m.BlockLastCommitHash + } + return nil +} + func init() { proto.RegisterType((*IndexedBlock)(nil), "babylon.finality.v1.IndexedBlock") + proto.RegisterType((*Evidence)(nil), "babylon.finality.v1.Evidence") } func init() { @@ -96,21 +165,31 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 215 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0x84, - 0xb3, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x84, 0xa1, 0x6a, 0xf4, 0xe0, 0xe2, 0x65, 0x86, - 0x4a, 0x79, 0x5c, 0x3c, 0x9e, 0x79, 0x29, 0xa9, 0x15, 0xa9, 0x29, 0x4e, 0x39, 0xf9, 0xc9, 0xd9, - 0x42, 0x62, 0x5c, 0x6c, 0x19, 0xa9, 0x99, 0xe9, 0x19, 0x25, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x2c, - 0x41, 0x50, 0x9e, 0x90, 0x06, 0x97, 0x40, 0x4e, 0x62, 0x71, 0x49, 0x7c, 0x72, 0x7e, 0x6e, 0x6e, - 0x66, 0x49, 0x7c, 0x46, 0x62, 0x71, 0x86, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x1f, 0x48, - 0xdc, 0x19, 0x2c, 0xec, 0x91, 0x58, 0x9c, 0x21, 0x24, 0xc3, 0xc5, 0x09, 0xb1, 0xa0, 0x2a, 0x35, - 0x45, 0x82, 0x59, 0x81, 0x51, 0x83, 0x23, 0x08, 0x21, 0xe0, 0xe4, 0x75, 0xe2, 0x91, 0x1c, 0xe3, - 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, - 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x06, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, - 0xb9, 0xfa, 0x50, 0x97, 0x26, 0x67, 0x24, 0x66, 0xe6, 0xc1, 0x38, 0xfa, 0x15, 0x08, 0xcf, 0x95, - 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0xfd, 0x65, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xdb, - 0xa3, 0x56, 0xfe, 0xfd, 0x00, 0x00, 0x00, + // 370 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x6b, 0xe2, 0x40, + 0x14, 0xc7, 0x8d, 0x8a, 0xb8, 0x63, 0x58, 0x96, 0xb8, 0x48, 0x58, 0x96, 0xe8, 0x7a, 0xca, 0x29, + 0xd1, 0x55, 0x96, 0x3d, 0xa7, 0x08, 0xda, 0x16, 0x2a, 0x89, 0xa7, 0xf6, 0x10, 0x26, 0x93, 0x69, + 0x66, 0x30, 0x66, 0xc4, 0x8c, 0xc1, 0xf4, 0x53, 0xf4, 0xd2, 0xef, 0xd4, 0xa3, 0xc7, 0xe2, 0x41, + 0x8a, 0x7e, 0x91, 0xe2, 0x24, 0x2a, 0xa5, 0x87, 0xf6, 0xf6, 0xde, 0xff, 0xfd, 0x79, 0xff, 0xf7, + 0x4b, 0x06, 0xb4, 0x3d, 0xe8, 0xa5, 0x21, 0x8b, 0xcc, 0x7b, 0x1a, 0xc1, 0x90, 0xf2, 0xd4, 0x4c, + 0xba, 0xa7, 0xda, 0x98, 0x2f, 0x18, 0x67, 0x4a, 0x3d, 0xf7, 0x18, 0x27, 0x3d, 0xe9, 0xfe, 0xfa, + 0x19, 0xb0, 0x80, 0x89, 0xb9, 0x79, 0xa8, 0x32, 0x6b, 0x3b, 0x02, 0xf2, 0x28, 0xf2, 0xf1, 0x0a, + 0xfb, 0x56, 0xc8, 0xd0, 0x54, 0x69, 0x80, 0x0a, 0xc1, 0x34, 0x20, 0x5c, 0x95, 0x5a, 0x92, 0x5e, + 0xb6, 0xf3, 0x4e, 0xd1, 0xc1, 0x8f, 0x10, 0xc6, 0xdc, 0x45, 0x6c, 0x36, 0xa3, 0xdc, 0x25, 0x30, + 0x26, 0x6a, 0xb1, 0x25, 0xe9, 0xb2, 0xfd, 0xfd, 0xa0, 0x5f, 0x08, 0x79, 0x08, 0x63, 0xa2, 0xfc, + 0x06, 0xdf, 0xb2, 0xd8, 0x07, 0xec, 0xab, 0xa5, 0x96, 0xa4, 0x57, 0xed, 0xb3, 0xd0, 0x7e, 0x2a, + 0x82, 0xea, 0x20, 0xa1, 0x3e, 0x8e, 0x10, 0x56, 0x26, 0x00, 0x24, 0x30, 0x74, 0x3d, 0x8e, 0xdc, + 0xf9, 0x54, 0x04, 0xca, 0xd6, 0xbf, 0xcd, 0xb6, 0xf9, 0x37, 0xa0, 0x9c, 0x2c, 0x3d, 0x03, 0xb1, + 0x99, 0x99, 0xa3, 0x20, 0x02, 0x69, 0x74, 0x6c, 0x4c, 0x9e, 0xce, 0x71, 0x6c, 0x58, 0xa3, 0x71, + 0xaf, 0xdf, 0x19, 0x2f, 0xbd, 0x2b, 0x9c, 0xda, 0xd5, 0x04, 0x86, 0x16, 0x47, 0xe3, 0xa9, 0xf2, + 0x07, 0xc8, 0xde, 0x81, 0xc5, 0xcd, 0x41, 0x8a, 0x02, 0xa4, 0x26, 0xb4, 0x61, 0x46, 0xd3, 0x03, + 0x8d, 0xcc, 0xf2, 0x81, 0xa9, 0x24, 0x98, 0xea, 0x62, 0x7a, 0xfd, 0x1e, 0xec, 0x0e, 0xc8, 0xc7, + 0xef, 0xe9, 0xc6, 0x34, 0x50, 0xcb, 0xe2, 0xde, 0xff, 0x9b, 0x6d, 0xb3, 0xff, 0xb5, 0x7b, 0x1d, + 0x44, 0x22, 0xb6, 0x58, 0x0c, 0x6e, 0x26, 0x8e, 0x43, 0x03, 0xbb, 0x76, 0xdc, 0xe6, 0xd0, 0xc0, + 0xba, 0x7c, 0xde, 0x69, 0xd2, 0x7a, 0xa7, 0x49, 0xaf, 0x3b, 0x4d, 0x7a, 0xdc, 0x6b, 0x85, 0xf5, + 0x5e, 0x2b, 0xbc, 0xec, 0xb5, 0xc2, 0x6d, 0xe7, 0xb3, 0xe5, 0xab, 0xf3, 0x53, 0x10, 0x39, 0x5e, + 0x45, 0xfc, 0xda, 0xde, 0x5b, 0x00, 0x00, 0x00, 0xff, 0xff, 0x55, 0x87, 0x93, 0x2b, 0x2b, 0x02, + 0x00, 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { @@ -158,6 +237,65 @@ func (m *IndexedBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Evidence) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.FinalitySig != nil { + { + size := m.FinalitySig.Size() + i -= size + if _, err := m.FinalitySig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.BlockLastCommitHash) > 0 { + i -= len(m.BlockLastCommitHash) + copy(dAtA[i:], m.BlockLastCommitHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.BlockLastCommitHash))) + i-- + dAtA[i] = 0x1a + } + if m.BlockHeight != 0 { + i = encodeVarintFinality(dAtA, i, uint64(m.BlockHeight)) + i-- + dAtA[i] = 0x10 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintFinality(dAtA []byte, offset int, v uint64) int { offset -= sovFinality(v) base := offset @@ -188,6 +326,30 @@ func (m *IndexedBlock) Size() (n int) { return n } +func (m *Evidence) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovFinality(uint64(l)) + } + if m.BlockHeight != 0 { + n += 1 + sovFinality(uint64(m.BlockHeight)) + } + l = len(m.BlockLastCommitHash) + if l > 0 { + n += 1 + l + sovFinality(uint64(l)) + } + if m.FinalitySig != nil { + l = m.FinalitySig.Size() + n += 1 + l + sovFinality(uint64(l)) + } + return n +} + func sovFinality(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -317,6 +479,179 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { } return nil } +func (m *Evidence) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) + } + m.BlockHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockLastCommitHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BlockLastCommitHash = append(m.BlockLastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.BlockLastCommitHash == nil { + m.BlockLastCommitHash = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FinalitySig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.SchnorrEOTSSig + m.FinalitySig = &v + if err := m.FinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFinality(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthFinality + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipFinality(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/keys.go b/x/finality/types/keys.go index 76c6dd3c8..b949e2271 100644 --- a/x/finality/types/keys.go +++ b/x/finality/types/keys.go @@ -15,10 +15,11 @@ const ( ) var ( - BlockKey = []byte{0x01} // key prefix for blocks - VoteKey = []byte{0x02} // key prefix for votes - PubRandKey = []byte{0x03} // key prefix for public randomness - ParamsKey = []byte{0x04} // key prefix for the parameters + BlockKey = []byte{0x01} // key prefix for blocks + VoteKey = []byte{0x02} // key prefix for votes + PubRandKey = []byte{0x03} // key prefix for public randomness + ParamsKey = []byte{0x04} // key prefix for the parameters + EvidenceKey = []byte{0x05} // key prefix for evidences ) func KeyPrefix(p string) []byte { diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index f0489079c..c1e9ef4f9 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -79,7 +79,7 @@ func (m *MsgAddFinalitySig) ValidateBasic() error { } func (m *MsgAddFinalitySig) MsgToSign() []byte { - return MsgToSignForVote(m.BlockHeight, m.BlockLastCommitHash) + return msgToSignForVote(m.BlockHeight, m.BlockLastCommitHash) } func (m *MsgAddFinalitySig) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { From 067082b9d3dd8dbe775d5ada70cd60151fe0f577 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 11 Jul 2023 15:28:54 +1000 Subject: [PATCH 025/202] BTC staking / finality: CLIs for submitting txs and e2e commands (#19) --- test/e2e/configurer/chain/commands.go | 15 +- .../configurer/chain/commands_btcstaking.go | 116 +++++++++++ types/btc_schnorr_eots.go | 14 ++ types/btc_schnorr_pk.go | 2 +- types/btc_schnorr_pub_rand.go | 14 ++ types/btc_schnorr_sig.go | 18 +- x/btccheckpoint/types/types.go | 20 ++ x/btcstaking/client/cli/tx.go | 191 +++++++++++++++++- x/btcstaking/keeper/voting_power_table.go | 2 +- .../keeper/voting_power_table_test.go | 2 +- x/btcstaking/types/btc_slashing_tx.go | 26 ++- x/btcstaking/types/btcstaking.go | 21 ++ x/btcstaking/types/pop.go | 21 ++ x/finality/client/cli/tx.go | 131 +++++++++++- x/finality/keeper/tallying_test.go | 4 +- x/finality/keeper/votes.go | 2 +- 16 files changed, 569 insertions(+), 30 deletions(-) create mode 100644 test/e2e/configurer/chain/commands_btcstaking.go diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index df6f5a6e7..66a6dd1d5 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -8,21 +8,16 @@ import ( "strings" "time" - "github.com/cosmos/cosmos-sdk/types/bech32" - - sdkquerytypes "github.com/cosmos/cosmos-sdk/types/query" - - btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - cttypes "github.com/babylonchain/babylon/x/checkpointing/types" - txformat "github.com/babylonchain/babylon/btctxformatter" - bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/test/e2e/initialization" "github.com/babylonchain/babylon/test/e2e/util" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" blc "github.com/babylonchain/babylon/x/btclightclient/types" - + cttypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/cosmos/cosmos-sdk/types/bech32" + sdkquerytypes "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" ) diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go new file mode 100644 index 000000000..1a7ae2f29 --- /dev/null +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -0,0 +1,116 @@ +package chain + +import ( + "encoding/hex" + "strconv" + + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/stretchr/testify/require" +) + +func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession) { + n.LogActionF("creating BTC validator") + + // get babylon PK hex + babylonPKBytes, err := babylonPK.Marshal() + require.NoError(n.t, err) + babylonPKHex := hex.EncodeToString(babylonPKBytes) + // get BTC PK hex + btcPKHex := btcPK.ToHexStr() + // get pop hex + popHex, err := pop.ToHexStr() + require.NoError(n.t, err) + + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-validator", babylonPKHex, btcPKHex, popHex, "--from=val"} + _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully created BTC validator") +} + +func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstypes.ProofOfPossession, stakingTx *bstypes.StakingTx, stakingTxInfo *btcctypes.TransactionInfo, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { + n.LogActionF("creating BTC delegation") + + // get babylon PK hex + babylonPKBytes, err := babylonPK.Marshal() + require.NoError(n.t, err) + babylonPKHex := hex.EncodeToString(babylonPKBytes) + // get pop hex + popHex, err := pop.ToHexStr() + require.NoError(n.t, err) + // get staking tx hex + stakingTxHex, err := stakingTx.ToHexStr() + require.NoError(n.t, err) + // get staking tx info hex + stakingTxInfoHex, err := stakingTxInfo.ToHexStr() + require.NoError(n.t, err) + // get slashing tx hex + slashingTxHex := slashingTx.ToHexStr() + // get delegator sig hex + delegatorSigHex := delegatorSig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, popHex, stakingTxHex, stakingTxInfoHex, slashingTxHex, delegatorSigHex, "--from=val"} + _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully created BTC delegation") +} + +func (n *NodeConfig) AddJurySig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, sig *bbn.BIP340Signature) { + n.LogActionF("adding jury signature") + + valPKHex := valPK.ToHexStr() + delPKHex := delPK.ToHexStr() + sigHex := sig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "add-jury-sig", valPKHex, delPKHex, sigHex, "--from=val"} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully added jury sig") +} + +func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand, sig *bbn.BIP340Signature) { + n.LogActionF("committing public randomness list") + + cmd := []string{"babylond", "tx", "finality", "commit-pubrand-list"} + + // add val BTC PK to cmd + valBTCPKHex := valBTCPK.ToHexStr() + cmd = append(cmd, valBTCPKHex) + + // add start height to cmd + startHeightStr := strconv.FormatUint(startHeight, 10) + cmd = append(cmd, startHeightStr) + + // add each pubrand to cmd + for _, pr := range pubRandList { + prHex := pr.ToHexStr() + cmd = append(cmd, prHex) + } + + // add sig to cmd + sigHex := sig.ToHexStr() + cmd = append(cmd, sigHex) + + // specify used key + cmd = append(cmd, "--from=val") + + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully committed public randomness list") +} + +func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint64, blockLch []byte, finalitySig *bbn.SchnorrEOTSSig) { + n.LogActionF("add finality signature") + + valBTCPKHex := valBTCPK.ToHexStr() + blockHeightStr := strconv.FormatUint(blockHeight, 10) + blockLchHex := hex.EncodeToString(blockLch) + finalitySigHex := finalitySig.ToHexStr() + + cmd := []string{"babylond", "tx", "finality", "add-finality-sig", valBTCPKHex, blockHeightStr, blockLchHex, finalitySigHex, "--from=val"} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully added finality signature") +} diff --git a/types/btc_schnorr_eots.go b/types/btc_schnorr_eots.go index acbdcdb91..f8980db5b 100644 --- a/types/btc_schnorr_eots.go +++ b/types/btc_schnorr_eots.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "encoding/hex" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -17,6 +18,14 @@ func NewSchnorrEOTSSig(data []byte) (*SchnorrEOTSSig, error) { return &sig, err } +func NewSchnorrEOTSSigFromHex(sigHex string) (*SchnorrEOTSSig, error) { + sigBytes, err := hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + return NewSchnorrEOTSSig(sigBytes) +} + func NewSchnorrEOTSSigFromModNScalar(s *btcec.ModNScalar) *SchnorrEOTSSig { prBytes := s.Bytes() sig := SchnorrEOTSSig(prBytes[:]) @@ -65,3 +74,8 @@ func (sig *SchnorrEOTSSig) Unmarshal(data []byte) error { func (sig *SchnorrEOTSSig) Equals(sig2 *SchnorrEOTSSig) bool { return bytes.Equal(sig.MustMarshal(), sig2.MustMarshal()) } + +func (sig *SchnorrEOTSSig) ToHexStr() string { + sigBytes := sig.MustMarshal() + return hex.EncodeToString(sigBytes) +} diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index ad20c6034..4109bdd52 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -39,7 +39,7 @@ func (pk BIP340PubKey) ToBTCPK() (*btcec.PublicKey, error) { return schnorr.ParsePubKey(pk) } -func (pk *BIP340PubKey) ToHex() string { +func (pk *BIP340PubKey) ToHexStr() string { return hex.EncodeToString(pk.MustMarshal()) } diff --git a/types/btc_schnorr_pub_rand.go b/types/btc_schnorr_pub_rand.go index d0bd52603..be92231d2 100644 --- a/types/btc_schnorr_pub_rand.go +++ b/types/btc_schnorr_pub_rand.go @@ -1,6 +1,7 @@ package types import ( + "encoding/hex" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -16,6 +17,14 @@ func NewSchnorrPubRand(data []byte) (*SchnorrPubRand, error) { return &pr, err } +func NewSchnorrPubRandFromHex(prHex string) (*SchnorrPubRand, error) { + prBytes, err := hex.DecodeString(prHex) + if err != nil { + return nil, err + } + return NewSchnorrPubRand(prBytes) +} + func NewSchnorrPubRandFromFieldVal(r *btcec.FieldVal) *SchnorrPubRand { prBytes := r.Bytes() pr := SchnorrPubRand(prBytes[:]) @@ -60,3 +69,8 @@ func (pr *SchnorrPubRand) Unmarshal(data []byte) error { *pr = data return nil } + +func (pr *SchnorrPubRand) ToHexStr() string { + prBytes := pr.MustMarshal() + return hex.EncodeToString(prBytes) +} diff --git a/types/btc_schnorr_sig.go b/types/btc_schnorr_sig.go index 74b919be2..eee896b7c 100644 --- a/types/btc_schnorr_sig.go +++ b/types/btc_schnorr_sig.go @@ -1,6 +1,7 @@ package types import ( + "encoding/hex" "errors" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -10,10 +11,18 @@ type BIP340Signature []byte const BIP340SignatureLen = schnorr.SignatureSize -func NewBIP340Signature(data []byte) (BIP340Signature, error) { +func NewBIP340Signature(data []byte) (*BIP340Signature, error) { var sig BIP340Signature err := sig.Unmarshal(data) - return sig, err + return &sig, err +} + +func NewBIP340SignatureFromHex(sigHex string) (*BIP340Signature, error) { + sigBytes, err := hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + return NewBIP340Signature(sigBytes) } func NewBIP340SignatureFromBTCSig(btcSig *schnorr.Signature) BIP340Signature { @@ -63,3 +72,8 @@ func (sig *BIP340Signature) Unmarshal(data []byte) error { *sig = data return nil } + +func (sig *BIP340Signature) ToHexStr() string { + sigBytes := sig.MustMarshal() + return hex.EncodeToString(sigBytes) +} diff --git a/x/btccheckpoint/types/types.go b/x/btccheckpoint/types/types.go index d5b9b5c93..c6e781fbc 100644 --- a/x/btccheckpoint/types/types.go +++ b/x/btccheckpoint/types/types.go @@ -162,6 +162,26 @@ func NewTransactionInfo(txKey *TransactionKey, txBytes []byte, proof []byte) *Tr } } +func NewTransactionInfoFromHex(txInfoHex string) (*TransactionInfo, error) { + txInfoBytes, err := hex.DecodeString(txInfoHex) + if err != nil { + return nil, err + } + var txInfo TransactionInfo + if err := txInfo.Unmarshal(txInfoBytes); err != nil { + return nil, err + } + return &txInfo, nil +} + +func (ti *TransactionInfo) ToHexStr() (string, error) { + txInfoBytes, err := ti.Marshal() + if err != nil { + return "", err + } + return hex.EncodeToString(txInfoBytes), nil +} + func (ti *TransactionInfo) ValidateBasic() error { if ti.Key == nil { return fmt.Errorf("key in TransactionInfo is nil") diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 0f60362c1..ac26ab9f5 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -1,18 +1,20 @@ package cli import ( + "encoding/hex" "fmt" - "time" + "strings" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/spf13/cobra" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -23,5 +25,184 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } + cmd.AddCommand( + NewCreateBTCValidatorCmd(), + NewCreateBTCDelegationCmd(), + NewAddJurySigCmd(), + ) + + return cmd +} + +func NewCreateBTCValidatorCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-btc-validator [babylon_pk] [btc_pk] [pop]", + Args: cobra.ExactArgs(3), + Short: "Create a BTC validator", + Long: strings.TrimSpace( + `Create a BTC validator.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get Babylon PK + babylonPKBytes, err := hex.DecodeString(args[0]) + if err != nil { + return err + } + var babylonPK secp256k1.PubKey + if err := babylonPK.Unmarshal(babylonPKBytes); err != nil { + return err + } + + // get BTC PK + btcPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + if err != nil { + return err + } + + // get PoP + pop, err := types.NewPoPFromHex(args[2]) + if err != nil { + return err + } + + msg := types.MsgCreateBTCValidator{ + Signer: clientCtx.FromAddress.String(), + BabylonPk: &babylonPK, + BtcPk: btcPK, + Pop: pop, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewCreateBTCDelegationCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-btc-delegation [babylon_pk] [pop] [staking_tx] [staking_tx_info] [slashing_tx] [delegator_sig]", + Args: cobra.ExactArgs(6), + Short: "Create a BTC delegation", + Long: strings.TrimSpace( + `Create a BTC delegation.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get Babylon PK + babylonPKBytes, err := hex.DecodeString(args[0]) + if err != nil { + return err + } + var babylonPK secp256k1.PubKey + if err := babylonPK.Unmarshal(babylonPKBytes); err != nil { + return err + } + + // get PoP + pop, err := types.NewPoPFromHex(args[1]) + if err != nil { + return err + } + + // get staking tx + stakingTx, err := types.NewStakingTxFromHex(args[2]) + if err != nil { + return err + } + + // get staking tx info + stakingTxInfo, err := btcctypes.NewTransactionInfoFromHex(args[3]) + if err != nil { + return err + } + + // get slashing tx + slashingTx, err := types.NewBTCSlashingTxFromHex(args[4]) + if err != nil { + return err + } + + // get delegator sig + delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[5]) + if err != nil { + return err + } + + msg := types.MsgCreateBTCDelegation{ + Signer: clientCtx.FromAddress.String(), + BabylonPk: &babylonPK, + Pop: pop, + StakingTx: stakingTx, + StakingTxInfo: stakingTxInfo, + SlashingTx: slashingTx, + DelegatorSig: delegatorSig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewAddJurySigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-jury-sig [val_pk] [del_pk] [sig]", + Args: cobra.ExactArgs(3), + Short: "Add a jury signature", + Long: strings.TrimSpace( + `Add a jury signature.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get validator PK + valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + + // get delegator PK + delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + if err != nil { + return err + } + + // get jury sigature + sig, err := bbn.NewBIP340SignatureFromHex(args[2]) + if err != nil { + return err + } + + msg := types.MsgAddJurySig{ + Signer: clientCtx.FromAddress.String(), + ValPk: valPK, + DelPk: delPK, + Sig: sig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 06c4ac408..40b4ab47d 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -83,7 +83,7 @@ func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]u // failing to unmarshal validator BTC PK in KVStore is a programming error panic(fmt.Errorf("failed to unmarshal validator BTC PK: %w", err)) } - valSet[valBTCPK.ToHex()] = sdk.BigEndianToUint64(iter.Value()) + valSet[valBTCPK.ToHexStr()] = sdk.BigEndianToUint64(iter.Value()) } return valSet diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index a8d3a9add..dbcb70730 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -82,7 +82,7 @@ func FuzzVotingPowerTable(f *testing.F) { require.NotNil(t, powerTable) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) - require.Equal(t, powerTable[btcVals[i].BtcPk.ToHex()], power) + require.Equal(t, powerTable[btcVals[i].BtcPk.ToHexStr()], power) } // the activation height should be the current Babylon height as well activatedHeight, err := keeper.GetBTCStakingActivatedHeight(ctx) diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 3b85d10d0..b16ac15e4 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "encoding/hex" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" @@ -26,6 +27,18 @@ func NewBTCSlashingTxFromMsgTx(msgTx *wire.MsgTx) (*BTCSlashingTx, error) { return &tx, nil } +func NewBTCSlashingTxFromHex(txHex string) (*BTCSlashingTx, error) { + txBytes, err := hex.DecodeString(txHex) + if err != nil { + return nil, err + } + var tx BTCSlashingTx + if err := tx.Unmarshal(txBytes); err != nil { + return nil, err + } + return &tx, nil +} + func (tx BTCSlashingTx) Marshal() ([]byte, error) { return tx, nil } @@ -48,9 +61,13 @@ func (tx BTCSlashingTx) MarshalTo(data []byte) (int, error) { } func (tx *BTCSlashingTx) Unmarshal(data []byte) error { - // TODO: verifications - *tx = data + + // ensure data can be decoded to a tx + if _, err := tx.ToMsgTx(); err != nil { + return err + } + return nil } @@ -58,6 +75,11 @@ func (tx *BTCSlashingTx) Size() int { return len(tx.MustMarshal()) } +func (tx *BTCSlashingTx) ToHexStr() string { + txBytes := tx.MustMarshal() + return hex.EncodeToString(txBytes) +} + func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { var msgTx wire.MsgTx rbuf := bytes.NewReader(*tx) diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 14a12a1eb..e60b693d2 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "encoding/hex" "fmt" "github.com/babylonchain/babylon/btcstaking" @@ -109,6 +110,26 @@ func (p *ProofOfPossession) ValidateBasic() error { return nil } +func NewStakingTxFromHex(txHex string) (*StakingTx, error) { + txBytes, err := hex.DecodeString(txHex) + if err != nil { + return nil, err + } + var tx StakingTx + if err := tx.Unmarshal(txBytes); err != nil { + return nil, err + } + return &tx, nil +} + +func (tx *StakingTx) ToHexStr() (string, error) { + txBytes, err := tx.Marshal() + if err != nil { + return "", err + } + return hex.EncodeToString(txBytes), nil +} + func (tx *StakingTx) Equals(tx2 *StakingTx) bool { return bytes.Equal(tx.Tx, tx2.Tx) && bytes.Equal(tx.StakingScript, tx2.StakingScript) } diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index c2653beec..1bb841ab3 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -1,6 +1,7 @@ package types import ( + "encoding/hex" "fmt" bbn "github.com/babylonchain/babylon/types" @@ -40,6 +41,26 @@ func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPos return &pop, nil } +func NewPoPFromHex(popHex string) (*ProofOfPossession, error) { + popBytes, err := hex.DecodeString(popHex) + if err != nil { + return nil, err + } + var pop ProofOfPossession + if err := pop.Unmarshal(popBytes); err != nil { + return nil, err + } + return &pop, nil +} + +func (pop *ProofOfPossession) ToHexStr() (string, error) { + popBytes, err := pop.Marshal() + if err != nil { + return "", err + } + return hex.EncodeToString(popBytes), nil +} + // Verify verifies the validity of PoP // 1. verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? // 2. verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? diff --git a/x/finality/client/cli/tx.go b/x/finality/client/cli/tx.go index ba0e1b4d9..7c941c0cd 100644 --- a/x/finality/client/cli/tx.go +++ b/x/finality/client/cli/tx.go @@ -1,18 +1,19 @@ package cli import ( + "encoding/hex" "fmt" - "time" + "strconv" + "strings" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" "github.com/spf13/cobra" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -23,5 +24,125 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } + cmd.AddCommand( + NewCommitPubRandListCmd(), + NewAddFinalitySigCmd(), + ) + + return cmd +} + +func NewCommitPubRandListCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "commit-pubrand-list [val_btc_pk] [start_height] [pub_rand1] [pub_rand2] ... [sig]", + Args: cobra.MinimumNArgs(4), + Short: "Commit a list of public randomness", + Long: strings.TrimSpace( + `Commit a list of public randomness.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get validator BTC PK + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + + // get start height + startHeight, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + // get signature + sig, err := bbn.NewBIP340SignatureFromHex(args[len(args)-1]) + if err != nil { + return err + } + + // get pub rand list + pubRandHexList := args[2 : len(args)-1] + pubRandList := []bbn.SchnorrPubRand{} + for _, prHex := range pubRandHexList { + pr, err := bbn.NewSchnorrPubRandFromHex(prHex) + if err != nil { + return err + } + pubRandList = append(pubRandList, *pr) + } + + msg := types.MsgCommitPubRandList{ + Signer: clientCtx.FromAddress.String(), + ValBtcPk: valBTCPK, + StartHeight: startHeight, + PubRandList: pubRandList, + Sig: sig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewAddFinalitySigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-finality-sig [val_btc_pk] [block_height] [block_last_commit_hash] [finality_sig]", + Args: cobra.ExactArgs(4), + Short: "Add a finality signature", + Long: strings.TrimSpace( + `Add a finality signature.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get validator BTC PK + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + + // get block height + blockHeight, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + // get block last commit hash + blockLch, err := hex.DecodeString(args[2]) + if err != nil { + return err + } + + // get finality signature + finalitySig, err := bbn.NewSchnorrEOTSSigFromHex(args[3]) + if err != nil { + return err + } + + msg := types.MsgAddFinalitySig{ + Signer: clientCtx.FromAddress.String(), + ValBtcPk: valBTCPK, + BlockHeight: blockHeight, + BlockLastCommitHash: blockLch, + FinalitySig: finalitySig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd } diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 5b68414e4..75ecd614e 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -130,7 +130,7 @@ func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakin } fKeeper.SetSig(ctx, height, votedValPK, votedSig) // add val - valSet[votedValPK.ToHex()] = 1 + valSet[votedValPK.ToHexStr()] = 1 } // the rest val that does not vote valSet[hex.EncodeToString(datagen.GenRandomByteArray(r, 32))] = 1 @@ -152,7 +152,7 @@ func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStak fKeeper.SetSig(ctx, height, votedValPK, votedSig) // 4 BTC vals valSet := map[string]uint64{ - votedValPK.ToHex(): 1, + votedValPK.ToHexStr(): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index 2a9f65b9b..910f8e01a 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -58,7 +58,7 @@ func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.Schnor // failing to unmarshal EOTS sig in KVStore is a programming error panic(fmt.Errorf("failed to unmarshal EOTS signature: %w", err)) } - sigs[valBTCPK.ToHex()] = sig + sigs[valBTCPK.ToHexStr()] = sig } return sigs } From 2f79071fffa8e74b2b9ff9bfb3cc0096d10531b6 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 11 Jul 2023 20:23:07 +1000 Subject: [PATCH 026/202] btcstaking: index BTC height (#21) --- x/btcstaking/abci.go | 8 +++- x/btcstaking/keeper/btc_height_index.go | 42 ++++++++++++++++++ x/btcstaking/keeper/btc_height_index_test.go | 43 +++++++++++++++++++ x/btcstaking/keeper/voting_power_table.go | 6 +-- .../keeper/voting_power_table_test.go | 3 ++ x/btcstaking/types/errors.go | 1 + x/btcstaking/types/keys.go | 1 + 7 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 x/btcstaking/keeper/btc_height_index.go create mode 100644 x/btcstaking/keeper/btc_height_index_test.go diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index 495b0e1d8..b995dad8a 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -17,8 +17,12 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - // update BTC validator power table - k.RecordVotingPowerTable(ctx) + // if the BTC staking protocol is activated, i.e., there exists a height where a BTC validator + // has voting power, index BTC height and update BTC validator power table + if _, err := k.GetBTCStakingActivatedHeight(ctx); err == nil { + k.IndexBTCHeight(ctx) + k.RecordVotingPowerTable(ctx) + } return []abci.ValidatorUpdate{} } diff --git a/x/btcstaking/keeper/btc_height_index.go b/x/btcstaking/keeper/btc_height_index.go new file mode 100644 index 000000000..6b82878ca --- /dev/null +++ b/x/btcstaking/keeper/btc_height_index.go @@ -0,0 +1,42 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// IndexBTCHeight indexes the current BTC height, and saves it to KVStore +func (k Keeper) IndexBTCHeight(ctx sdk.Context) { + babylonHeight := uint64(ctx.BlockHeight()) + btcTip := k.btclcKeeper.GetTipInfo(ctx) + if btcTip == nil { + return + } + btcHeight := btcTip.Height + store := k.btcHeightStore(ctx) + store.Set(sdk.Uint64ToBigEndian(babylonHeight), sdk.Uint64ToBigEndian(btcHeight)) +} + +func (k Keeper) GetBTCHeightAtBabylonHeight(ctx sdk.Context, babylonHeight uint64) (uint64, error) { + store := k.btcHeightStore(ctx) + btcHeightBytes := store.Get(sdk.Uint64ToBigEndian(babylonHeight)) + if len(btcHeightBytes) == 0 { + return 0, types.ErrBTCHeightNotFound + } + return sdk.BigEndianToUint64(btcHeightBytes), nil +} + +func (k Keeper) GetCurrentBTCHeight(ctx sdk.Context) (uint64, error) { + babylonHeight := uint64(ctx.BlockHeight()) + return k.GetBTCHeightAtBabylonHeight(ctx, babylonHeight) +} + +// btcHeightStore returns the KVStore of the BTC heights +// prefix: BTCHeightKey +// key: Babylon block height +// value: BTC block height +func (k Keeper) btcHeightStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.BTCHeightKey) +} diff --git a/x/btcstaking/keeper/btc_height_index_test.go b/x/btcstaking/keeper/btc_height_index_test.go new file mode 100644 index 000000000..1ed867f6a --- /dev/null +++ b/x/btcstaking/keeper/btc_height_index_test.go @@ -0,0 +1,43 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + keepertest "github.com/babylonchain/babylon/testutil/keeper" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzBTCHeightIndex(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, nil) + + // randomise Babylon height and BTC height + babylonHeight := datagen.RandomInt(r, 100) + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btcHeight := datagen.RandomInt(r, 100) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: btcHeight}).Times(1) + keeper.IndexBTCHeight(ctx) + + // assert BTC height + actualBtcHeight, err := keeper.GetBTCHeightAtBabylonHeight(ctx, babylonHeight) + require.NoError(t, err) + require.Equal(t, btcHeight, actualBtcHeight) + // assert current BTC height + curBtcHeight, err := keeper.GetCurrentBTCHeight(ctx) + require.NoError(t, err) + require.Equal(t, btcHeight, curBtcHeight) + }) +} diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 40b4ab47d..5ea78ca47 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -15,11 +15,11 @@ import ( func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // tip of Babylon and Bitcoin babylonTipHeight := uint64(ctx.BlockHeight()) - btcTip := k.btclcKeeper.GetTipInfo(ctx) - if btcTip == nil { + btcTipHeight, err := k.GetCurrentBTCHeight(ctx) + if err != nil { return } - btcTipHeight := btcTip.Height + // get value of w wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout // iterate all BTC validators diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index dbcb70730..13011787b 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -53,6 +53,7 @@ func FuzzVotingPowerTable(f *testing.F) { babylonHeight := datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) + keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) for _, btcVal := range btcVals { power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) @@ -67,6 +68,7 @@ func FuzzVotingPowerTable(f *testing.F) { babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) @@ -93,6 +95,7 @@ func FuzzVotingPowerTable(f *testing.F) { babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) + keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) for _, btcVal := range btcVals { power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 679f66f60..4a7a15d61 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -10,4 +10,5 @@ var ( ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1103, "the BTC staking protocol is not activated yet") + ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1104, "the BTC height is not found") ) diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 40ca35a80..90c9dc217 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -19,6 +19,7 @@ var ( BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators BTCDelegationKey = []byte{0x03} // key prefix for the BTC delegations VotingPowerKey = []byte{0x04} // key prefix for the voting power + BTCHeightKey = []byte{0x05} // key prefix for the BTC heights ) func KeyPrefix(p string) []byte { From ae95881ab3abfbf9270e0467a93d2e8b84b892b5 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 11 Jul 2023 21:45:51 +1000 Subject: [PATCH 027/202] e2e: basic BTC staking e2e (#20) --- .circleci/config.yml | 15 ++ test/e2e/btc_staking_e2e_test.go | 53 ++++++++ test/e2e/btc_timestamping_e2e_test.go | 189 ++++++++++++++++++++++++++ test/e2e/configurer/factory.go | 21 ++- test/e2e/e2e_setup_test.go | 49 ------- test/e2e/e2e_test.go | 149 ++------------------ 6 files changed, 284 insertions(+), 192 deletions(-) create mode 100644 test/e2e/btc_staking_e2e_test.go create mode 100644 test/e2e/btc_timestamping_e2e_test.go delete mode 100644 test/e2e/e2e_setup_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 758aaa2d9..56b2cf7ea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,6 +33,18 @@ jobs: name: Run tests command: | make test + e2e-test: + machine: + image: ubuntu-2204:2022.10.1 + resource_class: large + steps: + - go/install: + version: "1.20" + - checkout + - run: + name: Print Go environment + command: "go env" + - add_ssh_keys - run: name: Run e2e tests command: | @@ -106,6 +118,9 @@ workflows: build-test: jobs: - build-test + e2e-test: + jobs: + - e2e-test lint: jobs: - lint diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go new file mode 100644 index 000000000..58bee6729 --- /dev/null +++ b/test/e2e/btc_staking_e2e_test.go @@ -0,0 +1,53 @@ +package e2e + +import ( + "math/rand" + "time" + + "github.com/babylonchain/babylon/test/e2e/configurer" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/stretchr/testify/suite" +) + +type BTCStakingTestSuite struct { + suite.Suite + + configurer configurer.Configurer +} + +func (s *BTCStakingTestSuite) SetupSuite() { + s.T().Log("setting up e2e integration test suite...") + var err error + + // The e2e test flow is as follows: + // + // 1. Configure 1 chain with some validator nodes + // 2. Execute various e2e tests + s.configurer, err = configurer.NewBTCStakingConfigurer(s.T(), true) + s.NoError(err) + err = s.configurer.ConfigureChains() + s.NoError(err) + err = s.configurer.RunSetup() + s.NoError(err) +} + +func (s *BTCStakingTestSuite) TearDownSuite() { + err := s.configurer.ClearResources() + s.Require().NoError(err) +} + +func (s *BTCStakingTestSuite) TestCreateBTCValidator() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // generate a random BTC validator + r := rand.New(rand.NewSource(time.Now().Unix())) + btcVal, err := datagen.GenRandomBTCValidator(r) + s.NoError(err) + // create a BTC validator + nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop) + + // TODO: query the existence of BTC validator. need RPC query here +} diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go new file mode 100644 index 000000000..c99881e1f --- /dev/null +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -0,0 +1,189 @@ +package e2e + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "math/rand" + "strconv" + "time" + + "github.com/babylonchain/babylon/test/e2e/configurer" + "github.com/babylonchain/babylon/test/e2e/initialization" + bbn "github.com/babylonchain/babylon/types" + ct "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type BTCTimestampingTestSuite struct { + suite.Suite + + configurer configurer.Configurer +} + +func (s *BTCTimestampingTestSuite) SetupSuite() { + s.T().Log("setting up e2e integration test suite...") + var ( + err error + ) + + // The e2e test flow is as follows: + // + // 1. Configure two chains - chan A and chain B. + // * For each chain, set up several validator nodes + // * Initialize configs and genesis for all them. + // 2. Start both networks. + // 3. Run IBC relayer betweeen the two chains. + // 4. Execute various e2e tests, including IBC + s.configurer, err = configurer.NewBTCTimestampingConfigurer(s.T(), true) + + s.Require().NoError(err) + + err = s.configurer.ConfigureChains() + s.Require().NoError(err) + + err = s.configurer.RunSetup() + s.Require().NoError(err) +} + +func (s *BTCTimestampingTestSuite) TearDownSuite() { + err := s.configurer.ClearResources() + s.Require().NoError(err) +} + +// Most simple test, just checking that two chains are up and connected through +// ibc +func (s *BTCTimestampingTestSuite) TestConnectIbc() { + chainA := s.configurer.GetChainConfig(0) + chainB := s.configurer.GetChainConfig(1) + _, err := chainA.GetDefaultNode() + s.NoError(err) + _, err = chainB.GetDefaultNode() + s.NoError(err) +} + +func (s *BTCTimestampingTestSuite) TestBTCBaseHeader() { + hardcodedHeader, _ := bbn.NewBTCHeaderBytesFromHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000") + hardcodedHeaderHeight := uint64(0) + + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + baseHeader, err := nonValidatorNode.QueryBtcBaseHeader() + s.NoError(err) + s.True(baseHeader.Hash.Eq(hardcodedHeader.Hash())) + s.Equal(hardcodedHeaderHeight, baseHeader.Height) +} + +func (s *BTCTimestampingTestSuite) TestSendTx() { + r := rand.New(rand.NewSource(time.Now().Unix())) + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + tip1, err := nonValidatorNode.QueryTip() + s.NoError(err) + + nonValidatorNode.InsertNewEmptyBtcHeader(r) + + tip2, err := nonValidatorNode.QueryTip() + s.NoError(err) + + s.Equal(tip1.Height+1, tip2.Height) +} + +func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(35) + + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // Query checkpoint chain info for opposing chain + chainsInfo, err := nonValidatorNode.QueryChainsInfo([]string{initialization.ChainBID}) + s.NoError(err) + s.Equal(chainsInfo[0].ChainId, initialization.ChainBID) + + // Finalize epoch 1,2,3 , as first headers of opposing chain are in epoch 3 + var ( + startEpochNum uint64 = 1 + endEpochNum uint64 = 3 + ) + + nonValidatorNode.FinalizeSealedEpochs(startEpochNum, endEpochNum) + + endEpoch, err := nonValidatorNode.QueryRawCheckpoint(endEpochNum) + s.NoError(err) + s.Equal(endEpoch.Status, ct.Finalized) + + // Check we have epoch info for opposing chain and some basic assertions + epochChainsInfo, err := nonValidatorNode.QueryEpochChainsInfo(endEpochNum, []string{initialization.ChainBID}) + s.NoError(err) + s.Equal(epochChainsInfo[0].ChainId, initialization.ChainBID) + s.Equal(epochChainsInfo[0].LatestHeader.BabylonEpoch, endEpochNum) + + // Check we have finalized epoch info for opposing chain and some basic assertions + finalizedChainsInfo, err := nonValidatorNode.QueryFinalizedChainsInfo([]string{initialization.ChainBID}) + s.NoError(err) + + // TODO Add more assertion here. Maybe check proofs ? + s.Equal(finalizedChainsInfo[0].FinalizedChainInfo.ChainId, initialization.ChainBID) + s.Equal(finalizedChainsInfo[0].EpochInfo.EpochNumber, endEpochNum) + + currEpoch, err := nonValidatorNode.QueryCurrentEpoch() + s.NoError(err) + + heightAtEndedEpoch, err := nonValidatorNode.QueryLightClientHeightEpochEnd(currEpoch - 1) + s.NoError(err) + + if heightAtEndedEpoch == 0 { + // we can only assert, that btc lc height is larger than 0. + s.FailNow(fmt.Sprintf("Light client height should be > 0 on epoch %d", currEpoch-1)) + } + + chainB := s.configurer.GetChainConfig(1) + _, err = chainB.GetDefaultNode() + s.NoError(err) +} + +func (s *BTCTimestampingTestSuite) TestWasm() { + contractPath := "/bytecode/storage_contract.wasm" + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + require.NoError(s.T(), err) + nonValidatorNode.StoreWasmCode(contractPath, initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + latestWasmId := int(nonValidatorNode.QueryLatestWasmCodeID()) + nonValidatorNode.InstantiateWasmContract( + strconv.Itoa(latestWasmId), + `{}`, + initialization.ValidatorWalletName, + ) + nonValidatorNode.WaitForNextBlock() + contracts, err := nonValidatorNode.QueryContractsFromId(1) + s.NoError(err) + s.Require().Len(contracts, 1, "Wrong number of contracts for the counter") + contractAddr := contracts[0] + + data := []byte{1, 2, 3, 4, 5} + dataHex := hex.EncodeToString(data) + dataHash := sha256.Sum256(data) + dataHashHex := hex.EncodeToString(dataHash[:]) + + storeMsg := fmt.Sprintf(`{"save_data":{"data":"%s"}}`, dataHex) + nonValidatorNode.WasmExecute(contractAddr, storeMsg, initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + queryMsg := fmt.Sprintf(`{"check_data": {"data_hash":"%s"}}`, dataHashHex) + queryResult, err := nonValidatorNode.QueryWasmSmartObject(contractAddr, queryMsg) + require.NoError(s.T(), err) + finalized := queryResult["finalized"].(bool) + latestFinalizedEpoch := int(queryResult["latest_finalized_epoch"].(float64)) + saveEpoch := int(queryResult["save_epoch"].(float64)) + + require.False(s.T(), finalized) + // in previous test we already finalized epoch 3 + require.Equal(s.T(), 3, latestFinalizedEpoch) + // data is not finalized yet, so save epoch should be strictly greater than latest finalized epoch + require.Greater(s.T(), saveEpoch, latestFinalizedEpoch) +} diff --git a/test/e2e/configurer/factory.go b/test/e2e/configurer/factory.go index 8a587d1b4..915440165 100644 --- a/test/e2e/configurer/factory.go +++ b/test/e2e/configurer/factory.go @@ -91,10 +91,10 @@ var ( } ) -// New returns a new Configurer. +// New returns a new Configurer for BTC timestamping service. // TODO currently only one configuration is available. Consider testing upgrades // when necessary -func New(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { +func NewBTCTimestampingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { containerManager, err := containers.NewManager(isDebugLogEnabled) if err != nil { return nil, err @@ -109,3 +109,20 @@ func New(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { containerManager, ), nil } + +// New returns a new Configurer for BTC staking service +func NewBTCStakingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { + containerManager, err := containers.NewManager(isDebugLogEnabled) + if err != nil { + return nil, err + } + + return NewCurrentBranchConfigurer(t, + []*chain.Config{ + // we only need 1 chain for testing BTC staking + chain.New(t, containerManager, initialization.ChainAID, validatorConfigsChainA), + }, + baseSetup, // base set up + containerManager, + ), nil +} diff --git a/test/e2e/e2e_setup_test.go b/test/e2e/e2e_setup_test.go deleted file mode 100644 index 3a0113f69..000000000 --- a/test/e2e/e2e_setup_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package e2e - -import ( - "testing" - - "github.com/stretchr/testify/suite" - - configurer "github.com/babylonchain/babylon/test/e2e/configurer" -) - -type IntegrationTestSuite struct { - suite.Suite - - configurer configurer.Configurer -} - -func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} - -func (s *IntegrationTestSuite) SetupSuite() { - s.T().Log("setting up e2e integration test suite...") - var ( - err error - ) - - // The e2e test flow is as follows: - // - // 1. Configure two chains - chan A and chain B. - // * For each chain, set up several validator nodes - // * Initialize configs and genesis for all them. - // 2. Start both networks. - // 3. Run IBC relayer betweeen the two chains. - // 4. Execute various e2e tests, including IBC - s.configurer, err = configurer.New(s.T(), true) - - s.Require().NoError(err) - - err = s.configurer.ConfigureChains() - s.Require().NoError(err) - - err = s.configurer.RunSetup() - s.Require().NoError(err) -} - -func (s *IntegrationTestSuite) TearDownSuite() { - err := s.configurer.ClearResources() - s.Require().NoError(err) -} diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 1da2811d9..3ef7d8956 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -4,150 +4,17 @@ package e2e import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "math/rand" - "strconv" - "time" + "testing" - "github.com/babylonchain/babylon/test/e2e/initialization" - bbn "github.com/babylonchain/babylon/types" - ct "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) -// Most simple test, just checking that two chains are up and connected through -// ibc -func (s *IntegrationTestSuite) TestConnectIbc() { - chainA := s.configurer.GetChainConfig(0) - chainB := s.configurer.GetChainConfig(1) - _, err := chainA.GetDefaultNode() - s.NoError(err) - _, err = chainB.GetDefaultNode() - s.NoError(err) +// TestBTCTimestampingTestSuite tests BTC timestamping protocol end-to-end +func TestBTCTimestampingTestSuite(t *testing.T) { + suite.Run(t, new(BTCTimestampingTestSuite)) } -func (s *IntegrationTestSuite) TestBTCBaseHeader() { - hardcodedHeader, _ := bbn.NewBTCHeaderBytesFromHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000") - hardcodedHeaderHeight := uint64(0) - - chainA := s.configurer.GetChainConfig(0) - nonValidatorNode, err := chainA.GetNodeAtIndex(2) - s.NoError(err) - baseHeader, err := nonValidatorNode.QueryBtcBaseHeader() - s.True(baseHeader.Hash.Eq(hardcodedHeader.Hash())) - s.Equal(hardcodedHeaderHeight, baseHeader.Height) -} - -func (s *IntegrationTestSuite) TestSendTx() { - r := rand.New(rand.NewSource(time.Now().Unix())) - chainA := s.configurer.GetChainConfig(0) - nonValidatorNode, err := chainA.GetNodeAtIndex(2) - s.NoError(err) - - tip1, err := nonValidatorNode.QueryTip() - s.NoError(err) - - nonValidatorNode.InsertNewEmptyBtcHeader(r) - - tip2, err := nonValidatorNode.QueryTip() - s.NoError(err) - - s.Equal(tip1.Height+1, tip2.Height) -} - -func (s *IntegrationTestSuite) TestIbcCheckpointing() { - chainA := s.configurer.GetChainConfig(0) - chainA.WaitUntilHeight(35) - - nonValidatorNode, err := chainA.GetNodeAtIndex(2) - s.NoError(err) - - // Query checkpoint chain info for opposing chain - chainsInfo, err := nonValidatorNode.QueryChainsInfo([]string{initialization.ChainBID}) - s.NoError(err) - s.Equal(chainsInfo[0].ChainId, initialization.ChainBID) - - // Finalize epoch 1,2,3 , as first headers of opposing chain are in epoch 3 - var ( - startEpochNum uint64 = 1 - endEpochNum uint64 = 3 - ) - - nonValidatorNode.FinalizeSealedEpochs(startEpochNum, endEpochNum) - - endEpoch, err := nonValidatorNode.QueryRawCheckpoint(endEpochNum) - s.NoError(err) - s.Equal(endEpoch.Status, ct.Finalized) - - // Check we have epoch info for opposing chain and some basic assertions - epochChainsInfo, err := nonValidatorNode.QueryEpochChainsInfo(endEpochNum, []string{initialization.ChainBID}) - s.NoError(err) - s.Equal(epochChainsInfo[0].ChainId, initialization.ChainBID) - s.Equal(epochChainsInfo[0].LatestHeader.BabylonEpoch, endEpochNum) - - // Check we have finalized epoch info for opposing chain and some basic assertions - finalizedChainsInfo, err := nonValidatorNode.QueryFinalizedChainsInfo([]string{initialization.ChainBID}) - s.NoError(err) - - // TODO Add more assertion here. Maybe check proofs ? - s.Equal(finalizedChainsInfo[0].FinalizedChainInfo.ChainId, initialization.ChainBID) - s.Equal(finalizedChainsInfo[0].EpochInfo.EpochNumber, endEpochNum) - - currEpoch, err := nonValidatorNode.QueryCurrentEpoch() - s.NoError(err) - - heightAtEndedEpoch, err := nonValidatorNode.QueryLightClientHeightEpochEnd(currEpoch - 1) - s.NoError(err) - - if heightAtEndedEpoch == 0 { - // we can only assert, that btc lc height is larger than 0. - s.FailNow(fmt.Sprintf("Light client height should be > 0 on epoch %d", currEpoch-1)) - } - - chainB := s.configurer.GetChainConfig(1) - _, err = chainB.GetDefaultNode() - s.NoError(err) -} - -func (s *IntegrationTestSuite) TestWasm() { - contractPath := "/bytecode/storage_contract.wasm" - chainA := s.configurer.GetChainConfig(0) - nonValidatorNode, err := chainA.GetNodeAtIndex(2) - require.NoError(s.T(), err) - nonValidatorNode.StoreWasmCode(contractPath, initialization.ValidatorWalletName) - nonValidatorNode.WaitForNextBlock() - latestWasmId := int(nonValidatorNode.QueryLatestWasmCodeID()) - nonValidatorNode.InstantiateWasmContract( - strconv.Itoa(latestWasmId), - `{}`, - initialization.ValidatorWalletName, - ) - nonValidatorNode.WaitForNextBlock() - contracts, err := nonValidatorNode.QueryContractsFromId(1) - s.NoError(err) - s.Require().Len(contracts, 1, "Wrong number of contracts for the counter") - contractAddr := contracts[0] - - data := []byte{1, 2, 3, 4, 5} - dataHex := hex.EncodeToString(data) - dataHash := sha256.Sum256(data) - dataHashHex := hex.EncodeToString(dataHash[:]) - - storeMsg := fmt.Sprintf(`{"save_data":{"data":"%s"}}`, dataHex) - nonValidatorNode.WasmExecute(contractAddr, storeMsg, initialization.ValidatorWalletName) - nonValidatorNode.WaitForNextBlock() - queryMsg := fmt.Sprintf(`{"check_data": {"data_hash":"%s"}}`, dataHashHex) - queryResult, err := nonValidatorNode.QueryWasmSmartObject(contractAddr, queryMsg) - require.NoError(s.T(), err) - finalized := queryResult["finalized"].(bool) - latestFinalizedEpoch := int(queryResult["latest_finalized_epoch"].(float64)) - saveEpoch := int(queryResult["save_epoch"].(float64)) - - require.False(s.T(), finalized) - // in previous test we already finalized epoch 3 - require.Equal(s.T(), 3, latestFinalizedEpoch) - // data is not finalized yet, so save epoch should be strictly greater than latest finalized epoch - require.Greater(s.T(), saveEpoch, latestFinalizedEpoch) +// TestBTCStakingTestSuite tests BTC staking protocol end-to-end +func TestBTCStakingTestSuite(t *testing.T) { + suite.Run(t, new(BTCStakingTestSuite)) } From 317e5e7bd8f38ab8cec45b2f9c6752e481c47799 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Wed, 12 Jul 2023 20:23:19 -0400 Subject: [PATCH 028/202] feat: rpc queries for btcstaking and finality modules (#22) * proto files * list btc vals * btc vals query * rename rpc * query methods * more rpc * fix proto * fix proto * fix proto * fix lint * add pagination * fix * fix imports * fix err * fix str * Update proto/babylon/btcstaking/v1/query.proto Co-authored-by: Runchao Han * fix pr comments * rename * fix lint * fix bytes in req * fix del start/end ht * remove sentinel err * fix str --------- Co-authored-by: Runchao Han --- proto/babylon/btcstaking/v1/btcstaking.proto | 34 +- proto/babylon/btcstaking/v1/query.proto | 81 +- proto/babylon/finality/v1/query.proto | 22 +- types/errors.go | 5 + x/btcstaking/keeper/grpc_query.go | 118 ++ x/btcstaking/keeper/voting_power_table.go | 3 +- x/btcstaking/types/btcstaking.pb.go | 615 ++++++- x/btcstaking/types/query.pb.go | 1610 +++++++++++++++++- x/btcstaking/types/query.pb.gw.go | 321 ++++ x/finality/keeper/grpc_query.go | 39 + x/finality/keeper/votes.go | 2 +- x/finality/types/errors.go | 2 +- x/finality/types/query.pb.go | 401 ++++- x/finality/types/query.pb.gw.go | 101 ++ 14 files changed, 3269 insertions(+), 85 deletions(-) create mode 100644 types/errors.go create mode 100644 x/btcstaking/keeper/grpc_query.go create mode 100644 x/finality/keeper/grpc_query.go diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f9d17551e..f3880b523 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -17,6 +17,17 @@ message BTCValidator { ProofOfPossession pop = 3; } +// BTCValidatorWithMeta wraps the BTCValidator with meta data. +message BTCValidatorWithMeta { + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // height is the queried Babylon height + uint64 height = 2; + // voting_power is the voting power of this BTC validator at the given height + uint64 voting_power = 3; +} + // BTCDelegation defines a BTC delegation message BTCDelegation { // babylon_pk is the Babylon secp256k1 PK of this BTC delegation @@ -39,14 +50,14 @@ message BTCDelegation { // total_sat is the total amount of BTC stakes in this delegation // quantified in satoshi uint64 total_sat = 7; - // staking_tx is the staking tx + // staking_tx is the staking tx StakingTx staking_tx = 8; // slashing_tx is the slashing tx - // It is partially signed by SK corresponding to btc_pk, but not signed by + // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx - // by the delegator (i.e., SK corresponding to btc_pk). + // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // jury_sig is the signature signature on the slashing tx @@ -55,6 +66,21 @@ message BTCDelegation { bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } +// BTCDelegationWithMeta wraps the BTCDelegation with meta data. +message BTCDelegationWithMeta { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + uint64 start_height = 2; + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + uint64 end_height = 3; + // voting_power is the voting power of this BTC delegation at the given height + uint64 voting_power = 4; +} + // ProofOfPossession is the proof of possession that a Babylon secp256k1 // secret key and a Bitcoin secp256k1 secret key are held by the same // person @@ -72,4 +98,4 @@ message StakingTx { bytes tx = 1; // staking_script is the script for the staking tx's output bytes staking_script = 2; -} \ No newline at end of file +} diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 1024a068f..a22eee54d 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -3,7 +3,9 @@ package babylon.btcstaking.v1; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; import "babylon/btcstaking/v1/params.proto"; +import "babylon/btcstaking/v1/btcstaking.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; @@ -13,6 +15,22 @@ service Query { rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/params"; } + + // BTCValidators queries all btc validators + rpc BTCValidators(QueryBTCValidatorsRequest) returns (QueryBTCValidatorsResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators"; + } + + // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + rpc BTCValidatorsAtHeight(QueryBTCValidatorsAtHeightRequest) returns (QueryBTCValidatorsAtHeightResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{height}"; + } + + // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. + // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. + rpc BTCValidatorDelegationsAtHeight(QueryBTCValidatorDelegationsAtHeightRequest) returns (QueryBTCValidatorDelegationsAtHeightResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/delegations/{height}"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -22,4 +40,65 @@ message QueryParamsRequest {} message QueryParamsResponse { // params holds all the parameters of this module. Params params = 1 [(gogoproto.nullable) = false]; -} \ No newline at end of file +} + +// QueryBTCValidatorsRequest is the request type for the +// Query/BTCValidators RPC method. +message QueryBTCValidatorsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryBTCValidatorsResponse is the response type for the +// Query/BTCValidators RPC method. +message QueryBTCValidatorsResponse { + // btc_validators contains all the btc validators + repeated BTCValidator btc_validators = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryBTCValidatorsAtHeightRequest is the request type for the +// Query/BTCValidatorsAtHeight RPC method. +message QueryBTCValidatorsAtHeightRequest { + // height defines at which Babylon height to query the btc validators info. + uint64 height = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryBTCValidatorsAtHeightResponse is the response type for the +// Query/BTCValidatorsAtHeight RPC method. +message QueryBTCValidatorsAtHeightResponse { + // btc_validators contains all the queried btc validators. + repeated BTCValidatorWithMeta btc_validators = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryBTCValidatorDelegationsAtHeightRequest is the request type for the +// Query/BTCValidatorDelegationsAtHeight RPC method. +message QueryBTCValidatorDelegationsAtHeightRequest { + // btc_pk is the Bitcoin secp256k1 PK of this BTC Validator + // the PK follows encoding in BIP-340 spec + bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + + // height defines at which Babylon height to query the btc delegations info. + uint64 height = 2; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 3; +} + +// QueryBTCValidatorDelegationsAtHeightResponse is the response type for the +// Query/BTCValidatorDelegationsAtHeight RPC method. +message QueryBTCValidatorDelegationsAtHeightResponse { + // btc_validators contains all the queried btc delegations. + repeated BTCDelegationWithMeta btc_delegations = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 7c601cf6e..a629ff151 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -13,6 +13,11 @@ service Query { rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/babylonchain/babylon/finality/v1/params"; } + + // VotesAtHeight queries btc validators who have signed the block at given height. + rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { + option (google.api.http).get = "/babylonchain/babylon/finality/v1/votes/{height}"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -22,4 +27,19 @@ message QueryParamsRequest {} message QueryParamsResponse { // params holds all the parameters of this module. Params params = 1 [(gogoproto.nullable) = false]; -} \ No newline at end of file +} + +// QueryVotesAtHeightRequest is the request type for the +// Query/VotesAtHeight RPC method. +message QueryVotesAtHeightRequest { + // height defines at which height to query the btc validators. + uint64 height = 1; +} + +// QueryVotesAtHeightResponse is the response type for the +// Query/VotesAtHeight RPC method. +message QueryVotesAtHeightResponse { + // btc_pk is the Bitcoin secp256k1 PK of BTC validators who have signed the block at given height. + // the PK follows encoding in BIP-340 spec + repeated bytes btc_pks = 1 [(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey"]; +} diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 000000000..4ff369a35 --- /dev/null +++ b/types/errors.go @@ -0,0 +1,5 @@ +package types + +import "errors" + +var ErrUnmarshal = errors.New("unmarshal error") diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go new file mode 100644 index 000000000..f013746b6 --- /dev/null +++ b/x/btcstaking/keeper/grpc_query.go @@ -0,0 +1,118 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/babylonchain/babylon/x/btcstaking/types" +) + +var _ types.QueryServer = Keeper{} + +func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidatorsRequest) (*types.QueryBTCValidatorsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := k.btcValidatorStore(sdkCtx) + + var btcValidators []*types.BTCValidator + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + var btcValidator types.BTCValidator + k.cdc.MustUnmarshal(value, &btcValidator) + btcValidators = append(btcValidators, &btcValidator) + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil +} + +func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCValidatorsAtHeightRequest) (*types.QueryBTCValidatorsAtHeightResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := k.votingPowerStore(sdkCtx, req.Height) + + var btcValidatorsWithMeta []*types.BTCValidatorWithMeta + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + btcValidator, err := k.GetBTCValidator(sdkCtx, key) + if err != nil { + return err + } + + votingPower := k.GetVotingPower(sdkCtx, key, req.Height) + if votingPower > 0 { + btcValidatorWithMeta := types.BTCValidatorWithMeta{ + BtcPk: btcValidator.BtcPk, + Height: req.Height, + VotingPower: votingPower, + } + btcValidatorsWithMeta = append(btcValidatorsWithMeta, &btcValidatorWithMeta) + } + + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil +} + +func (k Keeper) BTCValidatorDelegationsAtHeight(ctx context.Context, req *types.QueryBTCValidatorDelegationsAtHeightRequest) (*types.QueryBTCValidatorDelegationsAtHeightResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + // convert pk to bytes + valPkBytes, err := req.ValBtcPk.Marshal() + if err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + btcHeight, err := k.GetBTCHeightAtBabylonHeight(sdkCtx, req.Height) + if err != nil { + return nil, err + } + + // get value of w + wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + store := k.btcDelegationStore(sdkCtx, valPkBytes) + + var btcDelsWithMeta []*types.BTCDelegationWithMeta + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + btcDel, err := k.GetBTCDelegation(sdkCtx, valPkBytes, key) + if err != nil { + return err + } + + delPower := btcDel.VotingPower(btcHeight, wValue) + if delPower > 0 { + btcDelMeta := types.BTCDelegationWithMeta{ + BtcPk: btcDel.BtcPk, + StartHeight: btcDel.StartHeight, + EndHeight: btcDel.EndHeight, + VotingPower: delPower, + } + btcDelsWithMeta = append(btcDelsWithMeta, &btcDelMeta) + } + + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryBTCValidatorDelegationsAtHeightResponse{BtcDelegations: btcDelsWithMeta, Pagination: pageRes}, nil +} diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 5ea78ca47..7d8739531 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" @@ -81,7 +80,7 @@ func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]u valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) if err != nil { // failing to unmarshal validator BTC PK in KVStore is a programming error - panic(fmt.Errorf("failed to unmarshal validator BTC PK: %w", err)) + panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } valSet[valBTCPK.ToHexStr()] = sdk.BigEndianToUint64(iter.Value()) } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 8793f3608..c38633898 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -83,6 +83,64 @@ func (m *BTCValidator) GetPop() *ProofOfPossession { return nil } +// BTCValidatorWithMeta wraps the BTCValidator with meta data. +type BTCValidatorWithMeta struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // height is the queried Babylon height + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + // voting_power is the voting power of this BTC validator at the given height + VotingPower uint64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +} + +func (m *BTCValidatorWithMeta) Reset() { *m = BTCValidatorWithMeta{} } +func (m *BTCValidatorWithMeta) String() string { return proto.CompactTextString(m) } +func (*BTCValidatorWithMeta) ProtoMessage() {} +func (*BTCValidatorWithMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{1} +} +func (m *BTCValidatorWithMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCValidatorWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCValidatorWithMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCValidatorWithMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCValidatorWithMeta.Merge(m, src) +} +func (m *BTCValidatorWithMeta) XXX_Size() int { + return m.Size() +} +func (m *BTCValidatorWithMeta) XXX_DiscardUnknown() { + xxx_messageInfo_BTCValidatorWithMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCValidatorWithMeta proto.InternalMessageInfo + +func (m *BTCValidatorWithMeta) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *BTCValidatorWithMeta) GetVotingPower() uint64 { + if m != nil { + return m.VotingPower + } + return 0 +} + // BTCDelegation defines a BTC delegation type BTCDelegation struct { // babylon_pk is the Babylon secp256k1 PK of this BTC delegation @@ -125,7 +183,7 @@ func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } func (m *BTCDelegation) String() string { return proto.CompactTextString(m) } func (*BTCDelegation) ProtoMessage() {} func (*BTCDelegation) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{1} + return fileDescriptor_3851ae95ccfaf7db, []int{2} } func (m *BTCDelegation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -196,6 +254,75 @@ func (m *BTCDelegation) GetStakingTx() *StakingTx { return nil } +// BTCDelegationWithMeta wraps the BTCDelegation with meta data. +type BTCDelegationWithMeta struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + StartHeight uint64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + EndHeight uint64 `protobuf:"varint,3,opt,name=end_height,json=endHeight,proto3" json:"end_height,omitempty"` + // voting_power is the voting power of this BTC delegation at the given height + VotingPower uint64 `protobuf:"varint,4,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +} + +func (m *BTCDelegationWithMeta) Reset() { *m = BTCDelegationWithMeta{} } +func (m *BTCDelegationWithMeta) String() string { return proto.CompactTextString(m) } +func (*BTCDelegationWithMeta) ProtoMessage() {} +func (*BTCDelegationWithMeta) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{3} +} +func (m *BTCDelegationWithMeta) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCDelegationWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCDelegationWithMeta.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCDelegationWithMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegationWithMeta.Merge(m, src) +} +func (m *BTCDelegationWithMeta) XXX_Size() int { + return m.Size() +} +func (m *BTCDelegationWithMeta) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegationWithMeta.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCDelegationWithMeta proto.InternalMessageInfo + +func (m *BTCDelegationWithMeta) GetStartHeight() uint64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +func (m *BTCDelegationWithMeta) GetEndHeight() uint64 { + if m != nil { + return m.EndHeight + } + return 0 +} + +func (m *BTCDelegationWithMeta) GetVotingPower() uint64 { + if m != nil { + return m.VotingPower + } + return 0 +} + // ProofOfPossession is the proof of possession that a Babylon secp256k1 // secret key and a Bitcoin secp256k1 secret key are held by the same // person @@ -211,7 +338,7 @@ func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } func (*ProofOfPossession) ProtoMessage() {} func (*ProofOfPossession) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{2} + return fileDescriptor_3851ae95ccfaf7db, []int{4} } func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -259,7 +386,7 @@ func (m *StakingTx) Reset() { *m = StakingTx{} } func (m *StakingTx) String() string { return proto.CompactTextString(m) } func (*StakingTx) ProtoMessage() {} func (*StakingTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{3} + return fileDescriptor_3851ae95ccfaf7db, []int{5} } func (m *StakingTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -304,7 +431,9 @@ func (m *StakingTx) GetStakingScript() []byte { func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") + proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") + proto.RegisterType((*BTCDelegationWithMeta)(nil), "babylon.btcstaking.v1.BTCDelegationWithMeta") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") } @@ -314,43 +443,48 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 575 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xae, 0xd3, 0xbf, 0x64, 0x92, 0x56, 0xea, 0x0a, 0xa4, 0xa8, 0x08, 0x27, 0x44, 0x02, 0xe5, - 0x64, 0xd3, 0x94, 0x56, 0x82, 0x03, 0x48, 0x2e, 0x07, 0x10, 0x20, 0x2c, 0xdb, 0xe2, 0xc0, 0x01, - 0x6b, 0xed, 0xb8, 0xce, 0x62, 0xd7, 0x6b, 0x79, 0x37, 0x91, 0xfd, 0x06, 0x1c, 0x79, 0x1e, 0x9e, - 0x80, 0x63, 0x8f, 0xd0, 0x43, 0x85, 0xda, 0x17, 0x41, 0xbb, 0x5e, 0x47, 0x95, 0x00, 0x21, 0x94, - 0x1b, 0xb7, 0xf1, 0xcc, 0x37, 0xdf, 0x7c, 0xe3, 0xf9, 0xb4, 0xf0, 0x20, 0xc0, 0x41, 0x95, 0xd2, - 0xcc, 0x0c, 0x78, 0xc8, 0x38, 0x4e, 0x48, 0x16, 0x9b, 0x8b, 0x83, 0x1b, 0x5f, 0x46, 0x5e, 0x50, - 0x4e, 0xd1, 0x6d, 0x85, 0x33, 0x6e, 0x54, 0x16, 0x07, 0xfb, 0xb7, 0x62, 0x1a, 0x53, 0x89, 0x30, - 0x45, 0x54, 0x83, 0xf7, 0x47, 0x21, 0x65, 0x67, 0x94, 0x99, 0x61, 0x51, 0xe5, 0x9c, 0x9a, 0x2c, - 0x0a, 0xf3, 0xc9, 0xd1, 0x71, 0x72, 0x60, 0x26, 0x51, 0xc5, 0x6a, 0xcc, 0xe8, 0xbb, 0x06, 0x3d, - 0xcb, 0x3b, 0x79, 0x87, 0x53, 0x32, 0xc5, 0x9c, 0x16, 0xe8, 0x29, 0x80, 0x9a, 0xe1, 0xe7, 0x49, - 0x5f, 0x1b, 0x6a, 0xe3, 0xee, 0x64, 0x60, 0xd4, 0x4c, 0x46, 0xcd, 0x64, 0x2c, 0x99, 0x0c, 0x7b, - 0x1e, 0xbc, 0x8a, 0x2a, 0xa7, 0xa3, 0x5a, 0xec, 0x04, 0xbd, 0x81, 0xad, 0x80, 0x87, 0xa2, 0xb7, - 0x35, 0xd4, 0xc6, 0x3d, 0xeb, 0xf8, 0xe2, 0x72, 0x30, 0x89, 0x09, 0x9f, 0xcd, 0x03, 0x23, 0xa4, - 0x67, 0xa6, 0x42, 0x86, 0x33, 0x4c, 0xb2, 0xe6, 0xc3, 0xe4, 0x55, 0x1e, 0x31, 0xc3, 0x7a, 0x69, - 0x1f, 0x3e, 0x7a, 0xa8, 0x28, 0x37, 0x03, 0x1e, 0xda, 0x09, 0x7a, 0x02, 0xeb, 0x39, 0xcd, 0xfb, - 0xeb, 0x52, 0xc7, 0xd8, 0xf8, 0xed, 0xfa, 0x86, 0x5d, 0x50, 0x7a, 0xfa, 0xf6, 0xd4, 0xa6, 0x8c, - 0x45, 0x8c, 0x11, 0x9a, 0x39, 0xa2, 0x69, 0xf4, 0x65, 0x13, 0x76, 0x2c, 0xef, 0xe4, 0x79, 0x94, - 0x46, 0x31, 0xe6, 0x84, 0x66, 0xff, 0xd1, 0x72, 0xc8, 0x03, 0x58, 0xe0, 0xd4, 0x57, 0x72, 0x36, - 0x56, 0x92, 0xd3, 0x5e, 0xe0, 0xd4, 0x92, 0x8a, 0xee, 0x41, 0x8f, 0x71, 0x5c, 0x70, 0x7f, 0x16, - 0x91, 0x78, 0xc6, 0xfb, 0x9b, 0x43, 0x6d, 0xbc, 0xe1, 0x74, 0x65, 0xee, 0x85, 0x4c, 0xa1, 0xbb, - 0x00, 0x51, 0x36, 0x6d, 0x00, 0x5b, 0x12, 0xd0, 0x89, 0xb2, 0xa9, 0x2a, 0xdf, 0x81, 0x0e, 0xa7, - 0x1c, 0xa7, 0x3e, 0xc3, 0xbc, 0xbf, 0x2d, 0xab, 0x6d, 0x99, 0x70, 0x31, 0x47, 0xcf, 0x00, 0xd4, - 0x66, 0x3e, 0x2f, 0xfb, 0x6d, 0xb9, 0xf7, 0xf0, 0x0f, 0x7b, 0xbb, 0x75, 0xe8, 0x95, 0x4e, 0x87, - 0x35, 0x21, 0x9a, 0x40, 0x97, 0xa5, 0x98, 0xcd, 0x14, 0x43, 0x47, 0xae, 0xbd, 0x77, 0x71, 0x39, - 0x10, 0x87, 0x76, 0x55, 0xc5, 0x2b, 0x1d, 0x60, 0xcb, 0x18, 0x7d, 0x80, 0x9d, 0x69, 0x6d, 0x01, - 0x5a, 0xf8, 0x8c, 0xc4, 0x7d, 0x90, 0x5d, 0x8f, 0x2f, 0x2e, 0x07, 0x47, 0xff, 0xf2, 0xb3, 0x5c, - 0x12, 0x67, 0x98, 0xcf, 0x8b, 0xc8, 0xe9, 0x2d, 0xf9, 0x5c, 0x12, 0x23, 0x0f, 0xda, 0x1f, 0xe7, - 0x45, 0x25, 0xa9, 0xbb, 0xab, 0x52, 0x6f, 0x0b, 0x2a, 0x97, 0xc4, 0xa3, 0x4f, 0x1a, 0xec, 0xfd, - 0x72, 0x7a, 0x34, 0x80, 0x6e, 0x63, 0x60, 0x31, 0x4e, 0x38, 0xb8, 0xe7, 0x34, 0x9e, 0x16, 0x62, - 0x1c, 0xd8, 0x16, 0x96, 0x10, 0xc5, 0xd6, 0xaa, 0x5a, 0x84, 0xd7, 0x85, 0x14, 0x0b, 0x3a, 0xcb, - 0x63, 0xa0, 0x5d, 0x68, 0xf1, 0x52, 0x0d, 0x6e, 0xf1, 0x12, 0xdd, 0x87, 0xdd, 0xe6, 0xa4, 0x2c, - 0x2c, 0x48, 0xce, 0xeb, 0xb9, 0xce, 0x8e, 0xca, 0xba, 0x32, 0x69, 0xbd, 0xfe, 0x7a, 0xa5, 0x6b, - 0xe7, 0x57, 0xba, 0xf6, 0xe3, 0x4a, 0xd7, 0x3e, 0x5f, 0xeb, 0x6b, 0xe7, 0xd7, 0xfa, 0xda, 0xb7, - 0x6b, 0x7d, 0xed, 0xfd, 0x5f, 0x0d, 0x5b, 0xde, 0x7c, 0x14, 0xa5, 0xd2, 0x60, 0x4b, 0x3e, 0x5e, - 0x87, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x32, 0x65, 0xa2, 0x37, 0x05, 0x00, 0x00, + // 651 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xae, 0xd3, 0x34, 0x4d, 0x26, 0x69, 0xa5, 0xae, 0x5a, 0x14, 0x15, 0x91, 0xb4, 0x91, 0x40, + 0x3d, 0xd9, 0x34, 0xa5, 0x95, 0xe0, 0x00, 0x92, 0xcb, 0x01, 0x04, 0x15, 0x96, 0x1d, 0x81, 0xc4, + 0x01, 0x6b, 0xed, 0x6c, 0x9d, 0x25, 0xae, 0xd7, 0xf2, 0x6e, 0x42, 0xf2, 0x06, 0x1c, 0x79, 0x04, + 0x9e, 0x83, 0x27, 0x40, 0xe2, 0xd2, 0x23, 0xf4, 0x50, 0xa1, 0xf6, 0x45, 0xd0, 0xae, 0x37, 0x51, + 0x68, 0x41, 0x15, 0x2a, 0x5c, 0xb8, 0xcd, 0xce, 0xcf, 0xe7, 0xf9, 0xbe, 0x99, 0x91, 0xe1, 0x4e, + 0x80, 0x83, 0x71, 0xcc, 0x12, 0x2b, 0x10, 0x21, 0x17, 0xb8, 0x4f, 0x93, 0xc8, 0x1a, 0x6e, 0xcf, + 0xbc, 0xcc, 0x34, 0x63, 0x82, 0xa1, 0x35, 0x9d, 0x67, 0xce, 0x44, 0x86, 0xdb, 0xeb, 0xab, 0x11, + 0x8b, 0x98, 0xca, 0xb0, 0xa4, 0x95, 0x27, 0xaf, 0xb7, 0x42, 0xc6, 0x8f, 0x18, 0xb7, 0xc2, 0x6c, + 0x9c, 0x0a, 0x66, 0x71, 0x12, 0xa6, 0xed, 0xdd, 0xbd, 0xfe, 0xb6, 0xd5, 0x27, 0x63, 0x9e, 0xe7, + 0xb4, 0xbe, 0x19, 0x50, 0xb3, 0x3b, 0xfb, 0x2f, 0x71, 0x4c, 0xbb, 0x58, 0xb0, 0x0c, 0x3d, 0x04, + 0xd0, 0xdf, 0xf0, 0xd3, 0x7e, 0xdd, 0xd8, 0x30, 0xb6, 0xaa, 0xed, 0xa6, 0x99, 0x23, 0x99, 0x39, + 0x92, 0x39, 0x45, 0x32, 0x9d, 0x41, 0xf0, 0x8c, 0x8c, 0xdd, 0x8a, 0x2e, 0x71, 0xfa, 0xe8, 0x00, + 0x4a, 0x81, 0x08, 0x65, 0x6d, 0x61, 0xc3, 0xd8, 0xaa, 0xd9, 0x7b, 0x27, 0xa7, 0xcd, 0x76, 0x44, + 0x45, 0x6f, 0x10, 0x98, 0x21, 0x3b, 0xb2, 0x74, 0x66, 0xd8, 0xc3, 0x34, 0x99, 0x3c, 0x2c, 0x31, + 0x4e, 0x09, 0x37, 0xed, 0xa7, 0xce, 0xce, 0xbd, 0xbb, 0x1a, 0x72, 0x21, 0x10, 0xa1, 0xd3, 0x47, + 0x0f, 0x60, 0x3e, 0x65, 0x69, 0x7d, 0x5e, 0xf5, 0xb1, 0x65, 0xfe, 0x92, 0xbe, 0xe9, 0x64, 0x8c, + 0x1d, 0xbe, 0x38, 0x74, 0x18, 0xe7, 0x84, 0x73, 0xca, 0x12, 0x57, 0x16, 0xb5, 0x3e, 0x1a, 0xb0, + 0x3a, 0xcb, 0xed, 0x15, 0x15, 0xbd, 0x03, 0x22, 0xf0, 0x4c, 0x8f, 0xc6, 0xdf, 0xe8, 0xf1, 0x06, + 0x94, 0x7a, 0x84, 0x46, 0x3d, 0xa1, 0x28, 0x17, 0x5d, 0xfd, 0x42, 0x9b, 0x50, 0x1b, 0x32, 0x41, + 0x93, 0xc8, 0x4f, 0xd9, 0x3b, 0x92, 0x29, 0x12, 0x45, 0xb7, 0x9a, 0xfb, 0x1c, 0xe9, 0x6a, 0x7d, + 0x5a, 0x80, 0x25, 0xbb, 0xb3, 0xff, 0x98, 0xc4, 0x24, 0xc2, 0x82, 0xb2, 0xe4, 0x3f, 0xd2, 0x1f, + 0x75, 0x00, 0x86, 0x38, 0xf6, 0x75, 0x3b, 0xc5, 0x6b, 0xb5, 0x53, 0x1e, 0xe2, 0xd8, 0x56, 0x1d, + 0x6d, 0x42, 0x8d, 0x0b, 0x9c, 0x09, 0x5f, 0x6b, 0xbe, 0x90, 0xab, 0xaa, 0x7c, 0x4f, 0x72, 0xe1, + 0x6f, 0x01, 0x90, 0xa4, 0x3b, 0x49, 0x28, 0xa9, 0x84, 0x0a, 0x49, 0xba, 0x3a, 0x7c, 0x13, 0x2a, + 0x82, 0x09, 0x1c, 0xfb, 0x1c, 0x8b, 0xfa, 0xa2, 0x8a, 0x96, 0x95, 0xc3, 0xc3, 0x02, 0x3d, 0x02, + 0xd0, 0xcc, 0x7c, 0x31, 0xaa, 0x97, 0x15, 0xef, 0x8d, 0xdf, 0xf0, 0xf6, 0x72, 0xb3, 0x33, 0x72, + 0x2b, 0x7c, 0x62, 0xa2, 0x36, 0x54, 0x79, 0x8c, 0x79, 0x4f, 0x23, 0x54, 0x14, 0xed, 0x95, 0x93, + 0xd3, 0xa6, 0x1c, 0xb4, 0xa7, 0x23, 0x9d, 0x91, 0x0b, 0x7c, 0x6a, 0xa3, 0x37, 0xb0, 0xd4, 0xcd, + 0x57, 0x80, 0x65, 0x3e, 0xa7, 0x51, 0x1d, 0x54, 0xd5, 0xfd, 0x93, 0xd3, 0xe6, 0xee, 0x9f, 0x88, + 0xe5, 0xd1, 0x28, 0xc1, 0x62, 0x90, 0x11, 0xb7, 0x36, 0xc5, 0xf3, 0x68, 0x84, 0x3a, 0x50, 0x7e, + 0x3b, 0xc8, 0xc6, 0x0a, 0xba, 0x7a, 0x5d, 0xe8, 0x45, 0x09, 0xe5, 0xd1, 0xa8, 0xf5, 0xc5, 0x80, + 0xb5, 0x9f, 0x96, 0xf7, 0x5f, 0x1d, 0xd8, 0xc5, 0x91, 0x17, 0xae, 0x1a, 0xf9, 0xfc, 0xc5, 0x91, + 0x5f, 0x3c, 0xc5, 0xe2, 0xe5, 0x53, 0x7c, 0x6f, 0xc0, 0xca, 0xa5, 0x45, 0x46, 0x4d, 0xa8, 0x4e, + 0xce, 0x51, 0x8a, 0xa7, 0xe8, 0xb8, 0x93, 0x0b, 0x95, 0xd2, 0xba, 0xb0, 0x28, 0xa9, 0xca, 0x60, + 0xe1, 0xba, 0xca, 0x4a, 0xd1, 0xa4, 0xb0, 0x36, 0x54, 0xa6, 0xab, 0x85, 0x96, 0xa1, 0x20, 0x46, + 0xfa, 0xc3, 0x05, 0x31, 0x42, 0xb7, 0x61, 0x79, 0xb2, 0xa0, 0x3c, 0xcc, 0x68, 0x9a, 0xcb, 0x51, + 0x73, 0x97, 0xb4, 0xd7, 0x53, 0x4e, 0xfb, 0xf9, 0xe7, 0xb3, 0x86, 0x71, 0x7c, 0xd6, 0x30, 0xbe, + 0x9f, 0x35, 0x8c, 0x0f, 0xe7, 0x8d, 0xb9, 0xe3, 0xf3, 0xc6, 0xdc, 0xd7, 0xf3, 0xc6, 0xdc, 0xeb, + 0x2b, 0x07, 0x31, 0x9a, 0xfd, 0x0b, 0xa9, 0x4e, 0x83, 0x92, 0xfa, 0x5b, 0xec, 0xfc, 0x08, 0x00, + 0x00, 0xff, 0xff, 0x9b, 0xe6, 0xe3, 0x0b, 0xa8, 0x06, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -412,6 +546,51 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BTCValidatorWithMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCValidatorWithMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCValidatorWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.VotingPower != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x18 + } + if m.Height != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *BTCDelegation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -546,6 +725,56 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BTCDelegationWithMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCDelegationWithMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCDelegationWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.VotingPower != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x20 + } + if m.EndHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.EndHeight)) + i-- + dAtA[i] = 0x18 + } + if m.StartHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x10 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *ProofOfPossession) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -657,6 +886,25 @@ func (m *BTCValidator) Size() (n int) { return n } +func (m *BTCValidatorWithMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovBtcstaking(uint64(m.Height)) + } + if m.VotingPower != 0 { + n += 1 + sovBtcstaking(uint64(m.VotingPower)) + } + return n +} + func (m *BTCDelegation) Size() (n int) { if m == nil { return 0 @@ -707,6 +955,28 @@ func (m *BTCDelegation) Size() (n int) { return n } +func (m *BTCDelegationWithMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.StartHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.StartHeight)) + } + if m.EndHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.EndHeight)) + } + if m.VotingPower != 0 { + n += 1 + sovBtcstaking(uint64(m.VotingPower)) + } + return n +} + func (m *ProofOfPossession) Size() (n int) { if m == nil { return 0 @@ -904,6 +1174,129 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { } return nil } +func (m *BTCValidatorWithMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCValidatorWithMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCValidatorWithMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *BTCDelegation) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1294,6 +1687,148 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } return nil } +func (m *BTCDelegationWithMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCDelegationWithMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCDelegationWithMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EndHeight", wireType) + } + m.EndHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EndHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 39b43d5a3..563dc2d0e 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -6,6 +6,8 @@ package types import ( context "context" fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + query "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -112,33 +114,402 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +// QueryBTCValidatorsRequest is the request type for the +// Query/BTCValidators RPC method. +type QueryBTCValidatorsRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorsRequest) Reset() { *m = QueryBTCValidatorsRequest{} } +func (m *QueryBTCValidatorsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorsRequest) ProtoMessage() {} +func (*QueryBTCValidatorsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{2} +} +func (m *QueryBTCValidatorsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorsRequest.Merge(m, src) +} +func (m *QueryBTCValidatorsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorsRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryBTCValidatorsResponse is the response type for the +// Query/BTCValidators RPC method. +type QueryBTCValidatorsResponse struct { + // btc_validators contains all the btc validators + BtcValidators []*BTCValidator `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorsResponse) Reset() { *m = QueryBTCValidatorsResponse{} } +func (m *QueryBTCValidatorsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorsResponse) ProtoMessage() {} +func (*QueryBTCValidatorsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{3} +} +func (m *QueryBTCValidatorsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorsResponse.Merge(m, src) +} +func (m *QueryBTCValidatorsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorsResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorsResponse) GetBtcValidators() []*BTCValidator { + if m != nil { + return m.BtcValidators + } + return nil +} + +func (m *QueryBTCValidatorsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryBTCValidatorsAtHeightRequest is the request type for the +// Query/BTCValidatorsAtHeight RPC method. +type QueryBTCValidatorsAtHeightRequest struct { + // height defines at which Babylon height to query the btc validators info. + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorsAtHeightRequest) Reset() { *m = QueryBTCValidatorsAtHeightRequest{} } +func (m *QueryBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorsAtHeightRequest) ProtoMessage() {} +func (*QueryBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{4} +} +func (m *QueryBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorsAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.Merge(m, src) +} +func (m *QueryBTCValidatorsAtHeightRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorsAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorsAtHeightRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorsAtHeightRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *QueryBTCValidatorsAtHeightRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryBTCValidatorsAtHeightResponse is the response type for the +// Query/BTCValidatorsAtHeight RPC method. +type QueryBTCValidatorsAtHeightResponse struct { + // btc_validators contains all the queried btc validators. + BtcValidators []*BTCValidatorWithMeta `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorsAtHeightResponse) Reset() { *m = QueryBTCValidatorsAtHeightResponse{} } +func (m *QueryBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorsAtHeightResponse) ProtoMessage() {} +func (*QueryBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{5} +} +func (m *QueryBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorsAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.Merge(m, src) +} +func (m *QueryBTCValidatorsAtHeightResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorsAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorsAtHeightResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorsAtHeightResponse) GetBtcValidators() []*BTCValidatorWithMeta { + if m != nil { + return m.BtcValidators + } + return nil +} + +func (m *QueryBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryBTCValidatorDelegationsAtHeightRequest is the request type for the +// Query/BTCValidatorDelegationsAtHeight RPC method. +type QueryBTCValidatorDelegationsAtHeightRequest struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC Validator + // the PK follows encoding in BIP-340 spec + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // height defines at which Babylon height to query the btc delegations info. + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) Reset() { + *m = QueryBTCValidatorDelegationsAtHeightRequest{} +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) String() string { + return proto.CompactTextString(m) +} +func (*QueryBTCValidatorDelegationsAtHeightRequest) ProtoMessage() {} +func (*QueryBTCValidatorDelegationsAtHeightRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{6} +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.Merge(m, src) +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryBTCValidatorDelegationsAtHeightResponse is the response type for the +// Query/BTCValidatorDelegationsAtHeight RPC method. +type QueryBTCValidatorDelegationsAtHeightResponse struct { + // btc_validators contains all the queried btc delegations. + BtcDelegations []*BTCDelegationWithMeta `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) Reset() { + *m = QueryBTCValidatorDelegationsAtHeightResponse{} +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) String() string { + return proto.CompactTextString(m) +} +func (*QueryBTCValidatorDelegationsAtHeightResponse) ProtoMessage() {} +func (*QueryBTCValidatorDelegationsAtHeightResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{7} +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.Merge(m, src) +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) GetBtcDelegations() []*BTCDelegationWithMeta { + if m != nil { + return m.BtcDelegations + } + return nil +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btcstaking.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") + proto.RegisterType((*QueryBTCValidatorsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsRequest") + proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") + proto.RegisterType((*QueryBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightRequest") + proto.RegisterType((*QueryBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightResponse") + proto.RegisterType((*QueryBTCValidatorDelegationsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsAtHeightRequest") + proto.RegisterType((*QueryBTCValidatorDelegationsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsAtHeightResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 282 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, - 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, - 0x2a, 0xd1, 0x43, 0x28, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, 0xd0, - 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x64, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x13, 0x0b, 0x32, - 0xf5, 0x13, 0xf3, 0xf2, 0xf2, 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0xa1, 0xb2, 0x4a, 0xd8, - 0x6d, 0x2b, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0xaa, 0x51, 0x12, 0xe1, 0x12, 0x0a, 0x04, 0xd9, 0x1e, - 0x00, 0x16, 0x0c, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x51, 0x0a, 0xe2, 0x12, 0x46, 0x11, 0x2d, - 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0xb2, 0xe6, 0x62, 0x83, 0x68, 0x96, 0x60, 0x54, 0x60, 0xd4, - 0xe0, 0x36, 0x92, 0xd5, 0xc3, 0xea, 0x58, 0x3d, 0x88, 0x36, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, - 0x82, 0xa0, 0x5a, 0x8c, 0xe6, 0x32, 0x72, 0xb1, 0x82, 0x0d, 0x15, 0x9a, 0xcc, 0xc8, 0xc5, 0x06, - 0x51, 0x22, 0xa4, 0x89, 0xc3, 0x04, 0x4c, 0x37, 0x49, 0x69, 0x11, 0xa3, 0x14, 0xe2, 0x50, 0x25, - 0xa3, 0xa6, 0xcb, 0x4f, 0x26, 0x33, 0xe9, 0x08, 0x69, 0xe9, 0x43, 0xf5, 0x24, 0x67, 0x24, 0x66, - 0xe6, 0xe9, 0xe3, 0x0b, 0x0f, 0x27, 0x9f, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, - 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, - 0x88, 0x32, 0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xc5, 0x6e, 0x5e, 0x05, - 0xb2, 0x89, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xe0, 0x35, 0x06, 0x04, 0x00, 0x00, - 0xff, 0xff, 0x63, 0x6e, 0x35, 0xce, 0xf2, 0x01, 0x00, 0x00, + // 687 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xdd, 0x4e, 0xd4, 0x4e, + 0x14, 0xdf, 0x59, 0xf8, 0x6f, 0xfe, 0x19, 0x44, 0x93, 0x11, 0x0c, 0x36, 0xba, 0x40, 0x4d, 0x10, + 0x81, 0x74, 0xd8, 0x42, 0x0c, 0x42, 0x4c, 0xb4, 0x18, 0xbf, 0x4d, 0xd6, 0x06, 0x35, 0xf1, 0x86, + 0x4c, 0xcb, 0xa4, 0xdb, 0x50, 0x3a, 0x65, 0x3b, 0xdb, 0xb8, 0x31, 0xde, 0xe8, 0x0b, 0x98, 0xf0, + 0x22, 0xfa, 0x08, 0xde, 0x71, 0x65, 0x30, 0xde, 0x18, 0x2f, 0x88, 0x61, 0xbd, 0xf4, 0x21, 0x4c, + 0xa7, 0xb3, 0x6c, 0x17, 0xba, 0x50, 0x50, 0xef, 0x68, 0xfb, 0x3b, 0xe7, 0xfc, 0x3e, 0x38, 0x67, + 0xe1, 0xb8, 0x45, 0xac, 0xa6, 0xc7, 0x7c, 0x6c, 0x71, 0x3b, 0xe4, 0x64, 0xdd, 0xf5, 0x1d, 0x1c, + 0x55, 0xf0, 0x66, 0x83, 0xd6, 0x9b, 0x5a, 0x50, 0x67, 0x9c, 0xa1, 0x61, 0x09, 0xd1, 0x3a, 0x10, + 0x2d, 0xaa, 0x28, 0x43, 0x0e, 0x73, 0x98, 0x40, 0xe0, 0xf8, 0xaf, 0x04, 0xac, 0x5c, 0x72, 0x18, + 0x73, 0x3c, 0x8a, 0x49, 0xe0, 0x62, 0xe2, 0xfb, 0x8c, 0x13, 0xee, 0x32, 0x3f, 0x94, 0x5f, 0xa7, + 0x6c, 0x16, 0x6e, 0xb0, 0x10, 0x5b, 0x24, 0xa4, 0xc9, 0x0c, 0x1c, 0x55, 0x2c, 0xca, 0x49, 0x05, + 0x07, 0xc4, 0x71, 0x7d, 0x01, 0x96, 0x58, 0x35, 0x9b, 0x59, 0x40, 0xea, 0x64, 0xa3, 0xdd, 0x6f, + 0x22, 0x1b, 0x93, 0x22, 0x2a, 0x70, 0xea, 0x10, 0x44, 0x4f, 0xe3, 0x69, 0x55, 0x51, 0x6c, 0xd2, + 0xcd, 0x06, 0x0d, 0xb9, 0x6a, 0xc2, 0xf3, 0x5d, 0x6f, 0xc3, 0x80, 0xf9, 0x21, 0x45, 0x4b, 0xb0, + 0x94, 0x0c, 0x19, 0x01, 0x63, 0x60, 0x72, 0x40, 0xbf, 0xac, 0x65, 0x1a, 0xa0, 0x25, 0x65, 0x46, + 0xff, 0xf6, 0xee, 0x68, 0xc1, 0x94, 0x25, 0xaa, 0x0d, 0x2f, 0x8a, 0x9e, 0xc6, 0xca, 0xf2, 0x73, + 0xe2, 0xb9, 0x6b, 0x84, 0xb3, 0x7a, 0x7b, 0x20, 0xba, 0x0b, 0x61, 0x47, 0xa6, 0xec, 0x3e, 0xa1, + 0x25, 0x9e, 0x68, 0xb1, 0x27, 0x5a, 0xe2, 0xbb, 0xf4, 0x44, 0xab, 0x12, 0x87, 0xca, 0x5a, 0x33, + 0x55, 0xa9, 0x7e, 0x04, 0x50, 0xc9, 0x9a, 0x22, 0x05, 0x3c, 0x84, 0x67, 0x2d, 0x6e, 0xaf, 0x46, + 0xfb, 0x5f, 0x46, 0xc0, 0x58, 0xdf, 0xe4, 0x80, 0x7e, 0xa5, 0x87, 0x90, 0x74, 0x17, 0x73, 0xd0, + 0xe2, 0x76, 0xa7, 0x27, 0xba, 0xd7, 0x45, 0xb9, 0x28, 0x28, 0x5f, 0x3d, 0x96, 0x72, 0x42, 0xa4, + 0x8b, 0xf3, 0x3b, 0x00, 0xc7, 0x0f, 0x73, 0xbe, 0xcd, 0xef, 0x53, 0xd7, 0xa9, 0xf1, 0xb6, 0x43, + 0x17, 0x60, 0xa9, 0x26, 0x5e, 0x08, 0x77, 0xfa, 0x4d, 0xf9, 0x74, 0xc0, 0xb9, 0xe2, 0xa9, 0x9d, + 0xfb, 0x04, 0xa0, 0x7a, 0x14, 0x0b, 0xe9, 0xa0, 0xd9, 0xc3, 0xc1, 0xe9, 0x1c, 0x0e, 0xbe, 0x70, + 0x79, 0xed, 0x09, 0xe5, 0xe4, 0x9f, 0x39, 0xd9, 0x02, 0x70, 0xfa, 0x90, 0x86, 0x3b, 0xd4, 0xa3, + 0x4e, 0xb2, 0x6b, 0x07, 0x3d, 0x5d, 0x81, 0x30, 0x22, 0xde, 0x6a, 0x2c, 0x28, 0x58, 0x17, 0xbe, + 0x9e, 0x31, 0xae, 0x7f, 0xdf, 0x1d, 0xd5, 0x1d, 0x97, 0xd7, 0x1a, 0x96, 0x66, 0xb3, 0x0d, 0x2c, + 0x65, 0xd9, 0x35, 0xe2, 0xfa, 0xed, 0x07, 0xcc, 0x9b, 0x01, 0x0d, 0x35, 0xe3, 0x41, 0x75, 0x6e, + 0x7e, 0xb6, 0xda, 0xb0, 0x1e, 0xd1, 0xa6, 0xf9, 0x7f, 0x44, 0x3c, 0x83, 0xdb, 0xd5, 0xf5, 0x54, + 0x52, 0xc5, 0x23, 0x92, 0xea, 0x3b, 0x75, 0x52, 0x9f, 0x01, 0x9c, 0xc9, 0xa7, 0x52, 0x66, 0xf6, + 0x0c, 0x9e, 0x8b, 0x25, 0xae, 0x75, 0x20, 0x32, 0xb4, 0x99, 0xde, 0xa1, 0x75, 0xfa, 0xed, 0xa7, + 0x16, 0x07, 0x9f, 0x1a, 0xf3, 0xd7, 0x62, 0xd3, 0xb7, 0x4a, 0xf0, 0x3f, 0x21, 0x08, 0x6d, 0x01, + 0x58, 0x4a, 0x8e, 0x07, 0xba, 0xd6, 0x83, 0xdb, 0xe1, 0x6b, 0xa5, 0x4c, 0xe5, 0x81, 0x26, 0x73, + 0x55, 0xfd, 0xed, 0xd7, 0x9f, 0x5b, 0xc5, 0x19, 0x34, 0x95, 0x1d, 0x6c, 0xd6, 0x45, 0x45, 0x1f, + 0x00, 0x1c, 0xec, 0xda, 0x0a, 0x34, 0x7b, 0xd4, 0xc4, 0xac, 0x03, 0xa7, 0x54, 0x4e, 0x50, 0x21, + 0xa9, 0x2e, 0x0a, 0xaa, 0xf3, 0x48, 0xcf, 0x43, 0xb5, 0x7b, 0x29, 0xd1, 0x17, 0x00, 0x87, 0x33, + 0x17, 0x19, 0x2d, 0xe4, 0x26, 0x72, 0x60, 0x5b, 0x94, 0x1b, 0xa7, 0xa8, 0x94, 0x52, 0x96, 0x85, + 0x94, 0x9b, 0x68, 0xe9, 0xe4, 0x52, 0xf0, 0xeb, 0x64, 0x7d, 0xde, 0xa0, 0x5f, 0x00, 0x8e, 0x1e, + 0xf3, 0x2f, 0x8f, 0x8c, 0xbc, 0x1c, 0x7b, 0x5f, 0x05, 0x65, 0xf9, 0x8f, 0x7a, 0x48, 0xc5, 0xb7, + 0x84, 0xe2, 0x45, 0xb4, 0x90, 0x47, 0x71, 0x6a, 0x33, 0xf7, 0xe5, 0x1a, 0x8f, 0xb7, 0xf7, 0xca, + 0x60, 0x67, 0xaf, 0x0c, 0x7e, 0xec, 0x95, 0xc1, 0xfb, 0x56, 0xb9, 0xb0, 0xd3, 0x2a, 0x17, 0xbe, + 0xb5, 0xca, 0x85, 0x97, 0xc7, 0x9e, 0xa7, 0x57, 0xe9, 0xfe, 0xe2, 0x56, 0x59, 0x25, 0xf1, 0x73, + 0x3f, 0xf7, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x45, 0x9f, 0xcd, 0x5b, 0xd6, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -155,6 +526,13 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // BTCValidators queries all btc validators + BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) + // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) + // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. + // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. + BTCValidatorDelegationsAtHeight(ctx context.Context, in *QueryBTCValidatorDelegationsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsAtHeightResponse, error) } type queryClient struct { @@ -174,10 +552,44 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } +func (c *queryClient) BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) { + out := new(QueryBTCValidatorsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidators", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) { + out := new(QueryBTCValidatorsAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorsAtHeight", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) BTCValidatorDelegationsAtHeight(ctx context.Context, in *QueryBTCValidatorDelegationsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsAtHeightResponse, error) { + out := new(QueryBTCValidatorDelegationsAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorDelegationsAtHeight", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // BTCValidators queries all btc validators + BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) + // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + BTCValidatorsAtHeight(context.Context, *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) + // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. + // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. + BTCValidatorDelegationsAtHeight(context.Context, *QueryBTCValidatorDelegationsAtHeightRequest) (*QueryBTCValidatorDelegationsAtHeightResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -187,6 +599,15 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidators not implemented") +} +func (*UnimplementedQueryServer) BTCValidatorsAtHeight(ctx context.Context, req *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorsAtHeight not implemented") +} +func (*UnimplementedQueryServer) BTCValidatorDelegationsAtHeight(ctx context.Context, req *QueryBTCValidatorDelegationsAtHeightRequest) (*QueryBTCValidatorDelegationsAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegationsAtHeight not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -210,6 +631,60 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Query_BTCValidators_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidators(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidators", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidators(ctx, req.(*QueryBTCValidatorsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_BTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorsAtHeightRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidatorsAtHeight(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorsAtHeight", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidatorsAtHeight(ctx, req.(*QueryBTCValidatorsAtHeightRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_BTCValidatorDelegationsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorDelegationsAtHeightRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidatorDelegationsAtHeight(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorDelegationsAtHeight", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidatorDelegationsAtHeight(ctx, req.(*QueryBTCValidatorDelegationsAtHeightRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btcstaking.v1.Query", HandlerType: (*QueryServer)(nil), @@ -218,6 +693,18 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "BTCValidators", + Handler: _Query_BTCValidators_Handler, + }, + { + MethodName: "BTCValidatorsAtHeight", + Handler: _Query_BTCValidatorsAtHeight_Handler, + }, + { + MethodName: "BTCValidatorDelegationsAtHeight", + Handler: _Query_BTCValidatorDelegationsAtHeight_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btcstaking/v1/query.proto", @@ -279,13 +766,287 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *QueryBTCValidatorsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.BtcValidators) > 0 { + for iNdEx := len(m.BtcValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.BtcValidators) > 0 { + for iNdEx := len(m.BtcValidators) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.BtcDelegations) > 0 { + for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ } dAtA[offset] = uint8(v) return base @@ -310,6 +1071,112 @@ func (m *QueryParamsResponse) Size() (n int) { return n } +func (m *QueryBTCValidatorsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcValidators) > 0 { + for _, e := range m.BtcValidators { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorsAtHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorsAtHeightResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcValidators) > 0 { + for _, e := range m.BtcValidators { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorDelegationsAtHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorDelegationsAtHeightResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcDelegations) > 0 { + for _, e := range m.BtcDelegations { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -449,6 +1316,697 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCValidatorsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcValidators = append(m.BtcValidators, &BTCValidator{}) + if err := m.BtcValidators[len(m.BtcValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorsAtHeightRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorsAtHeightResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcValidators = append(m.BtcValidators, &BTCValidatorWithMeta{}) + if err := m.BtcValidators[len(m.BtcValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorDelegationsAtHeightRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorDelegationsAtHeightResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegationWithMeta{}) + if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index bed63f45f..827c6d7a0 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -51,6 +51,186 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +var ( + filter_Query_BTCValidators_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_BTCValidators_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidators_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.BTCValidators(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidators_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidators_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BTCValidators(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_BTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorsAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorsAtHeight_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.BTCValidatorsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorsAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorsAtHeight_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BTCValidatorsAtHeight(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_BTCValidatorDelegationsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorDelegationsAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegationsAtHeight_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.BTCValidatorDelegationsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorDelegationsAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegationsAtHeight_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BTCValidatorDelegationsAtHeight(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -80,6 +260,75 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCValidators_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidators_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidators_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidatorsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCValidatorDelegationsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidatorDelegationsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorDelegationsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -141,13 +390,85 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCValidators_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidators_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidators_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidatorsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCValidatorDelegationsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidatorDelegationsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorDelegationsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCValidatorDelegationsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "delegations", "height"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_BTCValidators_0 = runtime.ForwardResponseMessage + + forward_Query_BTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage + + forward_Query_BTCValidatorDelegationsAtHeight_0 = runtime.ForwardResponseMessage ) diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go new file mode 100644 index 000000000..ebab4d4d0 --- /dev/null +++ b/x/finality/keeper/grpc_query.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "context" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + + bbn "github.com/babylonchain/babylon/types" + + "google.golang.org/grpc/status" + + "github.com/babylonchain/babylon/x/finality/types" +) + +var _ types.QueryServer = Keeper{} + +func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeightRequest) (*types.QueryVotesAtHeightResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + var btcPks []bbn.BIP340PubKey + + // get the validator set of block at given height + valSet := k.BTCStakingKeeper.GetVotingPowerTable(sdkCtx, req.Height) + for pkHex := range valSet { + pk, err := bbn.NewBIP340PubKeyFromHex(pkHex) + if err != nil { + // failing to unmarshal validator BTC PK in KVStore is a programming error + panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) + } + + btcPks = append(btcPks, pk.MustMarshal()) + } + + return &types.QueryVotesAtHeightResponse{BtcPks: btcPks}, nil +} diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index 910f8e01a..52b145bae 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -51,7 +51,7 @@ func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.Schnor valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) if err != nil { // failing to unmarshal validator BTC PK in KVStore is a programming error - panic(fmt.Errorf("failed to unmarshal validator BTC PK: %w", err)) + panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } sig, err := bbn.NewSchnorrEOTSSig(iter.Value()) if err != nil { diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 83fa6e380..21769b2c7 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -12,5 +12,5 @@ var ( ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") - ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1107, "evidence is not found") + ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1106, "evidence is not found") ) diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index 2ddeab5e3..df8117c26 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -6,6 +6,7 @@ package types import ( context "context" fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -112,15 +113,105 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +// QueryVotesAtHeightRequest is the request type for the +// Query/VotesAtHeight RPC method. +type QueryVotesAtHeightRequest struct { + // height defines at which height to query the btc validators. + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *QueryVotesAtHeightRequest) Reset() { *m = QueryVotesAtHeightRequest{} } +func (m *QueryVotesAtHeightRequest) String() string { return proto.CompactTextString(m) } +func (*QueryVotesAtHeightRequest) ProtoMessage() {} +func (*QueryVotesAtHeightRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{2} +} +func (m *QueryVotesAtHeightRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVotesAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVotesAtHeightRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVotesAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVotesAtHeightRequest.Merge(m, src) +} +func (m *QueryVotesAtHeightRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryVotesAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVotesAtHeightRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVotesAtHeightRequest proto.InternalMessageInfo + +func (m *QueryVotesAtHeightRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +// QueryVotesAtHeightResponse is the response type for the +// Query/VotesAtHeight RPC method. +type QueryVotesAtHeightResponse struct { + // btc_pk is the Bitcoin secp256k1 PK of BTC validators who have signed the block at given height. + // the PK follows encoding in BIP-340 spec + BtcPks []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,rep,name=btc_pks,json=btcPks,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pks,omitempty"` +} + +func (m *QueryVotesAtHeightResponse) Reset() { *m = QueryVotesAtHeightResponse{} } +func (m *QueryVotesAtHeightResponse) String() string { return proto.CompactTextString(m) } +func (*QueryVotesAtHeightResponse) ProtoMessage() {} +func (*QueryVotesAtHeightResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{3} +} +func (m *QueryVotesAtHeightResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVotesAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVotesAtHeightResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVotesAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVotesAtHeightResponse.Merge(m, src) +} +func (m *QueryVotesAtHeightResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryVotesAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVotesAtHeightResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVotesAtHeightResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.finality.v1.QueryParamsResponse") + proto.RegisterType((*QueryVotesAtHeightRequest)(nil), "babylon.finality.v1.QueryVotesAtHeightRequest") + proto.RegisterType((*QueryVotesAtHeightResponse)(nil), "babylon.finality.v1.QueryVotesAtHeightResponse") } func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 279 bytes of a gzipped FileDescriptorProto + // 410 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0x2a, 0xd0, @@ -131,14 +222,22 @@ var fileDescriptor_32bddab77af6fdae = []byte{ 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x28, 0x05, 0x70, 0x09, 0xa3, 0x88, 0x16, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x0a, 0x59, 0x72, 0xb1, 0x41, 0x34, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x49, 0xeb, 0x61, 0x71, 0xa8, 0x1e, 0x44, 0x93, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, - 0x0d, 0x46, 0xd3, 0x19, 0xb9, 0x58, 0xc1, 0x46, 0x0a, 0xf5, 0x32, 0x72, 0xb1, 0x41, 0x94, 0x08, - 0xa9, 0x63, 0xd5, 0x8f, 0xe9, 0x1e, 0x29, 0x0d, 0xc2, 0x0a, 0x21, 0x4e, 0x54, 0x32, 0x68, 0xba, - 0xfc, 0x64, 0x32, 0x93, 0x96, 0x90, 0x86, 0x3e, 0x54, 0x47, 0x72, 0x46, 0x62, 0x66, 0x9e, 0x3e, - 0xee, 0x70, 0x70, 0xf2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, - 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x83, - 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xec, 0xa6, 0x55, 0x20, 0xcc, 0x2b, - 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, 0x07, 0xaa, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x7d, - 0xa8, 0x3f, 0x3a, 0xe2, 0x01, 0x00, 0x00, + 0x0d, 0x4a, 0xc6, 0x5c, 0x92, 0x60, 0x13, 0xc3, 0xf2, 0x4b, 0x52, 0x8b, 0x1d, 0x4b, 0x3c, 0x52, + 0x33, 0xd3, 0x33, 0x4a, 0xa0, 0xd6, 0x09, 0x89, 0x71, 0xb1, 0x65, 0x80, 0x05, 0xc0, 0xe6, 0xb2, + 0x04, 0x41, 0x79, 0x4a, 0xb9, 0x5c, 0x52, 0xd8, 0x34, 0x41, 0x5d, 0xe3, 0xcf, 0xc5, 0x9e, 0x54, + 0x92, 0x1c, 0x5f, 0x90, 0x0d, 0x72, 0x0e, 0xb3, 0x06, 0x8f, 0x93, 0xd9, 0xad, 0x7b, 0xf2, 0x46, + 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0xc7, 0x25, 0x67, 0x24, + 0x66, 0xe6, 0xc1, 0x38, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x7a, 0x4e, 0x9e, 0x01, 0xc6, 0x26, + 0x06, 0x01, 0xa5, 0x49, 0xde, 0xa9, 0x95, 0x41, 0x6c, 0x49, 0x25, 0xc9, 0x01, 0xd9, 0xc5, 0x46, + 0x47, 0x98, 0xb8, 0x58, 0xc1, 0xf6, 0x09, 0xf5, 0x32, 0x72, 0xb1, 0x41, 0xbc, 0x21, 0xa4, 0x8e, + 0xd5, 0x8f, 0x98, 0x61, 0x26, 0xa5, 0x41, 0x58, 0x21, 0xc4, 0xe1, 0x4a, 0x06, 0x4d, 0x97, 0x9f, + 0x4c, 0x66, 0xd2, 0x12, 0xd2, 0xc0, 0xee, 0x42, 0xcc, 0xb8, 0x12, 0x5a, 0xc5, 0xc8, 0xc5, 0x8b, + 0x12, 0x08, 0x42, 0x7a, 0xb8, 0x6d, 0xc3, 0x16, 0xc4, 0x52, 0xfa, 0x44, 0xab, 0x87, 0x3a, 0xd2, + 0x02, 0xec, 0x48, 0x23, 0x21, 0x03, 0xc2, 0x8e, 0x2c, 0x03, 0x19, 0xa0, 0x5f, 0x0d, 0x89, 0xb4, + 0x5a, 0x27, 0xaf, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, + 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x20, 0x14, + 0x39, 0x15, 0x08, 0x73, 0xc1, 0xf1, 0x94, 0xc4, 0x06, 0x4e, 0xa5, 0xc6, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x27, 0xc4, 0xe1, 0xef, 0x33, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -155,6 +254,8 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // VotesAtHeight queries btc validators who have signed the block at given height. + VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) } type queryClient struct { @@ -174,10 +275,21 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } +func (c *queryClient) VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) { + out := new(QueryVotesAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/VotesAtHeight", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // VotesAtHeight queries btc validators who have signed the block at given height. + VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -187,6 +299,9 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) VotesAtHeight(ctx context.Context, req *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VotesAtHeight not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -210,6 +325,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Query_VotesAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryVotesAtHeightRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).VotesAtHeight(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/VotesAtHeight", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).VotesAtHeight(ctx, req.(*QueryVotesAtHeightRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.finality.v1.Query", HandlerType: (*QueryServer)(nil), @@ -218,6 +351,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "VotesAtHeight", + Handler: _Query_VotesAtHeight_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/finality/v1/query.proto", @@ -279,6 +416,71 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *QueryVotesAtHeightRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVotesAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVotesAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryVotesAtHeightResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVotesAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVotesAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcPks) > 0 { + for iNdEx := len(m.BtcPks) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.BtcPks[iNdEx].Size() + i -= size + if _, err := m.BtcPks[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -310,6 +512,33 @@ func (m *QueryParamsResponse) Size() (n int) { return n } +func (m *QueryVotesAtHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n +} + +func (m *QueryVotesAtHeightResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcPks) > 0 { + for _, e := range m.BtcPks { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -449,6 +678,160 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryVotesAtHeightRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVotesAtHeightRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVotesAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryVotesAtHeightResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVotesAtHeightResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVotesAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPks", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPks = append(m.BtcPks, v) + if err := m.BtcPks[len(m.BtcPks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index 430c89d5a..f816dc803 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -51,6 +51,60 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +func request_Query_VotesAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVotesAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := client.VotesAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_VotesAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVotesAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := server.VotesAtHeight(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -80,6 +134,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_VotesAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_VotesAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VotesAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -141,13 +218,37 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_VotesAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_VotesAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VotesAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_VotesAtHeight_0 = runtime.ForwardResponseMessage ) From d3b30761041fa2c46df3c8e8b9ce02096da8b2a4 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 13 Jul 2023 20:13:59 +1000 Subject: [PATCH 029/202] e2e: e2e test for creating BTC validator/delegation (#23) --- proto/babylon/btcstaking/v1/params.proto | 4 + proto/babylon/btcstaking/v1/tx.proto | 1 - test/e2e/btc_staking_e2e_test.go | 106 +++++++++++++++++- .../configurer/chain/queries_btcstaking.go | 61 ++++++++++ testutil/datagen/btcstaking.go | 20 ++-- types/btc_schnorr_pk.go | 8 ++ x/btccheckpoint/types/types.go | 11 ++ x/btcstaking/abci.go | 8 +- x/btcstaking/keeper/msg_server.go | 42 ++++--- x/btcstaking/keeper/msg_server_test.go | 5 +- x/btcstaking/types/btc_slashing_tx.go | 2 - x/btcstaking/types/errors.go | 6 + x/btcstaking/types/params.go | 28 ++++- x/btcstaking/types/params.pb.go | 65 +++++++++-- x/btcstaking/types/tx.pb.go | 2 - x/finality/keeper/msg_server.go | 19 ++-- x/finality/types/errors.go | 18 +-- 17 files changed, 331 insertions(+), 75 deletions(-) create mode 100644 test/e2e/configurer/chain/queries_btcstaking.go diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 5f1a23a3a..3bb753220 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -15,4 +15,8 @@ message Params { // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin string slashing_address = 2; + // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified + // in Satoshi) needed for the pre-signed slashing tx + // TODO: change to satoshi per byte? + int64 min_slashing_tx_fee_sat = 3; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 870de7ed8..f6cec432e 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -19,7 +19,6 @@ service Msg { rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); // AddJurySig handles a signature from jury rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse); - // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 58bee6729..e60b656ba 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -1,11 +1,20 @@ package e2e import ( + "math" "math/rand" "time" "github.com/babylonchain/babylon/test/e2e/configurer" + "github.com/babylonchain/babylon/test/e2e/initialization" + "github.com/babylonchain/babylon/test/e2e/util" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/stretchr/testify/suite" ) @@ -36,18 +45,109 @@ func (s *BTCStakingTestSuite) TearDownSuite() { s.Require().NoError(err) } -func (s *BTCStakingTestSuite) TestCreateBTCValidator() { +// TestCreateBTCValidatorAndDelegation is an end-to-end test for +// user story 1/2: user creates BTC validator and BTC delegation, then +// jury approves the BTC delegation +func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) + /* + create a random BTC validator on Babylon + */ // generate a random BTC validator r := rand.New(rand.NewSource(time.Now().Unix())) btcVal, err := datagen.GenRandomBTCValidator(r) s.NoError(err) - // create a BTC validator + // create this BTC validator nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop) - // TODO: query the existence of BTC validator. need RPC query here + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + // query the existence of BTC validator and assert equivalence + actualBtcVals := nonValidatorNode.QueryBTCValidators() + s.Len(actualBtcVals, 1) + s.Equal(util.Cdc.MustMarshal(btcVal), util.Cdc.MustMarshal(actualBtcVals[0])) + + /* + create a random BTC delegation under this bTC validator + */ + // BTC staking params, BTC delegation key pairs and PoP + params := nonValidatorNode.QueryBTCStakingParams() + delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + s.NoError(err) + delBTCSK, delBTCPK, err := datagen.GenRandomBTCKeyPair(r) + s.NoError(err) + pop, err := bstypes.NewPoP(delBabylonSK, delBTCSK) + s.NoError(err) + // generate staking tx and slashing tx + stakingTimeBlocks := uint16(math.MaxUint16) + stakingValue := int64(2 * 10e8) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + r, + delBTCSK, + btcVal.BtcPk.MustToBTCPK(), + params.JuryPk.MustToBTCPK(), + stakingTimeBlocks, + stakingValue, + params.SlashingAddress, + ) + s.NoError(err) + stakingMsgTx, err := stakingTx.ToMsgTx() + s.NoError(err) + // generate proper delegator sig + delegatorSig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.StakingScript, + delBTCSK, + &chaincfg.SimNetParams, + ) + s.NoError(err) + + // submit staking tx to Bitcoin and get inclusion proof + currentBtcTip, err := nonValidatorNode.QueryTip() + s.NoError(err) + blockWithStakingTx := datagen.CreateBlockWithTransaction(r, currentBtcTip.Header.ToBlockHeader(), stakingMsgTx) + nonValidatorNode.InsertHeader(&blockWithStakingTx.HeaderBytes) + // make block k-deep + for i := 0; i < initialization.BabylonBtcConfirmationPeriod; i++ { + nonValidatorNode.InsertNewEmptyBtcHeader(r) + } + stakingTxInfo := btcctypes.NewTransactionInfoFromSpvProof(blockWithStakingTx.SpvProof) + + // submit the message for creating BTC delegation + nonValidatorNode.CreateBTCDelegation(delBabylonPK.(*secp256k1.PubKey), pop, stakingTx, stakingTxInfo, slashingTx, delegatorSig) + + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + /* + generate and insert new jury signature, in order to activate the BTC delegation + */ + jurySKBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + jurySK, _ := btcec.PrivKeyFromBytes(jurySKBytes) + jurySig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.StakingScript, + jurySK, + &chaincfg.SimNetParams, + ) + s.NoError(err) + nonValidatorNode.AddJurySig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), jurySig) + + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + // query the existence of BTC delegation and assert equivalence + curHeight, err := nonValidatorNode.QueryCurrentHeight() + s.NoError(err) + actualDels := nonValidatorNode.QueryBTCValidatorDelegationsAtHeight(btcVal.BtcPk, uint64(curHeight)) + s.Len(actualDels, 1) + s.Equal(delBTCPK.SerializeCompressed()[1:], actualDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + stakingInfo, err := stakingTx.GetStakingOutputInfo(&chaincfg.SimNetParams) + s.NoError(err) + s.Equal(uint64(stakingInfo.StakingAmount), actualDels[0].VotingPower) } diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go new file mode 100644 index 000000000..0a7644713 --- /dev/null +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -0,0 +1,61 @@ +package chain + +import ( + "encoding/base64" + "fmt" + "net/url" + + "github.com/babylonchain/babylon/test/e2e/util" + bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/stretchr/testify/require" +) + +func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { + bz, err := n.QueryGRPCGateway("/babylonchain/babylon/btcstaking/v1/params", url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryParamsResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return &resp.Params +} + +func (n *NodeConfig) QueryBTCValidators() []*bstypes.BTCValidator { + bz, err := n.QueryGRPCGateway("/babylonchain/babylon/btcstaking/v1/btc_validators", url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryBTCValidatorsResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.BtcValidators +} + +func (n *NodeConfig) QueryBTCValidatorsAtHeight(height uint64) []*bstypes.BTCValidatorWithMeta { + path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%d", height) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryBTCValidatorsAtHeightResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.BtcValidators +} + +func (n *NodeConfig) QueryBTCValidatorDelegationsAtHeight(valBTCPK *bbn.BIP340PubKey, height uint64) []*bstypes.BTCDelegationWithMeta { + path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/delegations/%d", height) + valBTCPKStr := base64.URLEncoding.EncodeToString(valBTCPK.MustMarshal()) + bz, err := n.QueryGRPCGateway(path, url.Values{ + "val_btc_pk": []string{valBTCPKStr}, + }) + require.NoError(n.t, err) + + var resp bstypes.QueryBTCValidatorDelegationsAtHeightResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.BtcDelegations +} diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index c1b6b6574..e3ad17596 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" ) @@ -104,16 +103,13 @@ func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validator } tx := wire.NewMsgTx(2) - // an arbitrary input, doesn't matter - txid, err := chainhash.NewHash(GenRandomByteArray(r, 32)) - if err != nil { - return nil, nil, err - } - lastOut := &wire.OutPoint{ - Hash: *txid, - Index: 0, - } - tx.AddTxIn(wire.NewTxIn(lastOut, []byte{1, 2, 3}, [][]byte{[]byte{1, 2, 3}})) + // an arbitrary input + spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: spend.prevOut, + Sequence: wire.MaxTxInSequenceNum, + SignatureScript: nil, + }) // 2 outputs for changes and staking output tx.AddTxOut(wire.NewTxOut(100, []byte{1, 2, 3})) // output for change, doesn't matter tx.AddTxOut(stakingOutput) @@ -134,7 +130,7 @@ func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validator if err != nil { return nil, nil, err } - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 100, stakingScript, btcNet) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 10000, stakingScript, btcNet) if err != nil { return nil, nil, err } diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index 4109bdd52..f641d1097 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -39,6 +39,14 @@ func (pk BIP340PubKey) ToBTCPK() (*btcec.PublicKey, error) { return schnorr.ParsePubKey(pk) } +func (pk BIP340PubKey) MustToBTCPK() *btcec.PublicKey { + btcPK, err := schnorr.ParsePubKey(pk) + if err != nil { + panic(err) + } + return btcPK +} + func (pk *BIP340PubKey) ToHexStr() string { return hex.EncodeToString(pk.MustMarshal()) } diff --git a/x/btccheckpoint/types/types.go b/x/btccheckpoint/types/types.go index c6e781fbc..d70fa2894 100644 --- a/x/btccheckpoint/types/types.go +++ b/x/btccheckpoint/types/types.go @@ -162,6 +162,17 @@ func NewTransactionInfo(txKey *TransactionKey, txBytes []byte, proof []byte) *Tr } } +func NewTransactionInfoFromSpvProof(proof *BTCSpvProof) *TransactionInfo { + return &TransactionInfo{ + Key: &TransactionKey{ + Index: proof.BtcTransactionIndex, + Hash: proof.ConfirmingBtcHeader.Hash(), + }, + Transaction: proof.BtcTransaction, + Proof: proof.MerkleNodes, + } +} + func NewTransactionInfoFromHex(txInfoHex string) (*TransactionInfo, error) { txInfoBytes, err := hex.DecodeString(txInfoHex) if err != nil { diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index b995dad8a..45717b9c4 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -17,12 +17,8 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - // if the BTC staking protocol is activated, i.e., there exists a height where a BTC validator - // has voting power, index BTC height and update BTC validator power table - if _, err := k.GetBTCStakingActivatedHeight(ctx); err == nil { - k.IndexBTCHeight(ctx) - k.RecordVotingPowerTable(ctx) - } + k.RecordVotingPowerTable(ctx) + k.IndexBTCHeight(ctx) return []abci.ValidatorUpdate{} } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 31aa1be25..af64fa350 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -68,6 +68,8 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + params := ms.GetParams(ctx) + // extract staking script from staking tx stakingOutputInfo, err := req.StakingTx.GetStakingOutputInfo(ms.btcNet) if err != nil { @@ -85,13 +87,13 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // since a delegation is keyed by (valPK, delPK). Need to decide whether to support this btcDel, err := ms.GetBTCDelegation(ctx, valBTCPK.MustMarshal(), delBTCPK.MustMarshal()) if err == nil && btcDel.StakingTx.Equals(req.StakingTx) { - return nil, fmt.Errorf("the BTC staking tx is already used") + return nil, types.ErrReusedStakingTx } // ensure staking tx is using correct jury PK - paramJuryPK := ms.GetParams(ctx).JuryPk + paramJuryPK := params.JuryPk if !juryPK.Equals(paramJuryPK) { - return nil, fmt.Errorf("staking tx specifies a wrong jury PK %s (expected: %s)", hex.EncodeToString(*juryPK), hex.EncodeToString(*paramJuryPK)) + return nil, types.ErrInvalidJuryPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramJuryPK), hex.EncodeToString(*juryPK)) } // ensure staking tx is k-deep @@ -101,29 +103,35 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } kValue := ms.btccKeeper.GetParams(ctx).BtcConfirmationDepth if stakingTxDepth < kValue { - return nil, fmt.Errorf("staking tx is not k-deep yet. k=%d, depth=%d", kValue, stakingTxDepth) + return nil, types.ErrInvalidStakingTx.Wrapf("not k-deep: k=%d; depth=%d", kValue, stakingTxDepth) } // verify staking tx info, i.e., inclusion proof if err := req.StakingTxInfo.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { - return nil, err + return nil, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain: %v", err) } // check slashing tx and its consistency with staking tx slashingMsgTx, err := req.SlashingTx.ToMsgTx() if err != nil { - return nil, err + return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - slashingAddr, err := btcutil.DecodeAddress(ms.GetParams(ctx).SlashingAddress, ms.btcNet) + slashingAddr, err := btcutil.DecodeAddress(params.SlashingAddress, ms.btcNet) if err != nil { - return nil, err + panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) } stakingMsgTx, err := req.StakingTx.ToMsgTx() if err != nil { - return nil, err + return nil, types.ErrInvalidStakingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - // TODO: parameterise slash min fee - if _, err := btcstaking.CheckTransactions(slashingMsgTx, stakingMsgTx, 1, slashingAddr, req.StakingTx.StakingScript, ms.btcNet); err != nil { - return nil, err + if _, err := btcstaking.CheckTransactions( + slashingMsgTx, + stakingMsgTx, + params.MinSlashingTxFeeSat, + slashingAddr, + req.StakingTx.StakingScript, + ms.btcNet, + ); err != nil { + return nil, types.ErrInvalidStakingTx.Wrap(err.Error()) } // verify delegator_sig @@ -135,7 +143,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre req.DelegatorSig, ) if err != nil { - return nil, err + return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) } // all good, construct BTCDelegation and insert BTC delegation @@ -159,7 +167,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { - return nil, err + panic(fmt.Errorf("failed to emit EventNewBTCDelegation: %w", err)) } return &types.MsgCreateBTCDelegationResponse{}, nil @@ -175,7 +183,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) return nil, err } if btcDel.IsActivated() { - return nil, fmt.Errorf("the BTC delegation has already been signed by the jury") + return nil, types.ErrDuplicatedJurySig } stakingOutputInfo, err := btcDel.StakingTx.GetStakingOutputInfo(ms.btcNet) @@ -199,7 +207,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) req.Sig, ) if err != nil { - return nil, err + return nil, types.ErrInvalidJurySig.Wrap(err.Error()) } // all good, add signature to BTC delegation and set it back to KVStore @@ -208,7 +216,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { - return nil, err + panic(fmt.Errorf("failed to emit EventActivateBTCDelegation: %w", err)) } return &types.MsgAddJurySigResponse{}, nil diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 86a67fa45..f217132c7 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -95,8 +95,9 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ - JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), - SlashingAddress: slashingAddr, + JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + SlashingAddress: slashingAddr, + MinSlashingTxFeeSat: 10, }) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index b16ac15e4..13cebbd97 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -14,8 +14,6 @@ import ( type BTCSlashingTx []byte -// TODO: constructors, verification rules, and util functions - func NewBTCSlashingTxFromMsgTx(msgTx *wire.MsgTx) (*BTCSlashingTx, error) { var buf bytes.Buffer err := msgTx.Serialize(&buf) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 4a7a15d61..db7564780 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -11,4 +11,10 @@ var ( ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1103, "the BTC staking protocol is not activated yet") ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1104, "the BTC height is not found") + ErrReusedStakingTx = errorsmod.Register(ModuleName, 1105, "the BTC staking tx is already used") + ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1106, "the BTC staking tx specifies a wrong jury PK") + ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is not valid") + ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1108, "the BTC slashing tx is not valid") + ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1109, "the BTC delegation has already received jury signature") + ErrInvalidJurySig = errorsmod.Register(ModuleName, 1110, "the jury signature is not valid") ) diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 7f0d4f59e..c5ba3216f 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -1,21 +1,45 @@ package types import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) var _ paramtypes.ParamSet = (*Params)(nil) +func defaultJuryPk() *bbn.BIP340PubKey { + // 32 bytes + skBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + _, defaultPK := btcec.PrivKeyFromBytes(skBytes) + return bbn.NewBIP340PubKeyFromBTCPK(defaultPK) +} + +func defaultSlashingAddress() string { + // 20 bytes + pkHash := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + addr, err := btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.SimNetParams) + if err != nil { + panic(err) + } + return addr.EncodeAddress() +} + // ParamKeyTable the param key table for launch module func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } // DefaultParams returns a default set of parameters -// TODO: decide default parameters? func DefaultParams() Params { - return Params{} + return Params{ + JuryPk: defaultJuryPk(), + SlashingAddress: defaultSlashingAddress(), + MinSlashingTxFeeSat: 1000, + } } // ParamSetPairs get the params.ParamSet diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 21cb23aa4..99bd52c13 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -32,6 +32,10 @@ type Params struct { // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin SlashingAddress string `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` + // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified + // in Satoshi) needed for the pre-signed slashing tx + // TODO: change to satoshi per byte? + MinSlashingTxFeeSat int64 `protobuf:"varint,3,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -73,6 +77,13 @@ func (m *Params) GetSlashingAddress() string { return "" } +func (m *Params) GetMinSlashingTxFeeSat() int64 { + if m != nil { + return m.MinSlashingTxFeeSat + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "babylon.btcstaking.v1.Params") } @@ -82,22 +93,25 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 240 bytes of a gzipped FileDescriptorProto + // 282 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, - 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0xba, 0x18, 0xb9, 0xd8, 0x02, 0xc0, 0xba, 0x85, 0xfc, 0xb9, - 0xd8, 0xb3, 0x4a, 0x8b, 0x2a, 0xe3, 0x0b, 0xb2, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x9c, 0xcc, - 0x6e, 0xdd, 0x93, 0x37, 0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, - 0x9a, 0x9b, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x97, 0x54, 0x16, 0xa4, 0x16, 0xeb, 0x39, - 0x79, 0x06, 0x18, 0x9b, 0x18, 0x04, 0x94, 0x26, 0x79, 0xa7, 0x56, 0x06, 0xb1, 0x81, 0x8c, 0x09, - 0xc8, 0x16, 0xd2, 0xe4, 0x12, 0x28, 0xce, 0x49, 0x2c, 0xce, 0xc8, 0xcc, 0x4b, 0x8f, 0x4f, 0x4c, - 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x96, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0c, 0xe2, 0x87, 0x89, 0x3b, - 0x42, 0x84, 0xad, 0x58, 0x66, 0x2c, 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, - 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, - 0xc6, 0x63, 0x39, 0x86, 0x28, 0x82, 0xce, 0xa8, 0x40, 0x0e, 0x11, 0xb0, 0x9b, 0x92, 0xd8, 0xc0, - 0x3e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x1b, 0x71, 0xf7, 0xdb, 0x34, 0x01, 0x00, 0x00, + 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0x0e, 0x30, 0x72, 0xb1, 0x05, 0x80, 0x75, 0x0b, 0xf9, 0x73, + 0xb1, 0x67, 0x95, 0x16, 0x55, 0xc6, 0x17, 0x64, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x38, 0x99, + 0xdd, 0xba, 0x27, 0x6f, 0x94, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, + 0x35, 0x37, 0x39, 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x73, + 0xf2, 0x0c, 0x30, 0x36, 0x31, 0x08, 0x28, 0x4d, 0xf2, 0x4e, 0xad, 0x0c, 0x62, 0x03, 0x19, 0x13, + 0x90, 0x2d, 0xa4, 0xc9, 0x25, 0x50, 0x9c, 0x93, 0x58, 0x9c, 0x91, 0x99, 0x97, 0x1e, 0x9f, 0x98, + 0x92, 0x52, 0x94, 0x5a, 0x5c, 0x2c, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x19, 0xc4, 0x0f, 0x13, 0x77, + 0x84, 0x08, 0x0b, 0x99, 0x70, 0x89, 0xe7, 0x66, 0xe6, 0xc5, 0xc3, 0x95, 0x97, 0x54, 0xc4, 0xa7, + 0xa5, 0xa6, 0xc6, 0x17, 0x27, 0x96, 0x48, 0x30, 0x2b, 0x30, 0x6a, 0x30, 0x07, 0x09, 0xe7, 0x66, + 0xe6, 0x05, 0x43, 0x65, 0x43, 0x2a, 0xdc, 0x52, 0x53, 0x83, 0x13, 0x4b, 0xac, 0x58, 0x66, 0x2c, + 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, + 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x82, + 0x8e, 0xaf, 0x40, 0x0e, 0x47, 0xb0, 0x4f, 0x92, 0xd8, 0xc0, 0xe1, 0x62, 0x0c, 0x08, 0x00, 0x00, + 0xff, 0xff, 0x7e, 0x83, 0xd9, 0x99, 0x6a, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -120,6 +134,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MinSlashingTxFeeSat != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinSlashingTxFeeSat)) + i-- + dAtA[i] = 0x18 + } if len(m.SlashingAddress) > 0 { i -= len(m.SlashingAddress) copy(dAtA[i:], m.SlashingAddress) @@ -167,6 +186,9 @@ func (m *Params) Size() (n int) { if l > 0 { n += 1 + l + sovParams(uint64(l)) } + if m.MinSlashingTxFeeSat != 0 { + n += 1 + sovParams(uint64(m.MinSlashingTxFeeSat)) + } return n } @@ -272,6 +294,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { } m.SlashingAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinSlashingTxFeeSat", wireType) + } + m.MinSlashingTxFeeSat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinSlashingTxFeeSat |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 316750c5c..b75d9282a 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -535,7 +535,6 @@ type MsgClient interface { CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // AddJurySig handles a signature from jury AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) - // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -592,7 +591,6 @@ type MsgServer interface { CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // AddJurySig handles a signature from jury AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) - // TODO: handle validator sig (slashed case) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index ff6a1ebe7..73df364a5 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -7,6 +7,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/crypto/eots" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -45,19 +46,19 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // ensure the BTC validator has voting power at this height valPK := req.ValBtcPk if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { - return nil, fmt.Errorf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) + return nil, types.ErrInvalidFinalitySig.Wrapf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) } // ensure the BTC validator has not casted the same vote yet existingSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) if err == nil && existingSig.Equals(req.FinalitySig) { - return nil, fmt.Errorf("the BTC validator %v has casted the same vote before", valPK.MustMarshal()) + return nil, types.ErrDuplicatedFinalitySig } // ensure the BTC validator has committed public randomness pubRand, err := ms.GetPubRand(ctx, valPK, req.BlockHeight) if err != nil { - return nil, err + return nil, types.ErrPubRandNotFound } // verify EOTS signature w.r.t. public randomness @@ -66,7 +67,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal return nil, err } if err := eots.Verify(valBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar()); err != nil { - return nil, err + return nil, types.ErrInvalidFinalitySig.Wrapf("the EOTS signature is invalid: %v", err) } // verify whether the voted block is a fork or not @@ -96,7 +97,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { - return nil, fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err) + panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) } } @@ -127,7 +128,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { - return nil, fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err) + panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) } } @@ -142,13 +143,13 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi minPubRand := ms.GetParams(ctx).MinPubRand givenPubRand := len(req.PubRandList) if uint64(givenPubRand) < minPubRand { - return nil, fmt.Errorf("the request contains too few public randomness (required minimum: %d, actual: %d)", minPubRand, givenPubRand) + return nil, types.ErrTooFewPubRand.Wrapf("required minimum: %d, actual: %d", minPubRand, givenPubRand) } // ensure the BTC validator is registered valBTCPKBytes := req.ValBtcPk.MustMarshal() if !ms.BTCStakingKeeper.HasBTCValidator(ctx, valBTCPKBytes) { - return nil, fmt.Errorf("the validator with BTC PK %v is not registered", valBTCPKBytes) + return nil, bstypes.ErrBTCValNotFound.Wrapf("the validator with BTC PK %v is not registered", valBTCPKBytes) } // this BTC validator has not commit any public randomness, @@ -164,7 +165,7 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi return nil, err } if height >= req.StartHeight { - return nil, fmt.Errorf("the start height (%d) has overlap with the height of the highest public randomness (%d)", req.StartHeight, height) + return nil, types.ErrInvalidPubRand.Wrapf("the start height (%d) has overlap with the height of the highest public randomness (%d)", req.StartHeight, height) } // all good, commit the given public randomness list diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 21769b2c7..b27dfd2cb 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -6,11 +6,15 @@ import ( // x/finality module sentinel errors var ( - ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") - ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") - ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") - ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") - ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") - ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") - ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1106, "evidence is not found") + ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") + ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") + ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") + ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") + ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") + ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") + ErrTooFewPubRand = errorsmod.Register(ModuleName, 1106, "the request contains too few public randomness") + ErrInvalidPubRand = errorsmod.Register(ModuleName, 1107, "the public randomness list is invalid") + ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1108, "evidence is not found") + ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1109, "finality signature is not valid") + ErrDuplicatedFinalitySig = errorsmod.Register(ModuleName, 1110, "the finality signature has been casted before") ) From 46d558e1a0d9759aed32c549d677afff77dc76dc Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 17 Jul 2023 13:59:21 +0200 Subject: [PATCH 030/202] Bump comet bft (#399) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b47207dd9..2071f0c10 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ module github.com/babylonchain/babylon require ( github.com/CosmWasm/wasmd v0.40.0 github.com/btcsuite/btcd v0.23.4 - github.com/cometbft/cometbft v0.37.1 + github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.7.0 github.com/cosmos/cosmos-sdk v0.47.3 github.com/golang/protobuf v1.5.3 diff --git a/go.sum b/go.sum index 712541fbf..d5bb8c82a 100644 --- a/go.sum +++ b/go.sum @@ -336,8 +336,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= -github.com/cometbft/cometbft v0.37.1 h1:KLxkQTK2hICXYq21U2hn1W5hOVYUdQgDQ1uB+90xPIg= -github.com/cometbft/cometbft v0.37.1/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= +github.com/cometbft/cometbft v0.37.2 h1:XB0yyHGT0lwmJlFmM4+rsRnczPlHoAKFX6K8Zgc2/Jc= +github.com/cometbft/cometbft v0.37.2/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= From 433f8fc5ec580edc52a588ac7ce72451fc9d8d5d Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:46:07 -0400 Subject: [PATCH 031/202] feat: fuzz tests and cli queries for btcstaking (#24) * fix prev cmt and more tests * fix str * fix tests * more tests * setup query params * fix dels proto * btc dels query * refactor * fuzz for dels * fix proto * fix tests * fix tests * fix tests * fix comments * remove checks * add cli queries * fix str * fix str * fix * remove page checks * fix str --- proto/babylon/btcstaking/v1/query.proto | 31 +- test/e2e/btc_staking_e2e_test.go | 7 +- .../configurer/chain/queries_btcstaking.go | 16 +- x/btcstaking/client/cli/query.go | 105 ++++++ x/btcstaking/keeper/grpc_query.go | 46 +-- x/btcstaking/keeper/grpc_query_test.go | 258 ++++++++++++++ x/btcstaking/keeper/voting_power_table.go | 6 +- x/btcstaking/types/query.pb.go | 314 ++++++++---------- x/btcstaking/types/query.pb.gw.go | 50 +-- 9 files changed, 560 insertions(+), 273 deletions(-) create mode 100644 x/btcstaking/keeper/grpc_query_test.go diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index a22eee54d..c06e199b1 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -26,10 +26,9 @@ service Query { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{height}"; } - // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. - // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. - rpc BTCValidatorDelegationsAtHeight(QueryBTCValidatorDelegationsAtHeightRequest) returns (QueryBTCValidatorDelegationsAtHeightResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/delegations/{height}"; + // BTCValidatorDelegations queries all btc delegations of the given btc validator + rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; } } @@ -79,25 +78,23 @@ message QueryBTCValidatorsAtHeightResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } -// QueryBTCValidatorDelegationsAtHeightRequest is the request type for the -// Query/BTCValidatorDelegationsAtHeight RPC method. -message QueryBTCValidatorDelegationsAtHeightRequest { - // btc_pk is the Bitcoin secp256k1 PK of this BTC Validator +// QueryBTCValidatorDelegationsRequest is the request type for the +// Query/BTCValidatorDelegations RPC method. +message QueryBTCValidatorDelegationsRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - - // height defines at which Babylon height to query the btc delegations info. - uint64 height = 2; + string val_btc_pk_hex = 1; // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 3; + cosmos.base.query.v1beta1.PageRequest pagination = 2; } -// QueryBTCValidatorDelegationsAtHeightResponse is the response type for the -// Query/BTCValidatorDelegationsAtHeight RPC method. -message QueryBTCValidatorDelegationsAtHeightResponse { +// QueryBTCValidatorDelegationsResponse is the response type for the +// Query/BTCValidatorDelegations RPC method. +message QueryBTCValidatorDelegationsResponse { // btc_validators contains all the queried btc delegations. - repeated BTCDelegationWithMeta btc_delegations = 1; + repeated BTCDelegation btc_delegations = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index e60b656ba..24a50db05 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -142,12 +142,7 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { nonValidatorNode.WaitForNextBlock() // query the existence of BTC delegation and assert equivalence - curHeight, err := nonValidatorNode.QueryCurrentHeight() - s.NoError(err) - actualDels := nonValidatorNode.QueryBTCValidatorDelegationsAtHeight(btcVal.BtcPk, uint64(curHeight)) + actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr()) s.Len(actualDels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], actualDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) - stakingInfo, err := stakingTx.GetStakingOutputInfo(&chaincfg.SimNetParams) - s.NoError(err) - s.Equal(uint64(stakingInfo.StakingAmount), actualDels[0].VotingPower) } diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 0a7644713..f67902561 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -1,14 +1,11 @@ package chain import ( - "encoding/base64" "fmt" - "net/url" - "github.com/babylonchain/babylon/test/e2e/util" - bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/stretchr/testify/require" + "net/url" ) func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { @@ -45,15 +42,12 @@ func (n *NodeConfig) QueryBTCValidatorsAtHeight(height uint64) []*bstypes.BTCVal return resp.BtcValidators } -func (n *NodeConfig) QueryBTCValidatorDelegationsAtHeight(valBTCPK *bbn.BIP340PubKey, height uint64) []*bstypes.BTCDelegationWithMeta { - path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/delegations/%d", height) - valBTCPKStr := base64.URLEncoding.EncodeToString(valBTCPK.MustMarshal()) - bz, err := n.QueryGRPCGateway(path, url.Values{ - "val_btc_pk": []string{valBTCPKStr}, - }) +func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegation { + path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) + bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryBTCValidatorDelegationsAtHeightResponse + var resp bstypes.QueryBTCValidatorDelegationsResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 754ae7bbc..c90ab88c4 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + "github.com/cosmos/cosmos-sdk/client/flags" + "strconv" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/client" @@ -20,6 +22,109 @@ func GetQueryCmd(queryRoute string) *cobra.Command { } cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand(CmdBTCValidators()) + cmd.AddCommand(CmdBTCValidatorsAtHeight()) + cmd.AddCommand(CmdBTCValidatorDelegations()) + + return cmd +} + +func CmdBTCValidators() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-validators", + Short: "retrieve all btc validators", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.BTCValidators(cmd.Context(), &types.QueryBTCValidatorsRequest{ + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdBTCValidatorsAtHeight() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-validators-at-height [height]", + Short: "retrieve all btc validators at a given babylon height", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.BTCValidatorsAtHeight(cmd.Context(), &types.QueryBTCValidatorsAtHeightRequest{ + Height: height, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdBTCValidatorDelegations() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-validator-delegations [btc_val_pk_hex]", + Short: "retrieve all delegations under a given btc validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.BTCValidatorDelegations(cmd.Context(), &types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) return cmd } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index f013746b6..c56910e88 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -2,8 +2,10 @@ package keeper import ( "context" - + errorsmod "cosmossdk.io/errors" + bbn "github.com/babylonchain/babylon/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -69,50 +71,34 @@ func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCVa return &types.QueryBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil } -func (k Keeper) BTCValidatorDelegationsAtHeight(ctx context.Context, req *types.QueryBTCValidatorDelegationsAtHeightRequest) (*types.QueryBTCValidatorDelegationsAtHeightResponse, error) { +func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTCValidatorDelegationsRequest) (*types.QueryBTCValidatorDelegationsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - // convert pk to bytes - valPkBytes, err := req.ValBtcPk.Marshal() - if err != nil { - return nil, err + if len(req.ValBtcPkHex) == 0 { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidRequest, "validator BTC public key cannot be empty") } - sdkCtx := sdk.UnwrapSDKContext(ctx) - btcHeight, err := k.GetBTCHeightAtBabylonHeight(sdkCtx, req.Height) + valPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) if err != nil { return nil, err } - // get value of w - wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout - store := k.btcDelegationStore(sdkCtx, valPkBytes) - - var btcDelsWithMeta []*types.BTCDelegationWithMeta - pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { - btcDel, err := k.GetBTCDelegation(sdkCtx, valPkBytes, key) - if err != nil { - return err - } - - delPower := btcDel.VotingPower(btcHeight, wValue) - if delPower > 0 { - btcDelMeta := types.BTCDelegationWithMeta{ - BtcPk: btcDel.BtcPk, - StartHeight: btcDel.StartHeight, - EndHeight: btcDel.EndHeight, - VotingPower: delPower, - } - btcDelsWithMeta = append(btcDelsWithMeta, &btcDelMeta) - } + sdkCtx := sdk.UnwrapSDKContext(ctx) + btcDelStore := k.btcDelegationStore(sdkCtx, valPK.MustMarshal()) + var btcDels []*types.BTCDelegation + pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { + var btcDelegation types.BTCDelegation + k.cdc.MustUnmarshal(value, &btcDelegation) + btcDels = append(btcDels, &btcDelegation) return nil }) if err != nil { return nil, err } - return &types.QueryBTCValidatorDelegationsAtHeightResponse{BtcDelegations: btcDelsWithMeta, Pagination: pageRes}, nil + return &types.QueryBTCValidatorDelegationsResponse{BtcDelegations: btcDels, Pagination: pageRes}, nil } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go new file mode 100644 index 000000000..1618c6ca1 --- /dev/null +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -0,0 +1,258 @@ +package keeper_test + +import ( + "fmt" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/stretchr/testify/require" + "math/rand" + + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/types/query" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func FuzzBTCValidators(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // Generate random btc validators and add them to kv store + btcValsMap := make(map[string]*types.BTCValidator) + for i := 0; i < int(datagen.RandomInt(r, 10)+1); i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + + keeper.SetBTCValidator(ctx, btcVal) + btcValsMap[btcVal.BtcPk.ToHexStr()] = btcVal + } + numOfBTCValsInStore := len(btcValsMap) + + // Test nil request + resp, err := keeper.BTCValidators(ctx, nil) + if resp != nil { + t.Errorf("Nil input led to a non-nil response") + } + if err == nil { + t.Errorf("Nil input led to a nil error") + } + + // Generate a page request with a limit and a nil key + limit := datagen.RandomInt(r, numOfBTCValsInStore) + 1 + pagination := constructRequestWithLimit(r, limit) + // Generate the initial query + req := types.QueryBTCValidatorsRequest{Pagination: pagination} + // Construct a mapping from the btc vals found to a boolean value + // Will be used later to evaluate whether all the btc vals were returned + btcValsFound := make(map[string]bool, 0) + + for i := uint64(0); i < uint64(numOfBTCValsInStore); i += limit { + resp, err = keeper.BTCValidators(ctx, &req) + if err != nil { + t.Errorf("Valid request led to an error %s", err) + } + if resp == nil { + t.Fatalf("Valid request led to a nil response") + } + + for _, val := range resp.BtcValidators { + // Check if the pk exists in the map + if _, ok := btcValsMap[val.BtcPk.ToHexStr()]; !ok { + t.Fatalf("rpc returned a val that was not created") + } + btcValsFound[val.BtcPk.ToHexStr()] = true + } + + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorsRequest{Pagination: pagination} + } + + if len(btcValsFound) != len(btcValsMap) { + t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcValsFound), len(btcValsMap)) + } + }) +} + +func FuzzBTCValidatorsAtHeight(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + + // Generate a random batch of validators + var btcVals []*types.BTCValidator + numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 1 + numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) + for i := uint64(0); i < numBTCVals; i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + btcVals = append(btcVals, btcVal) + } + + // For numBTCValsWithVotingPower validators, generate a random number of BTC delegations + numBTCDels := datagen.RandomInt(r, 10) + 1 + babylonHeight := datagen.RandomInt(r, 10) + 1 + btcValsWithVotingPowerMap := make(map[string]*types.BTCValidator) + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + valBTCPK := btcVals[i].BtcPk + btcValsWithVotingPowerMap[valBTCPK.ToHexStr()] = btcVals[i] + + var totalVotingPower uint64 + for j := uint64(0); j < numBTCDels; j++ { + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, 1, 1000, 1) // timelock period: 1-1000 + require.NoError(t, err) + keeper.SetBTCDelegation(ctx, btcDel) + totalVotingPower += btcDel.TotalSat + } + + keeper.SetVotingPower(ctx, valBTCPK.MustMarshal(), babylonHeight, totalVotingPower) + } + + // Test nil request + resp, err := keeper.BTCValidatorsAtHeight(ctx, nil) + if resp != nil { + t.Errorf("Nil input led to a non-nil response") + } + if err == nil { + t.Errorf("Nil input led to a nil error") + } + + // Generate a page request with a limit and a nil key + limit := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) + 1 + pagination := constructRequestWithLimit(r, limit) + // Generate the initial query + req := types.QueryBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} + // Construct a mapping from the btc vals found to a boolean value + // Will be used later to evaluate whether all the btc vals were returned + btcValsFound := make(map[string]bool, 0) + + votingTable := keeper.GetVotingPowerTable(ctx, babylonHeight) + fmt.Println(votingTable) + + for i := uint64(0); i < numBTCValsWithVotingPower; i += limit { + resp, err = keeper.BTCValidatorsAtHeight(ctx, &req) + if err != nil { + t.Errorf("Valid request led to an error %s", err) + } + if resp == nil { + t.Fatalf("Valid request led to a nil response") + } + + for _, val := range resp.BtcValidators { + // Check if the pk exists in the map + if _, ok := btcValsWithVotingPowerMap[val.BtcPk.ToHexStr()]; !ok { + t.Fatalf("rpc returned a val that was not created") + } + btcValsFound[val.BtcPk.ToHexStr()] = true + } + + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} + } + + if len(btcValsFound) != len(btcValsWithVotingPowerMap) { + t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcValsFound), len(btcValsWithVotingPowerMap)) + } + }) +} + +func FuzzBTCValidatorDelegations(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + + // Generate a btc validator + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + + // Generate a random number of BTC delegations under this validator + numBTCDels := datagen.RandomInt(r, 10) + 1 + btcDelsMap := make(map[string]*types.BTCDelegation) + for j := uint64(0); j < numBTCDels; j++ { + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, 1, 1000, 1) // timelock period: 1-1000 + require.NoError(t, err) + keeper.SetBTCDelegation(ctx, btcDel) + btcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel + } + + // Test nil request + resp, err := keeper.BTCValidatorDelegations(ctx, nil) + if resp != nil { + t.Errorf("Nil input led to a non-nil response") + } + if err == nil { + t.Errorf("Nil input led to a nil error") + } + + // Generate a page request with a limit and a nil key + limit := datagen.RandomInt(r, int(numBTCDels)) + 1 + pagination := constructRequestWithLimit(r, limit) + // Generate the initial query + req := types.QueryBTCValidatorDelegationsRequest{ValBtcPkHex: btcVal.BtcPk.ToHexStr(), Pagination: pagination} + // Construct a mapping from the btc vals found to a boolean value + // Will be used later to evaluate whether all the btc vals were returned + btcDelsFound := make(map[string]bool, 0) + + for i := uint64(0); i < numBTCDels; i += limit { + resp, err = keeper.BTCValidatorDelegations(ctx, &req) + if err != nil { + t.Errorf("Valid request led to an error %s", err) + } + if resp == nil { + t.Fatalf("Valid request led to a nil response") + } + + for _, btcDel := range resp.BtcDelegations { + require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) + + // Check if the pk exists in the map + if _, ok := btcDelsMap[btcDel.BtcPk.ToHexStr()]; !ok { + t.Fatalf("rpc returned a val that was not created") + } + btcDelsFound[btcDel.BtcPk.ToHexStr()] = true + } + + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorDelegationsRequest{ValBtcPkHex: btcVal.BtcPk.ToHexStr(), Pagination: pagination} + } + + if len(btcDelsFound) != len(btcDelsMap) { + t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcDelsFound), len(btcDelsMap)) + } + }) +} + +// Constructors for PageRequest objects +func constructRequestWithKeyAndLimit(r *rand.Rand, key []byte, limit uint64) *query.PageRequest { + // If limit is 0, set one randomly + if limit == 0 { + limit = uint64(r.Int63() + 1) // Use Int63 instead of Uint64 to avoid overflows + } + return &query.PageRequest{ + Key: key, + Offset: 0, // only offset or key is set + Limit: limit, + CountTotal: false, // only used when offset is used + Reverse: false, + } +} + +func constructRequestWithLimit(r *rand.Rand, limit uint64) *query.PageRequest { + return constructRequestWithKeyAndLimit(r, nil, limit) +} diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 7d8739531..da79ff718 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -39,13 +39,13 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { btcDelIter.Close() if valPower > 0 { - k.setVotingPower(ctx, valBTCPK, babylonTipHeight, valPower) + k.SetVotingPower(ctx, valBTCPK, babylonTipHeight, valPower) } } } -// setVotingPower sets the voting power of a given BTC validator at a given Babylon height -func (k Keeper) setVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64, power uint64) { +// SetVotingPower sets the voting power of a given BTC validator at a given Babylon height +func (k Keeper) SetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64, power uint64) { store := k.votingPowerStore(ctx, height) store.Set(valBTCPK, sdk.Uint64ToBigEndian(power)) } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 563dc2d0e..1869fa28a 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -6,7 +6,6 @@ package types import ( context "context" fmt "fmt" - github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" query "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" @@ -329,34 +328,29 @@ func (m *QueryBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse return nil } -// QueryBTCValidatorDelegationsAtHeightRequest is the request type for the -// Query/BTCValidatorDelegationsAtHeight RPC method. -type QueryBTCValidatorDelegationsAtHeightRequest struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC Validator +// QueryBTCValidatorDelegationsRequest is the request type for the +// Query/BTCValidatorDelegations RPC method. +type QueryBTCValidatorDelegationsRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` - // height defines at which Babylon height to query the btc delegations info. - Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` // pagination defines an optional pagination for the request. - Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) Reset() { - *m = QueryBTCValidatorDelegationsAtHeightRequest{} -} -func (m *QueryBTCValidatorDelegationsAtHeightRequest) String() string { - return proto.CompactTextString(m) -} -func (*QueryBTCValidatorDelegationsAtHeightRequest) ProtoMessage() {} -func (*QueryBTCValidatorDelegationsAtHeightRequest) Descriptor() ([]byte, []int) { +func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCValidatorDelegationsRequest{} } +func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} +func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{6} } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryBTCValidatorDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryBTCValidatorDelegationsRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -366,57 +360,53 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Marshal(b []byte, dete return b[:n], nil } } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.Merge(m, src) +func (m *QueryBTCValidatorDelegationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorDelegationsRequest.Merge(m, src) } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_Size() int { +func (m *QueryBTCValidatorDelegationsRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest.DiscardUnknown(m) +func (m *QueryBTCValidatorDelegationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorDelegationsRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryBTCValidatorDelegationsRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsAtHeightRequest) GetHeight() uint64 { +func (m *QueryBTCValidatorDelegationsRequest) GetValBtcPkHex() string { if m != nil { - return m.Height + return m.ValBtcPkHex } - return 0 + return "" } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) GetPagination() *query.PageRequest { +func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination } return nil } -// QueryBTCValidatorDelegationsAtHeightResponse is the response type for the -// Query/BTCValidatorDelegationsAtHeight RPC method. -type QueryBTCValidatorDelegationsAtHeightResponse struct { +// QueryBTCValidatorDelegationsResponse is the response type for the +// Query/BTCValidatorDelegations RPC method. +type QueryBTCValidatorDelegationsResponse struct { // btc_validators contains all the queried btc delegations. - BtcDelegations []*BTCDelegationWithMeta `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) Reset() { - *m = QueryBTCValidatorDelegationsAtHeightResponse{} -} -func (m *QueryBTCValidatorDelegationsAtHeightResponse) String() string { - return proto.CompactTextString(m) -} -func (*QueryBTCValidatorDelegationsAtHeightResponse) ProtoMessage() {} -func (*QueryBTCValidatorDelegationsAtHeightResponse) Descriptor() ([]byte, []int) { +func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCValidatorDelegationsResponse{} } +func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} +func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{7} } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryBTCValidatorDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryBTCValidatorDelegationsResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -426,26 +416,26 @@ func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Marshal(b []byte, det return b[:n], nil } } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.Merge(m, src) +func (m *QueryBTCValidatorDelegationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorDelegationsResponse.Merge(m, src) } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_Size() int { +func (m *QueryBTCValidatorDelegationsResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse.DiscardUnknown(m) +func (m *QueryBTCValidatorDelegationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorDelegationsResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorDelegationsAtHeightResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryBTCValidatorDelegationsResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsAtHeightResponse) GetBtcDelegations() []*BTCDelegationWithMeta { +func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegation { if m != nil { return m.BtcDelegations } return nil } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) GetPagination() *query.PageResponse { +func (m *QueryBTCValidatorDelegationsResponse) GetPagination() *query.PageResponse { if m != nil { return m.Pagination } @@ -459,57 +449,56 @@ func init() { proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") proto.RegisterType((*QueryBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightRequest") proto.RegisterType((*QueryBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightResponse") - proto.RegisterType((*QueryBTCValidatorDelegationsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsAtHeightRequest") - proto.RegisterType((*QueryBTCValidatorDelegationsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsAtHeightResponse") + proto.RegisterType((*QueryBTCValidatorDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsRequest") + proto.RegisterType((*QueryBTCValidatorDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 687 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xdd, 0x4e, 0xd4, 0x4e, - 0x14, 0xdf, 0x59, 0xf8, 0x6f, 0xfe, 0x19, 0x44, 0x93, 0x11, 0x0c, 0x36, 0xba, 0x40, 0x4d, 0x10, - 0x81, 0x74, 0xd8, 0x42, 0x0c, 0x42, 0x4c, 0xb4, 0x18, 0xbf, 0x4d, 0xd6, 0x06, 0x35, 0xf1, 0x86, - 0x4c, 0xcb, 0xa4, 0xdb, 0x50, 0x3a, 0x65, 0x3b, 0xdb, 0xb8, 0x31, 0xde, 0xe8, 0x0b, 0x98, 0xf0, - 0x22, 0xfa, 0x08, 0xde, 0x71, 0x65, 0x30, 0xde, 0x18, 0x2f, 0x88, 0x61, 0xbd, 0xf4, 0x21, 0x4c, - 0xa7, 0xb3, 0x6c, 0x17, 0xba, 0x50, 0x50, 0xef, 0x68, 0xfb, 0x3b, 0xe7, 0xfc, 0x3e, 0x38, 0x67, - 0xe1, 0xb8, 0x45, 0xac, 0xa6, 0xc7, 0x7c, 0x6c, 0x71, 0x3b, 0xe4, 0x64, 0xdd, 0xf5, 0x1d, 0x1c, - 0x55, 0xf0, 0x66, 0x83, 0xd6, 0x9b, 0x5a, 0x50, 0x67, 0x9c, 0xa1, 0x61, 0x09, 0xd1, 0x3a, 0x10, - 0x2d, 0xaa, 0x28, 0x43, 0x0e, 0x73, 0x98, 0x40, 0xe0, 0xf8, 0xaf, 0x04, 0xac, 0x5c, 0x72, 0x18, - 0x73, 0x3c, 0x8a, 0x49, 0xe0, 0x62, 0xe2, 0xfb, 0x8c, 0x13, 0xee, 0x32, 0x3f, 0x94, 0x5f, 0xa7, - 0x6c, 0x16, 0x6e, 0xb0, 0x10, 0x5b, 0x24, 0xa4, 0xc9, 0x0c, 0x1c, 0x55, 0x2c, 0xca, 0x49, 0x05, - 0x07, 0xc4, 0x71, 0x7d, 0x01, 0x96, 0x58, 0x35, 0x9b, 0x59, 0x40, 0xea, 0x64, 0xa3, 0xdd, 0x6f, - 0x22, 0x1b, 0x93, 0x22, 0x2a, 0x70, 0xea, 0x10, 0x44, 0x4f, 0xe3, 0x69, 0x55, 0x51, 0x6c, 0xd2, - 0xcd, 0x06, 0x0d, 0xb9, 0x6a, 0xc2, 0xf3, 0x5d, 0x6f, 0xc3, 0x80, 0xf9, 0x21, 0x45, 0x4b, 0xb0, - 0x94, 0x0c, 0x19, 0x01, 0x63, 0x60, 0x72, 0x40, 0xbf, 0xac, 0x65, 0x1a, 0xa0, 0x25, 0x65, 0x46, - 0xff, 0xf6, 0xee, 0x68, 0xc1, 0x94, 0x25, 0xaa, 0x0d, 0x2f, 0x8a, 0x9e, 0xc6, 0xca, 0xf2, 0x73, - 0xe2, 0xb9, 0x6b, 0x84, 0xb3, 0x7a, 0x7b, 0x20, 0xba, 0x0b, 0x61, 0x47, 0xa6, 0xec, 0x3e, 0xa1, - 0x25, 0x9e, 0x68, 0xb1, 0x27, 0x5a, 0xe2, 0xbb, 0xf4, 0x44, 0xab, 0x12, 0x87, 0xca, 0x5a, 0x33, - 0x55, 0xa9, 0x7e, 0x04, 0x50, 0xc9, 0x9a, 0x22, 0x05, 0x3c, 0x84, 0x67, 0x2d, 0x6e, 0xaf, 0x46, - 0xfb, 0x5f, 0x46, 0xc0, 0x58, 0xdf, 0xe4, 0x80, 0x7e, 0xa5, 0x87, 0x90, 0x74, 0x17, 0x73, 0xd0, - 0xe2, 0x76, 0xa7, 0x27, 0xba, 0xd7, 0x45, 0xb9, 0x28, 0x28, 0x5f, 0x3d, 0x96, 0x72, 0x42, 0xa4, - 0x8b, 0xf3, 0x3b, 0x00, 0xc7, 0x0f, 0x73, 0xbe, 0xcd, 0xef, 0x53, 0xd7, 0xa9, 0xf1, 0xb6, 0x43, - 0x17, 0x60, 0xa9, 0x26, 0x5e, 0x08, 0x77, 0xfa, 0x4d, 0xf9, 0x74, 0xc0, 0xb9, 0xe2, 0xa9, 0x9d, - 0xfb, 0x04, 0xa0, 0x7a, 0x14, 0x0b, 0xe9, 0xa0, 0xd9, 0xc3, 0xc1, 0xe9, 0x1c, 0x0e, 0xbe, 0x70, - 0x79, 0xed, 0x09, 0xe5, 0xe4, 0x9f, 0x39, 0xd9, 0x02, 0x70, 0xfa, 0x90, 0x86, 0x3b, 0xd4, 0xa3, - 0x4e, 0xb2, 0x6b, 0x07, 0x3d, 0x5d, 0x81, 0x30, 0x22, 0xde, 0x6a, 0x2c, 0x28, 0x58, 0x17, 0xbe, - 0x9e, 0x31, 0xae, 0x7f, 0xdf, 0x1d, 0xd5, 0x1d, 0x97, 0xd7, 0x1a, 0x96, 0x66, 0xb3, 0x0d, 0x2c, - 0x65, 0xd9, 0x35, 0xe2, 0xfa, 0xed, 0x07, 0xcc, 0x9b, 0x01, 0x0d, 0x35, 0xe3, 0x41, 0x75, 0x6e, - 0x7e, 0xb6, 0xda, 0xb0, 0x1e, 0xd1, 0xa6, 0xf9, 0x7f, 0x44, 0x3c, 0x83, 0xdb, 0xd5, 0xf5, 0x54, - 0x52, 0xc5, 0x23, 0x92, 0xea, 0x3b, 0x75, 0x52, 0x9f, 0x01, 0x9c, 0xc9, 0xa7, 0x52, 0x66, 0xf6, - 0x0c, 0x9e, 0x8b, 0x25, 0xae, 0x75, 0x20, 0x32, 0xb4, 0x99, 0xde, 0xa1, 0x75, 0xfa, 0xed, 0xa7, - 0x16, 0x07, 0x9f, 0x1a, 0xf3, 0xd7, 0x62, 0xd3, 0xb7, 0x4a, 0xf0, 0x3f, 0x21, 0x08, 0x6d, 0x01, - 0x58, 0x4a, 0x8e, 0x07, 0xba, 0xd6, 0x83, 0xdb, 0xe1, 0x6b, 0xa5, 0x4c, 0xe5, 0x81, 0x26, 0x73, - 0x55, 0xfd, 0xed, 0xd7, 0x9f, 0x5b, 0xc5, 0x19, 0x34, 0x95, 0x1d, 0x6c, 0xd6, 0x45, 0x45, 0x1f, - 0x00, 0x1c, 0xec, 0xda, 0x0a, 0x34, 0x7b, 0xd4, 0xc4, 0xac, 0x03, 0xa7, 0x54, 0x4e, 0x50, 0x21, - 0xa9, 0x2e, 0x0a, 0xaa, 0xf3, 0x48, 0xcf, 0x43, 0xb5, 0x7b, 0x29, 0xd1, 0x17, 0x00, 0x87, 0x33, - 0x17, 0x19, 0x2d, 0xe4, 0x26, 0x72, 0x60, 0x5b, 0x94, 0x1b, 0xa7, 0xa8, 0x94, 0x52, 0x96, 0x85, - 0x94, 0x9b, 0x68, 0xe9, 0xe4, 0x52, 0xf0, 0xeb, 0x64, 0x7d, 0xde, 0xa0, 0x5f, 0x00, 0x8e, 0x1e, - 0xf3, 0x2f, 0x8f, 0x8c, 0xbc, 0x1c, 0x7b, 0x5f, 0x05, 0x65, 0xf9, 0x8f, 0x7a, 0x48, 0xc5, 0xb7, - 0x84, 0xe2, 0x45, 0xb4, 0x90, 0x47, 0x71, 0x6a, 0x33, 0xf7, 0xe5, 0x1a, 0x8f, 0xb7, 0xf7, 0xca, - 0x60, 0x67, 0xaf, 0x0c, 0x7e, 0xec, 0x95, 0xc1, 0xfb, 0x56, 0xb9, 0xb0, 0xd3, 0x2a, 0x17, 0xbe, - 0xb5, 0xca, 0x85, 0x97, 0xc7, 0x9e, 0xa7, 0x57, 0xe9, 0xfe, 0xe2, 0x56, 0x59, 0x25, 0xf1, 0x73, - 0x3f, 0xf7, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x45, 0x9f, 0xcd, 0x5b, 0xd6, 0x08, 0x00, 0x00, + // 662 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0x4f, 0x4f, 0xd4, 0x40, + 0x18, 0xc6, 0x77, 0x10, 0x37, 0x71, 0x08, 0x98, 0x8c, 0xa0, 0xd8, 0x68, 0x85, 0x42, 0x10, 0xd1, + 0x74, 0xdc, 0xea, 0x41, 0x21, 0x1e, 0x5c, 0x8c, 0x12, 0x23, 0x11, 0x1a, 0x23, 0x89, 0x17, 0x32, + 0x2d, 0x93, 0xb6, 0xa1, 0x74, 0xca, 0x76, 0x68, 0x20, 0x86, 0x8b, 0x7e, 0x01, 0x03, 0x5f, 0x44, + 0xbf, 0x80, 0x89, 0x37, 0x8e, 0x18, 0x2f, 0x9e, 0x8c, 0xd9, 0xf5, 0xe6, 0x97, 0x30, 0x9d, 0xce, + 0xba, 0x2d, 0xdb, 0x5d, 0x97, 0x0d, 0xde, 0x76, 0x67, 0xde, 0x3f, 0xcf, 0xf3, 0xdb, 0x79, 0xdf, + 0x85, 0x93, 0x16, 0xb1, 0xf6, 0x7c, 0x16, 0x60, 0x8b, 0xdb, 0x11, 0x27, 0x9b, 0x5e, 0xe0, 0xe0, + 0xb8, 0x82, 0xb7, 0x77, 0x68, 0x6d, 0x4f, 0x0f, 0x6b, 0x8c, 0x33, 0x34, 0x26, 0x43, 0xf4, 0x56, + 0x88, 0x1e, 0x57, 0x94, 0x51, 0x87, 0x39, 0x4c, 0x44, 0xe0, 0xe4, 0x53, 0x1a, 0xac, 0x5c, 0x73, + 0x18, 0x73, 0x7c, 0x8a, 0x49, 0xe8, 0x61, 0x12, 0x04, 0x8c, 0x13, 0xee, 0xb1, 0x20, 0x92, 0xb7, + 0x73, 0x36, 0x8b, 0xb6, 0x58, 0x84, 0x2d, 0x12, 0xd1, 0xb4, 0x07, 0x8e, 0x2b, 0x16, 0xe5, 0xa4, + 0x82, 0x43, 0xe2, 0x78, 0x81, 0x08, 0x96, 0xb1, 0x5a, 0xb1, 0xb2, 0x90, 0xd4, 0xc8, 0x56, 0xb3, + 0xde, 0x4c, 0x71, 0x4c, 0x46, 0xa8, 0x88, 0xd3, 0x46, 0x21, 0x5a, 0x4d, 0xba, 0xad, 0x88, 0x64, + 0x93, 0x6e, 0xef, 0xd0, 0x88, 0x6b, 0x26, 0xbc, 0x94, 0x3b, 0x8d, 0x42, 0x16, 0x44, 0x14, 0x2d, + 0xc0, 0x72, 0xda, 0x64, 0x1c, 0x4c, 0x80, 0xd9, 0x21, 0xe3, 0xba, 0x5e, 0x08, 0x40, 0x4f, 0xd3, + 0xaa, 0x83, 0x47, 0x3f, 0x6e, 0x94, 0x4c, 0x99, 0xa2, 0xd9, 0xf0, 0xaa, 0xa8, 0x59, 0x7d, 0xb5, + 0xf8, 0x9a, 0xf8, 0xde, 0x06, 0xe1, 0xac, 0xd6, 0x6c, 0x88, 0x9e, 0x42, 0xd8, 0xb2, 0x29, 0xab, + 0xcf, 0xe8, 0x29, 0x13, 0x3d, 0x61, 0xa2, 0xa7, 0xdc, 0x25, 0x13, 0x7d, 0x85, 0x38, 0x54, 0xe6, + 0x9a, 0x99, 0x4c, 0xed, 0x13, 0x80, 0x4a, 0x51, 0x17, 0x69, 0xe0, 0x39, 0x1c, 0xb1, 0xb8, 0xbd, + 0x1e, 0xff, 0xbd, 0x19, 0x07, 0x13, 0xe7, 0x66, 0x87, 0x8c, 0xa9, 0x0e, 0x46, 0xb2, 0x55, 0xcc, + 0x61, 0x8b, 0xdb, 0xad, 0x9a, 0xe8, 0x59, 0x4e, 0xf2, 0x80, 0x90, 0x7c, 0xf3, 0x9f, 0x92, 0x53, + 0x21, 0x39, 0xcd, 0xef, 0x01, 0x9c, 0x6c, 0xd7, 0xfc, 0x98, 0x2f, 0x51, 0xcf, 0x71, 0x79, 0x93, + 0xd0, 0x65, 0x58, 0x76, 0xc5, 0x81, 0xa0, 0x33, 0x68, 0xca, 0x6f, 0x27, 0xc8, 0x0d, 0xf4, 0x4d, + 0xee, 0x0b, 0x80, 0x5a, 0x37, 0x15, 0x92, 0xa0, 0xd9, 0x81, 0xe0, 0xed, 0x1e, 0x08, 0xae, 0x79, + 0xdc, 0x5d, 0xa6, 0x9c, 0xfc, 0x37, 0x92, 0x07, 0x00, 0x4e, 0xb5, 0x79, 0x78, 0x42, 0x7d, 0xea, + 0xa4, 0xb3, 0xd6, 0x64, 0x39, 0x05, 0x47, 0x62, 0xe2, 0xaf, 0x27, 0x46, 0xc2, 0xcd, 0x75, 0x97, + 0xee, 0x0a, 0xa6, 0x17, 0xcc, 0xa1, 0x98, 0xf8, 0x55, 0x6e, 0xaf, 0x6c, 0x2e, 0xd1, 0xdd, 0x33, + 0x03, 0xfb, 0x19, 0xc0, 0xe9, 0xee, 0xa2, 0x24, 0xda, 0x65, 0x78, 0x31, 0x51, 0xb4, 0xd1, 0xba, + 0x92, 0x6c, 0xa7, 0x3b, 0xb3, 0x6d, 0xd5, 0x31, 0x93, 0xdf, 0x25, 0x53, 0xf6, 0xcc, 0xa8, 0x1a, + 0x07, 0x65, 0x78, 0x5e, 0x18, 0x40, 0x87, 0x00, 0x96, 0xd3, 0xd9, 0x46, 0xb7, 0x3a, 0x68, 0x6a, + 0x5f, 0x26, 0xca, 0x5c, 0x2f, 0xa1, 0x69, 0x5f, 0xcd, 0x78, 0xf7, 0xed, 0xd7, 0xe1, 0xc0, 0x1d, + 0x34, 0x87, 0x65, 0x8e, 0xed, 0x12, 0x2f, 0xc0, 0xdd, 0x16, 0x1e, 0xfa, 0x08, 0xe0, 0x70, 0xee, + 0xd1, 0xa2, 0xbb, 0xdd, 0x3a, 0x16, 0xed, 0x1f, 0xa5, 0x72, 0x8a, 0x0c, 0x29, 0x75, 0x5e, 0x48, + 0xbd, 0x8f, 0x8c, 0x5e, 0xa4, 0xe6, 0x67, 0x06, 0x7d, 0x05, 0x70, 0xac, 0x70, 0xce, 0xd0, 0x83, + 0x9e, 0x85, 0x9c, 0x58, 0x10, 0xca, 0xc3, 0x3e, 0x32, 0xa5, 0x95, 0x45, 0x61, 0xe5, 0x11, 0x5a, + 0x38, 0xbd, 0x15, 0xfc, 0x36, 0xdd, 0x43, 0xfb, 0xe8, 0x37, 0x80, 0x57, 0x3a, 0x3c, 0x71, 0x34, + 0xdf, 0xab, 0xb6, 0xf6, 0x61, 0x55, 0x16, 0xfa, 0xca, 0x95, 0xce, 0xd6, 0x84, 0xb3, 0x55, 0xf4, + 0xb2, 0x1f, 0x67, 0xf9, 0x1d, 0xb1, 0x8f, 0x33, 0x93, 0x59, 0x7d, 0x71, 0x54, 0x57, 0xc1, 0x71, + 0x5d, 0x05, 0x3f, 0xeb, 0x2a, 0xf8, 0xd0, 0x50, 0x4b, 0xc7, 0x0d, 0xb5, 0xf4, 0xbd, 0xa1, 0x96, + 0xde, 0x18, 0x8e, 0xc7, 0xdd, 0x1d, 0x4b, 0xb7, 0xd9, 0x56, 0x71, 0xd3, 0xdd, 0x6c, 0x5b, 0xbe, + 0x17, 0xd2, 0xc8, 0x2a, 0x8b, 0x3f, 0xe3, 0x7b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x91, + 0x8e, 0xc4, 0x74, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -530,9 +519,8 @@ type QueryClient interface { BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) - // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. - // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. - BTCValidatorDelegationsAtHeight(ctx context.Context, in *QueryBTCValidatorDelegationsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsAtHeightResponse, error) + // BTCValidatorDelegations queries all btc delegations of the given btc validator + BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) } type queryClient struct { @@ -570,9 +558,9 @@ func (c *queryClient) BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCVal return out, nil } -func (c *queryClient) BTCValidatorDelegationsAtHeight(ctx context.Context, in *QueryBTCValidatorDelegationsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsAtHeightResponse, error) { - out := new(QueryBTCValidatorDelegationsAtHeightResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorDelegationsAtHeight", in, out, opts...) +func (c *queryClient) BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) { + out := new(QueryBTCValidatorDelegationsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorDelegations", in, out, opts...) if err != nil { return nil, err } @@ -587,9 +575,8 @@ type QueryServer interface { BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. BTCValidatorsAtHeight(context.Context, *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) - // BTCValidatorDelegationsAtHeight queries btc delegations with non zero voting power of given validator at given height. - // NOTE: a BTC delegation has non-zero voting power only when it receives a jury signature AND its timelock is not expired yet. - BTCValidatorDelegationsAtHeight(context.Context, *QueryBTCValidatorDelegationsAtHeightRequest) (*QueryBTCValidatorDelegationsAtHeightResponse, error) + // BTCValidatorDelegations queries all btc delegations of the given btc validator + BTCValidatorDelegations(context.Context, *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -605,8 +592,8 @@ func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBT func (*UnimplementedQueryServer) BTCValidatorsAtHeight(ctx context.Context, req *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorsAtHeight not implemented") } -func (*UnimplementedQueryServer) BTCValidatorDelegationsAtHeight(ctx context.Context, req *QueryBTCValidatorDelegationsAtHeightRequest) (*QueryBTCValidatorDelegationsAtHeightResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegationsAtHeight not implemented") +func (*UnimplementedQueryServer) BTCValidatorDelegations(ctx context.Context, req *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegations not implemented") } func RegisterQueryServer(s grpc1.Server, srv QueryServer) { @@ -667,20 +654,20 @@ func _Query_BTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _Query_BTCValidatorDelegationsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorDelegationsAtHeightRequest) +func _Query_BTCValidatorDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorDelegationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidatorDelegationsAtHeight(ctx, in) + return srv.(QueryServer).BTCValidatorDelegations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorDelegationsAtHeight", + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorDelegations", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidatorDelegationsAtHeight(ctx, req.(*QueryBTCValidatorDelegationsAtHeightRequest)) + return srv.(QueryServer).BTCValidatorDelegations(ctx, req.(*QueryBTCValidatorDelegationsRequest)) } return interceptor(ctx, in, info, handler) } @@ -702,8 +689,8 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_BTCValidatorsAtHeight_Handler, }, { - MethodName: "BTCValidatorDelegationsAtHeight", - Handler: _Query_BTCValidatorDelegationsAtHeight_Handler, + MethodName: "BTCValidatorDelegations", + Handler: _Query_BTCValidatorDelegations_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -939,7 +926,7 @@ func (m *QueryBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -949,12 +936,12 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) Marshal() (dAtA []byte, er return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryBTCValidatorDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -969,29 +956,19 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) MarshalToSizedBuffer(dAtA i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - if m.Height != 0 { - i = encodeVarintQuery(dAtA, i, uint64(m.Height)) - i-- - dAtA[i] = 0x10 + dAtA[i] = 0x12 } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintQuery(dAtA, i, uint64(size)) - } + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1001,12 +978,12 @@ func (m *QueryBTCValidatorDelegationsAtHeightResponse) Marshal() (dAtA []byte, e return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryBTCValidatorDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1138,19 +1115,16 @@ func (m *QueryBTCValidatorsAtHeightResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) Size() (n int) { +func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() + l = len(m.ValBtcPkHex) + if l > 0 { n += 1 + l + sovQuery(uint64(l)) } - if m.Height != 0 { - n += 1 + sovQuery(uint64(m.Height)) - } if m.Pagination != nil { l = m.Pagination.Size() n += 1 + l + sovQuery(uint64(l)) @@ -1158,7 +1132,7 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) Size() (n int) { return n } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) Size() (n int) { +func (m *QueryBTCValidatorDelegationsResponse) Size() (n int) { if m == nil { return 0 } @@ -1747,7 +1721,7 @@ func (m *QueryBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorDelegationsAtHeightRequest) Unmarshal(dAtA []byte) error { +func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1770,17 +1744,17 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) Unmarshal(dAtA []byte) err fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryBTCValidatorDelegationsRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryBTCValidatorDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowQuery @@ -1790,47 +1764,25 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) Unmarshal(dAtA []byte) err } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthQuery } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthQuery } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) - } - m.Height = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Height |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) } @@ -1887,7 +1839,7 @@ func (m *QueryBTCValidatorDelegationsAtHeightRequest) Unmarshal(dAtA []byte) err } return nil } -func (m *QueryBTCValidatorDelegationsAtHeightResponse) Unmarshal(dAtA []byte) error { +func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1910,10 +1862,10 @@ func (m *QueryBTCValidatorDelegationsAtHeightResponse) Unmarshal(dAtA []byte) er fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryBTCValidatorDelegationsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryBTCValidatorDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1945,7 +1897,7 @@ func (m *QueryBTCValidatorDelegationsAtHeightResponse) Unmarshal(dAtA []byte) er if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegationWithMeta{}) + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 827c6d7a0..11d1cbfe9 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -160,11 +160,11 @@ func local_request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler } var ( - filter_Query_BTCValidatorDelegationsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + filter_Query_BTCValidatorDelegations_0 = &utilities.DoubleArray{Encoding: map[string]int{"val_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) -func request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorDelegationsAtHeightRequest +func request_Query_BTCValidatorDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorDelegationsRequest var metadata runtime.ServerMetadata var ( @@ -174,31 +174,31 @@ func request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, marsha _ = err ) - val, ok = pathParams["height"] + val, ok = pathParams["val_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") } - protoReq.Height, err = runtime.Uint64(val) + protoReq.ValBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegationsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.BTCValidatorDelegationsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.BTCValidatorDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorDelegationsAtHeightRequest +func local_request_Query_BTCValidatorDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorDelegationsRequest var metadata runtime.ServerMetadata var ( @@ -208,25 +208,25 @@ func local_request_Query_BTCValidatorDelegationsAtHeight_0(ctx context.Context, _ = err ) - val, ok = pathParams["height"] + val, ok = pathParams["val_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") } - protoReq.Height, err = runtime.Uint64(val) + protoReq.ValBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegationsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.BTCValidatorDelegationsAtHeight(ctx, &protoReq) + msg, err := server.BTCValidatorDelegations(ctx, &protoReq) return msg, metadata, err } @@ -306,7 +306,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_BTCValidatorDelegationsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -317,7 +317,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidatorDelegationsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_BTCValidatorDelegations_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -325,7 +325,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidatorDelegationsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_BTCValidatorDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -430,7 +430,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_BTCValidatorDelegationsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -439,14 +439,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidatorDelegationsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_BTCValidatorDelegations_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidatorDelegationsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_BTCValidatorDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -460,7 +460,7 @@ var ( pattern_Query_BTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorDelegationsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "delegations", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -470,5 +470,5 @@ var ( forward_Query_BTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidatorDelegationsAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_BTCValidatorDelegations_0 = runtime.ForwardResponseMessage ) From 59557099036014a01bfa902de6c491e6de6721de Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 18 Jul 2023 13:50:07 +1000 Subject: [PATCH 032/202] RPC queries for BTC validators (#25) Co-authored-by: Gurjot --- proto/babylon/btcstaking/v1/query.proto | 45 +- proto/babylon/finality/v1/query.proto | 54 + testutil/datagen/finality.go | 10 +- x/btcstaking/client/cli/query.go | 70 +- x/btcstaking/keeper/grpc_query.go | 43 +- x/btcstaking/keeper/grpc_query_test.go | 62 +- x/btcstaking/keeper/voting_power_table.go | 2 +- x/btcstaking/types/query.pb.go | 937 +++++++++++++-- x/btcstaking/types/query.pb.gw.go | 188 +++ x/finality/client/cli/query.go | 72 ++ x/finality/keeper/grpc_query.go | 68 +- x/finality/keeper/grpc_query_test.go | 98 ++ x/finality/keeper/msg_server.go | 4 +- x/finality/keeper/public_randomness.go | 5 +- x/finality/types/query.pb.go | 1297 +++++++++++++++++++-- x/finality/types/query.pb.gw.go | 202 ++++ 16 files changed, 2975 insertions(+), 182 deletions(-) create mode 100644 x/finality/keeper/grpc_query_test.go diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index c06e199b1..8a98aa49f 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -26,6 +26,17 @@ service Query { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{height}"; } + // BTCValidatorPowerAtHeight queries a btc validator at a given height + rpc BTCValidatorPowerAtHeight(QueryBTCValidatorPowerAtHeightRequest) returns (QueryBTCValidatorPowerAtHeightResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; + } + + // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when + // there exists 1 BTC validator with voting power + rpc ActivatedHeight(QueryActivatedHeightRequest) returns (QueryActivatedHeightResponse) { + option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/activated_height"; + } + // BTCValidatorDelegations queries all btc delegations of the given btc validator rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; @@ -58,6 +69,25 @@ message QueryBTCValidatorsResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } +// QueryBTCValidatorPowerAtHeightRequest is the request type for the +// Query/BTCValidatorPowerAtHeight RPC method. +message QueryBTCValidatorPowerAtHeightRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + string val_btc_pk_hex = 1; + + // height is used for querying the given validator's voting power at this height + uint64 height = 2; +} + +// QueryBTCValidatorPowerAtHeightResponse is the response type for the +// Query/BTCValidatorPowerAtHeight RPC method. +message QueryBTCValidatorPowerAtHeightResponse { + // voting_power is the voting power of the BTC validator + uint64 voting_power = 1; +} + // QueryBTCValidatorsAtHeightRequest is the request type for the // Query/BTCValidatorsAtHeight RPC method. message QueryBTCValidatorsAtHeightRequest { @@ -78,6 +108,15 @@ message QueryBTCValidatorsAtHeightResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } + +// QueryActivatedHeightRequest is the request type for the Query/ActivatedHeight RPC method. +message QueryActivatedHeightRequest {} + +// QueryActivatedHeightResponse is the response type for the Query/ActivatedHeight RPC method. +message QueryActivatedHeightResponse { + uint64 height = 1; +} + // QueryBTCValidatorDelegationsRequest is the request type for the // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsRequest { @@ -86,8 +125,12 @@ message QueryBTCValidatorDelegationsRequest { // the PK follows encoding in BIP-340 spec string val_btc_pk_hex = 1; + // no_jury_sig_only indicates this query will only return BTC delegations that haven't + // received a jury signature yet + bool no_jury_sig_only = 2; + // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; + cosmos.base.query.v1beta1.PageRequest pagination = 3; } // QueryBTCValidatorDelegationsResponse is the response type for the diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index a629ff151..84adafbf3 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -3,7 +3,9 @@ package babylon.finality.v1; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; import "babylon/finality/v1/params.proto"; +import "babylon/finality/v1/finality.proto"; option go_package = "github.com/babylonchain/babylon/x/finality/types"; @@ -14,6 +16,16 @@ service Query { option (google.api.http).get = "/babylonchain/babylon/finality/v1/params"; } + // ListPublicRandomness is a range query for public randomness of a given BTC validator + rpc ListPublicRandomness(QueryListPublicRandomnessRequest) returns (QueryListPublicRandomnessResponse) { + option (google.api.http).get = "/babylonchain/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/public_randomness_list"; + } + + // ListBlocks is a range query for blocks at a given status + rpc ListBlocks(QueryListBlocksRequest) returns (QueryListBlocksResponse) { + option (google.api.http).get = "/babylonchain/babylon/finality/v1/blocks"; + } + // VotesAtHeight queries btc validators who have signed the block at given height. rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { option (google.api.http).get = "/babylonchain/babylon/finality/v1/votes/{height}"; @@ -29,6 +41,48 @@ message QueryParamsResponse { Params params = 1 [(gogoproto.nullable) = false]; } +// QueryListPublicRandomnessRequest is the request type for the +// Query/ListPublicRandomness RPC method. +message QueryListPublicRandomnessRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator + string val_btc_pk_hex = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryListPublicRandomnessResponse is the response type for the +// Query/ListPublicRandomness RPC method. +message QueryListPublicRandomnessResponse { + // pub_rand_map is the map where the key is the height and the value + // is the public randomness at this height for the given BTC validator + map pub_rand_map = 1 [(gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryListBlocksRequest is the request type for the +// Query/ListBlocks RPC method. +message QueryListBlocksRequest { + // finalized indicates whether to only return finalized or non-finalized + // blocks + bool finalized = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryListBlocksResponse is the response type for the +// Query/ListBlocks RPC method. +message QueryListBlocksResponse { + // blocks is the list of blocks at the given status + repeated IndexedBlock blocks = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. message QueryVotesAtHeightRequest { diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index fa5bbc3a8..af13efc32 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -10,7 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" ) -func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeight uint64, numPubRand uint64) ([]*eots.PrivateRand, *ftypes.MsgCommitPubRandList, error) { +func GenRandomPubRandList(r *rand.Rand, numPubRand uint64) ([]*eots.PrivateRand, []bbn.SchnorrPubRand, error) { srList := []*eots.PrivateRand{} prList := []bbn.SchnorrPubRand{} for i := uint64(0); i < numPubRand; i++ { @@ -22,6 +22,14 @@ func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeig srList = append(srList, eotsSR) prList = append(prList, *pr) } + return srList, prList, nil +} + +func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeight uint64, numPubRand uint64) ([]*eots.PrivateRand, *ftypes.MsgCommitPubRandList, error) { + srList, prList, err := GenRandomPubRandList(r, numPubRand) + if err != nil { + return nil, nil, err + } msg := &ftypes.MsgCommitPubRandList{ Signer: GenRandomAccount().Address, diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index c90ab88c4..48d8cacae 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -2,9 +2,10 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/client/flags" "strconv" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/client" "github.com/spf13/cobra" @@ -24,6 +25,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdBTCValidators()) cmd.AddCommand(CmdBTCValidatorsAtHeight()) + cmd.AddCommand(CmdBTCValidatorPowerAtHeight()) + cmd.AddCommand(CmdActivatedHeight()) cmd.AddCommand(CmdBTCValidatorDelegations()) return cmd @@ -60,6 +63,61 @@ func CmdBTCValidators() *cobra.Command { return cmd } +func CmdBTCValidatorPowerAtHeight() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-validator-power-at-height [val_btc_pk_hex] [height]", + Short: "get the voting power of a given BTC validator at a given height", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + height, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + res, err := queryClient.BTCValidatorPowerAtHeight(cmd.Context(), &types.QueryBTCValidatorPowerAtHeightRequest{ + ValBtcPkHex: args[0], + Height: height, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdActivatedHeight() *cobra.Command { + cmd := &cobra.Command{ + Use: "activated-height", + Short: "get activated height, i.e., the first height where there exists 1 BTC validator with voting power", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.ActivatedHeight(cmd.Context(), &types.QueryActivatedHeightRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + func CmdBTCValidatorsAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validators-at-height [height]", @@ -111,10 +169,15 @@ func CmdBTCValidatorDelegations() *cobra.Command { if err != nil { return err } + noJurySigOnly, err := cmd.Flags().GetBool("no_jury_sig_only") + if err != nil { + return err + } res, err := queryClient.BTCValidatorDelegations(cmd.Context(), &types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: args[0], - Pagination: pageReq, + ValBtcPkHex: args[0], + NoJurySigOnly: noJurySigOnly, + Pagination: pageReq, }) if err != nil { return err @@ -125,6 +188,7 @@ func CmdBTCValidatorDelegations() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + cmd.Flags().Bool("no_jury_sig_only", false, "whether to only return BTC delegations that haven't received a jury signature yet") return cmd } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index c56910e88..223ed1cc8 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -2,15 +2,15 @@ package keeper import ( "context" + errorsmod "cosmossdk.io/errors" bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - "github.com/babylonchain/babylon/x/btcstaking/types" ) var _ types.QueryServer = Keeper{} @@ -37,6 +37,22 @@ func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidators return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil } +func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryBTCValidatorPowerAtHeightRequest) (*types.QueryBTCValidatorPowerAtHeightResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + power := k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), req.Height) + + return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil +} + func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCValidatorsAtHeightRequest) (*types.QueryBTCValidatorsAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -71,6 +87,19 @@ func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCVa return &types.QueryBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil } +func (k Keeper) ActivatedHeight(ctx context.Context, req *types.QueryActivatedHeightRequest) (*types.QueryActivatedHeightResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + activatedHeight, err := k.GetBTCStakingActivatedHeight(sdkCtx) + if err != nil { + return nil, err + } + return &types.QueryActivatedHeightResponse{Height: activatedHeight}, nil +} + func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTCValidatorDelegationsRequest) (*types.QueryBTCValidatorDelegationsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -93,7 +122,15 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { var btcDelegation types.BTCDelegation k.cdc.MustUnmarshal(value, &btcDelegation) - btcDels = append(btcDels, &btcDelegation) + if req.NoJurySigOnly { + // only append BTC delegations that do not have jury signature + if btcDelegation.JurySig == nil { + btcDels = append(btcDels, &btcDelegation) + } + } else { + // append all BTC delegations, regardless whether it has jury signature or not + btcDels = append(btcDels, &btcDelegation) + } return nil }) if err != nil { diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 1618c6ca1..589adf21d 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -2,18 +2,42 @@ package keeper_test import ( "fmt" - testkeeper "github.com/babylonchain/babylon/testutil/keeper" - "github.com/stretchr/testify/require" "math/rand" - - "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/types/query" "testing" "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" ) +func FuzzActivatedHeight(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // not activated yet + _, err := keeper.GetBTCStakingActivatedHeight(ctx) + require.Error(t, err) + + randomActivatedHeight := datagen.RandomInt(r, 100) + 1 + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomActivatedHeight, uint64(10)) + + // now it's activated + resp, err := keeper.ActivatedHeight(ctx, &types.QueryActivatedHeightRequest{}) + require.NoError(t, err) + require.Equal(t, randomActivatedHeight, resp.Height) + }) +} + func FuzzBTCValidators(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -80,6 +104,34 @@ func FuzzBTCValidators(f *testing.F) { }) } +func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + + // random BTC validator + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + // add this BTC validator + keeper.SetBTCValidator(ctx, btcVal) + // set random voting power at random height + randomHeight := datagen.RandomInt(r, 100) + 1 + randomPower := datagen.RandomInt(r, 100) + 1 + keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) + + req := &types.QueryBTCValidatorPowerAtHeightRequest{ + ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + Height: randomHeight, + } + resp, err := keeper.BTCValidatorPowerAtHeight(ctx, req) + require.NoError(t, err) + require.Equal(t, randomPower, resp.VotingPower) + }) +} + func FuzzBTCValidatorsAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index da79ff718..6cbbc1e9e 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" @@ -90,7 +91,6 @@ func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]u // GetBTCStakingActivatedHeight returns the height when the BTC staking protocol is activated // i.e., the first height where a BTC validator has voting power -// otherwise, we return -1 here // Before the BTC staking protocol is activated, we don't index or tally any block func (k Keeper) GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) { store := ctx.KVStore(k.storeKey) diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 1869fa28a..fd15e2fcf 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -216,6 +216,113 @@ func (m *QueryBTCValidatorsResponse) GetPagination() *query.PageResponse { return nil } +// QueryBTCValidatorPowerAtHeightRequest is the request type for the +// Query/BTCValidatorPowerAtHeight RPC method. +type QueryBTCValidatorPowerAtHeightRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + // height is used for querying the given validator's voting power at this height + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCValidatorPowerAtHeightRequest{} } +func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} +func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{4} +} +func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.Merge(m, src) +} +func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorPowerAtHeightRequest) GetValBtcPkHex() string { + if m != nil { + return m.ValBtcPkHex + } + return "" +} + +func (m *QueryBTCValidatorPowerAtHeightRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +// QueryBTCValidatorPowerAtHeightResponse is the response type for the +// Query/BTCValidatorPowerAtHeight RPC method. +type QueryBTCValidatorPowerAtHeightResponse struct { + // voting_power is the voting power of the BTC validator + VotingPower uint64 `protobuf:"varint,1,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +} + +func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { + *m = QueryBTCValidatorPowerAtHeightResponse{} +} +func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} +func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{5} +} +func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.Merge(m, src) +} +func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { + if m != nil { + return m.VotingPower + } + return 0 +} + // QueryBTCValidatorsAtHeightRequest is the request type for the // Query/BTCValidatorsAtHeight RPC method. type QueryBTCValidatorsAtHeightRequest struct { @@ -229,7 +336,7 @@ func (m *QueryBTCValidatorsAtHeightRequest) Reset() { *m = QueryBTCValid func (m *QueryBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{4} + return fileDescriptor_74d49d26f7429697, []int{6} } func (m *QueryBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -285,7 +392,7 @@ func (m *QueryBTCValidatorsAtHeightResponse) Reset() { *m = QueryBTCVali func (m *QueryBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{5} + return fileDescriptor_74d49d26f7429697, []int{7} } func (m *QueryBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -328,6 +435,88 @@ func (m *QueryBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse return nil } +// QueryActivatedHeightRequest is the request type for the Query/ActivatedHeight RPC method. +type QueryActivatedHeightRequest struct { +} + +func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeightRequest{} } +func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } +func (*QueryActivatedHeightRequest) ProtoMessage() {} +func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{8} +} +func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryActivatedHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryActivatedHeightRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryActivatedHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActivatedHeightRequest.Merge(m, src) +} +func (m *QueryActivatedHeightRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryActivatedHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActivatedHeightRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryActivatedHeightRequest proto.InternalMessageInfo + +// QueryActivatedHeightResponse is the response type for the Query/ActivatedHeight RPC method. +type QueryActivatedHeightResponse struct { + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeightResponse{} } +func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } +func (*QueryActivatedHeightResponse) ProtoMessage() {} +func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{9} +} +func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryActivatedHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryActivatedHeightResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryActivatedHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActivatedHeightResponse.Merge(m, src) +} +func (m *QueryActivatedHeightResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryActivatedHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActivatedHeightResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryActivatedHeightResponse proto.InternalMessageInfo + +func (m *QueryActivatedHeightResponse) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + // QueryBTCValidatorDelegationsRequest is the request type for the // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsRequest struct { @@ -335,15 +524,18 @@ type QueryBTCValidatorDelegationsRequest struct { // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + // no_jury_sig_only indicates this query will only return BTC delegations that haven't + // received a jury signature yet + NoJurySigOnly bool `protobuf:"varint,2,opt,name=no_jury_sig_only,json=noJurySigOnly,proto3" json:"no_jury_sig_only,omitempty"` // pagination defines an optional pagination for the request. - Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` } func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCValidatorDelegationsRequest{} } func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{6} + return fileDescriptor_74d49d26f7429697, []int{10} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -379,6 +571,13 @@ func (m *QueryBTCValidatorDelegationsRequest) GetValBtcPkHex() string { return "" } +func (m *QueryBTCValidatorDelegationsRequest) GetNoJurySigOnly() bool { + if m != nil { + return m.NoJurySigOnly + } + return false +} + func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination @@ -399,7 +598,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{7} + return fileDescriptor_74d49d26f7429697, []int{11} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -447,8 +646,12 @@ func init() { proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") proto.RegisterType((*QueryBTCValidatorsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsRequest") proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") + proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") + proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") proto.RegisterType((*QueryBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightRequest") proto.RegisterType((*QueryBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightResponse") + proto.RegisterType((*QueryActivatedHeightRequest)(nil), "babylon.btcstaking.v1.QueryActivatedHeightRequest") + proto.RegisterType((*QueryActivatedHeightResponse)(nil), "babylon.btcstaking.v1.QueryActivatedHeightResponse") proto.RegisterType((*QueryBTCValidatorDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsRequest") proto.RegisterType((*QueryBTCValidatorDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsResponse") } @@ -456,49 +659,60 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 662 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0x4f, 0x4f, 0xd4, 0x40, - 0x18, 0xc6, 0x77, 0x10, 0x37, 0x71, 0x08, 0x98, 0x8c, 0xa0, 0xd8, 0x68, 0x85, 0x42, 0x10, 0xd1, - 0x74, 0xdc, 0xea, 0x41, 0x21, 0x1e, 0x5c, 0x8c, 0x12, 0x23, 0x11, 0x1a, 0x23, 0x89, 0x17, 0x32, - 0x2d, 0x93, 0xb6, 0xa1, 0x74, 0xca, 0x76, 0x68, 0x20, 0x86, 0x8b, 0x7e, 0x01, 0x03, 0x5f, 0x44, - 0xbf, 0x80, 0x89, 0x37, 0x8e, 0x18, 0x2f, 0x9e, 0x8c, 0xd9, 0xf5, 0xe6, 0x97, 0x30, 0x9d, 0xce, - 0xba, 0x2d, 0xdb, 0x5d, 0x97, 0x0d, 0xde, 0x76, 0x67, 0xde, 0x3f, 0xcf, 0xf3, 0xdb, 0x79, 0xdf, - 0x85, 0x93, 0x16, 0xb1, 0xf6, 0x7c, 0x16, 0x60, 0x8b, 0xdb, 0x11, 0x27, 0x9b, 0x5e, 0xe0, 0xe0, - 0xb8, 0x82, 0xb7, 0x77, 0x68, 0x6d, 0x4f, 0x0f, 0x6b, 0x8c, 0x33, 0x34, 0x26, 0x43, 0xf4, 0x56, - 0x88, 0x1e, 0x57, 0x94, 0x51, 0x87, 0x39, 0x4c, 0x44, 0xe0, 0xe4, 0x53, 0x1a, 0xac, 0x5c, 0x73, - 0x18, 0x73, 0x7c, 0x8a, 0x49, 0xe8, 0x61, 0x12, 0x04, 0x8c, 0x13, 0xee, 0xb1, 0x20, 0x92, 0xb7, - 0x73, 0x36, 0x8b, 0xb6, 0x58, 0x84, 0x2d, 0x12, 0xd1, 0xb4, 0x07, 0x8e, 0x2b, 0x16, 0xe5, 0xa4, - 0x82, 0x43, 0xe2, 0x78, 0x81, 0x08, 0x96, 0xb1, 0x5a, 0xb1, 0xb2, 0x90, 0xd4, 0xc8, 0x56, 0xb3, - 0xde, 0x4c, 0x71, 0x4c, 0x46, 0xa8, 0x88, 0xd3, 0x46, 0x21, 0x5a, 0x4d, 0xba, 0xad, 0x88, 0x64, - 0x93, 0x6e, 0xef, 0xd0, 0x88, 0x6b, 0x26, 0xbc, 0x94, 0x3b, 0x8d, 0x42, 0x16, 0x44, 0x14, 0x2d, - 0xc0, 0x72, 0xda, 0x64, 0x1c, 0x4c, 0x80, 0xd9, 0x21, 0xe3, 0xba, 0x5e, 0x08, 0x40, 0x4f, 0xd3, - 0xaa, 0x83, 0x47, 0x3f, 0x6e, 0x94, 0x4c, 0x99, 0xa2, 0xd9, 0xf0, 0xaa, 0xa8, 0x59, 0x7d, 0xb5, - 0xf8, 0x9a, 0xf8, 0xde, 0x06, 0xe1, 0xac, 0xd6, 0x6c, 0x88, 0x9e, 0x42, 0xd8, 0xb2, 0x29, 0xab, - 0xcf, 0xe8, 0x29, 0x13, 0x3d, 0x61, 0xa2, 0xa7, 0xdc, 0x25, 0x13, 0x7d, 0x85, 0x38, 0x54, 0xe6, - 0x9a, 0x99, 0x4c, 0xed, 0x13, 0x80, 0x4a, 0x51, 0x17, 0x69, 0xe0, 0x39, 0x1c, 0xb1, 0xb8, 0xbd, - 0x1e, 0xff, 0xbd, 0x19, 0x07, 0x13, 0xe7, 0x66, 0x87, 0x8c, 0xa9, 0x0e, 0x46, 0xb2, 0x55, 0xcc, - 0x61, 0x8b, 0xdb, 0xad, 0x9a, 0xe8, 0x59, 0x4e, 0xf2, 0x80, 0x90, 0x7c, 0xf3, 0x9f, 0x92, 0x53, - 0x21, 0x39, 0xcd, 0xef, 0x01, 0x9c, 0x6c, 0xd7, 0xfc, 0x98, 0x2f, 0x51, 0xcf, 0x71, 0x79, 0x93, - 0xd0, 0x65, 0x58, 0x76, 0xc5, 0x81, 0xa0, 0x33, 0x68, 0xca, 0x6f, 0x27, 0xc8, 0x0d, 0xf4, 0x4d, - 0xee, 0x0b, 0x80, 0x5a, 0x37, 0x15, 0x92, 0xa0, 0xd9, 0x81, 0xe0, 0xed, 0x1e, 0x08, 0xae, 0x79, - 0xdc, 0x5d, 0xa6, 0x9c, 0xfc, 0x37, 0x92, 0x07, 0x00, 0x4e, 0xb5, 0x79, 0x78, 0x42, 0x7d, 0xea, - 0xa4, 0xb3, 0xd6, 0x64, 0x39, 0x05, 0x47, 0x62, 0xe2, 0xaf, 0x27, 0x46, 0xc2, 0xcd, 0x75, 0x97, - 0xee, 0x0a, 0xa6, 0x17, 0xcc, 0xa1, 0x98, 0xf8, 0x55, 0x6e, 0xaf, 0x6c, 0x2e, 0xd1, 0xdd, 0x33, - 0x03, 0xfb, 0x19, 0xc0, 0xe9, 0xee, 0xa2, 0x24, 0xda, 0x65, 0x78, 0x31, 0x51, 0xb4, 0xd1, 0xba, - 0x92, 0x6c, 0xa7, 0x3b, 0xb3, 0x6d, 0xd5, 0x31, 0x93, 0xdf, 0x25, 0x53, 0xf6, 0xcc, 0xa8, 0x1a, - 0x07, 0x65, 0x78, 0x5e, 0x18, 0x40, 0x87, 0x00, 0x96, 0xd3, 0xd9, 0x46, 0xb7, 0x3a, 0x68, 0x6a, - 0x5f, 0x26, 0xca, 0x5c, 0x2f, 0xa1, 0x69, 0x5f, 0xcd, 0x78, 0xf7, 0xed, 0xd7, 0xe1, 0xc0, 0x1d, - 0x34, 0x87, 0x65, 0x8e, 0xed, 0x12, 0x2f, 0xc0, 0xdd, 0x16, 0x1e, 0xfa, 0x08, 0xe0, 0x70, 0xee, - 0xd1, 0xa2, 0xbb, 0xdd, 0x3a, 0x16, 0xed, 0x1f, 0xa5, 0x72, 0x8a, 0x0c, 0x29, 0x75, 0x5e, 0x48, - 0xbd, 0x8f, 0x8c, 0x5e, 0xa4, 0xe6, 0x67, 0x06, 0x7d, 0x05, 0x70, 0xac, 0x70, 0xce, 0xd0, 0x83, - 0x9e, 0x85, 0x9c, 0x58, 0x10, 0xca, 0xc3, 0x3e, 0x32, 0xa5, 0x95, 0x45, 0x61, 0xe5, 0x11, 0x5a, - 0x38, 0xbd, 0x15, 0xfc, 0x36, 0xdd, 0x43, 0xfb, 0xe8, 0x37, 0x80, 0x57, 0x3a, 0x3c, 0x71, 0x34, - 0xdf, 0xab, 0xb6, 0xf6, 0x61, 0x55, 0x16, 0xfa, 0xca, 0x95, 0xce, 0xd6, 0x84, 0xb3, 0x55, 0xf4, - 0xb2, 0x1f, 0x67, 0xf9, 0x1d, 0xb1, 0x8f, 0x33, 0x93, 0x59, 0x7d, 0x71, 0x54, 0x57, 0xc1, 0x71, - 0x5d, 0x05, 0x3f, 0xeb, 0x2a, 0xf8, 0xd0, 0x50, 0x4b, 0xc7, 0x0d, 0xb5, 0xf4, 0xbd, 0xa1, 0x96, - 0xde, 0x18, 0x8e, 0xc7, 0xdd, 0x1d, 0x4b, 0xb7, 0xd9, 0x56, 0x71, 0xd3, 0xdd, 0x6c, 0x5b, 0xbe, - 0x17, 0xd2, 0xc8, 0x2a, 0x8b, 0x3f, 0xe3, 0x7b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x91, - 0x8e, 0xc4, 0x74, 0x08, 0x00, 0x00, + // 844 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcf, 0x4f, 0x1b, 0x47, + 0x18, 0xf5, 0x00, 0xb5, 0xca, 0xb8, 0x40, 0x35, 0x85, 0x16, 0xb6, 0xe0, 0xc2, 0x42, 0x81, 0xd2, + 0x6a, 0xb7, 0x36, 0x08, 0xb5, 0xfc, 0x38, 0x60, 0xaa, 0x16, 0xd1, 0x22, 0xcc, 0x16, 0x15, 0x89, + 0xcb, 0x6a, 0x76, 0x3d, 0x5a, 0x6f, 0x59, 0x76, 0x16, 0xef, 0x78, 0x8b, 0x55, 0x71, 0x69, 0xff, + 0x81, 0x4a, 0xfc, 0x23, 0xcd, 0x2d, 0x52, 0xa4, 0x48, 0x91, 0x72, 0xe0, 0x48, 0x94, 0x4b, 0x4e, + 0x51, 0x04, 0xb9, 0xe5, 0x9a, 0x3f, 0x20, 0xf2, 0xec, 0x38, 0xfe, 0xb5, 0x36, 0xb6, 0x45, 0x6e, + 0xe0, 0xf9, 0xde, 0xf7, 0xbd, 0xf7, 0x3e, 0xcf, 0x1b, 0xc3, 0x19, 0x03, 0x1b, 0x25, 0x87, 0xba, + 0xaa, 0xc1, 0x4c, 0x9f, 0xe1, 0x13, 0xdb, 0xb5, 0xd4, 0x20, 0xa5, 0x9e, 0x15, 0x49, 0xa1, 0xa4, + 0x78, 0x05, 0xca, 0x28, 0x1a, 0x13, 0x25, 0x4a, 0xb5, 0x44, 0x09, 0x52, 0xd2, 0xa8, 0x45, 0x2d, + 0xca, 0x2b, 0xd4, 0xf2, 0x5f, 0x61, 0xb1, 0x34, 0x69, 0x51, 0x6a, 0x39, 0x44, 0xc5, 0x9e, 0xad, + 0x62, 0xd7, 0xa5, 0x0c, 0x33, 0x9b, 0xba, 0xbe, 0x38, 0x5d, 0x32, 0xa9, 0x7f, 0x4a, 0x7d, 0xd5, + 0xc0, 0x3e, 0x09, 0x67, 0xa8, 0x41, 0xca, 0x20, 0x0c, 0xa7, 0x54, 0x0f, 0x5b, 0xb6, 0xcb, 0x8b, + 0x45, 0xad, 0x1c, 0xcd, 0xcc, 0xc3, 0x05, 0x7c, 0x5a, 0xe9, 0x37, 0x1f, 0x5d, 0x53, 0x43, 0x94, + 0xd7, 0xc9, 0xa3, 0x10, 0x1d, 0x94, 0xa7, 0x65, 0x39, 0x58, 0x23, 0x67, 0x45, 0xe2, 0x33, 0x59, + 0x83, 0x9f, 0xd5, 0x7d, 0xea, 0x7b, 0xd4, 0xf5, 0x09, 0x5a, 0x87, 0xf1, 0x70, 0xc8, 0x38, 0x98, + 0x06, 0x8b, 0x89, 0xf4, 0x94, 0x12, 0x69, 0x80, 0x12, 0xc2, 0x32, 0x03, 0x57, 0x2f, 0xbf, 0x8a, + 0x69, 0x02, 0x22, 0x9b, 0x70, 0x82, 0xf7, 0xcc, 0x1c, 0x6e, 0xff, 0x81, 0x1d, 0x3b, 0x87, 0x19, + 0x2d, 0x54, 0x06, 0xa2, 0x9f, 0x21, 0xac, 0xca, 0x14, 0xdd, 0xe7, 0x95, 0xd0, 0x13, 0xa5, 0xec, + 0x89, 0x12, 0xfa, 0x2e, 0x3c, 0x51, 0xb2, 0xd8, 0x22, 0x02, 0xab, 0xd5, 0x20, 0xe5, 0x07, 0x00, + 0x4a, 0x51, 0x53, 0x84, 0x80, 0x5d, 0x38, 0x6c, 0x30, 0x53, 0x0f, 0xde, 0x9f, 0x8c, 0x83, 0xe9, + 0xfe, 0xc5, 0x44, 0x7a, 0xb6, 0x85, 0x90, 0xda, 0x2e, 0xda, 0x90, 0xc1, 0xcc, 0x6a, 0x4f, 0xf4, + 0x4b, 0x1d, 0xe5, 0x3e, 0x4e, 0x79, 0xe1, 0x4e, 0xca, 0x21, 0x91, 0x3a, 0xce, 0x39, 0xf8, 0x75, + 0x13, 0xe5, 0x2c, 0xfd, 0x8b, 0x14, 0xb6, 0xd8, 0x0e, 0xb1, 0xad, 0x3c, 0xab, 0x98, 0x34, 0x0b, + 0x87, 0x03, 0xec, 0xe8, 0x65, 0x05, 0xde, 0x89, 0x9e, 0x27, 0xe7, 0xdc, 0xa8, 0x41, 0x2d, 0x11, + 0x60, 0x27, 0xc3, 0xcc, 0xec, 0xc9, 0x0e, 0x39, 0x47, 0x9f, 0xc3, 0x78, 0x9e, 0xa3, 0x38, 0xa5, + 0x01, 0x4d, 0xfc, 0x27, 0xff, 0x0a, 0xe7, 0xef, 0x9a, 0x22, 0x4c, 0x9a, 0x81, 0x9f, 0x04, 0x94, + 0xd9, 0xae, 0xa5, 0x7b, 0xe5, 0x73, 0x3e, 0x64, 0x40, 0x4b, 0x84, 0x9f, 0x71, 0x88, 0xfc, 0x2f, + 0x80, 0x33, 0xcd, 0x36, 0x37, 0xf2, 0xad, 0x52, 0x01, 0xb5, 0x54, 0x1a, 0x96, 0xdd, 0xd7, 0xf3, + 0xb2, 0x9f, 0x00, 0x28, 0xb7, 0x63, 0x21, 0xf4, 0x68, 0x2d, 0x96, 0xfe, 0x6d, 0x07, 0x4b, 0x3f, + 0xb2, 0x59, 0x7e, 0x8f, 0x30, 0xfc, 0xc1, 0x96, 0x3f, 0x05, 0xbf, 0xe4, 0x12, 0xb6, 0x4c, 0x66, + 0x07, 0x98, 0x91, 0x5c, 0x9d, 0x85, 0xf2, 0x2a, 0x9c, 0x8c, 0x3e, 0x16, 0xda, 0x5a, 0x58, 0x2c, + 0x3f, 0x02, 0x70, 0xb6, 0xc9, 0x9a, 0x9f, 0x88, 0x43, 0xac, 0x30, 0x75, 0xba, 0xfa, 0x4a, 0x2d, + 0xc0, 0x4f, 0x5d, 0xaa, 0xff, 0x59, 0x2c, 0x94, 0x74, 0xdf, 0xb6, 0x74, 0xea, 0x3a, 0x25, 0x2e, + 0xf9, 0x63, 0x6d, 0xc8, 0xa5, 0xbb, 0xc5, 0x42, 0xe9, 0x77, 0xdb, 0xda, 0x77, 0x9d, 0x52, 0xc3, + 0x62, 0xfb, 0x7b, 0x5e, 0xec, 0x63, 0x00, 0xe7, 0xda, 0xb3, 0x17, 0xf2, 0xf7, 0xe0, 0x48, 0x99, + 0x7a, 0xae, 0x7a, 0x24, 0x76, 0x3b, 0xd7, 0x7a, 0xb7, 0xd5, 0x3e, 0x5a, 0xf9, 0x7b, 0x51, 0xd3, + 0xf6, 0xde, 0xb6, 0x9a, 0x7e, 0x3a, 0x08, 0x3f, 0xe2, 0x02, 0xd0, 0x25, 0x80, 0xf1, 0x30, 0x0e, + 0xd1, 0x37, 0x2d, 0x38, 0x35, 0xe7, 0xaf, 0xb4, 0xd4, 0x49, 0x69, 0x38, 0x57, 0x4e, 0xff, 0xf3, + 0xfc, 0xf5, 0x65, 0xdf, 0x77, 0x68, 0x49, 0x15, 0x18, 0x33, 0x8f, 0x6d, 0x57, 0x6d, 0xf7, 0x46, + 0xa0, 0xff, 0x01, 0x1c, 0xaa, 0xbb, 0x34, 0xe8, 0xfb, 0x76, 0x13, 0xa3, 0x22, 0x5b, 0x4a, 0x75, + 0x81, 0x10, 0x54, 0xd7, 0x38, 0xd5, 0x15, 0x94, 0xee, 0x84, 0x6a, 0xfd, 0x9d, 0x45, 0xcf, 0x00, + 0x1c, 0x8b, 0xbc, 0xe7, 0xe8, 0x87, 0x8e, 0x89, 0x34, 0x04, 0x94, 0xf4, 0x63, 0x0f, 0x48, 0x21, + 0x65, 0x9b, 0x4b, 0xd9, 0x44, 0xeb, 0xdd, 0x4b, 0x51, 0xff, 0x0e, 0x2f, 0xe9, 0x05, 0x7a, 0x0b, + 0xe0, 0x44, 0xcb, 0x3c, 0x46, 0x1b, 0x9d, 0xb2, 0x8b, 0x7a, 0x2c, 0xa4, 0xcd, 0x1e, 0xd1, 0x42, + 0xdf, 0x31, 0xd7, 0x77, 0x88, 0xb4, 0x5e, 0xf4, 0xd5, 0x47, 0xca, 0x85, 0xca, 0xdf, 0x91, 0xaa, + 0xec, 0x87, 0x00, 0x8e, 0x34, 0x04, 0x1a, 0x4a, 0xb7, 0xa3, 0x1b, 0x1d, 0x8e, 0xd2, 0x72, 0x57, + 0x18, 0x21, 0x6c, 0x83, 0x0b, 0x5b, 0x45, 0x2b, 0x9d, 0x08, 0xc3, 0x95, 0x26, 0xba, 0x78, 0xba, + 0xde, 0x00, 0xf8, 0x45, 0x8b, 0x50, 0x42, 0x6b, 0x9d, 0x3a, 0xde, 0x9c, 0xc3, 0xd2, 0x7a, 0x4f, + 0x58, 0x21, 0xe9, 0x88, 0x4b, 0x3a, 0x40, 0xfb, 0xf7, 0xb1, 0xab, 0x9a, 0x2c, 0xcd, 0xfc, 0x76, + 0x75, 0x93, 0x04, 0xd7, 0x37, 0x49, 0xf0, 0xea, 0x26, 0x09, 0xfe, 0xbb, 0x4d, 0xc6, 0xae, 0x6f, + 0x93, 0xb1, 0x17, 0xb7, 0xc9, 0xd8, 0x71, 0xda, 0xb2, 0x59, 0xbe, 0x68, 0x28, 0x26, 0x3d, 0x8d, + 0x1e, 0x7a, 0x5e, 0x3b, 0x96, 0x95, 0x3c, 0xe2, 0x1b, 0x71, 0xfe, 0x8b, 0x73, 0xf9, 0x5d, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xe8, 0xdf, 0x2c, 0xfa, 0x59, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -519,6 +733,11 @@ type QueryClient interface { BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) + // BTCValidatorPowerAtHeight queries a btc validator at a given height + BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) + // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when + // there exists 1 BTC validator with voting power + ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) // BTCValidatorDelegations queries all btc delegations of the given btc validator BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) } @@ -558,6 +777,24 @@ func (c *queryClient) BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCVal return out, nil } +func (c *queryClient) BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) { + out := new(QueryBTCValidatorPowerAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorPowerAtHeight", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) { + out := new(QueryActivatedHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActivatedHeight", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) { out := new(QueryBTCValidatorDelegationsResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorDelegations", in, out, opts...) @@ -575,6 +812,11 @@ type QueryServer interface { BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. BTCValidatorsAtHeight(context.Context, *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) + // BTCValidatorPowerAtHeight queries a btc validator at a given height + BTCValidatorPowerAtHeight(context.Context, *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) + // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when + // there exists 1 BTC validator with voting power + ActivatedHeight(context.Context, *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) // BTCValidatorDelegations queries all btc delegations of the given btc validator BTCValidatorDelegations(context.Context, *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) } @@ -592,6 +834,12 @@ func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBT func (*UnimplementedQueryServer) BTCValidatorsAtHeight(ctx context.Context, req *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorsAtHeight not implemented") } +func (*UnimplementedQueryServer) BTCValidatorPowerAtHeight(ctx context.Context, req *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorPowerAtHeight not implemented") +} +func (*UnimplementedQueryServer) ActivatedHeight(ctx context.Context, req *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ActivatedHeight not implemented") +} func (*UnimplementedQueryServer) BTCValidatorDelegations(ctx context.Context, req *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegations not implemented") } @@ -654,6 +902,42 @@ func _Query_BTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Query_BTCValidatorPowerAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorPowerAtHeightRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidatorPowerAtHeight(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorPowerAtHeight", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidatorPowerAtHeight(ctx, req.(*QueryBTCValidatorPowerAtHeightRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ActivatedHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryActivatedHeightRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ActivatedHeight(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/ActivatedHeight", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ActivatedHeight(ctx, req.(*QueryActivatedHeightRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_BTCValidatorDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryBTCValidatorDelegationsRequest) if err := dec(in); err != nil { @@ -688,6 +972,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BTCValidatorsAtHeight", Handler: _Query_BTCValidatorsAtHeight_Handler, }, + { + MethodName: "BTCValidatorPowerAtHeight", + Handler: _Query_BTCValidatorPowerAtHeight_Handler, + }, + { + MethodName: "ActivatedHeight", + Handler: _Query_ActivatedHeight_Handler, + }, { MethodName: "BTCValidatorDelegations", Handler: _Query_BTCValidatorDelegations_Handler, @@ -837,6 +1129,69 @@ func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *QueryBTCValidatorPowerAtHeightRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorPowerAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorPowerAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorPowerAtHeightResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.VotingPower != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *QueryBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -926,7 +1281,7 @@ func (m *QueryBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryActivatedHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -936,39 +1291,20 @@ func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActivatedHeightRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActivatedHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Pagination != nil { - { - size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) - i-- - dAtA[i] = 0xa - } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryActivatedHeightResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -978,23 +1314,103 @@ func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActivatedHeightResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActivatedHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Pagination != nil { - { - size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.NoJurySigOnly { + i-- + if m.NoJurySigOnly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- @@ -1080,6 +1496,34 @@ func (m *QueryBTCValidatorsResponse) Size() (n int) { return n } +func (m *QueryBTCValidatorPowerAtHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValBtcPkHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n +} + +func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.VotingPower != 0 { + n += 1 + sovQuery(uint64(m.VotingPower)) + } + return n +} + func (m *QueryBTCValidatorsAtHeightRequest) Size() (n int) { if m == nil { return 0 @@ -1115,6 +1559,27 @@ func (m *QueryBTCValidatorsAtHeightResponse) Size() (n int) { return n } +func (m *QueryActivatedHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryActivatedHeightResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n +} + func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { if m == nil { return 0 @@ -1125,6 +1590,9 @@ func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } + if m.NoJurySigOnly { + n += 2 + } if m.Pagination != nil { l = m.Pagination.Size() n += 1 + l + sovQuery(uint64(l)) @@ -1496,6 +1964,176 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1721,6 +2359,125 @@ func (m *QueryBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryActivatedHeightRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryActivatedHeightRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryActivatedHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryActivatedHeightResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryActivatedHeightResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryActivatedHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1783,6 +2540,26 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NoJurySigOnly", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.NoJurySigOnly = bool(v != 0) + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) } diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 11d1cbfe9..f03be6485 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -159,6 +159,100 @@ func local_request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler } +func request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorPowerAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := client.BTCValidatorPowerAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorPowerAtHeightRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := server.BTCValidatorPowerAtHeight(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ActivatedHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActivatedHeightRequest + var metadata runtime.ServerMetadata + + msg, err := client.ActivatedHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ActivatedHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActivatedHeightRequest + var metadata runtime.ServerMetadata + + msg, err := server.ActivatedHeight(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_BTCValidatorDelegations_0 = &utilities.DoubleArray{Encoding: map[string]int{"val_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -306,6 +400,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCValidatorPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidatorPowerAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ActivatedHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ActivatedHeight_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ActivatedHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -430,6 +570,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCValidatorPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidatorPowerAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ActivatedHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ActivatedHeight_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ActivatedHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -460,6 +640,10 @@ var ( pattern_Query_BTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) ) @@ -470,5 +654,9 @@ var ( forward_Query_BTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage + + forward_Query_ActivatedHeight_0 = runtime.ForwardResponseMessage + forward_Query_BTCValidatorDelegations_0 = runtime.ForwardResponseMessage ) diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index 4df0208f9..30dd613cc 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -5,6 +5,7 @@ import ( "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" ) @@ -20,6 +21,77 @@ func GetQueryCmd(queryRoute string) *cobra.Command { } cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand(CmdListPublicRandomness()) + cmd.AddCommand(CmdListBlocks()) + + return cmd +} + +func CmdListPublicRandomness() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-public-randomness [val_btc_pk_hex]", + Short: "list public randomness committed by a given BTC validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.ListPublicRandomness(cmd.Context(), &types.QueryListPublicRandomnessRequest{ + ValBtcPkHex: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdListBlocks() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-blocks", + Short: "list blocks at a given status", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + finalized, err := cmd.Flags().GetBool("finalized") + if err != nil { + return err + } + + res, err := queryClient.ListBlocks(cmd.Context(), &types.QueryListBlocksRequest{ + Finalized: finalized, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + cmd.Flags().Bool("finalized", false, "return finalized or non-finalized blocks") return cmd } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index ebab4d4d0..6fd41ab91 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -3,18 +3,76 @@ package keeper import ( "context" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "google.golang.org/grpc/codes" bbn "github.com/babylonchain/babylon/types" - - "google.golang.org/grpc/status" - "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var _ types.QueryServer = Keeper{} +func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPublicRandomnessRequest) (*types.QueryListPublicRandomnessResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := k.pubRandStore(sdkCtx, valBTCPK) + pubRandMap := map[uint64]*bbn.SchnorrPubRand{} + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + height := sdk.BigEndianToUint64(key) + pubRand, err := bbn.NewSchnorrPubRand(value) + if err != nil { + panic("failed to unmarshal EOTS public randomness in KVStore") + } + pubRandMap[height] = pubRand + return nil + }) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + resp := &types.QueryListPublicRandomnessResponse{ + PubRandMap: pubRandMap, + Pagination: pageRes, + } + return resp, nil +} + +func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksRequest) (*types.QueryListBlocksResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + sdkCtx := sdk.UnwrapSDKContext(ctx) + store := k.blockStore(sdkCtx) + ibs := []*types.IndexedBlock{} + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + var ib types.IndexedBlock + k.cdc.MustUnmarshal(value, &ib) + if ib.Finalized == req.Finalized { + ibs = append(ibs, &ib) + } + return nil + }) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + resp := &types.QueryListBlocksResponse{ + Blocks: ibs, + Pagination: pageRes, + } + return resp, nil +} + func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeightRequest) (*types.QueryVotesAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go new file mode 100644 index 000000000..2f9606287 --- /dev/null +++ b/x/finality/keeper/grpc_query_test.go @@ -0,0 +1,98 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + query "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" +) + +func FuzzListPublicRandomness(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // add a random list of EOTS public randomness + valBTCPK, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + startHeight := datagen.RandomInt(r, 100) + numPubRand := datagen.RandomInt(r, 1000) + 1 + _, prList, err := datagen.GenRandomPubRandList(r, numPubRand) + require.NoError(t, err) + keeper.SetPubRandList(ctx, valBTCPK, startHeight, prList) + + // perform a query to pubrand list and assert consistency + // NOTE: pagination is already tested in Cosmos SDK so we don't test it here again, + // instead only ensure it takes effect + limit := datagen.RandomInt(r, int(numPubRand)-1) + 1 + req := &types.QueryListPublicRandomnessRequest{ + ValBtcPkHex: valBTCPK.ToHexStr(), + Pagination: &query.PageRequest{ + Limit: limit, + }, + } + resp, err := keeper.ListPublicRandomness(ctx, req) + require.NoError(t, err) + require.Equal(t, int(limit), len(resp.PubRandMap)) // check if pagination takes effect + for i := startHeight; i < startHeight+limit; i++ { + expectedPR := prList[i-startHeight] + actualPR := resp.PubRandMap[i] + require.Equal(t, expectedPR.MustMarshal(), actualPR.MustMarshal()) + } + }) +} + +func FuzzListBlocks(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // index a random list of finalised blocks + startHeight := datagen.RandomInt(r, 100) + numIndexedBlocks := datagen.RandomInt(r, 100) + 1 + finalizedIndexedBlocks := map[uint64]*types.IndexedBlock{} + for i := startHeight; i < startHeight+numIndexedBlocks; i++ { + ib := &types.IndexedBlock{ + Height: startHeight + numIndexedBlocks, + LastCommitHash: datagen.GenRandomByteArray(r, 32), + } + // randomly finalise some of them + if datagen.RandomInt(r, 2) == 1 { + ib.Finalized = true + finalizedIndexedBlocks[ib.Height] = ib + } + // insert to KVStore + keeper.SetBlock(ctx, ib) + } + + // perform a query to pubrand list and assert consistency + // NOTE: pagination is already tested in Cosmos SDK so we don't test it here again, + // instead only ensure it takes effect + limit := datagen.RandomInt(r, int(numIndexedBlocks)-1) + 1 + req := &types.QueryListBlocksRequest{ + Finalized: true, + Pagination: &query.PageRequest{ + Limit: limit, + }, + } + resp, err := keeper.ListBlocks(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Blocks), int(limit)) // check if pagination takes effect + for _, actualIB := range resp.Blocks { + require.Equal(t, finalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + } + }) +} diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 73df364a5..edb1624c6 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -155,7 +155,7 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi // this BTC validator has not commit any public randomness, // commit the given public randomness list and return if ms.IsFirstPubRand(ctx, req.ValBtcPk) { - ms.setPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil } @@ -169,6 +169,6 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi } // all good, commit the given public randomness list - ms.setPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil } diff --git a/x/finality/keeper/public_randomness.go b/x/finality/keeper/public_randomness.go index 7c3a2f300..b2bb050ca 100644 --- a/x/finality/keeper/public_randomness.go +++ b/x/finality/keeper/public_randomness.go @@ -9,15 +9,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -//nolint:unused func (k Keeper) setPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64, pr *bbn.SchnorrPubRand) { store := k.pubRandStore(ctx, valBtcPK) store.Set(sdk.Uint64ToBigEndian(height), *pr) } -// setPubRandList sets a list of public randomness starting from a given startHeight +// SetPubRandList sets a list of public randomness starting from a given startHeight // for a given BTC validator -func (k Keeper) setPubRandList(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand) { +func (k Keeper) SetPubRandList(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand) { for i, pr := range pubRandList { k.setPubRand(ctx, valBtcPK, startHeight+uint64(i), &pr) } diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index df8117c26..69241a901 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + query "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -113,6 +114,223 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +// QueryListPublicRandomnessRequest is the request type for the +// Query/ListPublicRandomness RPC method. +type QueryListPublicRandomnessRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListPublicRandomnessRequest) Reset() { *m = QueryListPublicRandomnessRequest{} } +func (m *QueryListPublicRandomnessRequest) String() string { return proto.CompactTextString(m) } +func (*QueryListPublicRandomnessRequest) ProtoMessage() {} +func (*QueryListPublicRandomnessRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{2} +} +func (m *QueryListPublicRandomnessRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListPublicRandomnessRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListPublicRandomnessRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListPublicRandomnessRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListPublicRandomnessRequest.Merge(m, src) +} +func (m *QueryListPublicRandomnessRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryListPublicRandomnessRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListPublicRandomnessRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListPublicRandomnessRequest proto.InternalMessageInfo + +func (m *QueryListPublicRandomnessRequest) GetValBtcPkHex() string { + if m != nil { + return m.ValBtcPkHex + } + return "" +} + +func (m *QueryListPublicRandomnessRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryListPublicRandomnessResponse is the response type for the +// Query/ListPublicRandomness RPC method. +type QueryListPublicRandomnessResponse struct { + // pub_rand_map is the map where the key is the height and the value + // is the public randomness at this height for the given BTC validator + PubRandMap map[uint64]*github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,1,rep,name=pub_rand_map,json=pubRandMap,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand_map,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListPublicRandomnessResponse) Reset() { *m = QueryListPublicRandomnessResponse{} } +func (m *QueryListPublicRandomnessResponse) String() string { return proto.CompactTextString(m) } +func (*QueryListPublicRandomnessResponse) ProtoMessage() {} +func (*QueryListPublicRandomnessResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{3} +} +func (m *QueryListPublicRandomnessResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListPublicRandomnessResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListPublicRandomnessResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListPublicRandomnessResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListPublicRandomnessResponse.Merge(m, src) +} +func (m *QueryListPublicRandomnessResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryListPublicRandomnessResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListPublicRandomnessResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListPublicRandomnessResponse proto.InternalMessageInfo + +func (m *QueryListPublicRandomnessResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryListBlocksRequest is the request type for the +// Query/ListBlocks RPC method. +type QueryListBlocksRequest struct { + Finalized bool `protobuf:"varint,1,opt,name=finalized,proto3" json:"finalized,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListBlocksRequest) Reset() { *m = QueryListBlocksRequest{} } +func (m *QueryListBlocksRequest) String() string { return proto.CompactTextString(m) } +func (*QueryListBlocksRequest) ProtoMessage() {} +func (*QueryListBlocksRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{4} +} +func (m *QueryListBlocksRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListBlocksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListBlocksRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListBlocksRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListBlocksRequest.Merge(m, src) +} +func (m *QueryListBlocksRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryListBlocksRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListBlocksRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListBlocksRequest proto.InternalMessageInfo + +func (m *QueryListBlocksRequest) GetFinalized() bool { + if m != nil { + return m.Finalized + } + return false +} + +func (m *QueryListBlocksRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryListBlocksResponse is the response type for the +// Query/ListBlocks RPC method. +type QueryListBlocksResponse struct { + // blocks is the list of blocks at the given status + Blocks []*IndexedBlock `protobuf:"bytes,1,rep,name=blocks,proto3" json:"blocks,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListBlocksResponse) Reset() { *m = QueryListBlocksResponse{} } +func (m *QueryListBlocksResponse) String() string { return proto.CompactTextString(m) } +func (*QueryListBlocksResponse) ProtoMessage() {} +func (*QueryListBlocksResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{5} +} +func (m *QueryListBlocksResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListBlocksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListBlocksResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListBlocksResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListBlocksResponse.Merge(m, src) +} +func (m *QueryListBlocksResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryListBlocksResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListBlocksResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListBlocksResponse proto.InternalMessageInfo + +func (m *QueryListBlocksResponse) GetBlocks() []*IndexedBlock { + if m != nil { + return m.Blocks + } + return nil +} + +func (m *QueryListBlocksResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. type QueryVotesAtHeightRequest struct { @@ -124,7 +342,7 @@ func (m *QueryVotesAtHeightRequest) Reset() { *m = QueryVotesAtHeightReq func (m *QueryVotesAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryVotesAtHeightRequest) ProtoMessage() {} func (*QueryVotesAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{2} + return fileDescriptor_32bddab77af6fdae, []int{6} } func (m *QueryVotesAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -172,7 +390,7 @@ func (m *QueryVotesAtHeightResponse) Reset() { *m = QueryVotesAtHeightRe func (m *QueryVotesAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryVotesAtHeightResponse) ProtoMessage() {} func (*QueryVotesAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{3} + return fileDescriptor_32bddab77af6fdae, []int{7} } func (m *QueryVotesAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -204,6 +422,11 @@ var xxx_messageInfo_QueryVotesAtHeightResponse proto.InternalMessageInfo func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.finality.v1.QueryParamsResponse") + proto.RegisterType((*QueryListPublicRandomnessRequest)(nil), "babylon.finality.v1.QueryListPublicRandomnessRequest") + proto.RegisterType((*QueryListPublicRandomnessResponse)(nil), "babylon.finality.v1.QueryListPublicRandomnessResponse") + proto.RegisterMapType((map[uint64]*github_com_babylonchain_babylon_types.SchnorrPubRand)(nil), "babylon.finality.v1.QueryListPublicRandomnessResponse.PubRandMapEntry") + proto.RegisterType((*QueryListBlocksRequest)(nil), "babylon.finality.v1.QueryListBlocksRequest") + proto.RegisterType((*QueryListBlocksResponse)(nil), "babylon.finality.v1.QueryListBlocksResponse") proto.RegisterType((*QueryVotesAtHeightRequest)(nil), "babylon.finality.v1.QueryVotesAtHeightRequest") proto.RegisterType((*QueryVotesAtHeightResponse)(nil), "babylon.finality.v1.QueryVotesAtHeightResponse") } @@ -211,33 +434,56 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 410 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, - 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0x2a, 0xd0, - 0x83, 0x29, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, 0x58, - 0x10, 0xa5, 0x52, 0x32, 0xe9, 0xf9, 0xf9, 0xe9, 0x39, 0xa9, 0xfa, 0x89, 0x05, 0x99, 0xfa, 0x89, - 0x79, 0x79, 0xf9, 0x25, 0x89, 0x25, 0x99, 0xf9, 0x79, 0xc5, 0x50, 0x59, 0x05, 0x6c, 0x36, 0x15, - 0x24, 0x16, 0x25, 0xe6, 0x42, 0x55, 0x28, 0x89, 0x70, 0x09, 0x05, 0x82, 0x6c, 0x0e, 0x00, 0x0b, - 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x28, 0x05, 0x70, 0x09, 0xa3, 0x88, 0x16, 0x17, 0xe4, - 0xe7, 0x15, 0xa7, 0x0a, 0x59, 0x72, 0xb1, 0x41, 0x34, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, - 0x49, 0xeb, 0x61, 0x71, 0xa8, 0x1e, 0x44, 0x93, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, - 0x0d, 0x4a, 0xc6, 0x5c, 0x92, 0x60, 0x13, 0xc3, 0xf2, 0x4b, 0x52, 0x8b, 0x1d, 0x4b, 0x3c, 0x52, - 0x33, 0xd3, 0x33, 0x4a, 0xa0, 0xd6, 0x09, 0x89, 0x71, 0xb1, 0x65, 0x80, 0x05, 0xc0, 0xe6, 0xb2, - 0x04, 0x41, 0x79, 0x4a, 0xb9, 0x5c, 0x52, 0xd8, 0x34, 0x41, 0x5d, 0xe3, 0xcf, 0xc5, 0x9e, 0x54, - 0x92, 0x1c, 0x5f, 0x90, 0x0d, 0x72, 0x0e, 0xb3, 0x06, 0x8f, 0x93, 0xd9, 0xad, 0x7b, 0xf2, 0x46, - 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0xc7, 0x25, 0x67, 0x24, - 0x66, 0xe6, 0xc1, 0x38, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x7a, 0x4e, 0x9e, 0x01, 0xc6, 0x26, - 0x06, 0x01, 0xa5, 0x49, 0xde, 0xa9, 0x95, 0x41, 0x6c, 0x49, 0x25, 0xc9, 0x01, 0xd9, 0xc5, 0x46, - 0x47, 0x98, 0xb8, 0x58, 0xc1, 0xf6, 0x09, 0xf5, 0x32, 0x72, 0xb1, 0x41, 0xbc, 0x21, 0xa4, 0x8e, - 0xd5, 0x8f, 0x98, 0x61, 0x26, 0xa5, 0x41, 0x58, 0x21, 0xc4, 0xe1, 0x4a, 0x06, 0x4d, 0x97, 0x9f, - 0x4c, 0x66, 0xd2, 0x12, 0xd2, 0xc0, 0xee, 0x42, 0xcc, 0xb8, 0x12, 0x5a, 0xc5, 0xc8, 0xc5, 0x8b, - 0x12, 0x08, 0x42, 0x7a, 0xb8, 0x6d, 0xc3, 0x16, 0xc4, 0x52, 0xfa, 0x44, 0xab, 0x87, 0x3a, 0xd2, - 0x02, 0xec, 0x48, 0x23, 0x21, 0x03, 0xc2, 0x8e, 0x2c, 0x03, 0x19, 0xa0, 0x5f, 0x0d, 0x89, 0xb4, - 0x5a, 0x27, 0xaf, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, - 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x20, 0x14, - 0x39, 0x15, 0x08, 0x73, 0xc1, 0xf1, 0x94, 0xc4, 0x06, 0x4e, 0xa5, 0xc6, 0x80, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x27, 0xc4, 0xe1, 0xef, 0x33, 0x03, 0x00, 0x00, + // 778 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4f, 0xdb, 0x48, + 0x14, 0x8e, 0x03, 0x64, 0x97, 0x81, 0xfd, 0xa1, 0x21, 0x62, 0xd9, 0x2c, 0x4a, 0x82, 0x57, 0x5a, + 0x22, 0x76, 0x65, 0x27, 0x81, 0x45, 0xec, 0x4a, 0x7b, 0xd8, 0x48, 0xa5, 0xd0, 0x1f, 0xaa, 0xeb, + 0x4a, 0x55, 0xc5, 0x25, 0x1d, 0x3b, 0x53, 0xc7, 0x8a, 0xe3, 0x31, 0xf6, 0xd8, 0x4a, 0x8a, 0xe8, + 0xa1, 0xf7, 0xaa, 0x95, 0x7a, 0xea, 0xa1, 0x97, 0x1e, 0xfb, 0x97, 0xa0, 0x9e, 0x90, 0x7a, 0xa9, + 0x38, 0xa0, 0x0a, 0x2a, 0xf5, 0xdf, 0xa8, 0x3c, 0x33, 0x49, 0x48, 0x31, 0x04, 0x2a, 0x6e, 0x99, + 0xf1, 0x7b, 0xef, 0xfb, 0xde, 0xf7, 0xde, 0x37, 0x01, 0x05, 0x03, 0x19, 0x5d, 0x87, 0xb8, 0xea, + 0x23, 0xdb, 0x45, 0x8e, 0x4d, 0xbb, 0x6a, 0x54, 0x51, 0xb7, 0x43, 0xec, 0x77, 0x15, 0xcf, 0x27, + 0x94, 0xc0, 0x19, 0x11, 0xa0, 0xf4, 0x02, 0x94, 0xa8, 0x92, 0xcb, 0x5a, 0xc4, 0x22, 0xec, 0xbb, + 0x1a, 0xff, 0xe2, 0xa1, 0xb9, 0x79, 0x8b, 0x10, 0xcb, 0xc1, 0x2a, 0xf2, 0x6c, 0x15, 0xb9, 0x2e, + 0xa1, 0x88, 0xda, 0xc4, 0x0d, 0xc4, 0xd7, 0x25, 0x93, 0x04, 0x6d, 0x12, 0xa8, 0x06, 0x0a, 0x30, + 0x47, 0x50, 0xa3, 0x8a, 0x81, 0x29, 0xaa, 0xa8, 0x1e, 0xb2, 0x6c, 0x97, 0x05, 0x8b, 0xd8, 0x62, + 0x12, 0x2b, 0x0f, 0xf9, 0xa8, 0xdd, 0xab, 0x26, 0x27, 0x45, 0xf4, 0x29, 0xb2, 0x18, 0x39, 0x0b, + 0xe0, 0xdd, 0x18, 0x47, 0x63, 0x89, 0x3a, 0xde, 0x0e, 0x71, 0x40, 0x65, 0x0d, 0xcc, 0x0c, 0xdd, + 0x06, 0x1e, 0x71, 0x03, 0x0c, 0xff, 0x01, 0x19, 0x0e, 0x30, 0x27, 0x15, 0xa5, 0xd2, 0x54, 0xf5, + 0x37, 0x25, 0xa1, 0x71, 0x85, 0x27, 0xd5, 0xc6, 0xf7, 0x0e, 0x0b, 0x29, 0x5d, 0x24, 0xc8, 0xcf, + 0x25, 0x50, 0x64, 0x25, 0x6f, 0xd9, 0x01, 0xd5, 0x42, 0xc3, 0xb1, 0x4d, 0x1d, 0xb9, 0x0d, 0xd2, + 0x76, 0x71, 0xd0, 0x83, 0x85, 0xbf, 0x83, 0x1f, 0x23, 0xe4, 0xd4, 0x0d, 0x6a, 0xd6, 0xbd, 0x56, + 0xbd, 0x89, 0x3b, 0x0c, 0x67, 0x52, 0x9f, 0x8a, 0x90, 0x53, 0xa3, 0xa6, 0xd6, 0xda, 0xc0, 0x1d, + 0xb8, 0x0e, 0xc0, 0x40, 0x8b, 0xb9, 0x34, 0x23, 0xf2, 0x87, 0xc2, 0x85, 0x53, 0x62, 0xe1, 0x14, + 0x3e, 0x1a, 0x21, 0x9c, 0xa2, 0x21, 0x0b, 0x0b, 0x00, 0xfd, 0x44, 0xa6, 0xbc, 0x9f, 0x06, 0x0b, + 0xe7, 0x30, 0x12, 0x2d, 0xbf, 0x91, 0xc0, 0xb4, 0x17, 0x1a, 0x75, 0x1f, 0xb9, 0x8d, 0x7a, 0x1b, + 0x79, 0x73, 0x52, 0x71, 0xac, 0x34, 0x55, 0x5d, 0x4f, 0xec, 0x7c, 0x64, 0x39, 0x45, 0x0b, 0x8d, + 0xf8, 0xf6, 0x36, 0xf2, 0xae, 0xb9, 0xd4, 0xef, 0xd6, 0xd6, 0x0e, 0x0e, 0x0b, 0x2b, 0x96, 0x4d, + 0x9b, 0xa1, 0xa1, 0x98, 0xa4, 0xad, 0x8a, 0xaa, 0x66, 0x13, 0xd9, 0x6e, 0xef, 0xa0, 0xd2, 0xae, + 0x87, 0x03, 0xe5, 0x9e, 0xd9, 0x74, 0x89, 0xef, 0x8b, 0x0a, 0x3a, 0xf0, 0xfa, 0xa5, 0xe0, 0xf5, + 0x04, 0x49, 0x16, 0x47, 0x4a, 0xc2, 0x29, 0x9d, 0xd4, 0x24, 0xf7, 0x1f, 0xf8, 0xe9, 0x2b, 0x86, + 0xf0, 0x67, 0x30, 0xd6, 0xc2, 0x5d, 0x36, 0x88, 0x71, 0x3d, 0xfe, 0x09, 0xb3, 0x60, 0x22, 0x42, + 0x4e, 0x88, 0x19, 0xd0, 0xb4, 0xce, 0x0f, 0xff, 0xa6, 0xd7, 0x24, 0xf9, 0x09, 0x98, 0xed, 0x4b, + 0x50, 0x73, 0x88, 0xd9, 0xea, 0x4f, 0x76, 0x1e, 0x4c, 0x72, 0xa1, 0x1e, 0xe3, 0x06, 0xab, 0xf5, + 0xbd, 0x3e, 0xb8, 0xb8, 0xb2, 0x91, 0xbe, 0x96, 0xc0, 0x2f, 0xa7, 0x08, 0x0c, 0x76, 0xd7, 0x60, + 0x37, 0x62, 0x82, 0x0b, 0x89, 0x13, 0xdc, 0x74, 0x1b, 0xb8, 0x83, 0x1b, 0x2c, 0x57, 0x17, 0x09, + 0x57, 0x26, 0xaf, 0xbc, 0x0c, 0x7e, 0x65, 0xf4, 0xee, 0x13, 0x8a, 0x83, 0xff, 0xe9, 0x06, 0xb6, + 0xad, 0x26, 0xed, 0x49, 0x34, 0x0b, 0x32, 0x4d, 0x76, 0x21, 0xb4, 0x16, 0x27, 0xb9, 0x0d, 0x72, + 0x49, 0x49, 0xa2, 0xad, 0x3b, 0xe0, 0x3b, 0x6e, 0x17, 0xde, 0xd7, 0x74, 0x6d, 0xf5, 0xe0, 0xb0, + 0x50, 0xbd, 0xd8, 0x46, 0xd5, 0x36, 0xb5, 0xe5, 0x95, 0xb2, 0x16, 0x1a, 0x37, 0x71, 0x57, 0xcf, + 0x18, 0xb1, 0xc1, 0x82, 0xea, 0xbb, 0x09, 0x30, 0xc1, 0xf0, 0xe0, 0x33, 0x09, 0x64, 0xb8, 0x97, + 0xe1, 0xe2, 0xd9, 0xeb, 0x3e, 0xf4, 0x70, 0xe4, 0x4a, 0xa3, 0x03, 0x39, 0x71, 0xb9, 0xfc, 0xf4, + 0xfd, 0xa7, 0x97, 0xe9, 0x25, 0x58, 0x4a, 0x66, 0x78, 0xfa, 0x51, 0x83, 0x9f, 0x25, 0x90, 0x4d, + 0x32, 0x17, 0xfc, 0xfb, 0xb2, 0x66, 0xe4, 0x5c, 0x57, 0xbf, 0xcd, 0xc3, 0xf2, 0x43, 0xc6, 0x7c, + 0x0b, 0x3e, 0x18, 0xcd, 0x3c, 0x1e, 0x4d, 0x84, 0x1c, 0xbb, 0x81, 0x28, 0xf1, 0x03, 0x75, 0x67, + 0xf8, 0x75, 0xdb, 0x55, 0x3d, 0x86, 0xc1, 0x1e, 0x17, 0x0e, 0x52, 0x77, 0xec, 0x80, 0xc2, 0x57, + 0x12, 0x00, 0x83, 0x15, 0x86, 0x7f, 0x9e, 0x4f, 0x74, 0xc8, 0x69, 0xb9, 0xbf, 0x2e, 0x16, 0x7c, + 0xf9, 0x29, 0x08, 0x33, 0xbc, 0x95, 0xc0, 0x0f, 0x43, 0xab, 0x08, 0x95, 0xb3, 0x11, 0x93, 0x16, + 0x3d, 0xa7, 0x5e, 0x38, 0x5e, 0x90, 0x5c, 0x63, 0x24, 0xab, 0xb0, 0x3c, 0x9a, 0x64, 0x14, 0x17, + 0x50, 0x77, 0xb8, 0x75, 0x76, 0x6b, 0x37, 0xf6, 0x8e, 0xf2, 0xd2, 0xfe, 0x51, 0x5e, 0xfa, 0x78, + 0x94, 0x97, 0x5e, 0x1c, 0xe7, 0x53, 0xfb, 0xc7, 0xf9, 0xd4, 0x87, 0xe3, 0x7c, 0x6a, 0xab, 0x3c, + 0xca, 0x22, 0x9d, 0x41, 0x5d, 0xe6, 0x16, 0x23, 0xc3, 0xfe, 0x30, 0x97, 0xbf, 0x04, 0x00, 0x00, + 0xff, 0xff, 0xf5, 0xad, 0xaa, 0x4c, 0x0e, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -254,6 +500,10 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // ListPublicRandomness is a range query for public randomness of a given BTC validator + ListPublicRandomness(ctx context.Context, in *QueryListPublicRandomnessRequest, opts ...grpc.CallOption) (*QueryListPublicRandomnessResponse, error) + // ListBlocks is a range query for blocks at a given status + ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) // VotesAtHeight queries btc validators who have signed the block at given height. VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) } @@ -275,6 +525,24 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } +func (c *queryClient) ListPublicRandomness(ctx context.Context, in *QueryListPublicRandomnessRequest, opts ...grpc.CallOption) (*QueryListPublicRandomnessResponse, error) { + out := new(QueryListPublicRandomnessResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/ListPublicRandomness", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) { + out := new(QueryListBlocksResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/ListBlocks", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) { out := new(QueryVotesAtHeightResponse) err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/VotesAtHeight", in, out, opts...) @@ -288,6 +556,10 @@ func (c *queryClient) VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightR type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // ListPublicRandomness is a range query for public randomness of a given BTC validator + ListPublicRandomness(context.Context, *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) + // ListBlocks is a range query for blocks at a given status + ListBlocks(context.Context, *QueryListBlocksRequest) (*QueryListBlocksResponse, error) // VotesAtHeight queries btc validators who have signed the block at given height. VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) } @@ -299,6 +571,12 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) ListPublicRandomness(ctx context.Context, req *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPublicRandomness not implemented") +} +func (*UnimplementedQueryServer) ListBlocks(ctx context.Context, req *QueryListBlocksRequest) (*QueryListBlocksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListBlocks not implemented") +} func (*UnimplementedQueryServer) VotesAtHeight(ctx context.Context, req *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VotesAtHeight not implemented") } @@ -325,6 +603,42 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Query_ListPublicRandomness_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryListPublicRandomnessRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ListPublicRandomness(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/ListPublicRandomness", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ListPublicRandomness(ctx, req.(*QueryListPublicRandomnessRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ListBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryListBlocksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ListBlocks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/ListBlocks", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ListBlocks(ctx, req.(*QueryListBlocksRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_VotesAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryVotesAtHeightRequest) if err := dec(in); err != nil { @@ -351,6 +665,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "ListPublicRandomness", + Handler: _Query_ListPublicRandomness_Handler, + }, + { + MethodName: "ListBlocks", + Handler: _Query_ListBlocks_Handler, + }, { MethodName: "VotesAtHeight", Handler: _Query_VotesAtHeight_Handler, @@ -416,7 +738,7 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryVotesAtHeightRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryListPublicRandomnessRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -426,25 +748,39 @@ func (m *QueryVotesAtHeightRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryVotesAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryListPublicRandomnessRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryVotesAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryListPublicRandomnessRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Height != 0 { - i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x8 + dAtA[i] = 0x12 + } + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryVotesAtHeightResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryListPublicRandomnessResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -454,26 +790,48 @@ func (m *QueryVotesAtHeightResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryVotesAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryListPublicRandomnessResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryVotesAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryListPublicRandomnessResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.BtcPks) > 0 { - for iNdEx := len(m.BtcPks) - 1; iNdEx >= 0; iNdEx-- { - { - size := m.BtcPks[iNdEx].Size() - i -= size - if _, err := m.BtcPks[iNdEx].MarshalTo(dAtA[i:]); err != nil { - return 0, err + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.PubRandMap) > 0 { + for k := range m.PubRandMap { + v := m.PubRandMap[k] + baseI := i + if v != nil { + { + size := v.Size() + i -= size + if _, err := v.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) } - i = encodeVarintQuery(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } + i = encodeVarintQuery(dAtA, i, uint64(k)) + i-- + dAtA[i] = 0x8 + i = encodeVarintQuery(dAtA, i, uint64(baseI-i)) i-- dAtA[i] = 0xa } @@ -481,47 +839,284 @@ func (m *QueryVotesAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *QueryListBlocksRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *QueryParamsRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n + +func (m *QueryListBlocksRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryParamsResponse) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryListBlocksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = m.Params.Size() - n += 1 + l + sovQuery(uint64(l)) - return n + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Finalized { + i-- + if m.Finalized { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil } -func (m *QueryVotesAtHeightRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Height != 0 { - n += 1 + sovQuery(uint64(m.Height)) +func (m *QueryListBlocksResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - return n + return dAtA[:n], nil +} + +func (m *QueryListBlocksResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryListBlocksResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Blocks) > 0 { + for iNdEx := len(m.Blocks) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Blocks[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryVotesAtHeightRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVotesAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVotesAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryVotesAtHeightResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVotesAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVotesAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcPks) > 0 { + for iNdEx := len(m.BtcPks) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.BtcPks[iNdEx].Size() + i -= size + if _, err := m.BtcPks[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryListPublicRandomnessRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValBtcPkHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryListPublicRandomnessResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.PubRandMap) > 0 { + for k, v := range m.PubRandMap { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovQuery(uint64(l)) + } + mapEntrySize := 1 + sovQuery(uint64(k)) + l + n += mapEntrySize + 1 + sovQuery(uint64(mapEntrySize)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryListBlocksRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Finalized { + n += 2 + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryListBlocksResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Blocks) > 0 { + for _, e := range m.Blocks { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryVotesAtHeightRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n } func (m *QueryVotesAtHeightResponse) Size() (n int) { @@ -678,6 +1273,552 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryListPublicRandomnessRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListPublicRandomnessRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListPublicRandomnessRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryListPublicRandomnessResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListPublicRandomnessResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListPublicRandomnessResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PubRandMap", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PubRandMap == nil { + m.PubRandMap = make(map[uint64]*github_com_babylonchain_babylon_types.SchnorrPubRand) + } + var mapkey uint64 + var mapvalue1 github_com_babylonchain_babylon_types.SchnorrPubRand + var mapvalue = &mapvalue1 + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + } else if fieldNum == 2 { + var mapbyteLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapbyteLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intMapbyteLen := int(mapbyteLen) + if intMapbyteLen < 0 { + return ErrInvalidLengthQuery + } + postbytesIndex := iNdEx + intMapbyteLen + if postbytesIndex < 0 { + return ErrInvalidLengthQuery + } + if postbytesIndex > l { + return io.ErrUnexpectedEOF + } + if err := mapvalue.Unmarshal(dAtA[iNdEx:postbytesIndex]); err != nil { + return err + } + iNdEx = postbytesIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.PubRandMap[mapkey] = ((*github_com_babylonchain_babylon_types.SchnorrPubRand)(mapvalue)) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryListBlocksRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListBlocksRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListBlocksRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Finalized", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Finalized = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryListBlocksResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListBlocksResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListBlocksResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Blocks", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Blocks = append(m.Blocks, &IndexedBlock{}) + if err := m.Blocks[len(m.Blocks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryVotesAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index f816dc803..110402d7e 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -51,6 +51,114 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +var ( + filter_Query_ListPublicRandomness_0 = &utilities.DoubleArray{Encoding: map[string]int{"val_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_ListPublicRandomness_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListPublicRandomnessRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListPublicRandomness_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListPublicRandomness(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ListPublicRandomness_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListPublicRandomnessRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListPublicRandomness_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListPublicRandomness(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ListBlocks_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_ListBlocks_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListBlocksRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListBlocks_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListBlocks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ListBlocks_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListBlocksRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListBlocks_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListBlocks(ctx, &protoReq) + return msg, metadata, err + +} + func request_Query_VotesAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryVotesAtHeightRequest var metadata runtime.ServerMetadata @@ -134,6 +242,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_ListPublicRandomness_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ListPublicRandomness_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListPublicRandomness_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ListBlocks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ListBlocks_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListBlocks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_VotesAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -218,6 +372,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_ListPublicRandomness_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ListPublicRandomness_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListPublicRandomness_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ListBlocks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ListBlocks_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListBlocks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_VotesAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -244,11 +438,19 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"babylonchain", "babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ListBlocks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "blocks"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage + forward_Query_ListPublicRandomness_0 = runtime.ForwardResponseMessage + + forward_Query_ListBlocks_0 = runtime.ForwardResponseMessage + forward_Query_VotesAtHeight_0 = runtime.ForwardResponseMessage ) From c90799927f89880998e3d753c2195c26134b3a5e Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:32:50 -0400 Subject: [PATCH 033/202] feat: fuzz tests, cli queries and other fixes in finality rpc. (#26) * fix finality query * add comments * fuzz tests * more checks * fix cond * add cli queries * fix comment * add comments * more checks --- x/finality/client/cli/query.go | 33 +++++++++++++++++- x/finality/keeper/grpc_query.go | 6 ++-- x/finality/keeper/grpc_query_test.go | 51 +++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index 30dd613cc..e8919d1a1 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -2,10 +2,11 @@ package cli import ( "fmt" + "github.com/cosmos/cosmos-sdk/client/flags" + "strconv" "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" ) @@ -23,6 +24,36 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdListPublicRandomness()) cmd.AddCommand(CmdListBlocks()) + cmd.AddCommand(CmdVotesAtHeight()) + + return cmd +} + +func CmdVotesAtHeight() *cobra.Command { + cmd := &cobra.Command{ + Use: "votes-at-height [height]", + Short: "retrieve all btc val pks who voted at requested babylon height", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + res, err := queryClient.VotesAtHeight(cmd.Context(), &types.QueryVotesAtHeightRequest{Height: height}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) return cmd } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 6fd41ab91..4f824cd61 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -81,9 +81,9 @@ func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeight sdkCtx := sdk.UnwrapSDKContext(ctx) var btcPks []bbn.BIP340PubKey - // get the validator set of block at given height - valSet := k.BTCStakingKeeper.GetVotingPowerTable(sdkCtx, req.Height) - for pkHex := range valSet { + // get the sig set of babylon block at given height + sigSet := k.GetSigSet(sdkCtx, req.Height) + for pkHex := range sigSet { pk, err := bbn.NewBIP340PubKeyFromHex(pkHex) if err != nil { // failing to unmarshal validator BTC PK in KVStore is a programming error diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 2f9606287..80ae54f1c 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -1,15 +1,16 @@ package keeper_test import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" "math/rand" "testing" "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" - "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" - query "github.com/cosmos/cosmos-sdk/types/query" - "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/types/query" ) func FuzzListPublicRandomness(f *testing.F) { @@ -96,3 +97,45 @@ func FuzzListBlocks(f *testing.F) { } }) } + +func FuzzVotesAtHeight(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // Add random number of voted validators to the store + babylonHeight := datagen.RandomInt(r, 10) + 1 + numVotedVals := datagen.RandomInt(r, 10) + 1 + votedValMap := make(map[string]bool, numVotedVals) + for i := uint64(0); i < numVotedVals; i++ { + votedValPK, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + keeper.SetSig(ctx, babylonHeight, votedValPK, votedSig) + + votedValMap[votedValPK.ToHexStr()] = true + } + + resp, err := keeper.VotesAtHeight(ctx, &types.QueryVotesAtHeightRequest{ + Height: babylonHeight, + }) + require.NoError(t, err) + + // Check if all voted validators are returned + valFoundMap := make(map[string]bool) + for _, pk := range resp.BtcPks { + if _, ok := votedValMap[pk.ToHexStr()]; !ok { + t.Fatalf("rpc returned a val that was not created") + } + valFoundMap[pk.ToHexStr()] = true + } + if len(valFoundMap) != len(votedValMap) { + t.Errorf("Some vals were missed. Got %d while %d were expected", len(valFoundMap), len(votedValMap)) + } + }) +} From 5aa5d394a5f545bf3d1d675870dc150e4052c362 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Tue, 18 Jul 2023 21:47:58 -0400 Subject: [PATCH 034/202] fix: ListBlocks rpc response (#27) * fix bug * fix bug * fix cmts * update condition Co-authored-by: Runchao Han --------- Co-authored-by: Runchao Han --- x/finality/keeper/grpc_query.go | 19 ++++++---- x/finality/keeper/grpc_query_test.go | 53 +++++++++++++++++++++------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 4f824cd61..785d0d09a 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -4,12 +4,13 @@ import ( "context" "fmt" - bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" ) var _ types.QueryServer = Keeper{} @@ -54,13 +55,19 @@ func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksReques sdkCtx := sdk.UnwrapSDKContext(ctx) store := k.blockStore(sdkCtx) ibs := []*types.IndexedBlock{} - pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) { var ib types.IndexedBlock k.cdc.MustUnmarshal(value, &ib) - if ib.Finalized == req.Finalized { - ibs = append(ibs, &ib) + + // return finalized blocks if the request is for finalized blocks + // otherwise only return non-finalized blocks + if (req.Finalized && ib.Finalized) || (!req.Finalized && !ib.Finalized) { + if accumulate { + ibs = append(ibs, &ib) + } + return true, nil } - return nil + return false, nil }) if err != nil { return nil, status.Error(codes.Internal, err.Error()) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 80ae54f1c..0ebcf6cc4 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -1,16 +1,20 @@ package keeper_test import ( - bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" + "fmt" "math/rand" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/finality/types" + + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" - "github.com/cosmos/cosmos-sdk/types/query" ) func FuzzListPublicRandomness(f *testing.F) { @@ -64,37 +68,60 @@ func FuzzListBlocks(f *testing.F) { // index a random list of finalised blocks startHeight := datagen.RandomInt(r, 100) numIndexedBlocks := datagen.RandomInt(r, 100) + 1 - finalizedIndexedBlocks := map[uint64]*types.IndexedBlock{} + finalizedIndexedBlocks := make(map[uint64]*types.IndexedBlock) + nonFinalizedIndexedBlocks := make(map[uint64]*types.IndexedBlock) for i := startHeight; i < startHeight+numIndexedBlocks; i++ { ib := &types.IndexedBlock{ - Height: startHeight + numIndexedBlocks, + Height: i, LastCommitHash: datagen.GenRandomByteArray(r, 32), } // randomly finalise some of them if datagen.RandomInt(r, 2) == 1 { ib.Finalized = true finalizedIndexedBlocks[ib.Height] = ib + } else { + nonFinalizedIndexedBlocks[ib.Height] = ib } // insert to KVStore keeper.SetBlock(ctx, ib) } - // perform a query to pubrand list and assert consistency + // perform a query to fetch finalized blocks and assert consistency // NOTE: pagination is already tested in Cosmos SDK so we don't test it here again, // instead only ensure it takes effect - limit := datagen.RandomInt(r, int(numIndexedBlocks)-1) + 1 + limit := datagen.RandomInt(r, len(finalizedIndexedBlocks)) + 1 req := &types.QueryListBlocksRequest{ Finalized: true, Pagination: &query.PageRequest{ - Limit: limit, + CountTotal: true, + Limit: limit, }, } - resp, err := keeper.ListBlocks(ctx, req) + resp1, err := keeper.ListBlocks(ctx, req) require.NoError(t, err) - require.LessOrEqual(t, len(resp.Blocks), int(limit)) // check if pagination takes effect - for _, actualIB := range resp.Blocks { + require.LessOrEqual(t, len(resp1.Blocks), int(limit)) // check if pagination takes effect + fmt.Println(resp1.Pagination.Total, len(nonFinalizedIndexedBlocks)) + require.EqualValues(t, resp1.Pagination.Total, len(finalizedIndexedBlocks)) + for _, actualIB := range resp1.Blocks { require.Equal(t, finalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) } + + // perform a query to fetch non-finalized blocks and assert consistency + limit = datagen.RandomInt(r, len(nonFinalizedIndexedBlocks)) + 1 + req = &types.QueryListBlocksRequest{ + Finalized: false, + Pagination: &query.PageRequest{ + CountTotal: true, + Limit: limit, + }, + } + resp2, err := keeper.ListBlocks(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp2.Blocks), int(limit)) // check if pagination takes effect + require.EqualValues(t, resp2.Pagination.Total, len(nonFinalizedIndexedBlocks)) + for _, actualIB := range resp2.Blocks { + require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + } }) } From a2a687c51a7aa2df8a3e7a7d49dc5c79c5192be6 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Thu, 20 Jul 2023 09:35:45 +0300 Subject: [PATCH 035/202] feat: Add delegation status to types and use it in BTCDelegations query (#29) --------- Co-authored-by: Runchao Han --- proto/babylon/btcstaking/v1/btcstaking.proto | 10 + proto/babylon/btcstaking/v1/query.proto | 23 +- test/e2e/btc_staking_e2e_test.go | 6 +- .../configurer/chain/queries_btcstaking.go | 10 +- x/btcstaking/client/cli/query.go | 19 +- x/btcstaking/keeper/grpc_query.go | 32 +- x/btcstaking/keeper/grpc_query_test.go | 141 ++++++--- x/btcstaking/keeper/msg_server.go | 2 +- x/btcstaking/keeper/msg_server_test.go | 4 +- x/btcstaking/types/btcstaking.go | 36 ++- x/btcstaking/types/btcstaking.pb.go | 120 +++++--- x/btcstaking/types/query.pb.go | 283 +++++++++--------- x/btcstaking/types/query.pb.gw.go | 34 +-- x/btcstaking/types/utils.go | 18 ++ x/finality/keeper/grpc_query.go | 2 +- x/finality/keeper/grpc_query_test.go | 64 ++-- x/finality/types/query.pb.go | 2 + 17 files changed, 478 insertions(+), 328 deletions(-) create mode 100644 x/btcstaking/types/utils.go diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f3880b523..2fa85c9c5 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -99,3 +99,13 @@ message StakingTx { // staking_script is the script for the staking tx's output bytes staking_script = 2; } + +// BTCDelegationStatus is the status of a delegation. +enum BTCDelegationStatus { + // PENDING defines a delegation that is waiting for a jury signature to become active. + PENDING = 0; + // ACTIVE defines a delegation that has voting power + ACTIVE = 1; + // EXPIRED defines a delegation that has expired + EXPIRED = 2; +} diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 8a98aa49f..396a23368 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -21,12 +21,12 @@ service Query { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators"; } - // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. - rpc BTCValidatorsAtHeight(QueryBTCValidatorsAtHeightRequest) returns (QueryBTCValidatorsAtHeightResponse) { + // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + rpc ActiveBTCValidatorsAtHeight(QueryActiveBTCValidatorsAtHeightRequest) returns (QueryActiveBTCValidatorsAtHeightResponse) { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{height}"; } - // BTCValidatorPowerAtHeight queries a btc validator at a given height + // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height rpc BTCValidatorPowerAtHeight(QueryBTCValidatorPowerAtHeightRequest) returns (QueryBTCValidatorPowerAtHeightResponse) { option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; } @@ -88,9 +88,9 @@ message QueryBTCValidatorPowerAtHeightResponse { uint64 voting_power = 1; } -// QueryBTCValidatorsAtHeightRequest is the request type for the -// Query/BTCValidatorsAtHeight RPC method. -message QueryBTCValidatorsAtHeightRequest { +// QueryActiveBTCValidatorsAtHeightRequest is the request type for the +// Query/ActiveBTCValidatorsAtHeight RPC method. +message QueryActiveBTCValidatorsAtHeightRequest { // height defines at which Babylon height to query the btc validators info. uint64 height = 1; @@ -98,9 +98,9 @@ message QueryBTCValidatorsAtHeightRequest { cosmos.base.query.v1beta1.PageRequest pagination = 2; } -// QueryBTCValidatorsAtHeightResponse is the response type for the -// Query/BTCValidatorsAtHeight RPC method. -message QueryBTCValidatorsAtHeightResponse { +// QueryActiveBTCValidatorsAtHeightResponse is the response type for the +// Query/ActiveBTCValidatorsAtHeight RPC method. +message QueryActiveBTCValidatorsAtHeightResponse { // btc_validators contains all the queried btc validators. repeated BTCValidatorWithMeta btc_validators = 1; @@ -125,9 +125,8 @@ message QueryBTCValidatorDelegationsRequest { // the PK follows encoding in BIP-340 spec string val_btc_pk_hex = 1; - // no_jury_sig_only indicates this query will only return BTC delegations that haven't - // received a jury signature yet - bool no_jury_sig_only = 2; + // delegation_status is the status of the delegations we are filtering for + BTCDelegationStatus del_status = 2; // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 3; diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 24a50db05..938af530b 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -124,6 +124,10 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() + pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr(), bstypes.BTCDelegationStatus_PENDING) + s.Len(pendingDels, 1) + s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + /* generate and insert new jury signature, in order to activate the BTC delegation */ @@ -142,7 +146,7 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { nonValidatorNode.WaitForNextBlock() // query the existence of BTC delegation and assert equivalence - actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr()) + actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr(), bstypes.BTCDelegationStatus_ACTIVE) s.Len(actualDels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], actualDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) } diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index f67902561..b677b8009 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -30,21 +30,23 @@ func (n *NodeConfig) QueryBTCValidators() []*bstypes.BTCValidator { return resp.BtcValidators } -func (n *NodeConfig) QueryBTCValidatorsAtHeight(height uint64) []*bstypes.BTCValidatorWithMeta { +func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes.BTCValidatorWithMeta { path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%d", height) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryBTCValidatorsAtHeightResponse + var resp bstypes.QueryActiveBTCValidatorsAtHeightResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) return resp.BtcValidators } -func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegation { +func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string, status bstypes.BTCDelegationStatus) []*bstypes.BTCDelegation { path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) - bz, err := n.QueryGRPCGateway(path, url.Values{}) + values := url.Values{} + values.Set("del_status", fmt.Sprintf("%d", status)) + bz, err := n.QueryGRPCGateway(path, values) require.NoError(n.t, err) var resp bstypes.QueryBTCValidatorDelegationsResponse diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 48d8cacae..0d238d54f 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -11,6 +11,8 @@ import ( "github.com/spf13/cobra" ) +const flagDelegationStatus = "delegation-status" + // GetQueryCmd returns the cli query commands for this module func GetQueryCmd(queryRoute string) *cobra.Command { // Group btcstaking queries under a subcommand @@ -138,7 +140,7 @@ func CmdBTCValidatorsAtHeight() *cobra.Command { return err } - res, err := queryClient.BTCValidatorsAtHeight(cmd.Context(), &types.QueryBTCValidatorsAtHeightRequest{ + res, err := queryClient.ActiveBTCValidatorsAtHeight(cmd.Context(), &types.QueryActiveBTCValidatorsAtHeightRequest{ Height: height, Pagination: pageReq, }) @@ -169,15 +171,20 @@ func CmdBTCValidatorDelegations() *cobra.Command { if err != nil { return err } - noJurySigOnly, err := cmd.Flags().GetBool("no_jury_sig_only") + delegationStatusString, err := cmd.Flags().GetString(flagDelegationStatus) + if err != nil { + return err + } + + delegationStatus, err := types.NewBTCDelegationStatus(delegationStatusString) if err != nil { return err } res, err := queryClient.BTCValidatorDelegations(cmd.Context(), &types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: args[0], - NoJurySigOnly: noJurySigOnly, - Pagination: pageReq, + ValBtcPkHex: args[0], + DelStatus: delegationStatus, + Pagination: pageReq, }) if err != nil { return err @@ -188,7 +195,7 @@ func CmdBTCValidatorDelegations() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) - cmd.Flags().Bool("no_jury_sig_only", false, "whether to only return BTC delegations that haven't received a jury signature yet") + cmd.Flags().String(flagDelegationStatus, "Active", "Status of the queried delegations (Pending|Active|Expired)") return cmd } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 223ed1cc8..f19647fb1 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -15,6 +15,7 @@ import ( var _ types.QueryServer = Keeper{} +// BTCValidators returns a paginated list of all Babylon maintained validators func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidatorsRequest) (*types.QueryBTCValidatorsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -37,6 +38,8 @@ func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidators return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil } +// BTCValidatorPowerAtHeight returns the voting power of the specified validator +// at the provided Babylon height func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryBTCValidatorPowerAtHeightRequest) (*types.QueryBTCValidatorPowerAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -53,7 +56,8 @@ func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryB return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil } -func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCValidatorsAtHeightRequest) (*types.QueryBTCValidatorsAtHeightResponse, error) { +// ActiveBTCValidatorsAtHeight returns the active BTC validators at the provided height +func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.QueryActiveBTCValidatorsAtHeightRequest) (*types.QueryActiveBTCValidatorsAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } @@ -84,9 +88,11 @@ func (k Keeper) BTCValidatorsAtHeight(ctx context.Context, req *types.QueryBTCVa return nil, err } - return &types.QueryBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil + return &types.QueryActiveBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil } +// ActivatedHeight returns the Babylon height in which the BTC Staking protocol was enabled +// TODO: Requires investigation on whether we can enable the BTC staking protocol at genesis func (k Keeper) ActivatedHeight(ctx context.Context, req *types.QueryActivatedHeightRequest) (*types.QueryActivatedHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -100,6 +106,7 @@ func (k Keeper) ActivatedHeight(ctx context.Context, req *types.QueryActivatedHe return &types.QueryActivatedHeightResponse{Height: activatedHeight}, nil } +// BTCValidatorDelegations returns all the delegations of the provided validator filtered by the provided status. func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTCValidatorDelegationsRequest) (*types.QueryBTCValidatorDelegationsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -118,20 +125,25 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC sdkCtx := sdk.UnwrapSDKContext(ctx) btcDelStore := k.btcDelegationStore(sdkCtx, valPK.MustMarshal()) + // get current BTC height + btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) + if err != nil { + return nil, err + } + // get value of w + wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + var btcDels []*types.BTCDelegation - pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { + pageRes, err := query.FilteredPaginate(btcDelStore, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { var btcDelegation types.BTCDelegation k.cdc.MustUnmarshal(value, &btcDelegation) - if req.NoJurySigOnly { - // only append BTC delegations that do not have jury signature - if btcDelegation.JurySig == nil { + if req.DelStatus == btcDelegation.GetStatus(btcTipHeight, wValue) { + if accumulate { btcDels = append(btcDels, &btcDelegation) } - } else { - // append all BTC delegations, regardless whether it has jury signature or not - btcDels = append(btcDels, &btcDelegation) + return true, nil } - return nil + return false, nil }) if err != nil { return nil, err diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 589adf21d..6103db802 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -1,15 +1,17 @@ package keeper_test import ( - "fmt" "math/rand" "testing" "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -132,7 +134,7 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { }) } -func FuzzBTCValidatorsAtHeight(f *testing.F) { +func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -171,7 +173,7 @@ func FuzzBTCValidatorsAtHeight(f *testing.F) { } // Test nil request - resp, err := keeper.BTCValidatorsAtHeight(ctx, nil) + resp, err := keeper.ActiveBTCValidatorsAtHeight(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -183,16 +185,13 @@ func FuzzBTCValidatorsAtHeight(f *testing.F) { limit := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) + 1 pagination := constructRequestWithLimit(r, limit) // Generate the initial query - req := types.QueryBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} + req := types.QueryActiveBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} // Construct a mapping from the btc vals found to a boolean value // Will be used later to evaluate whether all the btc vals were returned btcValsFound := make(map[string]bool, 0) - votingTable := keeper.GetVotingPowerTable(ctx, babylonHeight) - fmt.Println(votingTable) - for i := uint64(0); i < numBTCValsWithVotingPower; i += limit { - resp, err = keeper.BTCValidatorsAtHeight(ctx, &req) + resp, err = keeper.ActiveBTCValidatorsAtHeight(ctx, &req) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -210,7 +209,7 @@ func FuzzBTCValidatorsAtHeight(f *testing.F) { // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} + req = types.QueryActiveBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} } if len(btcValsFound) != len(btcValsWithVotingPowerMap) { @@ -223,69 +222,117 @@ func FuzzBTCValidatorDelegations(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() // Setup keeper and context - keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // Generate a btc validator btcVal, err := datagen.GenRandomBTCValidator(r) require.NoError(t, err) keeper.SetBTCValidator(ctx, btcVal) + startHeight := datagen.RandomInt(r, 100) + 1 + endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 // Generate a random number of BTC delegations under this validator numBTCDels := datagen.RandomInt(r, 10) + 1 - btcDelsMap := make(map[string]*types.BTCDelegation) + activeBtcDelsMap := make(map[string]*types.BTCDelegation) + pendingBtcDelsMap := make(map[string]*types.BTCDelegation) for j := uint64(0); j < numBTCDels; j++ { - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, 1, 1000, 1) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, startHeight, endHeight, 1) require.NoError(t, err) + if datagen.RandomInt(r, 2) == 1 { + // remove jury sig in random BTC delegations to make them inactive + btcDel.JurySig = nil + pendingBtcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel + } else { + activeBtcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel + } keeper.SetBTCDelegation(ctx, btcDel) - btcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel } // Test nil request resp, err := keeper.BTCValidatorDelegations(ctx, nil) - if resp != nil { - t.Errorf("Nil input led to a non-nil response") - } - if err == nil { - t.Errorf("Nil input led to a nil error") - } + require.Nil(t, resp) + require.Error(t, err) - // Generate a page request with a limit and a nil key - limit := datagen.RandomInt(r, int(numBTCDels)) + 1 - pagination := constructRequestWithLimit(r, limit) - // Generate the initial query - req := types.QueryBTCValidatorDelegationsRequest{ValBtcPkHex: btcVal.BtcPk.ToHexStr(), Pagination: pagination} - // Construct a mapping from the btc vals found to a boolean value - // Will be used later to evaluate whether all the btc vals were returned - btcDelsFound := make(map[string]bool, 0) + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) + keeper.IndexBTCHeight(ctx) - for i := uint64(0); i < numBTCDels; i += limit { - resp, err = keeper.BTCValidatorDelegations(ctx, &req) - if err != nil { - t.Errorf("Valid request led to an error %s", err) - } - if resp == nil { - t.Fatalf("Valid request led to a nil response") + // Generate a page request with a limit and a nil key + // query a page of active BTC delegations and assert consistency + if len(activeBtcDelsMap) > 0 { + limit := datagen.RandomInt(r, len(activeBtcDelsMap)) + 1 + pagination := constructRequestWithLimit(r, limit) + // Generate the initial query + req := types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + DelStatus: types.BTCDelegationStatus_ACTIVE, + Pagination: pagination, } + // Construct a mapping from the btc vals found to a boolean value + // Will be used later to evaluate whether all the btc vals were returned + btcDelsFound := make(map[string]bool, 0) - for _, btcDel := range resp.BtcDelegations { - require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) - - // Check if the pk exists in the map - if _, ok := btcDelsMap[btcDel.BtcPk.ToHexStr()]; !ok { - t.Fatalf("rpc returned a val that was not created") + for i := uint64(0); i < numBTCDels; i += limit { + resp, err = keeper.BTCValidatorDelegations(ctx, &req) + require.NoError(t, err) + require.NotNil(t, resp) + for _, btcDel := range resp.BtcDelegations { + require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) + // Check if the pk exists in the map + _, ok := activeBtcDelsMap[btcDel.BtcPk.ToHexStr()] + require.True(t, ok) + btcDelsFound[btcDel.BtcPk.ToHexStr()] = true + } + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + DelStatus: types.BTCDelegationStatus_ACTIVE, + Pagination: pagination, } - btcDelsFound[btcDel.BtcPk.ToHexStr()] = true } - - // Construct the next page request - pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorDelegationsRequest{ValBtcPkHex: btcVal.BtcPk.ToHexStr(), Pagination: pagination} + require.Equal(t, len(btcDelsFound), len(activeBtcDelsMap)) } - if len(btcDelsFound) != len(btcDelsMap) { - t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcDelsFound), len(btcDelsMap)) + // query a page of pending BTC delegations and assert consistency + if len(pendingBtcDelsMap) > 0 { + limit := datagen.RandomInt(r, len(pendingBtcDelsMap)) + 1 + pagination := constructRequestWithLimit(r, limit) + req := types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig + Pagination: pagination, + } + pendingBtcDelsFound := make(map[string]bool, 0) + + for i := uint64(0); i < uint64(len(pendingBtcDelsMap)); i += limit { + resp, err := keeper.BTCValidatorDelegations(ctx, &req) + require.NoError(t, err) + require.NotNil(t, resp) + for _, btcDel := range resp.BtcDelegations { + require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) + // Check if the pk exists in the map + _, ok := pendingBtcDelsMap[btcDel.BtcPk.ToHexStr()] + require.True(t, ok) + pendingBtcDelsFound[btcDel.BtcPk.ToHexStr()] = true + } + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig + Pagination: pagination, + } + } + require.Equal(t, len(pendingBtcDelsFound), len(pendingBtcDelsMap)) } }) } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index af64fa350..fc8ac5d6a 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -182,7 +182,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) if err != nil { return nil, err } - if btcDel.IsActivated() { + if btcDel.HasJurySig() { return nil, types.ErrDuplicatedJurySig } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index f217132c7..c47e91fda 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -186,7 +186,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { err = actualDel.ValidateBasic() require.NoError(t, err) // delegation is not activated by jury yet - require.False(t, actualDel.IsActivated()) + require.False(t, actualDel.HasJurySig()) /* generate and insert new jury signature @@ -213,6 +213,6 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) require.NoError(t, err) require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) - require.True(t, actualDelWithJurySig.IsActivated()) + require.True(t, actualDelWithJurySig.HasJurySig()) }) } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index e60b693d2..6aee6a4fc 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -76,24 +76,38 @@ func (d *BTCDelegation) ValidateBasic() error { return nil } -// IsActivated returns whether a BTC delegation is activated or not -// a BTC delegation is activated when it receives a signature from jury -func (d *BTCDelegation) IsActivated() bool { +// HasJurySig returns whether a BTC delegation has a jury signature +func (d *BTCDelegation) HasJurySig() bool { return d.JurySig != nil } +// GetStatus returns the status of the BTC Delegation based on a BTC height and a w value +// TODO: Given that we only accept delegations that can be activated immediately, +// we can only have expired delegations. If we accept optimistic submissions, +// we could also have delegations that are in the waiting, so we need an extra status. +// This is covered by expired for now as it is the default value. +// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a jury sig +// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a jury sig +// Expired: Delegation timelock +func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { + if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { + if d.HasJurySig() { + return BTCDelegationStatus_ACTIVE + } else { + return BTCDelegationStatus_PENDING + } + } + return BTCDelegationStatus_EXPIRED +} + // VotingPower returns the voting power of the BTC delegation at a given BTC height -// and a given w value -// The BTC delegation d has voting power iff the following holds -// - d has a jury signature -// - d's timelock start height <= the given BTC height -// - the given BTC height <= d's timelock end height - w +// and a given w value. +// The BTC delegation d has voting power iff it is active. func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { - if d.IsActivated() && d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { - return d.TotalSat - } else { + if d.GetStatus(btcHeight, w) != BTCDelegationStatus_ACTIVE { return 0 } + return d.GetTotalSat() } func (p *ProofOfPossession) ValidateBasic() error { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index c38633898..768f0045c 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -25,6 +25,38 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// BTCDelegationStatus is the status of a delegation. +type BTCDelegationStatus int32 + +const ( + // PENDING defines a delegation that is waiting for a jury signature to become active. + BTCDelegationStatus_PENDING BTCDelegationStatus = 0 + // ACTIVE defines a delegation that has voting power + BTCDelegationStatus_ACTIVE BTCDelegationStatus = 1 + // EXPIRED defines a delegation that has expired + BTCDelegationStatus_EXPIRED BTCDelegationStatus = 2 +) + +var BTCDelegationStatus_name = map[int32]string{ + 0: "PENDING", + 1: "ACTIVE", + 2: "EXPIRED", +} + +var BTCDelegationStatus_value = map[string]int32{ + "PENDING": 0, + "ACTIVE": 1, + "EXPIRED": 2, +} + +func (x BTCDelegationStatus) String() string { + return proto.EnumName(BTCDelegationStatus_name, int32(x)) +} + +func (BTCDelegationStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{0} +} + // BTCValidator defines a BTC validator type BTCValidator struct { // babylon_pk is the Babylon secp256k1 PK of this BTC validator @@ -430,6 +462,7 @@ func (m *StakingTx) GetStakingScript() []byte { } func init() { + proto.RegisterEnum("babylon.btcstaking.v1.BTCDelegationStatus", BTCDelegationStatus_name, BTCDelegationStatus_value) proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") @@ -443,48 +476,51 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 651 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xae, 0xd3, 0x34, 0x4d, 0x26, 0x69, 0xa5, 0xae, 0x5a, 0x14, 0x15, 0x91, 0xb4, 0x91, 0x40, - 0x3d, 0xd9, 0x34, 0xa5, 0x95, 0xe0, 0x00, 0x92, 0xcb, 0x01, 0x04, 0x15, 0x96, 0x1d, 0x81, 0xc4, - 0x01, 0x6b, 0xed, 0x6c, 0x9d, 0x25, 0xae, 0xd7, 0xf2, 0x6e, 0x42, 0xf2, 0x06, 0x1c, 0x79, 0x04, - 0x9e, 0x83, 0x27, 0x40, 0xe2, 0xd2, 0x23, 0xf4, 0x50, 0xa1, 0xf6, 0x45, 0xd0, 0xae, 0x37, 0x51, - 0x68, 0x41, 0x15, 0x2a, 0x5c, 0xb8, 0xcd, 0xce, 0xcf, 0xe7, 0xf9, 0xbe, 0x99, 0x91, 0xe1, 0x4e, - 0x80, 0x83, 0x71, 0xcc, 0x12, 0x2b, 0x10, 0x21, 0x17, 0xb8, 0x4f, 0x93, 0xc8, 0x1a, 0x6e, 0xcf, - 0xbc, 0xcc, 0x34, 0x63, 0x82, 0xa1, 0x35, 0x9d, 0x67, 0xce, 0x44, 0x86, 0xdb, 0xeb, 0xab, 0x11, - 0x8b, 0x98, 0xca, 0xb0, 0xa4, 0x95, 0x27, 0xaf, 0xb7, 0x42, 0xc6, 0x8f, 0x18, 0xb7, 0xc2, 0x6c, - 0x9c, 0x0a, 0x66, 0x71, 0x12, 0xa6, 0xed, 0xdd, 0xbd, 0xfe, 0xb6, 0xd5, 0x27, 0x63, 0x9e, 0xe7, - 0xb4, 0xbe, 0x19, 0x50, 0xb3, 0x3b, 0xfb, 0x2f, 0x71, 0x4c, 0xbb, 0x58, 0xb0, 0x0c, 0x3d, 0x04, - 0xd0, 0xdf, 0xf0, 0xd3, 0x7e, 0xdd, 0xd8, 0x30, 0xb6, 0xaa, 0xed, 0xa6, 0x99, 0x23, 0x99, 0x39, - 0x92, 0x39, 0x45, 0x32, 0x9d, 0x41, 0xf0, 0x8c, 0x8c, 0xdd, 0x8a, 0x2e, 0x71, 0xfa, 0xe8, 0x00, - 0x4a, 0x81, 0x08, 0x65, 0x6d, 0x61, 0xc3, 0xd8, 0xaa, 0xd9, 0x7b, 0x27, 0xa7, 0xcd, 0x76, 0x44, - 0x45, 0x6f, 0x10, 0x98, 0x21, 0x3b, 0xb2, 0x74, 0x66, 0xd8, 0xc3, 0x34, 0x99, 0x3c, 0x2c, 0x31, - 0x4e, 0x09, 0x37, 0xed, 0xa7, 0xce, 0xce, 0xbd, 0xbb, 0x1a, 0x72, 0x21, 0x10, 0xa1, 0xd3, 0x47, - 0x0f, 0x60, 0x3e, 0x65, 0x69, 0x7d, 0x5e, 0xf5, 0xb1, 0x65, 0xfe, 0x92, 0xbe, 0xe9, 0x64, 0x8c, - 0x1d, 0xbe, 0x38, 0x74, 0x18, 0xe7, 0x84, 0x73, 0xca, 0x12, 0x57, 0x16, 0xb5, 0x3e, 0x1a, 0xb0, - 0x3a, 0xcb, 0xed, 0x15, 0x15, 0xbd, 0x03, 0x22, 0xf0, 0x4c, 0x8f, 0xc6, 0xdf, 0xe8, 0xf1, 0x06, - 0x94, 0x7a, 0x84, 0x46, 0x3d, 0xa1, 0x28, 0x17, 0x5d, 0xfd, 0x42, 0x9b, 0x50, 0x1b, 0x32, 0x41, - 0x93, 0xc8, 0x4f, 0xd9, 0x3b, 0x92, 0x29, 0x12, 0x45, 0xb7, 0x9a, 0xfb, 0x1c, 0xe9, 0x6a, 0x7d, - 0x5a, 0x80, 0x25, 0xbb, 0xb3, 0xff, 0x98, 0xc4, 0x24, 0xc2, 0x82, 0xb2, 0xe4, 0x3f, 0xd2, 0x1f, - 0x75, 0x00, 0x86, 0x38, 0xf6, 0x75, 0x3b, 0xc5, 0x6b, 0xb5, 0x53, 0x1e, 0xe2, 0xd8, 0x56, 0x1d, - 0x6d, 0x42, 0x8d, 0x0b, 0x9c, 0x09, 0x5f, 0x6b, 0xbe, 0x90, 0xab, 0xaa, 0x7c, 0x4f, 0x72, 0xe1, - 0x6f, 0x01, 0x90, 0xa4, 0x3b, 0x49, 0x28, 0xa9, 0x84, 0x0a, 0x49, 0xba, 0x3a, 0x7c, 0x13, 0x2a, - 0x82, 0x09, 0x1c, 0xfb, 0x1c, 0x8b, 0xfa, 0xa2, 0x8a, 0x96, 0x95, 0xc3, 0xc3, 0x02, 0x3d, 0x02, - 0xd0, 0xcc, 0x7c, 0x31, 0xaa, 0x97, 0x15, 0xef, 0x8d, 0xdf, 0xf0, 0xf6, 0x72, 0xb3, 0x33, 0x72, - 0x2b, 0x7c, 0x62, 0xa2, 0x36, 0x54, 0x79, 0x8c, 0x79, 0x4f, 0x23, 0x54, 0x14, 0xed, 0x95, 0x93, - 0xd3, 0xa6, 0x1c, 0xb4, 0xa7, 0x23, 0x9d, 0x91, 0x0b, 0x7c, 0x6a, 0xa3, 0x37, 0xb0, 0xd4, 0xcd, - 0x57, 0x80, 0x65, 0x3e, 0xa7, 0x51, 0x1d, 0x54, 0xd5, 0xfd, 0x93, 0xd3, 0xe6, 0xee, 0x9f, 0x88, - 0xe5, 0xd1, 0x28, 0xc1, 0x62, 0x90, 0x11, 0xb7, 0x36, 0xc5, 0xf3, 0x68, 0x84, 0x3a, 0x50, 0x7e, - 0x3b, 0xc8, 0xc6, 0x0a, 0xba, 0x7a, 0x5d, 0xe8, 0x45, 0x09, 0xe5, 0xd1, 0xa8, 0xf5, 0xc5, 0x80, - 0xb5, 0x9f, 0x96, 0xf7, 0x5f, 0x1d, 0xd8, 0xc5, 0x91, 0x17, 0xae, 0x1a, 0xf9, 0xfc, 0xc5, 0x91, - 0x5f, 0x3c, 0xc5, 0xe2, 0xe5, 0x53, 0x7c, 0x6f, 0xc0, 0xca, 0xa5, 0x45, 0x46, 0x4d, 0xa8, 0x4e, - 0xce, 0x51, 0x8a, 0xa7, 0xe8, 0xb8, 0x93, 0x0b, 0x95, 0xd2, 0xba, 0xb0, 0x28, 0xa9, 0xca, 0x60, - 0xe1, 0xba, 0xca, 0x4a, 0xd1, 0xa4, 0xb0, 0x36, 0x54, 0xa6, 0xab, 0x85, 0x96, 0xa1, 0x20, 0x46, - 0xfa, 0xc3, 0x05, 0x31, 0x42, 0xb7, 0x61, 0x79, 0xb2, 0xa0, 0x3c, 0xcc, 0x68, 0x9a, 0xcb, 0x51, - 0x73, 0x97, 0xb4, 0xd7, 0x53, 0x4e, 0xfb, 0xf9, 0xe7, 0xb3, 0x86, 0x71, 0x7c, 0xd6, 0x30, 0xbe, - 0x9f, 0x35, 0x8c, 0x0f, 0xe7, 0x8d, 0xb9, 0xe3, 0xf3, 0xc6, 0xdc, 0xd7, 0xf3, 0xc6, 0xdc, 0xeb, - 0x2b, 0x07, 0x31, 0x9a, 0xfd, 0x0b, 0xa9, 0x4e, 0x83, 0x92, 0xfa, 0x5b, 0xec, 0xfc, 0x08, 0x00, - 0x00, 0xff, 0xff, 0x9b, 0xe6, 0xe3, 0x0b, 0xa8, 0x06, 0x00, 0x00, + // 697 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xdd, 0x4e, 0x13, 0x41, + 0x14, 0xee, 0x96, 0xd2, 0x9f, 0xd3, 0x42, 0x60, 0x04, 0xd3, 0x60, 0x6c, 0xa1, 0x89, 0x86, 0x78, + 0xb1, 0x2b, 0x45, 0x48, 0xd4, 0x44, 0x63, 0xa1, 0xd1, 0x46, 0xc1, 0xcd, 0x6e, 0x83, 0xc6, 0x0b, + 0x9b, 0xd9, 0xed, 0xb2, 0x1d, 0xbb, 0xec, 0x6c, 0x76, 0xa6, 0xb5, 0x7d, 0x03, 0x2f, 0x7d, 0x04, + 0x9f, 0xc3, 0x27, 0x30, 0xf1, 0x86, 0x4b, 0xe5, 0x82, 0x18, 0x78, 0x11, 0xb3, 0xb3, 0xd3, 0xa6, + 0x80, 0x86, 0x18, 0xf4, 0xc6, 0xbb, 0x33, 0xe7, 0xe7, 0xdb, 0xf3, 0x7d, 0xe7, 0x9c, 0x2c, 0xdc, + 0xb6, 0xb0, 0x35, 0xf4, 0xa8, 0xaf, 0x59, 0xdc, 0x66, 0x1c, 0x77, 0x89, 0xef, 0x6a, 0xfd, 0xb5, + 0x89, 0x97, 0x1a, 0x84, 0x94, 0x53, 0xb4, 0x28, 0xf3, 0xd4, 0x89, 0x48, 0x7f, 0x6d, 0x69, 0xc1, + 0xa5, 0x2e, 0x15, 0x19, 0x5a, 0x64, 0xc5, 0xc9, 0x4b, 0x15, 0x9b, 0xb2, 0x03, 0xca, 0x34, 0x3b, + 0x1c, 0x06, 0x9c, 0x6a, 0xcc, 0xb1, 0x83, 0xea, 0xc6, 0x66, 0x77, 0x4d, 0xeb, 0x3a, 0x43, 0x16, + 0xe7, 0x54, 0xbe, 0x2b, 0x50, 0xa8, 0x35, 0xb7, 0xf6, 0xb0, 0x47, 0xda, 0x98, 0xd3, 0x10, 0x3d, + 0x02, 0x90, 0xdf, 0x68, 0x05, 0xdd, 0xa2, 0xb2, 0xac, 0xac, 0xe6, 0xab, 0x65, 0x35, 0x46, 0x52, + 0x63, 0x24, 0x75, 0x8c, 0xa4, 0xea, 0x3d, 0xeb, 0xb9, 0x33, 0x34, 0x72, 0xb2, 0x44, 0xef, 0xa2, + 0x1d, 0x48, 0x5b, 0xdc, 0x8e, 0x6a, 0x93, 0xcb, 0xca, 0x6a, 0xa1, 0xb6, 0x79, 0x74, 0x5c, 0xae, + 0xba, 0x84, 0x77, 0x7a, 0x96, 0x6a, 0xd3, 0x03, 0x4d, 0x66, 0xda, 0x1d, 0x4c, 0xfc, 0xd1, 0x43, + 0xe3, 0xc3, 0xc0, 0x61, 0x6a, 0xad, 0xa1, 0xaf, 0xdf, 0xbb, 0x2b, 0x21, 0xa7, 0x2d, 0x6e, 0xeb, + 0x5d, 0xf4, 0x00, 0xa6, 0x02, 0x1a, 0x14, 0xa7, 0x44, 0x1f, 0xab, 0xea, 0x2f, 0xe9, 0xab, 0x7a, + 0x48, 0xe9, 0xfe, 0xcb, 0x7d, 0x9d, 0x32, 0xe6, 0x30, 0x46, 0xa8, 0x6f, 0x44, 0x45, 0x95, 0x4f, + 0x0a, 0x2c, 0x4c, 0x72, 0x7b, 0x45, 0x78, 0x67, 0xc7, 0xe1, 0x78, 0xa2, 0x47, 0xe5, 0x6f, 0xf4, + 0x78, 0x1d, 0xd2, 0x1d, 0x87, 0xb8, 0x1d, 0x2e, 0x28, 0xa7, 0x0c, 0xf9, 0x42, 0x2b, 0x50, 0xe8, + 0x53, 0x4e, 0x7c, 0xb7, 0x15, 0xd0, 0xf7, 0x4e, 0x28, 0x48, 0xa4, 0x8c, 0x7c, 0xec, 0xd3, 0x23, + 0x57, 0xe5, 0xf3, 0x34, 0xcc, 0xd4, 0x9a, 0x5b, 0xdb, 0x8e, 0xe7, 0xb8, 0x98, 0x13, 0xea, 0xff, + 0x47, 0xfa, 0xa3, 0x26, 0x40, 0x1f, 0x7b, 0x2d, 0xd9, 0x4e, 0xea, 0x4a, 0xed, 0x64, 0xfb, 0xd8, + 0xab, 0x89, 0x8e, 0x56, 0xa0, 0xc0, 0x38, 0x0e, 0x79, 0x4b, 0x6a, 0x3e, 0x1d, 0xab, 0x2a, 0x7c, + 0xcf, 0x62, 0xe1, 0x6f, 0x02, 0x38, 0x7e, 0x7b, 0x94, 0x90, 0x16, 0x09, 0x39, 0xc7, 0x6f, 0xcb, + 0xf0, 0x0d, 0xc8, 0x71, 0xca, 0xb1, 0xd7, 0x62, 0x98, 0x17, 0x33, 0x22, 0x9a, 0x15, 0x0e, 0x13, + 0x73, 0xf4, 0x18, 0x40, 0x32, 0x6b, 0xf1, 0x41, 0x31, 0x2b, 0x78, 0x2f, 0xff, 0x86, 0xb7, 0x19, + 0x9b, 0xcd, 0x81, 0x91, 0x63, 0x23, 0x13, 0x55, 0x21, 0xcf, 0x3c, 0xcc, 0x3a, 0x12, 0x21, 0x27, + 0x68, 0xcf, 0x1f, 0x1d, 0x97, 0xa3, 0x41, 0x9b, 0x32, 0xd2, 0x1c, 0x18, 0xc0, 0xc6, 0x36, 0x7a, + 0x0b, 0x33, 0xed, 0x78, 0x05, 0x68, 0xd8, 0x62, 0xc4, 0x2d, 0x82, 0xa8, 0xba, 0x7f, 0x74, 0x5c, + 0xde, 0xf8, 0x13, 0xb1, 0x4c, 0xe2, 0xfa, 0x98, 0xf7, 0x42, 0xc7, 0x28, 0x8c, 0xf1, 0x4c, 0xe2, + 0xa2, 0x26, 0x64, 0xdf, 0xf5, 0xc2, 0xa1, 0x80, 0xce, 0x5f, 0x15, 0x3a, 0x13, 0x41, 0x99, 0xc4, + 0xad, 0x7c, 0x55, 0x60, 0xf1, 0xcc, 0xf2, 0xfe, 0xab, 0x03, 0x3b, 0x3f, 0xf2, 0xe4, 0x65, 0x23, + 0x9f, 0x3a, 0x3f, 0xf2, 0xf3, 0xa7, 0x98, 0xba, 0x78, 0x8a, 0x1f, 0x14, 0x98, 0xbf, 0xb0, 0xc8, + 0xa8, 0x0c, 0xf9, 0xd1, 0x39, 0x46, 0xe2, 0x09, 0x3a, 0xc6, 0xe8, 0x42, 0x23, 0x69, 0x0d, 0xc8, + 0x44, 0x54, 0xa3, 0x60, 0xf2, 0xaa, 0xca, 0x46, 0xa2, 0x45, 0xc2, 0xd6, 0x20, 0x37, 0x5e, 0x2d, + 0x34, 0x0b, 0x49, 0x3e, 0x90, 0x1f, 0x4e, 0xf2, 0x01, 0xba, 0x05, 0xb3, 0xa3, 0x05, 0x65, 0x76, + 0x48, 0x82, 0x58, 0x8e, 0x82, 0x31, 0x23, 0xbd, 0xa6, 0x70, 0xde, 0x79, 0x08, 0xd7, 0xce, 0xcc, + 0xc6, 0xe4, 0x98, 0xf7, 0x18, 0xca, 0x43, 0x46, 0xaf, 0xef, 0x6e, 0x37, 0x76, 0x9f, 0xce, 0x25, + 0x10, 0x40, 0xfa, 0xc9, 0x56, 0xb3, 0xb1, 0x57, 0x9f, 0x53, 0xa2, 0x40, 0xfd, 0xb5, 0xde, 0x30, + 0xea, 0xdb, 0x73, 0xc9, 0xda, 0x8b, 0x2f, 0x27, 0x25, 0xe5, 0xf0, 0xa4, 0xa4, 0xfc, 0x38, 0x29, + 0x29, 0x1f, 0x4f, 0x4b, 0x89, 0xc3, 0xd3, 0x52, 0xe2, 0xdb, 0x69, 0x29, 0xf1, 0xe6, 0xd2, 0x29, + 0x0e, 0x26, 0x7f, 0x61, 0x82, 0xa6, 0x95, 0x16, 0xbf, 0x9a, 0xf5, 0x9f, 0x01, 0x00, 0x00, 0xff, + 0xff, 0xa2, 0x3c, 0xeb, 0x15, 0xe5, 0x06, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index fd15e2fcf..79b0a5c81 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -323,27 +323,29 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { return 0 } -// QueryBTCValidatorsAtHeightRequest is the request type for the -// Query/BTCValidatorsAtHeight RPC method. -type QueryBTCValidatorsAtHeightRequest struct { +// QueryActiveBTCValidatorsAtHeightRequest is the request type for the +// Query/ActiveBTCValidatorsAtHeight RPC method. +type QueryActiveBTCValidatorsAtHeightRequest struct { // height defines at which Babylon height to query the btc validators info. Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorsAtHeightRequest) Reset() { *m = QueryBTCValidatorsAtHeightRequest{} } -func (m *QueryBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorsAtHeightRequest) ProtoMessage() {} -func (*QueryBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { + *m = QueryActiveBTCValidatorsAtHeightRequest{} +} +func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } +func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} +func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{6} } -func (m *QueryBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -353,53 +355,55 @@ func (m *QueryBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, deterministic return b[:n], nil } } -func (m *QueryBTCValidatorsAtHeightRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.Merge(m, src) +func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.Merge(m, src) } -func (m *QueryBTCValidatorsAtHeightRequest) XXX_Size() int { +func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorsAtHeightRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorsAtHeightRequest.DiscardUnknown(m) +func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorsAtHeightRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorsAtHeightRequest) GetHeight() uint64 { +func (m *QueryActiveBTCValidatorsAtHeightRequest) GetHeight() uint64 { if m != nil { return m.Height } return 0 } -func (m *QueryBTCValidatorsAtHeightRequest) GetPagination() *query.PageRequest { +func (m *QueryActiveBTCValidatorsAtHeightRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination } return nil } -// QueryBTCValidatorsAtHeightResponse is the response type for the -// Query/BTCValidatorsAtHeight RPC method. -type QueryBTCValidatorsAtHeightResponse struct { +// QueryActiveBTCValidatorsAtHeightResponse is the response type for the +// Query/ActiveBTCValidatorsAtHeight RPC method. +type QueryActiveBTCValidatorsAtHeightResponse struct { // btc_validators contains all the queried btc validators. BtcValidators []*BTCValidatorWithMeta `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorsAtHeightResponse) Reset() { *m = QueryBTCValidatorsAtHeightResponse{} } -func (m *QueryBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorsAtHeightResponse) ProtoMessage() {} -func (*QueryBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { + *m = QueryActiveBTCValidatorsAtHeightResponse{} +} +func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } +func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} +func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{7} } -func (m *QueryBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -409,26 +413,26 @@ func (m *QueryBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, deterministic return b[:n], nil } } -func (m *QueryBTCValidatorsAtHeightResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.Merge(m, src) +func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.Merge(m, src) } -func (m *QueryBTCValidatorsAtHeightResponse) XXX_Size() int { +func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorsAtHeightResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorsAtHeightResponse.DiscardUnknown(m) +func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorsAtHeightResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorsAtHeightResponse) GetBtcValidators() []*BTCValidatorWithMeta { +func (m *QueryActiveBTCValidatorsAtHeightResponse) GetBtcValidators() []*BTCValidatorWithMeta { if m != nil { return m.BtcValidators } return nil } -func (m *QueryBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse { +func (m *QueryActiveBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse { if m != nil { return m.Pagination } @@ -524,9 +528,8 @@ type QueryBTCValidatorDelegationsRequest struct { // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` - // no_jury_sig_only indicates this query will only return BTC delegations that haven't - // received a jury signature yet - NoJurySigOnly bool `protobuf:"varint,2,opt,name=no_jury_sig_only,json=noJurySigOnly,proto3" json:"no_jury_sig_only,omitempty"` + // delegation_status is the status of the delegations we are filtering for + DelStatus BTCDelegationStatus `protobuf:"varint,2,opt,name=del_status,json=delStatus,proto3,enum=babylon.btcstaking.v1.BTCDelegationStatus" json:"del_status,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -571,11 +574,11 @@ func (m *QueryBTCValidatorDelegationsRequest) GetValBtcPkHex() string { return "" } -func (m *QueryBTCValidatorDelegationsRequest) GetNoJurySigOnly() bool { +func (m *QueryBTCValidatorDelegationsRequest) GetDelStatus() BTCDelegationStatus { if m != nil { - return m.NoJurySigOnly + return m.DelStatus } - return false + return BTCDelegationStatus_PENDING } func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest { @@ -648,8 +651,8 @@ func init() { proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") - proto.RegisterType((*QueryBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightRequest") - proto.RegisterType((*QueryBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsAtHeightResponse") + proto.RegisterType((*QueryActiveBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightRequest") + proto.RegisterType((*QueryActiveBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightResponse") proto.RegisterType((*QueryActivatedHeightRequest)(nil), "babylon.btcstaking.v1.QueryActivatedHeightRequest") proto.RegisterType((*QueryActivatedHeightResponse)(nil), "babylon.btcstaking.v1.QueryActivatedHeightResponse") proto.RegisterType((*QueryBTCValidatorDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsRequest") @@ -659,60 +662,60 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 844 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcf, 0x4f, 0x1b, 0x47, - 0x18, 0xf5, 0x00, 0xb5, 0xca, 0xb8, 0x40, 0x35, 0x85, 0x16, 0xb6, 0xe0, 0xc2, 0x42, 0x81, 0xd2, - 0x6a, 0xb7, 0x36, 0x08, 0xb5, 0xfc, 0x38, 0x60, 0xaa, 0x16, 0xd1, 0x22, 0xcc, 0x16, 0x15, 0x89, - 0xcb, 0x6a, 0x76, 0x3d, 0x5a, 0x6f, 0x59, 0x76, 0x16, 0xef, 0x78, 0x8b, 0x55, 0x71, 0x69, 0xff, - 0x81, 0x4a, 0xfc, 0x23, 0xcd, 0x2d, 0x52, 0xa4, 0x48, 0x91, 0x72, 0xe0, 0x48, 0x94, 0x4b, 0x4e, - 0x51, 0x04, 0xb9, 0xe5, 0x9a, 0x3f, 0x20, 0xf2, 0xec, 0x38, 0xfe, 0xb5, 0x36, 0xb6, 0x45, 0x6e, - 0xe0, 0xf9, 0xde, 0xf7, 0xbd, 0xf7, 0x3e, 0xcf, 0x1b, 0xc3, 0x19, 0x03, 0x1b, 0x25, 0x87, 0xba, - 0xaa, 0xc1, 0x4c, 0x9f, 0xe1, 0x13, 0xdb, 0xb5, 0xd4, 0x20, 0xa5, 0x9e, 0x15, 0x49, 0xa1, 0xa4, - 0x78, 0x05, 0xca, 0x28, 0x1a, 0x13, 0x25, 0x4a, 0xb5, 0x44, 0x09, 0x52, 0xd2, 0xa8, 0x45, 0x2d, - 0xca, 0x2b, 0xd4, 0xf2, 0x5f, 0x61, 0xb1, 0x34, 0x69, 0x51, 0x6a, 0x39, 0x44, 0xc5, 0x9e, 0xad, - 0x62, 0xd7, 0xa5, 0x0c, 0x33, 0x9b, 0xba, 0xbe, 0x38, 0x5d, 0x32, 0xa9, 0x7f, 0x4a, 0x7d, 0xd5, - 0xc0, 0x3e, 0x09, 0x67, 0xa8, 0x41, 0xca, 0x20, 0x0c, 0xa7, 0x54, 0x0f, 0x5b, 0xb6, 0xcb, 0x8b, - 0x45, 0xad, 0x1c, 0xcd, 0xcc, 0xc3, 0x05, 0x7c, 0x5a, 0xe9, 0x37, 0x1f, 0x5d, 0x53, 0x43, 0x94, - 0xd7, 0xc9, 0xa3, 0x10, 0x1d, 0x94, 0xa7, 0x65, 0x39, 0x58, 0x23, 0x67, 0x45, 0xe2, 0x33, 0x59, - 0x83, 0x9f, 0xd5, 0x7d, 0xea, 0x7b, 0xd4, 0xf5, 0x09, 0x5a, 0x87, 0xf1, 0x70, 0xc8, 0x38, 0x98, - 0x06, 0x8b, 0x89, 0xf4, 0x94, 0x12, 0x69, 0x80, 0x12, 0xc2, 0x32, 0x03, 0x57, 0x2f, 0xbf, 0x8a, - 0x69, 0x02, 0x22, 0x9b, 0x70, 0x82, 0xf7, 0xcc, 0x1c, 0x6e, 0xff, 0x81, 0x1d, 0x3b, 0x87, 0x19, - 0x2d, 0x54, 0x06, 0xa2, 0x9f, 0x21, 0xac, 0xca, 0x14, 0xdd, 0xe7, 0x95, 0xd0, 0x13, 0xa5, 0xec, - 0x89, 0x12, 0xfa, 0x2e, 0x3c, 0x51, 0xb2, 0xd8, 0x22, 0x02, 0xab, 0xd5, 0x20, 0xe5, 0x07, 0x00, - 0x4a, 0x51, 0x53, 0x84, 0x80, 0x5d, 0x38, 0x6c, 0x30, 0x53, 0x0f, 0xde, 0x9f, 0x8c, 0x83, 0xe9, - 0xfe, 0xc5, 0x44, 0x7a, 0xb6, 0x85, 0x90, 0xda, 0x2e, 0xda, 0x90, 0xc1, 0xcc, 0x6a, 0x4f, 0xf4, - 0x4b, 0x1d, 0xe5, 0x3e, 0x4e, 0x79, 0xe1, 0x4e, 0xca, 0x21, 0x91, 0x3a, 0xce, 0x39, 0xf8, 0x75, - 0x13, 0xe5, 0x2c, 0xfd, 0x8b, 0x14, 0xb6, 0xd8, 0x0e, 0xb1, 0xad, 0x3c, 0xab, 0x98, 0x34, 0x0b, - 0x87, 0x03, 0xec, 0xe8, 0x65, 0x05, 0xde, 0x89, 0x9e, 0x27, 0xe7, 0xdc, 0xa8, 0x41, 0x2d, 0x11, - 0x60, 0x27, 0xc3, 0xcc, 0xec, 0xc9, 0x0e, 0x39, 0x47, 0x9f, 0xc3, 0x78, 0x9e, 0xa3, 0x38, 0xa5, - 0x01, 0x4d, 0xfc, 0x27, 0xff, 0x0a, 0xe7, 0xef, 0x9a, 0x22, 0x4c, 0x9a, 0x81, 0x9f, 0x04, 0x94, - 0xd9, 0xae, 0xa5, 0x7b, 0xe5, 0x73, 0x3e, 0x64, 0x40, 0x4b, 0x84, 0x9f, 0x71, 0x88, 0xfc, 0x2f, - 0x80, 0x33, 0xcd, 0x36, 0x37, 0xf2, 0xad, 0x52, 0x01, 0xb5, 0x54, 0x1a, 0x96, 0xdd, 0xd7, 0xf3, - 0xb2, 0x9f, 0x00, 0x28, 0xb7, 0x63, 0x21, 0xf4, 0x68, 0x2d, 0x96, 0xfe, 0x6d, 0x07, 0x4b, 0x3f, - 0xb2, 0x59, 0x7e, 0x8f, 0x30, 0xfc, 0xc1, 0x96, 0x3f, 0x05, 0xbf, 0xe4, 0x12, 0xb6, 0x4c, 0x66, - 0x07, 0x98, 0x91, 0x5c, 0x9d, 0x85, 0xf2, 0x2a, 0x9c, 0x8c, 0x3e, 0x16, 0xda, 0x5a, 0x58, 0x2c, - 0x3f, 0x02, 0x70, 0xb6, 0xc9, 0x9a, 0x9f, 0x88, 0x43, 0xac, 0x30, 0x75, 0xba, 0xfa, 0x4a, 0x2d, - 0xc0, 0x4f, 0x5d, 0xaa, 0xff, 0x59, 0x2c, 0x94, 0x74, 0xdf, 0xb6, 0x74, 0xea, 0x3a, 0x25, 0x2e, - 0xf9, 0x63, 0x6d, 0xc8, 0xa5, 0xbb, 0xc5, 0x42, 0xe9, 0x77, 0xdb, 0xda, 0x77, 0x9d, 0x52, 0xc3, - 0x62, 0xfb, 0x7b, 0x5e, 0xec, 0x63, 0x00, 0xe7, 0xda, 0xb3, 0x17, 0xf2, 0xf7, 0xe0, 0x48, 0x99, - 0x7a, 0xae, 0x7a, 0x24, 0x76, 0x3b, 0xd7, 0x7a, 0xb7, 0xd5, 0x3e, 0x5a, 0xf9, 0x7b, 0x51, 0xd3, - 0xf6, 0xde, 0xb6, 0x9a, 0x7e, 0x3a, 0x08, 0x3f, 0xe2, 0x02, 0xd0, 0x25, 0x80, 0xf1, 0x30, 0x0e, - 0xd1, 0x37, 0x2d, 0x38, 0x35, 0xe7, 0xaf, 0xb4, 0xd4, 0x49, 0x69, 0x38, 0x57, 0x4e, 0xff, 0xf3, - 0xfc, 0xf5, 0x65, 0xdf, 0x77, 0x68, 0x49, 0x15, 0x18, 0x33, 0x8f, 0x6d, 0x57, 0x6d, 0xf7, 0x46, - 0xa0, 0xff, 0x01, 0x1c, 0xaa, 0xbb, 0x34, 0xe8, 0xfb, 0x76, 0x13, 0xa3, 0x22, 0x5b, 0x4a, 0x75, - 0x81, 0x10, 0x54, 0xd7, 0x38, 0xd5, 0x15, 0x94, 0xee, 0x84, 0x6a, 0xfd, 0x9d, 0x45, 0xcf, 0x00, - 0x1c, 0x8b, 0xbc, 0xe7, 0xe8, 0x87, 0x8e, 0x89, 0x34, 0x04, 0x94, 0xf4, 0x63, 0x0f, 0x48, 0x21, - 0x65, 0x9b, 0x4b, 0xd9, 0x44, 0xeb, 0xdd, 0x4b, 0x51, 0xff, 0x0e, 0x2f, 0xe9, 0x05, 0x7a, 0x0b, - 0xe0, 0x44, 0xcb, 0x3c, 0x46, 0x1b, 0x9d, 0xb2, 0x8b, 0x7a, 0x2c, 0xa4, 0xcd, 0x1e, 0xd1, 0x42, - 0xdf, 0x31, 0xd7, 0x77, 0x88, 0xb4, 0x5e, 0xf4, 0xd5, 0x47, 0xca, 0x85, 0xca, 0xdf, 0x91, 0xaa, - 0xec, 0x87, 0x00, 0x8e, 0x34, 0x04, 0x1a, 0x4a, 0xb7, 0xa3, 0x1b, 0x1d, 0x8e, 0xd2, 0x72, 0x57, - 0x18, 0x21, 0x6c, 0x83, 0x0b, 0x5b, 0x45, 0x2b, 0x9d, 0x08, 0xc3, 0x95, 0x26, 0xba, 0x78, 0xba, - 0xde, 0x00, 0xf8, 0x45, 0x8b, 0x50, 0x42, 0x6b, 0x9d, 0x3a, 0xde, 0x9c, 0xc3, 0xd2, 0x7a, 0x4f, - 0x58, 0x21, 0xe9, 0x88, 0x4b, 0x3a, 0x40, 0xfb, 0xf7, 0xb1, 0xab, 0x9a, 0x2c, 0xcd, 0xfc, 0x76, - 0x75, 0x93, 0x04, 0xd7, 0x37, 0x49, 0xf0, 0xea, 0x26, 0x09, 0xfe, 0xbb, 0x4d, 0xc6, 0xae, 0x6f, - 0x93, 0xb1, 0x17, 0xb7, 0xc9, 0xd8, 0x71, 0xda, 0xb2, 0x59, 0xbe, 0x68, 0x28, 0x26, 0x3d, 0x8d, - 0x1e, 0x7a, 0x5e, 0x3b, 0x96, 0x95, 0x3c, 0xe2, 0x1b, 0x71, 0xfe, 0x8b, 0x73, 0xf9, 0x5d, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xe8, 0xdf, 0x2c, 0xfa, 0x59, 0x0b, 0x00, 0x00, + // 836 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x4f, 0x3b, 0x45, + 0x18, 0xee, 0x00, 0x36, 0x61, 0x2a, 0x90, 0x8c, 0xa8, 0xb0, 0x40, 0x85, 0x05, 0x01, 0xab, 0xd9, + 0xb5, 0x0b, 0xe1, 0xc0, 0x87, 0x86, 0x62, 0x14, 0x3f, 0x88, 0x65, 0x25, 0x92, 0x70, 0x69, 0x66, + 0xb7, 0x93, 0xed, 0x86, 0x65, 0x67, 0xe9, 0x4e, 0x57, 0x88, 0xe1, 0xe2, 0xcd, 0x9b, 0x09, 0xff, + 0x88, 0xde, 0x3c, 0x79, 0x95, 0x23, 0x89, 0x89, 0xf1, 0xa2, 0x31, 0xd4, 0x9b, 0x57, 0xff, 0x00, + 0xd3, 0xd9, 0xe9, 0x6f, 0xbb, 0xed, 0xf6, 0x33, 0xfc, 0x6e, 0xdb, 0x9d, 0xf7, 0x79, 0xdf, 0xe7, + 0x79, 0xde, 0x77, 0xdf, 0x29, 0x5c, 0x31, 0xb0, 0x71, 0xeb, 0x50, 0x57, 0x35, 0x98, 0xe9, 0x33, + 0x7c, 0x69, 0xbb, 0x96, 0x1a, 0xe4, 0xd5, 0xeb, 0x1a, 0xa9, 0xde, 0x2a, 0x5e, 0x95, 0x32, 0x8a, + 0x5e, 0x17, 0x21, 0x4a, 0x14, 0xa2, 0x04, 0x79, 0x69, 0xd6, 0xa2, 0x16, 0xe5, 0x11, 0x6a, 0xe3, + 0x29, 0x0c, 0x96, 0x16, 0x2d, 0x4a, 0x2d, 0x87, 0xa8, 0xd8, 0xb3, 0x55, 0xec, 0xba, 0x94, 0x61, + 0x66, 0x53, 0xd7, 0x17, 0xa7, 0x39, 0x93, 0xfa, 0x57, 0xd4, 0x57, 0x0d, 0xec, 0x93, 0xb0, 0x86, + 0x1a, 0xe4, 0x0d, 0xc2, 0x70, 0x5e, 0xf5, 0xb0, 0x65, 0xbb, 0x3c, 0x58, 0xc4, 0xca, 0xc9, 0xcc, + 0x3c, 0x5c, 0xc5, 0x57, 0xcd, 0x7c, 0xeb, 0xc9, 0x31, 0x2d, 0x44, 0x79, 0x9c, 0x3c, 0x0b, 0xd1, + 0x69, 0xa3, 0x5a, 0x91, 0x83, 0x75, 0x72, 0x5d, 0x23, 0x3e, 0x93, 0x75, 0xf8, 0x5a, 0xec, 0xad, + 0xef, 0x51, 0xd7, 0x27, 0x68, 0x0f, 0xa6, 0xc3, 0x22, 0x73, 0x60, 0x19, 0x6c, 0x66, 0xb4, 0x25, + 0x25, 0xd1, 0x00, 0x25, 0x84, 0x15, 0x26, 0x1e, 0xfe, 0x7a, 0x2b, 0xa5, 0x0b, 0x88, 0x6c, 0xc2, + 0x79, 0x9e, 0xb3, 0x70, 0x76, 0xf4, 0x35, 0x76, 0xec, 0x32, 0x66, 0xb4, 0xda, 0x2c, 0x88, 0x3e, + 0x86, 0x30, 0x92, 0x29, 0xb2, 0xaf, 0x2b, 0xa1, 0x27, 0x4a, 0xc3, 0x13, 0x25, 0xf4, 0x5d, 0x78, + 0xa2, 0x14, 0xb1, 0x45, 0x04, 0x56, 0x6f, 0x41, 0xca, 0x3f, 0x01, 0x28, 0x25, 0x55, 0x11, 0x02, + 0x3e, 0x83, 0xd3, 0x06, 0x33, 0x4b, 0xc1, 0x8b, 0x93, 0x39, 0xb0, 0x3c, 0xbe, 0x99, 0xd1, 0x56, + 0xbb, 0x08, 0x69, 0xcd, 0xa2, 0x4f, 0x19, 0xcc, 0x8c, 0x72, 0xa2, 0x4f, 0x62, 0x94, 0xc7, 0x38, + 0xe5, 0x8d, 0xbe, 0x94, 0x43, 0x22, 0x31, 0xce, 0x65, 0xf8, 0x76, 0x07, 0xe5, 0x22, 0xfd, 0x86, + 0x54, 0x0f, 0xd9, 0x31, 0xb1, 0xad, 0x0a, 0x6b, 0x9a, 0xb4, 0x0a, 0xa7, 0x03, 0xec, 0x94, 0x1a, + 0x0a, 0xbc, 0xcb, 0x52, 0x85, 0xdc, 0x70, 0xa3, 0x26, 0xf5, 0x4c, 0x80, 0x9d, 0x02, 0x33, 0x8b, + 0x97, 0xc7, 0xe4, 0x06, 0xbd, 0x01, 0xd3, 0x15, 0x8e, 0xe2, 0x94, 0x26, 0x74, 0xf1, 0x4b, 0xfe, + 0x1c, 0xae, 0xf7, 0xab, 0x22, 0x4c, 0x5a, 0x81, 0xaf, 0x06, 0x94, 0xd9, 0xae, 0x55, 0xf2, 0x1a, + 0xe7, 0xbc, 0xc8, 0x84, 0x9e, 0x09, 0xdf, 0x71, 0x88, 0xfc, 0x3d, 0x80, 0x1b, 0x3c, 0xdb, 0xa1, + 0xc9, 0xec, 0x80, 0xc4, 0xcc, 0x6e, 0x67, 0x1d, 0x11, 0x02, 0xad, 0x84, 0xda, 0x5a, 0x3e, 0x36, + 0x72, 0xcb, 0x7f, 0x05, 0x70, 0xb3, 0x3f, 0x17, 0xa1, 0x4d, 0xef, 0x32, 0x00, 0xef, 0x0e, 0x30, + 0x00, 0xe7, 0x36, 0xab, 0x9c, 0x10, 0x86, 0x5f, 0xda, 0x20, 0x2c, 0xc1, 0x85, 0x48, 0x08, 0x66, + 0xa4, 0x1c, 0x33, 0x52, 0xde, 0x81, 0x8b, 0xc9, 0xc7, 0x42, 0x5b, 0x17, 0xa3, 0xe5, 0x3f, 0x01, + 0x5c, 0xed, 0x68, 0xfd, 0x47, 0xc4, 0x21, 0x56, 0xb8, 0x81, 0x86, 0x1a, 0xaf, 0x4f, 0x21, 0x2c, + 0x13, 0xa7, 0xe4, 0x33, 0xcc, 0x6a, 0x3e, 0x17, 0x3b, 0xad, 0xe5, 0xba, 0x9b, 0x17, 0x95, 0xf9, + 0x8a, 0x23, 0xf4, 0xc9, 0x32, 0x71, 0xc2, 0xc7, 0xb6, 0x01, 0x18, 0x1f, 0x79, 0x00, 0x7e, 0x01, + 0x70, 0xad, 0xb7, 0x3e, 0x61, 0xd0, 0x09, 0x9c, 0x69, 0x88, 0x2b, 0x47, 0x47, 0xa2, 0xfb, 0x6b, + 0x83, 0x08, 0xd0, 0x1b, 0x93, 0xd3, 0x92, 0xf6, 0xd9, 0xfa, 0xae, 0xfd, 0x3e, 0x09, 0x5f, 0xe1, + 0x02, 0xd0, 0x3d, 0x80, 0xe9, 0x70, 0x79, 0xa2, 0x77, 0xba, 0x70, 0xea, 0xdc, 0xd6, 0x52, 0x6e, + 0x90, 0xd0, 0xb0, 0xae, 0xac, 0x7d, 0xf7, 0xdb, 0x3f, 0xf7, 0x63, 0xef, 0xa1, 0x9c, 0x2a, 0x30, + 0x66, 0x05, 0xdb, 0xae, 0xda, 0xeb, 0x46, 0x41, 0x3f, 0x02, 0x38, 0x15, 0xfb, 0xac, 0xd0, 0xfb, + 0xbd, 0x2a, 0x26, 0x2d, 0x78, 0x29, 0x3f, 0x04, 0x42, 0x50, 0xdd, 0xe5, 0x54, 0xb7, 0x91, 0x36, + 0x08, 0xd5, 0xf8, 0x57, 0x8d, 0xea, 0x00, 0x2e, 0xf4, 0xd8, 0x07, 0xe8, 0x83, 0x5e, 0x74, 0xfa, + 0x2f, 0x35, 0xe9, 0xc3, 0x91, 0xf1, 0x42, 0xdc, 0x11, 0x17, 0x77, 0x80, 0xf6, 0x86, 0x17, 0xa7, + 0x7e, 0x1b, 0x7e, 0xd8, 0x77, 0xe8, 0x3f, 0x00, 0xe7, 0xbb, 0xee, 0x73, 0xb4, 0x3f, 0xa8, 0xe5, + 0x49, 0x97, 0x8d, 0x74, 0x30, 0x22, 0x5a, 0xe8, 0xbb, 0xe0, 0xfa, 0xce, 0x90, 0x3e, 0x8a, 0xbe, + 0xf8, 0x1a, 0xba, 0x53, 0xf9, 0x3d, 0x14, 0xc9, 0xfe, 0x19, 0xc0, 0x99, 0xb6, 0x25, 0x88, 0xb4, + 0xbe, 0x0d, 0xe9, 0x58, 0xa8, 0xd2, 0xd6, 0x50, 0x18, 0x21, 0x6c, 0x9f, 0x0b, 0xdb, 0x41, 0xdb, + 0x83, 0x08, 0xc3, 0xcd, 0x24, 0x25, 0x71, 0xe9, 0xfd, 0x0b, 0xe0, 0x9b, 0x5d, 0xd6, 0x14, 0xda, + 0x1d, 0xd4, 0xf1, 0xce, 0xdd, 0x2d, 0xed, 0x8d, 0x84, 0x15, 0x92, 0xce, 0xb9, 0xa4, 0x53, 0xf4, + 0xe5, 0x73, 0xf4, 0xaa, 0x65, 0xbb, 0x16, 0xbe, 0x78, 0x78, 0xca, 0x82, 0xc7, 0xa7, 0x2c, 0xf8, + 0xfb, 0x29, 0x0b, 0x7e, 0xa8, 0x67, 0x53, 0x8f, 0xf5, 0x6c, 0xea, 0x8f, 0x7a, 0x36, 0x75, 0xa1, + 0x59, 0x36, 0xab, 0xd4, 0x0c, 0xc5, 0xa4, 0x57, 0xc9, 0x45, 0x6f, 0x5a, 0xcb, 0xb2, 0x5b, 0x8f, + 0xf8, 0x46, 0x9a, 0xff, 0x63, 0xdd, 0xfa, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x98, 0xcd, 0x91, + 0x99, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -731,9 +734,9 @@ type QueryClient interface { Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) // BTCValidators queries all btc validators BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) - // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. - BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries a btc validator at a given height + // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) + // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power @@ -768,9 +771,9 @@ func (c *queryClient) BTCValidators(ctx context.Context, in *QueryBTCValidatorsR return out, nil } -func (c *queryClient) BTCValidatorsAtHeight(ctx context.Context, in *QueryBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsAtHeightResponse, error) { - out := new(QueryBTCValidatorsAtHeightResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorsAtHeight", in, out, opts...) +func (c *queryClient) ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) { + out := new(QueryActiveBTCValidatorsAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", in, out, opts...) if err != nil { return nil, err } @@ -810,9 +813,9 @@ type QueryServer interface { Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) // BTCValidators queries all btc validators BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) - // BTCValidatorsAtHeight queries btc validators with non zero voting power at given height. - BTCValidatorsAtHeight(context.Context, *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries a btc validator at a given height + // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) + // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height BTCValidatorPowerAtHeight(context.Context, *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power @@ -831,8 +834,8 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidators not implemented") } -func (*UnimplementedQueryServer) BTCValidatorsAtHeight(ctx context.Context, req *QueryBTCValidatorsAtHeightRequest) (*QueryBTCValidatorsAtHeightResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorsAtHeight not implemented") +func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context, req *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ActiveBTCValidatorsAtHeight not implemented") } func (*UnimplementedQueryServer) BTCValidatorPowerAtHeight(ctx context.Context, req *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorPowerAtHeight not implemented") @@ -884,20 +887,20 @@ func _Query_BTCValidators_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } -func _Query_BTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorsAtHeightRequest) +func _Query_ActiveBTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryActiveBTCValidatorsAtHeightRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidatorsAtHeight(ctx, in) + return srv.(QueryServer).ActiveBTCValidatorsAtHeight(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorsAtHeight", + FullMethod: "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidatorsAtHeight(ctx, req.(*QueryBTCValidatorsAtHeightRequest)) + return srv.(QueryServer).ActiveBTCValidatorsAtHeight(ctx, req.(*QueryActiveBTCValidatorsAtHeightRequest)) } return interceptor(ctx, in, info, handler) } @@ -969,8 +972,8 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_BTCValidators_Handler, }, { - MethodName: "BTCValidatorsAtHeight", - Handler: _Query_BTCValidatorsAtHeight_Handler, + MethodName: "ActiveBTCValidatorsAtHeight", + Handler: _Query_ActiveBTCValidatorsAtHeight_Handler, }, { MethodName: "BTCValidatorPowerAtHeight", @@ -1192,7 +1195,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } -func (m *QueryBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1202,12 +1205,12 @@ func (m *QueryBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryBTCValidatorsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1232,7 +1235,7 @@ func (m *QueryBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (i return len(dAtA) - i, nil } -func (m *QueryBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1242,12 +1245,12 @@ func (m *QueryBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *QueryBTCValidatorsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1364,13 +1367,8 @@ func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) i-- dAtA[i] = 0x1a } - if m.NoJurySigOnly { - i-- - if m.NoJurySigOnly { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + if m.DelStatus != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.DelStatus)) i-- dAtA[i] = 0x10 } @@ -1524,7 +1522,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorsAtHeightRequest) Size() (n int) { +func (m *QueryActiveBTCValidatorsAtHeightRequest) Size() (n int) { if m == nil { return 0 } @@ -1540,7 +1538,7 @@ func (m *QueryBTCValidatorsAtHeightRequest) Size() (n int) { return n } -func (m *QueryBTCValidatorsAtHeightResponse) Size() (n int) { +func (m *QueryActiveBTCValidatorsAtHeightResponse) Size() (n int) { if m == nil { return 0 } @@ -1590,8 +1588,8 @@ func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } - if m.NoJurySigOnly { - n += 2 + if m.DelStatus != 0 { + n += 1 + sovQuery(uint64(m.DelStatus)) } if m.Pagination != nil { l = m.Pagination.Size() @@ -2134,7 +2132,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { +func (m *QueryActiveBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2157,10 +2155,10 @@ func (m *QueryBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorsAtHeightRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2239,7 +2237,7 @@ func (m *QueryBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { +func (m *QueryActiveBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2262,10 +2260,10 @@ func (m *QueryBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorsAtHeightResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2541,9 +2539,9 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field NoJurySigOnly", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelStatus", wireType) } - var v int + m.DelStatus = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowQuery @@ -2553,12 +2551,11 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + m.DelStatus |= BTCDelegationStatus(b&0x7F) << shift if b < 0x80 { break } } - m.NoJurySigOnly = bool(v != 0) case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index f03be6485..1b8f841e8 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -88,11 +88,11 @@ func local_request_Query_BTCValidators_0(ctx context.Context, marshaler runtime. } var ( - filter_Query_BTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + filter_Query_ActiveBTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) -func request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorsAtHeightRequest +func request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActiveBTCValidatorsAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -116,17 +116,17 @@ func request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler runtim if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveBTCValidatorsAtHeight_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.BTCValidatorsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ActiveBTCValidatorsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorsAtHeightRequest +func local_request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActiveBTCValidatorsAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -150,11 +150,11 @@ func local_request_Query_BTCValidatorsAtHeight_0(ctx context.Context, marshaler if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveBTCValidatorsAtHeight_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.BTCValidatorsAtHeight(ctx, &protoReq) + msg, err := server.ActiveBTCValidatorsAtHeight(ctx, &protoReq) return msg, metadata, err } @@ -377,7 +377,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_BTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -388,7 +388,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidatorsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_ActiveBTCValidatorsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -396,7 +396,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_ActiveBTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -550,7 +550,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_BTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -559,14 +559,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidatorsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_ActiveBTCValidatorsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_ActiveBTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -638,7 +638,7 @@ var ( pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -652,7 +652,7 @@ var ( forward_Query_BTCValidators_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage diff --git a/x/btcstaking/types/utils.go b/x/btcstaking/types/utils.go new file mode 100644 index 000000000..c1ae6a521 --- /dev/null +++ b/x/btcstaking/types/utils.go @@ -0,0 +1,18 @@ +package types + +import "fmt" + +// NewBTCDelegationStatus takes a human-readable delegation status format and returns our custom enum. +// Options: Active | Pending | Expired +func NewBTCDelegationStatus(status string) (BTCDelegationStatus, error) { + if status == "Active" { + return BTCDelegationStatus_ACTIVE, nil + } + if status == "Pending" { + return BTCDelegationStatus_PENDING, nil + } + if status == "Expired" { + return BTCDelegationStatus_EXPIRED, nil + } + return BTCDelegationStatus_ACTIVE, fmt.Errorf("invalid delegation status %s", status) +} diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 785d0d09a..77ae2fd95 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -54,7 +54,7 @@ func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksReques } sdkCtx := sdk.UnwrapSDKContext(ctx) store := k.blockStore(sdkCtx) - ibs := []*types.IndexedBlock{} + var ibs []*types.IndexedBlock pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) { var ib types.IndexedBlock k.cdc.MustUnmarshal(value, &ib) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 0ebcf6cc4..56850cf6f 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "math/rand" "testing" @@ -89,38 +88,41 @@ func FuzzListBlocks(f *testing.F) { // perform a query to fetch finalized blocks and assert consistency // NOTE: pagination is already tested in Cosmos SDK so we don't test it here again, // instead only ensure it takes effect - limit := datagen.RandomInt(r, len(finalizedIndexedBlocks)) + 1 - req := &types.QueryListBlocksRequest{ - Finalized: true, - Pagination: &query.PageRequest{ - CountTotal: true, - Limit: limit, - }, - } - resp1, err := keeper.ListBlocks(ctx, req) - require.NoError(t, err) - require.LessOrEqual(t, len(resp1.Blocks), int(limit)) // check if pagination takes effect - fmt.Println(resp1.Pagination.Total, len(nonFinalizedIndexedBlocks)) - require.EqualValues(t, resp1.Pagination.Total, len(finalizedIndexedBlocks)) - for _, actualIB := range resp1.Blocks { - require.Equal(t, finalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + if len(finalizedIndexedBlocks) != 0 { + limit := datagen.RandomInt(r, len(finalizedIndexedBlocks)) + 1 + req := &types.QueryListBlocksRequest{ + Finalized: true, + Pagination: &query.PageRequest{ + CountTotal: true, + Limit: limit, + }, + } + resp1, err := keeper.ListBlocks(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp1.Blocks), int(limit)) // check if pagination takes effect + require.EqualValues(t, resp1.Pagination.Total, len(finalizedIndexedBlocks)) + for _, actualIB := range resp1.Blocks { + require.Equal(t, finalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + } } - // perform a query to fetch non-finalized blocks and assert consistency - limit = datagen.RandomInt(r, len(nonFinalizedIndexedBlocks)) + 1 - req = &types.QueryListBlocksRequest{ - Finalized: false, - Pagination: &query.PageRequest{ - CountTotal: true, - Limit: limit, - }, - } - resp2, err := keeper.ListBlocks(ctx, req) - require.NoError(t, err) - require.LessOrEqual(t, len(resp2.Blocks), int(limit)) // check if pagination takes effect - require.EqualValues(t, resp2.Pagination.Total, len(nonFinalizedIndexedBlocks)) - for _, actualIB := range resp2.Blocks { - require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + if len(nonFinalizedIndexedBlocks) != 0 { + // perform a query to fetch non-finalized blocks and assert consistency + limit := datagen.RandomInt(r, len(nonFinalizedIndexedBlocks)) + 1 + req := &types.QueryListBlocksRequest{ + Finalized: false, + Pagination: &query.PageRequest{ + CountTotal: true, + Limit: limit, + }, + } + resp2, err := keeper.ListBlocks(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp2.Blocks), int(limit)) // check if pagination takes effect + require.EqualValues(t, resp2.Pagination.Total, len(nonFinalizedIndexedBlocks)) + for _, actualIB := range resp2.Blocks { + require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + } } }) } diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index 69241a901..b220ea421 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -223,6 +223,8 @@ func (m *QueryListPublicRandomnessResponse) GetPagination() *query.PageResponse // QueryListBlocksRequest is the request type for the // Query/ListBlocks RPC method. type QueryListBlocksRequest struct { + // finalized indicates whether to only return finalized or non-finalized + // blocks Finalized bool `protobuf:"varint,1,opt,name=finalized,proto3" json:"finalized,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` From 3ce6ddf208f5f1cba0045d4757f19b0e8b8ab713 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Thu, 20 Jul 2023 18:44:53 +0300 Subject: [PATCH 036/202] fix: Do not include babylonchain on gRPC query paths for epoching & finality (#30) --- proto/babylon/btcstaking/v1/query.proto | 12 ++++++------ proto/babylon/finality/v1/query.proto | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 396a23368..a083e840f 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -13,33 +13,33 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; service Query { // Parameters queries the parameters of the module. rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/params"; + option (google.api.http).get = "/babylon/btcstaking/v1/params"; } // BTCValidators queries all btc validators rpc BTCValidators(QueryBTCValidatorsRequest) returns (QueryBTCValidatorsResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators"; + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators"; } // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. rpc ActiveBTCValidatorsAtHeight(QueryActiveBTCValidatorsAtHeightRequest) returns (QueryActiveBTCValidatorsAtHeightResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{height}"; + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{height}"; } // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height rpc BTCValidatorPowerAtHeight(QueryBTCValidatorPowerAtHeightRequest) returns (QueryBTCValidatorPowerAtHeightResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; } // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power rpc ActivatedHeight(QueryActivatedHeightRequest) returns (QueryActivatedHeightResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/activated_height"; + option (google.api.http).get = "/babylon/btcstaking/v1/activated_height"; } // BTCValidatorDelegations queries all btc delegations of the given btc validator rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { - option (google.api.http).get = "/babylonchain/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; } } diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 84adafbf3..39b608385 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -13,22 +13,22 @@ option go_package = "github.com/babylonchain/babylon/x/finality/types"; service Query { // Parameters queries the parameters of the module. rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/babylonchain/babylon/finality/v1/params"; + option (google.api.http).get = "/babylon/finality/v1/params"; } // ListPublicRandomness is a range query for public randomness of a given BTC validator rpc ListPublicRandomness(QueryListPublicRandomnessRequest) returns (QueryListPublicRandomnessResponse) { - option (google.api.http).get = "/babylonchain/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/public_randomness_list"; + option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/public_randomness_list"; } // ListBlocks is a range query for blocks at a given status rpc ListBlocks(QueryListBlocksRequest) returns (QueryListBlocksResponse) { - option (google.api.http).get = "/babylonchain/babylon/finality/v1/blocks"; + option (google.api.http).get = "/babylon/finality/v1/blocks"; } // VotesAtHeight queries btc validators who have signed the block at given height. rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { - option (google.api.http).get = "/babylonchain/babylon/finality/v1/votes/{height}"; + option (google.api.http).get = "/babylon/finality/v1/votes/{height}"; } } From ab889bb6f5ad79d89f34b1cd4b6a1a9ab02e2716 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Thu, 20 Jul 2023 19:16:51 +0300 Subject: [PATCH 037/202] fix: Update proto generated files (#31) --- .../configurer/chain/queries_btcstaking.go | 8 +- x/btcstaking/types/query.pb.go | 108 +++++++++--------- x/btcstaking/types/query.pb.gw.go | 12 +- x/finality/types/query.pb.go | 100 ++++++++-------- x/finality/types/query.pb.gw.go | 8 +- 5 files changed, 118 insertions(+), 118 deletions(-) diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index b677b8009..ca0008454 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -9,7 +9,7 @@ import ( ) func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { - bz, err := n.QueryGRPCGateway("/babylonchain/babylon/btcstaking/v1/params", url.Values{}) + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/params", url.Values{}) require.NoError(n.t, err) var resp bstypes.QueryParamsResponse @@ -20,7 +20,7 @@ func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { } func (n *NodeConfig) QueryBTCValidators() []*bstypes.BTCValidator { - bz, err := n.QueryGRPCGateway("/babylonchain/babylon/btcstaking/v1/btc_validators", url.Values{}) + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/btc_validators", url.Values{}) require.NoError(n.t, err) var resp bstypes.QueryBTCValidatorsResponse @@ -31,7 +31,7 @@ func (n *NodeConfig) QueryBTCValidators() []*bstypes.BTCValidator { } func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes.BTCValidatorWithMeta { - path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%d", height) + path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%d", height) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) @@ -43,7 +43,7 @@ func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes. } func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string, status bstypes.BTCDelegationStatus) []*bstypes.BTCDelegation { - path := fmt.Sprintf("/babylonchain/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) + path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) values := url.Values{} values.Set("del_status", fmt.Sprintf("%d", status)) bz, err := n.QueryGRPCGateway(path, values) diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 79b0a5c81..a74f00a57 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -662,60 +662,60 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 836 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x4f, 0x3b, 0x45, - 0x18, 0xee, 0x00, 0x36, 0x61, 0x2a, 0x90, 0x8c, 0xa8, 0xb0, 0x40, 0x85, 0x05, 0x01, 0xab, 0xd9, - 0xb5, 0x0b, 0xe1, 0xc0, 0x87, 0x86, 0x62, 0x14, 0x3f, 0x88, 0x65, 0x25, 0x92, 0x70, 0x69, 0x66, - 0xb7, 0x93, 0xed, 0x86, 0x65, 0x67, 0xe9, 0x4e, 0x57, 0x88, 0xe1, 0xe2, 0xcd, 0x9b, 0x09, 0xff, - 0x88, 0xde, 0x3c, 0x79, 0x95, 0x23, 0x89, 0x89, 0xf1, 0xa2, 0x31, 0xd4, 0x9b, 0x57, 0xff, 0x00, - 0xd3, 0xd9, 0xe9, 0x6f, 0xbb, 0xed, 0xf6, 0x33, 0xfc, 0x6e, 0xdb, 0x9d, 0xf7, 0x79, 0xdf, 0xe7, - 0x79, 0xde, 0x77, 0xdf, 0x29, 0x5c, 0x31, 0xb0, 0x71, 0xeb, 0x50, 0x57, 0x35, 0x98, 0xe9, 0x33, - 0x7c, 0x69, 0xbb, 0x96, 0x1a, 0xe4, 0xd5, 0xeb, 0x1a, 0xa9, 0xde, 0x2a, 0x5e, 0x95, 0x32, 0x8a, - 0x5e, 0x17, 0x21, 0x4a, 0x14, 0xa2, 0x04, 0x79, 0x69, 0xd6, 0xa2, 0x16, 0xe5, 0x11, 0x6a, 0xe3, - 0x29, 0x0c, 0x96, 0x16, 0x2d, 0x4a, 0x2d, 0x87, 0xa8, 0xd8, 0xb3, 0x55, 0xec, 0xba, 0x94, 0x61, - 0x66, 0x53, 0xd7, 0x17, 0xa7, 0x39, 0x93, 0xfa, 0x57, 0xd4, 0x57, 0x0d, 0xec, 0x93, 0xb0, 0x86, - 0x1a, 0xe4, 0x0d, 0xc2, 0x70, 0x5e, 0xf5, 0xb0, 0x65, 0xbb, 0x3c, 0x58, 0xc4, 0xca, 0xc9, 0xcc, - 0x3c, 0x5c, 0xc5, 0x57, 0xcd, 0x7c, 0xeb, 0xc9, 0x31, 0x2d, 0x44, 0x79, 0x9c, 0x3c, 0x0b, 0xd1, - 0x69, 0xa3, 0x5a, 0x91, 0x83, 0x75, 0x72, 0x5d, 0x23, 0x3e, 0x93, 0x75, 0xf8, 0x5a, 0xec, 0xad, - 0xef, 0x51, 0xd7, 0x27, 0x68, 0x0f, 0xa6, 0xc3, 0x22, 0x73, 0x60, 0x19, 0x6c, 0x66, 0xb4, 0x25, - 0x25, 0xd1, 0x00, 0x25, 0x84, 0x15, 0x26, 0x1e, 0xfe, 0x7a, 0x2b, 0xa5, 0x0b, 0x88, 0x6c, 0xc2, - 0x79, 0x9e, 0xb3, 0x70, 0x76, 0xf4, 0x35, 0x76, 0xec, 0x32, 0x66, 0xb4, 0xda, 0x2c, 0x88, 0x3e, - 0x86, 0x30, 0x92, 0x29, 0xb2, 0xaf, 0x2b, 0xa1, 0x27, 0x4a, 0xc3, 0x13, 0x25, 0xf4, 0x5d, 0x78, - 0xa2, 0x14, 0xb1, 0x45, 0x04, 0x56, 0x6f, 0x41, 0xca, 0x3f, 0x01, 0x28, 0x25, 0x55, 0x11, 0x02, - 0x3e, 0x83, 0xd3, 0x06, 0x33, 0x4b, 0xc1, 0x8b, 0x93, 0x39, 0xb0, 0x3c, 0xbe, 0x99, 0xd1, 0x56, - 0xbb, 0x08, 0x69, 0xcd, 0xa2, 0x4f, 0x19, 0xcc, 0x8c, 0x72, 0xa2, 0x4f, 0x62, 0x94, 0xc7, 0x38, - 0xe5, 0x8d, 0xbe, 0x94, 0x43, 0x22, 0x31, 0xce, 0x65, 0xf8, 0x76, 0x07, 0xe5, 0x22, 0xfd, 0x86, - 0x54, 0x0f, 0xd9, 0x31, 0xb1, 0xad, 0x0a, 0x6b, 0x9a, 0xb4, 0x0a, 0xa7, 0x03, 0xec, 0x94, 0x1a, - 0x0a, 0xbc, 0xcb, 0x52, 0x85, 0xdc, 0x70, 0xa3, 0x26, 0xf5, 0x4c, 0x80, 0x9d, 0x02, 0x33, 0x8b, - 0x97, 0xc7, 0xe4, 0x06, 0xbd, 0x01, 0xd3, 0x15, 0x8e, 0xe2, 0x94, 0x26, 0x74, 0xf1, 0x4b, 0xfe, - 0x1c, 0xae, 0xf7, 0xab, 0x22, 0x4c, 0x5a, 0x81, 0xaf, 0x06, 0x94, 0xd9, 0xae, 0x55, 0xf2, 0x1a, - 0xe7, 0xbc, 0xc8, 0x84, 0x9e, 0x09, 0xdf, 0x71, 0x88, 0xfc, 0x3d, 0x80, 0x1b, 0x3c, 0xdb, 0xa1, - 0xc9, 0xec, 0x80, 0xc4, 0xcc, 0x6e, 0x67, 0x1d, 0x11, 0x02, 0xad, 0x84, 0xda, 0x5a, 0x3e, 0x36, - 0x72, 0xcb, 0x7f, 0x05, 0x70, 0xb3, 0x3f, 0x17, 0xa1, 0x4d, 0xef, 0x32, 0x00, 0xef, 0x0e, 0x30, - 0x00, 0xe7, 0x36, 0xab, 0x9c, 0x10, 0x86, 0x5f, 0xda, 0x20, 0x2c, 0xc1, 0x85, 0x48, 0x08, 0x66, - 0xa4, 0x1c, 0x33, 0x52, 0xde, 0x81, 0x8b, 0xc9, 0xc7, 0x42, 0x5b, 0x17, 0xa3, 0xe5, 0x3f, 0x01, - 0x5c, 0xed, 0x68, 0xfd, 0x47, 0xc4, 0x21, 0x56, 0xb8, 0x81, 0x86, 0x1a, 0xaf, 0x4f, 0x21, 0x2c, - 0x13, 0xa7, 0xe4, 0x33, 0xcc, 0x6a, 0x3e, 0x17, 0x3b, 0xad, 0xe5, 0xba, 0x9b, 0x17, 0x95, 0xf9, - 0x8a, 0x23, 0xf4, 0xc9, 0x32, 0x71, 0xc2, 0xc7, 0xb6, 0x01, 0x18, 0x1f, 0x79, 0x00, 0x7e, 0x01, - 0x70, 0xad, 0xb7, 0x3e, 0x61, 0xd0, 0x09, 0x9c, 0x69, 0x88, 0x2b, 0x47, 0x47, 0xa2, 0xfb, 0x6b, - 0x83, 0x08, 0xd0, 0x1b, 0x93, 0xd3, 0x92, 0xf6, 0xd9, 0xfa, 0xae, 0xfd, 0x3e, 0x09, 0x5f, 0xe1, - 0x02, 0xd0, 0x3d, 0x80, 0xe9, 0x70, 0x79, 0xa2, 0x77, 0xba, 0x70, 0xea, 0xdc, 0xd6, 0x52, 0x6e, - 0x90, 0xd0, 0xb0, 0xae, 0xac, 0x7d, 0xf7, 0xdb, 0x3f, 0xf7, 0x63, 0xef, 0xa1, 0x9c, 0x2a, 0x30, - 0x66, 0x05, 0xdb, 0xae, 0xda, 0xeb, 0x46, 0x41, 0x3f, 0x02, 0x38, 0x15, 0xfb, 0xac, 0xd0, 0xfb, - 0xbd, 0x2a, 0x26, 0x2d, 0x78, 0x29, 0x3f, 0x04, 0x42, 0x50, 0xdd, 0xe5, 0x54, 0xb7, 0x91, 0x36, - 0x08, 0xd5, 0xf8, 0x57, 0x8d, 0xea, 0x00, 0x2e, 0xf4, 0xd8, 0x07, 0xe8, 0x83, 0x5e, 0x74, 0xfa, - 0x2f, 0x35, 0xe9, 0xc3, 0x91, 0xf1, 0x42, 0xdc, 0x11, 0x17, 0x77, 0x80, 0xf6, 0x86, 0x17, 0xa7, - 0x7e, 0x1b, 0x7e, 0xd8, 0x77, 0xe8, 0x3f, 0x00, 0xe7, 0xbb, 0xee, 0x73, 0xb4, 0x3f, 0xa8, 0xe5, - 0x49, 0x97, 0x8d, 0x74, 0x30, 0x22, 0x5a, 0xe8, 0xbb, 0xe0, 0xfa, 0xce, 0x90, 0x3e, 0x8a, 0xbe, - 0xf8, 0x1a, 0xba, 0x53, 0xf9, 0x3d, 0x14, 0xc9, 0xfe, 0x19, 0xc0, 0x99, 0xb6, 0x25, 0x88, 0xb4, - 0xbe, 0x0d, 0xe9, 0x58, 0xa8, 0xd2, 0xd6, 0x50, 0x18, 0x21, 0x6c, 0x9f, 0x0b, 0xdb, 0x41, 0xdb, - 0x83, 0x08, 0xc3, 0xcd, 0x24, 0x25, 0x71, 0xe9, 0xfd, 0x0b, 0xe0, 0x9b, 0x5d, 0xd6, 0x14, 0xda, - 0x1d, 0xd4, 0xf1, 0xce, 0xdd, 0x2d, 0xed, 0x8d, 0x84, 0x15, 0x92, 0xce, 0xb9, 0xa4, 0x53, 0xf4, - 0xe5, 0x73, 0xf4, 0xaa, 0x65, 0xbb, 0x16, 0xbe, 0x78, 0x78, 0xca, 0x82, 0xc7, 0xa7, 0x2c, 0xf8, - 0xfb, 0x29, 0x0b, 0x7e, 0xa8, 0x67, 0x53, 0x8f, 0xf5, 0x6c, 0xea, 0x8f, 0x7a, 0x36, 0x75, 0xa1, - 0x59, 0x36, 0xab, 0xd4, 0x0c, 0xc5, 0xa4, 0x57, 0xc9, 0x45, 0x6f, 0x5a, 0xcb, 0xb2, 0x5b, 0x8f, - 0xf8, 0x46, 0x9a, 0xff, 0x63, 0xdd, 0xfa, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x98, 0xcd, 0x91, - 0x99, 0x0b, 0x00, 0x00, + // 840 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4d, 0x4f, 0x1b, 0x47, + 0x18, 0xf6, 0x00, 0xb5, 0xc4, 0xb8, 0x80, 0x34, 0xa5, 0x2d, 0x2c, 0x60, 0x60, 0xf9, 0x30, 0x50, + 0x75, 0x17, 0x1b, 0x89, 0x43, 0xe9, 0x87, 0x70, 0x69, 0xa1, 0xb4, 0x48, 0xee, 0xb6, 0x6a, 0xa5, + 0x5c, 0xac, 0xd9, 0xf5, 0x68, 0xbd, 0x62, 0xd9, 0x59, 0xbc, 0xe3, 0x0d, 0x28, 0xe2, 0x92, 0x43, + 0x94, 0xdc, 0x22, 0xe5, 0x27, 0xe4, 0x98, 0x53, 0xfe, 0x40, 0xae, 0xe1, 0x88, 0x14, 0x29, 0xca, + 0x25, 0x28, 0x82, 0x48, 0xf9, 0x1b, 0x91, 0x67, 0xc7, 0xec, 0xfa, 0x63, 0x6d, 0x63, 0x25, 0x37, + 0xb3, 0xf3, 0x3e, 0xf3, 0x7c, 0xcc, 0x3b, 0xef, 0x00, 0xe7, 0x75, 0xac, 0x9f, 0xda, 0xd4, 0x51, + 0x75, 0x66, 0x78, 0x0c, 0x1f, 0x5a, 0x8e, 0xa9, 0xfa, 0x59, 0xf5, 0xb8, 0x4a, 0x2a, 0xa7, 0x8a, + 0x5b, 0xa1, 0x8c, 0xa2, 0xaf, 0x45, 0x89, 0x12, 0x96, 0x28, 0x7e, 0x56, 0x1a, 0x37, 0xa9, 0x49, + 0x79, 0x85, 0x5a, 0xfb, 0x15, 0x14, 0x4b, 0xd3, 0x26, 0xa5, 0xa6, 0x4d, 0x54, 0xec, 0x5a, 0x2a, + 0x76, 0x1c, 0xca, 0x30, 0xb3, 0xa8, 0xe3, 0x89, 0xd5, 0x35, 0x83, 0x7a, 0x47, 0xd4, 0x53, 0x75, + 0xec, 0x91, 0x80, 0x43, 0xf5, 0xb3, 0x3a, 0x61, 0x38, 0xab, 0xba, 0xd8, 0xb4, 0x1c, 0x5e, 0x2c, + 0x6a, 0xe5, 0xf6, 0xca, 0x5c, 0x5c, 0xc1, 0x47, 0xf5, 0xfd, 0x96, 0xdb, 0xd7, 0x44, 0x84, 0xf2, + 0x3a, 0x79, 0x1c, 0xa2, 0xbf, 0x6b, 0x6c, 0x05, 0x0e, 0xd6, 0xc8, 0x71, 0x95, 0x78, 0x4c, 0xd6, + 0xe0, 0x57, 0x0d, 0x5f, 0x3d, 0x97, 0x3a, 0x1e, 0x41, 0x5b, 0x30, 0x19, 0x90, 0x4c, 0x80, 0x39, + 0xb0, 0x92, 0xca, 0xcd, 0x28, 0x6d, 0x03, 0x50, 0x02, 0x58, 0x7e, 0xe8, 0xfc, 0x72, 0x36, 0xa1, + 0x09, 0x88, 0x6c, 0xc0, 0x49, 0xbe, 0x67, 0xfe, 0xdf, 0x5f, 0xff, 0xc3, 0xb6, 0x55, 0xc2, 0x8c, + 0x56, 0xea, 0x84, 0xe8, 0x77, 0x08, 0x43, 0x9b, 0x62, 0xf7, 0x65, 0x25, 0xc8, 0x44, 0xa9, 0x65, + 0xa2, 0x04, 0xb9, 0x8b, 0x4c, 0x94, 0x02, 0x36, 0x89, 0xc0, 0x6a, 0x11, 0xa4, 0xfc, 0x1c, 0x40, + 0xa9, 0x1d, 0x8b, 0x30, 0xb0, 0x0f, 0x47, 0x75, 0x66, 0x14, 0xfd, 0x9b, 0x95, 0x09, 0x30, 0x37, + 0xb8, 0x92, 0xca, 0x2d, 0xc4, 0x18, 0x89, 0xee, 0xa2, 0x8d, 0xe8, 0xcc, 0x08, 0xf7, 0x44, 0xbb, + 0x0d, 0x92, 0x07, 0xb8, 0xe4, 0x4c, 0x57, 0xc9, 0x81, 0x90, 0x06, 0xcd, 0x25, 0xb8, 0xd4, 0x22, + 0xb9, 0x40, 0xef, 0x92, 0xca, 0x36, 0xdb, 0x23, 0x96, 0x59, 0x66, 0xf5, 0x90, 0x16, 0xe0, 0xa8, + 0x8f, 0xed, 0x62, 0xcd, 0x81, 0x7b, 0x58, 0x2c, 0x93, 0x13, 0x1e, 0xd4, 0xb0, 0x96, 0xf2, 0xb1, + 0x9d, 0x67, 0x46, 0xe1, 0x70, 0x8f, 0x9c, 0xa0, 0x6f, 0x60, 0xb2, 0xcc, 0x51, 0x5c, 0xd2, 0x90, + 0x26, 0xfe, 0x92, 0xff, 0x84, 0xcb, 0xdd, 0x58, 0x44, 0x48, 0xf3, 0xf0, 0x4b, 0x9f, 0x32, 0xcb, + 0x31, 0x8b, 0x6e, 0x6d, 0x9d, 0x93, 0x0c, 0x69, 0xa9, 0xe0, 0x1b, 0x87, 0xc8, 0x8f, 0x00, 0xcc, + 0xf0, 0xdd, 0xb6, 0x0d, 0x66, 0xf9, 0xa4, 0x21, 0xec, 0x66, 0xd5, 0xa1, 0x20, 0x10, 0x15, 0xd4, + 0x74, 0xe4, 0x03, 0x7d, 0x1f, 0xf9, 0x4b, 0x00, 0x57, 0xba, 0x6b, 0x11, 0xde, 0xb4, 0x98, 0x06, + 0xf8, 0xae, 0x87, 0x06, 0xf8, 0xdf, 0x62, 0xe5, 0x03, 0xc2, 0xf0, 0x67, 0x6b, 0x84, 0x19, 0x38, + 0x15, 0x1a, 0xc1, 0x8c, 0x94, 0x1a, 0x82, 0x94, 0x37, 0xe1, 0x74, 0xfb, 0x65, 0xe1, 0x2d, 0x26, + 0x68, 0xf9, 0x2d, 0x80, 0x0b, 0x2d, 0x47, 0xbf, 0x43, 0x6c, 0x62, 0x06, 0x13, 0xe8, 0x56, 0xed, + 0xf5, 0x07, 0x84, 0x25, 0x62, 0x17, 0x3d, 0x86, 0x59, 0xd5, 0xe3, 0x66, 0x47, 0x73, 0x6b, 0xf1, + 0xe1, 0x85, 0x34, 0xff, 0x70, 0x84, 0x36, 0x5c, 0x22, 0x76, 0xf0, 0xb3, 0xa9, 0x01, 0x06, 0xfb, + 0x6e, 0x80, 0x17, 0x00, 0x2e, 0x76, 0xf6, 0x27, 0x02, 0x3a, 0x80, 0x63, 0x35, 0x73, 0xa5, 0x70, + 0x49, 0x9c, 0xfe, 0x62, 0x2f, 0x06, 0xb4, 0x5a, 0xe7, 0x44, 0xb6, 0xfd, 0x64, 0xe7, 0x9e, 0x7b, + 0x38, 0x0c, 0xbf, 0xe0, 0x06, 0xd0, 0x03, 0x00, 0x93, 0xc1, 0xf0, 0x44, 0xab, 0x31, 0x9a, 0x5a, + 0xa7, 0xb5, 0xb4, 0xd6, 0x4b, 0x69, 0xc0, 0x2b, 0x2f, 0xdd, 0x7f, 0xf5, 0xfe, 0xc9, 0xc0, 0x2c, + 0x9a, 0x51, 0x3b, 0x3d, 0x22, 0xe8, 0x29, 0x80, 0x23, 0x0d, 0x37, 0x09, 0xad, 0x77, 0x22, 0x69, + 0x37, 0xd3, 0xa5, 0xec, 0x2d, 0x10, 0x42, 0xdd, 0xf7, 0x5c, 0x5d, 0x06, 0x2d, 0xa9, 0xb1, 0xcf, + 0x57, 0xe4, 0xee, 0xa2, 0xd7, 0x00, 0x4e, 0x75, 0xb8, 0xf5, 0xe8, 0xe7, 0x4e, 0x0a, 0xba, 0x8f, + 0x2e, 0xe9, 0x97, 0xbe, 0xf1, 0xc2, 0xcf, 0x26, 0xf7, 0xb3, 0x8e, 0x94, 0x9e, 0xfc, 0xa8, 0xf7, + 0x82, 0x1b, 0x7b, 0x86, 0x3e, 0x00, 0x38, 0x19, 0x3b, 0xa8, 0xd1, 0x8f, 0xbd, 0x06, 0xdb, 0xee, + 0x15, 0x91, 0x7e, 0xea, 0x13, 0x2d, 0x2c, 0x1d, 0x70, 0x4b, 0xbb, 0xe8, 0xb7, 0x1e, 0x2d, 0x35, + 0x8e, 0x94, 0x33, 0x95, 0xbf, 0x29, 0xa1, 0xd3, 0x67, 0x00, 0x8e, 0x35, 0x0d, 0x34, 0x94, 0xeb, + 0x1a, 0x7b, 0xcb, 0x70, 0x94, 0x36, 0x6e, 0x85, 0x11, 0x5e, 0x54, 0xee, 0x65, 0x15, 0x65, 0x62, + 0xbc, 0xe0, 0x3a, 0xae, 0x28, 0xde, 0xac, 0x4b, 0x00, 0xbf, 0x8d, 0x99, 0x32, 0xe8, 0x87, 0x5e, + 0x73, 0x6d, 0x1d, 0xbd, 0xd2, 0x56, 0x5f, 0x58, 0xe1, 0x62, 0x9f, 0xbb, 0xd8, 0x41, 0xf9, 0x3e, + 0x4f, 0x24, 0x32, 0x0f, 0xf3, 0x7f, 0x9d, 0x5f, 0xa5, 0xc1, 0xc5, 0x55, 0x1a, 0xbc, 0xbb, 0x4a, + 0x83, 0xc7, 0xd7, 0xe9, 0xc4, 0xc5, 0x75, 0x3a, 0xf1, 0xe6, 0x3a, 0x9d, 0xb8, 0x93, 0x33, 0x2d, + 0x56, 0xae, 0xea, 0x8a, 0x41, 0x8f, 0xea, 0x3c, 0x46, 0x19, 0x5b, 0xce, 0x0d, 0xe9, 0x49, 0x94, + 0x96, 0x9d, 0xba, 0xc4, 0xd3, 0x93, 0xfc, 0x7f, 0xcc, 0x8d, 0x8f, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x64, 0x9b, 0x7f, 0x68, 0x4b, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 1b8f841e8..112e6df42 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -634,17 +634,17 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"babylonchain", "babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index b220ea421..f1875b71a 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -436,56 +436,56 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 778 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4f, 0xdb, 0x48, - 0x14, 0x8e, 0x03, 0x64, 0x97, 0x81, 0xfd, 0xa1, 0x21, 0x62, 0xd9, 0x2c, 0x4a, 0x82, 0x57, 0x5a, - 0x22, 0x76, 0x65, 0x27, 0x81, 0x45, 0xec, 0x4a, 0x7b, 0xd8, 0x48, 0xa5, 0xd0, 0x1f, 0xaa, 0xeb, - 0x4a, 0x55, 0xc5, 0x25, 0x1d, 0x3b, 0x53, 0xc7, 0x8a, 0xe3, 0x31, 0xf6, 0xd8, 0x4a, 0x8a, 0xe8, - 0xa1, 0xf7, 0xaa, 0x95, 0x7a, 0xea, 0xa1, 0x97, 0x1e, 0xfb, 0x97, 0xa0, 0x9e, 0x90, 0x7a, 0xa9, - 0x38, 0xa0, 0x0a, 0x2a, 0xf5, 0xdf, 0xa8, 0x3c, 0x33, 0x49, 0x48, 0x31, 0x04, 0x2a, 0x6e, 0x99, - 0xf1, 0x7b, 0xef, 0xfb, 0xde, 0xf7, 0xde, 0x37, 0x01, 0x05, 0x03, 0x19, 0x5d, 0x87, 0xb8, 0xea, - 0x23, 0xdb, 0x45, 0x8e, 0x4d, 0xbb, 0x6a, 0x54, 0x51, 0xb7, 0x43, 0xec, 0x77, 0x15, 0xcf, 0x27, - 0x94, 0xc0, 0x19, 0x11, 0xa0, 0xf4, 0x02, 0x94, 0xa8, 0x92, 0xcb, 0x5a, 0xc4, 0x22, 0xec, 0xbb, - 0x1a, 0xff, 0xe2, 0xa1, 0xb9, 0x79, 0x8b, 0x10, 0xcb, 0xc1, 0x2a, 0xf2, 0x6c, 0x15, 0xb9, 0x2e, - 0xa1, 0x88, 0xda, 0xc4, 0x0d, 0xc4, 0xd7, 0x25, 0x93, 0x04, 0x6d, 0x12, 0xa8, 0x06, 0x0a, 0x30, - 0x47, 0x50, 0xa3, 0x8a, 0x81, 0x29, 0xaa, 0xa8, 0x1e, 0xb2, 0x6c, 0x97, 0x05, 0x8b, 0xd8, 0x62, - 0x12, 0x2b, 0x0f, 0xf9, 0xa8, 0xdd, 0xab, 0x26, 0x27, 0x45, 0xf4, 0x29, 0xb2, 0x18, 0x39, 0x0b, - 0xe0, 0xdd, 0x18, 0x47, 0x63, 0x89, 0x3a, 0xde, 0x0e, 0x71, 0x40, 0x65, 0x0d, 0xcc, 0x0c, 0xdd, - 0x06, 0x1e, 0x71, 0x03, 0x0c, 0xff, 0x01, 0x19, 0x0e, 0x30, 0x27, 0x15, 0xa5, 0xd2, 0x54, 0xf5, - 0x37, 0x25, 0xa1, 0x71, 0x85, 0x27, 0xd5, 0xc6, 0xf7, 0x0e, 0x0b, 0x29, 0x5d, 0x24, 0xc8, 0xcf, - 0x25, 0x50, 0x64, 0x25, 0x6f, 0xd9, 0x01, 0xd5, 0x42, 0xc3, 0xb1, 0x4d, 0x1d, 0xb9, 0x0d, 0xd2, - 0x76, 0x71, 0xd0, 0x83, 0x85, 0xbf, 0x83, 0x1f, 0x23, 0xe4, 0xd4, 0x0d, 0x6a, 0xd6, 0xbd, 0x56, - 0xbd, 0x89, 0x3b, 0x0c, 0x67, 0x52, 0x9f, 0x8a, 0x90, 0x53, 0xa3, 0xa6, 0xd6, 0xda, 0xc0, 0x1d, - 0xb8, 0x0e, 0xc0, 0x40, 0x8b, 0xb9, 0x34, 0x23, 0xf2, 0x87, 0xc2, 0x85, 0x53, 0x62, 0xe1, 0x14, - 0x3e, 0x1a, 0x21, 0x9c, 0xa2, 0x21, 0x0b, 0x0b, 0x00, 0xfd, 0x44, 0xa6, 0xbc, 0x9f, 0x06, 0x0b, - 0xe7, 0x30, 0x12, 0x2d, 0xbf, 0x91, 0xc0, 0xb4, 0x17, 0x1a, 0x75, 0x1f, 0xb9, 0x8d, 0x7a, 0x1b, - 0x79, 0x73, 0x52, 0x71, 0xac, 0x34, 0x55, 0x5d, 0x4f, 0xec, 0x7c, 0x64, 0x39, 0x45, 0x0b, 0x8d, - 0xf8, 0xf6, 0x36, 0xf2, 0xae, 0xb9, 0xd4, 0xef, 0xd6, 0xd6, 0x0e, 0x0e, 0x0b, 0x2b, 0x96, 0x4d, - 0x9b, 0xa1, 0xa1, 0x98, 0xa4, 0xad, 0x8a, 0xaa, 0x66, 0x13, 0xd9, 0x6e, 0xef, 0xa0, 0xd2, 0xae, - 0x87, 0x03, 0xe5, 0x9e, 0xd9, 0x74, 0x89, 0xef, 0x8b, 0x0a, 0x3a, 0xf0, 0xfa, 0xa5, 0xe0, 0xf5, - 0x04, 0x49, 0x16, 0x47, 0x4a, 0xc2, 0x29, 0x9d, 0xd4, 0x24, 0xf7, 0x1f, 0xf8, 0xe9, 0x2b, 0x86, - 0xf0, 0x67, 0x30, 0xd6, 0xc2, 0x5d, 0x36, 0x88, 0x71, 0x3d, 0xfe, 0x09, 0xb3, 0x60, 0x22, 0x42, - 0x4e, 0x88, 0x19, 0xd0, 0xb4, 0xce, 0x0f, 0xff, 0xa6, 0xd7, 0x24, 0xf9, 0x09, 0x98, 0xed, 0x4b, - 0x50, 0x73, 0x88, 0xd9, 0xea, 0x4f, 0x76, 0x1e, 0x4c, 0x72, 0xa1, 0x1e, 0xe3, 0x06, 0xab, 0xf5, - 0xbd, 0x3e, 0xb8, 0xb8, 0xb2, 0x91, 0xbe, 0x96, 0xc0, 0x2f, 0xa7, 0x08, 0x0c, 0x76, 0xd7, 0x60, - 0x37, 0x62, 0x82, 0x0b, 0x89, 0x13, 0xdc, 0x74, 0x1b, 0xb8, 0x83, 0x1b, 0x2c, 0x57, 0x17, 0x09, - 0x57, 0x26, 0xaf, 0xbc, 0x0c, 0x7e, 0x65, 0xf4, 0xee, 0x13, 0x8a, 0x83, 0xff, 0xe9, 0x06, 0xb6, - 0xad, 0x26, 0xed, 0x49, 0x34, 0x0b, 0x32, 0x4d, 0x76, 0x21, 0xb4, 0x16, 0x27, 0xb9, 0x0d, 0x72, - 0x49, 0x49, 0xa2, 0xad, 0x3b, 0xe0, 0x3b, 0x6e, 0x17, 0xde, 0xd7, 0x74, 0x6d, 0xf5, 0xe0, 0xb0, - 0x50, 0xbd, 0xd8, 0x46, 0xd5, 0x36, 0xb5, 0xe5, 0x95, 0xb2, 0x16, 0x1a, 0x37, 0x71, 0x57, 0xcf, - 0x18, 0xb1, 0xc1, 0x82, 0xea, 0xbb, 0x09, 0x30, 0xc1, 0xf0, 0xe0, 0x33, 0x09, 0x64, 0xb8, 0x97, - 0xe1, 0xe2, 0xd9, 0xeb, 0x3e, 0xf4, 0x70, 0xe4, 0x4a, 0xa3, 0x03, 0x39, 0x71, 0xb9, 0xfc, 0xf4, - 0xfd, 0xa7, 0x97, 0xe9, 0x25, 0x58, 0x4a, 0x66, 0x78, 0xfa, 0x51, 0x83, 0x9f, 0x25, 0x90, 0x4d, - 0x32, 0x17, 0xfc, 0xfb, 0xb2, 0x66, 0xe4, 0x5c, 0x57, 0xbf, 0xcd, 0xc3, 0xf2, 0x43, 0xc6, 0x7c, - 0x0b, 0x3e, 0x18, 0xcd, 0x3c, 0x1e, 0x4d, 0x84, 0x1c, 0xbb, 0x81, 0x28, 0xf1, 0x03, 0x75, 0x67, - 0xf8, 0x75, 0xdb, 0x55, 0x3d, 0x86, 0xc1, 0x1e, 0x17, 0x0e, 0x52, 0x77, 0xec, 0x80, 0xc2, 0x57, - 0x12, 0x00, 0x83, 0x15, 0x86, 0x7f, 0x9e, 0x4f, 0x74, 0xc8, 0x69, 0xb9, 0xbf, 0x2e, 0x16, 0x7c, - 0xf9, 0x29, 0x08, 0x33, 0xbc, 0x95, 0xc0, 0x0f, 0x43, 0xab, 0x08, 0x95, 0xb3, 0x11, 0x93, 0x16, - 0x3d, 0xa7, 0x5e, 0x38, 0x5e, 0x90, 0x5c, 0x63, 0x24, 0xab, 0xb0, 0x3c, 0x9a, 0x64, 0x14, 0x17, - 0x50, 0x77, 0xb8, 0x75, 0x76, 0x6b, 0x37, 0xf6, 0x8e, 0xf2, 0xd2, 0xfe, 0x51, 0x5e, 0xfa, 0x78, - 0x94, 0x97, 0x5e, 0x1c, 0xe7, 0x53, 0xfb, 0xc7, 0xf9, 0xd4, 0x87, 0xe3, 0x7c, 0x6a, 0xab, 0x3c, - 0xca, 0x22, 0x9d, 0x41, 0x5d, 0xe6, 0x16, 0x23, 0xc3, 0xfe, 0x30, 0x97, 0xbf, 0x04, 0x00, 0x00, - 0xff, 0xff, 0xf5, 0xad, 0xaa, 0x4c, 0x0e, 0x08, 0x00, 0x00, + // 773 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4f, 0xe3, 0x46, + 0x14, 0x8e, 0x03, 0xa4, 0x65, 0xa0, 0x3f, 0x34, 0x44, 0x94, 0x06, 0x9a, 0x04, 0xa3, 0x16, 0x54, + 0x2a, 0x0f, 0x09, 0x14, 0xd1, 0x4a, 0x3d, 0x34, 0x52, 0x29, 0x94, 0xa2, 0xba, 0x46, 0xea, 0xa1, + 0x97, 0x68, 0xec, 0x4c, 0x1d, 0x2b, 0x8e, 0xc7, 0xd8, 0x63, 0x2b, 0x29, 0xa2, 0xaa, 0x7a, 0xae, + 0xb4, 0x2b, 0xad, 0xb4, 0x37, 0x2e, 0xfb, 0xd7, 0x70, 0x44, 0xda, 0xcb, 0x8a, 0x03, 0x5a, 0xc1, + 0xfe, 0x21, 0x2b, 0xcf, 0x4c, 0x12, 0xb2, 0x6b, 0x48, 0x76, 0xc5, 0x2d, 0x33, 0xfe, 0xde, 0xf7, + 0xbe, 0xf7, 0xbd, 0xf7, 0x26, 0xa0, 0x64, 0x62, 0xb3, 0xeb, 0x52, 0x0f, 0xfd, 0xe5, 0x78, 0xd8, + 0x75, 0x58, 0x17, 0xc5, 0x15, 0x74, 0x1c, 0x91, 0xa0, 0xab, 0xf9, 0x01, 0x65, 0x14, 0xce, 0x49, + 0x80, 0xd6, 0x03, 0x68, 0x71, 0xa5, 0x90, 0xb7, 0xa9, 0x4d, 0xf9, 0x77, 0x94, 0xfc, 0x12, 0xd0, + 0xc2, 0x92, 0x4d, 0xa9, 0xed, 0x12, 0x84, 0x7d, 0x07, 0x61, 0xcf, 0xa3, 0x0c, 0x33, 0x87, 0x7a, + 0xa1, 0xfc, 0xfa, 0xb5, 0x45, 0xc3, 0x36, 0x0d, 0x91, 0x89, 0x43, 0x22, 0x32, 0xa0, 0xb8, 0x62, + 0x12, 0x86, 0x2b, 0xc8, 0xc7, 0xb6, 0xe3, 0x71, 0xb0, 0xc4, 0x96, 0xd3, 0x54, 0xf9, 0x38, 0xc0, + 0xed, 0x1e, 0x9b, 0x9a, 0x86, 0xe8, 0x4b, 0xe4, 0x18, 0x35, 0x0f, 0xe0, 0xef, 0x49, 0x1e, 0x9d, + 0x07, 0x1a, 0xe4, 0x38, 0x22, 0x21, 0x53, 0x75, 0x30, 0x37, 0x74, 0x1b, 0xfa, 0xd4, 0x0b, 0x09, + 0xfc, 0x0e, 0xe4, 0x44, 0x82, 0x05, 0xa5, 0xac, 0xac, 0xcd, 0x54, 0x17, 0xb5, 0x94, 0xc2, 0x35, + 0x11, 0x54, 0x9b, 0x3c, 0xbf, 0x2a, 0x65, 0x0c, 0x19, 0xa0, 0x3e, 0x52, 0x40, 0x99, 0x53, 0xfe, + 0xea, 0x84, 0x4c, 0x8f, 0x4c, 0xd7, 0xb1, 0x0c, 0xec, 0x35, 0x68, 0xdb, 0x23, 0x61, 0x2f, 0x2d, + 0x5c, 0x01, 0x1f, 0xc7, 0xd8, 0xad, 0x9b, 0xcc, 0xaa, 0xfb, 0xad, 0x7a, 0x93, 0x74, 0x78, 0x9e, + 0x69, 0x63, 0x26, 0xc6, 0x6e, 0x8d, 0x59, 0x7a, 0x6b, 0x8f, 0x74, 0xe0, 0x2e, 0x00, 0x03, 0x2f, + 0x16, 0xb2, 0x5c, 0xc8, 0x57, 0x9a, 0x30, 0x4e, 0x4b, 0x8c, 0xd3, 0x44, 0x6b, 0xa4, 0x71, 0x9a, + 0x8e, 0x6d, 0x22, 0x13, 0x18, 0xb7, 0x22, 0xd5, 0x8b, 0x2c, 0x58, 0xbe, 0x47, 0x91, 0x2c, 0xf9, + 0x99, 0x02, 0x66, 0xfd, 0xc8, 0xac, 0x07, 0xd8, 0x6b, 0xd4, 0xdb, 0xd8, 0x5f, 0x50, 0xca, 0x13, + 0x6b, 0x33, 0xd5, 0xdd, 0xd4, 0xca, 0x47, 0xd2, 0x69, 0x7a, 0x64, 0x26, 0xb7, 0x87, 0xd8, 0xff, + 0xc9, 0x63, 0x41, 0xb7, 0xb6, 0x73, 0x79, 0x55, 0xda, 0xb2, 0x1d, 0xd6, 0x8c, 0x4c, 0xcd, 0xa2, + 0x6d, 0x24, 0x59, 0xad, 0x26, 0x76, 0xbc, 0xde, 0x01, 0xb1, 0xae, 0x4f, 0x42, 0xed, 0xc8, 0x6a, + 0x7a, 0x34, 0x08, 0x24, 0x83, 0x01, 0xfc, 0x3e, 0x15, 0xfc, 0x39, 0xc5, 0x92, 0xd5, 0x91, 0x96, + 0x08, 0x49, 0xb7, 0x3d, 0x29, 0xfc, 0x00, 0x3e, 0x79, 0x43, 0x21, 0xfc, 0x14, 0x4c, 0xb4, 0x48, + 0x97, 0x37, 0x62, 0xd2, 0x48, 0x7e, 0xc2, 0x3c, 0x98, 0x8a, 0xb1, 0x1b, 0x11, 0x9e, 0x68, 0xd6, + 0x10, 0x87, 0xef, 0xb3, 0x3b, 0x8a, 0xfa, 0x0f, 0x98, 0xef, 0x5b, 0x50, 0x73, 0xa9, 0xd5, 0xea, + 0x77, 0x76, 0x09, 0x4c, 0x0b, 0xa3, 0xfe, 0x26, 0x0d, 0xce, 0xf5, 0xa1, 0x31, 0xb8, 0x78, 0xb0, + 0x96, 0x9e, 0x29, 0xe0, 0xb3, 0xb7, 0x04, 0x0c, 0x66, 0xd7, 0xe4, 0x37, 0xb2, 0x83, 0xcb, 0xa9, + 0x1d, 0xdc, 0xf7, 0x1a, 0xa4, 0x43, 0x1a, 0x3c, 0xd6, 0x90, 0x01, 0x0f, 0x66, 0xaf, 0xba, 0x09, + 0x3e, 0xe7, 0xf2, 0xfe, 0xa0, 0x8c, 0x84, 0x3f, 0xb2, 0x3d, 0xe2, 0xd8, 0x4d, 0xd6, 0xb3, 0x68, + 0x1e, 0xe4, 0x9a, 0xfc, 0x42, 0x7a, 0x2d, 0x4f, 0x6a, 0x1b, 0x14, 0xd2, 0x82, 0x64, 0x59, 0xbf, + 0x81, 0x0f, 0xc4, 0xba, 0x88, 0xba, 0x66, 0x6b, 0xdb, 0x97, 0x57, 0xa5, 0xea, 0x78, 0x13, 0x55, + 0xdb, 0xd7, 0x37, 0xb7, 0x36, 0xf4, 0xc8, 0x3c, 0x20, 0x5d, 0x23, 0x67, 0x26, 0x0b, 0x16, 0x56, + 0x9f, 0x4e, 0x81, 0x29, 0x9e, 0x0f, 0xfe, 0xab, 0x80, 0x9c, 0xd8, 0x65, 0xb8, 0x7a, 0xf7, 0xb8, + 0x0f, 0x3d, 0x1c, 0x85, 0xb5, 0xd1, 0x40, 0x21, 0x5c, 0x5d, 0xf9, 0xef, 0xf9, 0xab, 0x27, 0xd9, + 0x2f, 0xe0, 0x22, 0xba, 0xfb, 0x1d, 0x83, 0x97, 0x0a, 0xc8, 0xa7, 0xed, 0x13, 0xfc, 0xf6, 0x5d, + 0xf7, 0x4f, 0xc8, 0xdb, 0x7e, 0xbf, 0xb5, 0x55, 0x8f, 0xb8, 0xd8, 0x43, 0x78, 0x90, 0x2a, 0x36, + 0x69, 0x40, 0x8c, 0x5d, 0xa7, 0x81, 0x19, 0x0d, 0x42, 0x74, 0x32, 0xfc, 0x86, 0x9d, 0x22, 0x9f, + 0xd3, 0xf2, 0x27, 0x44, 0xf0, 0xd6, 0x5d, 0x27, 0x64, 0xf0, 0x7f, 0x05, 0x80, 0xc1, 0xa0, 0xc2, + 0xf5, 0xfb, 0xb5, 0x0d, 0xed, 0x53, 0xe1, 0x9b, 0xf1, 0xc0, 0x63, 0x79, 0x2d, 0xa7, 0xfc, 0x4c, + 0x01, 0x1f, 0x0d, 0xcd, 0x18, 0xd4, 0xee, 0x4e, 0x92, 0x36, 0xc1, 0x05, 0x34, 0x36, 0x5e, 0xea, + 0x5a, 0xe7, 0xba, 0xbe, 0x84, 0x2b, 0xa9, 0xba, 0xe2, 0x24, 0x06, 0x9d, 0x88, 0x35, 0x38, 0xad, + 0xfd, 0x72, 0x7e, 0x5d, 0x54, 0x2e, 0xae, 0x8b, 0xca, 0xcb, 0xeb, 0xa2, 0xf2, 0xf8, 0xa6, 0x98, + 0xb9, 0xb8, 0x29, 0x66, 0x5e, 0xdc, 0x14, 0x33, 0x7f, 0x6e, 0x8c, 0x1a, 0xf7, 0xce, 0x80, 0x97, + 0x4f, 0xbe, 0x99, 0xe3, 0x7f, 0x7e, 0x9b, 0xaf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x02, 0xee, 0x0c, + 0x23, 0xda, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index 110402d7e..d42dd6da6 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -436,13 +436,13 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"babylonchain", "babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ListBlocks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"babylonchain", "babylon", "finality", "v1", "blocks"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListBlocks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "blocks"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"babylonchain", "babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( From 62d902df7bb04ce4a4170f312db92410d8845c7b Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 21 Jul 2023 10:46:44 +0300 Subject: [PATCH 038/202] feat: Add btcstaking and finality params to genesis and testnet cli (#32) --- cmd/babylond/cmd/flags.go | 86 ++++++++++++++++++++++++------------- cmd/babylond/cmd/genesis.go | 36 +++++++++++++++- cmd/babylond/cmd/testnet.go | 9 ++-- 3 files changed, 95 insertions(+), 36 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 0e24553ec..3ba0004a5 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -5,6 +5,7 @@ import ( babylonApp "github.com/babylonchain/babylon/app" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" tmrand "github.com/cometbft/cometbft/libs/rand" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" @@ -25,24 +26,32 @@ const ( flagBlocksPerYear = "blocks-per-year" flagGenesisTime = "genesis-time" flagBlockGasLimit = "block-gas-limit" + flagJuryPk = "jury-pk" + flagSlashingAddress = "slashing-address" + flagMinSlashingFee = "min-slashing-fee-sat" + flagMinPubRand = "min-pub-rand" ) type GenesisCLIArgs struct { - ChainID string - MaxActiveValidators uint32 - BtcConfirmationDepth uint64 - BtcFinalizationTimeout uint64 - CheckpointTag string - EpochInterval uint64 - BaseBtcHeaderHex string - BaseBtcHeaderHeight uint64 - InflationRateChange float64 - InflationMax float64 - InflationMin float64 - GoalBonded float64 - BlocksPerYear uint64 - GenesisTime time.Time - BlockGasLimit int64 + ChainID string + MaxActiveValidators uint32 + BtcConfirmationDepth uint64 + BtcFinalizationTimeout uint64 + CheckpointTag string + EpochInterval uint64 + BaseBtcHeaderHex string + BaseBtcHeaderHeight uint64 + InflationRateChange float64 + InflationMax float64 + InflationMin float64 + GoalBonded float64 + BlocksPerYear uint64 + GenesisTime time.Time + BlockGasLimit int64 + JuryPK string + SlashingAddress string + MinSlashingTransactionFeeSat int64 + MinPubRand uint64 } func addGenesisFlags(cmd *cobra.Command) { @@ -59,12 +68,21 @@ func addGenesisFlags(cmd *cobra.Command) { // Genesis header for the simnet cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") + // btcstaking args + cmd.Flags().String(flagJuryPk, btcstypes.DefaultParams().JuryPk.ToHexStr(), "Bitcoin staking jury public key") + cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") + cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") + // finality args + cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") + // inflation args cmd.Flags().Float64(flagInflationRateChange, 0.13, "Inflation rate change") cmd.Flags().Float64(flagInflationMax, 0.2, "Maximum inflation") cmd.Flags().Float64(flagInflationMin, 0.07, "Minimum inflation") cmd.Flags().Float64(flagGoalBonded, 0.67, "Bonded tokens goal") cmd.Flags().Uint64(flagBlocksPerYear, 6311520, "Blocks per year") + // genesis args cmd.Flags().Int64(flagGenesisTime, time.Now().Unix(), "Genesis time") + // blocks args cmd.Flags().Int64(flagBlockGasLimit, babylonApp.DefaultGasLimit, "Block gas limit") } @@ -77,6 +95,10 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { epochInterval, _ := cmd.Flags().GetUint64(flagEpochInterval) baseBtcHeaderHex, _ := cmd.Flags().GetString(flagBaseBtcHeaderHex) baseBtcHeaderHeight, _ := cmd.Flags().GetUint64(flagBaseBtcHeaderHeight) + juryPk, _ := cmd.Flags().GetString(flagJuryPk) + slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) + minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) + minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) inflationMax, _ := cmd.Flags().GetFloat64(flagInflationMax) @@ -92,20 +114,24 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { genesisTime := time.Unix(genesisTimeUnix, 0) return &GenesisCLIArgs{ - ChainID: chainID, - MaxActiveValidators: maxActiveValidators, - BtcConfirmationDepth: btcConfirmationDepth, - BtcFinalizationTimeout: btcFinalizationTimeout, - CheckpointTag: checkpointTag, - EpochInterval: epochInterval, - BaseBtcHeaderHeight: baseBtcHeaderHeight, - BaseBtcHeaderHex: baseBtcHeaderHex, - GenesisTime: genesisTime, - InflationRateChange: inflationRateChange, - InflationMax: inflationMax, - InflationMin: inflationMin, - GoalBonded: goalBonded, - BlocksPerYear: blocksPerYear, - BlockGasLimit: blockGasLimit, + ChainID: chainID, + MaxActiveValidators: maxActiveValidators, + BtcConfirmationDepth: btcConfirmationDepth, + BtcFinalizationTimeout: btcFinalizationTimeout, + CheckpointTag: checkpointTag, + EpochInterval: epochInterval, + BaseBtcHeaderHeight: baseBtcHeaderHeight, + BaseBtcHeaderHex: baseBtcHeaderHex, + JuryPK: juryPk, + SlashingAddress: slashingAddress, + MinSlashingTransactionFeeSat: minSlashingFee, + MinPubRand: minPubRand, + GenesisTime: genesisTime, + InflationRateChange: inflationRateChange, + InflationMax: inflationMax, + InflationMin: inflationMin, + GoalBonded: goalBonded, + BlocksPerYear: blocksPerYear, + BlockGasLimit: blockGasLimit, } } diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index fd61482d9..f15516c7e 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -3,6 +3,8 @@ package cmd import ( "encoding/json" "fmt" + btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types" + finalitytypes "github.com/babylonchain/babylon/x/finality/types" "time" appparams "github.com/babylonchain/babylon/app/params" @@ -65,7 +67,9 @@ Example: genesisParams = TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.InflationRateChange, + genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, + genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, + genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) } else if network == "mainnet" { @@ -139,6 +143,16 @@ func PrepareGenesis( checkpointingGenState.GenesisKeys = genesisParams.CheckpointingGenKeys appState[checkpointingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(checkpointingGenState) + // btcstaking module genesis + btcstakingGenState := btcstakingtypes.DefaultGenesis() + btcstakingGenState.Params = genesisParams.BtcstakingParams + appState[btcstakingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btcstakingGenState) + + // finality module genesis + finalityGenState := finalitytypes.DefaultGenesis() + finalityGenState.Params = genesisParams.FinalityParams + appState[finalitytypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(finalityGenState) + // staking module genesis stakingGenState := stakingtypes.GetGenesisStateFromAppState(depCdc, appState) clientCtx.Codec.MustUnmarshalJSON(appState[stakingtypes.ModuleName], stakingGenState) @@ -206,13 +220,16 @@ type GenesisParams struct { BtccheckpointParams btccheckpointtypes.Params EpochingParams epochingtypes.Params + BtcstakingParams btcstakingtypes.Params + FinalityParams finalitytypes.Params BtclightclientBaseBtcHeader btclightclienttypes.BTCHeaderInfo BlockGasLimit int64 } func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, - baseBtcHeaderHeight uint64, inflationRateChange float64, + baseBtcHeaderHeight uint64, juryPk string, slashingAddress string, minSlashingFee int64, + minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -285,11 +302,26 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 baseBtcHeaderInfo := btclightclienttypes.NewBTCHeaderInfo(&baseBtcHeader, baseBtcHeader.Hash(), baseBtcHeaderHeight, &work) genParams.BtclightclientBaseBtcHeader = *baseBtcHeaderInfo + genParams.BtcstakingParams = btcstakingtypes.DefaultParams() + genParams.BtcstakingParams.JuryPk, err = bbn.NewBIP340PubKeyFromHex(juryPk) + if err != nil { + panic(err) + } + genParams.BtcstakingParams.SlashingAddress = slashingAddress + genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee + if err := genParams.BtcstakingParams.Validate(); err != nil { + panic(err) + } + if epochInterval == 0 { panic(fmt.Sprintf("Invalid epoch interval %d", epochInterval)) } genParams.EpochingParams = epochingtypes.DefaultParams() genParams.EpochingParams.EpochInterval = epochInterval + if err := genParams.EpochingParams.Validate(); err != nil { + panic(err) + } + genParams.BlockGasLimit = blockGasLimit return genParams } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 6f3ac6cc1..0cd8502ef 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -97,10 +97,11 @@ Example: genesisParams := TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, - genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.InflationRateChange, - genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, - genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) + genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, + genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, + genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, + genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, + genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) return InitTestnet( clientCtx, cmd, config, mbm, genBalIterator, outputDir, genesisCliArgs.ChainID, minGasPrices, From aaa0aad2ae01790e68b4ad052fbfe9bb1d532597 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Sat, 22 Jul 2023 17:02:38 +0300 Subject: [PATCH 039/202] feat: Display BIP340 public keys in hex format on CLI (#33) --- cmd/babylond/cmd/flags.go | 2 +- test/e2e/btc_staking_e2e_test.go | 4 +-- .../configurer/chain/commands_btcstaking.go | 10 +++--- types/btc_schnorr_pk.go | 34 +++++++++++++++---- x/btcstaking/keeper/grpc_query_test.go | 34 +++++++++---------- x/btcstaking/keeper/voting_power_table.go | 2 +- .../keeper/voting_power_table_test.go | 2 +- x/finality/keeper/grpc_query_test.go | 8 ++--- x/finality/keeper/tallying_test.go | 4 +-- x/finality/keeper/votes.go | 2 +- 10 files changed, 62 insertions(+), 40 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 3ba0004a5..7c0781b11 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -69,7 +69,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") // btcstaking args - cmd.Flags().String(flagJuryPk, btcstypes.DefaultParams().JuryPk.ToHexStr(), "Bitcoin staking jury public key") + cmd.Flags().String(flagJuryPk, btcstypes.DefaultParams().JuryPk.MarshalHex(), "Bitcoin staking jury public key") cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") // finality args diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 938af530b..4387a64f2 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -124,7 +124,7 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr(), bstypes.BTCDelegationStatus_PENDING) + pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_PENDING) s.Len(pendingDels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) @@ -146,7 +146,7 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { nonValidatorNode.WaitForNextBlock() // query the existence of BTC delegation and assert equivalence - actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.ToHexStr(), bstypes.BTCDelegationStatus_ACTIVE) + actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_ACTIVE) s.Len(actualDels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], actualDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) } diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 1a7ae2f29..d4ca5b525 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -19,7 +19,7 @@ func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn. require.NoError(n.t, err) babylonPKHex := hex.EncodeToString(babylonPKBytes) // get BTC PK hex - btcPKHex := btcPK.ToHexStr() + btcPKHex := btcPK.MarshalHex() // get pop hex popHex, err := pop.ToHexStr() require.NoError(n.t, err) @@ -60,8 +60,8 @@ func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstyp func (n *NodeConfig) AddJurySig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, sig *bbn.BIP340Signature) { n.LogActionF("adding jury signature") - valPKHex := valPK.ToHexStr() - delPKHex := delPK.ToHexStr() + valPKHex := valPK.MarshalHex() + delPKHex := delPK.MarshalHex() sigHex := sig.ToHexStr() cmd := []string{"babylond", "tx", "btcstaking", "add-jury-sig", valPKHex, delPKHex, sigHex, "--from=val"} @@ -76,7 +76,7 @@ func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight u cmd := []string{"babylond", "tx", "finality", "commit-pubrand-list"} // add val BTC PK to cmd - valBTCPKHex := valBTCPK.ToHexStr() + valBTCPKHex := valBTCPK.MarshalHex() cmd = append(cmd, valBTCPKHex) // add start height to cmd @@ -104,7 +104,7 @@ func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight u func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint64, blockLch []byte, finalitySig *bbn.SchnorrEOTSSig) { n.LogActionF("add finality signature") - valBTCPKHex := valBTCPK.ToHexStr() + valBTCPKHex := valBTCPK.MarshalHex() blockHeightStr := strconv.FormatUint(blockHeight, 10) blockLchHex := hex.EncodeToString(blockLch) finalitySigHex := finalitySig.ToHexStr() diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index f641d1097..b321e030f 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -3,6 +3,7 @@ package types import ( "bytes" "encoding/hex" + "encoding/json" "errors" "github.com/btcsuite/btcd/btcec/v2" @@ -20,12 +21,8 @@ func NewBIP340PubKey(data []byte) (*BIP340PubKey, error) { } func NewBIP340PubKeyFromHex(hexStr string) (*BIP340PubKey, error) { - pkBytes, err := hex.DecodeString(hexStr) - if err != nil { - return nil, err - } var pk BIP340PubKey - err = pk.Unmarshal(pkBytes) + err := pk.UnmarshalHex(hexStr) return &pk, err } @@ -47,10 +44,20 @@ func (pk BIP340PubKey) MustToBTCPK() *btcec.PublicKey { return btcPK } -func (pk *BIP340PubKey) ToHexStr() string { +func (pk *BIP340PubKey) MarshalHex() string { return hex.EncodeToString(pk.MustMarshal()) } +func (pk *BIP340PubKey) UnmarshalHex(header string) error { + // Decode the hash string from hex + decoded, err := hex.DecodeString(header) + if err != nil { + return err + } + + return pk.Unmarshal(decoded) +} + func (pk BIP340PubKey) Size() int { return len(pk.MustMarshal()) } @@ -90,6 +97,21 @@ func (pk *BIP340PubKey) Unmarshal(data []byte) error { return nil } +func (pk BIP340PubKey) MarshalJSON() ([]byte, error) { + return json.Marshal(pk.MarshalHex()) +} + +func (pk *BIP340PubKey) UnmarshalJSON(bz []byte) error { + var pkHexString string + err := json.Unmarshal(bz, &pkHexString) + + if err != nil { + return err + } + + return pk.UnmarshalHex(pkHexString) +} + func (pk *BIP340PubKey) Equals(pk2 *BIP340PubKey) bool { return bytes.Equal(*pk, *pk2) } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 6103db802..ddaf1a378 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -56,7 +56,7 @@ func FuzzBTCValidators(f *testing.F) { require.NoError(t, err) keeper.SetBTCValidator(ctx, btcVal) - btcValsMap[btcVal.BtcPk.ToHexStr()] = btcVal + btcValsMap[btcVal.BtcPk.MarshalHex()] = btcVal } numOfBTCValsInStore := len(btcValsMap) @@ -89,10 +89,10 @@ func FuzzBTCValidators(f *testing.F) { for _, val := range resp.BtcValidators { // Check if the pk exists in the map - if _, ok := btcValsMap[val.BtcPk.ToHexStr()]; !ok { + if _, ok := btcValsMap[val.BtcPk.MarshalHex()]; !ok { t.Fatalf("rpc returned a val that was not created") } - btcValsFound[val.BtcPk.ToHexStr()] = true + btcValsFound[val.BtcPk.MarshalHex()] = true } // Construct the next page request @@ -125,7 +125,7 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) req := &types.QueryBTCValidatorPowerAtHeightRequest{ - ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), Height: randomHeight, } resp, err := keeper.BTCValidatorPowerAtHeight(ctx, req) @@ -159,7 +159,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { btcValsWithVotingPowerMap := make(map[string]*types.BTCValidator) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { valBTCPK := btcVals[i].BtcPk - btcValsWithVotingPowerMap[valBTCPK.ToHexStr()] = btcVals[i] + btcValsWithVotingPowerMap[valBTCPK.MarshalHex()] = btcVals[i] var totalVotingPower uint64 for j := uint64(0); j < numBTCDels; j++ { @@ -201,10 +201,10 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { for _, val := range resp.BtcValidators { // Check if the pk exists in the map - if _, ok := btcValsWithVotingPowerMap[val.BtcPk.ToHexStr()]; !ok { + if _, ok := btcValsWithVotingPowerMap[val.BtcPk.MarshalHex()]; !ok { t.Fatalf("rpc returned a val that was not created") } - btcValsFound[val.BtcPk.ToHexStr()] = true + btcValsFound[val.BtcPk.MarshalHex()] = true } // Construct the next page request @@ -248,9 +248,9 @@ func FuzzBTCValidatorDelegations(f *testing.F) { if datagen.RandomInt(r, 2) == 1 { // remove jury sig in random BTC delegations to make them inactive btcDel.JurySig = nil - pendingBtcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel + pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } else { - activeBtcDelsMap[btcDel.BtcPk.ToHexStr()] = btcDel + activeBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } keeper.SetBTCDelegation(ctx, btcDel) } @@ -272,7 +272,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { pagination := constructRequestWithLimit(r, limit) // Generate the initial query req := types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), DelStatus: types.BTCDelegationStatus_ACTIVE, Pagination: pagination, } @@ -287,14 +287,14 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for _, btcDel := range resp.BtcDelegations { require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) // Check if the pk exists in the map - _, ok := activeBtcDelsMap[btcDel.BtcPk.ToHexStr()] + _, ok := activeBtcDelsMap[btcDel.BtcPk.MarshalHex()] require.True(t, ok) - btcDelsFound[btcDel.BtcPk.ToHexStr()] = true + btcDelsFound[btcDel.BtcPk.MarshalHex()] = true } // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) req = types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), DelStatus: types.BTCDelegationStatus_ACTIVE, Pagination: pagination, } @@ -307,7 +307,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { limit := datagen.RandomInt(r, len(pendingBtcDelsMap)) + 1 pagination := constructRequestWithLimit(r, limit) req := types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig Pagination: pagination, } @@ -320,14 +320,14 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for _, btcDel := range resp.BtcDelegations { require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) // Check if the pk exists in the map - _, ok := pendingBtcDelsMap[btcDel.BtcPk.ToHexStr()] + _, ok := pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] require.True(t, ok) - pendingBtcDelsFound[btcDel.BtcPk.ToHexStr()] = true + pendingBtcDelsFound[btcDel.BtcPk.MarshalHex()] = true } // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) req = types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.ToHexStr(), + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig Pagination: pagination, } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 6cbbc1e9e..a8ec46a47 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -83,7 +83,7 @@ func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]u // failing to unmarshal validator BTC PK in KVStore is a programming error panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } - valSet[valBTCPK.ToHexStr()] = sdk.BigEndianToUint64(iter.Value()) + valSet[valBTCPK.MarshalHex()] = sdk.BigEndianToUint64(iter.Value()) } return valSet diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 13011787b..2944cc8c0 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -84,7 +84,7 @@ func FuzzVotingPowerTable(f *testing.F) { require.NotNil(t, powerTable) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) - require.Equal(t, powerTable[btcVals[i].BtcPk.ToHexStr()], power) + require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power) } // the activation height should be the current Babylon height as well activatedHeight, err := keeper.GetBTCStakingActivatedHeight(ctx) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 56850cf6f..83f15901f 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -39,7 +39,7 @@ func FuzzListPublicRandomness(f *testing.F) { // instead only ensure it takes effect limit := datagen.RandomInt(r, int(numPubRand)-1) + 1 req := &types.QueryListPublicRandomnessRequest{ - ValBtcPkHex: valBTCPK.ToHexStr(), + ValBtcPkHex: valBTCPK.MarshalHex(), Pagination: &query.PageRequest{ Limit: limit, }, @@ -147,7 +147,7 @@ func FuzzVotesAtHeight(f *testing.F) { require.NoError(t, err) keeper.SetSig(ctx, babylonHeight, votedValPK, votedSig) - votedValMap[votedValPK.ToHexStr()] = true + votedValMap[votedValPK.MarshalHex()] = true } resp, err := keeper.VotesAtHeight(ctx, &types.QueryVotesAtHeightRequest{ @@ -158,10 +158,10 @@ func FuzzVotesAtHeight(f *testing.F) { // Check if all voted validators are returned valFoundMap := make(map[string]bool) for _, pk := range resp.BtcPks { - if _, ok := votedValMap[pk.ToHexStr()]; !ok { + if _, ok := votedValMap[pk.MarshalHex()]; !ok { t.Fatalf("rpc returned a val that was not created") } - valFoundMap[pk.ToHexStr()] = true + valFoundMap[pk.MarshalHex()] = true } if len(valFoundMap) != len(votedValMap) { t.Errorf("Some vals were missed. Got %d while %d were expected", len(valFoundMap), len(votedValMap)) diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 75ecd614e..cab2015e4 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -130,7 +130,7 @@ func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakin } fKeeper.SetSig(ctx, height, votedValPK, votedSig) // add val - valSet[votedValPK.ToHexStr()] = 1 + valSet[votedValPK.MarshalHex()] = 1 } // the rest val that does not vote valSet[hex.EncodeToString(datagen.GenRandomByteArray(r, 32))] = 1 @@ -152,7 +152,7 @@ func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStak fKeeper.SetSig(ctx, height, votedValPK, votedSig) // 4 BTC vals valSet := map[string]uint64{ - votedValPK.ToHexStr(): 1, + votedValPK.MarshalHex(): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index 52b145bae..f5447cbdd 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -58,7 +58,7 @@ func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.Schnor // failing to unmarshal EOTS sig in KVStore is a programming error panic(fmt.Errorf("failed to unmarshal EOTS signature: %w", err)) } - sigs[valBTCPK.ToHexStr()] = sig + sigs[valBTCPK.MarshalHex()] = sig } return sigs } From 744ada452cc622e212af09a0dd815c5c94418f03 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 24 Jul 2023 16:59:49 +0800 Subject: [PATCH 040/202] btcstaking: RPC query for all pending BTC delegations (#34) --- proto/babylon/btcstaking/v1/query.proto | 27 +- x/btcstaking/client/cli/query.go | 25 +- x/btcstaking/keeper/grpc_query.go | 44 +++ x/btcstaking/keeper/grpc_query_test.go | 58 +++ x/btcstaking/types/query.pb.go | 489 ++++++++++++++++++++---- x/btcstaking/types/query.pb.gw.go | 65 ++++ 6 files changed, 631 insertions(+), 77 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index a083e840f..4f6cdadf4 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -16,17 +16,22 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/params"; } - // BTCValidators queries all btc validators + // BTCValidators queries all BTC validators rpc BTCValidators(QueryBTCValidatorsRequest) returns (QueryBTCValidatorsResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators"; } - // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + // PendingBTCDelegations queries all pending BTC delegations + rpc PendingBTCDelegations(QueryPendingBTCDelegationsRequest) returns (QueryPendingBTCDelegationsResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/pending_btc_delegations"; + } + + // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. rpc ActiveBTCValidatorsAtHeight(QueryActiveBTCValidatorsAtHeightRequest) returns (QueryActiveBTCValidatorsAtHeightResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{height}"; } - // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height + // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height rpc BTCValidatorPowerAtHeight(QueryBTCValidatorPowerAtHeightRequest) returns (QueryBTCValidatorPowerAtHeightResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; } @@ -37,7 +42,7 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/activated_height"; } - // BTCValidatorDelegations queries all btc delegations of the given btc validator + // BTCValidatorDelegations queries all BTC delegations of the given BTC validator rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; } @@ -69,6 +74,18 @@ message QueryBTCValidatorsResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } + +// QueryPendingBTCDelegationsRequest is the request type for the +// Query/PendingBTCDelegations RPC method. +message QueryPendingBTCDelegationsRequest {} + +// QueryPendingBTCDelegationsResponse is the response type for the +// Query/PendingBTCDelegations RPC method. +message QueryPendingBTCDelegationsResponse { + // btc_delegations contains all the queried btc delegations. + repeated BTCDelegation btc_delegations = 1; +} + // QueryBTCValidatorPowerAtHeightRequest is the request type for the // Query/BTCValidatorPowerAtHeight RPC method. message QueryBTCValidatorPowerAtHeightRequest { @@ -135,7 +152,7 @@ message QueryBTCValidatorDelegationsRequest { // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsResponse { - // btc_validators contains all the queried btc delegations. + // btc_delegations contains all the queried btc delegations. repeated BTCDelegation btc_delegations = 1; // pagination defines the pagination in the response. diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 0d238d54f..7e0e89eeb 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -26,6 +26,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdBTCValidators()) + cmd.AddCommand(CmdPendingBTCDelegations()) cmd.AddCommand(CmdBTCValidatorsAtHeight()) cmd.AddCommand(CmdBTCValidatorPowerAtHeight()) cmd.AddCommand(CmdActivatedHeight()) @@ -37,7 +38,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { func CmdBTCValidators() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validators", - Short: "retrieve all btc validators", + Short: "retrieve all BTC validators", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -65,6 +66,28 @@ func CmdBTCValidators() *cobra.Command { return cmd } +func CmdPendingBTCDelegations() *cobra.Command { + cmd := &cobra.Command{ + Use: "pending-btc-delegations", + Short: "retrieve all pending BTC delegations", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.PendingBTCDelegations(cmd.Context(), &types.QueryPendingBTCDelegationsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + func CmdBTCValidatorPowerAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validator-power-at-height [val_btc_pk_hex] [height]", diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index f19647fb1..df6445616 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -38,6 +38,50 @@ func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidators return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil } +// PendingBTCDelegations returns all pending BTC delegations +// TODO: find a good way to support pagination of this query +func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendingBTCDelegationsRequest) (*types.QueryPendingBTCDelegationsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + btcDels := []*types.BTCDelegation{} + + // get current BTC height + btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) + if err != nil { + return nil, err + } + // get value of w + wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + + // iterate over each BTC validator + valStore := k.btcValidatorStore(sdkCtx) + valIter := valStore.Iterator(nil, nil) + defer valIter.Close() + + for ; valIter.Valid(); valIter.Next() { + valBTCPKBytes := valIter.Key() + delStore := k.btcDelegationStore(sdkCtx, valBTCPKBytes) + delIter := delStore.Iterator(nil, nil) + + // iterate over each BTC delegation under this BTC validator + for ; delIter.Valid(); delIter.Next() { + btcDelBytes := delIter.Value() + var btcDel types.BTCDelegation + k.cdc.MustUnmarshal(btcDelBytes, &btcDel) + if btcDel.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING { + btcDels = append(btcDels, &btcDel) + } + } + + delIter.Close() + } + + return &types.QueryPendingBTCDelegationsResponse{BtcDelegations: btcDels}, nil +} + // BTCValidatorPowerAtHeight returns the voting power of the specified validator // at the provided Babylon height func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryBTCValidatorPowerAtHeightRequest) (*types.QueryBTCValidatorPowerAtHeightResponse, error) { diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index ddaf1a378..01b3f445b 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -106,6 +106,64 @@ func FuzzBTCValidators(f *testing.F) { }) } +func FuzzBTCDelegations(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Setup keeper and context + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // Generate a random number of BTC validators + numBTCVals := datagen.RandomInt(r, 5) + 1 + btcVals := []*types.BTCValidator{} + for i := uint64(0); i < numBTCVals; i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + btcVals = append(btcVals, btcVal) + } + + // Generate a random number of BTC delegations under each validator + startHeight := datagen.RandomInt(r, 100) + 1 + endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 + numBTCDels := datagen.RandomInt(r, 10) + 1 + pendingBtcDelsMap := make(map[string]*types.BTCDelegation) + for _, btcVal := range btcVals { + for j := uint64(0); j < numBTCDels; j++ { + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, startHeight, endHeight, 1) + require.NoError(t, err) + if datagen.RandomInt(r, 2) == 1 { + // remove jury sig in random BTC delegations to make them inactive + btcDel.JurySig = nil + pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel + } + keeper.SetBTCDelegation(ctx, btcDel) + } + } + + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) + keeper.IndexBTCHeight(ctx) + + // assert all pending BTC delegations + resp, err := keeper.PendingBTCDelegations(ctx, &types.QueryPendingBTCDelegationsRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, len(pendingBtcDelsMap), len(resp.BtcDelegations)) + for _, btcDel := range resp.BtcDelegations { + _, ok := pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] + require.True(t, ok) + } + }) +} + func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index a74f00a57..23df77631 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -216,6 +216,91 @@ func (m *QueryBTCValidatorsResponse) GetPagination() *query.PageResponse { return nil } +// QueryPendingBTCDelegationsRequest is the request type for the +// Query/PendingBTCDelegations RPC method. +type QueryPendingBTCDelegationsRequest struct { +} + +func (m *QueryPendingBTCDelegationsRequest) Reset() { *m = QueryPendingBTCDelegationsRequest{} } +func (m *QueryPendingBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPendingBTCDelegationsRequest) ProtoMessage() {} +func (*QueryPendingBTCDelegationsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{4} +} +func (m *QueryPendingBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingBTCDelegationsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingBTCDelegationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingBTCDelegationsRequest.Merge(m, src) +} +func (m *QueryPendingBTCDelegationsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingBTCDelegationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingBTCDelegationsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingBTCDelegationsRequest proto.InternalMessageInfo + +// QueryPendingBTCDelegationsResponse is the response type for the +// Query/PendingBTCDelegations RPC method. +type QueryPendingBTCDelegationsResponse struct { + // btc_delegations contains all the queried btc delegations. + BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` +} + +func (m *QueryPendingBTCDelegationsResponse) Reset() { *m = QueryPendingBTCDelegationsResponse{} } +func (m *QueryPendingBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPendingBTCDelegationsResponse) ProtoMessage() {} +func (*QueryPendingBTCDelegationsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{5} +} +func (m *QueryPendingBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingBTCDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingBTCDelegationsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingBTCDelegationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingBTCDelegationsResponse.Merge(m, src) +} +func (m *QueryPendingBTCDelegationsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingBTCDelegationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingBTCDelegationsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingBTCDelegationsResponse proto.InternalMessageInfo + +func (m *QueryPendingBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegation { + if m != nil { + return m.BtcDelegations + } + return nil +} + // QueryBTCValidatorPowerAtHeightRequest is the request type for the // Query/BTCValidatorPowerAtHeight RPC method. type QueryBTCValidatorPowerAtHeightRequest struct { @@ -231,7 +316,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{4} + return fileDescriptor_74d49d26f7429697, []int{6} } func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -287,7 +372,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{5} + return fileDescriptor_74d49d26f7429697, []int{7} } func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -338,7 +423,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{6} + return fileDescriptor_74d49d26f7429697, []int{8} } func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -396,7 +481,7 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{7} + return fileDescriptor_74d49d26f7429697, []int{9} } func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -447,7 +532,7 @@ func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeigh func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightRequest) ProtoMessage() {} func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{8} + return fileDescriptor_74d49d26f7429697, []int{10} } func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -485,7 +570,7 @@ func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeig func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightResponse) ProtoMessage() {} func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{9} + return fileDescriptor_74d49d26f7429697, []int{11} } func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -538,7 +623,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCVal func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{10} + return fileDescriptor_74d49d26f7429697, []int{12} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -591,7 +676,7 @@ func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsResponse struct { - // btc_validators contains all the queried btc delegations. + // btc_delegations contains all the queried btc delegations. BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` @@ -601,7 +686,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{11} + return fileDescriptor_74d49d26f7429697, []int{13} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -649,6 +734,8 @@ func init() { proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") proto.RegisterType((*QueryBTCValidatorsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsRequest") proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") + proto.RegisterType((*QueryPendingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsRequest") + proto.RegisterType((*QueryPendingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") proto.RegisterType((*QueryActiveBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightRequest") @@ -662,60 +749,64 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 840 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4d, 0x4f, 0x1b, 0x47, - 0x18, 0xf6, 0x00, 0xb5, 0xc4, 0xb8, 0x80, 0x34, 0xa5, 0x2d, 0x2c, 0x60, 0x60, 0xf9, 0x30, 0x50, - 0x75, 0x17, 0x1b, 0x89, 0x43, 0xe9, 0x87, 0x70, 0x69, 0xa1, 0xb4, 0x48, 0xee, 0xb6, 0x6a, 0xa5, - 0x5c, 0xac, 0xd9, 0xf5, 0x68, 0xbd, 0x62, 0xd9, 0x59, 0xbc, 0xe3, 0x0d, 0x28, 0xe2, 0x92, 0x43, - 0x94, 0xdc, 0x22, 0xe5, 0x27, 0xe4, 0x98, 0x53, 0xfe, 0x40, 0xae, 0xe1, 0x88, 0x14, 0x29, 0xca, - 0x25, 0x28, 0x82, 0x48, 0xf9, 0x1b, 0x91, 0x67, 0xc7, 0xec, 0xfa, 0x63, 0x6d, 0x63, 0x25, 0x37, - 0xb3, 0xf3, 0x3e, 0xf3, 0x7c, 0xcc, 0x3b, 0xef, 0x00, 0xe7, 0x75, 0xac, 0x9f, 0xda, 0xd4, 0x51, - 0x75, 0x66, 0x78, 0x0c, 0x1f, 0x5a, 0x8e, 0xa9, 0xfa, 0x59, 0xf5, 0xb8, 0x4a, 0x2a, 0xa7, 0x8a, - 0x5b, 0xa1, 0x8c, 0xa2, 0xaf, 0x45, 0x89, 0x12, 0x96, 0x28, 0x7e, 0x56, 0x1a, 0x37, 0xa9, 0x49, - 0x79, 0x85, 0x5a, 0xfb, 0x15, 0x14, 0x4b, 0xd3, 0x26, 0xa5, 0xa6, 0x4d, 0x54, 0xec, 0x5a, 0x2a, - 0x76, 0x1c, 0xca, 0x30, 0xb3, 0xa8, 0xe3, 0x89, 0xd5, 0x35, 0x83, 0x7a, 0x47, 0xd4, 0x53, 0x75, - 0xec, 0x91, 0x80, 0x43, 0xf5, 0xb3, 0x3a, 0x61, 0x38, 0xab, 0xba, 0xd8, 0xb4, 0x1c, 0x5e, 0x2c, - 0x6a, 0xe5, 0xf6, 0xca, 0x5c, 0x5c, 0xc1, 0x47, 0xf5, 0xfd, 0x96, 0xdb, 0xd7, 0x44, 0x84, 0xf2, - 0x3a, 0x79, 0x1c, 0xa2, 0xbf, 0x6b, 0x6c, 0x05, 0x0e, 0xd6, 0xc8, 0x71, 0x95, 0x78, 0x4c, 0xd6, - 0xe0, 0x57, 0x0d, 0x5f, 0x3d, 0x97, 0x3a, 0x1e, 0x41, 0x5b, 0x30, 0x19, 0x90, 0x4c, 0x80, 0x39, - 0xb0, 0x92, 0xca, 0xcd, 0x28, 0x6d, 0x03, 0x50, 0x02, 0x58, 0x7e, 0xe8, 0xfc, 0x72, 0x36, 0xa1, - 0x09, 0x88, 0x6c, 0xc0, 0x49, 0xbe, 0x67, 0xfe, 0xdf, 0x5f, 0xff, 0xc3, 0xb6, 0x55, 0xc2, 0x8c, - 0x56, 0xea, 0x84, 0xe8, 0x77, 0x08, 0x43, 0x9b, 0x62, 0xf7, 0x65, 0x25, 0xc8, 0x44, 0xa9, 0x65, - 0xa2, 0x04, 0xb9, 0x8b, 0x4c, 0x94, 0x02, 0x36, 0x89, 0xc0, 0x6a, 0x11, 0xa4, 0xfc, 0x1c, 0x40, - 0xa9, 0x1d, 0x8b, 0x30, 0xb0, 0x0f, 0x47, 0x75, 0x66, 0x14, 0xfd, 0x9b, 0x95, 0x09, 0x30, 0x37, - 0xb8, 0x92, 0xca, 0x2d, 0xc4, 0x18, 0x89, 0xee, 0xa2, 0x8d, 0xe8, 0xcc, 0x08, 0xf7, 0x44, 0xbb, - 0x0d, 0x92, 0x07, 0xb8, 0xe4, 0x4c, 0x57, 0xc9, 0x81, 0x90, 0x06, 0xcd, 0x25, 0xb8, 0xd4, 0x22, - 0xb9, 0x40, 0xef, 0x92, 0xca, 0x36, 0xdb, 0x23, 0x96, 0x59, 0x66, 0xf5, 0x90, 0x16, 0xe0, 0xa8, - 0x8f, 0xed, 0x62, 0xcd, 0x81, 0x7b, 0x58, 0x2c, 0x93, 0x13, 0x1e, 0xd4, 0xb0, 0x96, 0xf2, 0xb1, - 0x9d, 0x67, 0x46, 0xe1, 0x70, 0x8f, 0x9c, 0xa0, 0x6f, 0x60, 0xb2, 0xcc, 0x51, 0x5c, 0xd2, 0x90, - 0x26, 0xfe, 0x92, 0xff, 0x84, 0xcb, 0xdd, 0x58, 0x44, 0x48, 0xf3, 0xf0, 0x4b, 0x9f, 0x32, 0xcb, - 0x31, 0x8b, 0x6e, 0x6d, 0x9d, 0x93, 0x0c, 0x69, 0xa9, 0xe0, 0x1b, 0x87, 0xc8, 0x8f, 0x00, 0xcc, - 0xf0, 0xdd, 0xb6, 0x0d, 0x66, 0xf9, 0xa4, 0x21, 0xec, 0x66, 0xd5, 0xa1, 0x20, 0x10, 0x15, 0xd4, - 0x74, 0xe4, 0x03, 0x7d, 0x1f, 0xf9, 0x4b, 0x00, 0x57, 0xba, 0x6b, 0x11, 0xde, 0xb4, 0x98, 0x06, - 0xf8, 0xae, 0x87, 0x06, 0xf8, 0xdf, 0x62, 0xe5, 0x03, 0xc2, 0xf0, 0x67, 0x6b, 0x84, 0x19, 0x38, - 0x15, 0x1a, 0xc1, 0x8c, 0x94, 0x1a, 0x82, 0x94, 0x37, 0xe1, 0x74, 0xfb, 0x65, 0xe1, 0x2d, 0x26, - 0x68, 0xf9, 0x2d, 0x80, 0x0b, 0x2d, 0x47, 0xbf, 0x43, 0x6c, 0x62, 0x06, 0x13, 0xe8, 0x56, 0xed, - 0xf5, 0x07, 0x84, 0x25, 0x62, 0x17, 0x3d, 0x86, 0x59, 0xd5, 0xe3, 0x66, 0x47, 0x73, 0x6b, 0xf1, - 0xe1, 0x85, 0x34, 0xff, 0x70, 0x84, 0x36, 0x5c, 0x22, 0x76, 0xf0, 0xb3, 0xa9, 0x01, 0x06, 0xfb, - 0x6e, 0x80, 0x17, 0x00, 0x2e, 0x76, 0xf6, 0x27, 0x02, 0x3a, 0x80, 0x63, 0x35, 0x73, 0xa5, 0x70, - 0x49, 0x9c, 0xfe, 0x62, 0x2f, 0x06, 0xb4, 0x5a, 0xe7, 0x44, 0xb6, 0xfd, 0x64, 0xe7, 0x9e, 0x7b, - 0x38, 0x0c, 0xbf, 0xe0, 0x06, 0xd0, 0x03, 0x00, 0x93, 0xc1, 0xf0, 0x44, 0xab, 0x31, 0x9a, 0x5a, - 0xa7, 0xb5, 0xb4, 0xd6, 0x4b, 0x69, 0xc0, 0x2b, 0x2f, 0xdd, 0x7f, 0xf5, 0xfe, 0xc9, 0xc0, 0x2c, - 0x9a, 0x51, 0x3b, 0x3d, 0x22, 0xe8, 0x29, 0x80, 0x23, 0x0d, 0x37, 0x09, 0xad, 0x77, 0x22, 0x69, - 0x37, 0xd3, 0xa5, 0xec, 0x2d, 0x10, 0x42, 0xdd, 0xf7, 0x5c, 0x5d, 0x06, 0x2d, 0xa9, 0xb1, 0xcf, - 0x57, 0xe4, 0xee, 0xa2, 0xd7, 0x00, 0x4e, 0x75, 0xb8, 0xf5, 0xe8, 0xe7, 0x4e, 0x0a, 0xba, 0x8f, - 0x2e, 0xe9, 0x97, 0xbe, 0xf1, 0xc2, 0xcf, 0x26, 0xf7, 0xb3, 0x8e, 0x94, 0x9e, 0xfc, 0xa8, 0xf7, - 0x82, 0x1b, 0x7b, 0x86, 0x3e, 0x00, 0x38, 0x19, 0x3b, 0xa8, 0xd1, 0x8f, 0xbd, 0x06, 0xdb, 0xee, - 0x15, 0x91, 0x7e, 0xea, 0x13, 0x2d, 0x2c, 0x1d, 0x70, 0x4b, 0xbb, 0xe8, 0xb7, 0x1e, 0x2d, 0x35, - 0x8e, 0x94, 0x33, 0x95, 0xbf, 0x29, 0xa1, 0xd3, 0x67, 0x00, 0x8e, 0x35, 0x0d, 0x34, 0x94, 0xeb, - 0x1a, 0x7b, 0xcb, 0x70, 0x94, 0x36, 0x6e, 0x85, 0x11, 0x5e, 0x54, 0xee, 0x65, 0x15, 0x65, 0x62, - 0xbc, 0xe0, 0x3a, 0xae, 0x28, 0xde, 0xac, 0x4b, 0x00, 0xbf, 0x8d, 0x99, 0x32, 0xe8, 0x87, 0x5e, - 0x73, 0x6d, 0x1d, 0xbd, 0xd2, 0x56, 0x5f, 0x58, 0xe1, 0x62, 0x9f, 0xbb, 0xd8, 0x41, 0xf9, 0x3e, - 0x4f, 0x24, 0x32, 0x0f, 0xf3, 0x7f, 0x9d, 0x5f, 0xa5, 0xc1, 0xc5, 0x55, 0x1a, 0xbc, 0xbb, 0x4a, - 0x83, 0xc7, 0xd7, 0xe9, 0xc4, 0xc5, 0x75, 0x3a, 0xf1, 0xe6, 0x3a, 0x9d, 0xb8, 0x93, 0x33, 0x2d, - 0x56, 0xae, 0xea, 0x8a, 0x41, 0x8f, 0xea, 0x3c, 0x46, 0x19, 0x5b, 0xce, 0x0d, 0xe9, 0x49, 0x94, - 0x96, 0x9d, 0xba, 0xc4, 0xd3, 0x93, 0xfc, 0x7f, 0xcc, 0x8d, 0x8f, 0x01, 0x00, 0x00, 0xff, 0xff, - 0x64, 0x9b, 0x7f, 0x68, 0x4b, 0x0b, 0x00, 0x00, + // 897 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdf, 0x6f, 0xdb, 0x54, + 0x14, 0xce, 0xed, 0x4a, 0xa4, 0x9d, 0xb0, 0x4e, 0xba, 0x6c, 0xb0, 0x79, 0x6b, 0xb6, 0x3a, 0x6b, + 0xd3, 0x15, 0x61, 0x2f, 0x99, 0x34, 0x01, 0xe3, 0x87, 0x96, 0x0d, 0x36, 0x06, 0x95, 0x82, 0x99, + 0x40, 0xe2, 0x25, 0xba, 0x76, 0xae, 0x1c, 0xab, 0xae, 0xaf, 0x17, 0xdf, 0x98, 0x56, 0x68, 0x2f, + 0x3c, 0x20, 0xf1, 0x86, 0xc4, 0x9f, 0xc0, 0x23, 0x4f, 0xfc, 0x03, 0x3c, 0x21, 0xb1, 0x37, 0x26, + 0x21, 0x21, 0x5e, 0x98, 0x50, 0x8b, 0xc4, 0xbf, 0x81, 0x72, 0x7d, 0x53, 0xdb, 0x89, 0xed, 0xb8, + 0xd1, 0xfa, 0x96, 0xfa, 0x9e, 0xef, 0x9c, 0xef, 0xfb, 0xee, 0xb9, 0xe7, 0x14, 0xd6, 0x4c, 0x62, + 0xee, 0xbb, 0xcc, 0xd3, 0x4d, 0x6e, 0x05, 0x9c, 0xec, 0x38, 0x9e, 0xad, 0x87, 0x2d, 0xfd, 0xf1, + 0x88, 0x0e, 0xf7, 0x35, 0x7f, 0xc8, 0x38, 0xc3, 0xe7, 0x65, 0x88, 0x16, 0x87, 0x68, 0x61, 0x4b, + 0x39, 0x67, 0x33, 0x9b, 0x89, 0x08, 0x7d, 0xfc, 0x2b, 0x0a, 0x56, 0x2e, 0xdb, 0x8c, 0xd9, 0x2e, + 0xd5, 0x89, 0xef, 0xe8, 0xc4, 0xf3, 0x18, 0x27, 0xdc, 0x61, 0x5e, 0x20, 0x4f, 0xb7, 0x2c, 0x16, + 0xec, 0xb2, 0x40, 0x37, 0x49, 0x40, 0xa3, 0x1a, 0x7a, 0xd8, 0x32, 0x29, 0x27, 0x2d, 0xdd, 0x27, + 0xb6, 0xe3, 0x89, 0x60, 0x19, 0xab, 0x66, 0x33, 0xf3, 0xc9, 0x90, 0xec, 0x4e, 0xf2, 0x6d, 0x64, + 0xc7, 0x24, 0x88, 0x8a, 0x38, 0xf5, 0x1c, 0xe0, 0x4f, 0xc7, 0xd5, 0xba, 0x02, 0x6c, 0xd0, 0xc7, + 0x23, 0x1a, 0x70, 0xd5, 0x80, 0x57, 0x52, 0x5f, 0x03, 0x9f, 0x79, 0x01, 0xc5, 0xb7, 0xa1, 0x1a, + 0x15, 0xb9, 0x80, 0xae, 0xa2, 0xcd, 0x5a, 0x7b, 0x55, 0xcb, 0x34, 0x40, 0x8b, 0x60, 0x9d, 0xe5, + 0xa7, 0xcf, 0xaf, 0x54, 0x0c, 0x09, 0x51, 0x2d, 0xb8, 0x28, 0x72, 0x76, 0x1e, 0xdd, 0xfd, 0x9c, + 0xb8, 0x4e, 0x9f, 0x70, 0x36, 0x9c, 0x14, 0xc4, 0x1f, 0x02, 0xc4, 0x32, 0x65, 0xf6, 0x0d, 0x2d, + 0xf2, 0x44, 0x1b, 0x7b, 0xa2, 0x45, 0xbe, 0x4b, 0x4f, 0xb4, 0x2e, 0xb1, 0xa9, 0xc4, 0x1a, 0x09, + 0xa4, 0xfa, 0x33, 0x02, 0x25, 0xab, 0x8a, 0x14, 0xf0, 0x10, 0x56, 0x4c, 0x6e, 0xf5, 0xc2, 0xa3, + 0x93, 0x0b, 0xe8, 0xea, 0xa9, 0xcd, 0x5a, 0xbb, 0x91, 0x23, 0x24, 0x99, 0xc5, 0x38, 0x63, 0x72, + 0x2b, 0xce, 0x89, 0xef, 0xa7, 0x28, 0x2f, 0x09, 0xca, 0xcd, 0xb9, 0x94, 0x23, 0x22, 0x29, 0xce, + 0x0d, 0x58, 0x8b, 0xcc, 0xa6, 0x5e, 0xdf, 0xf1, 0xec, 0xce, 0xa3, 0xbb, 0xf7, 0xa8, 0x4b, 0xed, + 0xa8, 0x3d, 0x26, 0x37, 0x12, 0x80, 0x5a, 0x14, 0x24, 0xf5, 0x6d, 0xc3, 0xd9, 0xb1, 0xbe, 0x7e, + 0x7c, 0x24, 0x05, 0x5e, 0xcb, 0x17, 0x18, 0xe7, 0x31, 0xc6, 0xe6, 0x24, 0xd2, 0xaa, 0x7d, 0x58, + 0x9f, 0x31, 0xb3, 0xcb, 0xbe, 0xa2, 0xc3, 0x3b, 0xfc, 0x01, 0x75, 0xec, 0x01, 0x9f, 0x5c, 0x5f, + 0x03, 0x56, 0x42, 0xe2, 0xf6, 0xc6, 0xb5, 0xfd, 0x9d, 0xde, 0x80, 0xee, 0x89, 0x2b, 0x3c, 0x6d, + 0xd4, 0x42, 0xe2, 0x76, 0xb8, 0xd5, 0xdd, 0x79, 0x40, 0xf7, 0xf0, 0xab, 0x50, 0x1d, 0x08, 0x94, + 0x30, 0x6b, 0xd9, 0x90, 0x7f, 0xa9, 0x1f, 0xc3, 0xc6, 0xbc, 0x2a, 0x52, 0xde, 0x1a, 0xbc, 0x1c, + 0x32, 0xee, 0x78, 0x76, 0xcf, 0x1f, 0x9f, 0x8b, 0x22, 0xcb, 0x46, 0x2d, 0xfa, 0x26, 0x20, 0xea, + 0x77, 0x08, 0x9a, 0x22, 0xdb, 0x1d, 0x8b, 0x3b, 0x21, 0x4d, 0xb5, 0xc1, 0x34, 0xeb, 0x98, 0x10, + 0x4a, 0x12, 0x9a, 0x6a, 0xc6, 0xa5, 0x85, 0x9b, 0xf1, 0x37, 0x04, 0x9b, 0xf3, 0xb9, 0x48, 0x6d, + 0x46, 0x4e, 0x6b, 0xbe, 0x5e, 0xa2, 0x35, 0xbf, 0x70, 0xf8, 0x60, 0x9b, 0x72, 0x72, 0x62, 0x2d, + 0xba, 0x0a, 0x97, 0x62, 0x21, 0x84, 0xd3, 0x7e, 0xca, 0x48, 0xf5, 0x16, 0x5c, 0xce, 0x3e, 0x96, + 0xda, 0x72, 0x8c, 0x56, 0xff, 0x46, 0xd0, 0x98, 0xb9, 0xfa, 0xd9, 0xe6, 0x2f, 0xd7, 0x5e, 0x1f, + 0x01, 0xf4, 0xa9, 0xdb, 0x0b, 0x38, 0xe1, 0xa3, 0x40, 0x88, 0x5d, 0x69, 0x6f, 0x95, 0x69, 0xfb, + 0xcf, 0x04, 0xc2, 0x38, 0xdd, 0xa7, 0x6e, 0xf4, 0x73, 0xaa, 0x01, 0x4e, 0x2d, 0xdc, 0x00, 0xbf, + 0x20, 0xb8, 0x56, 0xac, 0xef, 0x44, 0xde, 0xed, 0x0b, 0xbb, 0xf7, 0xf6, 0xef, 0x00, 0x2f, 0x09, + 0x01, 0xf8, 0x5b, 0x04, 0xd5, 0x68, 0xac, 0xe3, 0xeb, 0x39, 0x9c, 0x66, 0xf7, 0x88, 0xb2, 0x55, + 0x26, 0x34, 0xaa, 0xab, 0xae, 0x7f, 0xf3, 0xc7, 0xbf, 0x3f, 0x2c, 0x5d, 0xc1, 0xab, 0x7a, 0xd1, + 0x7a, 0xc3, 0x3f, 0x22, 0x38, 0x93, 0x7a, 0x49, 0xf8, 0x46, 0x51, 0x91, 0xac, 0x6d, 0xa3, 0xb4, + 0x8e, 0x81, 0x90, 0xec, 0xde, 0x10, 0xec, 0x9a, 0x78, 0x5d, 0xcf, 0x5d, 0xac, 0x89, 0xb7, 0x8b, + 0x7f, 0x45, 0x70, 0x3e, 0x73, 0x54, 0xe3, 0x37, 0x0b, 0x2d, 0x29, 0x58, 0x01, 0xca, 0x5b, 0x0b, + 0x20, 0x25, 0xfb, 0x5b, 0x82, 0xfd, 0x0d, 0xac, 0xe5, 0x79, 0x1b, 0xa1, 0x7b, 0x53, 0x4d, 0x88, + 0xff, 0x44, 0x70, 0xa9, 0x60, 0x78, 0xe1, 0xf7, 0x8a, 0x28, 0xcd, 0x9f, 0xc0, 0xca, 0xfb, 0x0b, + 0xe3, 0x4b, 0x0a, 0x4b, 0x5f, 0x8b, 0xfe, 0x75, 0x34, 0x78, 0x9e, 0xe0, 0xff, 0x10, 0x5c, 0xcc, + 0xdd, 0x37, 0xf8, 0x9d, 0xb2, 0xfd, 0x91, 0xb5, 0x0c, 0x95, 0x77, 0x17, 0x44, 0x4b, 0x49, 0xdb, + 0x42, 0xd2, 0x7d, 0xfc, 0x41, 0x49, 0x49, 0xe9, 0xc9, 0xf8, 0x44, 0x17, 0xab, 0x31, 0x56, 0xfa, + 0x13, 0x82, 0xb3, 0x53, 0x73, 0x19, 0xb7, 0xe7, 0xda, 0x3e, 0x33, 0xe3, 0x95, 0x9b, 0xc7, 0xc2, + 0x48, 0x2d, 0xba, 0xd0, 0x72, 0x1d, 0x37, 0x73, 0xb4, 0x90, 0x09, 0xae, 0x27, 0x57, 0xef, 0x73, + 0x04, 0xaf, 0xe5, 0x0c, 0x4b, 0xfc, 0x76, 0x59, 0x5f, 0x33, 0xde, 0xce, 0xed, 0x85, 0xb0, 0x52, + 0xc5, 0x43, 0xa1, 0xe2, 0x1e, 0xee, 0x2c, 0x78, 0x23, 0x89, 0x17, 0xd5, 0xf9, 0xe4, 0xe9, 0x41, + 0x1d, 0x3d, 0x3b, 0xa8, 0xa3, 0x7f, 0x0e, 0xea, 0xe8, 0xfb, 0xc3, 0x7a, 0xe5, 0xd9, 0x61, 0xbd, + 0xf2, 0xd7, 0x61, 0xbd, 0xf2, 0x65, 0xdb, 0x76, 0xf8, 0x60, 0x64, 0x6a, 0x16, 0xdb, 0x9d, 0xd4, + 0xb1, 0x06, 0xc4, 0xf1, 0x8e, 0x8a, 0xee, 0x25, 0xcb, 0xf2, 0x7d, 0x9f, 0x06, 0x66, 0x55, 0xfc, + 0x13, 0x7f, 0xf3, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xc9, 0xa4, 0x87, 0xac, 0x0c, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -732,16 +823,18 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // BTCValidators queries all btc validators + // BTCValidators queries all BTC validators BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) - // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + // PendingBTCDelegations queries all pending BTC delegations + PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) + // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height + // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) - // BTCValidatorDelegations queries all btc delegations of the given btc validator + // BTCValidatorDelegations queries all BTC delegations of the given BTC validator BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) } @@ -771,6 +864,15 @@ func (c *queryClient) BTCValidators(ctx context.Context, in *QueryBTCValidatorsR return out, nil } +func (c *queryClient) PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) { + out := new(QueryPendingBTCDelegationsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/PendingBTCDelegations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) { out := new(QueryActiveBTCValidatorsAtHeightResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", in, out, opts...) @@ -811,16 +913,18 @@ func (c *queryClient) BTCValidatorDelegations(ctx context.Context, in *QueryBTCV type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // BTCValidators queries all btc validators + // BTCValidators queries all BTC validators BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) - // ActiveBTCValidatorsAtHeight queries btc validators with non zero voting power at given height. + // PendingBTCDelegations queries all pending BTC delegations + PendingBTCDelegations(context.Context, *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) + // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries the voting power of a btc validator at a given height + // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height BTCValidatorPowerAtHeight(context.Context, *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power ActivatedHeight(context.Context, *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) - // BTCValidatorDelegations queries all btc delegations of the given btc validator + // BTCValidatorDelegations queries all BTC delegations of the given BTC validator BTCValidatorDelegations(context.Context, *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) } @@ -834,6 +938,9 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidators not implemented") } +func (*UnimplementedQueryServer) PendingBTCDelegations(ctx context.Context, req *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PendingBTCDelegations not implemented") +} func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context, req *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ActiveBTCValidatorsAtHeight not implemented") } @@ -887,6 +994,24 @@ func _Query_BTCValidators_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Query_PendingBTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPendingBTCDelegationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PendingBTCDelegations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/PendingBTCDelegations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PendingBTCDelegations(ctx, req.(*QueryPendingBTCDelegationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_ActiveBTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryActiveBTCValidatorsAtHeightRequest) if err := dec(in); err != nil { @@ -971,6 +1096,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BTCValidators", Handler: _Query_BTCValidators_Handler, }, + { + MethodName: "PendingBTCDelegations", + Handler: _Query_PendingBTCDelegations_Handler, + }, { MethodName: "ActiveBTCValidatorsAtHeight", Handler: _Query_ActiveBTCValidatorsAtHeight_Handler, @@ -1132,6 +1261,66 @@ func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *QueryPendingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingBTCDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingBTCDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryPendingBTCDelegationsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingBTCDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcDelegations) > 0 { + for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func (m *QueryBTCValidatorPowerAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1494,6 +1683,30 @@ func (m *QueryBTCValidatorsResponse) Size() (n int) { return n } +func (m *QueryPendingBTCDelegationsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryPendingBTCDelegationsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcDelegations) > 0 { + for _, e := range m.BtcDelegations { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + func (m *QueryBTCValidatorPowerAtHeightRequest) Size() (n int) { if m == nil { return 0 @@ -1962,6 +2175,140 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryPendingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingBTCDelegationsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingBTCDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPendingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingBTCDelegationsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingBTCDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) + if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 112e6df42..e24feb212 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -87,6 +87,24 @@ func local_request_Query_BTCValidators_0(ctx context.Context, marshaler runtime. } +func request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingBTCDelegationsRequest + var metadata runtime.ServerMetadata + + msg, err := client.PendingBTCDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingBTCDelegationsRequest + var metadata runtime.ServerMetadata + + msg, err := server.PendingBTCDelegations(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_ActiveBTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -377,6 +395,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PendingBTCDelegations_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -550,6 +591,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PendingBTCDelegations_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -638,6 +699,8 @@ var ( pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_PendingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "pending_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -652,6 +715,8 @@ var ( forward_Query_BTCValidators_0 = runtime.ForwardResponseMessage + forward_Query_PendingBTCDelegations_0 = runtime.ForwardResponseMessage + forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage From 21cd9aa3ab5f4b61f9e01489e48f1097f02e6fc8 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 24 Jul 2023 16:22:48 +0200 Subject: [PATCH 041/202] Add query for header main chain depth (#35) * Add query for header main chain depth --- proto/babylon/btclightclient/v1/query.proto | 14 + test/e2e/btc_timestamping_e2e_test.go | 10 + test/e2e/configurer/chain/queries.go | 13 + x/btclightclient/client/cli/query.go | 29 ++ x/btclightclient/keeper/grpc_query.go | 27 ++ x/btclightclient/types/errors.go | 1 + x/btclightclient/types/querier.go | 9 + x/btclightclient/types/query.pb.go | 462 ++++++++++++++++++-- x/btclightclient/types/query.pb.gw.go | 101 +++++ 9 files changed, 621 insertions(+), 45 deletions(-) diff --git a/proto/babylon/btclightclient/v1/query.proto b/proto/babylon/btclightclient/v1/query.proto index a562cfece..a4934911a 100644 --- a/proto/babylon/btclightclient/v1/query.proto +++ b/proto/babylon/btclightclient/v1/query.proto @@ -44,6 +44,12 @@ service Query { rpc BaseHeader(QueryBaseHeaderRequest) returns (QueryBaseHeaderResponse) { option (google.api.http).get = "/babylon/btclightclient/v1/baseheader"; } + + // HeaderDepth returns the depth of the header in main chain or error if the + // block is not found or it exists on fork + rpc HeaderDepth(QueryHeaderDepthRequest) returns(QueryHeaderDepthResponse) { + option (google.api.http).get = "/babylon/btclightclient/v1/depth/{hash}"; + } } // QueryHashesRequest is request type for the Query/Hashes RPC method. @@ -105,3 +111,11 @@ message QueryBaseHeaderRequest {} // QueryBaseHeaderResponse is the response type for the Query/BaseHeader RPC // method. message QueryBaseHeaderResponse { BTCHeaderInfo header = 1; } + +// QueryMainChainDepthRequest is the request type for the Query/MainChainDepth RPC +// it contains hex encoded hash of btc block header as parameter +message QueryHeaderDepthRequest { string hash = 1; } + +// QueryMainChainDepthResponse is the response type for the Query/MainChainDepth RPC +// it contains depth of the block in main chain +message QueryHeaderDepthResponse { uint64 depth = 1; } diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go index c99881e1f..85f066a13 100644 --- a/test/e2e/btc_timestamping_e2e_test.go +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -91,6 +91,16 @@ func (s *BTCTimestampingTestSuite) TestSendTx() { s.NoError(err) s.Equal(tip1.Height+1, tip2.Height) + + // check that light client properly updates its state + tip1Depth, err := nonValidatorNode.QueryHeaderDepth(tip1.Hash.MarshalHex()) + s.NoError(err) + s.Equal(tip1Depth, uint64(1)) + + tip2Depth, err := nonValidatorNode.QueryHeaderDepth(tip2.Hash.MarshalHex()) + s.NoError(err) + // tip should have 0 depth + s.Equal(tip2Depth, uint64(0)) } func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index 176c9da5b..0c49feee4 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -201,6 +201,19 @@ func (n *NodeConfig) QueryTip() (*blc.BTCHeaderInfo, error) { return blcResponse.Header, nil } +func (n *NodeConfig) QueryHeaderDepth(hash string) (uint64, error) { + path := fmt.Sprintf("babylon/btclightclient/v1/depth/%s", hash) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var blcResponse blc.QueryHeaderDepthResponse + if err := util.Cdc.UnmarshalJSON(bz, &blcResponse); err != nil { + return 0, err + } + + return blcResponse.Depth, nil +} + func (n *NodeConfig) QueryFinalizedChainsInfo(chainIDs []string) ([]*zctypes.FinalizedChainInfo, error) { queryParams := url.Values{} for _, chainId := range chainIDs { diff --git a/x/btclightclient/client/cli/query.go b/x/btclightclient/client/cli/query.go index 98360a8d0..e6446c563 100644 --- a/x/btclightclient/client/cli/query.go +++ b/x/btclightclient/client/cli/query.go @@ -26,6 +26,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdMainChain()) cmd.AddCommand(CmdTip()) cmd.AddCommand(CmdBaseHeader()) + cmd.AddCommand(CmdHeaderDepth()) return cmd } @@ -167,3 +168,31 @@ func CmdBaseHeader() *cobra.Command { return cmd } + +func CmdHeaderDepth() *cobra.Command { + cmd := &cobra.Command{ + Use: "header-depth [hex-hash]", + Short: "check main chain depth of the header with the given hash", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + depthRequest, err := types.NewQueryHeaderDepthRequest(args[0]) + if err != nil { + return err + } + res, err := queryClient.HeaderDepth(context.Background(), depthRequest) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btclightclient/keeper/grpc_query.go b/x/btclightclient/keeper/grpc_query.go index edd0d1369..4b37d4c09 100644 --- a/x/btclightclient/keeper/grpc_query.go +++ b/x/btclightclient/keeper/grpc_query.go @@ -179,3 +179,30 @@ func (k Keeper) BaseHeader(ctx context.Context, req *types.QueryBaseHeaderReques return &types.QueryBaseHeaderResponse{Header: baseHeader}, nil } + +func (k Keeper) HeaderDepth(ctx context.Context, req *types.QueryHeaderDepthRequest) (*types.QueryHeaderDepthResponse, error) { + + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + haderHash, err := bbn.NewBTCHeaderHashBytesFromHex(req.Hash) + + if err != nil { + return nil, status.Error(codes.InvalidArgument, "provided hash is not a valid hex string") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + depth, err := k.MainChainDepth(sdkCtx, &haderHash) + + if err != nil { + return nil, err + } + + if depth < 0 { + return nil, types.ErrHeaderOnFork + } + + return &types.QueryHeaderDepthResponse{Depth: uint64(depth)}, nil +} diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index 4ff8ee786..d6dfc1e63 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -14,4 +14,5 @@ var ( ErrInvalidDifficulty = errorsmod.Register(ModuleName, 1103, "invalid difficulty bits") ErrEmptyMessage = errorsmod.Register(ModuleName, 1104, "empty message provided") ErrInvalidProofOfWOrk = errorsmod.Register(ModuleName, 1105, "provided header has invalid proof of work") + ErrHeaderOnFork = errorsmod.Register(ModuleName, 1106, "provided header is on a fork") ) diff --git a/x/btclightclient/types/querier.go b/x/btclightclient/types/querier.go index 51537d46c..a35893026 100644 --- a/x/btclightclient/types/querier.go +++ b/x/btclightclient/types/querier.go @@ -20,6 +20,15 @@ func NewQueryContainsRequest(hash string) (*QueryContainsRequest, error) { return res, nil } +func NewQueryHeaderDepthRequest(hash string) (*QueryHeaderDepthRequest, error) { + _, err := types.NewBTCHeaderHashBytesFromHex(hash) + if err != nil { + return nil, err + } + res := &QueryHeaderDepthRequest{Hash: hash} + return res, nil +} + func NewQueryMainChainRequest(req *query.PageRequest) *QueryMainChainRequest { return &QueryMainChainRequest{Pagination: req} } diff --git a/x/btclightclient/types/query.pb.go b/x/btclightclient/types/query.pb.go index 123bb92d6..cf79b0c7c 100644 --- a/x/btclightclient/types/query.pb.go +++ b/x/btclightclient/types/query.pb.go @@ -564,6 +564,98 @@ func (m *QueryBaseHeaderResponse) GetHeader() *BTCHeaderInfo { return nil } +// QueryMainChainDepthRequest is the request type for the Query/MainChainDepth RPC +// it contains hex encoded hash of btc block header as parameter +type QueryHeaderDepthRequest struct { + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *QueryHeaderDepthRequest) Reset() { *m = QueryHeaderDepthRequest{} } +func (m *QueryHeaderDepthRequest) String() string { return proto.CompactTextString(m) } +func (*QueryHeaderDepthRequest) ProtoMessage() {} +func (*QueryHeaderDepthRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_3961270631e52721, []int{12} +} +func (m *QueryHeaderDepthRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryHeaderDepthRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryHeaderDepthRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryHeaderDepthRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryHeaderDepthRequest.Merge(m, src) +} +func (m *QueryHeaderDepthRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryHeaderDepthRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryHeaderDepthRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryHeaderDepthRequest proto.InternalMessageInfo + +func (m *QueryHeaderDepthRequest) GetHash() string { + if m != nil { + return m.Hash + } + return "" +} + +// QueryMainChainDepthResponse is the response type for the Query/MainChainDepth RPC +// it contains depth of the block in main chain +type QueryHeaderDepthResponse struct { + Depth uint64 `protobuf:"varint,1,opt,name=depth,proto3" json:"depth,omitempty"` +} + +func (m *QueryHeaderDepthResponse) Reset() { *m = QueryHeaderDepthResponse{} } +func (m *QueryHeaderDepthResponse) String() string { return proto.CompactTextString(m) } +func (*QueryHeaderDepthResponse) ProtoMessage() {} +func (*QueryHeaderDepthResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_3961270631e52721, []int{13} +} +func (m *QueryHeaderDepthResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryHeaderDepthResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryHeaderDepthResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryHeaderDepthResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryHeaderDepthResponse.Merge(m, src) +} +func (m *QueryHeaderDepthResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryHeaderDepthResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryHeaderDepthResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryHeaderDepthResponse proto.InternalMessageInfo + +func (m *QueryHeaderDepthResponse) GetDepth() uint64 { + if m != nil { + return m.Depth + } + return 0 +} + func init() { proto.RegisterType((*QueryHashesRequest)(nil), "babylon.btclightclient.v1.QueryHashesRequest") proto.RegisterType((*QueryHashesResponse)(nil), "babylon.btclightclient.v1.QueryHashesResponse") @@ -577,6 +669,8 @@ func init() { proto.RegisterType((*QueryTipResponse)(nil), "babylon.btclightclient.v1.QueryTipResponse") proto.RegisterType((*QueryBaseHeaderRequest)(nil), "babylon.btclightclient.v1.QueryBaseHeaderRequest") proto.RegisterType((*QueryBaseHeaderResponse)(nil), "babylon.btclightclient.v1.QueryBaseHeaderResponse") + proto.RegisterType((*QueryHeaderDepthRequest)(nil), "babylon.btclightclient.v1.QueryHeaderDepthRequest") + proto.RegisterType((*QueryHeaderDepthResponse)(nil), "babylon.btclightclient.v1.QueryHeaderDepthResponse") } func init() { @@ -584,51 +678,55 @@ func init() { } var fileDescriptor_3961270631e52721 = []byte{ - // 694 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xb1, 0x4f, 0x14, 0x4f, - 0x14, 0xc7, 0x19, 0xf8, 0xfd, 0x0e, 0x7c, 0x68, 0xd4, 0x11, 0x15, 0x36, 0x66, 0xc5, 0x45, 0xe0, - 0x04, 0xd9, 0xe1, 0x0e, 0x35, 0x14, 0x16, 0xe6, 0x48, 0x14, 0x0b, 0x13, 0xbc, 0x5c, 0xa5, 0x26, - 0x66, 0xf6, 0x1c, 0x77, 0x37, 0x81, 0x9d, 0x85, 0x19, 0x88, 0xd7, 0x5a, 0x58, 0x1b, 0xed, 0x2c, - 0x2c, 0x4c, 0x6c, 0xad, 0x6c, 0xfc, 0x0f, 0x2c, 0x49, 0x6c, 0x8c, 0x85, 0x31, 0xe0, 0x1f, 0x62, - 0x76, 0xe6, 0x1d, 0xc7, 0x1d, 0x72, 0xb7, 0x44, 0x1a, 0xc2, 0xee, 0xbc, 0xef, 0xfb, 0x7e, 0xe6, - 0x65, 0xbe, 0xb3, 0x07, 0x93, 0x01, 0x0f, 0x1a, 0xab, 0x32, 0x61, 0x81, 0xae, 0xaf, 0xc6, 0x61, - 0x94, 0xfd, 0x15, 0x89, 0x66, 0x5b, 0x25, 0xb6, 0xbe, 0x29, 0x36, 0x1a, 0x7e, 0xba, 0x21, 0xb5, - 0xa4, 0x63, 0x58, 0xe6, 0xb7, 0x97, 0xf9, 0x5b, 0x25, 0x67, 0x24, 0x94, 0xa1, 0x34, 0x55, 0x2c, - 0xfb, 0xcf, 0x0a, 0x9c, 0x4b, 0xa1, 0x94, 0xe1, 0xaa, 0x60, 0x3c, 0x8d, 0x19, 0x4f, 0x12, 0xa9, - 0xb9, 0x8e, 0x65, 0xa2, 0x70, 0x75, 0xa6, 0x2e, 0xd5, 0x9a, 0x54, 0x2c, 0xe0, 0x4a, 0x58, 0x1f, - 0xb6, 0x55, 0x0a, 0x84, 0xe6, 0x25, 0x96, 0xf2, 0x30, 0x4e, 0x4c, 0x31, 0xd6, 0xfa, 0x87, 0x13, - 0x76, 0xc0, 0x98, 0x7a, 0xef, 0x09, 0xd0, 0x87, 0x59, 0xc7, 0x65, 0xae, 0x22, 0xa1, 0xaa, 0x62, - 0x7d, 0x53, 0x28, 0x4d, 0xef, 0x02, 0xb4, 0x3a, 0x8f, 0x92, 0x71, 0x52, 0x1c, 0x2e, 0x4f, 0xf9, - 0x16, 0xc3, 0xcf, 0x30, 0x7c, 0xbb, 0x5d, 0xc4, 0xf0, 0x57, 0x78, 0x28, 0x50, 0x5b, 0xdd, 0xa7, - 0xf4, 0x3e, 0x13, 0x38, 0xd7, 0xd6, 0x5e, 0xa5, 0x32, 0x51, 0x82, 0xd6, 0xa0, 0x10, 0x99, 0x37, - 0xa3, 0x64, 0x7c, 0xa0, 0x78, 0xb2, 0x72, 0xfb, 0xc7, 0xcf, 0xcb, 0x8b, 0x61, 0xac, 0xa3, 0xcd, - 0xc0, 0xaf, 0xcb, 0x35, 0x86, 0x9b, 0xa8, 0x47, 0x3c, 0x4e, 0x9a, 0x0f, 0x4c, 0x37, 0x52, 0xa1, - 0xfc, 0x4a, 0x6d, 0x69, 0x59, 0xf0, 0x67, 0x62, 0x23, 0x6b, 0x59, 0x69, 0x68, 0xa1, 0xaa, 0xd8, - 0x8b, 0xde, 0x6b, 0xa3, 0xee, 0x37, 0xd4, 0xd3, 0x3d, 0xa9, 0x2d, 0x52, 0x1b, 0x76, 0x04, 0x23, - 0x86, 0x7a, 0x49, 0x26, 0x9a, 0xc7, 0xc9, 0xde, 0x58, 0x56, 0xe0, 0xbf, 0xcc, 0xca, 0x0c, 0xe4, - 0x5f, 0xa1, 0x4d, 0x27, 0x6f, 0x01, 0xce, 0x77, 0x38, 0xe1, 0x84, 0x1c, 0x18, 0xaa, 0xe3, 0x3b, - 0x63, 0x37, 0x54, 0xdd, 0x7b, 0xf6, 0x18, 0x8c, 0xb5, 0x89, 0x6c, 0x43, 0x64, 0xa4, 0xfb, 0x19, - 0xd1, 0x65, 0x11, 0x9c, 0xbf, 0x09, 0x72, 0x58, 0x3d, 0x45, 0xbe, 0x07, 0x3c, 0x4e, 0x96, 0xb2, - 0x8d, 0x1d, 0xf7, 0x09, 0xf9, 0x48, 0xe0, 0x42, 0xa7, 0x03, 0x72, 0x55, 0x60, 0x30, 0x32, 0x43, - 0xb3, 0xa7, 0x64, 0xb8, 0x5c, 0xf4, 0x0f, 0xcd, 0x55, 0x6b, 0xc2, 0xf7, 0x93, 0xe7, 0xb2, 0xda, - 0x14, 0x1e, 0xdf, 0x91, 0x38, 0x0b, 0xa7, 0x0d, 0x66, 0x2d, 0x4e, 0x71, 0x1b, 0x5e, 0x0d, 0xce, - 0xb4, 0x5e, 0x21, 0xf3, 0x1d, 0x28, 0x58, 0x6b, 0x1c, 0x49, 0x7e, 0x64, 0xd4, 0x79, 0xa3, 0x38, - 0x8f, 0x0a, 0x57, 0xc2, 0x2e, 0x37, 0xfd, 0x1e, 0xc3, 0xc5, 0x03, 0x2b, 0xc7, 0x65, 0x5b, 0xfe, - 0x32, 0x08, 0xff, 0x9b, 0xee, 0xf4, 0x0d, 0x81, 0x82, 0x8d, 0x2b, 0x9d, 0xeb, 0xd2, 0xe6, 0xe0, - 0xad, 0xe1, 0xf8, 0x79, 0xcb, 0x2d, 0xb5, 0x77, 0xed, 0xe5, 0xb7, 0xdf, 0x6f, 0xfb, 0x27, 0xe8, - 0x15, 0x76, 0xf8, 0xa5, 0x85, 0xd1, 0x7e, 0x47, 0x60, 0xa8, 0x79, 0x7a, 0x29, 0xeb, 0xe5, 0xd3, - 0x91, 0x5b, 0x67, 0x3e, 0xbf, 0x00, 0xd1, 0x66, 0x0d, 0xda, 0x24, 0x9d, 0xe8, 0x82, 0xd6, 0x0c, - 0x09, 0xfd, 0x44, 0xe0, 0x54, 0x5b, 0xb4, 0xe8, 0x8d, 0xbc, 0x86, 0xfb, 0xa3, 0xeb, 0xdc, 0x3c, - 0xa2, 0x0a, 0x59, 0xe7, 0x0d, 0xeb, 0x0c, 0x2d, 0xe6, 0x60, 0xb5, 0x78, 0xef, 0x09, 0x9c, 0xd8, - 0xcb, 0x1b, 0xed, 0x39, 0x9d, 0xce, 0xf0, 0x3b, 0xa5, 0x23, 0x28, 0x10, 0xf2, 0xba, 0x81, 0x9c, - 0xa2, 0x57, 0xbb, 0x40, 0xae, 0xf1, 0xd8, 0xde, 0x9e, 0xf4, 0x15, 0x81, 0x81, 0x5a, 0x9c, 0xd2, - 0x99, 0x5e, 0x46, 0xad, 0x38, 0x3a, 0xb3, 0xb9, 0x6a, 0x11, 0x67, 0xca, 0xe0, 0x8c, 0x53, 0xb7, - 0x0b, 0x8e, 0x8e, 0x53, 0xfa, 0x81, 0x00, 0xb4, 0xf2, 0x46, 0x7b, 0x6e, 0xfc, 0x40, 0x6a, 0x9d, - 0xf2, 0x51, 0x24, 0x48, 0x37, 0x67, 0xe8, 0xa6, 0xe9, 0x64, 0x17, 0xba, 0xec, 0xf2, 0xb2, 0xd9, - 0xad, 0xac, 0x7c, 0xdd, 0x71, 0xc9, 0xf6, 0x8e, 0x4b, 0x7e, 0xed, 0xb8, 0xe4, 0xf5, 0xae, 0xdb, - 0xb7, 0xbd, 0xeb, 0xf6, 0x7d, 0xdf, 0x75, 0xfb, 0x1e, 0xdd, 0xea, 0xf5, 0x79, 0x7a, 0xd1, 0xd9, - 0xd9, 0x7c, 0xaf, 0x82, 0x82, 0xf9, 0x71, 0xb0, 0xf0, 0x27, 0x00, 0x00, 0xff, 0xff, 0x88, 0x96, - 0xa9, 0x76, 0xf0, 0x08, 0x00, 0x00, + // 760 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x4f, 0x4f, 0x13, 0x4f, + 0x18, 0xc7, 0x19, 0xfe, 0xf4, 0x57, 0x1e, 0x7e, 0x46, 0x1d, 0x51, 0xcb, 0xc6, 0x54, 0x2c, 0x02, + 0x05, 0x64, 0x87, 0x16, 0x35, 0x1c, 0x3c, 0x98, 0x62, 0x14, 0x0f, 0x26, 0xd8, 0xf4, 0xa4, 0x26, + 0x66, 0xb6, 0x8c, 0xbb, 0x9b, 0xc0, 0xce, 0xc2, 0x2e, 0xc4, 0xc6, 0x78, 0xf1, 0xe0, 0xd9, 0xe8, + 0xcd, 0x83, 0x07, 0x13, 0xe3, 0xcd, 0x93, 0x2f, 0xc2, 0x23, 0x89, 0x17, 0xe3, 0xc1, 0x18, 0xea, + 0x0b, 0x31, 0x3b, 0xf3, 0x94, 0xfe, 0x83, 0xee, 0x36, 0x72, 0x21, 0xcc, 0xcc, 0xf3, 0x7d, 0xbe, + 0x9f, 0x79, 0x32, 0xcf, 0xb3, 0x85, 0x69, 0x8b, 0x5b, 0xb5, 0x4d, 0xe9, 0x31, 0x2b, 0xac, 0x6e, + 0xba, 0xb6, 0x13, 0xfd, 0x15, 0x5e, 0xc8, 0xf6, 0x0a, 0x6c, 0x7b, 0x57, 0xec, 0xd4, 0x4c, 0x7f, + 0x47, 0x86, 0x92, 0x4e, 0x60, 0x98, 0xd9, 0x1e, 0x66, 0xee, 0x15, 0x8c, 0x71, 0x5b, 0xda, 0x52, + 0x45, 0xb1, 0xe8, 0x3f, 0x2d, 0x30, 0x2e, 0xd9, 0x52, 0xda, 0x9b, 0x82, 0x71, 0xdf, 0x65, 0xdc, + 0xf3, 0x64, 0xc8, 0x43, 0x57, 0x7a, 0x01, 0x9e, 0xce, 0x57, 0x65, 0xb0, 0x25, 0x03, 0x66, 0xf1, + 0x40, 0x68, 0x1f, 0xb6, 0x57, 0xb0, 0x44, 0xc8, 0x0b, 0xcc, 0xe7, 0xb6, 0xeb, 0xa9, 0x60, 0x8c, + 0x35, 0x8f, 0x27, 0xec, 0x80, 0x51, 0xf1, 0xb9, 0x27, 0x40, 0x1f, 0x46, 0x19, 0xd7, 0x78, 0xe0, + 0x88, 0xa0, 0x2c, 0xb6, 0x77, 0x45, 0x10, 0xd2, 0xbb, 0x00, 0xcd, 0xcc, 0x19, 0x32, 0x49, 0xf2, + 0x63, 0xc5, 0x19, 0x53, 0x63, 0x98, 0x11, 0x86, 0xa9, 0xaf, 0x8b, 0x18, 0xe6, 0x3a, 0xb7, 0x05, + 0x6a, 0xcb, 0x2d, 0xca, 0xdc, 0x57, 0x02, 0xe7, 0xda, 0xd2, 0x07, 0xbe, 0xf4, 0x02, 0x41, 0x2b, + 0x90, 0x72, 0xd4, 0x4e, 0x86, 0x4c, 0x0e, 0xe5, 0xff, 0x2f, 0xdd, 0xfa, 0xf9, 0xeb, 0xf2, 0x8a, + 0xed, 0x86, 0xce, 0xae, 0x65, 0x56, 0xe5, 0x16, 0xc3, 0x4b, 0x54, 0x1d, 0xee, 0x7a, 0x8d, 0x05, + 0x0b, 0x6b, 0xbe, 0x08, 0xcc, 0x52, 0x65, 0x75, 0x4d, 0xf0, 0x0d, 0xb1, 0x13, 0xa5, 0x2c, 0xd5, + 0x42, 0x11, 0x94, 0x31, 0x17, 0xbd, 0xd7, 0x46, 0x3d, 0xa8, 0xa8, 0x67, 0x63, 0xa9, 0x35, 0x52, + 0x1b, 0xb6, 0x03, 0xe3, 0x8a, 0x7a, 0x55, 0x7a, 0x21, 0x77, 0xbd, 0xc3, 0xb2, 0xac, 0xc3, 0x70, + 0x64, 0xa5, 0x0a, 0xf2, 0xaf, 0xd0, 0x2a, 0x53, 0x6e, 0x19, 0xce, 0x77, 0x38, 0x61, 0x85, 0x0c, + 0x48, 0x57, 0x71, 0x4f, 0xd9, 0xa5, 0xcb, 0x87, 0xeb, 0x1c, 0x83, 0x89, 0x36, 0x91, 0x4e, 0x88, + 0x8c, 0xb4, 0x95, 0x11, 0x5d, 0x56, 0xc0, 0x38, 0x4a, 0x90, 0xc0, 0xea, 0x29, 0xf2, 0x3d, 0xe0, + 0xae, 0xb7, 0x1a, 0x5d, 0xec, 0xa4, 0x5f, 0xc8, 0x27, 0x02, 0x17, 0x3a, 0x1d, 0x90, 0xab, 0x04, + 0xff, 0x39, 0xaa, 0x68, 0xfa, 0x95, 0x8c, 0x15, 0xf3, 0xe6, 0xb1, 0x7d, 0xd5, 0xac, 0xf0, 0x7d, + 0xef, 0x99, 0x2c, 0x37, 0x84, 0x27, 0xf7, 0x24, 0xce, 0xc2, 0x69, 0x85, 0x59, 0x71, 0x7d, 0xbc, + 0x46, 0xae, 0x02, 0x67, 0x9a, 0x5b, 0xc8, 0x7c, 0x1b, 0x52, 0xda, 0x1a, 0x4b, 0x92, 0x1c, 0x19, + 0x75, 0xb9, 0x0c, 0xd6, 0xa3, 0xc4, 0x03, 0xa1, 0x8f, 0x1b, 0x7e, 0x8f, 0xe1, 0x62, 0xd7, 0xc9, + 0x89, 0xd9, 0x2e, 0x62, 0x72, 0x7d, 0x74, 0x47, 0xf8, 0xa1, 0x73, 0xd4, 0x8b, 0x1a, 0xc5, 0x17, + 0xb5, 0x04, 0x99, 0xee, 0x70, 0x84, 0x19, 0x87, 0x91, 0x8d, 0x68, 0x43, 0x09, 0x86, 0xcb, 0x7a, + 0x51, 0xac, 0xa7, 0x61, 0x44, 0x49, 0xe8, 0x5b, 0x02, 0x29, 0x3d, 0x0f, 0xe8, 0x62, 0x0f, 0xce, + 0xee, 0xb1, 0x64, 0x98, 0x49, 0xc3, 0x35, 0x49, 0x6e, 0xee, 0xd5, 0xf7, 0x3f, 0xef, 0x06, 0xa7, + 0xe8, 0x15, 0x76, 0xfc, 0x54, 0xc4, 0xd9, 0xf1, 0x9e, 0x40, 0xba, 0xd1, 0x1e, 0x94, 0xc5, 0xf9, + 0x74, 0x0c, 0x06, 0x63, 0x29, 0xb9, 0x00, 0xd1, 0x16, 0x14, 0xda, 0x34, 0x9d, 0xea, 0x81, 0xd6, + 0xe8, 0x42, 0xfa, 0x85, 0xc0, 0xa9, 0xb6, 0xde, 0xa5, 0xd7, 0x93, 0x1a, 0xb6, 0xce, 0x06, 0xe3, + 0x46, 0x9f, 0x2a, 0x64, 0x5d, 0x52, 0xac, 0xf3, 0x34, 0x9f, 0x80, 0x55, 0xe3, 0x7d, 0x20, 0x30, + 0x7a, 0xd8, 0xd0, 0x34, 0xb6, 0x3a, 0x9d, 0xd3, 0xc5, 0x28, 0xf4, 0xa1, 0x40, 0xc8, 0x6b, 0x0a, + 0x72, 0x86, 0x5e, 0xed, 0x01, 0xb9, 0xc5, 0x5d, 0x3d, 0x9e, 0xe9, 0x6b, 0x02, 0x43, 0x15, 0xd7, + 0xa7, 0xf3, 0x71, 0x46, 0xcd, 0x7e, 0x37, 0x16, 0x12, 0xc5, 0x22, 0xce, 0x8c, 0xc2, 0x99, 0xa4, + 0xd9, 0x1e, 0x38, 0xa1, 0xeb, 0xd3, 0x8f, 0x04, 0xa0, 0xd9, 0xd0, 0x34, 0xf6, 0xe2, 0x5d, 0x63, + 0xc1, 0x28, 0xf6, 0x23, 0x41, 0xba, 0x45, 0x45, 0x37, 0x4b, 0xa7, 0x7b, 0xd0, 0x45, 0xd3, 0x51, + 0x0f, 0x07, 0xfa, 0x99, 0xc0, 0x58, 0x4b, 0xa7, 0xd3, 0x58, 0xcb, 0xee, 0x29, 0x62, 0x2c, 0xf7, + 0xa5, 0x41, 0x4e, 0xa6, 0x38, 0xe7, 0xe8, 0x6c, 0x0f, 0x4e, 0x35, 0x5e, 0xd8, 0x8b, 0xa8, 0x8f, + 0x5f, 0x96, 0xd6, 0xbf, 0x1d, 0x64, 0xc9, 0xfe, 0x41, 0x96, 0xfc, 0x3e, 0xc8, 0x92, 0x37, 0xf5, + 0xec, 0xc0, 0x7e, 0x3d, 0x3b, 0xf0, 0xa3, 0x9e, 0x1d, 0x78, 0x74, 0x33, 0xee, 0x4b, 0xfd, 0xbc, + 0x33, 0xb7, 0xfa, 0x74, 0x5b, 0x29, 0xf5, 0x3b, 0x69, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xf7, 0x92, 0x0d, 0xa9, 0xfb, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -659,6 +757,9 @@ type QueryClient interface { // BaseHeader returns the base BTC header of the chain. This header is defined // on genesis. BaseHeader(ctx context.Context, in *QueryBaseHeaderRequest, opts ...grpc.CallOption) (*QueryBaseHeaderResponse, error) + // HeaderDepth returns the depth of the header in main chain or error if the + // block is not found or it exists on fork + HeaderDepth(ctx context.Context, in *QueryHeaderDepthRequest, opts ...grpc.CallOption) (*QueryHeaderDepthResponse, error) } type queryClient struct { @@ -723,6 +824,15 @@ func (c *queryClient) BaseHeader(ctx context.Context, in *QueryBaseHeaderRequest return out, nil } +func (c *queryClient) HeaderDepth(ctx context.Context, in *QueryHeaderDepthRequest, opts ...grpc.CallOption) (*QueryHeaderDepthResponse, error) { + out := new(QueryHeaderDepthResponse) + err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Query/HeaderDepth", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Hashes retrieves the hashes maintained by the module. @@ -741,6 +851,9 @@ type QueryServer interface { // BaseHeader returns the base BTC header of the chain. This header is defined // on genesis. BaseHeader(context.Context, *QueryBaseHeaderRequest) (*QueryBaseHeaderResponse, error) + // HeaderDepth returns the depth of the header in main chain or error if the + // block is not found or it exists on fork + HeaderDepth(context.Context, *QueryHeaderDepthRequest) (*QueryHeaderDepthResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -765,6 +878,9 @@ func (*UnimplementedQueryServer) Tip(ctx context.Context, req *QueryTipRequest) func (*UnimplementedQueryServer) BaseHeader(ctx context.Context, req *QueryBaseHeaderRequest) (*QueryBaseHeaderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BaseHeader not implemented") } +func (*UnimplementedQueryServer) HeaderDepth(ctx context.Context, req *QueryHeaderDepthRequest) (*QueryHeaderDepthResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method HeaderDepth not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -878,6 +994,24 @@ func _Query_BaseHeader_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _Query_HeaderDepth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryHeaderDepthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).HeaderDepth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btclightclient.v1.Query/HeaderDepth", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).HeaderDepth(ctx, req.(*QueryHeaderDepthRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btclightclient.v1.Query", HandlerType: (*QueryServer)(nil), @@ -906,6 +1040,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BaseHeader", Handler: _Query_BaseHeader_Handler, }, + { + MethodName: "HeaderDepth", + Handler: _Query_HeaderDepth_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btclightclient/v1/query.proto", @@ -1326,6 +1464,64 @@ func (m *QueryBaseHeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *QueryHeaderDepthRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryHeaderDepthRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryHeaderDepthRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryHeaderDepthResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryHeaderDepthResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryHeaderDepthResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Depth != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Depth)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1495,6 +1691,31 @@ func (m *QueryBaseHeaderResponse) Size() (n int) { return n } +func (m *QueryHeaderDepthRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHeaderDepthResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Depth != 0 { + n += 1 + sovQuery(uint64(m.Depth)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2495,6 +2716,157 @@ func (m *QueryBaseHeaderResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryHeaderDepthRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryHeaderDepthRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryHeaderDepthRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryHeaderDepthResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryHeaderDepthResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryHeaderDepthResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Depth", wireType) + } + m.Depth = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Depth |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btclightclient/types/query.pb.gw.go b/x/btclightclient/types/query.pb.gw.go index 14fc314c2..bcb180f44 100644 --- a/x/btclightclient/types/query.pb.gw.go +++ b/x/btclightclient/types/query.pb.gw.go @@ -213,6 +213,60 @@ func local_request_Query_BaseHeader_0(ctx context.Context, marshaler runtime.Mar } +func request_Query_HeaderDepth_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryHeaderDepthRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.HeaderDepth(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_HeaderDepth_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryHeaderDepthRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.HeaderDepth(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -357,6 +411,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_HeaderDepth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_HeaderDepth_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_HeaderDepth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -518,6 +595,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_HeaderDepth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_HeaderDepth_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_HeaderDepth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -533,6 +630,8 @@ var ( pattern_Query_Tip_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btclightclient", "v1", "tip"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BaseHeader_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btclightclient", "v1", "baseheader"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_HeaderDepth_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btclightclient", "v1", "depth", "hash"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -547,4 +646,6 @@ var ( forward_Query_Tip_0 = runtime.ForwardResponseMessage forward_Query_BaseHeader_0 = runtime.ForwardResponseMessage + + forward_Query_HeaderDepth_0 = runtime.ForwardResponseMessage ) From b98b160099214a6758847865acc5b12691585447 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 25 Jul 2023 14:28:20 +0800 Subject: [PATCH 042/202] chore: fixing RPCs and typos (#36) --- proto/babylon/btcstaking/v1/query.proto | 10 +++++----- proto/babylon/finality/v1/query.proto | 4 ++-- x/btccheckpoint/client/cli/query.go | 2 ++ x/btclightclient/client/cli/query.go | 2 ++ x/btcstaking/abci.go | 2 +- x/btcstaking/client/cli/query.go | 7 +++++-- x/btcstaking/keeper/keeper.go | 2 +- x/btcstaking/keeper/voting_power_table_test.go | 4 ++++ x/checkpointing/client/cli/query.go | 1 + x/finality/client/cli/query.go | 7 +++++-- x/monitor/keeper/keeper.go | 4 ++-- 11 files changed, 30 insertions(+), 15 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 4f6cdadf4..12989aa48 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -67,7 +67,7 @@ message QueryBTCValidatorsRequest { // QueryBTCValidatorsResponse is the response type for the // Query/BTCValidators RPC method. message QueryBTCValidatorsResponse { - // btc_validators contains all the btc validators + // btc_validators contains all the BTC validators repeated BTCValidator btc_validators = 1; // pagination defines the pagination in the response. @@ -82,7 +82,7 @@ message QueryPendingBTCDelegationsRequest {} // QueryPendingBTCDelegationsResponse is the response type for the // Query/PendingBTCDelegations RPC method. message QueryPendingBTCDelegationsResponse { - // btc_delegations contains all the queried btc delegations. + // btc_delegations contains all the queried BTC delegations. repeated BTCDelegation btc_delegations = 1; } @@ -108,7 +108,7 @@ message QueryBTCValidatorPowerAtHeightResponse { // QueryActiveBTCValidatorsAtHeightRequest is the request type for the // Query/ActiveBTCValidatorsAtHeight RPC method. message QueryActiveBTCValidatorsAtHeightRequest { - // height defines at which Babylon height to query the btc validators info. + // height defines at which Babylon height to query the BTC validators info. uint64 height = 1; // pagination defines an optional pagination for the request. @@ -118,7 +118,7 @@ message QueryActiveBTCValidatorsAtHeightRequest { // QueryActiveBTCValidatorsAtHeightResponse is the response type for the // Query/ActiveBTCValidatorsAtHeight RPC method. message QueryActiveBTCValidatorsAtHeightResponse { - // btc_validators contains all the queried btc validators. + // btc_validators contains all the queried BTC validators. repeated BTCValidatorWithMeta btc_validators = 1; // pagination defines the pagination in the response. @@ -152,7 +152,7 @@ message QueryBTCValidatorDelegationsRequest { // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsResponse { - // btc_delegations contains all the queried btc delegations. + // btc_delegations contains all the queried BTC delegations. repeated BTCDelegation btc_delegations = 1; // pagination defines the pagination in the response. diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 39b608385..e95a33f68 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -26,7 +26,7 @@ service Query { option (google.api.http).get = "/babylon/finality/v1/blocks"; } - // VotesAtHeight queries btc validators who have signed the block at given height. + // VotesAtHeight queries BTC validators who have signed the block at given height. rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { option (google.api.http).get = "/babylon/finality/v1/votes/{height}"; } @@ -86,7 +86,7 @@ message QueryListBlocksResponse { // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. message QueryVotesAtHeightRequest { - // height defines at which height to query the btc validators. + // height defines at which height to query the BTC validators. uint64 height = 1; } diff --git a/x/btccheckpoint/client/cli/query.go b/x/btccheckpoint/client/cli/query.go index 73696536e..49a2376cd 100644 --- a/x/btccheckpoint/client/cli/query.go +++ b/x/btccheckpoint/client/cli/query.go @@ -97,5 +97,7 @@ func CmdEpochSubmissions() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "epoch-submissions") + return cmd } diff --git a/x/btclightclient/client/cli/query.go b/x/btclightclient/client/cli/query.go index e6446c563..fb955b3d1 100644 --- a/x/btclightclient/client/cli/query.go +++ b/x/btclightclient/client/cli/query.go @@ -57,6 +57,7 @@ func CmdHashes() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "hashes") return cmd } @@ -115,6 +116,7 @@ func CmdMainChain() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "main-chain") return cmd } diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index 45717b9c4..0527abbbb 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -17,8 +17,8 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - k.RecordVotingPowerTable(ctx) k.IndexBTCHeight(ctx) + k.RecordVotingPowerTable(ctx) return []abci.ValidatorUpdate{} } diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 7e0e89eeb..7bb8949a0 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -62,6 +62,7 @@ func CmdBTCValidators() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "btc-validators") return cmd } @@ -146,7 +147,7 @@ func CmdActivatedHeight() *cobra.Command { func CmdBTCValidatorsAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validators-at-height [height]", - Short: "retrieve all btc validators at a given babylon height", + Short: "retrieve all BTC validators at a given babylon height", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -176,6 +177,7 @@ func CmdBTCValidatorsAtHeight() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "btc-validators-at-height") return cmd } @@ -183,7 +185,7 @@ func CmdBTCValidatorsAtHeight() *cobra.Command { func CmdBTCValidatorDelegations() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validator-delegations [btc_val_pk_hex]", - Short: "retrieve all delegations under a given btc validator", + Short: "retrieve all delegations under a given BTC validator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -218,6 +220,7 @@ func CmdBTCValidatorDelegations() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "btc-validator-delegations") cmd.Flags().String(flagDelegationStatus, "Active", "Status of the queried delegations (Pending|Active|Expired)") return cmd diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 1b43070d4..0d8b889ac 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -68,7 +68,7 @@ func (k Keeper) getHeaderAndDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHash } // get the tip tip := k.btclcKeeper.GetTipInfo(ctx) - // If the height of the requested header is larger than the tip, return -1 + // If the height of the requested header is larger than the tip, return error if tip.Height < header.Height { return nil, 0, fmt.Errorf("header is higher than the tip in BTC light client") } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 2944cc8c0..77f0c247f 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -102,5 +102,9 @@ func FuzzVotingPowerTable(f *testing.F) { require.Zero(t, power) } + // the activation height should be same as befofre + activatedHeight2, err := keeper.GetBTCStakingActivatedHeight(ctx) + require.NoError(t, err) + require.Equal(t, activatedHeight, activatedHeight2) }) } diff --git a/x/checkpointing/client/cli/query.go b/x/checkpointing/client/cli/query.go index 76b89bb05..5b1184ad6 100644 --- a/x/checkpointing/client/cli/query.go +++ b/x/checkpointing/client/cli/query.go @@ -64,6 +64,7 @@ func CmdRawCheckpointList() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "raw-checkpoint-list") return cmd } diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index e8919d1a1..c86f79aa0 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -2,9 +2,10 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/client/flags" "strconv" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/client" "github.com/spf13/cobra" @@ -32,7 +33,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { func CmdVotesAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "votes-at-height [height]", - Short: "retrieve all btc val pks who voted at requested babylon height", + Short: "retrieve all BTC val pks who voted at requested babylon height", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -86,6 +87,7 @@ func CmdListPublicRandomness() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "list-public-randomness") return cmd } @@ -122,6 +124,7 @@ func CmdListBlocks() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "list-blocks") cmd.Flags().Bool("finalized", false, "return finalized or non-finalized blocks") return cmd diff --git a/x/monitor/keeper/keeper.go b/x/monitor/keeper/keeper.go index aa5ff716a..d891d6f54 100644 --- a/x/monitor/keeper/keeper.go +++ b/x/monitor/keeper/keeper.go @@ -62,8 +62,8 @@ func (k Keeper) updateBtcLightClientHeightForCheckpoint(ctx sdk.Context, ckpt *c return err } - // if the checkpoint exists, meaning an earlier checkpoint with a lower btc height is already recorded - // we should keep the lower btc height in the store + // if the checkpoint exists, meaning an earlier checkpoint with a lower BTC height is already recorded + // we should keep the lower BTC height in the store if store.Has(storeKey) { k.Logger(ctx).With("module", fmt.Sprintf("checkpoint %s is already recorded", ckptHashStr)) return nil From 74890fa0abea5d7b874dc2e740019cf6d2620621 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 25 Jul 2023 18:48:25 +0800 Subject: [PATCH 043/202] rpc: supporting querying blocks in any state (#37) --- client/docs/swagger-ui/swagger.yaml | 62 +++++++++ proto/babylon/finality/v1/query.proto | 16 ++- x/btcstaking/types/query.pb.go | 10 +- x/finality/client/cli/query.go | 12 +- x/finality/keeper/grpc_query.go | 9 +- x/finality/keeper/grpc_query_test.go | 23 +++- x/finality/types/query.go | 20 +++ x/finality/types/query.pb.go | 173 +++++++++++++++----------- 8 files changed, 237 insertions(+), 88 deletions(-) create mode 100644 x/finality/types/query.go diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 7d297243c..bd6f813c4 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -820,6 +820,57 @@ paths: format: byte tags: - Query + /babylon/btclightclient/v1/depth/{hash}: + get: + summary: >- + HeaderDepth returns the depth of the header in main chain or error if + the + + block is not found or it exists on fork + operationId: HeaderDepth + responses: + '200': + description: A successful response. + schema: + type: object + properties: + depth: + type: string + format: uint64 + title: >- + QueryMainChainDepthResponse is the response type for the + Query/MainChainDepth RPC + + it contains depth of the block in main chain + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: hash + in: path + required: true + type: string + tags: + - Query /babylon/btclightclient/v1/hashes: get: summary: Hashes retrieves the hashes maintained by the module. @@ -11831,6 +11882,17 @@ definitions: PageResponse page = 2; } description: QueryHashesResponse is response type for the Query/Hashes RPC method. + babylon.btclightclient.v1.QueryHeaderDepthResponse: + type: object + properties: + depth: + type: string + format: uint64 + title: >- + QueryMainChainDepthResponse is the response type for the + Query/MainChainDepth RPC + + it contains depth of the block in main chain babylon.btclightclient.v1.QueryMainChainResponse: type: object properties: diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index e95a33f68..ceb31dce9 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -62,12 +62,22 @@ message QueryListPublicRandomnessResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } + +// QueriedBlockStatus is the status of blocks that the querier wants to query. +enum QueriedBlockStatus { + // NON_FINALIZED means the block is not finalised + NON_FINALIZED = 0; + // FINALIZED means the block is finalized + FINALIZED = 1; + // ANY means the block can be in any status + ANY = 2; +} + // QueryListBlocksRequest is the request type for the // Query/ListBlocks RPC method. message QueryListBlocksRequest { - // finalized indicates whether to only return finalized or non-finalized - // blocks - bool finalized = 1; + // status indicates the status of blocks that the querier wants to query + QueriedBlockStatus status = 1; // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 2; diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 23df77631..80642d75c 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -163,7 +163,7 @@ func (m *QueryBTCValidatorsRequest) GetPagination() *query.PageRequest { // QueryBTCValidatorsResponse is the response type for the // Query/BTCValidators RPC method. type QueryBTCValidatorsResponse struct { - // btc_validators contains all the btc validators + // btc_validators contains all the BTC validators BtcValidators []*BTCValidator `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` @@ -257,7 +257,7 @@ var xxx_messageInfo_QueryPendingBTCDelegationsRequest proto.InternalMessageInfo // QueryPendingBTCDelegationsResponse is the response type for the // Query/PendingBTCDelegations RPC method. type QueryPendingBTCDelegationsResponse struct { - // btc_delegations contains all the queried btc delegations. + // btc_delegations contains all the queried BTC delegations. BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` } @@ -411,7 +411,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { // QueryActiveBTCValidatorsAtHeightRequest is the request type for the // Query/ActiveBTCValidatorsAtHeight RPC method. type QueryActiveBTCValidatorsAtHeightRequest struct { - // height defines at which Babylon height to query the btc validators info. + // height defines at which Babylon height to query the BTC validators info. Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` @@ -469,7 +469,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) GetPagination() *query.PageReq // QueryActiveBTCValidatorsAtHeightResponse is the response type for the // Query/ActiveBTCValidatorsAtHeight RPC method. type QueryActiveBTCValidatorsAtHeightResponse struct { - // btc_validators contains all the queried btc validators. + // btc_validators contains all the queried BTC validators. BtcValidators []*BTCValidatorWithMeta `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` @@ -676,7 +676,7 @@ func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsResponse struct { - // btc_delegations contains all the queried btc delegations. + // btc_delegations contains all the queried BTC delegations. BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index c86f79aa0..b2d4fdb55 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -11,6 +11,8 @@ import ( "github.com/spf13/cobra" ) +const flagQueriedBlockStatus = "queried-block-status" + // GetQueryCmd returns the cli query commands for this module func GetQueryCmd(queryRoute string) *cobra.Command { // Group finality queries under a subcommand @@ -106,13 +108,17 @@ func CmdListBlocks() *cobra.Command { if err != nil { return err } - finalized, err := cmd.Flags().GetBool("finalized") + queriedBlockStatusString, err := cmd.Flags().GetString(flagQueriedBlockStatus) + if err != nil { + return err + } + queriedBlockStatus, err := types.NewQueriedBlockStatus(queriedBlockStatusString) if err != nil { return err } res, err := queryClient.ListBlocks(cmd.Context(), &types.QueryListBlocksRequest{ - Finalized: finalized, + Status: queriedBlockStatus, Pagination: pageReq, }) if err != nil { @@ -125,7 +131,7 @@ func CmdListBlocks() *cobra.Command { flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "list-blocks") - cmd.Flags().Bool("finalized", false, "return finalized or non-finalized blocks") + cmd.Flags().String(flagQueriedBlockStatus, "Any", "Status of the queried blocks (NonFinalized|Finalized|Any)") return cmd } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 77ae2fd95..eacd8adf6 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -48,6 +48,7 @@ func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPu return resp, nil } +// TODO: allow returning all blocks in this API func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksRequest) (*types.QueryListBlocksResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -59,14 +60,16 @@ func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksReques var ib types.IndexedBlock k.cdc.MustUnmarshal(value, &ib) - // return finalized blocks if the request is for finalized blocks - // otherwise only return non-finalized blocks - if (req.Finalized && ib.Finalized) || (!req.Finalized && !ib.Finalized) { + // hit if the queried status matches the block status, or the querier wants blocks in any state + if (req.Status == types.QueriedBlockStatus_FINALIZED && ib.Finalized) || + (req.Status == types.QueriedBlockStatus_NON_FINALIZED && !ib.Finalized) || + (req.Status == types.QueriedBlockStatus_ANY) { if accumulate { ibs = append(ibs, &ib) } return true, nil } + return false, nil }) if err != nil { diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 83f15901f..45a3baacb 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -69,6 +69,7 @@ func FuzzListBlocks(f *testing.F) { numIndexedBlocks := datagen.RandomInt(r, 100) + 1 finalizedIndexedBlocks := make(map[uint64]*types.IndexedBlock) nonFinalizedIndexedBlocks := make(map[uint64]*types.IndexedBlock) + indexedBlocks := make(map[uint64]*types.IndexedBlock) for i := startHeight; i < startHeight+numIndexedBlocks; i++ { ib := &types.IndexedBlock{ Height: i, @@ -81,6 +82,7 @@ func FuzzListBlocks(f *testing.F) { } else { nonFinalizedIndexedBlocks[ib.Height] = ib } + indexedBlocks[ib.Height] = ib // insert to KVStore keeper.SetBlock(ctx, ib) } @@ -91,7 +93,7 @@ func FuzzListBlocks(f *testing.F) { if len(finalizedIndexedBlocks) != 0 { limit := datagen.RandomInt(r, len(finalizedIndexedBlocks)) + 1 req := &types.QueryListBlocksRequest{ - Finalized: true, + Status: types.QueriedBlockStatus_FINALIZED, Pagination: &query.PageRequest{ CountTotal: true, Limit: limit, @@ -110,7 +112,7 @@ func FuzzListBlocks(f *testing.F) { // perform a query to fetch non-finalized blocks and assert consistency limit := datagen.RandomInt(r, len(nonFinalizedIndexedBlocks)) + 1 req := &types.QueryListBlocksRequest{ - Finalized: false, + Status: types.QueriedBlockStatus_NON_FINALIZED, Pagination: &query.PageRequest{ CountTotal: true, Limit: limit, @@ -124,6 +126,23 @@ func FuzzListBlocks(f *testing.F) { require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) } } + + // perform a query to fetch all blocks and assert consistency + limit := datagen.RandomInt(r, len(indexedBlocks)) + 1 + req := &types.QueryListBlocksRequest{ + Status: types.QueriedBlockStatus_ANY, + Pagination: &query.PageRequest{ + CountTotal: true, + Limit: limit, + }, + } + resp3, err := keeper.ListBlocks(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp3.Blocks), int(limit)) // check if pagination takes effect + require.EqualValues(t, resp3.Pagination.Total, len(indexedBlocks)) + for _, actualIB := range resp3.Blocks { + require.Equal(t, indexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + } }) } diff --git a/x/finality/types/query.go b/x/finality/types/query.go new file mode 100644 index 000000000..f6dc32702 --- /dev/null +++ b/x/finality/types/query.go @@ -0,0 +1,20 @@ +package types + +import ( + "fmt" +) + +// NewQueriedBlockStatus takes a human-readable queried block status format and returns our custom enum. +// Options: NonFinalized | Finalized | Any +func NewQueriedBlockStatus(status string) (QueriedBlockStatus, error) { + if status == "NonFinalized" { + return QueriedBlockStatus_NON_FINALIZED, nil + } + if status == "Finalized" { + return QueriedBlockStatus_FINALIZED, nil + } + if status == "Any" { + return QueriedBlockStatus_ANY, nil + } + return QueriedBlockStatus_NON_FINALIZED, fmt.Errorf("invalid queried block status %s", status) +} diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index f1875b71a..67212bc9e 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -31,6 +31,38 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// QueriedBlockStatus is the status of blocks that the querier wants to query. +type QueriedBlockStatus int32 + +const ( + // NON_FINALIZED means the block is not finalised + QueriedBlockStatus_NON_FINALIZED QueriedBlockStatus = 0 + // FINALIZED means the block is finalized + QueriedBlockStatus_FINALIZED QueriedBlockStatus = 1 + // ANY means the block can be in any status + QueriedBlockStatus_ANY QueriedBlockStatus = 2 +) + +var QueriedBlockStatus_name = map[int32]string{ + 0: "NON_FINALIZED", + 1: "FINALIZED", + 2: "ANY", +} + +var QueriedBlockStatus_value = map[string]int32{ + "NON_FINALIZED": 0, + "FINALIZED": 1, + "ANY": 2, +} + +func (x QueriedBlockStatus) String() string { + return proto.EnumName(QueriedBlockStatus_name, int32(x)) +} + +func (QueriedBlockStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{0} +} + // QueryParamsRequest is request type for the Query/Params RPC method. type QueryParamsRequest struct { } @@ -223,9 +255,8 @@ func (m *QueryListPublicRandomnessResponse) GetPagination() *query.PageResponse // QueryListBlocksRequest is the request type for the // Query/ListBlocks RPC method. type QueryListBlocksRequest struct { - // finalized indicates whether to only return finalized or non-finalized - // blocks - Finalized bool `protobuf:"varint,1,opt,name=finalized,proto3" json:"finalized,omitempty"` + // status indicates the status of blocks that the querier wants to query + Status QueriedBlockStatus `protobuf:"varint,1,opt,name=status,proto3,enum=babylon.finality.v1.QueriedBlockStatus" json:"status,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -263,11 +294,11 @@ func (m *QueryListBlocksRequest) XXX_DiscardUnknown() { var xxx_messageInfo_QueryListBlocksRequest proto.InternalMessageInfo -func (m *QueryListBlocksRequest) GetFinalized() bool { +func (m *QueryListBlocksRequest) GetStatus() QueriedBlockStatus { if m != nil { - return m.Finalized + return m.Status } - return false + return QueriedBlockStatus_NON_FINALIZED } func (m *QueryListBlocksRequest) GetPagination() *query.PageRequest { @@ -336,7 +367,7 @@ func (m *QueryListBlocksResponse) GetPagination() *query.PageResponse { // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. type QueryVotesAtHeightRequest struct { - // height defines at which height to query the btc validators. + // height defines at which height to query the BTC validators. Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` } @@ -422,6 +453,7 @@ func (m *QueryVotesAtHeightResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryVotesAtHeightResponse proto.InternalMessageInfo func init() { + proto.RegisterEnum("babylon.finality.v1.QueriedBlockStatus", QueriedBlockStatus_name, QueriedBlockStatus_value) proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.finality.v1.QueryParamsResponse") proto.RegisterType((*QueryListPublicRandomnessRequest)(nil), "babylon.finality.v1.QueryListPublicRandomnessRequest") @@ -436,56 +468,59 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 773 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x4f, 0xe3, 0x46, - 0x14, 0x8e, 0x03, 0xa4, 0x65, 0xa0, 0x3f, 0x34, 0x44, 0x94, 0x06, 0x9a, 0x04, 0xa3, 0x16, 0x54, - 0x2a, 0x0f, 0x09, 0x14, 0xd1, 0x4a, 0x3d, 0x34, 0x52, 0x29, 0x94, 0xa2, 0xba, 0x46, 0xea, 0xa1, - 0x97, 0x68, 0xec, 0x4c, 0x1d, 0x2b, 0x8e, 0xc7, 0xd8, 0x63, 0x2b, 0x29, 0xa2, 0xaa, 0x7a, 0xae, - 0xb4, 0x2b, 0xad, 0xb4, 0x37, 0x2e, 0xfb, 0xd7, 0x70, 0x44, 0xda, 0xcb, 0x8a, 0x03, 0x5a, 0xc1, - 0xfe, 0x21, 0x2b, 0xcf, 0x4c, 0x12, 0xb2, 0x6b, 0x48, 0x76, 0xc5, 0x2d, 0x33, 0xfe, 0xde, 0xf7, - 0xbe, 0xf7, 0xbd, 0xf7, 0x26, 0xa0, 0x64, 0x62, 0xb3, 0xeb, 0x52, 0x0f, 0xfd, 0xe5, 0x78, 0xd8, - 0x75, 0x58, 0x17, 0xc5, 0x15, 0x74, 0x1c, 0x91, 0xa0, 0xab, 0xf9, 0x01, 0x65, 0x14, 0xce, 0x49, - 0x80, 0xd6, 0x03, 0x68, 0x71, 0xa5, 0x90, 0xb7, 0xa9, 0x4d, 0xf9, 0x77, 0x94, 0xfc, 0x12, 0xd0, - 0xc2, 0x92, 0x4d, 0xa9, 0xed, 0x12, 0x84, 0x7d, 0x07, 0x61, 0xcf, 0xa3, 0x0c, 0x33, 0x87, 0x7a, - 0xa1, 0xfc, 0xfa, 0xb5, 0x45, 0xc3, 0x36, 0x0d, 0x91, 0x89, 0x43, 0x22, 0x32, 0xa0, 0xb8, 0x62, - 0x12, 0x86, 0x2b, 0xc8, 0xc7, 0xb6, 0xe3, 0x71, 0xb0, 0xc4, 0x96, 0xd3, 0x54, 0xf9, 0x38, 0xc0, - 0xed, 0x1e, 0x9b, 0x9a, 0x86, 0xe8, 0x4b, 0xe4, 0x18, 0x35, 0x0f, 0xe0, 0xef, 0x49, 0x1e, 0x9d, - 0x07, 0x1a, 0xe4, 0x38, 0x22, 0x21, 0x53, 0x75, 0x30, 0x37, 0x74, 0x1b, 0xfa, 0xd4, 0x0b, 0x09, - 0xfc, 0x0e, 0xe4, 0x44, 0x82, 0x05, 0xa5, 0xac, 0xac, 0xcd, 0x54, 0x17, 0xb5, 0x94, 0xc2, 0x35, - 0x11, 0x54, 0x9b, 0x3c, 0xbf, 0x2a, 0x65, 0x0c, 0x19, 0xa0, 0x3e, 0x52, 0x40, 0x99, 0x53, 0xfe, - 0xea, 0x84, 0x4c, 0x8f, 0x4c, 0xd7, 0xb1, 0x0c, 0xec, 0x35, 0x68, 0xdb, 0x23, 0x61, 0x2f, 0x2d, - 0x5c, 0x01, 0x1f, 0xc7, 0xd8, 0xad, 0x9b, 0xcc, 0xaa, 0xfb, 0xad, 0x7a, 0x93, 0x74, 0x78, 0x9e, - 0x69, 0x63, 0x26, 0xc6, 0x6e, 0x8d, 0x59, 0x7a, 0x6b, 0x8f, 0x74, 0xe0, 0x2e, 0x00, 0x03, 0x2f, - 0x16, 0xb2, 0x5c, 0xc8, 0x57, 0x9a, 0x30, 0x4e, 0x4b, 0x8c, 0xd3, 0x44, 0x6b, 0xa4, 0x71, 0x9a, - 0x8e, 0x6d, 0x22, 0x13, 0x18, 0xb7, 0x22, 0xd5, 0x8b, 0x2c, 0x58, 0xbe, 0x47, 0x91, 0x2c, 0xf9, - 0x99, 0x02, 0x66, 0xfd, 0xc8, 0xac, 0x07, 0xd8, 0x6b, 0xd4, 0xdb, 0xd8, 0x5f, 0x50, 0xca, 0x13, - 0x6b, 0x33, 0xd5, 0xdd, 0xd4, 0xca, 0x47, 0xd2, 0x69, 0x7a, 0x64, 0x26, 0xb7, 0x87, 0xd8, 0xff, - 0xc9, 0x63, 0x41, 0xb7, 0xb6, 0x73, 0x79, 0x55, 0xda, 0xb2, 0x1d, 0xd6, 0x8c, 0x4c, 0xcd, 0xa2, - 0x6d, 0x24, 0x59, 0xad, 0x26, 0x76, 0xbc, 0xde, 0x01, 0xb1, 0xae, 0x4f, 0x42, 0xed, 0xc8, 0x6a, - 0x7a, 0x34, 0x08, 0x24, 0x83, 0x01, 0xfc, 0x3e, 0x15, 0xfc, 0x39, 0xc5, 0x92, 0xd5, 0x91, 0x96, - 0x08, 0x49, 0xb7, 0x3d, 0x29, 0xfc, 0x00, 0x3e, 0x79, 0x43, 0x21, 0xfc, 0x14, 0x4c, 0xb4, 0x48, - 0x97, 0x37, 0x62, 0xd2, 0x48, 0x7e, 0xc2, 0x3c, 0x98, 0x8a, 0xb1, 0x1b, 0x11, 0x9e, 0x68, 0xd6, - 0x10, 0x87, 0xef, 0xb3, 0x3b, 0x8a, 0xfa, 0x0f, 0x98, 0xef, 0x5b, 0x50, 0x73, 0xa9, 0xd5, 0xea, - 0x77, 0x76, 0x09, 0x4c, 0x0b, 0xa3, 0xfe, 0x26, 0x0d, 0xce, 0xf5, 0xa1, 0x31, 0xb8, 0x78, 0xb0, - 0x96, 0x9e, 0x29, 0xe0, 0xb3, 0xb7, 0x04, 0x0c, 0x66, 0xd7, 0xe4, 0x37, 0xb2, 0x83, 0xcb, 0xa9, - 0x1d, 0xdc, 0xf7, 0x1a, 0xa4, 0x43, 0x1a, 0x3c, 0xd6, 0x90, 0x01, 0x0f, 0x66, 0xaf, 0xba, 0x09, - 0x3e, 0xe7, 0xf2, 0xfe, 0xa0, 0x8c, 0x84, 0x3f, 0xb2, 0x3d, 0xe2, 0xd8, 0x4d, 0xd6, 0xb3, 0x68, - 0x1e, 0xe4, 0x9a, 0xfc, 0x42, 0x7a, 0x2d, 0x4f, 0x6a, 0x1b, 0x14, 0xd2, 0x82, 0x64, 0x59, 0xbf, - 0x81, 0x0f, 0xc4, 0xba, 0x88, 0xba, 0x66, 0x6b, 0xdb, 0x97, 0x57, 0xa5, 0xea, 0x78, 0x13, 0x55, - 0xdb, 0xd7, 0x37, 0xb7, 0x36, 0xf4, 0xc8, 0x3c, 0x20, 0x5d, 0x23, 0x67, 0x26, 0x0b, 0x16, 0x56, - 0x9f, 0x4e, 0x81, 0x29, 0x9e, 0x0f, 0xfe, 0xab, 0x80, 0x9c, 0xd8, 0x65, 0xb8, 0x7a, 0xf7, 0xb8, - 0x0f, 0x3d, 0x1c, 0x85, 0xb5, 0xd1, 0x40, 0x21, 0x5c, 0x5d, 0xf9, 0xef, 0xf9, 0xab, 0x27, 0xd9, - 0x2f, 0xe0, 0x22, 0xba, 0xfb, 0x1d, 0x83, 0x97, 0x0a, 0xc8, 0xa7, 0xed, 0x13, 0xfc, 0xf6, 0x5d, - 0xf7, 0x4f, 0xc8, 0xdb, 0x7e, 0xbf, 0xb5, 0x55, 0x8f, 0xb8, 0xd8, 0x43, 0x78, 0x90, 0x2a, 0x36, - 0x69, 0x40, 0x8c, 0x5d, 0xa7, 0x81, 0x19, 0x0d, 0x42, 0x74, 0x32, 0xfc, 0x86, 0x9d, 0x22, 0x9f, - 0xd3, 0xf2, 0x27, 0x44, 0xf0, 0xd6, 0x5d, 0x27, 0x64, 0xf0, 0x7f, 0x05, 0x80, 0xc1, 0xa0, 0xc2, - 0xf5, 0xfb, 0xb5, 0x0d, 0xed, 0x53, 0xe1, 0x9b, 0xf1, 0xc0, 0x63, 0x79, 0x2d, 0xa7, 0xfc, 0x4c, - 0x01, 0x1f, 0x0d, 0xcd, 0x18, 0xd4, 0xee, 0x4e, 0x92, 0x36, 0xc1, 0x05, 0x34, 0x36, 0x5e, 0xea, - 0x5a, 0xe7, 0xba, 0xbe, 0x84, 0x2b, 0xa9, 0xba, 0xe2, 0x24, 0x06, 0x9d, 0x88, 0x35, 0x38, 0xad, - 0xfd, 0x72, 0x7e, 0x5d, 0x54, 0x2e, 0xae, 0x8b, 0xca, 0xcb, 0xeb, 0xa2, 0xf2, 0xf8, 0xa6, 0x98, - 0xb9, 0xb8, 0x29, 0x66, 0x5e, 0xdc, 0x14, 0x33, 0x7f, 0x6e, 0x8c, 0x1a, 0xf7, 0xce, 0x80, 0x97, - 0x4f, 0xbe, 0x99, 0xe3, 0x7f, 0x7e, 0x9b, 0xaf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x02, 0xee, 0x0c, - 0x23, 0xda, 0x07, 0x00, 0x00, + // 824 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0x38, 0x8d, 0xab, 0x4e, 0x92, 0x12, 0xa6, 0x56, 0x09, 0x2e, 0xd8, 0xee, 0x46, 0xd0, + 0xa8, 0x45, 0x3b, 0xb5, 0x53, 0xaa, 0x82, 0x84, 0xaa, 0x58, 0x34, 0xd4, 0xb4, 0x75, 0x97, 0x8d, + 0x84, 0x44, 0x2f, 0xd6, 0xec, 0x7a, 0x58, 0xaf, 0xbc, 0xde, 0xd9, 0xee, 0xce, 0xae, 0x6c, 0x55, + 0x91, 0x10, 0x67, 0x24, 0x90, 0x90, 0xb8, 0xe5, 0x00, 0x7f, 0x4d, 0x8e, 0x91, 0xb8, 0xa0, 0x1c, + 0x22, 0x94, 0xf0, 0x87, 0xa0, 0x9d, 0x19, 0xdb, 0x31, 0xac, 0x63, 0x83, 0x72, 0xdb, 0x99, 0xf9, + 0xde, 0x7b, 0xdf, 0xfb, 0xde, 0x8f, 0x85, 0x15, 0x8b, 0x58, 0x43, 0x8f, 0xf9, 0xf8, 0x5b, 0xd7, + 0x27, 0x9e, 0xcb, 0x87, 0x38, 0xa9, 0xe1, 0xd7, 0x31, 0x0d, 0x87, 0x7a, 0x10, 0x32, 0xce, 0xd0, + 0x0d, 0x05, 0xd0, 0x47, 0x00, 0x3d, 0xa9, 0x95, 0x8a, 0x0e, 0x73, 0x98, 0x78, 0xc7, 0xe9, 0x97, + 0x84, 0x96, 0xde, 0x73, 0x18, 0x73, 0x3c, 0x8a, 0x49, 0xe0, 0x62, 0xe2, 0xfb, 0x8c, 0x13, 0xee, + 0x32, 0x3f, 0x52, 0xaf, 0x77, 0x6d, 0x16, 0xf5, 0x59, 0x84, 0x2d, 0x12, 0x51, 0x19, 0x01, 0x27, + 0x35, 0x8b, 0x72, 0x52, 0xc3, 0x01, 0x71, 0x5c, 0x5f, 0x80, 0x15, 0xb6, 0x9a, 0xc5, 0x2a, 0x20, + 0x21, 0xe9, 0x8f, 0xbc, 0x69, 0x59, 0x88, 0x31, 0x45, 0x81, 0xd1, 0x8a, 0x10, 0x7d, 0x95, 0xc6, + 0x31, 0x84, 0xa1, 0x49, 0x5f, 0xc7, 0x34, 0xe2, 0x9a, 0x01, 0x6f, 0x4c, 0xdd, 0x46, 0x01, 0xf3, + 0x23, 0x8a, 0x3e, 0x81, 0x05, 0x19, 0x60, 0x03, 0x54, 0xc1, 0xd6, 0x4a, 0xfd, 0x96, 0x9e, 0x91, + 0xb8, 0x2e, 0x8d, 0x1a, 0x57, 0x0e, 0x4f, 0x2a, 0x39, 0x53, 0x19, 0x68, 0x3f, 0x02, 0x58, 0x15, + 0x2e, 0x9f, 0xbb, 0x11, 0x37, 0x62, 0xcb, 0x73, 0x6d, 0x93, 0xf8, 0x1d, 0xd6, 0xf7, 0x69, 0x34, + 0x0a, 0x8b, 0x36, 0xe1, 0xf5, 0x84, 0x78, 0x6d, 0x8b, 0xdb, 0xed, 0xa0, 0xd7, 0xee, 0xd2, 0x81, + 0x88, 0x73, 0xcd, 0x5c, 0x49, 0x88, 0xd7, 0xe0, 0xb6, 0xd1, 0x7b, 0x4a, 0x07, 0x68, 0x17, 0xc2, + 0x89, 0x16, 0x1b, 0x79, 0x41, 0xe4, 0x43, 0x5d, 0x0a, 0xa7, 0xa7, 0xc2, 0xe9, 0xb2, 0x34, 0x4a, + 0x38, 0xdd, 0x20, 0x0e, 0x55, 0x01, 0xcc, 0x73, 0x96, 0xda, 0x51, 0x1e, 0xde, 0xbe, 0x80, 0x91, + 0x4a, 0xf9, 0x37, 0x00, 0x57, 0x83, 0xd8, 0x6a, 0x87, 0xc4, 0xef, 0xb4, 0xfb, 0x24, 0xd8, 0x00, + 0xd5, 0xa5, 0xad, 0x95, 0xfa, 0x6e, 0x66, 0xe6, 0x73, 0xdd, 0xe9, 0x46, 0x6c, 0xa5, 0xb7, 0x2f, + 0x48, 0xf0, 0xc4, 0xe7, 0xe1, 0xb0, 0xf1, 0xe8, 0xf8, 0xa4, 0xf2, 0xc0, 0x71, 0x79, 0x37, 0xb6, + 0x74, 0x9b, 0xf5, 0xb1, 0xf2, 0x6a, 0x77, 0x89, 0xeb, 0x8f, 0x0e, 0x98, 0x0f, 0x03, 0x1a, 0xe9, + 0x7b, 0x76, 0xd7, 0x67, 0x61, 0xa8, 0x3c, 0x98, 0x30, 0x18, 0xbb, 0x42, 0x5f, 0x64, 0x48, 0x72, + 0x67, 0xae, 0x24, 0x92, 0xd2, 0x79, 0x4d, 0x4a, 0x9f, 0xc1, 0xb7, 0xfe, 0xc1, 0x10, 0xad, 0xc3, + 0xa5, 0x1e, 0x1d, 0x8a, 0x42, 0x5c, 0x31, 0xd3, 0x4f, 0x54, 0x84, 0xcb, 0x09, 0xf1, 0x62, 0x2a, + 0x02, 0xad, 0x9a, 0xf2, 0xf0, 0x69, 0xfe, 0x11, 0xd0, 0x7e, 0x05, 0xf0, 0xe6, 0x58, 0x83, 0x86, + 0xc7, 0xec, 0xde, 0xb8, 0xb4, 0x8f, 0x61, 0x21, 0xe2, 0x84, 0xc7, 0xb2, 0x75, 0xae, 0xd7, 0xef, + 0xcc, 0x14, 0xd0, 0xa5, 0x1d, 0x61, 0xba, 0x27, 0xe0, 0xa6, 0x32, 0xbb, 0xb4, 0xb2, 0x1f, 0x00, + 0xf8, 0xce, 0xbf, 0x38, 0x4e, 0xfa, 0xdb, 0x12, 0x37, 0xaa, 0xca, 0xb7, 0x33, 0x49, 0x36, 0xfd, + 0x0e, 0x1d, 0x28, 0x92, 0xa6, 0x32, 0xb8, 0xb4, 0x12, 0x68, 0xdb, 0xf0, 0x5d, 0x41, 0xef, 0x6b, + 0xc6, 0x69, 0xb4, 0xc3, 0x9f, 0x52, 0xd7, 0xe9, 0xf2, 0x91, 0x8a, 0x37, 0x61, 0xa1, 0x2b, 0x2e, + 0x54, 0x3d, 0xd4, 0x49, 0xeb, 0xc3, 0x52, 0x96, 0x91, 0x4a, 0xeb, 0x25, 0xbc, 0x2a, 0x47, 0x4a, + 0xe6, 0xb5, 0xda, 0x78, 0x78, 0x7c, 0x52, 0xa9, 0x2f, 0xd6, 0x75, 0x8d, 0xa6, 0xb1, 0xfd, 0xe0, + 0xbe, 0x11, 0x5b, 0xcf, 0xe8, 0xd0, 0x2c, 0x58, 0xe9, 0x10, 0x46, 0x77, 0x1f, 0xcb, 0xa5, 0x31, + 0x5d, 0x29, 0xf4, 0x36, 0x5c, 0x6b, 0xbd, 0x6c, 0xb5, 0x77, 0x9b, 0xad, 0x9d, 0xe7, 0xcd, 0x57, + 0x4f, 0x3e, 0x5f, 0xcf, 0xa1, 0x35, 0x78, 0x6d, 0x72, 0x04, 0xe8, 0x2a, 0x5c, 0xda, 0x69, 0x7d, + 0xb3, 0x9e, 0xaf, 0xff, 0xb2, 0x0c, 0x97, 0x05, 0x61, 0xf4, 0x1d, 0x80, 0x05, 0xb9, 0x30, 0xd0, + 0xec, 0x96, 0x98, 0xde, 0x4e, 0xa5, 0xad, 0xf9, 0x40, 0x99, 0xb9, 0xb6, 0xf9, 0xfd, 0xef, 0x7f, + 0xfd, 0x9c, 0x7f, 0x1f, 0xdd, 0xc2, 0xb3, 0x97, 0x25, 0x3a, 0x06, 0xb0, 0x98, 0x35, 0xb4, 0xe8, + 0xe3, 0xff, 0x3a, 0xe4, 0x92, 0xde, 0xc3, 0xff, 0xb7, 0x1b, 0xb4, 0x3d, 0x41, 0xf6, 0x05, 0x7a, + 0x96, 0x49, 0x36, 0xad, 0x60, 0x42, 0x3c, 0xb7, 0x43, 0x38, 0x0b, 0x23, 0xfc, 0x66, 0x7a, 0x51, + 0xee, 0xe3, 0x40, 0xb8, 0x15, 0x7b, 0x4a, 0xfa, 0x6d, 0x7b, 0x6e, 0xc4, 0xd1, 0x0f, 0x00, 0xc2, + 0x49, 0xa7, 0xa3, 0x7b, 0x17, 0x73, 0x9b, 0x9a, 0xd9, 0xd2, 0x47, 0x8b, 0x81, 0x17, 0xd2, 0x5a, + 0x8d, 0xc9, 0x01, 0x80, 0x6b, 0x53, 0x4d, 0x8a, 0xf4, 0xd9, 0x41, 0xb2, 0x46, 0xa0, 0x84, 0x17, + 0xc6, 0x2b, 0x5e, 0xf7, 0x04, 0xaf, 0x0f, 0xd0, 0x66, 0x26, 0xaf, 0x24, 0xb5, 0xc1, 0x6f, 0xe4, + 0x1c, 0xed, 0x37, 0xbe, 0x3c, 0x3c, 0x2d, 0x83, 0xa3, 0xd3, 0x32, 0xf8, 0xf3, 0xb4, 0x0c, 0x7e, + 0x3a, 0x2b, 0xe7, 0x8e, 0xce, 0xca, 0xb9, 0x3f, 0xce, 0xca, 0xb9, 0x57, 0xf7, 0xe7, 0xcd, 0xcb, + 0x60, 0xe2, 0x57, 0x8c, 0x8e, 0x55, 0x10, 0x7f, 0xd8, 0xed, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, + 0x8e, 0x6e, 0x6c, 0x67, 0x3f, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -506,7 +541,7 @@ type QueryClient interface { ListPublicRandomness(ctx context.Context, in *QueryListPublicRandomnessRequest, opts ...grpc.CallOption) (*QueryListPublicRandomnessResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) - // VotesAtHeight queries btc validators who have signed the block at given height. + // VotesAtHeight queries BTC validators who have signed the block at given height. VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) } @@ -562,7 +597,7 @@ type QueryServer interface { ListPublicRandomness(context.Context, *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(context.Context, *QueryListBlocksRequest) (*QueryListBlocksResponse, error) - // VotesAtHeight queries btc validators who have signed the block at given height. + // VotesAtHeight queries BTC validators who have signed the block at given height. VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) } @@ -873,13 +908,8 @@ func (m *QueryListBlocksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x12 } - if m.Finalized { - i-- - if m.Finalized { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + if m.Status != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Status)) i-- dAtA[i] = 0x8 } @@ -1080,8 +1110,8 @@ func (m *QueryListBlocksRequest) Size() (n int) { } var l int _ = l - if m.Finalized { - n += 2 + if m.Status != 0 { + n += 1 + sovQuery(uint64(m.Status)) } if m.Pagination != nil { l = m.Pagination.Size() @@ -1626,9 +1656,9 @@ func (m *QueryListBlocksRequest) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Finalized", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } - var v int + m.Status = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowQuery @@ -1638,12 +1668,11 @@ func (m *QueryListBlocksRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + m.Status |= QueriedBlockStatus(b&0x7F) << shift if b < 0x80 { break } } - m.Finalized = bool(v != 0) case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) From 760dd151029faa229fe4c35ee8b27de15fa4a2d4 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Thu, 27 Jul 2023 12:43:08 +0200 Subject: [PATCH 044/202] Bump our fork of ibc to v7.2.0 (#38) --- go.mod | 40 ++++++++++++---------- go.sum | 106 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 83 insertions(+), 63 deletions(-) diff --git a/go.mod b/go.mod index 36346ef0d..5d4414b98 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,18 @@ module github.com/babylonchain/babylon require ( github.com/CosmWasm/wasmd v0.40.0 github.com/btcsuite/btcd v0.23.4 - github.com/cometbft/cometbft v0.37.1 - github.com/cometbft/cometbft-db v0.7.0 - github.com/cosmos/cosmos-sdk v0.47.2 + github.com/cometbft/cometbft v0.37.2 + github.com/cometbft/cometbft-db v0.8.0 + github.com/cosmos/cosmos-sdk v0.47.3 github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 // indirect github.com/spf13/cast v1.5.1 - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.3 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 github.com/supranational/blst v0.3.8 google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.55.0 @@ -34,7 +34,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.3 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.2 - github.com/cosmos/gogoproto v1.4.8 + github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/ibc-go/v7 v7.0.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/golang/mock v1.6.0 @@ -93,13 +93,13 @@ require ( github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/petermattis/goid v0.0.0-20221215004737-a150e88a970d // indirect + github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.15.0 github.com/prometheus/client_model v0.3.0 // indirect @@ -108,7 +108,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rs/cors v1.8.3 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 github.com/subosito/gotenv v1.4.2 // indirect @@ -116,7 +116,7 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.7.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect @@ -132,6 +132,7 @@ require ( cloud.google.com/go/storage v1.29.0 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.3 // indirect + cosmossdk.io/log v1.1.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -143,7 +144,7 @@ require ( github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab // indirect + github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect github.com/creachadair/taskgroup v0.4.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -160,10 +161,11 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.3 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect + github.com/googleapis/gax-go/v2 v2.8.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -171,16 +173,18 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/linxGnu/grocksdb v1.7.16 // indirect github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/runc v1.1.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/rs/zerolog v1.29.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -188,13 +192,13 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/zondax/ledger-go v0.14.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.114.0 // indirect + google.golang.org/api v0.122.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -207,7 +211,7 @@ replace ( // slay the dragonberry github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/ibc-go/v7 => github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf + github.com/cosmos/ibc-go/v7 => github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 diff --git a/go.sum b/go.sum index beedb10c9..070e1094e 100644 --- a/go.sum +++ b/go.sum @@ -196,6 +196,8 @@ cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= +cosmossdk.io/log v1.1.0 h1:v0ogPHYeTzPcBTcPR1A3j1hkei4pZama8kz8LKlCMv0= +cosmossdk.io/log v1.1.0/go.mod h1:6zjroETlcDs+mm62gd8Ig7mZ+N+fVOZS91V17H+M4N4= cosmossdk.io/math v1.0.1 h1:Qx3ifyOPaMLNH/89WeZFH268yCvU4xEcnPLu3sJqPPg= cosmossdk.io/math v1.0.1/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= @@ -250,8 +252,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf h1:NJU3YuruPqV8w6/y45Zsb8FudcWSkTBugdpTT7kJmjw= -github.com/babylonchain/ibc-go/v7 v7.0.0-20230324085744-4d6a0d2c0fcf/go.mod h1:BFh8nKWjr5zeR2OZfhkzdgDzj1+KjRn3aJLpwapStj8= +github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61 h1:0NPV8yfawKAYrw96b2ZR70QHIAwB1QcKWyf2BWcRvmU= +github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61/go.mod h1:OOcjKIRku/j1Xs1RgKK0yvKRrJ5iFuZYMetR1n3yMlc= 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= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -290,6 +292,7 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -334,10 +337,10 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= -github.com/cometbft/cometbft v0.37.1 h1:KLxkQTK2hICXYq21U2hn1W5hOVYUdQgDQ1uB+90xPIg= -github.com/cometbft/cometbft v0.37.1/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= -github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= -github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= +github.com/cometbft/cometbft v0.37.2 h1:XB0yyHGT0lwmJlFmM4+rsRnczPlHoAKFX6K8Zgc2/Jc= +github.com/cometbft/cometbft v0.37.2/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= +github.com/cometbft/cometbft-db v0.8.0 h1:vUMDaH3ApkX8m0KZvOFFy9b5DZHBAjsnEuo9AKVZpjo= +github.com/cometbft/cometbft-db v0.8.0/go.mod h1:6ASCP4pfhmrCBpfk01/9E1SI29nD3HfVHrY4PG8x5c0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= @@ -346,13 +349,14 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= github.com/cosmos/cosmos-proto v1.0.0-beta.2 h1:X3OKvWgK9Gsejo0F1qs5l8Qn6xJV/AzgIWR2wZ8Nua8= github.com/cosmos/cosmos-proto v1.0.0-beta.2/go.mod h1:+XRCLJ14pr5HFEHIUcn51IKXD1Fy3rkEQqt4WqmN4V0= -github.com/cosmos/cosmos-sdk v0.47.2 h1:9rSriCoiJD+4F+tEDobyM8V7HF5BtY5Ef4VYNig96s0= -github.com/cosmos/cosmos-sdk v0.47.2/go.mod h1:zYzgI8w8hhotXTSoGbbSOAKfpJTx4wOy4XgbaKhtRtc= +github.com/cosmos/cosmos-sdk v0.47.3 h1:r0hGmZoAzP2D+MaPaFGHwAaTdFQq3pNpHaUp1BsffbM= +github.com/cosmos/cosmos-sdk v0.47.3/go.mod h1:c4OfLdAykA9zsj1CqrxBRqXzVz48I++JSvIMPSPcEmk= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= @@ -361,12 +365,12 @@ github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4x github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= -github.com/cosmos/gogoproto v1.4.8 h1:BrHKc6WFZt8+jRV71vKSQE+JrfF+JAnzrKo2VP7wIZ4= -github.com/cosmos/gogoproto v1.4.8/go.mod h1:hnb0DIEWTv+wdNzNcqus5xCQXq5+CXauq1FJuurRfVY= +github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= +github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= -github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab h1:I9ialKTQo7248V827Bba4OuKPmk+FPzmTVHsLXaIJWw= -github.com/cosmos/ics23/go v0.9.1-0.20221207100636-b1abd8678aab/go.mod h1:2CwqasX5dSD7Hbp/9b6lhK6BwoBDCBldx7gPKRukR60= +github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= +github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8= github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= @@ -441,9 +445,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= @@ -480,10 +481,10 @@ github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -599,6 +600,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -619,8 +622,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -703,13 +706,12 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -757,14 +759,16 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.7.16 h1:Q2co1xrpdkr5Hx3Fp+f+f7fRGhQFQhvi/+226dtLmA8= +github.com/linxGnu/grocksdb v1.7.16/go.mod h1:JkS7pl5qWpGpuVb3bPqTz8nC12X3YtPZT+Xq7+QfQo4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -772,11 +776,16 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -866,12 +875,12 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/petermattis/goid v0.0.0-20221215004737-a150e88a970d h1:htwtWgtQo8YS6JFWWi2DNgY0RwSGJ1ruMoxY6CUUclk= -github.com/petermattis/goid v0.0.0-20221215004737-a150e88a970d/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= +github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -928,6 +937,9 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -955,15 +967,15 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -972,8 +984,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -992,8 +1004,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.8 h1:glwLF4oBRSJOTr05lRBgNwGQST0ndP2wg29fSeTRKCY= @@ -1001,8 +1014,6 @@ github.com/supranational/blst v0.3.8/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= -github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= @@ -1012,8 +1023,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -1080,9 +1091,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1094,8 +1106,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1173,6 +1185,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1308,6 +1321,7 @@ golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1333,6 +1347,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1349,6 +1364,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -1478,8 +1494,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From ab12294499b6e50e937a220f7029f386e38f5f5e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 27 Jul 2023 22:51:59 +0800 Subject: [PATCH 045/202] e2e tests for committing public randomness and submitting finality signature (#39) --- test/e2e/btc_staking_e2e_test.go | 132 +++++++++++++++--- .../configurer/chain/commands_btcstaking.go | 5 +- test/e2e/configurer/chain/queries.go | 19 ++- .../configurer/chain/queries_btcstaking.go | 55 +++++++- x/finality/keeper/grpc_query.go | 2 +- 5 files changed, 187 insertions(+), 26 deletions(-) diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 4387a64f2..7164b5f68 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -5,6 +5,7 @@ import ( "math/rand" "time" + "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/test/e2e/configurer" "github.com/babylonchain/babylon/test/e2e/initialization" "github.com/babylonchain/babylon/test/e2e/util" @@ -12,12 +13,28 @@ import ( bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + ftypes "github.com/babylonchain/babylon/x/finality/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" ) +var ( + r = rand.New(rand.NewSource(time.Now().Unix())) + // BTC validator + valSK, _, _ = datagen.GenRandomBTCKeyPair(r) + btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) + // BTC delegation + delBabylonSK, delBabylonPK, _ = datagen.GenRandomSecp256k1KeyPair(r) + delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) + // jury + jurySK, _ = btcec.PrivKeyFromBytes( + []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + ) +) + type BTCStakingTestSuite struct { suite.Suite @@ -46,9 +63,8 @@ func (s *BTCStakingTestSuite) TearDownSuite() { } // TestCreateBTCValidatorAndDelegation is an end-to-end test for -// user story 1/2: user creates BTC validator and BTC delegation, then -// jury approves the BTC delegation -func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { +// user story 1: user creates BTC validator and BTC delegation +func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) @@ -57,11 +73,6 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { /* create a random BTC validator on Babylon */ - // generate a random BTC validator - r := rand.New(rand.NewSource(time.Now().Unix())) - btcVal, err := datagen.GenRandomBTCValidator(r) - s.NoError(err) - // create this BTC validator nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop) // wait for a block so that above txs take effect @@ -73,14 +84,10 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { s.Equal(util.Cdc.MustMarshal(btcVal), util.Cdc.MustMarshal(actualBtcVals[0])) /* - create a random BTC delegation under this bTC validator + create a random BTC delegation under this BTC validator */ // BTC staking params, BTC delegation key pairs and PoP params := nonValidatorNode.QueryBTCStakingParams() - delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) - s.NoError(err) - delBTCSK, delBTCPK, err := datagen.GenRandomBTCKeyPair(r) - s.NoError(err) pop, err := bstypes.NewPoP(delBabylonSK, delBTCSK) s.NoError(err) // generate staking tx and slashing tx @@ -127,12 +134,28 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_PENDING) s.Len(pendingDels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) +} + +// Test2SubmitJurySignature is an end-to-end test for user +// story 2: jury approves the BTC delegation +func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // get last BTC delegation + pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_PENDING) + s.Len(pendingDels, 1) + btcDel := pendingDels[0] + slashingTx := btcDel.SlashingTx + stakingTx := btcDel.StakingTx + stakingMsgTx, err := stakingTx.ToMsgTx() + s.NoError(err) /* generate and insert new jury signature, in order to activate the BTC delegation */ - jurySKBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - jurySK, _ := btcec.PrivKeyFromBytes(jurySKBytes) jurySig, err := slashingTx.Sign( stakingMsgTx, stakingTx.StakingScript, @@ -146,7 +169,80 @@ func (s *BTCStakingTestSuite) TestCreateBTCValidatorAndDelegation() { nonValidatorNode.WaitForNextBlock() // query the existence of BTC delegation and assert equivalence - actualDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_ACTIVE) - s.Len(actualDels, 1) - s.Equal(delBTCPK.SerializeCompressed()[1:], actualDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + activeDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_ACTIVE) + s.Len(activeDels, 1) + s.Equal(delBTCPK.SerializeCompressed()[1:], activeDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + + // ensure BTC staking is activated + activatedHeight := nonValidatorNode.QueryActivatedHeight() + s.Positive(activatedHeight) + // ensure BTC validator has voting power at activated height + currentBtcTip, err := nonValidatorNode.QueryTip() + s.NoError(err) + activeBTCVals := nonValidatorNode.QueryActiveBTCValidatorsAtHeight(activatedHeight) + s.Len(activeBTCVals, 1) + s.Equal(activeBTCVals[0].VotingPower, activeDels[0].VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) +} + +// Test2CommitPublicRandomnessAndSubmitFinalitySignature is an end-to-end +// test for user story 3: BTC validator commits public randomness and submits +// finality signature, such that blocks can be finalised. +func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignature() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // get activated height + activatedHeight := nonValidatorNode.QueryActivatedHeight() + s.Positive(activatedHeight) + + /* + commit a number of public randomness since activatedHeight + */ + // commit public randomness list + srList, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, valSK, activatedHeight, 100) + s.NoError(err) + nonValidatorNode.CommitPubRandList( + msgCommitPubRandList.ValBtcPk, + msgCommitPubRandList.StartHeight, + msgCommitPubRandList.PubRandList, + msgCommitPubRandList.Sig, + ) + + // ensure public randomness list is eventually committed + nonValidatorNode.WaitForNextBlock() + var pubRandMap map[uint64]*bbn.SchnorrPubRand + s.Eventually(func() bool { + pubRandMap = nonValidatorNode.QueryListPublicRandomness(btcVal.BtcPk) + return len(pubRandMap) > 0 + }, time.Minute, time.Second*5) + s.Equal(pubRandMap[activatedHeight].MustMarshal(), msgCommitPubRandList.PubRandList[0].MustMarshal()) + + /* + submit finality signature + */ + // get block to vote + blockToVote, err := nonValidatorNode.QueryBlock(int64(activatedHeight)) + s.NoError(err) + msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), blockToVote.LastCommitHash...) + // generate EOTS signature + sig, err := eots.Sign(valSK, srList[0], msgToSign) + s.NoError(err) + eotsSig := bbn.NewSchnorrEOTSSigFromModNScalar(sig) + // submit finality signature + nonValidatorNode.AddFinalitySig(btcVal.BtcPk, activatedHeight, blockToVote.LastCommitHash, eotsSig) + + // ensure vote is eventually casted + nonValidatorNode.WaitForNextBlock() + var votes []bbn.BIP340PubKey + s.Eventually(func() bool { + votes = nonValidatorNode.QueryVotesAtHeight(activatedHeight) + return len(votes) > 0 + }, time.Minute, time.Second*5) + s.Equal(votes[0].MarshalHex(), btcVal.BtcPk.MarshalHex()) + // once the vote is castedm, ensure block is finalised + finalizedBlocks := nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) + s.NotEmpty(finalizedBlocks) + s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) } diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index d4ca5b525..2a0449ca8 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -96,6 +96,9 @@ func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight u // specify used key cmd = append(cmd, "--from=val") + // gas + cmd = append(cmd, "--gas=auto", "--gas-prices=1ubbn", "--gas-adjustment=1.3") + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully committed public randomness list") @@ -109,7 +112,7 @@ func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint blockLchHex := hex.EncodeToString(blockLch) finalitySigHex := finalitySig.ToHexStr() - cmd := []string{"babylond", "tx", "finality", "add-finality-sig", valBTCPKHex, blockHeightStr, blockLchHex, finalitySigHex, "--from=val"} + cmd := []string{"babylond", "tx", "finality", "add-finality-sig", valBTCPKHex, blockHeightStr, blockLchHex, finalitySigHex, "--from=val", "--gas=500000"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added finality signature") diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index 0c49feee4..dc0ab768b 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -14,11 +14,6 @@ import ( sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/types/query" - tmabcitypes "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/require" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/babylonchain/babylon/test/e2e/util" blc "github.com/babylonchain/babylon/x/btclightclient/types" @@ -26,6 +21,11 @@ import ( etypes "github.com/babylonchain/babylon/x/epoching/types" mtypes "github.com/babylonchain/babylon/x/monitor/types" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" + tmabcitypes "github.com/cometbft/cometbft/abci/types" + tmtypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" ) func (n *NodeConfig) QueryGRPCGateway(path string, queryParams url.Values) ([]byte, error) { @@ -92,6 +92,15 @@ func (n *NodeConfig) QuerySupplyOf(denom string) (sdkmath.Int, error) { return supplyResp.Amount.Amount, nil } +// QueryBlock gets block at a specific height +func (n *NodeConfig) QueryBlock(height int64) (*tmtypes.Block, error) { + block, err := n.rpcClient.Block(context.Background(), &height) + if err != nil { + return nil, err + } + return block.Block, nil +} + // QueryHashFromBlock gets block hash at a specific height. Otherwise, error. func (n *NodeConfig) QueryHashFromBlock(height int64) (string, error) { block, err := n.rpcClient.Block(context.Background(), &height) diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index ca0008454..842ebedac 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -2,10 +2,13 @@ package chain import ( "fmt" + "net/url" + "github.com/babylonchain/babylon/test/e2e/util" + bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + ftypes "github.com/babylonchain/babylon/x/finality/types" "github.com/stretchr/testify/require" - "net/url" ) func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { @@ -55,3 +58,53 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string, status bstype return resp.BtcDelegations } + +func (n *NodeConfig) QueryActivatedHeight() uint64 { + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/activated_height", url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryActivatedHeightResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.Height +} + +// TODO: pagination support +func (n *NodeConfig) QueryListPublicRandomness(valBTCPK *bbn.BIP340PubKey) map[uint64]*bbn.SchnorrPubRand { + path := fmt.Sprintf("/babylon/finality/v1/btc_validators/%s/public_randomness_list", valBTCPK.MarshalHex()) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp ftypes.QueryListPublicRandomnessResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.PubRandMap +} + +func (n *NodeConfig) QueryVotesAtHeight(height uint64) []bbn.BIP340PubKey { + path := fmt.Sprintf("/babylon/finality/v1/votes/%d", height) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp ftypes.QueryVotesAtHeightResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.BtcPks +} + +// TODO: pagination support +func (n *NodeConfig) QueryListBlocks(status ftypes.QueriedBlockStatus) []*ftypes.IndexedBlock { + values := url.Values{} + values.Set("status", fmt.Sprintf("%d", status)) + bz, err := n.QueryGRPCGateway("/babylon/finality/v1/blocks", values) + require.NoError(n.t, err) + + var resp ftypes.QueryListBlocksResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.Blocks +} diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index eacd8adf6..69bf86443 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -89,9 +89,9 @@ func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeight } sdkCtx := sdk.UnwrapSDKContext(ctx) - var btcPks []bbn.BIP340PubKey // get the sig set of babylon block at given height + btcPks := []bbn.BIP340PubKey{} sigSet := k.GetSigSet(sdkCtx, req.Height) for pkHex := range sigSet { pk, err := bbn.NewBIP340PubKeyFromHex(pkHex) From 12ec39a6bcaecdeb7c103e48b72d6c9c4404650e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 28 Jul 2023 11:04:53 +0800 Subject: [PATCH 046/202] chore: fix typo in Docker Makefile (#40) --- contrib/images/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/images/Makefile b/contrib/images/Makefile index 711077c7e..39d716bb5 100644 --- a/contrib/images/Makefile +++ b/contrib/images/Makefile @@ -7,4 +7,4 @@ babylond: babylond-rmi babylond-rmi: docker rmi babylonchain/babylond 2>/dev/null; true -.PHONY: all babylondbabylond-rmi +.PHONY: all babylond babylond-rmi From 71e9d0f49c35f7e3eb6b3beb561df253c5eed31c Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 31 Jul 2023 10:20:38 +0800 Subject: [PATCH 047/202] btcstaking: remove slashed BTC validator from validator set (#41) --- proto/babylon/btcstaking/v1/btcstaking.proto | 2 + x/btcstaking/keeper/btc_validators.go | 15 ++ x/btcstaking/keeper/voting_power_table.go | 10 ++ .../keeper/voting_power_table_test.go | 43 +++++- x/btcstaking/types/btcstaking.pb.go | 131 ++++++++++++------ x/btcstaking/types/errors.go | 17 +-- x/finality/keeper/msg_server.go | 12 +- x/finality/keeper/msg_server_test.go | 2 + x/finality/types/expected_keepers.go | 1 + x/finality/types/mocked_keepers.go | 14 ++ 10 files changed, 191 insertions(+), 56 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 2fa85c9c5..d4a77ab46 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -15,6 +15,8 @@ message BTCValidator { bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; + // slashed indicates whether the BTC validator is slashed or not + bool slashed = 4; } // BTCValidatorWithMeta wraps the BTCValidator with meta data. diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 87b0a17c4..40b361d5d 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -31,6 +31,21 @@ func (k Keeper) GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*types.BTCVal return &btcVal, nil } +// SlashBTCValidator slashes a BTC validator with the given PK +// A slashed BTC validator will not have voting power +func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { + btcVal, err := k.GetBTCValidator(ctx, valBTCPK) + if err != nil { + return err + } + if btcVal.Slashed { + return types.ErrBTCValAlreadySlashed + } + btcVal.Slashed = true + k.SetBTCValidator(ctx, btcVal) + return nil +} + // btcValidatorStore returns the KVStore of the BTC validator set // prefix: BTCValidatorKey // key: Bitcoin secp256k1 PK diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index a8ec46a47..87b3106a7 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -27,6 +27,16 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { defer btcValIter.Close() for ; btcValIter.Valid(); btcValIter.Next() { valBTCPK := btcValIter.Key() + btcVal, err := k.GetBTCValidator(ctx, valBTCPK) + if err != nil { + // failed to get a BTC validator with voting power is a programming error + panic(err) + } + if btcVal.Slashed { + // slashed BTC validator is removed from BTC validator set + continue + } + valPower := uint64(0) // iterate all BTC delegations under this validator diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 77f0c247f..c602e8c32 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -29,7 +29,7 @@ func FuzzVotingPowerTable(f *testing.F) { // generate a random batch of validators btcVals := []*types.BTCValidator{} - numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 1 + numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) for i := uint64(0); i < numBTCVals; i++ { btcVal, err := datagen.GenRandomBTCValidator(r) @@ -91,7 +91,44 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) require.Equal(t, babylonHeight, activatedHeight) - // Case 3: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + // Case 3: move to 2nd BTC block and slash a random BTC validator, + // then assert the slashed BTC validator does not have voting power + babylonHeight += datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1) + // slash a random BTC validator + slashedIdx := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) + slashedVal := btcVals[slashedIdx] + err = keeper.SlashBTCValidator(ctx, slashedVal.BtcPk.MustMarshal()) + require.NoError(t, err) + // index height and record power table + keeper.IndexBTCHeight(ctx) + keeper.RecordVotingPowerTable(ctx) + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + if i == slashedIdx { + require.Zero(t, power) + } else { + require.Equal(t, uint64(numBTCDels), power) + } + } + for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + require.Zero(t, power) + } + + // also, get voting power table and assert consistency + powerTable = keeper.GetVotingPowerTable(ctx, babylonHeight) + require.NotNil(t, powerTable) + for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + if i == slashedIdx { + require.Zero(t, power) + } + require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power) + } + + // Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) @@ -102,7 +139,7 @@ func FuzzVotingPowerTable(f *testing.F) { require.Zero(t, power) } - // the activation height should be same as befofre + // the activation height should be same as before activatedHeight2, err := keeper.GetBTCStakingActivatedHeight(ctx) require.NoError(t, err) require.Equal(t, activatedHeight, activatedHeight2) diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 768f0045c..3f1cfd071 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -66,6 +66,8 @@ type BTCValidator struct { BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` + // slashed indicates whether the BTC validator is slashed or not + Slashed bool `protobuf:"varint,4,opt,name=slashed,proto3" json:"slashed,omitempty"` } func (m *BTCValidator) Reset() { *m = BTCValidator{} } @@ -115,6 +117,13 @@ func (m *BTCValidator) GetPop() *ProofOfPossession { return nil } +func (m *BTCValidator) GetSlashed() bool { + if m != nil { + return m.Slashed + } + return false +} + // BTCValidatorWithMeta wraps the BTCValidator with meta data. type BTCValidatorWithMeta struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC validator @@ -476,51 +485,52 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 697 bytes of a gzipped FileDescriptorProto + // 712 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xdd, 0x4e, 0x13, 0x41, - 0x14, 0xee, 0x96, 0xd2, 0x9f, 0xd3, 0x42, 0x60, 0x04, 0xd3, 0x60, 0x6c, 0xa1, 0x89, 0x86, 0x78, - 0xb1, 0x2b, 0x45, 0x48, 0xd4, 0x44, 0x63, 0xa1, 0xd1, 0x46, 0xc1, 0xcd, 0x6e, 0x83, 0xc6, 0x0b, - 0x9b, 0xd9, 0xed, 0xb2, 0x1d, 0xbb, 0xec, 0x6c, 0x76, 0xa6, 0xb5, 0x7d, 0x03, 0x2f, 0x7d, 0x04, - 0x9f, 0xc3, 0x27, 0x30, 0xf1, 0x86, 0x4b, 0xe5, 0x82, 0x18, 0x78, 0x11, 0xb3, 0xb3, 0xd3, 0xa6, - 0x80, 0x86, 0x18, 0xf4, 0xc6, 0xbb, 0x33, 0xe7, 0xe7, 0xdb, 0xf3, 0x7d, 0xe7, 0x9c, 0x2c, 0xdc, - 0xb6, 0xb0, 0x35, 0xf4, 0xa8, 0xaf, 0x59, 0xdc, 0x66, 0x1c, 0x77, 0x89, 0xef, 0x6a, 0xfd, 0xb5, - 0x89, 0x97, 0x1a, 0x84, 0x94, 0x53, 0xb4, 0x28, 0xf3, 0xd4, 0x89, 0x48, 0x7f, 0x6d, 0x69, 0xc1, - 0xa5, 0x2e, 0x15, 0x19, 0x5a, 0x64, 0xc5, 0xc9, 0x4b, 0x15, 0x9b, 0xb2, 0x03, 0xca, 0x34, 0x3b, - 0x1c, 0x06, 0x9c, 0x6a, 0xcc, 0xb1, 0x83, 0xea, 0xc6, 0x66, 0x77, 0x4d, 0xeb, 0x3a, 0x43, 0x16, - 0xe7, 0x54, 0xbe, 0x2b, 0x50, 0xa8, 0x35, 0xb7, 0xf6, 0xb0, 0x47, 0xda, 0x98, 0xd3, 0x10, 0x3d, - 0x02, 0x90, 0xdf, 0x68, 0x05, 0xdd, 0xa2, 0xb2, 0xac, 0xac, 0xe6, 0xab, 0x65, 0x35, 0x46, 0x52, - 0x63, 0x24, 0x75, 0x8c, 0xa4, 0xea, 0x3d, 0xeb, 0xb9, 0x33, 0x34, 0x72, 0xb2, 0x44, 0xef, 0xa2, - 0x1d, 0x48, 0x5b, 0xdc, 0x8e, 0x6a, 0x93, 0xcb, 0xca, 0x6a, 0xa1, 0xb6, 0x79, 0x74, 0x5c, 0xae, - 0xba, 0x84, 0x77, 0x7a, 0x96, 0x6a, 0xd3, 0x03, 0x4d, 0x66, 0xda, 0x1d, 0x4c, 0xfc, 0xd1, 0x43, - 0xe3, 0xc3, 0xc0, 0x61, 0x6a, 0xad, 0xa1, 0xaf, 0xdf, 0xbb, 0x2b, 0x21, 0xa7, 0x2d, 0x6e, 0xeb, - 0x5d, 0xf4, 0x00, 0xa6, 0x02, 0x1a, 0x14, 0xa7, 0x44, 0x1f, 0xab, 0xea, 0x2f, 0xe9, 0xab, 0x7a, - 0x48, 0xe9, 0xfe, 0xcb, 0x7d, 0x9d, 0x32, 0xe6, 0x30, 0x46, 0xa8, 0x6f, 0x44, 0x45, 0x95, 0x4f, - 0x0a, 0x2c, 0x4c, 0x72, 0x7b, 0x45, 0x78, 0x67, 0xc7, 0xe1, 0x78, 0xa2, 0x47, 0xe5, 0x6f, 0xf4, - 0x78, 0x1d, 0xd2, 0x1d, 0x87, 0xb8, 0x1d, 0x2e, 0x28, 0xa7, 0x0c, 0xf9, 0x42, 0x2b, 0x50, 0xe8, - 0x53, 0x4e, 0x7c, 0xb7, 0x15, 0xd0, 0xf7, 0x4e, 0x28, 0x48, 0xa4, 0x8c, 0x7c, 0xec, 0xd3, 0x23, - 0x57, 0xe5, 0xf3, 0x34, 0xcc, 0xd4, 0x9a, 0x5b, 0xdb, 0x8e, 0xe7, 0xb8, 0x98, 0x13, 0xea, 0xff, - 0x47, 0xfa, 0xa3, 0x26, 0x40, 0x1f, 0x7b, 0x2d, 0xd9, 0x4e, 0xea, 0x4a, 0xed, 0x64, 0xfb, 0xd8, - 0xab, 0x89, 0x8e, 0x56, 0xa0, 0xc0, 0x38, 0x0e, 0x79, 0x4b, 0x6a, 0x3e, 0x1d, 0xab, 0x2a, 0x7c, - 0xcf, 0x62, 0xe1, 0x6f, 0x02, 0x38, 0x7e, 0x7b, 0x94, 0x90, 0x16, 0x09, 0x39, 0xc7, 0x6f, 0xcb, - 0xf0, 0x0d, 0xc8, 0x71, 0xca, 0xb1, 0xd7, 0x62, 0x98, 0x17, 0x33, 0x22, 0x9a, 0x15, 0x0e, 0x13, - 0x73, 0xf4, 0x18, 0x40, 0x32, 0x6b, 0xf1, 0x41, 0x31, 0x2b, 0x78, 0x2f, 0xff, 0x86, 0xb7, 0x19, - 0x9b, 0xcd, 0x81, 0x91, 0x63, 0x23, 0x13, 0x55, 0x21, 0xcf, 0x3c, 0xcc, 0x3a, 0x12, 0x21, 0x27, - 0x68, 0xcf, 0x1f, 0x1d, 0x97, 0xa3, 0x41, 0x9b, 0x32, 0xd2, 0x1c, 0x18, 0xc0, 0xc6, 0x36, 0x7a, - 0x0b, 0x33, 0xed, 0x78, 0x05, 0x68, 0xd8, 0x62, 0xc4, 0x2d, 0x82, 0xa8, 0xba, 0x7f, 0x74, 0x5c, - 0xde, 0xf8, 0x13, 0xb1, 0x4c, 0xe2, 0xfa, 0x98, 0xf7, 0x42, 0xc7, 0x28, 0x8c, 0xf1, 0x4c, 0xe2, - 0xa2, 0x26, 0x64, 0xdf, 0xf5, 0xc2, 0xa1, 0x80, 0xce, 0x5f, 0x15, 0x3a, 0x13, 0x41, 0x99, 0xc4, - 0xad, 0x7c, 0x55, 0x60, 0xf1, 0xcc, 0xf2, 0xfe, 0xab, 0x03, 0x3b, 0x3f, 0xf2, 0xe4, 0x65, 0x23, - 0x9f, 0x3a, 0x3f, 0xf2, 0xf3, 0xa7, 0x98, 0xba, 0x78, 0x8a, 0x1f, 0x14, 0x98, 0xbf, 0xb0, 0xc8, - 0xa8, 0x0c, 0xf9, 0xd1, 0x39, 0x46, 0xe2, 0x09, 0x3a, 0xc6, 0xe8, 0x42, 0x23, 0x69, 0x0d, 0xc8, - 0x44, 0x54, 0xa3, 0x60, 0xf2, 0xaa, 0xca, 0x46, 0xa2, 0x45, 0xc2, 0xd6, 0x20, 0x37, 0x5e, 0x2d, - 0x34, 0x0b, 0x49, 0x3e, 0x90, 0x1f, 0x4e, 0xf2, 0x01, 0xba, 0x05, 0xb3, 0xa3, 0x05, 0x65, 0x76, - 0x48, 0x82, 0x58, 0x8e, 0x82, 0x31, 0x23, 0xbd, 0xa6, 0x70, 0xde, 0x79, 0x08, 0xd7, 0xce, 0xcc, - 0xc6, 0xe4, 0x98, 0xf7, 0x18, 0xca, 0x43, 0x46, 0xaf, 0xef, 0x6e, 0x37, 0x76, 0x9f, 0xce, 0x25, - 0x10, 0x40, 0xfa, 0xc9, 0x56, 0xb3, 0xb1, 0x57, 0x9f, 0x53, 0xa2, 0x40, 0xfd, 0xb5, 0xde, 0x30, - 0xea, 0xdb, 0x73, 0xc9, 0xda, 0x8b, 0x2f, 0x27, 0x25, 0xe5, 0xf0, 0xa4, 0xa4, 0xfc, 0x38, 0x29, - 0x29, 0x1f, 0x4f, 0x4b, 0x89, 0xc3, 0xd3, 0x52, 0xe2, 0xdb, 0x69, 0x29, 0xf1, 0xe6, 0xd2, 0x29, - 0x0e, 0x26, 0x7f, 0x61, 0x82, 0xa6, 0x95, 0x16, 0xbf, 0x9a, 0xf5, 0x9f, 0x01, 0x00, 0x00, 0xff, - 0xff, 0xa2, 0x3c, 0xeb, 0x15, 0xe5, 0x06, 0x00, 0x00, + 0x14, 0xee, 0x96, 0xd2, 0x9f, 0xd3, 0x42, 0x60, 0x04, 0xd3, 0x60, 0x6c, 0x4b, 0x13, 0x4d, 0xe3, + 0xc5, 0xae, 0x14, 0x21, 0x51, 0x13, 0x8d, 0x85, 0x46, 0x1b, 0x05, 0x37, 0xbb, 0x0d, 0x1a, 0x2f, + 0x6c, 0x66, 0xb7, 0xcb, 0x76, 0x6c, 0xd9, 0xd9, 0xec, 0x4c, 0x6b, 0xfb, 0x06, 0x5e, 0xfa, 0x08, + 0x3e, 0x87, 0x4f, 0x60, 0xe2, 0x0d, 0x97, 0x86, 0x0b, 0x62, 0xe0, 0x11, 0x7c, 0x01, 0xb3, 0xb3, + 0xd3, 0xa6, 0x80, 0x86, 0x18, 0xf4, 0xc6, 0xbb, 0x39, 0x7f, 0xdf, 0x9e, 0xef, 0x3b, 0xe7, 0x64, + 0xe1, 0xb6, 0x85, 0xad, 0x51, 0x8f, 0x7a, 0x9a, 0xc5, 0x6d, 0xc6, 0x71, 0x97, 0x78, 0xae, 0x36, + 0x58, 0x9b, 0xb2, 0x54, 0x3f, 0xa0, 0x9c, 0xa2, 0x65, 0x99, 0xa7, 0x4e, 0x45, 0x06, 0x6b, 0x2b, + 0x4b, 0x2e, 0x75, 0xa9, 0xc8, 0xd0, 0xc2, 0x57, 0x94, 0xbc, 0x52, 0xb6, 0x29, 0x3b, 0xa0, 0x4c, + 0xb3, 0x83, 0x91, 0xcf, 0xa9, 0xc6, 0x1c, 0xdb, 0xaf, 0x6e, 0x6c, 0x76, 0xd7, 0xb4, 0xae, 0x33, + 0x62, 0x51, 0x4e, 0xf9, 0x87, 0x02, 0xb9, 0x5a, 0x73, 0x6b, 0x0f, 0xf7, 0x48, 0x1b, 0x73, 0x1a, + 0xa0, 0x47, 0x00, 0xf2, 0x1b, 0x2d, 0xbf, 0x9b, 0x57, 0x4a, 0x4a, 0x25, 0x5b, 0x2d, 0xaa, 0x11, + 0x92, 0x1a, 0x21, 0xa9, 0x13, 0x24, 0x55, 0xef, 0x5b, 0xcf, 0x9d, 0x91, 0x91, 0x91, 0x25, 0x7a, + 0x17, 0xed, 0x40, 0xd2, 0xe2, 0x76, 0x58, 0x1b, 0x2f, 0x29, 0x95, 0x5c, 0x6d, 0xf3, 0xe8, 0xb8, + 0x58, 0x75, 0x09, 0xef, 0xf4, 0x2d, 0xd5, 0xa6, 0x07, 0x9a, 0xcc, 0xb4, 0x3b, 0x98, 0x78, 0x63, + 0x43, 0xe3, 0x23, 0xdf, 0x61, 0x6a, 0xad, 0xa1, 0xaf, 0xdf, 0xbb, 0x2b, 0x21, 0x67, 0x2d, 0x6e, + 0xeb, 0x5d, 0xf4, 0x00, 0x66, 0x7c, 0xea, 0xe7, 0x67, 0x44, 0x1f, 0x15, 0xf5, 0x97, 0xf4, 0x55, + 0x3d, 0xa0, 0x74, 0xff, 0xe5, 0xbe, 0x4e, 0x19, 0x73, 0x18, 0x23, 0xd4, 0x33, 0xc2, 0x22, 0x94, + 0x87, 0x14, 0xeb, 0x61, 0xd6, 0x71, 0xda, 0xf9, 0x44, 0x49, 0xa9, 0xa4, 0x8d, 0xb1, 0x59, 0xfe, + 0xa4, 0xc0, 0xd2, 0x34, 0xeb, 0x57, 0x84, 0x77, 0x76, 0x1c, 0x8e, 0xa7, 0xba, 0x57, 0xfe, 0x46, + 0xf7, 0xd7, 0x21, 0xd9, 0x71, 0x88, 0xdb, 0xe1, 0x42, 0x8c, 0x84, 0x21, 0x2d, 0xb4, 0x0a, 0xb9, + 0x01, 0xe5, 0xc4, 0x73, 0x5b, 0x3e, 0x7d, 0xef, 0x04, 0x82, 0x5e, 0xc2, 0xc8, 0x46, 0x3e, 0x3d, + 0x74, 0x95, 0x3f, 0xcf, 0xc2, 0x5c, 0xad, 0xb9, 0xb5, 0xed, 0xf4, 0x1c, 0x17, 0x73, 0x42, 0xbd, + 0xff, 0x69, 0x32, 0x4d, 0x80, 0x01, 0xee, 0xb5, 0x64, 0x3b, 0x89, 0x2b, 0xb5, 0x93, 0x1e, 0xe0, + 0x5e, 0x4d, 0x74, 0xb4, 0x0a, 0x39, 0xc6, 0x71, 0xc0, 0x5b, 0x52, 0xf3, 0xd9, 0x48, 0x55, 0xe1, + 0x7b, 0x16, 0x09, 0x7f, 0x13, 0xc0, 0xf1, 0xda, 0xe3, 0x84, 0xa4, 0x48, 0xc8, 0x38, 0x5e, 0x5b, + 0x86, 0x6f, 0x40, 0x86, 0x53, 0x8e, 0x7b, 0x2d, 0x86, 0x79, 0x3e, 0x25, 0xa2, 0x69, 0xe1, 0x30, + 0x31, 0x47, 0x8f, 0x01, 0x24, 0xb3, 0x16, 0x1f, 0xe6, 0xd3, 0x82, 0x77, 0xe9, 0x37, 0xbc, 0xcd, + 0xe8, 0xd9, 0x1c, 0x1a, 0x19, 0x36, 0x7e, 0xa2, 0x2a, 0x64, 0xc5, 0x02, 0x4a, 0x84, 0x8c, 0xa0, + 0xbd, 0x78, 0x74, 0x5c, 0x0c, 0x07, 0x6d, 0xca, 0x48, 0x73, 0x68, 0x00, 0x9b, 0xbc, 0xd1, 0x5b, + 0x98, 0x6b, 0x47, 0x2b, 0x40, 0x83, 0x16, 0x23, 0x6e, 0x1e, 0x44, 0xd5, 0xfd, 0xa3, 0xe3, 0xe2, + 0xc6, 0x9f, 0x88, 0x65, 0x12, 0xd7, 0xc3, 0xbc, 0x1f, 0x38, 0x46, 0x6e, 0x82, 0x67, 0x12, 0x17, + 0x35, 0x21, 0xfd, 0xae, 0x1f, 0x8c, 0x04, 0x74, 0xf6, 0xaa, 0xd0, 0xa9, 0x10, 0xca, 0x24, 0x6e, + 0xf9, 0xab, 0x02, 0xcb, 0x67, 0x96, 0xf7, 0x5f, 0x1d, 0xd8, 0xf9, 0x91, 0xc7, 0x2f, 0x1b, 0xf9, + 0xcc, 0xf9, 0x91, 0x9f, 0x3f, 0xc5, 0xc4, 0xc5, 0x53, 0xfc, 0xa0, 0xc0, 0xe2, 0x85, 0x45, 0x46, + 0x45, 0xc8, 0x8e, 0xcf, 0x31, 0x14, 0x4f, 0xd0, 0x31, 0xc6, 0x17, 0x1a, 0x4a, 0x6b, 0x40, 0x2a, + 0xa4, 0x1a, 0x06, 0xe3, 0x57, 0x55, 0x36, 0x14, 0x2d, 0x14, 0xb6, 0x06, 0x99, 0xc9, 0x6a, 0xa1, + 0x79, 0x88, 0xf3, 0xa1, 0xfc, 0x70, 0x9c, 0x0f, 0xd1, 0x2d, 0x98, 0x1f, 0x2f, 0x28, 0xb3, 0x03, + 0xe2, 0x47, 0x72, 0xe4, 0x8c, 0x39, 0xe9, 0x35, 0x85, 0xf3, 0xce, 0x43, 0xb8, 0x76, 0x66, 0x36, + 0x26, 0xc7, 0xbc, 0xcf, 0x50, 0x16, 0x52, 0x7a, 0x7d, 0x77, 0xbb, 0xb1, 0xfb, 0x74, 0x21, 0x86, + 0x00, 0x92, 0x4f, 0xb6, 0x9a, 0x8d, 0xbd, 0xfa, 0x82, 0x12, 0x06, 0xea, 0xaf, 0xf5, 0x86, 0x51, + 0xdf, 0x5e, 0x88, 0xd7, 0x5e, 0x7c, 0x39, 0x29, 0x28, 0x87, 0x27, 0x05, 0xe5, 0xfb, 0x49, 0x41, + 0xf9, 0x78, 0x5a, 0x88, 0x1d, 0x9e, 0x16, 0x62, 0xdf, 0x4e, 0x0b, 0xb1, 0x37, 0x97, 0x4e, 0x71, + 0x38, 0xfd, 0x73, 0x13, 0x34, 0xad, 0xa4, 0xf8, 0x09, 0xad, 0xff, 0x0c, 0x00, 0x00, 0xff, 0xff, + 0x32, 0xbf, 0x48, 0x1b, 0xff, 0x06, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -543,6 +553,16 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Slashed { + i-- + if m.Slashed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } if m.Pop != nil { { size, err := m.Pop.MarshalToSizedBuffer(dAtA[:i]) @@ -919,6 +939,9 @@ func (m *BTCValidator) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovBtcstaking(uint64(l)) } + if m.Slashed { + n += 2 + } return n } @@ -1189,6 +1212,26 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Slashed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Slashed = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index db7564780..eb70ee98b 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -9,12 +9,13 @@ var ( ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") - ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1103, "the BTC staking protocol is not activated yet") - ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1104, "the BTC height is not found") - ErrReusedStakingTx = errorsmod.Register(ModuleName, 1105, "the BTC staking tx is already used") - ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1106, "the BTC staking tx specifies a wrong jury PK") - ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is not valid") - ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1108, "the BTC slashing tx is not valid") - ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1109, "the BTC delegation has already received jury signature") - ErrInvalidJurySig = errorsmod.Register(ModuleName, 1110, "the jury signature is not valid") + ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been slashed") + ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1104, "the BTC staking protocol is not activated yet") + ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1105, "the BTC height is not found") + ErrReusedStakingTx = errorsmod.Register(ModuleName, 1106, "the BTC staking tx is already used") + ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1107, "the BTC staking tx specifies a wrong jury PK") + ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1108, "the BTC staking tx is not valid") + ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1109, "the BTC slashing tx is not valid") + ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") + ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") ) diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index edb1624c6..8b7bbdc7b 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -90,6 +90,11 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // if this BTC validator has also signed canonical block, extract its secret key and emit an event canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) if err == nil { + // slash this BTC validator, i.e., set its voting power to zero + if err := ms.BTCStakingKeeper.SlashBTCValidator(ctx, req.ValBtcPk.MustMarshal()); err != nil { + panic(fmt.Errorf("failed to slash BTC validator: %v", err)) + } + btcSK, err := evidence.ExtractBTCSK(indexedBlock, pubRand, canonicalSig) if err != nil { panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) @@ -112,7 +117,12 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // if this BTC validator has signed the canonical block before, // slash it via extracting its secret key, and emit an event if ms.HasEvidence(ctx, req.BlockHeight, req.ValBtcPk) { - // the BTC validator has voted for a fork before! + // the BTC validator has voted for a fork before! slash it + + // slash this BTC validator, i.e., set its voting power to zero + if err := ms.BTCStakingKeeper.SlashBTCValidator(ctx, req.ValBtcPk.MustMarshal()); err != nil { + panic(fmt.Errorf("failed to slash BTC validator: %v", err)) + } // get evidence evidence, err := ms.GetEvidence(ctx, req.BlockHeight, req.ValBtcPk) diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 4791d9e49..0bca3ba19 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -169,6 +169,8 @@ func FuzzAddFinalitySig(f *testing.F) { blockHash2 := datagen.GenRandomByteArray(r, 32) msg2, err := types.NewMsgAddFinalitySig(signer, btcSK, sr, blockHeight, blockHash2) require.NoError(t, err) + // mock slashing interface + bsKeeper.EXPECT().SlashBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(nil).Times(1) // NOTE: even though this BTC validator is slashed, the msg should be successful // Otherwise the saved evidence will be rolled back _, err = ms.AddFinalitySig(ctx, msg2) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index 6fdbf70f8..f9d6ccd9a 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -7,6 +7,7 @@ import ( type BTCStakingKeeper interface { HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool + SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index ed32cec4e..ead17c340 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -92,6 +92,20 @@ func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).HasBTCValidator), ctx, valBTCPK) } +// SlashBTCValidator mocks base method. +func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx types.Context, valBTCPK []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashBTCValidator", ctx, valBTCPK) + ret0, _ := ret[0].(error) + return ret0 +} + +// SlashBTCValidator indicates an expected call of SlashBTCValidator. +func (mr *MockBTCStakingKeeperMockRecorder) SlashBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).SlashBTCValidator), ctx, valBTCPK) +} + // MockAccountKeeper is a mock of AccountKeeper interface. type MockAccountKeeper struct { ctrl *gomock.Controller From 5969a9a185cfbb0961b58f2f4a3ff042da4da1a4 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 1 Aug 2023 14:45:25 +0800 Subject: [PATCH 048/202] finality: refactor evidence KVStore and evidence RPC query (#42) --- proto/babylon/finality/v1/events.proto | 4 +- proto/babylon/finality/v1/finality.proto | 24 +- proto/babylon/finality/v1/query.proto | 19 + testutil/datagen/finality.go | 31 ++ x/finality/keeper/evidence.go | 42 +- x/finality/keeper/grpc_query.go | 29 +- x/finality/keeper/grpc_query_test.go | 58 ++- x/finality/keeper/msg_server.go | 88 ++-- x/finality/keeper/msg_server_test.go | 14 +- x/finality/types/errors.go | 1 + x/finality/types/events.go | 3 +- x/finality/types/events.pb.go | 105 +---- x/finality/types/finality.go | 58 ++- x/finality/types/finality.pb.go | 270 +++++++++--- x/finality/types/query.pb.go | 503 ++++++++++++++++++++--- x/finality/types/query.pb.gw.go | 101 +++++ 16 files changed, 1067 insertions(+), 283 deletions(-) diff --git a/proto/babylon/finality/v1/events.proto b/proto/babylon/finality/v1/events.proto index 68b06f431..c71ba10ce 100644 --- a/proto/babylon/finality/v1/events.proto +++ b/proto/babylon/finality/v1/events.proto @@ -11,10 +11,8 @@ option go_package = "github.com/babylonchain/babylon/x/finality/types"; message EventSlashedBTCValidator { // val_btc_pk is the BTC validator's BTC PK bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // indexed_block is the canonical block at this height - IndexedBlock indexed_block = 2; // evidence is the evidence that the BTC validator double signs - Evidence evidence = 3; + Evidence evidence = 2; // extracted_btc_sk is the extracted BTC SK of this BTC validator bytes extracted_btc_sk = 4; } diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index 256b3334d..2886f04a9 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -16,21 +16,25 @@ message IndexedBlock { bool finalized = 3; } -// Evidence is the evidence that a BTC validator has signed a finality -// signature with correct public randomness on a fork header -// It includes all fields of MsgAddFinalitySig, such that anyone seeing -// the evidence and a signature on the canonical fork can extract the -// BTC validator's BTC secret key. +// Evidence is the evidence that a BTC validator has signed finality +// signatures with correct public randomness on two conflicting Babylon headers message Evidence { // val_btc_pk is the BTC Pk of the validator that casts this vote bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // block_height is the height of the voted block + // block_height is the height of the conflicting blocks uint64 block_height = 2; - // block_last_commit_hash is the last_commit_hash of the voted block - bytes block_last_commit_hash = 3; - // finality_sig is the finality signature to this block + // pub_rand is the public randomness the BTC validator has committed to + bytes pub_rand = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; + // canonical_last_commit_hash is the last_commit_hash of the canonical block + bytes canonical_last_commit_hash = 4; + // fork_last_commit_hash is the last_commit_hash of the fork block + bytes fork_last_commit_hash = 5; + // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` // `r` is the public randomness that is already committed by the validator - bytes finality_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; + bytes canonical_finality_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; + // fork_finality_sig is the finality signature to the fork block + // where finality signature is an EOTS signature + bytes fork_finality_sig = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; } \ No newline at end of file diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index ceb31dce9..9d243d7dd 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -30,6 +30,11 @@ service Query { rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { option (google.api.http).get = "/babylon/finality/v1/votes/{height}"; } + + // Evidence queries the first evidence which can be used for extracting the BTC SK + rpc Evidence(QueryEvidenceRequest) returns (QueryEvidenceResponse) { + option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/evidence"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -107,3 +112,17 @@ message QueryVotesAtHeightResponse { // the PK follows encoding in BIP-340 spec repeated bytes btc_pks = 1 [(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey"]; } + +// QueryEvidenceRequest is the request type for the +// Query/Evidence RPC method. +message QueryEvidenceRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK + // (in BIP340 format) of the BTC validator + string val_btc_pk_hex = 1; +} + +// QueryEvidenceResponse is the response type for the +// Query/Evidence RPC method. +message QueryEvidenceResponse { + Evidence evidence = 1; +} diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index af13efc32..ae6ce6886 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -8,6 +8,7 @@ import ( ftypes "github.com/babylonchain/babylon/x/finality/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + sdk "github.com/cosmos/cosmos-sdk/types" ) func GenRandomPubRandList(r *rand.Rand, numPubRand uint64) ([]*eots.PrivateRand, []bbn.SchnorrPubRand, error) { @@ -49,3 +50,33 @@ func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeig msg.Sig = &sig return srList, msg, nil } + +func GenRandomEvidence(r *rand.Rand, sk *btcec.PrivateKey, height uint64) (*ftypes.Evidence, error) { + pk := sk.PubKey() + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(pk) + sr, pr, err := eots.RandGen(r) + if err != nil { + return nil, err + } + cLch := GenRandomByteArray(r, 32) + cSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), cLch...)) + if err != nil { + return nil, err + } + fLch := GenRandomByteArray(r, 32) + fSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), fLch...)) + if err != nil { + return nil, err + } + + evidence := &ftypes.Evidence{ + ValBtcPk: bip340PK, + BlockHeight: height, + PubRand: bbn.NewSchnorrPubRandFromFieldVal(pr), + CanonicalLastCommitHash: cLch, + ForkLastCommitHash: fLch, + CanonicalFinalitySig: bbn.NewSchnorrEOTSSigFromModNScalar(cSig), + ForkFinalitySig: bbn.NewSchnorrEOTSSigFromModNScalar(fSig), + } + return evidence, nil +} \ No newline at end of file diff --git a/x/finality/keeper/evidence.go b/x/finality/keeper/evidence.go index 249af7817..947603be8 100644 --- a/x/finality/keeper/evidence.go +++ b/x/finality/keeper/evidence.go @@ -8,21 +8,21 @@ import ( ) func (k Keeper) SetEvidence(ctx sdk.Context, evidence *types.Evidence) { - store := k.evidenceStore(ctx, evidence.BlockHeight) - store.Set(evidence.ValBtcPk.MustMarshal(), k.cdc.MustMarshal(evidence)) + store := k.evidenceStore(ctx, evidence.ValBtcPk) + store.Set(sdk.Uint64ToBigEndian(evidence.BlockHeight), k.cdc.MustMarshal(evidence)) } -func (k Keeper) HasEvidence(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { - store := k.evidenceStore(ctx, height) +func (k Keeper) HasEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { + store := k.evidenceStore(ctx, valBtcPK) return store.Has(valBtcPK.MustMarshal()) } -func (k Keeper) GetEvidence(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*types.Evidence, error) { +func (k Keeper) GetEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { if uint64(ctx.BlockHeight()) < height { return nil, types.ErrHeightTooHigh } - store := k.evidenceStore(ctx, height) - evidenceBytes := store.Get(valBtcPK.MustMarshal()) + store := k.evidenceStore(ctx, valBtcPK) + evidenceBytes := store.Get(sdk.Uint64ToBigEndian(height)) if len(evidenceBytes) == 0 { return nil, types.ErrEvidenceNotFound } @@ -31,12 +31,32 @@ func (k Keeper) GetEvidence(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340 return &evidence, nil } +// GetFirstSlashableEvidence gets the first evidence that is slashable, +// i.e., it contains all fields. +// NOTE: it's possible that the CanonicalFinalitySig field is empty for +// an evidence, which happens when the BTC validator signed a fork block +// but hasn't signed the canonical block yet. +func (k Keeper) GetFirstSlashableEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) *types.Evidence { + store := k.evidenceStore(ctx, valBtcPK) + iter := store.Iterator(nil, nil) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + evidenceBytes := iter.Value() + var evidence types.Evidence + k.cdc.MustUnmarshal(evidenceBytes, &evidence) + if evidence.IsSlashable() { + return &evidence + } + } + return nil +} + // evidenceStore returns the KVStore of the evidences // prefix: EvidenceKey -// key: (block height || BTC validator PK) +// key: (BTC validator PK || height) // value: Evidence -func (k Keeper) evidenceStore(ctx sdk.Context, height uint64) prefix.Store { +func (k Keeper) evidenceStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { store := ctx.KVStore(k.storeKey) - prefixedStore := prefix.NewStore(store, types.EvidenceKey) - return prefix.NewStore(prefixedStore, sdk.Uint64ToBigEndian(height)) + eStore := prefix.NewStore(store, types.EvidenceKey) + return prefix.NewStore(eStore, valBTCPK.MustMarshal()) } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 69bf86443..597d95365 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -15,6 +15,8 @@ import ( var _ types.QueryServer = Keeper{} +// ListPublicRandomness returns a list of public randomness committed by a given +// BTC validator func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPublicRandomnessRequest) (*types.QueryListPublicRandomnessResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -48,7 +50,7 @@ func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPu return resp, nil } -// TODO: allow returning all blocks in this API +// ListBlocks returns a list of blocks at the given finalisation status func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksRequest) (*types.QueryListBlocksResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -83,6 +85,7 @@ func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksReques return resp, nil } +// VotesAtHeight returns the set of votes at a given Babylon height func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeightRequest) (*types.QueryVotesAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") @@ -105,3 +108,27 @@ func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeight return &types.QueryVotesAtHeightResponse{BtcPks: btcPks}, nil } + +// Evidence returns the first evidence that allows to extract the BTC validator's SK +// associated with the given BTC validator's PK. +func (k Keeper) Evidence(ctx context.Context, req *types.QueryEvidenceRequest) (*types.QueryEvidenceResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + evidence := k.GetFirstSlashableEvidence(sdkCtx, valBTCPK) + if evidence == nil { + return nil, types.ErrNoSlashableEvidence + } + + resp := &types.QueryEvidenceResponse{ + Evidence: evidence, + } + return resp, nil +} diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 45a3baacb..5bb59f635 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -4,16 +4,13 @@ import ( "math/rand" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" - + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" - - "github.com/babylonchain/babylon/testutil/datagen" - testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/stretchr/testify/require" ) func FuzzListPublicRandomness(f *testing.F) { @@ -187,3 +184,50 @@ func FuzzVotesAtHeight(f *testing.F) { } }) } + +func FuzzQueryEvidence(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // set random BTC SK PK + sk, _, err := datagen.GenRandomBTCKeyPair(r) + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()) + require.NoError(t, err) + + var randomFirstSlashableEvidence *types.Evidence = nil + numEvidences := datagen.RandomInt(r, 10) + 1 + height := uint64(5) + + // set a list of evidences, in which some of them are slashable while the others are not + for i := uint64(0); i < numEvidences; i++ { + evidence, err := datagen.GenRandomEvidence(r, sk, height) + require.NoError(t, err) + if datagen.RandomInt(r, 2) == 1 { + evidence.CanonicalFinalitySig = nil // not slashable + } else { + if randomFirstSlashableEvidence == nil { + randomFirstSlashableEvidence = evidence // first slashable + } + } + keeper.SetEvidence(ctx, evidence) + + height += datagen.RandomInt(r, 5) + 1 + } + + // get first slashable evidence + evidenceResp, err := keeper.Evidence(ctx, &types.QueryEvidenceRequest{ValBtcPkHex: bip340PK.MarshalHex()}) + if randomFirstSlashableEvidence == nil { + require.Error(t, err) + require.Nil(t, evidenceResp) + } else { + require.NoError(t, err) + require.Equal(t, randomFirstSlashableEvidence, evidenceResp.Evidence) + require.True(t, evidenceResp.Evidence.IsSlashable()) + } + }) +} diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 8b7bbdc7b..a25cdfe48 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -7,6 +7,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/crypto/eots" + bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -44,6 +45,8 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal ctx := sdk.UnwrapSDKContext(goCtx) // ensure the BTC validator has voting power at this height + // NOTE: if a BTC validator has signed two different blocks at the same height, + // then it will be slashed and its voting power will be reduced to zero valPK := req.ValBtcPk if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { return nil, types.ErrInvalidFinalitySig.Wrapf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) @@ -78,34 +81,30 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal if !bytes.Equal(indexedBlock.LastCommitHash, req.BlockLastCommitHash) { // the BTC validator votes for a fork! - // construct and save evidence + // construct evidence evidence := &types.Evidence{ - ValBtcPk: req.ValBtcPk, - BlockHeight: req.BlockHeight, - BlockLastCommitHash: req.BlockLastCommitHash, - FinalitySig: req.FinalitySig, + ValBtcPk: req.ValBtcPk, + BlockHeight: req.BlockHeight, + PubRand: pubRand, + CanonicalLastCommitHash: indexedBlock.LastCommitHash, + CanonicalFinalitySig: nil, + ForkLastCommitHash: req.BlockLastCommitHash, + ForkFinalitySig: req.FinalitySig, } - ms.SetEvidence(ctx, evidence) - // if this BTC validator has also signed canonical block, extract its secret key and emit an event + // if this BTC validator has also signed canonical block, slash it canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) if err == nil { - // slash this BTC validator, i.e., set its voting power to zero - if err := ms.BTCStakingKeeper.SlashBTCValidator(ctx, req.ValBtcPk.MustMarshal()); err != nil { - panic(fmt.Errorf("failed to slash BTC validator: %v", err)) - } - - btcSK, err := evidence.ExtractBTCSK(indexedBlock, pubRand, canonicalSig) - if err != nil { - panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) - } - - eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) - if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { - panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) - } + //set canonial sig + evidence.CanonicalFinalitySig = canonicalSig + // slash this BTC validator, including setting its voting power to + // zero, extracting its BTC SK, and emit an event + ms.slashBTCValidator(ctx, req.ValBtcPk, evidence) } + // save evidence + ms.SetEvidence(ctx, evidence) + // NOTE: we should NOT return error here, otherwise the state change triggered in this tx // (including the evidence) will be rolled back return &types.MsgAddFinalitySigResponse{}, nil @@ -116,30 +115,23 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // if this BTC validator has signed the canonical block before, // slash it via extracting its secret key, and emit an event - if ms.HasEvidence(ctx, req.BlockHeight, req.ValBtcPk) { - // the BTC validator has voted for a fork before! slash it - - // slash this BTC validator, i.e., set its voting power to zero - if err := ms.BTCStakingKeeper.SlashBTCValidator(ctx, req.ValBtcPk.MustMarshal()); err != nil { - panic(fmt.Errorf("failed to slash BTC validator: %v", err)) - } + if ms.HasEvidence(ctx, req.ValBtcPk, req.BlockHeight) { + // the BTC validator has voted for a fork before! + // If this evidence is at the same height as this signature, slash this BTC validator // get evidence - evidence, err := ms.GetEvidence(ctx, req.BlockHeight, req.ValBtcPk) + evidence, err := ms.GetEvidence(ctx, req.ValBtcPk, req.BlockHeight) if err != nil { panic(fmt.Errorf("failed to get evidence despite HasEvidence returns true")) } - // extract its SK - btcSK, err := evidence.ExtractBTCSK(indexedBlock, pubRand, req.FinalitySig) - if err != nil { - panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) - } + // set canonical sig to this evidence + evidence.CanonicalFinalitySig = req.FinalitySig + ms.SetEvidence(ctx, evidence) - eventSlashing := types.NewEventSlashedBTCValidator(req.ValBtcPk, indexedBlock, evidence, btcSK) - if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { - panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) - } + // slash this BTC validator, including setting its voting power to + // zero, extracting its BTC SK, and emit an event + ms.slashBTCValidator(ctx, req.ValBtcPk, evidence) } return &types.MsgAddFinalitySigResponse{}, nil @@ -182,3 +174,23 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil } + +// slashBTCValidator slashes a BTC validator with the given evidence +// including setting its voting power to zero, extracting its BTC SK, +// and emit an event +func (k Keeper) slashBTCValidator(ctx sdk.Context, valBtcPk *bbn.BIP340PubKey, evidence *types.Evidence) { + // slash this BTC validator, i.e., set its voting power to zero + if err := k.BTCStakingKeeper.SlashBTCValidator(ctx, valBtcPk.MustMarshal()); err != nil { + panic(fmt.Errorf("failed to slash BTC validator: %v", err)) + } + + // extract SK and emit slashing event + btcSK, err := evidence.ExtractBTCSK() + if err != nil { + panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) + } + eventSlashing := types.NewEventSlashedBTCValidator(valBtcPk, evidence, btcSK) + if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { + panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) + } +} diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 0bca3ba19..3b097cee3 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -126,7 +126,7 @@ func FuzzAddFinalitySig(f *testing.F) { // generate a vote blockHeight := uint64(1) - sr, pr := srList[startHeight+blockHeight], msgCommitPubRandList.PubRandList[startHeight+blockHeight] + sr, _ := srList[startHeight+blockHeight], msgCommitPubRandList.PubRandList[startHeight+blockHeight] blockHash := datagen.GenRandomByteArray(r, 32) signer := datagen.GenRandomAccount().Address msg, err := types.NewMsgAddFinalitySig(signer, btcSK, sr, blockHeight, blockHash) @@ -176,21 +176,21 @@ func FuzzAddFinalitySig(f *testing.F) { _, err = ms.AddFinalitySig(ctx, msg2) require.NoError(t, err) // ensure the evidence has been stored - evidence, err := fKeeper.GetEvidence(ctx, blockHeight, valBTCPK) + evidence, err := fKeeper.GetEvidence(ctx, valBTCPK, blockHeight) require.NoError(t, err) require.Equal(t, msg2.BlockHeight, evidence.BlockHeight) require.Equal(t, msg2.ValBtcPk.MustMarshal(), evidence.ValBtcPk.MustMarshal()) - require.Equal(t, msg2.BlockLastCommitHash, evidence.BlockLastCommitHash) - require.Equal(t, msg2.FinalitySig.MustMarshal(), evidence.FinalitySig.MustMarshal()) + require.Equal(t, msg2.BlockLastCommitHash, evidence.ForkLastCommitHash) + require.Equal(t, msg2.FinalitySig.MustMarshal(), evidence.ForkFinalitySig.MustMarshal()) // extract the SK and assert the extracted SK is correct - indexedBlock := &types.IndexedBlock{Height: blockHeight, LastCommitHash: blockHash} - btcSK2, err := evidence.ExtractBTCSK(indexedBlock, &pr, msg.FinalitySig) + btcSK2, err := evidence.ExtractBTCSK() require.NoError(t, err) - // ensure btcSK and btcSK2 correspond to the same PK + // ensure btcSK and btcSK2 are same or inverse, AND correspond to the same PK // NOTE: it's possible that different SKs derive to the same PK // In this scenario, signature of any of these SKs can be verified with this PK // exclude the first byte here since it denotes the y axis of PubKey, which does // not affect verification + require.True(t, btcSK.Key.Equals(&btcSK2.Key) || btcSK.Key.Negate().Equals(&btcSK2.Key)) require.Equal(t, btcSK.PubKey().SerializeCompressed()[1:], btcSK2.PubKey().SerializeCompressed()[1:]) }) } diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index b27dfd2cb..77a4ed23a 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -17,4 +17,5 @@ var ( ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1108, "evidence is not found") ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1109, "finality signature is not valid") ErrDuplicatedFinalitySig = errorsmod.Register(ModuleName, 1110, "the finality signature has been casted before") + ErrNoSlashableEvidence = errorsmod.Register(ModuleName, 1111, "there is no slashable evidence") ) diff --git a/x/finality/types/events.go b/x/finality/types/events.go index d2337a65c..d2a1f4b10 100644 --- a/x/finality/types/events.go +++ b/x/finality/types/events.go @@ -5,10 +5,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2" ) -func NewEventSlashedBTCValidator(valBTCPK *bbn.BIP340PubKey, indexedBlock *IndexedBlock, evidence *Evidence, btcSK *btcec.PrivateKey) *EventSlashedBTCValidator { +func NewEventSlashedBTCValidator(valBTCPK *bbn.BIP340PubKey, evidence *Evidence, btcSK *btcec.PrivateKey) *EventSlashedBTCValidator { return &EventSlashedBTCValidator{ ValBtcPk: valBTCPK, - IndexedBlock: indexedBlock, Evidence: evidence, ExtractedBtcSk: btcSK.Serialize(), } diff --git a/x/finality/types/events.pb.go b/x/finality/types/events.pb.go index b5f1358b1..8296e8e48 100644 --- a/x/finality/types/events.pb.go +++ b/x/finality/types/events.pb.go @@ -29,10 +29,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type EventSlashedBTCValidator struct { // val_btc_pk is the BTC validator's BTC PK ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` - // indexed_block is the canonical block at this height - IndexedBlock *IndexedBlock `protobuf:"bytes,2,opt,name=indexed_block,json=indexedBlock,proto3" json:"indexed_block,omitempty"` // evidence is the evidence that the BTC validator double signs - Evidence *Evidence `protobuf:"bytes,3,opt,name=evidence,proto3" json:"evidence,omitempty"` + Evidence *Evidence `protobuf:"bytes,2,opt,name=evidence,proto3" json:"evidence,omitempty"` // extracted_btc_sk is the extracted BTC SK of this BTC validator ExtractedBtcSk []byte `protobuf:"bytes,4,opt,name=extracted_btc_sk,json=extractedBtcSk,proto3" json:"extracted_btc_sk,omitempty"` } @@ -70,13 +68,6 @@ func (m *EventSlashedBTCValidator) XXX_DiscardUnknown() { var xxx_messageInfo_EventSlashedBTCValidator proto.InternalMessageInfo -func (m *EventSlashedBTCValidator) GetIndexedBlock() *IndexedBlock { - if m != nil { - return m.IndexedBlock - } - return nil -} - func (m *EventSlashedBTCValidator) GetEvidence() *Evidence { if m != nil { return m.Evidence @@ -98,28 +89,26 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/events.proto", fileDescriptor_c34c03aae5e3e6bf) } var fileDescriptor_c34c03aae5e3e6bf = []byte{ - // 330 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xcf, 0x4a, 0xc3, 0x30, - 0x1c, 0xc7, 0xd7, 0x29, 0x32, 0xe2, 0x14, 0xa9, 0x1e, 0xca, 0xc0, 0x3a, 0x77, 0xda, 0x29, 0xdd, - 0x1f, 0x11, 0xbc, 0x46, 0x26, 0x4c, 0x2f, 0xa3, 0x1b, 0x1e, 0xbc, 0x94, 0x24, 0x8d, 0x5b, 0x68, - 0x6c, 0xc6, 0xfa, 0x5b, 0x58, 0xdf, 0xc2, 0x17, 0xf0, 0x7d, 0x3c, 0xee, 0x28, 0x1e, 0x44, 0xb6, - 0x17, 0x91, 0x76, 0x7f, 0xf4, 0x30, 0xf0, 0x96, 0x5f, 0xf2, 0xc9, 0x37, 0xf9, 0x7e, 0x50, 0x95, - 0x51, 0x96, 0x2a, 0x1d, 0x7b, 0xcf, 0x32, 0xa6, 0x4a, 0x42, 0xea, 0x99, 0xa6, 0x27, 0x8c, 0x88, - 0x21, 0xc1, 0xe3, 0x89, 0x06, 0x6d, 0x9f, 0xae, 0x09, 0xbc, 0x21, 0xb0, 0x69, 0x56, 0xce, 0x86, - 0x7a, 0xa8, 0xf3, 0x73, 0x2f, 0x5b, 0xad, 0xd0, 0x4a, 0x6d, 0x57, 0xd8, 0xf6, 0x5a, 0xce, 0xd4, - 0xde, 0x8a, 0xc8, 0xe9, 0x64, 0xf9, 0x7d, 0x45, 0x93, 0x91, 0x08, 0xc9, 0xe0, 0xf6, 0x91, 0x2a, - 0x19, 0x52, 0xd0, 0x13, 0x7b, 0x80, 0x90, 0xa1, 0x2a, 0x60, 0xc0, 0x83, 0x71, 0xe4, 0x58, 0x55, - 0xab, 0x5e, 0x26, 0xd7, 0x9f, 0x5f, 0x17, 0xad, 0xa1, 0x84, 0xd1, 0x94, 0x61, 0xae, 0x5f, 0xbc, - 0xf5, 0x1b, 0x7c, 0x44, 0x65, 0xbc, 0x19, 0x3c, 0x48, 0xc7, 0x22, 0xc1, 0xa4, 0xdb, 0x6b, 0x5f, - 0x35, 0x7a, 0x53, 0xf6, 0x20, 0x52, 0xbf, 0x64, 0xa8, 0x22, 0xc0, 0x7b, 0x91, 0x7d, 0x87, 0x8e, - 0x64, 0x1c, 0x8a, 0x99, 0x08, 0x03, 0xa6, 0x34, 0x8f, 0x9c, 0x62, 0xd5, 0xaa, 0x1f, 0xb6, 0x2e, - 0xf1, 0x8e, 0x66, 0xb8, 0xbb, 0x22, 0x49, 0x06, 0xfa, 0x65, 0xf9, 0x67, 0xb2, 0x6f, 0x50, 0x49, - 0x18, 0x19, 0x8a, 0x98, 0x0b, 0x67, 0x2f, 0x8f, 0x38, 0xdf, 0x19, 0xd1, 0x59, 0x43, 0xfe, 0x16, - 0xb7, 0xeb, 0xe8, 0x44, 0xcc, 0x60, 0x42, 0x39, 0x64, 0x9f, 0x00, 0x1e, 0x24, 0x91, 0xb3, 0x9f, - 0xd5, 0xf3, 0x8f, 0xb7, 0xfb, 0x04, 0x78, 0x3f, 0x22, 0xf7, 0xef, 0x0b, 0xd7, 0x9a, 0x2f, 0x5c, - 0xeb, 0x7b, 0xe1, 0x5a, 0xaf, 0x4b, 0xb7, 0x30, 0x5f, 0xba, 0x85, 0x8f, 0xa5, 0x5b, 0x78, 0x6a, - 0xfc, 0x27, 0x61, 0xf6, 0xeb, 0x3d, 0xf7, 0xc1, 0x0e, 0x72, 0xe5, 0xed, 0x9f, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x90, 0xd8, 0x29, 0xfa, 0xe5, 0x01, 0x00, 0x00, + // 295 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, + 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xaa, + 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, + 0x58, 0x10, 0xa5, 0x52, 0x4a, 0xd8, 0x0c, 0x83, 0x6b, 0x03, 0xab, 0x51, 0xba, 0xca, 0xc8, 0x25, + 0xe1, 0x0a, 0x32, 0x3f, 0x38, 0x27, 0xb1, 0x38, 0x23, 0x35, 0xc5, 0x29, 0xc4, 0x39, 0x2c, 0x31, + 0x27, 0x33, 0x25, 0xb1, 0x24, 0xbf, 0x48, 0x28, 0x84, 0x8b, 0xab, 0x2c, 0x31, 0x27, 0x3e, 0xa9, + 0x24, 0x39, 0xbe, 0x20, 0x5b, 0x82, 0x51, 0x81, 0x51, 0x83, 0xc7, 0xc9, 0xec, 0xd6, 0x3d, 0x79, + 0xa3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x1d, 0xc9, 0x19, + 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x93, 0x67, 0x80, 0xb1, + 0x89, 0x41, 0x40, 0x69, 0x92, 0x77, 0x6a, 0x65, 0x10, 0x47, 0x59, 0x62, 0x8e, 0x53, 0x49, 0x72, + 0x40, 0xb6, 0x90, 0x25, 0x17, 0x47, 0x6a, 0x59, 0x66, 0x4a, 0x6a, 0x5e, 0x72, 0xaa, 0x04, 0x93, + 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xac, 0x1e, 0x16, 0x4f, 0xe9, 0xb9, 0x42, 0x15, 0x05, 0xc1, 0x95, + 0x0b, 0x69, 0x70, 0x09, 0xa4, 0x56, 0x94, 0x14, 0x25, 0x26, 0x97, 0xa4, 0xa6, 0x80, 0x9d, 0x55, + 0x9c, 0x2d, 0xc1, 0x02, 0x72, 0x56, 0x10, 0x1f, 0x5c, 0xdc, 0xa9, 0x24, 0x39, 0x38, 0xdb, 0xc9, + 0xeb, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, + 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0x08, 0x39, 0xbe, 0x02, + 0x11, 0x5e, 0x60, 0x7f, 0x24, 0xb1, 0x81, 0x83, 0xca, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0b, + 0x3c, 0x79, 0xa2, 0x9d, 0x01, 0x00, 0x00, } func (m *EventSlashedBTCValidator) Marshal() (dAtA []byte, err error) { @@ -159,18 +148,6 @@ func (m *EventSlashedBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error i = encodeVarintEvents(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - if m.IndexedBlock != nil { - { - size, err := m.IndexedBlock.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEvents(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0x12 } if m.ValBtcPk != nil { @@ -209,10 +186,6 @@ func (m *EventSlashedBTCValidator) Size() (n int) { l = m.ValBtcPk.Size() n += 1 + l + sovEvents(uint64(l)) } - if m.IndexedBlock != nil { - l = m.IndexedBlock.Size() - n += 1 + l + sovEvents(uint64(l)) - } if m.Evidence != nil { l = m.Evidence.Size() n += 1 + l + sovEvents(uint64(l)) @@ -295,42 +268,6 @@ func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IndexedBlock", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.IndexedBlock == nil { - m.IndexedBlock = &IndexedBlock{} - } - if err := m.IndexedBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) } diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go index 5fe688819..3e6cbc6d2 100644 --- a/x/finality/types/finality.go +++ b/x/finality/types/finality.go @@ -2,9 +2,9 @@ package types import ( "bytes" + "fmt" "github.com/babylonchain/babylon/crypto/eots" - bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -12,10 +12,7 @@ import ( // msgToSignForVote returns the message for an EOTS signature // The EOTS signature on a block will be (blockHeight || blockHash) func msgToSignForVote(blockHeight uint64, blockHash []byte) []byte { - msgToSign := []byte{} - msgToSign = append(msgToSign, sdk.Uint64ToBigEndian(blockHeight)...) - msgToSign = append(msgToSign, blockHash...) - return msgToSign + return append(sdk.Uint64ToBigEndian(blockHeight), blockHash...) } func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { @@ -33,20 +30,55 @@ func (ib *IndexedBlock) MsgToSign() []byte { return msgToSignForVote(ib.Height, ib.LastCommitHash) } -func (e *Evidence) MsgToSign() []byte { - return msgToSignForVote(e.BlockHeight, e.BlockLastCommitHash) +func (e *Evidence) canonicalMsgToSign() []byte { + return msgToSignForVote(e.BlockHeight, e.CanonicalLastCommitHash) } -// ExtractBTCSK extracts the BTC SK given the canonical block, public randomness -// and EOTS signature on the canonical block -func (e *Evidence) ExtractBTCSK(indexedBlock *IndexedBlock, pubRand *bbn.SchnorrPubRand, sig *bbn.SchnorrEOTSSig) (*btcec.PrivateKey, error) { +func (e *Evidence) forkMsgToSign() []byte { + return msgToSignForVote(e.BlockHeight, e.ForkLastCommitHash) +} + +func (e *Evidence) ValidateBasic() error { + if e.ValBtcPk == nil { + return fmt.Errorf("empty ValBtcPk") + } + if e.PubRand == nil { + return fmt.Errorf("empty PubRand") + } + if len(e.CanonicalLastCommitHash) != 32 { + return fmt.Errorf("malformed CanonicalLastCommitHash") + } + if len(e.ForkLastCommitHash) != 32 { + return fmt.Errorf("malformed ForkLastCommitHash") + } + if e.ForkFinalitySig == nil { + return fmt.Errorf("empty ValBtcPk") + } + return nil +} + +func (e *Evidence) IsSlashable() bool { + if err := e.ValidateBasic(); err != nil { + return false + } + if e.CanonicalFinalitySig == nil { + return false + } + return true +} + +// ExtractBTCSK extracts the BTC SK given the data in the evidence +func (e *Evidence) ExtractBTCSK() (*btcec.PrivateKey, error) { + if !e.IsSlashable() { + return nil, fmt.Errorf("the evidence lacks some fields so does not allow extracting BTC SK") + } btcPK, err := e.ValBtcPk.ToBTCPK() if err != nil { return nil, err } return eots.Extract( - btcPK, pubRand.ToFieldVal(), - indexedBlock.MsgToSign(), sig.ToModNScalar(), // msg and sig for canonical block - e.MsgToSign(), e.FinalitySig.ToModNScalar(), // msg and sig for fork block + btcPK, e.PubRand.ToFieldVal(), + e.canonicalMsgToSign(), e.CanonicalFinalitySig.ToModNScalar(), // msg and sig for canonical block + e.forkMsgToSign(), e.ForkFinalitySig.ToModNScalar(), // msg and sig for fork block ) } diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index c268622d7..8e9afb343 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -89,23 +89,27 @@ func (m *IndexedBlock) GetFinalized() bool { return false } -// Evidence is the evidence that a BTC validator has signed a finality -// signature with correct public randomness on a fork header -// It includes all fields of MsgAddFinalitySig, such that anyone seeing -// the evidence and a signature on the canonical fork can extract the -// BTC validator's BTC secret key. +// Evidence is the evidence that a BTC validator has signed finality +// signatures with correct public randomness on two conflicting Babylon headers type Evidence struct { // val_btc_pk is the BTC Pk of the validator that casts this vote ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` - // block_height is the height of the voted block + // block_height is the height of the conflicting blocks BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // block_last_commit_hash is the last_commit_hash of the voted block - BlockLastCommitHash []byte `protobuf:"bytes,3,opt,name=block_last_commit_hash,json=blockLastCommitHash,proto3" json:"block_last_commit_hash,omitempty"` - // finality_sig is the finality signature to this block + // pub_rand is the public randomness the BTC validator has committed to + PubRand *github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,3,opt,name=pub_rand,json=pubRand,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand,omitempty"` + // canonical_last_commit_hash is the last_commit_hash of the canonical block + CanonicalLastCommitHash []byte `protobuf:"bytes,4,opt,name=canonical_last_commit_hash,json=canonicalLastCommitHash,proto3" json:"canonical_last_commit_hash,omitempty"` + // fork_last_commit_hash is the last_commit_hash of the fork block + ForkLastCommitHash []byte `protobuf:"bytes,5,opt,name=fork_last_commit_hash,json=forkLastCommitHash,proto3" json:"fork_last_commit_hash,omitempty"` + // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` // `r` is the public randomness that is already committed by the validator - FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,4,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` + CanonicalFinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,6,opt,name=canonical_finality_sig,json=canonicalFinalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"canonical_finality_sig,omitempty"` + // fork_finality_sig is the finality signature to the fork block + // where finality signature is an EOTS signature + ForkFinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,7,opt,name=fork_finality_sig,json=forkFinalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"fork_finality_sig,omitempty"` } func (m *Evidence) Reset() { *m = Evidence{} } @@ -148,9 +152,16 @@ func (m *Evidence) GetBlockHeight() uint64 { return 0 } -func (m *Evidence) GetBlockLastCommitHash() []byte { +func (m *Evidence) GetCanonicalLastCommitHash() []byte { if m != nil { - return m.BlockLastCommitHash + return m.CanonicalLastCommitHash + } + return nil +} + +func (m *Evidence) GetForkLastCommitHash() []byte { + if m != nil { + return m.ForkLastCommitHash } return nil } @@ -165,31 +176,35 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 370 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x6b, 0xe2, 0x40, - 0x14, 0xc7, 0x8d, 0x8a, 0xb8, 0x63, 0x58, 0x96, 0xb8, 0x48, 0x58, 0x96, 0xe8, 0x7a, 0xca, 0x29, - 0xd1, 0x55, 0x96, 0x3d, 0xa7, 0x08, 0xda, 0x16, 0x2a, 0x89, 0xa7, 0xf6, 0x10, 0x26, 0x93, 0x69, - 0x66, 0x30, 0x66, 0xc4, 0x8c, 0xc1, 0xf4, 0x53, 0xf4, 0xd2, 0xef, 0xd4, 0xa3, 0xc7, 0xe2, 0x41, - 0x8a, 0x7e, 0x91, 0xe2, 0x24, 0x2a, 0xa5, 0x87, 0xf6, 0xf6, 0xde, 0xff, 0xfd, 0x79, 0xff, 0xf7, - 0x4b, 0x06, 0xb4, 0x3d, 0xe8, 0xa5, 0x21, 0x8b, 0xcc, 0x7b, 0x1a, 0xc1, 0x90, 0xf2, 0xd4, 0x4c, - 0xba, 0xa7, 0xda, 0x98, 0x2f, 0x18, 0x67, 0x4a, 0x3d, 0xf7, 0x18, 0x27, 0x3d, 0xe9, 0xfe, 0xfa, - 0x19, 0xb0, 0x80, 0x89, 0xb9, 0x79, 0xa8, 0x32, 0x6b, 0x3b, 0x02, 0xf2, 0x28, 0xf2, 0xf1, 0x0a, - 0xfb, 0x56, 0xc8, 0xd0, 0x54, 0x69, 0x80, 0x0a, 0xc1, 0x34, 0x20, 0x5c, 0x95, 0x5a, 0x92, 0x5e, - 0xb6, 0xf3, 0x4e, 0xd1, 0xc1, 0x8f, 0x10, 0xc6, 0xdc, 0x45, 0x6c, 0x36, 0xa3, 0xdc, 0x25, 0x30, - 0x26, 0x6a, 0xb1, 0x25, 0xe9, 0xb2, 0xfd, 0xfd, 0xa0, 0x5f, 0x08, 0x79, 0x08, 0x63, 0xa2, 0xfc, - 0x06, 0xdf, 0xb2, 0xd8, 0x07, 0xec, 0xab, 0xa5, 0x96, 0xa4, 0x57, 0xed, 0xb3, 0xd0, 0x7e, 0x2a, - 0x82, 0xea, 0x20, 0xa1, 0x3e, 0x8e, 0x10, 0x56, 0x26, 0x00, 0x24, 0x30, 0x74, 0x3d, 0x8e, 0xdc, - 0xf9, 0x54, 0x04, 0xca, 0xd6, 0xbf, 0xcd, 0xb6, 0xf9, 0x37, 0xa0, 0x9c, 0x2c, 0x3d, 0x03, 0xb1, - 0x99, 0x99, 0xa3, 0x20, 0x02, 0x69, 0x74, 0x6c, 0x4c, 0x9e, 0xce, 0x71, 0x6c, 0x58, 0xa3, 0x71, - 0xaf, 0xdf, 0x19, 0x2f, 0xbd, 0x2b, 0x9c, 0xda, 0xd5, 0x04, 0x86, 0x16, 0x47, 0xe3, 0xa9, 0xf2, - 0x07, 0xc8, 0xde, 0x81, 0xc5, 0xcd, 0x41, 0x8a, 0x02, 0xa4, 0x26, 0xb4, 0x61, 0x46, 0xd3, 0x03, - 0x8d, 0xcc, 0xf2, 0x81, 0xa9, 0x24, 0x98, 0xea, 0x62, 0x7a, 0xfd, 0x1e, 0xec, 0x0e, 0xc8, 0xc7, - 0xef, 0xe9, 0xc6, 0x34, 0x50, 0xcb, 0xe2, 0xde, 0xff, 0x9b, 0x6d, 0xb3, 0xff, 0xb5, 0x7b, 0x1d, - 0x44, 0x22, 0xb6, 0x58, 0x0c, 0x6e, 0x26, 0x8e, 0x43, 0x03, 0xbb, 0x76, 0xdc, 0xe6, 0xd0, 0xc0, - 0xba, 0x7c, 0xde, 0x69, 0xd2, 0x7a, 0xa7, 0x49, 0xaf, 0x3b, 0x4d, 0x7a, 0xdc, 0x6b, 0x85, 0xf5, - 0x5e, 0x2b, 0xbc, 0xec, 0xb5, 0xc2, 0x6d, 0xe7, 0xb3, 0xe5, 0xab, 0xf3, 0x53, 0x10, 0x39, 0x5e, - 0x45, 0xfc, 0xda, 0xde, 0x5b, 0x00, 0x00, 0x00, 0xff, 0xff, 0x55, 0x87, 0x93, 0x2b, 0x2b, 0x02, - 0x00, 0x00, + // 440 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x6f, 0xd3, 0x30, + 0x14, 0xc7, 0x9b, 0x51, 0xba, 0x62, 0x22, 0x7e, 0x98, 0x31, 0xaa, 0x09, 0x65, 0xa5, 0xa7, 0x9e, + 0x9a, 0x95, 0x4d, 0x08, 0x89, 0x5b, 0xd0, 0xd0, 0x06, 0x48, 0x54, 0xc9, 0x4e, 0x5c, 0x2c, 0xdb, + 0xf1, 0x62, 0xab, 0xa9, 0x1d, 0x25, 0x4e, 0xb4, 0xf0, 0x57, 0xf0, 0x17, 0xf0, 0xf7, 0x70, 0xdc, + 0x11, 0xed, 0x30, 0xa1, 0xf6, 0x1f, 0x41, 0x71, 0xb2, 0x94, 0xb1, 0x03, 0x88, 0xdd, 0x9e, 0xbf, + 0x7e, 0xef, 0x7d, 0xde, 0xf7, 0x25, 0x06, 0x23, 0x82, 0x49, 0x19, 0x2b, 0xe9, 0x9e, 0x0a, 0x89, + 0x63, 0xa1, 0x4b, 0xb7, 0x98, 0xb6, 0xf1, 0x24, 0x49, 0x95, 0x56, 0xf0, 0x49, 0x93, 0x33, 0x69, + 0xf5, 0x62, 0xba, 0xb3, 0x15, 0xa9, 0x48, 0x99, 0x7b, 0xb7, 0x8a, 0xea, 0xd4, 0x91, 0x04, 0xf6, + 0xb1, 0x0c, 0xd9, 0x19, 0x0b, 0xbd, 0x58, 0xd1, 0x39, 0xdc, 0x06, 0x3d, 0xce, 0x44, 0xc4, 0xf5, + 0xc0, 0x1a, 0x5a, 0xe3, 0xae, 0xdf, 0x9c, 0xe0, 0x18, 0x3c, 0x8a, 0x71, 0xa6, 0x11, 0x55, 0x8b, + 0x85, 0xd0, 0x88, 0xe3, 0x8c, 0x0f, 0x36, 0x86, 0xd6, 0xd8, 0xf6, 0x1f, 0x54, 0xfa, 0x5b, 0x23, + 0x1f, 0xe1, 0x8c, 0xc3, 0xe7, 0xe0, 0x5e, 0x8d, 0xfd, 0xc2, 0xc2, 0xc1, 0x9d, 0xa1, 0x35, 0xee, + 0xfb, 0x6b, 0x61, 0xf4, 0xad, 0x0b, 0xfa, 0x87, 0x85, 0x08, 0x99, 0xa4, 0x0c, 0x9e, 0x00, 0x50, + 0xe0, 0x18, 0x11, 0x4d, 0x51, 0x32, 0x37, 0x40, 0xdb, 0x7b, 0x75, 0x71, 0xb9, 0xfb, 0x32, 0x12, + 0x9a, 0xe7, 0x64, 0x42, 0xd5, 0xc2, 0x6d, 0xac, 0x50, 0x8e, 0x85, 0xbc, 0x3a, 0xb8, 0xba, 0x4c, + 0x58, 0x36, 0xf1, 0x8e, 0x67, 0xfb, 0x07, 0x7b, 0xb3, 0x9c, 0x7c, 0x60, 0xa5, 0xdf, 0x2f, 0x70, + 0xec, 0x69, 0x3a, 0x9b, 0xc3, 0x17, 0xc0, 0x26, 0x95, 0x17, 0xd4, 0x18, 0xd9, 0x30, 0x46, 0xee, + 0x1b, 0xed, 0xa8, 0x76, 0x13, 0x80, 0x7e, 0x92, 0x13, 0x94, 0x62, 0x59, 0x8f, 0x68, 0x7b, 0xaf, + 0x2f, 0x2e, 0x77, 0x0f, 0xfe, 0x0d, 0x1b, 0x50, 0x2e, 0x55, 0x9a, 0xce, 0x72, 0xe2, 0x63, 0x19, + 0xfa, 0x9b, 0x49, 0x1d, 0xc0, 0x37, 0x60, 0x87, 0x62, 0xa9, 0xa4, 0xa0, 0x38, 0x46, 0x37, 0x96, + 0xd5, 0x35, 0xcb, 0x7a, 0xd6, 0x66, 0x7c, 0xbc, 0xbe, 0xb5, 0x29, 0x78, 0x7a, 0xaa, 0xd2, 0xf9, + 0xcd, 0xba, 0xbb, 0xa6, 0x0e, 0x56, 0x97, 0x7f, 0x94, 0x48, 0xb0, 0xbd, 0xe6, 0x5d, 0x7d, 0x69, + 0x94, 0x89, 0x68, 0xd0, 0xfb, 0x4f, 0x4b, 0x87, 0x9f, 0x4e, 0x82, 0x40, 0x44, 0xfe, 0x56, 0xdb, + 0xf7, 0x5d, 0xd3, 0x36, 0x10, 0x11, 0x0c, 0xc1, 0x63, 0x33, 0xe2, 0x35, 0xd4, 0xe6, 0x2d, 0x51, + 0x0f, 0xab, 0x96, 0xbf, 0x51, 0xbc, 0xf7, 0xdf, 0x97, 0x8e, 0x75, 0xbe, 0x74, 0xac, 0x9f, 0x4b, + 0xc7, 0xfa, 0xba, 0x72, 0x3a, 0xe7, 0x2b, 0xa7, 0xf3, 0x63, 0xe5, 0x74, 0x3e, 0xef, 0xfd, 0x0d, + 0x70, 0xb6, 0x7e, 0x13, 0x86, 0x45, 0x7a, 0xe6, 0x1f, 0xdf, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, + 0x5e, 0x45, 0x73, 0x06, 0x34, 0x03, 0x00, 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { @@ -257,22 +272,53 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.FinalitySig != nil { + if m.ForkFinalitySig != nil { + { + size := m.ForkFinalitySig.Size() + i -= size + if _, err := m.ForkFinalitySig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.CanonicalFinalitySig != nil { { - size := m.FinalitySig.Size() + size := m.CanonicalFinalitySig.Size() i -= size - if _, err := m.FinalitySig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CanonicalFinalitySig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintFinality(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x32 + } + if len(m.ForkLastCommitHash) > 0 { + i -= len(m.ForkLastCommitHash) + copy(dAtA[i:], m.ForkLastCommitHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.ForkLastCommitHash))) + i-- + dAtA[i] = 0x2a + } + if len(m.CanonicalLastCommitHash) > 0 { + i -= len(m.CanonicalLastCommitHash) + copy(dAtA[i:], m.CanonicalLastCommitHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.CanonicalLastCommitHash))) + i-- dAtA[i] = 0x22 } - if len(m.BlockLastCommitHash) > 0 { - i -= len(m.BlockLastCommitHash) - copy(dAtA[i:], m.BlockLastCommitHash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.BlockLastCommitHash))) + if m.PubRand != nil { + { + size := m.PubRand.Size() + i -= size + if _, err := m.PubRand.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintFinality(dAtA, i, uint64(size)) + } i-- dAtA[i] = 0x1a } @@ -339,12 +385,24 @@ func (m *Evidence) Size() (n int) { if m.BlockHeight != 0 { n += 1 + sovFinality(uint64(m.BlockHeight)) } - l = len(m.BlockLastCommitHash) + if m.PubRand != nil { + l = m.PubRand.Size() + n += 1 + l + sovFinality(uint64(l)) + } + l = len(m.CanonicalLastCommitHash) + if l > 0 { + n += 1 + l + sovFinality(uint64(l)) + } + l = len(m.ForkLastCommitHash) if l > 0 { n += 1 + l + sovFinality(uint64(l)) } - if m.FinalitySig != nil { - l = m.FinalitySig.Size() + if m.CanonicalFinalitySig != nil { + l = m.CanonicalFinalitySig.Size() + n += 1 + l + sovFinality(uint64(l)) + } + if m.ForkFinalitySig != nil { + l = m.ForkFinalitySig.Size() n += 1 + l + sovFinality(uint64(l)) } return n @@ -564,7 +622,7 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockLastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field PubRand", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -591,14 +649,118 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BlockLastCommitHash = append(m.BlockLastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.BlockLastCommitHash == nil { - m.BlockLastCommitHash = []byte{} + var v github_com_babylonchain_babylon_types.SchnorrPubRand + m.PubRand = &v + if err := m.PubRand.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FinalitySig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CanonicalLastCommitHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CanonicalLastCommitHash = append(m.CanonicalLastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.CanonicalLastCommitHash == nil { + m.CanonicalLastCommitHash = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ForkLastCommitHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ForkLastCommitHash = append(m.ForkLastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.ForkLastCommitHash == nil { + m.ForkLastCommitHash = []byte{} + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CanonicalFinalitySig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFinality + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFinality + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFinality + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.SchnorrEOTSSig + m.CanonicalFinalitySig = &v + if err := m.CanonicalFinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ForkFinalitySig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -626,8 +788,8 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.SchnorrEOTSSig - m.FinalitySig = &v - if err := m.FinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ForkFinalitySig = &v + if err := m.ForkFinalitySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index 67212bc9e..69338cfe9 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -452,6 +452,100 @@ func (m *QueryVotesAtHeightResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryVotesAtHeightResponse proto.InternalMessageInfo +// QueryEvidenceRequest is the request type for the +// Query/Evidence RPC method. +type QueryEvidenceRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK + // (in BIP340 format) of the BTC validator + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` +} + +func (m *QueryEvidenceRequest) Reset() { *m = QueryEvidenceRequest{} } +func (m *QueryEvidenceRequest) String() string { return proto.CompactTextString(m) } +func (*QueryEvidenceRequest) ProtoMessage() {} +func (*QueryEvidenceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{8} +} +func (m *QueryEvidenceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEvidenceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEvidenceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEvidenceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEvidenceRequest.Merge(m, src) +} +func (m *QueryEvidenceRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryEvidenceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEvidenceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEvidenceRequest proto.InternalMessageInfo + +func (m *QueryEvidenceRequest) GetValBtcPkHex() string { + if m != nil { + return m.ValBtcPkHex + } + return "" +} + +// QueryEvidenceResponse is the response type for the +// Query/Evidence RPC method. +type QueryEvidenceResponse struct { + Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` +} + +func (m *QueryEvidenceResponse) Reset() { *m = QueryEvidenceResponse{} } +func (m *QueryEvidenceResponse) String() string { return proto.CompactTextString(m) } +func (*QueryEvidenceResponse) ProtoMessage() {} +func (*QueryEvidenceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{9} +} +func (m *QueryEvidenceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEvidenceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEvidenceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEvidenceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEvidenceResponse.Merge(m, src) +} +func (m *QueryEvidenceResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryEvidenceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEvidenceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEvidenceResponse proto.InternalMessageInfo + +func (m *QueryEvidenceResponse) GetEvidence() *Evidence { + if m != nil { + return m.Evidence + } + return nil +} + func init() { proto.RegisterEnum("babylon.finality.v1.QueriedBlockStatus", QueriedBlockStatus_name, QueriedBlockStatus_value) proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") @@ -463,64 +557,70 @@ func init() { proto.RegisterType((*QueryListBlocksResponse)(nil), "babylon.finality.v1.QueryListBlocksResponse") proto.RegisterType((*QueryVotesAtHeightRequest)(nil), "babylon.finality.v1.QueryVotesAtHeightRequest") proto.RegisterType((*QueryVotesAtHeightResponse)(nil), "babylon.finality.v1.QueryVotesAtHeightResponse") + proto.RegisterType((*QueryEvidenceRequest)(nil), "babylon.finality.v1.QueryEvidenceRequest") + proto.RegisterType((*QueryEvidenceResponse)(nil), "babylon.finality.v1.QueryEvidenceResponse") } func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 824 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xf6, 0x38, 0x8d, 0xab, 0x4e, 0x92, 0x12, 0xa6, 0x56, 0x09, 0x2e, 0xd8, 0xee, 0x46, 0xd0, - 0xa8, 0x45, 0x3b, 0xb5, 0x53, 0xaa, 0x82, 0x84, 0xaa, 0x58, 0x34, 0xd4, 0xb4, 0x75, 0x97, 0x8d, - 0x84, 0x44, 0x2f, 0xd6, 0xec, 0x7a, 0x58, 0xaf, 0xbc, 0xde, 0xd9, 0xee, 0xce, 0xae, 0x6c, 0x55, - 0x91, 0x10, 0x67, 0x24, 0x90, 0x90, 0xb8, 0xe5, 0x00, 0x7f, 0x4d, 0x8e, 0x91, 0xb8, 0xa0, 0x1c, - 0x22, 0x94, 0xf0, 0x87, 0xa0, 0x9d, 0x19, 0xdb, 0x31, 0xac, 0x63, 0x83, 0x72, 0xdb, 0x99, 0xf9, - 0xde, 0x7b, 0xdf, 0xfb, 0xde, 0x8f, 0x85, 0x15, 0x8b, 0x58, 0x43, 0x8f, 0xf9, 0xf8, 0x5b, 0xd7, - 0x27, 0x9e, 0xcb, 0x87, 0x38, 0xa9, 0xe1, 0xd7, 0x31, 0x0d, 0x87, 0x7a, 0x10, 0x32, 0xce, 0xd0, - 0x0d, 0x05, 0xd0, 0x47, 0x00, 0x3d, 0xa9, 0x95, 0x8a, 0x0e, 0x73, 0x98, 0x78, 0xc7, 0xe9, 0x97, - 0x84, 0x96, 0xde, 0x73, 0x18, 0x73, 0x3c, 0x8a, 0x49, 0xe0, 0x62, 0xe2, 0xfb, 0x8c, 0x13, 0xee, - 0x32, 0x3f, 0x52, 0xaf, 0x77, 0x6d, 0x16, 0xf5, 0x59, 0x84, 0x2d, 0x12, 0x51, 0x19, 0x01, 0x27, - 0x35, 0x8b, 0x72, 0x52, 0xc3, 0x01, 0x71, 0x5c, 0x5f, 0x80, 0x15, 0xb6, 0x9a, 0xc5, 0x2a, 0x20, - 0x21, 0xe9, 0x8f, 0xbc, 0x69, 0x59, 0x88, 0x31, 0x45, 0x81, 0xd1, 0x8a, 0x10, 0x7d, 0x95, 0xc6, - 0x31, 0x84, 0xa1, 0x49, 0x5f, 0xc7, 0x34, 0xe2, 0x9a, 0x01, 0x6f, 0x4c, 0xdd, 0x46, 0x01, 0xf3, - 0x23, 0x8a, 0x3e, 0x81, 0x05, 0x19, 0x60, 0x03, 0x54, 0xc1, 0xd6, 0x4a, 0xfd, 0x96, 0x9e, 0x91, - 0xb8, 0x2e, 0x8d, 0x1a, 0x57, 0x0e, 0x4f, 0x2a, 0x39, 0x53, 0x19, 0x68, 0x3f, 0x02, 0x58, 0x15, - 0x2e, 0x9f, 0xbb, 0x11, 0x37, 0x62, 0xcb, 0x73, 0x6d, 0x93, 0xf8, 0x1d, 0xd6, 0xf7, 0x69, 0x34, - 0x0a, 0x8b, 0x36, 0xe1, 0xf5, 0x84, 0x78, 0x6d, 0x8b, 0xdb, 0xed, 0xa0, 0xd7, 0xee, 0xd2, 0x81, - 0x88, 0x73, 0xcd, 0x5c, 0x49, 0x88, 0xd7, 0xe0, 0xb6, 0xd1, 0x7b, 0x4a, 0x07, 0x68, 0x17, 0xc2, - 0x89, 0x16, 0x1b, 0x79, 0x41, 0xe4, 0x43, 0x5d, 0x0a, 0xa7, 0xa7, 0xc2, 0xe9, 0xb2, 0x34, 0x4a, - 0x38, 0xdd, 0x20, 0x0e, 0x55, 0x01, 0xcc, 0x73, 0x96, 0xda, 0x51, 0x1e, 0xde, 0xbe, 0x80, 0x91, - 0x4a, 0xf9, 0x37, 0x00, 0x57, 0x83, 0xd8, 0x6a, 0x87, 0xc4, 0xef, 0xb4, 0xfb, 0x24, 0xd8, 0x00, - 0xd5, 0xa5, 0xad, 0x95, 0xfa, 0x6e, 0x66, 0xe6, 0x73, 0xdd, 0xe9, 0x46, 0x6c, 0xa5, 0xb7, 0x2f, - 0x48, 0xf0, 0xc4, 0xe7, 0xe1, 0xb0, 0xf1, 0xe8, 0xf8, 0xa4, 0xf2, 0xc0, 0x71, 0x79, 0x37, 0xb6, - 0x74, 0x9b, 0xf5, 0xb1, 0xf2, 0x6a, 0x77, 0x89, 0xeb, 0x8f, 0x0e, 0x98, 0x0f, 0x03, 0x1a, 0xe9, - 0x7b, 0x76, 0xd7, 0x67, 0x61, 0xa8, 0x3c, 0x98, 0x30, 0x18, 0xbb, 0x42, 0x5f, 0x64, 0x48, 0x72, - 0x67, 0xae, 0x24, 0x92, 0xd2, 0x79, 0x4d, 0x4a, 0x9f, 0xc1, 0xb7, 0xfe, 0xc1, 0x10, 0xad, 0xc3, - 0xa5, 0x1e, 0x1d, 0x8a, 0x42, 0x5c, 0x31, 0xd3, 0x4f, 0x54, 0x84, 0xcb, 0x09, 0xf1, 0x62, 0x2a, - 0x02, 0xad, 0x9a, 0xf2, 0xf0, 0x69, 0xfe, 0x11, 0xd0, 0x7e, 0x05, 0xf0, 0xe6, 0x58, 0x83, 0x86, - 0xc7, 0xec, 0xde, 0xb8, 0xb4, 0x8f, 0x61, 0x21, 0xe2, 0x84, 0xc7, 0xb2, 0x75, 0xae, 0xd7, 0xef, - 0xcc, 0x14, 0xd0, 0xa5, 0x1d, 0x61, 0xba, 0x27, 0xe0, 0xa6, 0x32, 0xbb, 0xb4, 0xb2, 0x1f, 0x00, - 0xf8, 0xce, 0xbf, 0x38, 0x4e, 0xfa, 0xdb, 0x12, 0x37, 0xaa, 0xca, 0xb7, 0x33, 0x49, 0x36, 0xfd, - 0x0e, 0x1d, 0x28, 0x92, 0xa6, 0x32, 0xb8, 0xb4, 0x12, 0x68, 0xdb, 0xf0, 0x5d, 0x41, 0xef, 0x6b, - 0xc6, 0x69, 0xb4, 0xc3, 0x9f, 0x52, 0xd7, 0xe9, 0xf2, 0x91, 0x8a, 0x37, 0x61, 0xa1, 0x2b, 0x2e, - 0x54, 0x3d, 0xd4, 0x49, 0xeb, 0xc3, 0x52, 0x96, 0x91, 0x4a, 0xeb, 0x25, 0xbc, 0x2a, 0x47, 0x4a, - 0xe6, 0xb5, 0xda, 0x78, 0x78, 0x7c, 0x52, 0xa9, 0x2f, 0xd6, 0x75, 0x8d, 0xa6, 0xb1, 0xfd, 0xe0, - 0xbe, 0x11, 0x5b, 0xcf, 0xe8, 0xd0, 0x2c, 0x58, 0xe9, 0x10, 0x46, 0x77, 0x1f, 0xcb, 0xa5, 0x31, - 0x5d, 0x29, 0xf4, 0x36, 0x5c, 0x6b, 0xbd, 0x6c, 0xb5, 0x77, 0x9b, 0xad, 0x9d, 0xe7, 0xcd, 0x57, - 0x4f, 0x3e, 0x5f, 0xcf, 0xa1, 0x35, 0x78, 0x6d, 0x72, 0x04, 0xe8, 0x2a, 0x5c, 0xda, 0x69, 0x7d, - 0xb3, 0x9e, 0xaf, 0xff, 0xb2, 0x0c, 0x97, 0x05, 0x61, 0xf4, 0x1d, 0x80, 0x05, 0xb9, 0x30, 0xd0, - 0xec, 0x96, 0x98, 0xde, 0x4e, 0xa5, 0xad, 0xf9, 0x40, 0x99, 0xb9, 0xb6, 0xf9, 0xfd, 0xef, 0x7f, - 0xfd, 0x9c, 0x7f, 0x1f, 0xdd, 0xc2, 0xb3, 0x97, 0x25, 0x3a, 0x06, 0xb0, 0x98, 0x35, 0xb4, 0xe8, - 0xe3, 0xff, 0x3a, 0xe4, 0x92, 0xde, 0xc3, 0xff, 0xb7, 0x1b, 0xb4, 0x3d, 0x41, 0xf6, 0x05, 0x7a, - 0x96, 0x49, 0x36, 0xad, 0x60, 0x42, 0x3c, 0xb7, 0x43, 0x38, 0x0b, 0x23, 0xfc, 0x66, 0x7a, 0x51, - 0xee, 0xe3, 0x40, 0xb8, 0x15, 0x7b, 0x4a, 0xfa, 0x6d, 0x7b, 0x6e, 0xc4, 0xd1, 0x0f, 0x00, 0xc2, - 0x49, 0xa7, 0xa3, 0x7b, 0x17, 0x73, 0x9b, 0x9a, 0xd9, 0xd2, 0x47, 0x8b, 0x81, 0x17, 0xd2, 0x5a, - 0x8d, 0xc9, 0x01, 0x80, 0x6b, 0x53, 0x4d, 0x8a, 0xf4, 0xd9, 0x41, 0xb2, 0x46, 0xa0, 0x84, 0x17, - 0xc6, 0x2b, 0x5e, 0xf7, 0x04, 0xaf, 0x0f, 0xd0, 0x66, 0x26, 0xaf, 0x24, 0xb5, 0xc1, 0x6f, 0xe4, - 0x1c, 0xed, 0x37, 0xbe, 0x3c, 0x3c, 0x2d, 0x83, 0xa3, 0xd3, 0x32, 0xf8, 0xf3, 0xb4, 0x0c, 0x7e, - 0x3a, 0x2b, 0xe7, 0x8e, 0xce, 0xca, 0xb9, 0x3f, 0xce, 0xca, 0xb9, 0x57, 0xf7, 0xe7, 0xcd, 0xcb, - 0x60, 0xe2, 0x57, 0x8c, 0x8e, 0x55, 0x10, 0x7f, 0xd8, 0xed, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, - 0x8e, 0x6e, 0x6c, 0x67, 0x3f, 0x08, 0x00, 0x00, + // 892 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0x38, 0xad, 0xdb, 0xbe, 0x24, 0x25, 0x4c, 0x4d, 0x09, 0x2e, 0x75, 0xd2, 0x8d, 0xa0, + 0x21, 0x45, 0x3b, 0x8d, 0x53, 0xaa, 0x02, 0xaa, 0xa2, 0x58, 0x24, 0x34, 0xb4, 0x4d, 0xcd, 0x46, + 0x42, 0xa2, 0x17, 0x6b, 0x76, 0x3d, 0xac, 0x57, 0x59, 0xef, 0x6c, 0x77, 0x67, 0x57, 0xb1, 0xaa, + 0x4a, 0x88, 0x33, 0x12, 0x48, 0x9c, 0x7b, 0x80, 0x13, 0x7f, 0x83, 0x5b, 0x8f, 0x91, 0xb8, 0xa0, + 0x1c, 0x22, 0x94, 0xf0, 0x43, 0xd0, 0xce, 0x8c, 0xed, 0x3a, 0x5d, 0x37, 0x6e, 0x95, 0x9b, 0x67, + 0xf6, 0x7b, 0xef, 0x7d, 0xf3, 0xbd, 0xf7, 0x3e, 0x19, 0xe6, 0x6c, 0x6a, 0x77, 0x7d, 0x1e, 0x90, + 0x1f, 0xbc, 0x80, 0xfa, 0x9e, 0xe8, 0x92, 0x74, 0x99, 0x3c, 0x49, 0x58, 0xd4, 0x35, 0xc3, 0x88, + 0x0b, 0x8e, 0x2f, 0x69, 0x80, 0xd9, 0x03, 0x98, 0xe9, 0x72, 0xa5, 0xec, 0x72, 0x97, 0xcb, 0xef, + 0x24, 0xfb, 0xa5, 0xa0, 0x95, 0x0f, 0x5d, 0xce, 0x5d, 0x9f, 0x11, 0x1a, 0x7a, 0x84, 0x06, 0x01, + 0x17, 0x54, 0x78, 0x3c, 0x88, 0xf5, 0xd7, 0x25, 0x87, 0xc7, 0x1d, 0x1e, 0x13, 0x9b, 0xc6, 0x4c, + 0x55, 0x20, 0xe9, 0xb2, 0xcd, 0x04, 0x5d, 0x26, 0x21, 0x75, 0xbd, 0x40, 0x82, 0x35, 0x76, 0x3e, + 0x8f, 0x55, 0x48, 0x23, 0xda, 0xe9, 0x65, 0x33, 0xf2, 0x10, 0x7d, 0x8a, 0x12, 0x63, 0x94, 0x01, + 0x7f, 0x9b, 0xd5, 0x69, 0xc8, 0x40, 0x8b, 0x3d, 0x49, 0x58, 0x2c, 0x8c, 0x06, 0x5c, 0x1a, 0xba, + 0x8d, 0x43, 0x1e, 0xc4, 0x0c, 0x7f, 0x0e, 0x25, 0x55, 0x60, 0x16, 0xcd, 0xa3, 0xc5, 0xc9, 0xda, + 0x15, 0x33, 0xe7, 0xe1, 0xa6, 0x0a, 0xaa, 0x9f, 0x79, 0x71, 0x30, 0x57, 0xb0, 0x74, 0x80, 0xf1, + 0x0b, 0x82, 0x79, 0x99, 0xf2, 0x81, 0x17, 0x8b, 0x46, 0x62, 0xfb, 0x9e, 0x63, 0xd1, 0xa0, 0xc5, + 0x3b, 0x01, 0x8b, 0x7b, 0x65, 0xf1, 0x02, 0x5c, 0x4c, 0xa9, 0xdf, 0xb4, 0x85, 0xd3, 0x0c, 0x77, + 0x9a, 0x6d, 0xb6, 0x2b, 0xeb, 0x5c, 0xb0, 0x26, 0x53, 0xea, 0xd7, 0x85, 0xd3, 0xd8, 0xb9, 0xc7, + 0x76, 0xf1, 0x06, 0xc0, 0x40, 0x8b, 0xd9, 0xa2, 0x24, 0xf2, 0xb1, 0xa9, 0x84, 0x33, 0x33, 0xe1, + 0x4c, 0xd5, 0x1a, 0x2d, 0x9c, 0xd9, 0xa0, 0x2e, 0xd3, 0x05, 0xac, 0x97, 0x22, 0x8d, 0xbd, 0x22, + 0x5c, 0x7b, 0x0d, 0x23, 0xfd, 0xe4, 0x3f, 0x10, 0x4c, 0x85, 0x89, 0xdd, 0x8c, 0x68, 0xd0, 0x6a, + 0x76, 0x68, 0x38, 0x8b, 0xe6, 0x27, 0x16, 0x27, 0x6b, 0x1b, 0xb9, 0x2f, 0x3f, 0x31, 0x9d, 0xd9, + 0x48, 0xec, 0xec, 0xf6, 0x21, 0x0d, 0xd7, 0x03, 0x11, 0x75, 0xeb, 0x77, 0xf6, 0x0f, 0xe6, 0x6e, + 0xb9, 0x9e, 0x68, 0x27, 0xb6, 0xe9, 0xf0, 0x0e, 0xd1, 0x59, 0x9d, 0x36, 0xf5, 0x82, 0xde, 0x81, + 0x88, 0x6e, 0xc8, 0x62, 0x73, 0xdb, 0x69, 0x07, 0x3c, 0x8a, 0x74, 0x06, 0x0b, 0xc2, 0x7e, 0x2a, + 0xfc, 0x75, 0x8e, 0x24, 0xd7, 0x4f, 0x94, 0x44, 0x51, 0x7a, 0x59, 0x93, 0xca, 0x5d, 0x78, 0xe7, + 0x18, 0x43, 0x3c, 0x03, 0x13, 0x3b, 0xac, 0x2b, 0x1b, 0x71, 0xc6, 0xca, 0x7e, 0xe2, 0x32, 0x9c, + 0x4d, 0xa9, 0x9f, 0x30, 0x59, 0x68, 0xca, 0x52, 0x87, 0x2f, 0x8a, 0x77, 0x90, 0xf1, 0x3b, 0x82, + 0xcb, 0x7d, 0x0d, 0xea, 0x3e, 0x77, 0x76, 0xfa, 0xad, 0x5d, 0x85, 0x52, 0x2c, 0xa8, 0x48, 0xd4, + 0xe8, 0x5c, 0xac, 0x5d, 0x1f, 0x29, 0xa0, 0xc7, 0x5a, 0x32, 0x74, 0x5b, 0xc2, 0x2d, 0x1d, 0x76, + 0x6a, 0x6d, 0x7f, 0x8e, 0xe0, 0xfd, 0x57, 0x38, 0x0e, 0xe6, 0xdb, 0x96, 0x37, 0xba, 0xcb, 0xd7, + 0x72, 0x49, 0x6e, 0x06, 0x2d, 0xb6, 0xab, 0x49, 0x5a, 0x3a, 0xe0, 0xd4, 0x5a, 0x60, 0xac, 0xc0, + 0x07, 0x92, 0xde, 0x77, 0x5c, 0xb0, 0x78, 0x4d, 0xdc, 0x63, 0x9e, 0xdb, 0x16, 0x3d, 0x15, 0x2f, + 0x43, 0xa9, 0x2d, 0x2f, 0x74, 0x3f, 0xf4, 0xc9, 0xe8, 0x40, 0x25, 0x2f, 0x48, 0x3f, 0xeb, 0x11, + 0x9c, 0x53, 0x2b, 0xa5, 0xde, 0x35, 0x55, 0xbf, 0xbd, 0x7f, 0x30, 0x57, 0x1b, 0x6f, 0xea, 0xea, + 0x9b, 0x8d, 0x95, 0x5b, 0x37, 0x1b, 0x89, 0x7d, 0x9f, 0x75, 0xad, 0x92, 0x9d, 0x2d, 0x61, 0x6c, + 0x7c, 0x09, 0x65, 0x59, 0x6e, 0x3d, 0xf5, 0x5a, 0x2c, 0x70, 0xd8, 0x9b, 0xec, 0xaf, 0x61, 0xc1, + 0x7b, 0xc7, 0x82, 0xfb, 0xea, 0x9f, 0x67, 0xfa, 0x4e, 0xfb, 0xcb, 0xd5, 0x5c, 0xfd, 0xfb, 0x81, + 0x7d, 0xf8, 0xd2, 0xaa, 0x72, 0xb1, 0xe1, 0xd1, 0xc1, 0xef, 0xc2, 0xf4, 0xd6, 0xa3, 0xad, 0xe6, + 0xc6, 0xe6, 0xd6, 0xda, 0x83, 0xcd, 0xc7, 0xeb, 0x5f, 0xcd, 0x14, 0xf0, 0x34, 0x5c, 0x18, 0x1c, + 0x11, 0x3e, 0x07, 0x13, 0x6b, 0x5b, 0xdf, 0xcf, 0x14, 0x6b, 0x7f, 0x95, 0xe0, 0xac, 0x64, 0x85, + 0x7f, 0x44, 0x50, 0x52, 0x0e, 0x86, 0x47, 0xcf, 0xe8, 0xb0, 0x5d, 0x56, 0x16, 0x4f, 0x06, 0xaa, + 0x37, 0x1a, 0x0b, 0x3f, 0xfd, 0xfd, 0xdf, 0x6f, 0xc5, 0xab, 0xf8, 0x0a, 0x19, 0xed, 0xde, 0x78, + 0x1f, 0x41, 0x39, 0xcf, 0x45, 0xf0, 0x67, 0x6f, 0xea, 0x3a, 0x8a, 0xde, 0xed, 0xb7, 0x33, 0x2b, + 0x63, 0x5b, 0x92, 0x7d, 0x88, 0xef, 0xe7, 0x92, 0xcd, 0xba, 0x9c, 0x52, 0xdf, 0x6b, 0x51, 0xc1, + 0xa3, 0x98, 0x3c, 0x1d, 0xee, 0xfc, 0x33, 0x12, 0xca, 0xb4, 0xd2, 0x38, 0x55, 0xde, 0xa6, 0xef, + 0xc5, 0x02, 0xff, 0x8c, 0x00, 0x06, 0xab, 0x87, 0x6f, 0xbc, 0x9e, 0xdb, 0x90, 0x89, 0x54, 0x3e, + 0x1d, 0x0f, 0x3c, 0x96, 0xd6, 0x7a, 0x6f, 0x9f, 0x23, 0x98, 0x1e, 0xda, 0x1a, 0x6c, 0x8e, 0x2e, + 0x92, 0xb7, 0x93, 0x15, 0x32, 0x36, 0x5e, 0xf3, 0xba, 0x21, 0x79, 0x7d, 0x84, 0x17, 0x72, 0x79, + 0xa5, 0x59, 0x0c, 0x79, 0xaa, 0x16, 0xfb, 0x19, 0xfe, 0x13, 0xc1, 0xf9, 0xde, 0xc0, 0xe3, 0x4f, + 0x46, 0x97, 0x3a, 0xb6, 0x8a, 0x95, 0xa5, 0x71, 0xa0, 0x9a, 0xd0, 0xba, 0x24, 0xb4, 0x8a, 0xef, + 0xbe, 0x55, 0x9f, 0x7b, 0x4b, 0x58, 0xff, 0xe6, 0xc5, 0x61, 0x15, 0xed, 0x1d, 0x56, 0xd1, 0xbf, + 0x87, 0x55, 0xf4, 0xeb, 0x51, 0xb5, 0xb0, 0x77, 0x54, 0x2d, 0xfc, 0x73, 0x54, 0x2d, 0x3c, 0xbe, + 0x79, 0x92, 0xd7, 0xec, 0x0e, 0x2a, 0x4a, 0xdb, 0xb1, 0x4b, 0xf2, 0xdf, 0xc9, 0xca, 0xff, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x45, 0xbd, 0xba, 0x60, 0x7b, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -543,6 +643,8 @@ type QueryClient interface { ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) // VotesAtHeight queries BTC validators who have signed the block at given height. VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) + // Evidence queries the first evidence which can be used for extracting the BTC SK + Evidence(ctx context.Context, in *QueryEvidenceRequest, opts ...grpc.CallOption) (*QueryEvidenceResponse, error) } type queryClient struct { @@ -589,6 +691,15 @@ func (c *queryClient) VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightR return out, nil } +func (c *queryClient) Evidence(ctx context.Context, in *QueryEvidenceRequest, opts ...grpc.CallOption) (*QueryEvidenceResponse, error) { + out := new(QueryEvidenceResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/Evidence", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. @@ -599,6 +710,8 @@ type QueryServer interface { ListBlocks(context.Context, *QueryListBlocksRequest) (*QueryListBlocksResponse, error) // VotesAtHeight queries BTC validators who have signed the block at given height. VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) + // Evidence queries the first evidence which can be used for extracting the BTC SK + Evidence(context.Context, *QueryEvidenceRequest) (*QueryEvidenceResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -617,6 +730,9 @@ func (*UnimplementedQueryServer) ListBlocks(ctx context.Context, req *QueryListB func (*UnimplementedQueryServer) VotesAtHeight(ctx context.Context, req *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method VotesAtHeight not implemented") } +func (*UnimplementedQueryServer) Evidence(ctx context.Context, req *QueryEvidenceRequest) (*QueryEvidenceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Evidence not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -694,6 +810,24 @@ func _Query_VotesAtHeight_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Query_Evidence_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryEvidenceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Evidence(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/Evidence", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Evidence(ctx, req.(*QueryEvidenceRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.finality.v1.Query", HandlerType: (*QueryServer)(nil), @@ -714,6 +848,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "VotesAtHeight", Handler: _Query_VotesAtHeight_Handler, }, + { + MethodName: "Evidence", + Handler: _Query_Evidence_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/finality/v1/query.proto", @@ -1030,6 +1168,71 @@ func (m *QueryVotesAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *QueryEvidenceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEvidenceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEvidenceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryEvidenceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEvidenceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEvidenceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Evidence != nil { + { + size, err := m.Evidence.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1166,6 +1369,32 @@ func (m *QueryVotesAtHeightResponse) Size() (n int) { return n } +func (m *QueryEvidenceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValBtcPkHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryEvidenceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Evidence != nil { + l = m.Evidence.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2004,6 +2233,174 @@ func (m *QueryVotesAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryEvidenceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEvidenceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEvidenceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryEvidenceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEvidenceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEvidenceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index d42dd6da6..186c3e46f 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -213,6 +213,60 @@ func local_request_Query_VotesAtHeight_0(ctx context.Context, marshaler runtime. } +func request_Query_Evidence_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryEvidenceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := client.Evidence(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Evidence_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryEvidenceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := server.Evidence(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -311,6 +365,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_Evidence_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Evidence_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Evidence_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -432,6 +509,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_Evidence_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Evidence_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Evidence_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -443,6 +540,8 @@ var ( pattern_Query_ListBlocks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "blocks"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Evidence_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "evidence"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -453,4 +552,6 @@ var ( forward_Query_ListBlocks_0 = runtime.ForwardResponseMessage forward_Query_VotesAtHeight_0 = runtime.ForwardResponseMessage + + forward_Query_Evidence_0 = runtime.ForwardResponseMessage ) From dcf3b45516a1438b2e385f06b614ea22a92fa571 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 2 Aug 2023 08:02:26 +0800 Subject: [PATCH 049/202] btcstaking: compose witness of a slashing tx (#43) --- btcstaking/staking.go | 51 +++++++------- btcstaking/staking_test.go | 69 +------------------ testutil/bitcoin/utils.go | 72 ++++++++++++++++++++ testutil/datagen/btcstaking.go | 10 ++- x/btcstaking/types/btc_slashing_tx.go | 57 ++++++++++++++++ x/btcstaking/types/btc_slashing_tx_test.go | 78 ++++++++++++++++++++++ 6 files changed, 247 insertions(+), 90 deletions(-) create mode 100644 testutil/bitcoin/utils.go create mode 100644 x/btcstaking/types/btc_slashing_tx_test.go diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 2d59156f5..76340a9b0 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -404,38 +404,24 @@ func BuildStakingOutput( return wire.NewTxOut(int64(stAmount), pkScript), script, nil } -// BuildWitnessToSpendStakingOutput builds witness for spending staking as single staker -// Current assumptions: -// - staking output is the only input to the transaction -// - staking output is valid staking output -func BuildWitnessToSpendStakingOutput( - tx *wire.MsgTx, - stakingOutput *wire.TxOut, +// NewWitnessFromStakingScriptAndSignature creates witness for spending +// staking from the given staking script and the given signature of +// a single party in the multisig +func NewWitnessFromStakingScriptAndSignature( stakingScript []byte, - privKey *btcec.PrivateKey) (wire.TxWitness, error) { - + sig *schnorr.Signature, +) (wire.TxWitness, error) { + // get ctrlBlockBytes internalPubKey := UnspendableKeyPathInternalPubKey() - tapLeaf := txscript.NewBaseTapLeaf(stakingScript) - tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) - - ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock( - internalPubKey, - ) - + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(internalPubKey) ctrlBlockBytes, err := ctrlBlock.ToBytes() - - if err != nil { - return nil, err - } - - sig, err := SignTxWithOneScriptSpendInputFromTapLeaf(tx, stakingOutput, privKey, tapLeaf) - if err != nil { return nil, err } + // compose witness stack witnessStack := wire.TxWitness(make([][]byte, 3)) witnessStack[0] = sig.Serialize() witnessStack[1] = stakingScript @@ -443,6 +429,25 @@ func BuildWitnessToSpendStakingOutput( return witnessStack, nil } +// BuildWitnessToSpendStakingOutput builds witness for spending staking as single staker +// Current assumptions: +// - staking output is the only input to the transaction +// - staking output is valid staking output +func BuildWitnessToSpendStakingOutput( + slashingMsgTx *wire.MsgTx, // slashing tx + stakingOutput *wire.TxOut, + stakingScript []byte, + privKey *btcec.PrivateKey, +) (wire.TxWitness, error) { + tapLeaf := txscript.NewBaseTapLeaf(stakingScript) + sig, err := SignTxWithOneScriptSpendInputFromTapLeaf(slashingMsgTx, stakingOutput, privKey, tapLeaf) + if err != nil { + return nil, err + } + + return NewWitnessFromStakingScriptAndSignature(stakingScript, sig) +} + // ValidateStakingOutputPkScript validates that: // - provided output commits to the given script with unspendable spending key path // - provided script is valid staking script diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index d0ecb59a7..0bffc9f20 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -1,14 +1,13 @@ package btcstaking_test import ( - "bytes" - "fmt" "math" "math/rand" "testing" "time" "github.com/babylonchain/babylon/btcstaking" + btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -160,68 +159,6 @@ func FuzzGeneratingSignatureValidation(f *testing.F) { }) } -// Help function to assert the execution of a script engine. Copied from: -// https://github.com/lightningnetwork/lnd/blob/master/input/script_utils_test.go#L24 -func assertEngineExecution(t *testing.T, testNum int, valid bool, - newEngine func() (*txscript.Engine, error)) { - - t.Helper() - - // Get a new VM to execute. - vm, err := newEngine() - require.NoError(t, err, "unable to create engine") - - // Execute the VM, only go on to the step-by-step execution if - // it doesn't validate as expected. - vmErr := vm.Execute() - if valid == (vmErr == nil) { - return - } - - // Now that the execution didn't match what we expected, fetch a new VM - // to step through. - vm, err = newEngine() - require.NoError(t, err, "unable to create engine") - - // This buffer will trace execution of the Script, dumping out - // to stdout. - var debugBuf bytes.Buffer - - done := false - for !done { - dis, err := vm.DisasmPC() - if err != nil { - t.Fatalf("stepping (%v)\n", err) - } - debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) - - done, err = vm.Step() - if err != nil && valid { - t.Log(debugBuf.String()) - t.Fatalf("spend test case #%v failed, spend "+ - "should be valid: %v", testNum, err) - } else if err == nil && !valid && done { - t.Log(debugBuf.String()) - t.Fatalf("spend test case #%v succeed, spend "+ - "should be invalid: %v", testNum, err) - } - - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) - } - - // If we get to this point the unexpected case was not reached - // during step execution, which happens for some checks, like - // the clean-stack rule. - validity := "invalid" - if valid { - validity = "valid" - } - - t.Log(debugBuf.String()) - t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) -} - func TestStakingScriptExecutionSingleStaker(t *testing.T) { const ( stakingValue = btcutil.Amount(2 * 10e8) @@ -295,7 +232,7 @@ func TestStakingScriptExecutionSingleStaker(t *testing.T) { prevOutputFetcher, ) } - assertEngineExecution(t, 0, true, newEngine) + btctest.AssertEngineExecution(t, 0, true, newEngine) } func TestStakingScriptExecutionMulitSig(t *testing.T) { @@ -395,5 +332,5 @@ func TestStakingScriptExecutionMulitSig(t *testing.T) { prevOutputFetcher, ) } - assertEngineExecution(t, 0, true, newEngine) + btctest.AssertEngineExecution(t, 0, true, newEngine) } diff --git a/testutil/bitcoin/utils.go b/testutil/bitcoin/utils.go new file mode 100644 index 000000000..07f6c9999 --- /dev/null +++ b/testutil/bitcoin/utils.go @@ -0,0 +1,72 @@ +package bitcoin + +import ( + "bytes" + "fmt" + "testing" + + "github.com/btcsuite/btcd/txscript" + "github.com/stretchr/testify/require" +) + +// Help function to assert the execution of a script engine. Copied from: +// https://github.com/lightningnetwork/lnd/blob/master/input/script_utils_test.go#L24 +func AssertEngineExecution(t *testing.T, testNum int, valid bool, + newEngine func() (*txscript.Engine, error)) { + + t.Helper() + + // Get a new VM to execute. + vm, err := newEngine() + require.NoError(t, err, "unable to create engine") + + // Execute the VM, only go on to the step-by-step execution if + // it doesn't validate as expected. + vmErr := vm.Execute() + if valid == (vmErr == nil) { + return + } + + // Now that the execution didn't match what we expected, fetch a new VM + // to step through. + vm, err = newEngine() + require.NoError(t, err, "unable to create engine") + + // This buffer will trace execution of the Script, dumping out + // to stdout. + var debugBuf bytes.Buffer + + done := false + for !done { + dis, err := vm.DisasmPC() + if err != nil { + t.Fatalf("stepping (%v)\n", err) + } + debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) + + done, err = vm.Step() + if err != nil && valid { + t.Log(debugBuf.String()) + t.Fatalf("spend test case #%v failed, spend "+ + "should be valid: %v", testNum, err) + } else if err == nil && !valid && done { + t.Log(debugBuf.String()) + t.Fatalf("spend test case #%v succeed, spend "+ + "should be invalid: %v", testNum, err) + } + + debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) + debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + } + + // If we get to this point the unexpected case was not reached + // during step execution, which happens for some checks, like + // the clean-stack rule. + validity := "invalid" + if valid { + validity = "valid" + } + + t.Log(debugBuf.String()) + t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) +} diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index e3ad17596..b919b5c7d 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -87,7 +87,15 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, startHeigh }, nil } -func GenBTCStakingSlashingTx(r *rand.Rand, stakerSK *btcec.PrivateKey, validatorPK, juryPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { +func GenBTCStakingSlashingTx( + r *rand.Rand, + stakerSK *btcec.PrivateKey, + validatorPK *btcec.PublicKey, + juryPK *btcec.PublicKey, + stakingTimeBlocks uint16, + stakingValue int64, + slashingAddress string, +) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { btcNet := &chaincfg.SimNetParams stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 13cebbd97..623cc3ed8 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -3,6 +3,7 @@ package types import ( "bytes" "encoding/hex" + "fmt" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" @@ -134,3 +135,59 @@ func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount i *sig, ) } + +// ToMsgTxWithWitness generates a BTC slashing tx with witness from +// - the staking tx +// - validator signature +// - delegator signature +// - jury signature +func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *StakingTx, valSig, delSig, jurySig *bbn.BIP340Signature) (*wire.MsgTx, error) { + // get staking script + stakingScript := stakingTx.StakingScript + + // get Schnorr signatures + valSchnorrSig, err := valSig.ToBTCSig() + if err != nil { + return nil, fmt.Errorf("failed to convert BTC validator signature to Schnorr signature format: %w", err) + } + delSchnorrSig, err := delSig.ToBTCSig() + if err != nil { + return nil, fmt.Errorf("failed to convert BTC delegator signature to Schnorr signature format: %w", err) + } + jurySchnorrSig, err := jurySig.ToBTCSig() + if err != nil { + return nil, fmt.Errorf("failed to convert jury signature to Schnorr signature format: %w", err) + } + + // build witness from each signature + valWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, valSchnorrSig) + if err != nil { + return nil, fmt.Errorf("failed to build witness for BTC validator: %w", err) + } + delWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, delSchnorrSig) + if err != nil { + return nil, fmt.Errorf("failed to build witness for BTC delegator: %w", err) + } + juryWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, jurySchnorrSig) + if err != nil { + return nil, fmt.Errorf("failed to build witness for jury: %w", err) + } + + // To Construct valid witness, for multisig case we need: + // - jury signature - witnessJury[0] + // - validator signature - witnessValidator[0] + // - staker signature - witnessStaker[0] + // - empty signature - which is just an empty byte array which signals we are going to use multisig. + // This must be signature on top of the stack. + // - whole script - witnessStaker[1] (any other witness[1] will work as well) + // - control block - witnessStaker[2] (any other witness[2] will work as well) + slashingMsgTx, err := tx.ToMsgTx() + if err != nil { + return nil, fmt.Errorf("failed to convert slashing tx to Bitcoin format: %w", err) + } + slashingMsgTx.TxIn[0].Witness = [][]byte{ + juryWitness[0], valWitness[0], delWitness[0], []byte{}, delWitness[1], delWitness[2], + } + + return slashingMsgTx, nil +} diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go new file mode 100644 index 000000000..6537d72b8 --- /dev/null +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -0,0 +1,78 @@ +package types_test + +import ( + "math/rand" + "testing" + + btctest "github.com/babylonchain/babylon/testutil/bitcoin" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/stretchr/testify/require" +) + +func FuzzSlashingTxWithWitness(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + var ( + stakingValue = int64(2 * 10e8) + stakingTimeBlocks = uint16(5) + net = &chaincfg.SimNetParams + ) + + // slashing address and key paris + slashingAddr, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + // generate staking/slashing tx + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, valPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + require.NoError(t, err) + stakingOutInfo, err := stakingTx.GetStakingOutputInfo(net) + require.NoError(t, err) + stakingPkScript := stakingOutInfo.StakingPkScript + stakingMsgTx, err := stakingTx.ToMsgTx() + require.NoError(t, err) + + // sign slashing tx + valSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, valSK, net) + require.NoError(t, err) + delSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, delSK, net) + require.NoError(t, err) + jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, jurySK, net) + require.NoError(t, err) + + // verify signatures first + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, valPK, valSig) + require.NoError(t, err) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, delPK, delSig) + require.NoError(t, err) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, juryPK, jurySig) + require.NoError(t, err) + + // build slashing tx with witness + slashingMsgTxWithWitness, err := slashingTx.ToMsgTxWithWitness(stakingTx, valSig, delSig, jurySig) + require.NoError(t, err) + + // verify slashing tx with witness + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingPkScript, stakingValue, + ) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingPkScript, + slashingMsgTxWithWitness, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(slashingMsgTxWithWitness, prevOutputFetcher), stakingValue, + prevOutputFetcher, + ) + } + btctest.AssertEngineExecution(t, 0, true, newEngine) + }) +} From b3db59d61b1d0229b1c714499f4cc9c3fc07ccfc Mon Sep 17 00:00:00 2001 From: Cirrus Gai Date: Thu, 3 Aug 2023 10:29:35 +0800 Subject: [PATCH 050/202] feat: Add query for block at the given height (#45) --- proto/babylon/finality/v1/query.proto | 19 + test/e2e/btc_staking_e2e_test.go | 18 +- .../configurer/chain/queries_btcstaking.go | 15 +- x/finality/client/cli/query.go | 39 +- x/finality/keeper/grpc_query.go | 13 + x/finality/keeper/grpc_query_test.go | 38 +- x/finality/types/query.pb.go | 507 +++++++++++++++--- x/finality/types/query.pb.gw.go | 101 ++++ 8 files changed, 674 insertions(+), 76 deletions(-) diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 9d243d7dd..f9df8dcdb 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -20,6 +20,11 @@ service Query { rpc ListPublicRandomness(QueryListPublicRandomnessRequest) returns (QueryListPublicRandomnessResponse) { option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/public_randomness_list"; } + + // Block queries a block at a given height + rpc Block(QueryBlockRequest) returns (QueryBlockResponse) { + option (google.api.http).get = "/babylon/finality/v1/blocks/{height}"; + } // ListBlocks is a range query for blocks at a given status rpc ListBlocks(QueryListBlocksRequest) returns (QueryListBlocksResponse) { @@ -78,6 +83,20 @@ enum QueriedBlockStatus { ANY = 2; } +// QueryBlockRequest is the request type for the +// Query/Block RPC method. +message QueryBlockRequest { + // height is the height of the Babylon block + uint64 height = 1; +} + +// QueryBlockResponse is the response type for the +// Query/Block RPC method. +message QueryBlockResponse { + // block is the Babylon at the given height + IndexedBlock block = 1; +} + // QueryListBlocksRequest is the request type for the // Query/ListBlocks RPC method. message QueryListBlocksRequest { diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 7164b5f68..87fb7ede6 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -5,6 +5,12 @@ import ( "math/rand" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/test/e2e/configurer" "github.com/babylonchain/babylon/test/e2e/initialization" @@ -14,11 +20,6 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ftypes "github.com/babylonchain/babylon/x/finality/types" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" ) var ( @@ -233,7 +234,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat // submit finality signature nonValidatorNode.AddFinalitySig(btcVal.BtcPk, activatedHeight, blockToVote.LastCommitHash, eotsSig) - // ensure vote is eventually casted + // ensure vote is eventually cast nonValidatorNode.WaitForNextBlock() var votes []bbn.BIP340PubKey s.Eventually(func() bool { @@ -241,7 +242,10 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat return len(votes) > 0 }, time.Minute, time.Second*5) s.Equal(votes[0].MarshalHex(), btcVal.BtcPk.MarshalHex()) - // once the vote is castedm, ensure block is finalised + // once the vote is cast, ensure block is finalised + finalizedBlock := nonValidatorNode.QueryIndexedBlock(activatedHeight) + s.NotEmpty(finalizedBlock) + s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlock.LastCommitHash) finalizedBlocks := nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) s.NotEmpty(finalizedBlocks) s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 842ebedac..25a636166 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -4,11 +4,12 @@ import ( "fmt" "net/url" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/test/e2e/util" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ftypes "github.com/babylonchain/babylon/x/finality/types" - "github.com/stretchr/testify/require" ) func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { @@ -108,3 +109,15 @@ func (n *NodeConfig) QueryListBlocks(status ftypes.QueriedBlockStatus) []*ftypes return resp.Blocks } + +func (n *NodeConfig) QueryIndexedBlock(height uint64) *ftypes.IndexedBlock { + path := fmt.Sprintf("/babylon/finality/v1/blocks/%d", height) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp ftypes.QueryBlockResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.Block +} diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index b2d4fdb55..61c74f38b 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -6,12 +6,15 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/client" "github.com/spf13/cobra" + + "github.com/babylonchain/babylon/x/finality/types" ) -const flagQueriedBlockStatus = "queried-block-status" +const ( + flagQueriedBlockStatus = "queried-block-status" +) // GetQueryCmd returns the cli query commands for this module func GetQueryCmd(queryRoute string) *cobra.Command { @@ -26,6 +29,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdListPublicRandomness()) + cmd.AddCommand(CmdBlock()) cmd.AddCommand(CmdListBlocks()) cmd.AddCommand(CmdVotesAtHeight()) @@ -135,3 +139,34 @@ func CmdListBlocks() *cobra.Command { return cmd } + +func CmdBlock() *cobra.Command { + cmd := &cobra.Command{ + Use: "block [height]", + Short: "show the information of the block at a given height", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + queriedBlockHeight, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + res, err := queryClient.Block(cmd.Context(), &types.QueryBlockRequest{ + Height: queriedBlockHeight, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 597d95365..cf9aca842 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -50,6 +50,19 @@ func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPu return resp, nil } +func (k Keeper) Block(ctx context.Context, req *types.QueryBlockRequest) (*types.QueryBlockResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + sdkCtx := sdk.UnwrapSDKContext(ctx) + b, err := k.GetBlock(sdkCtx, req.Height) + if err != nil { + return nil, err + } + + return &types.QueryBlockResponse{Block: b}, nil +} + // ListBlocks returns a list of blocks at the given finalisation status func (k Keeper) ListBlocks(ctx context.Context, req *types.QueryListBlocksRequest) (*types.QueryListBlocksResponse, error) { if req == nil { diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 5bb59f635..f7c4519a4 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -4,13 +4,14 @@ import ( "math/rand" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/query" - "github.com/stretchr/testify/require" ) func FuzzListPublicRandomness(f *testing.F) { @@ -52,6 +53,37 @@ func FuzzListPublicRandomness(f *testing.F) { }) } +func FuzzBlock(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + height := datagen.RandomInt(r, 100) + lch := datagen.GenRandomByteArray(r, 32) + ib := &types.IndexedBlock{ + Height: height, + LastCommitHash: lch, + } + + if datagen.RandomInt(r, 2) == 1 { + ib.Finalized = true + } + + keeper.SetBlock(ctx, ib) + req := &types.QueryBlockRequest{ + Height: height, + } + resp, err := keeper.Block(ctx, req) + require.NoError(t, err) + require.Equal(t, height, resp.Block.Height) + require.Equal(t, lch, resp.Block.LastCommitHash) + }) +} + func FuzzListBlocks(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index 69338cfe9..b853c6b5d 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -252,6 +252,100 @@ func (m *QueryListPublicRandomnessResponse) GetPagination() *query.PageResponse return nil } +// QueryBlockRequest is the request type for the +// Query/Block RPC method. +type QueryBlockRequest struct { + // height is the height of the Babylon block + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *QueryBlockRequest) Reset() { *m = QueryBlockRequest{} } +func (m *QueryBlockRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBlockRequest) ProtoMessage() {} +func (*QueryBlockRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{4} +} +func (m *QueryBlockRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBlockRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBlockRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBlockRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBlockRequest.Merge(m, src) +} +func (m *QueryBlockRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBlockRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBlockRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBlockRequest proto.InternalMessageInfo + +func (m *QueryBlockRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +// QueryBlockResponse is the response type for the +// Query/Block RPC method. +type QueryBlockResponse struct { + // block is the Babylon at the given height + Block *IndexedBlock `protobuf:"bytes,1,opt,name=block,proto3" json:"block,omitempty"` +} + +func (m *QueryBlockResponse) Reset() { *m = QueryBlockResponse{} } +func (m *QueryBlockResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBlockResponse) ProtoMessage() {} +func (*QueryBlockResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{5} +} +func (m *QueryBlockResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBlockResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBlockResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBlockResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBlockResponse.Merge(m, src) +} +func (m *QueryBlockResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBlockResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBlockResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBlockResponse proto.InternalMessageInfo + +func (m *QueryBlockResponse) GetBlock() *IndexedBlock { + if m != nil { + return m.Block + } + return nil +} + // QueryListBlocksRequest is the request type for the // Query/ListBlocks RPC method. type QueryListBlocksRequest struct { @@ -265,7 +359,7 @@ func (m *QueryListBlocksRequest) Reset() { *m = QueryListBlocksRequest{} func (m *QueryListBlocksRequest) String() string { return proto.CompactTextString(m) } func (*QueryListBlocksRequest) ProtoMessage() {} func (*QueryListBlocksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{4} + return fileDescriptor_32bddab77af6fdae, []int{6} } func (m *QueryListBlocksRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -321,7 +415,7 @@ func (m *QueryListBlocksResponse) Reset() { *m = QueryListBlocksResponse func (m *QueryListBlocksResponse) String() string { return proto.CompactTextString(m) } func (*QueryListBlocksResponse) ProtoMessage() {} func (*QueryListBlocksResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{5} + return fileDescriptor_32bddab77af6fdae, []int{7} } func (m *QueryListBlocksResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -375,7 +469,7 @@ func (m *QueryVotesAtHeightRequest) Reset() { *m = QueryVotesAtHeightReq func (m *QueryVotesAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryVotesAtHeightRequest) ProtoMessage() {} func (*QueryVotesAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{6} + return fileDescriptor_32bddab77af6fdae, []int{8} } func (m *QueryVotesAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -423,7 +517,7 @@ func (m *QueryVotesAtHeightResponse) Reset() { *m = QueryVotesAtHeightRe func (m *QueryVotesAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryVotesAtHeightResponse) ProtoMessage() {} func (*QueryVotesAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{7} + return fileDescriptor_32bddab77af6fdae, []int{9} } func (m *QueryVotesAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -464,7 +558,7 @@ func (m *QueryEvidenceRequest) Reset() { *m = QueryEvidenceRequest{} } func (m *QueryEvidenceRequest) String() string { return proto.CompactTextString(m) } func (*QueryEvidenceRequest) ProtoMessage() {} func (*QueryEvidenceRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{8} + return fileDescriptor_32bddab77af6fdae, []int{10} } func (m *QueryEvidenceRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -510,7 +604,7 @@ func (m *QueryEvidenceResponse) Reset() { *m = QueryEvidenceResponse{} } func (m *QueryEvidenceResponse) String() string { return proto.CompactTextString(m) } func (*QueryEvidenceResponse) ProtoMessage() {} func (*QueryEvidenceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_32bddab77af6fdae, []int{9} + return fileDescriptor_32bddab77af6fdae, []int{11} } func (m *QueryEvidenceResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -553,6 +647,8 @@ func init() { proto.RegisterType((*QueryListPublicRandomnessRequest)(nil), "babylon.finality.v1.QueryListPublicRandomnessRequest") proto.RegisterType((*QueryListPublicRandomnessResponse)(nil), "babylon.finality.v1.QueryListPublicRandomnessResponse") proto.RegisterMapType((map[uint64]*github_com_babylonchain_babylon_types.SchnorrPubRand)(nil), "babylon.finality.v1.QueryListPublicRandomnessResponse.PubRandMapEntry") + proto.RegisterType((*QueryBlockRequest)(nil), "babylon.finality.v1.QueryBlockRequest") + proto.RegisterType((*QueryBlockResponse)(nil), "babylon.finality.v1.QueryBlockResponse") proto.RegisterType((*QueryListBlocksRequest)(nil), "babylon.finality.v1.QueryListBlocksRequest") proto.RegisterType((*QueryListBlocksResponse)(nil), "babylon.finality.v1.QueryListBlocksResponse") proto.RegisterType((*QueryVotesAtHeightRequest)(nil), "babylon.finality.v1.QueryVotesAtHeightRequest") @@ -564,63 +660,67 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 892 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x41, 0x6f, 0x1b, 0x45, - 0x14, 0xf6, 0x38, 0xad, 0xdb, 0xbe, 0x24, 0x25, 0x4c, 0x4d, 0x09, 0x2e, 0x75, 0xd2, 0x8d, 0xa0, - 0x21, 0x45, 0x3b, 0x8d, 0x53, 0xaa, 0x02, 0xaa, 0xa2, 0x58, 0x24, 0x34, 0xb4, 0x4d, 0xcd, 0x46, - 0x42, 0xa2, 0x17, 0x6b, 0x76, 0x3d, 0xac, 0x57, 0x59, 0xef, 0x6c, 0x77, 0x67, 0x57, 0xb1, 0xaa, - 0x4a, 0x88, 0x33, 0x12, 0x48, 0x9c, 0x7b, 0x80, 0x13, 0x7f, 0x83, 0x5b, 0x8f, 0x91, 0xb8, 0xa0, - 0x1c, 0x22, 0x94, 0xf0, 0x43, 0xd0, 0xce, 0x8c, 0xed, 0x3a, 0x5d, 0x37, 0x6e, 0x95, 0x9b, 0x67, - 0xf6, 0x7b, 0xef, 0x7d, 0xf3, 0xbd, 0xf7, 0x3e, 0x19, 0xe6, 0x6c, 0x6a, 0x77, 0x7d, 0x1e, 0x90, - 0x1f, 0xbc, 0x80, 0xfa, 0x9e, 0xe8, 0x92, 0x74, 0x99, 0x3c, 0x49, 0x58, 0xd4, 0x35, 0xc3, 0x88, - 0x0b, 0x8e, 0x2f, 0x69, 0x80, 0xd9, 0x03, 0x98, 0xe9, 0x72, 0xa5, 0xec, 0x72, 0x97, 0xcb, 0xef, - 0x24, 0xfb, 0xa5, 0xa0, 0x95, 0x0f, 0x5d, 0xce, 0x5d, 0x9f, 0x11, 0x1a, 0x7a, 0x84, 0x06, 0x01, - 0x17, 0x54, 0x78, 0x3c, 0x88, 0xf5, 0xd7, 0x25, 0x87, 0xc7, 0x1d, 0x1e, 0x13, 0x9b, 0xc6, 0x4c, - 0x55, 0x20, 0xe9, 0xb2, 0xcd, 0x04, 0x5d, 0x26, 0x21, 0x75, 0xbd, 0x40, 0x82, 0x35, 0x76, 0x3e, - 0x8f, 0x55, 0x48, 0x23, 0xda, 0xe9, 0x65, 0x33, 0xf2, 0x10, 0x7d, 0x8a, 0x12, 0x63, 0x94, 0x01, - 0x7f, 0x9b, 0xd5, 0x69, 0xc8, 0x40, 0x8b, 0x3d, 0x49, 0x58, 0x2c, 0x8c, 0x06, 0x5c, 0x1a, 0xba, - 0x8d, 0x43, 0x1e, 0xc4, 0x0c, 0x7f, 0x0e, 0x25, 0x55, 0x60, 0x16, 0xcd, 0xa3, 0xc5, 0xc9, 0xda, - 0x15, 0x33, 0xe7, 0xe1, 0xa6, 0x0a, 0xaa, 0x9f, 0x79, 0x71, 0x30, 0x57, 0xb0, 0x74, 0x80, 0xf1, - 0x0b, 0x82, 0x79, 0x99, 0xf2, 0x81, 0x17, 0x8b, 0x46, 0x62, 0xfb, 0x9e, 0x63, 0xd1, 0xa0, 0xc5, - 0x3b, 0x01, 0x8b, 0x7b, 0x65, 0xf1, 0x02, 0x5c, 0x4c, 0xa9, 0xdf, 0xb4, 0x85, 0xd3, 0x0c, 0x77, - 0x9a, 0x6d, 0xb6, 0x2b, 0xeb, 0x5c, 0xb0, 0x26, 0x53, 0xea, 0xd7, 0x85, 0xd3, 0xd8, 0xb9, 0xc7, - 0x76, 0xf1, 0x06, 0xc0, 0x40, 0x8b, 0xd9, 0xa2, 0x24, 0xf2, 0xb1, 0xa9, 0x84, 0x33, 0x33, 0xe1, - 0x4c, 0xd5, 0x1a, 0x2d, 0x9c, 0xd9, 0xa0, 0x2e, 0xd3, 0x05, 0xac, 0x97, 0x22, 0x8d, 0xbd, 0x22, - 0x5c, 0x7b, 0x0d, 0x23, 0xfd, 0xe4, 0x3f, 0x10, 0x4c, 0x85, 0x89, 0xdd, 0x8c, 0x68, 0xd0, 0x6a, - 0x76, 0x68, 0x38, 0x8b, 0xe6, 0x27, 0x16, 0x27, 0x6b, 0x1b, 0xb9, 0x2f, 0x3f, 0x31, 0x9d, 0xd9, - 0x48, 0xec, 0xec, 0xf6, 0x21, 0x0d, 0xd7, 0x03, 0x11, 0x75, 0xeb, 0x77, 0xf6, 0x0f, 0xe6, 0x6e, - 0xb9, 0x9e, 0x68, 0x27, 0xb6, 0xe9, 0xf0, 0x0e, 0xd1, 0x59, 0x9d, 0x36, 0xf5, 0x82, 0xde, 0x81, - 0x88, 0x6e, 0xc8, 0x62, 0x73, 0xdb, 0x69, 0x07, 0x3c, 0x8a, 0x74, 0x06, 0x0b, 0xc2, 0x7e, 0x2a, - 0xfc, 0x75, 0x8e, 0x24, 0xd7, 0x4f, 0x94, 0x44, 0x51, 0x7a, 0x59, 0x93, 0xca, 0x5d, 0x78, 0xe7, - 0x18, 0x43, 0x3c, 0x03, 0x13, 0x3b, 0xac, 0x2b, 0x1b, 0x71, 0xc6, 0xca, 0x7e, 0xe2, 0x32, 0x9c, - 0x4d, 0xa9, 0x9f, 0x30, 0x59, 0x68, 0xca, 0x52, 0x87, 0x2f, 0x8a, 0x77, 0x90, 0xf1, 0x3b, 0x82, - 0xcb, 0x7d, 0x0d, 0xea, 0x3e, 0x77, 0x76, 0xfa, 0xad, 0x5d, 0x85, 0x52, 0x2c, 0xa8, 0x48, 0xd4, - 0xe8, 0x5c, 0xac, 0x5d, 0x1f, 0x29, 0xa0, 0xc7, 0x5a, 0x32, 0x74, 0x5b, 0xc2, 0x2d, 0x1d, 0x76, - 0x6a, 0x6d, 0x7f, 0x8e, 0xe0, 0xfd, 0x57, 0x38, 0x0e, 0xe6, 0xdb, 0x96, 0x37, 0xba, 0xcb, 0xd7, - 0x72, 0x49, 0x6e, 0x06, 0x2d, 0xb6, 0xab, 0x49, 0x5a, 0x3a, 0xe0, 0xd4, 0x5a, 0x60, 0xac, 0xc0, - 0x07, 0x92, 0xde, 0x77, 0x5c, 0xb0, 0x78, 0x4d, 0xdc, 0x63, 0x9e, 0xdb, 0x16, 0x3d, 0x15, 0x2f, - 0x43, 0xa9, 0x2d, 0x2f, 0x74, 0x3f, 0xf4, 0xc9, 0xe8, 0x40, 0x25, 0x2f, 0x48, 0x3f, 0xeb, 0x11, - 0x9c, 0x53, 0x2b, 0xa5, 0xde, 0x35, 0x55, 0xbf, 0xbd, 0x7f, 0x30, 0x57, 0x1b, 0x6f, 0xea, 0xea, - 0x9b, 0x8d, 0x95, 0x5b, 0x37, 0x1b, 0x89, 0x7d, 0x9f, 0x75, 0xad, 0x92, 0x9d, 0x2d, 0x61, 0x6c, - 0x7c, 0x09, 0x65, 0x59, 0x6e, 0x3d, 0xf5, 0x5a, 0x2c, 0x70, 0xd8, 0x9b, 0xec, 0xaf, 0x61, 0xc1, - 0x7b, 0xc7, 0x82, 0xfb, 0xea, 0x9f, 0x67, 0xfa, 0x4e, 0xfb, 0xcb, 0xd5, 0x5c, 0xfd, 0xfb, 0x81, - 0x7d, 0xf8, 0xd2, 0xaa, 0x72, 0xb1, 0xe1, 0xd1, 0xc1, 0xef, 0xc2, 0xf4, 0xd6, 0xa3, 0xad, 0xe6, - 0xc6, 0xe6, 0xd6, 0xda, 0x83, 0xcd, 0xc7, 0xeb, 0x5f, 0xcd, 0x14, 0xf0, 0x34, 0x5c, 0x18, 0x1c, - 0x11, 0x3e, 0x07, 0x13, 0x6b, 0x5b, 0xdf, 0xcf, 0x14, 0x6b, 0x7f, 0x95, 0xe0, 0xac, 0x64, 0x85, - 0x7f, 0x44, 0x50, 0x52, 0x0e, 0x86, 0x47, 0xcf, 0xe8, 0xb0, 0x5d, 0x56, 0x16, 0x4f, 0x06, 0xaa, - 0x37, 0x1a, 0x0b, 0x3f, 0xfd, 0xfd, 0xdf, 0x6f, 0xc5, 0xab, 0xf8, 0x0a, 0x19, 0xed, 0xde, 0x78, - 0x1f, 0x41, 0x39, 0xcf, 0x45, 0xf0, 0x67, 0x6f, 0xea, 0x3a, 0x8a, 0xde, 0xed, 0xb7, 0x33, 0x2b, - 0x63, 0x5b, 0x92, 0x7d, 0x88, 0xef, 0xe7, 0x92, 0xcd, 0xba, 0x9c, 0x52, 0xdf, 0x6b, 0x51, 0xc1, - 0xa3, 0x98, 0x3c, 0x1d, 0xee, 0xfc, 0x33, 0x12, 0xca, 0xb4, 0xd2, 0x38, 0x55, 0xde, 0xa6, 0xef, - 0xc5, 0x02, 0xff, 0x8c, 0x00, 0x06, 0xab, 0x87, 0x6f, 0xbc, 0x9e, 0xdb, 0x90, 0x89, 0x54, 0x3e, - 0x1d, 0x0f, 0x3c, 0x96, 0xd6, 0x7a, 0x6f, 0x9f, 0x23, 0x98, 0x1e, 0xda, 0x1a, 0x6c, 0x8e, 0x2e, - 0x92, 0xb7, 0x93, 0x15, 0x32, 0x36, 0x5e, 0xf3, 0xba, 0x21, 0x79, 0x7d, 0x84, 0x17, 0x72, 0x79, - 0xa5, 0x59, 0x0c, 0x79, 0xaa, 0x16, 0xfb, 0x19, 0xfe, 0x13, 0xc1, 0xf9, 0xde, 0xc0, 0xe3, 0x4f, - 0x46, 0x97, 0x3a, 0xb6, 0x8a, 0x95, 0xa5, 0x71, 0xa0, 0x9a, 0xd0, 0xba, 0x24, 0xb4, 0x8a, 0xef, - 0xbe, 0x55, 0x9f, 0x7b, 0x4b, 0x58, 0xff, 0xe6, 0xc5, 0x61, 0x15, 0xed, 0x1d, 0x56, 0xd1, 0xbf, - 0x87, 0x55, 0xf4, 0xeb, 0x51, 0xb5, 0xb0, 0x77, 0x54, 0x2d, 0xfc, 0x73, 0x54, 0x2d, 0x3c, 0xbe, - 0x79, 0x92, 0xd7, 0xec, 0x0e, 0x2a, 0x4a, 0xdb, 0xb1, 0x4b, 0xf2, 0xdf, 0xc9, 0xca, 0xff, 0x01, - 0x00, 0x00, 0xff, 0xff, 0x45, 0xbd, 0xba, 0x60, 0x7b, 0x09, 0x00, 0x00, + // 951 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x41, 0x4f, 0x1b, 0x47, + 0x14, 0xf6, 0x98, 0x60, 0x92, 0x07, 0xa4, 0x64, 0xe2, 0xa6, 0x74, 0xd3, 0x18, 0xb2, 0xb4, 0x40, + 0x21, 0xda, 0x0d, 0x26, 0x4d, 0xd3, 0x56, 0x11, 0xc2, 0x2a, 0x34, 0x34, 0x81, 0xb8, 0x8b, 0x54, + 0xa9, 0xb9, 0x58, 0xb3, 0xeb, 0xa9, 0xbd, 0x62, 0xbd, 0xb3, 0xf1, 0xce, 0xae, 0xb0, 0xa2, 0x48, + 0x55, 0x0f, 0x3d, 0x55, 0x6a, 0xa5, 0x9e, 0x73, 0x68, 0x4f, 0xfd, 0x29, 0x51, 0x4f, 0x48, 0xbd, + 0x54, 0x1c, 0x50, 0x05, 0xfd, 0x21, 0xd5, 0xce, 0x8c, 0x6d, 0x4c, 0xd6, 0xb1, 0x13, 0x71, 0xf3, + 0xcc, 0x7e, 0xef, 0xbd, 0xef, 0x7d, 0xef, 0xcd, 0x07, 0x30, 0x63, 0x13, 0xbb, 0xe5, 0x31, 0xdf, + 0xfc, 0xde, 0xf5, 0x89, 0xe7, 0xf2, 0x96, 0x19, 0xaf, 0x98, 0x4f, 0x23, 0xda, 0x6c, 0x19, 0x41, + 0x93, 0x71, 0x86, 0xaf, 0x2a, 0x80, 0xd1, 0x06, 0x18, 0xf1, 0x8a, 0x96, 0xaf, 0xb1, 0x1a, 0x13, + 0xdf, 0xcd, 0xe4, 0x97, 0x84, 0x6a, 0x1f, 0xd4, 0x18, 0xab, 0x79, 0xd4, 0x24, 0x81, 0x6b, 0x12, + 0xdf, 0x67, 0x9c, 0x70, 0x97, 0xf9, 0xa1, 0xfa, 0xba, 0xe4, 0xb0, 0xb0, 0xc1, 0x42, 0xd3, 0x26, + 0x21, 0x95, 0x15, 0xcc, 0x78, 0xc5, 0xa6, 0x9c, 0xac, 0x98, 0x01, 0xa9, 0xb9, 0xbe, 0x00, 0x2b, + 0xec, 0x6c, 0x1a, 0xab, 0x80, 0x34, 0x49, 0xa3, 0x9d, 0x4d, 0x4f, 0x43, 0x74, 0x28, 0x0a, 0x8c, + 0x9e, 0x07, 0xfc, 0x4d, 0x52, 0xa7, 0x2c, 0x02, 0x2d, 0xfa, 0x34, 0xa2, 0x21, 0xd7, 0xcb, 0x70, + 0xb5, 0xe7, 0x36, 0x0c, 0x98, 0x1f, 0x52, 0xfc, 0x19, 0xe4, 0x64, 0x81, 0x69, 0x34, 0x8b, 0x16, + 0xc7, 0x8b, 0xd7, 0x8d, 0x94, 0xc6, 0x0d, 0x19, 0x54, 0xba, 0xf0, 0xf2, 0x68, 0x26, 0x63, 0xa9, + 0x00, 0xfd, 0x17, 0x04, 0xb3, 0x22, 0xe5, 0x23, 0x37, 0xe4, 0xe5, 0xc8, 0xf6, 0x5c, 0xc7, 0x22, + 0x7e, 0x95, 0x35, 0x7c, 0x1a, 0xb6, 0xcb, 0xe2, 0x39, 0xb8, 0x1c, 0x13, 0xaf, 0x62, 0x73, 0xa7, + 0x12, 0xec, 0x55, 0xea, 0x74, 0x5f, 0xd4, 0xb9, 0x64, 0x8d, 0xc7, 0xc4, 0x2b, 0x71, 0xa7, 0xbc, + 0xf7, 0x80, 0xee, 0xe3, 0x4d, 0x80, 0xae, 0x16, 0xd3, 0x59, 0x41, 0x64, 0xde, 0x90, 0xc2, 0x19, + 0x89, 0x70, 0x86, 0x1c, 0x8d, 0x12, 0xce, 0x28, 0x93, 0x1a, 0x55, 0x05, 0xac, 0x53, 0x91, 0xfa, + 0x41, 0x16, 0x6e, 0xbe, 0x86, 0x91, 0x6a, 0xf9, 0x0f, 0x04, 0x13, 0x41, 0x64, 0x57, 0x9a, 0xc4, + 0xaf, 0x56, 0x1a, 0x24, 0x98, 0x46, 0xb3, 0x23, 0x8b, 0xe3, 0xc5, 0xcd, 0xd4, 0xce, 0x07, 0xa6, + 0x33, 0xca, 0x91, 0x9d, 0xdc, 0x6e, 0x93, 0x60, 0xc3, 0xe7, 0xcd, 0x56, 0xe9, 0xde, 0xe1, 0xd1, + 0xcc, 0x9d, 0x9a, 0xcb, 0xeb, 0x91, 0x6d, 0x38, 0xac, 0x61, 0xaa, 0xac, 0x4e, 0x9d, 0xb8, 0x7e, + 0xfb, 0x60, 0xf2, 0x56, 0x40, 0x43, 0x63, 0xd7, 0xa9, 0xfb, 0xac, 0xd9, 0x54, 0x19, 0x2c, 0x08, + 0x3a, 0xa9, 0xf0, 0x57, 0x29, 0x92, 0x2c, 0x0c, 0x94, 0x44, 0x52, 0x3a, 0xad, 0x89, 0x76, 0x1f, + 0xde, 0x39, 0xc3, 0x10, 0x4f, 0xc1, 0xc8, 0x1e, 0x6d, 0x89, 0x41, 0x5c, 0xb0, 0x92, 0x9f, 0x38, + 0x0f, 0xa3, 0x31, 0xf1, 0x22, 0x2a, 0x0a, 0x4d, 0x58, 0xf2, 0xf0, 0x79, 0xf6, 0x1e, 0xd2, 0x97, + 0xe1, 0x8a, 0x90, 0xa0, 0xe4, 0x31, 0x67, 0xaf, 0x3d, 0xd4, 0x6b, 0x90, 0xab, 0x53, 0xb7, 0x56, + 0xe7, 0x2a, 0x87, 0x3a, 0xe9, 0xdb, 0x6a, 0xf3, 0x14, 0x58, 0xe9, 0xfd, 0x29, 0x8c, 0xda, 0xc9, + 0x85, 0xda, 0xb0, 0x9b, 0xa9, 0x3a, 0x6f, 0xf9, 0x55, 0xba, 0x4f, 0xab, 0x32, 0x52, 0xe2, 0xf5, + 0xdf, 0x11, 0x5c, 0xeb, 0xe8, 0x2f, 0xbe, 0x74, 0xd6, 0x6a, 0x0d, 0x72, 0x21, 0x27, 0x3c, 0x92, + 0x6b, 0x7b, 0xb9, 0xb8, 0xd0, 0x77, 0x78, 0xae, 0x4a, 0xba, 0x2b, 0xe0, 0x96, 0x0a, 0x3b, 0xb7, + 0x95, 0x7b, 0x81, 0xe0, 0xbd, 0x57, 0x38, 0x76, 0xdf, 0x96, 0x68, 0x24, 0x54, 0x1b, 0x36, 0x44, + 0xe7, 0x2a, 0xe0, 0xdc, 0xc6, 0xaf, 0xaf, 0xc2, 0xfb, 0x82, 0xde, 0xb7, 0x8c, 0xd3, 0x70, 0x9d, + 0x3f, 0x10, 0x83, 0x1a, 0x34, 0xc7, 0x06, 0x68, 0x69, 0x41, 0xaa, 0xad, 0xc7, 0x30, 0x26, 0x9f, + 0xb3, 0xec, 0x6b, 0xa2, 0x74, 0xf7, 0xf0, 0x68, 0xa6, 0x38, 0xdc, 0xc6, 0x97, 0xb6, 0xca, 0xab, + 0x77, 0x6e, 0x97, 0x23, 0xfb, 0x21, 0x6d, 0x59, 0x39, 0x3b, 0x31, 0x80, 0x50, 0xff, 0x02, 0xf2, + 0xa2, 0xdc, 0x46, 0xec, 0x56, 0xa9, 0xef, 0xd0, 0x37, 0xf1, 0x0e, 0xdd, 0x82, 0x77, 0xcf, 0x04, + 0x77, 0xd4, 0xbf, 0x48, 0xd5, 0x9d, 0xda, 0xbc, 0x1b, 0xa9, 0xfa, 0x77, 0x02, 0x3b, 0xf0, 0xa5, + 0x35, 0xb9, 0xc7, 0xbd, 0xab, 0x83, 0xaf, 0xc0, 0xe4, 0xce, 0xe3, 0x9d, 0xca, 0xe6, 0xd6, 0xce, + 0xfa, 0xa3, 0xad, 0x27, 0x1b, 0x5f, 0x4e, 0x65, 0xf0, 0x24, 0x5c, 0xea, 0x1e, 0x11, 0x1e, 0x83, + 0x91, 0xf5, 0x9d, 0xef, 0xa6, 0xb2, 0xc5, 0xbf, 0xc6, 0x60, 0x54, 0xb0, 0xc2, 0x3f, 0x20, 0xc8, + 0x49, 0xf7, 0xc4, 0xfd, 0x77, 0xb4, 0xd7, 0xaa, 0xb5, 0xc5, 0xc1, 0x40, 0xd9, 0xa3, 0x3e, 0xf7, + 0xe3, 0xdf, 0xff, 0xfd, 0x96, 0xbd, 0x81, 0xaf, 0x9b, 0xfd, 0xff, 0x72, 0xe0, 0x43, 0x04, 0xf9, + 0x34, 0x07, 0xc3, 0x9f, 0xbc, 0xa9, 0xe3, 0x49, 0x7a, 0x77, 0xdf, 0xce, 0x28, 0xf5, 0x5d, 0x41, + 0x76, 0x1b, 0x3f, 0x4c, 0x25, 0x9b, 0x4c, 0x39, 0x26, 0x9e, 0x5b, 0x25, 0x9c, 0x35, 0x43, 0xf3, + 0x59, 0xef, 0xe4, 0x9f, 0x9b, 0x81, 0x48, 0x2b, 0x4c, 0x5b, 0xe6, 0xad, 0x78, 0x6e, 0xc8, 0xf1, + 0x4f, 0x08, 0x46, 0xc5, 0x90, 0xf0, 0x7c, 0x7f, 0x5a, 0xa7, 0xcd, 0x4b, 0x5b, 0x18, 0x88, 0x53, + 0x7c, 0x6f, 0x09, 0xbe, 0xf3, 0xf8, 0xc3, 0x74, 0xbe, 0xe2, 0xa1, 0x9a, 0xcf, 0xe4, 0x93, 0x79, + 0x8e, 0x7f, 0x46, 0x00, 0x5d, 0x0f, 0xc0, 0xcb, 0xaf, 0x17, 0xa9, 0xc7, 0xcd, 0xb4, 0x5b, 0xc3, + 0x81, 0x87, 0x1a, 0xba, 0x32, 0x90, 0x17, 0x08, 0x26, 0x7b, 0x9e, 0x2f, 0x36, 0xfa, 0x17, 0x49, + 0x33, 0x07, 0xcd, 0x1c, 0x1a, 0xaf, 0x78, 0x2d, 0x0b, 0x5e, 0x1f, 0xe1, 0xb9, 0x54, 0x5e, 0x71, + 0x12, 0xd3, 0x95, 0xeb, 0x4f, 0x04, 0x17, 0xdb, 0x2f, 0x0f, 0x7f, 0xdc, 0xbf, 0xd4, 0x19, 0x4f, + 0xd0, 0x96, 0x86, 0x81, 0x2a, 0x42, 0x1b, 0x82, 0xd0, 0x1a, 0xbe, 0xff, 0x56, 0x0b, 0xd7, 0x76, + 0x83, 0xd2, 0xd7, 0x2f, 0x8f, 0x0b, 0xe8, 0xe0, 0xb8, 0x80, 0xfe, 0x3d, 0x2e, 0xa0, 0x5f, 0x4f, + 0x0a, 0x99, 0x83, 0x93, 0x42, 0xe6, 0x9f, 0x93, 0x42, 0xe6, 0xc9, 0xed, 0x41, 0xa6, 0xb7, 0xdf, + 0xad, 0x28, 0xfc, 0xcf, 0xce, 0x89, 0x7f, 0xd1, 0x56, 0xff, 0x0f, 0x00, 0x00, 0xff, 0xff, 0xf5, + 0xfd, 0x8b, 0x8b, 0x80, 0x0a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -639,6 +739,8 @@ type QueryClient interface { Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) // ListPublicRandomness is a range query for public randomness of a given BTC validator ListPublicRandomness(ctx context.Context, in *QueryListPublicRandomnessRequest, opts ...grpc.CallOption) (*QueryListPublicRandomnessResponse, error) + // Block queries a block at a given height + Block(ctx context.Context, in *QueryBlockRequest, opts ...grpc.CallOption) (*QueryBlockResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) // VotesAtHeight queries BTC validators who have signed the block at given height. @@ -673,6 +775,15 @@ func (c *queryClient) ListPublicRandomness(ctx context.Context, in *QueryListPub return out, nil } +func (c *queryClient) Block(ctx context.Context, in *QueryBlockRequest, opts ...grpc.CallOption) (*QueryBlockResponse, error) { + out := new(QueryBlockResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/Block", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) { out := new(QueryListBlocksResponse) err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/ListBlocks", in, out, opts...) @@ -706,6 +817,8 @@ type QueryServer interface { Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) // ListPublicRandomness is a range query for public randomness of a given BTC validator ListPublicRandomness(context.Context, *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) + // Block queries a block at a given height + Block(context.Context, *QueryBlockRequest) (*QueryBlockResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(context.Context, *QueryListBlocksRequest) (*QueryListBlocksResponse, error) // VotesAtHeight queries BTC validators who have signed the block at given height. @@ -724,6 +837,9 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq func (*UnimplementedQueryServer) ListPublicRandomness(ctx context.Context, req *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListPublicRandomness not implemented") } +func (*UnimplementedQueryServer) Block(ctx context.Context, req *QueryBlockRequest) (*QueryBlockResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Block not implemented") +} func (*UnimplementedQueryServer) ListBlocks(ctx context.Context, req *QueryListBlocksRequest) (*QueryListBlocksResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListBlocks not implemented") } @@ -774,6 +890,24 @@ func _Query_ListPublicRandomness_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Query_Block_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBlockRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Block(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/Block", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Block(ctx, req.(*QueryBlockRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_ListBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryListBlocksRequest) if err := dec(in); err != nil { @@ -840,6 +974,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "ListPublicRandomness", Handler: _Query_ListPublicRandomness_Handler, }, + { + MethodName: "Block", + Handler: _Query_Block_Handler, + }, { MethodName: "ListBlocks", Handler: _Query_ListBlocks_Handler, @@ -1014,6 +1152,69 @@ func (m *QueryListPublicRandomnessResponse) MarshalToSizedBuffer(dAtA []byte) (i return len(dAtA) - i, nil } +func (m *QueryBlockRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBlockRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBlockRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryBlockResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBlockResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBlockResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Block != nil { + { + size, err := m.Block.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *QueryListBlocksRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1307,6 +1508,31 @@ func (m *QueryListPublicRandomnessResponse) Size() (n int) { return n } +func (m *QueryBlockRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n +} + +func (m *QueryBlockResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Block != nil { + l = m.Block.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func (m *QueryListBlocksRequest) Size() (n int) { if m == nil { return 0 @@ -1854,6 +2080,161 @@ func (m *QueryListPublicRandomnessResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBlockRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBlockRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBlockRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBlockResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBlockResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBlockResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Block", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Block == nil { + m.Block = &IndexedBlock{} + } + if err := m.Block.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryListBlocksRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index 186c3e46f..c1c2903d0 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -123,6 +123,60 @@ func local_request_Query_ListPublicRandomness_0(ctx context.Context, marshaler r } +func request_Query_Block_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBlockRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := client.Block(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Block_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBlockRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := server.Block(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_ListBlocks_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -319,6 +373,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_Block_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Block_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Block_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ListBlocks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -469,6 +546,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_Block_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Block_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Block_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ListBlocks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -537,6 +634,8 @@ var ( pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Block_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "blocks", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListBlocks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "blocks"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -549,6 +648,8 @@ var ( forward_Query_ListPublicRandomness_0 = runtime.ForwardResponseMessage + forward_Query_Block_0 = runtime.ForwardResponseMessage + forward_Query_ListBlocks_0 = runtime.ForwardResponseMessage forward_Query_VotesAtHeight_0 = runtime.ForwardResponseMessage From 7ec25a6109113e17e0eb7b89d60385e91f3def4f Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 3 Aug 2023 16:51:16 +0800 Subject: [PATCH 051/202] btcstaking: supporting concurrent delegations from a single delegator (#44) --- proto/babylon/btcstaking/v1/btcstaking.proto | 16 +- proto/babylon/btcstaking/v1/query.proto | 8 +- proto/babylon/btcstaking/v1/tx.proto | 5 +- test/e2e/btc_staking_e2e_test.go | 40 ++- .../configurer/chain/commands_btcstaking.go | 4 +- .../configurer/chain/queries_btcstaking.go | 6 +- testutil/datagen/btc_address.go | 3 +- testutil/datagen/btcstaking.go | 56 ++-- x/btcstaking/client/cli/query.go | 13 - x/btcstaking/client/cli/tx.go | 18 +- x/btcstaking/keeper/btc_delegations.go | 105 +++++-- x/btcstaking/keeper/grpc_query.go | 47 ++- x/btcstaking/keeper/grpc_query_test.go | 141 ++++----- x/btcstaking/keeper/msg_server.go | 30 +- x/btcstaking/keeper/msg_server_test.go | 14 +- x/btcstaking/keeper/voting_power_table.go | 17 +- .../keeper/voting_power_table_test.go | 21 +- x/btcstaking/types/btc_slashing_tx.go | 4 +- x/btcstaking/types/btcstaking.go | 95 ++++++ x/btcstaking/types/btcstaking.pb.go | 296 ++++++------------ x/btcstaking/types/msg.go | 4 + x/btcstaking/types/query.pb.go | 158 ++++------ x/btcstaking/types/tx.pb.go | 158 +++++++--- 23 files changed, 672 insertions(+), 587 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index d4a77ab46..ce8fde88e 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -68,19 +68,9 @@ message BTCDelegation { bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// BTCDelegationWithMeta wraps the BTCDelegation with meta data. -message BTCDelegationWithMeta { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // start_height is the start BTC height of the BTC delegation - // it is the start BTC height of the timelock - uint64 start_height = 2; - // end_height is the end height of the BTC delegation - // it is the end BTC height of the timelock - w - uint64 end_height = 3; - // voting_power is the voting power of this BTC delegation at the given height - uint64 voting_power = 4; +// BTCDelegations is a collection of BTC delegations, typically from the same delegator. +message BTCDelegations { + repeated BTCDelegation dels = 1; } // ProofOfPossession is the proof of possession that a Babylon secp256k1 diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 12989aa48..fbefb23b6 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -125,7 +125,6 @@ message QueryActiveBTCValidatorsAtHeightResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } - // QueryActivatedHeightRequest is the request type for the Query/ActivatedHeight RPC method. message QueryActivatedHeightRequest {} @@ -142,18 +141,15 @@ message QueryBTCValidatorDelegationsRequest { // the PK follows encoding in BIP-340 spec string val_btc_pk_hex = 1; - // delegation_status is the status of the delegations we are filtering for - BTCDelegationStatus del_status = 2; - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 3; + cosmos.base.query.v1beta1.PageRequest pagination = 2; } // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsResponse { // btc_delegations contains all the queried BTC delegations. - repeated BTCDelegation btc_delegations = 1; + repeated BTCDelegations btc_delegations = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index f6cec432e..94a1aa9f0 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -69,9 +69,12 @@ message MsgAddJurySig { // del_pk is the Bitcoin secp256k1 PK of the BTC delegation // the PK follows encoding in BIP-340 spec bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + string staking_tx_hash = 4; // sig is the signature of the jury // the signature follows encoding in BIP-340 spec - bytes sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgAddJurySigResponse is the response for MsgAddJurySig message MsgAddJurySigResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 87fb7ede6..57282048c 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -132,9 +132,12 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_PENDING) - s.Len(pendingDels, 1) - s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + pendingDelSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(pendingDelSet, 1) + pendingDels := pendingDelSet[0] + s.Len(pendingDels.Dels, 1) + s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels.Dels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + s.Nil(pendingDels.Dels[0].JurySig) } // Test2SubmitJurySignature is an end-to-end test for user @@ -146,13 +149,18 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { s.NoError(err) // get last BTC delegation - pendingDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_PENDING) - s.Len(pendingDels, 1) - btcDel := pendingDels[0] - slashingTx := btcDel.SlashingTx - stakingTx := btcDel.StakingTx + pendingDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(pendingDelsSet, 1) + pendingDels := pendingDelsSet[0] + s.Len(pendingDels.Dels, 1) + pendingDel := pendingDels.Dels[0] + s.Nil(pendingDel.JurySig) + + slashingTx := pendingDel.SlashingTx + stakingTx := pendingDel.StakingTx stakingMsgTx, err := stakingTx.ToMsgTx() s.NoError(err) + stakingTxHash := stakingTx.MustGetTxHash() /* generate and insert new jury signature, in order to activate the BTC delegation @@ -164,15 +172,18 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { &chaincfg.SimNetParams, ) s.NoError(err) - nonValidatorNode.AddJurySig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), jurySig) + nonValidatorNode.AddJurySig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, jurySig) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - // query the existence of BTC delegation and assert equivalence - activeDels := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex(), bstypes.BTCDelegationStatus_ACTIVE) - s.Len(activeDels, 1) - s.Equal(delBTCPK.SerializeCompressed()[1:], activeDels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) + // ensure the BTC delegation has jury sig now + activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(activeDelsSet, 1) + activeDels := activeDelsSet[0] + s.Len(activeDels.Dels, 1) + activeDel := activeDels.Dels[0] + s.NotNil(activeDel.JurySig) // ensure BTC staking is activated activatedHeight := nonValidatorNode.QueryActivatedHeight() @@ -182,7 +193,8 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { s.NoError(err) activeBTCVals := nonValidatorNode.QueryActiveBTCValidatorsAtHeight(activatedHeight) s.Len(activeBTCVals, 1) - s.Equal(activeBTCVals[0].VotingPower, activeDels[0].VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) + s.Equal(activeBTCVals[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) + s.Equal(activeBTCVals[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) } // Test2CommitPublicRandomnessAndSubmitFinalitySignature is an end-to-end diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 2a0449ca8..644fd69c7 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -57,14 +57,14 @@ func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstyp n.LogActionF("successfully created BTC delegation") } -func (n *NodeConfig) AddJurySig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, sig *bbn.BIP340Signature) { +func (n *NodeConfig) AddJurySig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { n.LogActionF("adding jury signature") valPKHex := valPK.MarshalHex() delPKHex := delPK.MarshalHex() sigHex := sig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "add-jury-sig", valPKHex, delPKHex, sigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-jury-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added jury sig") diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 25a636166..7b846f343 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -46,11 +46,9 @@ func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes. return resp.BtcValidators } -func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string, status bstypes.BTCDelegationStatus) []*bstypes.BTCDelegation { +func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegations { path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) - values := url.Values{} - values.Set("del_status", fmt.Sprintf("%d", status)) - bz, err := n.QueryGRPCGateway(path, values) + bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) var resp bstypes.QueryBTCValidatorDelegationsResponse diff --git a/testutil/datagen/btc_address.go b/testutil/datagen/btc_address.go index 7de1ce836..2f4f232d4 100644 --- a/testutil/datagen/btc_address.go +++ b/testutil/datagen/btc_address.go @@ -8,8 +8,7 @@ import ( ) func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (string, error) { - pkHash := GenRandomByteArray(r, 20) - addr, err := btcutil.NewAddressPubKeyHash(pkHash, net) + addr, err := btcutil.NewAddressWitnessPubKeyHash(GenRandomByteArray(r, 20), net) if err != nil { return "", err } diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index b919b5c7d..d6cd982ca 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -9,7 +9,6 @@ import ( bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -49,13 +48,17 @@ func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst }, nil } -func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { - // key pairs - btcSK, btcPK, err := GenRandomBTCKeyPair(r) +func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btcec.PrivateKey, jurySK *btcec.PrivateKey, slashingAddr string, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { + net := &chaincfg.SimNetParams + delPK := delSK.PubKey() + delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) + juryBTCPK := jurySK.PubKey() + valPK, err := valBTCPK.ToBTCPK() if err != nil { return nil, err } - bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + + // BTC delegation Babylon key pairs bbnSK, bbnPK, err := GenRandomSecp256k1KeyPair(r) if err != nil { return nil, err @@ -65,25 +68,42 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, startHeigh return nil, fmt.Errorf("failed to assert bbnPK to *secp256k1.PubKey") } // pop - pop, err := bstypes.NewPoP(bbnSK, btcSK) + pop, err := bstypes.NewPoP(bbnSK, delSK) if err != nil { return nil, err } - // TODO: generate legitimate jury signature and staking/slashing tx - jurySchnorrSig, err := schnorr.Sign(btcSK, GenRandomByteArray(r, 32)) + // staking/slashing tx + stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, delSK, valPK, juryBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) + if err != nil { + return nil, err + } + + // jury sig and delegator sig + stakingMsgTx, err := stakingTx.ToMsgTx() if err != nil { return nil, err } - jurySig := bbn.NewBIP340SignatureFromBTCSig(jurySchnorrSig) + jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, jurySK, net) + if err != nil { + return nil, err + } + delegatorSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, delSK, net) + if err != nil { + return nil, err + } + return &bstypes.BTCDelegation{ - BabylonPk: secp256k1PK, - BtcPk: bip340PK, - Pop: pop, - ValBtcPk: valBTCPK, - StartHeight: startHeight, - EndHeight: endHeight, - TotalSat: totalSat, - JurySig: &jurySig, + BabylonPk: secp256k1PK, + BtcPk: delBTCPK, + Pop: pop, + ValBtcPk: valBTCPK, + StartHeight: startHeight, + EndHeight: endHeight, + TotalSat: totalSat, + DelegatorSig: delegatorSig, + JurySig: jurySig, + StakingTx: stakingTx, + SlashingTx: slashingTx, }, nil } @@ -138,7 +158,7 @@ func GenBTCStakingSlashingTx( if err != nil { return nil, nil, err } - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 10000, stakingScript, btcNet) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 2000, stakingScript, btcNet) if err != nil { return nil, nil, err } diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 7bb8949a0..eb46247e9 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -11,8 +11,6 @@ import ( "github.com/spf13/cobra" ) -const flagDelegationStatus = "delegation-status" - // GetQueryCmd returns the cli query commands for this module func GetQueryCmd(queryRoute string) *cobra.Command { // Group btcstaking queries under a subcommand @@ -196,19 +194,9 @@ func CmdBTCValidatorDelegations() *cobra.Command { if err != nil { return err } - delegationStatusString, err := cmd.Flags().GetString(flagDelegationStatus) - if err != nil { - return err - } - - delegationStatus, err := types.NewBTCDelegationStatus(delegationStatusString) - if err != nil { - return err - } res, err := queryClient.BTCValidatorDelegations(cmd.Context(), &types.QueryBTCValidatorDelegationsRequest{ ValBtcPkHex: args[0], - DelStatus: delegationStatus, Pagination: pageReq, }) if err != nil { @@ -221,7 +209,6 @@ func CmdBTCValidatorDelegations() *cobra.Command { flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "btc-validator-delegations") - cmd.Flags().String(flagDelegationStatus, "Active", "Status of the queried delegations (Pending|Active|Expired)") return cmd } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index ac26ab9f5..a29a7bce5 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -161,8 +161,8 @@ func NewCreateBTCDelegationCmd() *cobra.Command { func NewAddJurySigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-jury-sig [val_pk] [del_pk] [sig]", - Args: cobra.ExactArgs(3), + Use: "add-jury-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", + Args: cobra.ExactArgs(4), Short: "Add a jury signature", Long: strings.TrimSpace( `Add a jury signature.`, // TODO: example @@ -185,17 +185,21 @@ func NewAddJurySigCmd() *cobra.Command { return err } + // get staking tx hash + stakingTxHash := args[2] + // get jury sigature - sig, err := bbn.NewBIP340SignatureFromHex(args[2]) + sig, err := bbn.NewBIP340SignatureFromHex(args[3]) if err != nil { return err } msg := types.MsgAddJurySig{ - Signer: clientCtx.FromAddress.String(), - ValPk: valPK, - DelPk: delPK, - Sig: sig, + Signer: clientCtx.FromAddress.String(), + ValPk: valPK, + DelPk: delPK, + StakingTxHash: stakingTxHash, + Sig: sig, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index bf19926e8..d1fe738fb 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -3,54 +3,117 @@ package keeper import ( "fmt" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -// SetBTCDelegation adds the given BTC delegation to KVStore -func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { - store := k.btcDelegationStore(ctx, btcDel.ValBtcPk.MustMarshal()) - btcDelBytes := k.cdc.MustMarshal(btcDel) - store.Set(btcDel.BtcPk.MustMarshal(), btcDelBytes) +func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { + var ( + btcDels = types.NewBTCDelegations() + err error + ) + if k.hasBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) { + btcDels, err = k.getBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) + if err != nil { + // this can only be a programming error + panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegations returns true")) + } + } + if err := btcDels.Add(btcDel); err != nil { + return types.ErrInvalidStakingTx.Wrapf(err.Error()) + } + + k.setBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk, btcDels) + return nil +} + +// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation +// with the given (val PK, del PK, staking tx hash) tuple +func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { + btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) + if err != nil { + return err + } + if err := btcDels.AddJurySig(stakingTxHash, jurySig); err != nil { + return types.ErrInvalidJurySig.Wrapf(err.Error()) + } + k.setBTCDelegations(ctx, valBTCPK, delBTCPK, btcDels) + return nil +} + +// setBTCDelegations sets the given BTC delegation to KVStore +func (k Keeper) setBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, btcDels *types.BTCDelegations) { + delBTCPKBytes := delBTCPK.MustMarshal() + + store := k.btcDelegationStore(ctx, valBTCPK) + btcDelBytes := k.cdc.MustMarshal(btcDels) + store.Set(delBTCPKBytes, btcDelBytes) } -// HasBTCDelegation checks if the given BTC delegation exists under a given BTC validator -func (k Keeper) HasBTCDelegation(ctx sdk.Context, valBTCPK []byte, delBTCPK []byte) bool { - if !k.HasBTCValidator(ctx, valBTCPK) { +// hasBTCDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator +func (k Keeper) hasBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { + valBTCPKBytes := valBTCPK.MustMarshal() + delBTCPKBytes := delBTCPK.MustMarshal() + + if !k.HasBTCValidator(ctx, valBTCPKBytes) { return false } store := k.btcDelegationStore(ctx, valBTCPK) - return store.Has(delBTCPK) + return store.Has(delBTCPKBytes) +} + +// HasBTCDelegation checks if the given BTC delegator has a BTC delegation with the given staking tx hash under a given BTC validator +func (k Keeper) HasBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string) bool { + btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) + if err != nil { + return false + } + return btcDels.Has(stakingTxHash) } -// GetBTCDelegation gets the BTC delegation with a given BTC PK under a given BTC validator -func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK []byte, delBTCPK []byte) (*types.BTCDelegation, error) { +// getBTCDelegations gets the BTC delegations with a given BTC PK under a given BTC validator +func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegations, error) { + valBTCPKBytes := valBTCPK.MustMarshal() + delBTCPKBytes := delBTCPK.MustMarshal() + // ensure the BTC validator exists - if !k.HasBTCValidator(ctx, valBTCPK) { + if !k.HasBTCValidator(ctx, valBTCPKBytes) { return nil, types.ErrBTCValNotFound } store := k.btcDelegationStore(ctx, valBTCPK) // ensure BTC delegation exists - if !store.Has(delBTCPK) { + if !store.Has(delBTCPKBytes) { return nil, types.ErrBTCDelNotFound } // get and unmarshal - btcDelBytes := store.Get(delBTCPK) - var btcDel types.BTCDelegation - if err := k.cdc.Unmarshal(btcDelBytes, &btcDel); err != nil { - return nil, fmt.Errorf("failed to unmarshal BTC delegation: %w", err) + var btcDels types.BTCDelegations + btcDelsBytes := store.Get(delBTCPKBytes) + k.cdc.MustUnmarshal(btcDelsBytes, &btcDels) + return &btcDels, nil +} + +// GetBTCDelegation gets the BTC delegation with a given BTC PK and staking tx hash under a given BTC validator +func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string) (*types.BTCDelegation, error) { + btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) + if err != nil { + return nil, err + } + btcDel, err := btcDels.Get(stakingTxHash) + if err != nil { + return nil, types.ErrBTCDelNotFound.Wrapf(err.Error()) } - return &btcDel, nil + return btcDel, nil } // btcDelegationStore returns the KVStore of the BTC delegations // prefix: BTCDelegationKey || validator's Bitcoin secp256k1 PK // key: delegation's Bitcoin secp256k1 PK -// value: BTCDelegation object -func (k Keeper) btcDelegationStore(ctx sdk.Context, valBTCPK []byte) prefix.Store { +// value: BTCDelegations (a list of BTCDelegation) +func (k Keeper) btcDelegationStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { store := ctx.KVStore(k.storeKey) delegationStore := prefix.NewStore(store, types.BTCDelegationKey) - return prefix.NewStore(delegationStore, valBTCPK) + return prefix.NewStore(delegationStore, valBTCPK.MustMarshal()) } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index df6445616..cd9420d6a 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -63,16 +63,24 @@ func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendi for ; valIter.Valid(); valIter.Next() { valBTCPKBytes := valIter.Key() - delStore := k.btcDelegationStore(sdkCtx, valBTCPKBytes) + valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + if err != nil { + // this can only be programming error + panic("failed to unmarshal validator BTC PK in KVstore") + } + delStore := k.btcDelegationStore(sdkCtx, valBTCPK) delIter := delStore.Iterator(nil, nil) // iterate over each BTC delegation under this BTC validator for ; delIter.Valid(); delIter.Next() { - btcDelBytes := delIter.Value() - var btcDel types.BTCDelegation - k.cdc.MustUnmarshal(btcDelBytes, &btcDel) - if btcDel.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING { - btcDels = append(btcDels, &btcDel) + var curBTCDels types.BTCDelegations + btcDelsBytes := delIter.Value() + k.cdc.MustUnmarshal(btcDelsBytes, &curBTCDels) + for i, btcDel := range curBTCDels.Dels { + if btcDel.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING { + // avoid using btcDel which changes over the iterations + btcDels = append(btcDels, curBTCDels.Dels[i]) + } } } @@ -167,27 +175,14 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC } sdkCtx := sdk.UnwrapSDKContext(ctx) - btcDelStore := k.btcDelegationStore(sdkCtx, valPK.MustMarshal()) + btcDelStore := k.btcDelegationStore(sdkCtx, valPK) - // get current BTC height - btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) - if err != nil { - return nil, err - } - // get value of w - wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout - - var btcDels []*types.BTCDelegation - pageRes, err := query.FilteredPaginate(btcDelStore, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { - var btcDelegation types.BTCDelegation - k.cdc.MustUnmarshal(value, &btcDelegation) - if req.DelStatus == btcDelegation.GetStatus(btcTipHeight, wValue) { - if accumulate { - btcDels = append(btcDels, &btcDelegation) - } - return true, nil - } - return false, nil + btcDels := []*types.BTCDelegations{} + pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { + var curBTCDels types.BTCDelegations + k.cdc.MustUnmarshal(value, &curBTCDels) + btcDels = append(btcDels, &curBTCDels) + return nil }) if err != nil { return nil, err diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 01b3f445b..2d1e3bc72 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -9,6 +9,7 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/golang/mock/gomock" @@ -119,6 +120,12 @@ func FuzzBTCDelegations(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a random number of BTC validators numBTCVals := datagen.RandomInt(r, 5) + 1 btcVals := []*types.BTCValidator{} @@ -136,14 +143,17 @@ func FuzzBTCDelegations(f *testing.F) { pendingBtcDelsMap := make(map[string]*types.BTCDelegation) for _, btcVal := range btcVals { for j := uint64(0); j < numBTCDels; j++ { - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, startHeight, endHeight, 1) + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr, startHeight, endHeight, 10000) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { // remove jury sig in random BTC delegations to make them inactive btcDel.JurySig = nil pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } - keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.SetBTCDelegation(ctx, btcDel) + require.NoError(t, err) } } @@ -200,6 +210,12 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { // Setup keeper and context keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a random batch of validators var btcVals []*types.BTCValidator numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 1 @@ -221,9 +237,12 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { var totalVotingPower uint64 for j := uint64(0); j < numBTCDels; j++ { - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, 1, 1000, 1) // timelock period: 1-1000 + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr, 1, 1000, 10000) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) - keeper.SetBTCDelegation(ctx, btcDel) totalVotingPower += btcDel.TotalSat } @@ -289,6 +308,12 @@ func FuzzBTCValidatorDelegations(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a btc validator btcVal, err := datagen.GenRandomBTCValidator(r) require.NoError(t, err) @@ -298,19 +323,15 @@ func FuzzBTCValidatorDelegations(f *testing.F) { endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 // Generate a random number of BTC delegations under this validator numBTCDels := datagen.RandomInt(r, 10) + 1 - activeBtcDelsMap := make(map[string]*types.BTCDelegation) - pendingBtcDelsMap := make(map[string]*types.BTCDelegation) + expectedBtcDelsMap := make(map[string]*types.BTCDelegation) for j := uint64(0); j < numBTCDels; j++ { - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, startHeight, endHeight, 1) + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr, startHeight, endHeight, 10000) + require.NoError(t, err) + expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel + err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) - if datagen.RandomInt(r, 2) == 1 { - // remove jury sig in random BTC delegations to make them inactive - btcDel.JurySig = nil - pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel - } else { - activeBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel - } - keeper.SetBTCDelegation(ctx, btcDel) } // Test nil request @@ -324,74 +345,40 @@ func FuzzBTCValidatorDelegations(f *testing.F) { keeper.IndexBTCHeight(ctx) // Generate a page request with a limit and a nil key - // query a page of active BTC delegations and assert consistency - if len(activeBtcDelsMap) > 0 { - limit := datagen.RandomInt(r, len(activeBtcDelsMap)) + 1 - pagination := constructRequestWithLimit(r, limit) - // Generate the initial query - req := types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - DelStatus: types.BTCDelegationStatus_ACTIVE, - Pagination: pagination, - } - // Construct a mapping from the btc vals found to a boolean value - // Will be used later to evaluate whether all the btc vals were returned - btcDelsFound := make(map[string]bool, 0) - - for i := uint64(0); i < numBTCDels; i += limit { - resp, err = keeper.BTCValidatorDelegations(ctx, &req) - require.NoError(t, err) - require.NotNil(t, resp) - for _, btcDel := range resp.BtcDelegations { - require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) - // Check if the pk exists in the map - _, ok := activeBtcDelsMap[btcDel.BtcPk.MarshalHex()] - require.True(t, ok) - btcDelsFound[btcDel.BtcPk.MarshalHex()] = true - } - // Construct the next page request - pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - DelStatus: types.BTCDelegationStatus_ACTIVE, - Pagination: pagination, - } - } - require.Equal(t, len(btcDelsFound), len(activeBtcDelsMap)) + // query a page of BTC delegations and assert consistency + limit := datagen.RandomInt(r, len(expectedBtcDelsMap)) + 1 + pagination := constructRequestWithLimit(r, limit) + // Generate the initial query + req := types.QueryBTCValidatorDelegationsRequest{ + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), + Pagination: pagination, } + // Construct a mapping from the btc vals found to a boolean value + // Will be used later to evaluate whether all the btc vals were returned + btcDelsFound := make(map[string]bool, 0) - // query a page of pending BTC delegations and assert consistency - if len(pendingBtcDelsMap) > 0 { - limit := datagen.RandomInt(r, len(pendingBtcDelsMap)) + 1 - pagination := constructRequestWithLimit(r, limit) - req := types.QueryBTCValidatorDelegationsRequest{ + for i := uint64(0); i < numBTCDels; i += limit { + resp, err = keeper.BTCValidatorDelegations(ctx, &req) + require.NoError(t, err) + require.NotNil(t, resp) + for _, btcDels := range resp.BtcDelegations { + require.Len(t, btcDels.Dels, 1) + btcDel := btcDels.Dels[0] + require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) + // Check if the pk exists in the map + _, ok := expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] + require.True(t, ok) + btcDelsFound[btcDel.BtcPk.MarshalHex()] = true + } + // Construct the next page request + pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) + req = types.QueryBTCValidatorDelegationsRequest{ ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig Pagination: pagination, } - pendingBtcDelsFound := make(map[string]bool, 0) - - for i := uint64(0); i < uint64(len(pendingBtcDelsMap)); i += limit { - resp, err := keeper.BTCValidatorDelegations(ctx, &req) - require.NoError(t, err) - require.NotNil(t, resp) - for _, btcDel := range resp.BtcDelegations { - require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) - // Check if the pk exists in the map - _, ok := pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] - require.True(t, ok) - pendingBtcDelsFound[btcDel.BtcPk.MarshalHex()] = true - } - // Construct the next page request - pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - DelStatus: types.BTCDelegationStatus_PENDING, // only request BTC delegations without jury sig - Pagination: pagination, - } - } - require.Equal(t, len(pendingBtcDelsFound), len(pendingBtcDelsMap)) } + require.Equal(t, len(btcDelsFound), len(expectedBtcDelsMap)) + }) } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index fc8ac5d6a..fc9dc6b5c 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -79,14 +79,15 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) juryPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.JuryKey) + // extract staking tx and its hash + stakingMsgTx, err := req.StakingTx.ToMsgTx() + if err != nil { + return nil, types.ErrInvalidStakingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + } + stakingTxHash := stakingMsgTx.TxHash().String() + // ensure the staking tx is not duplicated - // NOTE: it's okay that the same staker has multiple delegations - // the situation that we need to prevent here is that every staking tx - // can only correspond to a single BTC delegation - // TODO: the current impl does not support multiple delegations with the same (valPK, delPK) pair - // since a delegation is keyed by (valPK, delPK). Need to decide whether to support this - btcDel, err := ms.GetBTCDelegation(ctx, valBTCPK.MustMarshal(), delBTCPK.MustMarshal()) - if err == nil && btcDel.StakingTx.Equals(req.StakingTx) { + if ms.HasBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash) { return nil, types.ErrReusedStakingTx } @@ -119,10 +120,6 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre if err != nil { panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) } - stakingMsgTx, err := req.StakingTx.ToMsgTx() - if err != nil { - return nil, types.ErrInvalidStakingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) - } if _, err := btcstaking.CheckTransactions( slashingMsgTx, stakingMsgTx, @@ -163,7 +160,9 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre DelegatorSig: req.DelegatorSig, JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury } - ms.SetBTCDelegation(ctx, newBTCDel) + if err := ms.SetBTCDelegation(ctx, newBTCDel); err != nil { + panic("failed to set BTC delegation that has passed verification") + } // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { @@ -178,7 +177,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) ctx := sdk.UnwrapSDKContext(goCtx) // ensure BTC delegation exists - btcDel, err := ms.GetBTCDelegation(ctx, *req.ValPk, *req.DelPk) + btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) if err != nil { return nil, err } @@ -211,8 +210,9 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) } // all good, add signature to BTC delegation and set it back to KVStore - btcDel.JurySig = req.Sig - ms.SetBTCDelegation(ctx, btcDel) + if err := ms.AddJurySigToBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.Sig); err != nil { + panic("failed to set BTC delegation that has passed verification") + } // notify subscriber if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index c47e91fda..13eaf0f6b 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -130,6 +130,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() require.NoError(t, err) + stakingTxHash := stakingTx.MustGetTxHash() // random signer signer := datagen.GenRandomAccount().Address @@ -176,7 +177,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { verify the new BTC delegation */ // check existence - actualDel, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) + actualDel, err := bsKeeper.GetBTCDelegation(ctx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delPK), stakingTxHash) require.NoError(t, err) require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) @@ -199,10 +200,11 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { ) require.NoError(t, err) msgAddJurySig := &types.MsgAddJurySig{ - Signer: signer, - ValPk: btcVal.BtcPk, - DelPk: actualDel.BtcPk, - Sig: jurySig, + Signer: signer, + ValPk: btcVal.BtcPk, + DelPk: actualDel.BtcPk, + StakingTxHash: stakingTxHash, + Sig: jurySig, } _, err = ms.AddJurySig(ctx, msgAddJurySig) require.NoError(t, err) @@ -210,7 +212,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { /* ensure jury sig is added successfully */ - actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(ctx, *bbn.NewBIP340PubKeyFromBTCPK(validatorPK), *bbn.NewBIP340PubKeyFromBTCPK(delPK)) + actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(ctx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delPK), stakingTxHash) require.NoError(t, err) require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) require.True(t, actualDelWithJurySig.HasJurySig()) diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 87b3106a7..7da3983a6 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -26,8 +26,13 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) defer btcValIter.Close() for ; btcValIter.Valid(); btcValIter.Next() { - valBTCPK := btcValIter.Key() - btcVal, err := k.GetBTCValidator(ctx, valBTCPK) + valBTCPKBytes := btcValIter.Key() + valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + if err != nil { + // failed to unmarshal BTC validator PK in KVStore is a programming error + panic(err) + } + btcVal, err := k.GetBTCValidator(ctx, valBTCPKBytes) if err != nil { // failed to get a BTC validator with voting power is a programming error panic(err) @@ -43,14 +48,14 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // to calculate this validator's total voting power btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { - var btcDel types.BTCDelegation - k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDel) - valPower += btcDel.VotingPower(btcTipHeight, wValue) + var btcDels types.BTCDelegations + k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) + valPower += btcDels.VotingPower(btcTipHeight, wValue) } btcDelIter.Close() if valPower > 0 { - k.SetVotingPower(ctx, valBTCPK, babylonTipHeight, valPower) + k.SetVotingPower(ctx, valBTCPKBytes, babylonTipHeight, valPower) } } } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index c602e8c32..aaf4eff87 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -9,6 +9,7 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -27,6 +28,12 @@ func FuzzVotingPowerTable(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SigNetParams) + require.NoError(t, err) + // generate a random batch of validators btcVals := []*types.BTCValidator{} numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 @@ -40,12 +47,16 @@ func FuzzVotingPowerTable(f *testing.F) { // for the first numBTCValsWithVotingPower validators, generate a random number of BTC delegations numBTCDels := datagen.RandomInt(r, 10) + 1 + stakingValue := datagen.RandomInt(r, 100000) + 100000 for i := uint64(0); i < numBTCValsWithVotingPower; i++ { valBTCPK := btcVals[i].BtcPk for j := uint64(0); j < numBTCDels; j++ { - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, 1, 1000, 1) // timelock period: 1-1000 + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr, 1, 1000, stakingValue) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) - keeper.SetBTCDelegation(ctx, btcDel) } } @@ -61,7 +72,7 @@ func FuzzVotingPowerTable(f *testing.F) { } // since there is no BTC validator with BTC delegation, the BTC staking protocol is not activated yet - _, err := keeper.GetBTCStakingActivatedHeight(ctx) + _, err = keeper.GetBTCStakingActivatedHeight(ctx) require.Error(t, err) // Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power @@ -72,7 +83,7 @@ func FuzzVotingPowerTable(f *testing.F) { keeper.RecordVotingPowerTable(ctx) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) - require.Equal(t, uint64(numBTCDels), power) + require.Equal(t, uint64(numBTCDels)*stakingValue, power) } for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) @@ -109,7 +120,7 @@ func FuzzVotingPowerTable(f *testing.F) { if i == slashedIdx { require.Zero(t, power) } else { - require.Equal(t, uint64(numBTCDels), power) + require.Equal(t, uint64(numBTCDels)*stakingValue, power) } } for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 623cc3ed8..f83ae92ac 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -116,8 +116,8 @@ func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk if err != nil { return nil, err } - delegatorSig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) - return &delegatorSig, nil + sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) + return &sig, nil } // VerifySignature verifies a signature on the slashing tx signed by staker, validator or jury diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 6aee6a4fc..7000bf924 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -110,6 +111,84 @@ func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { return d.GetTotalSat() } +// GetStakingTxHash returns the staking tx hash of the BTC delegation +// it can be used for uniquely identifying a BTC delegation +func (d *BTCDelegation) GetStakingTxHash() (string, error) { + return d.StakingTx.GetTxHash() +} + +func (d *BTCDelegation) MustGetStakingTxHash() string { + return d.StakingTx.MustGetTxHash() +} + +func NewBTCDelegations() *BTCDelegations { + return &BTCDelegations{ + Dels: []*BTCDelegation{}, + } +} + +// Add appends a given BTC delegation to the BTC delegations +// It requires the given BTC delegation is not in the list yet +// TODO: this is an O(n) operation. Consider optimisation later +func (dels *BTCDelegations) Add(del *BTCDelegation) error { + stakingTxHash, err := del.GetStakingTxHash() + if err != nil { + return fmt.Errorf("failed to add BTC delegation to BTC delegations: %w", err) + } + // ensure the given del is not duplicated + if dels.Has(stakingTxHash) { + return fmt.Errorf("the given BTC delegation %s is duplicated", stakingTxHash) + } + // append + dels.Dels = append(dels.Dels, del) + return nil +} + +// AddJurySig adds a jury signature to an existing BTC delegation in the BTC delegations +// TODO: this is an O(n) operation. Consider optimisation later +func (dels *BTCDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { + del, err := dels.Get(stakingTxHash) + if err != nil { + return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) + } + if del.JurySig != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) + } + del.JurySig = sig + return nil +} + +// TODO: this is an O(n) operation. Consider optimisation later +func (dels *BTCDelegations) Has(stakingTxHash string) bool { + for _, d := range dels.Dels { + dStakingTxHash := d.MustGetStakingTxHash() + if dStakingTxHash == stakingTxHash { + return true + } + } + return false +} + +// TODO: this is an O(n) operation. Consider optimisation later +func (dels *BTCDelegations) Get(stakingTxHash string) (*BTCDelegation, error) { + for _, d := range dels.Dels { + dStakingTxHash := d.MustGetStakingTxHash() + if dStakingTxHash == stakingTxHash { + return d, nil + } + } + return nil, fmt.Errorf("cannot find the BTC delegation with staking tx hash %s", stakingTxHash) +} + +// VotingPower calculates the total voting power of all BTC delegations +func (dels *BTCDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { + power := uint64(0) + for _, del := range dels.Dels { + power += del.VotingPower(btcHeight, w) + } + return power +} + func (p *ProofOfPossession) ValidateBasic() error { if len(p.BabylonSig) == 0 { return fmt.Errorf("empty Babylon signature") @@ -173,6 +252,22 @@ func (tx *StakingTx) ToMsgTx() (*wire.MsgTx, error) { return &msgTx, nil } +func (tx *StakingTx) GetTxHash() (string, error) { + msgTx, err := tx.ToMsgTx() + if err != nil { + return "", err + } + return msgTx.TxHash().String(), nil +} + +func (tx *StakingTx) MustGetTxHash() string { + msgTx, err := tx.ToMsgTx() + if err != nil { + panic(err) + } + return msgTx.TxHash().String() +} + func (tx *StakingTx) GetStakingScriptData() (*btcstaking.StakingScriptData, error) { return btcstaking.ParseStakingTransactionScript(tx.StakingScript) } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 3f1cfd071..ecfb16ab5 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -295,33 +295,23 @@ func (m *BTCDelegation) GetStakingTx() *StakingTx { return nil } -// BTCDelegationWithMeta wraps the BTCDelegation with meta data. -type BTCDelegationWithMeta struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // start_height is the start BTC height of the BTC delegation - // it is the start BTC height of the timelock - StartHeight uint64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` - // end_height is the end height of the BTC delegation - // it is the end BTC height of the timelock - w - EndHeight uint64 `protobuf:"varint,3,opt,name=end_height,json=endHeight,proto3" json:"end_height,omitempty"` - // voting_power is the voting power of this BTC delegation at the given height - VotingPower uint64 `protobuf:"varint,4,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +// BTCDelegations is a collection of BTC delegations, typically from the same delegator. +type BTCDelegations struct { + Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` } -func (m *BTCDelegationWithMeta) Reset() { *m = BTCDelegationWithMeta{} } -func (m *BTCDelegationWithMeta) String() string { return proto.CompactTextString(m) } -func (*BTCDelegationWithMeta) ProtoMessage() {} -func (*BTCDelegationWithMeta) Descriptor() ([]byte, []int) { +func (m *BTCDelegations) Reset() { *m = BTCDelegations{} } +func (m *BTCDelegations) String() string { return proto.CompactTextString(m) } +func (*BTCDelegations) ProtoMessage() {} +func (*BTCDelegations) Descriptor() ([]byte, []int) { return fileDescriptor_3851ae95ccfaf7db, []int{3} } -func (m *BTCDelegationWithMeta) XXX_Unmarshal(b []byte) error { +func (m *BTCDelegations) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BTCDelegationWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *BTCDelegations) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BTCDelegationWithMeta.Marshal(b, m, deterministic) + return xxx_messageInfo_BTCDelegations.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -331,37 +321,23 @@ func (m *BTCDelegationWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byt return b[:n], nil } } -func (m *BTCDelegationWithMeta) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCDelegationWithMeta.Merge(m, src) +func (m *BTCDelegations) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegations.Merge(m, src) } -func (m *BTCDelegationWithMeta) XXX_Size() int { +func (m *BTCDelegations) XXX_Size() int { return m.Size() } -func (m *BTCDelegationWithMeta) XXX_DiscardUnknown() { - xxx_messageInfo_BTCDelegationWithMeta.DiscardUnknown(m) +func (m *BTCDelegations) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegations.DiscardUnknown(m) } -var xxx_messageInfo_BTCDelegationWithMeta proto.InternalMessageInfo +var xxx_messageInfo_BTCDelegations proto.InternalMessageInfo -func (m *BTCDelegationWithMeta) GetStartHeight() uint64 { +func (m *BTCDelegations) GetDels() []*BTCDelegation { if m != nil { - return m.StartHeight + return m.Dels } - return 0 -} - -func (m *BTCDelegationWithMeta) GetEndHeight() uint64 { - if m != nil { - return m.EndHeight - } - return 0 -} - -func (m *BTCDelegationWithMeta) GetVotingPower() uint64 { - if m != nil { - return m.VotingPower - } - return 0 + return nil } // ProofOfPossession is the proof of possession that a Babylon secp256k1 @@ -475,7 +451,7 @@ func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") - proto.RegisterType((*BTCDelegationWithMeta)(nil), "babylon.btcstaking.v1.BTCDelegationWithMeta") + proto.RegisterType((*BTCDelegations)(nil), "babylon.btcstaking.v1.BTCDelegations") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") } @@ -485,52 +461,52 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 712 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xdd, 0x4e, 0x13, 0x41, - 0x14, 0xee, 0x96, 0xd2, 0x9f, 0xd3, 0x42, 0x60, 0x04, 0xd3, 0x60, 0x6c, 0x4b, 0x13, 0x4d, 0xe3, - 0xc5, 0xae, 0x14, 0x21, 0x51, 0x13, 0x8d, 0x85, 0x46, 0x1b, 0x05, 0x37, 0xbb, 0x0d, 0x1a, 0x2f, - 0x6c, 0x66, 0xb7, 0xcb, 0x76, 0x6c, 0xd9, 0xd9, 0xec, 0x4c, 0x6b, 0xfb, 0x06, 0x5e, 0xfa, 0x08, - 0x3e, 0x87, 0x4f, 0x60, 0xe2, 0x0d, 0x97, 0x86, 0x0b, 0x62, 0xe0, 0x11, 0x7c, 0x01, 0xb3, 0xb3, - 0xd3, 0xa6, 0x80, 0x86, 0x18, 0xf4, 0xc6, 0xbb, 0x39, 0x7f, 0xdf, 0x9e, 0xef, 0x3b, 0xe7, 0x64, - 0xe1, 0xb6, 0x85, 0xad, 0x51, 0x8f, 0x7a, 0x9a, 0xc5, 0x6d, 0xc6, 0x71, 0x97, 0x78, 0xae, 0x36, - 0x58, 0x9b, 0xb2, 0x54, 0x3f, 0xa0, 0x9c, 0xa2, 0x65, 0x99, 0xa7, 0x4e, 0x45, 0x06, 0x6b, 0x2b, - 0x4b, 0x2e, 0x75, 0xa9, 0xc8, 0xd0, 0xc2, 0x57, 0x94, 0xbc, 0x52, 0xb6, 0x29, 0x3b, 0xa0, 0x4c, - 0xb3, 0x83, 0x91, 0xcf, 0xa9, 0xc6, 0x1c, 0xdb, 0xaf, 0x6e, 0x6c, 0x76, 0xd7, 0xb4, 0xae, 0x33, - 0x62, 0x51, 0x4e, 0xf9, 0x87, 0x02, 0xb9, 0x5a, 0x73, 0x6b, 0x0f, 0xf7, 0x48, 0x1b, 0x73, 0x1a, - 0xa0, 0x47, 0x00, 0xf2, 0x1b, 0x2d, 0xbf, 0x9b, 0x57, 0x4a, 0x4a, 0x25, 0x5b, 0x2d, 0xaa, 0x11, - 0x92, 0x1a, 0x21, 0xa9, 0x13, 0x24, 0x55, 0xef, 0x5b, 0xcf, 0x9d, 0x91, 0x91, 0x91, 0x25, 0x7a, - 0x17, 0xed, 0x40, 0xd2, 0xe2, 0x76, 0x58, 0x1b, 0x2f, 0x29, 0x95, 0x5c, 0x6d, 0xf3, 0xe8, 0xb8, - 0x58, 0x75, 0x09, 0xef, 0xf4, 0x2d, 0xd5, 0xa6, 0x07, 0x9a, 0xcc, 0xb4, 0x3b, 0x98, 0x78, 0x63, - 0x43, 0xe3, 0x23, 0xdf, 0x61, 0x6a, 0xad, 0xa1, 0xaf, 0xdf, 0xbb, 0x2b, 0x21, 0x67, 0x2d, 0x6e, - 0xeb, 0x5d, 0xf4, 0x00, 0x66, 0x7c, 0xea, 0xe7, 0x67, 0x44, 0x1f, 0x15, 0xf5, 0x97, 0xf4, 0x55, - 0x3d, 0xa0, 0x74, 0xff, 0xe5, 0xbe, 0x4e, 0x19, 0x73, 0x18, 0x23, 0xd4, 0x33, 0xc2, 0x22, 0x94, - 0x87, 0x14, 0xeb, 0x61, 0xd6, 0x71, 0xda, 0xf9, 0x44, 0x49, 0xa9, 0xa4, 0x8d, 0xb1, 0x59, 0xfe, - 0xa4, 0xc0, 0xd2, 0x34, 0xeb, 0x57, 0x84, 0x77, 0x76, 0x1c, 0x8e, 0xa7, 0xba, 0x57, 0xfe, 0x46, - 0xf7, 0xd7, 0x21, 0xd9, 0x71, 0x88, 0xdb, 0xe1, 0x42, 0x8c, 0x84, 0x21, 0x2d, 0xb4, 0x0a, 0xb9, - 0x01, 0xe5, 0xc4, 0x73, 0x5b, 0x3e, 0x7d, 0xef, 0x04, 0x82, 0x5e, 0xc2, 0xc8, 0x46, 0x3e, 0x3d, - 0x74, 0x95, 0x3f, 0xcf, 0xc2, 0x5c, 0xad, 0xb9, 0xb5, 0xed, 0xf4, 0x1c, 0x17, 0x73, 0x42, 0xbd, - 0xff, 0x69, 0x32, 0x4d, 0x80, 0x01, 0xee, 0xb5, 0x64, 0x3b, 0x89, 0x2b, 0xb5, 0x93, 0x1e, 0xe0, - 0x5e, 0x4d, 0x74, 0xb4, 0x0a, 0x39, 0xc6, 0x71, 0xc0, 0x5b, 0x52, 0xf3, 0xd9, 0x48, 0x55, 0xe1, - 0x7b, 0x16, 0x09, 0x7f, 0x13, 0xc0, 0xf1, 0xda, 0xe3, 0x84, 0xa4, 0x48, 0xc8, 0x38, 0x5e, 0x5b, - 0x86, 0x6f, 0x40, 0x86, 0x53, 0x8e, 0x7b, 0x2d, 0x86, 0x79, 0x3e, 0x25, 0xa2, 0x69, 0xe1, 0x30, - 0x31, 0x47, 0x8f, 0x01, 0x24, 0xb3, 0x16, 0x1f, 0xe6, 0xd3, 0x82, 0x77, 0xe9, 0x37, 0xbc, 0xcd, - 0xe8, 0xd9, 0x1c, 0x1a, 0x19, 0x36, 0x7e, 0xa2, 0x2a, 0x64, 0xc5, 0x02, 0x4a, 0x84, 0x8c, 0xa0, - 0xbd, 0x78, 0x74, 0x5c, 0x0c, 0x07, 0x6d, 0xca, 0x48, 0x73, 0x68, 0x00, 0x9b, 0xbc, 0xd1, 0x5b, - 0x98, 0x6b, 0x47, 0x2b, 0x40, 0x83, 0x16, 0x23, 0x6e, 0x1e, 0x44, 0xd5, 0xfd, 0xa3, 0xe3, 0xe2, - 0xc6, 0x9f, 0x88, 0x65, 0x12, 0xd7, 0xc3, 0xbc, 0x1f, 0x38, 0x46, 0x6e, 0x82, 0x67, 0x12, 0x17, - 0x35, 0x21, 0xfd, 0xae, 0x1f, 0x8c, 0x04, 0x74, 0xf6, 0xaa, 0xd0, 0xa9, 0x10, 0xca, 0x24, 0x6e, - 0xf9, 0xab, 0x02, 0xcb, 0x67, 0x96, 0xf7, 0x5f, 0x1d, 0xd8, 0xf9, 0x91, 0xc7, 0x2f, 0x1b, 0xf9, - 0xcc, 0xf9, 0x91, 0x9f, 0x3f, 0xc5, 0xc4, 0xc5, 0x53, 0xfc, 0xa0, 0xc0, 0xe2, 0x85, 0x45, 0x46, - 0x45, 0xc8, 0x8e, 0xcf, 0x31, 0x14, 0x4f, 0xd0, 0x31, 0xc6, 0x17, 0x1a, 0x4a, 0x6b, 0x40, 0x2a, - 0xa4, 0x1a, 0x06, 0xe3, 0x57, 0x55, 0x36, 0x14, 0x2d, 0x14, 0xb6, 0x06, 0x99, 0xc9, 0x6a, 0xa1, - 0x79, 0x88, 0xf3, 0xa1, 0xfc, 0x70, 0x9c, 0x0f, 0xd1, 0x2d, 0x98, 0x1f, 0x2f, 0x28, 0xb3, 0x03, - 0xe2, 0x47, 0x72, 0xe4, 0x8c, 0x39, 0xe9, 0x35, 0x85, 0xf3, 0xce, 0x43, 0xb8, 0x76, 0x66, 0x36, - 0x26, 0xc7, 0xbc, 0xcf, 0x50, 0x16, 0x52, 0x7a, 0x7d, 0x77, 0xbb, 0xb1, 0xfb, 0x74, 0x21, 0x86, - 0x00, 0x92, 0x4f, 0xb6, 0x9a, 0x8d, 0xbd, 0xfa, 0x82, 0x12, 0x06, 0xea, 0xaf, 0xf5, 0x86, 0x51, - 0xdf, 0x5e, 0x88, 0xd7, 0x5e, 0x7c, 0x39, 0x29, 0x28, 0x87, 0x27, 0x05, 0xe5, 0xfb, 0x49, 0x41, - 0xf9, 0x78, 0x5a, 0x88, 0x1d, 0x9e, 0x16, 0x62, 0xdf, 0x4e, 0x0b, 0xb1, 0x37, 0x97, 0x4e, 0x71, - 0x38, 0xfd, 0x73, 0x13, 0x34, 0xad, 0xa4, 0xf8, 0x09, 0xad, 0xff, 0x0c, 0x00, 0x00, 0xff, 0xff, - 0x32, 0xbf, 0x48, 0x1b, 0xff, 0x06, 0x00, 0x00, + // 713 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcb, 0x6e, 0xd3, 0x4c, + 0x14, 0x8e, 0xd3, 0x34, 0x97, 0x93, 0xb4, 0x6a, 0xe7, 0xef, 0x8f, 0xac, 0x22, 0x92, 0x34, 0x02, + 0x14, 0xb1, 0xb0, 0x69, 0x4a, 0x2b, 0x2e, 0x12, 0x88, 0xb4, 0x11, 0x04, 0x68, 0xb1, 0xec, 0xa8, + 0x20, 0x16, 0x44, 0x63, 0x67, 0xea, 0x98, 0xb8, 0x1e, 0xcb, 0x33, 0x09, 0xc9, 0x1b, 0xb0, 0xe4, + 0x11, 0x78, 0x0e, 0x9e, 0x80, 0x65, 0x97, 0xa8, 0x8b, 0x0a, 0xb5, 0x8f, 0xc0, 0x0b, 0x20, 0x8f, + 0xed, 0x28, 0x15, 0x54, 0x08, 0x95, 0x15, 0xbb, 0x73, 0xfd, 0xfc, 0x7d, 0x67, 0xce, 0x91, 0xe1, + 0xa6, 0x89, 0xcd, 0x89, 0x4b, 0x3d, 0xd5, 0xe4, 0x16, 0xe3, 0x78, 0xe0, 0x78, 0xb6, 0x3a, 0x5a, + 0x9f, 0xf1, 0x14, 0x3f, 0xa0, 0x9c, 0xa2, 0xff, 0xe3, 0x3a, 0x65, 0x26, 0x33, 0x5a, 0x5f, 0x5d, + 0xb1, 0xa9, 0x4d, 0x45, 0x85, 0x1a, 0x5a, 0x51, 0xf1, 0x6a, 0xcd, 0xa2, 0xec, 0x90, 0x32, 0xd5, + 0x0a, 0x26, 0x3e, 0xa7, 0x2a, 0x23, 0x96, 0xdf, 0xd8, 0xdc, 0x1a, 0xac, 0xab, 0x03, 0x32, 0x61, + 0x51, 0x4d, 0xed, 0xbb, 0x04, 0xa5, 0x66, 0x67, 0x7b, 0x1f, 0xbb, 0x4e, 0x0f, 0x73, 0x1a, 0xa0, + 0x87, 0x00, 0xf1, 0x37, 0xba, 0xfe, 0x40, 0x96, 0xaa, 0x52, 0xbd, 0xd8, 0xa8, 0x28, 0x11, 0x92, + 0x12, 0x21, 0x29, 0x53, 0x24, 0x45, 0x1b, 0x9a, 0xcf, 0xc9, 0x44, 0x2f, 0xc4, 0x2d, 0xda, 0x00, + 0xed, 0x42, 0xd6, 0xe4, 0x56, 0xd8, 0x9b, 0xae, 0x4a, 0xf5, 0x52, 0x73, 0xeb, 0xf8, 0xa4, 0xd2, + 0xb0, 0x1d, 0xde, 0x1f, 0x9a, 0x8a, 0x45, 0x0f, 0xd5, 0xb8, 0xd2, 0xea, 0x63, 0xc7, 0x4b, 0x1c, + 0x95, 0x4f, 0x7c, 0xc2, 0x94, 0x66, 0x5b, 0xdb, 0xb8, 0x73, 0x3b, 0x86, 0x9c, 0x37, 0xb9, 0xa5, + 0x0d, 0xd0, 0x7d, 0x98, 0xf3, 0xa9, 0x2f, 0xcf, 0x09, 0x1e, 0x75, 0xe5, 0x97, 0xf2, 0x15, 0x2d, + 0xa0, 0xf4, 0xe0, 0xe5, 0x81, 0x46, 0x19, 0x23, 0x8c, 0x39, 0xd4, 0xd3, 0xc3, 0x26, 0x24, 0x43, + 0x8e, 0xb9, 0x98, 0xf5, 0x49, 0x4f, 0xce, 0x54, 0xa5, 0x7a, 0x5e, 0x4f, 0xdc, 0xda, 0x27, 0x09, + 0x56, 0x66, 0x55, 0xbf, 0x72, 0x78, 0x7f, 0x97, 0x70, 0x3c, 0xc3, 0x5e, 0xfa, 0x1b, 0xec, 0xaf, + 0x40, 0xb6, 0x4f, 0x1c, 0xbb, 0xcf, 0xc5, 0x30, 0x32, 0x7a, 0xec, 0xa1, 0x35, 0x28, 0x8d, 0x28, + 0x77, 0x3c, 0xbb, 0xeb, 0xd3, 0xf7, 0x24, 0x10, 0xf2, 0x32, 0x7a, 0x31, 0x8a, 0x69, 0x61, 0xa8, + 0xf6, 0x79, 0x1e, 0x16, 0x9a, 0x9d, 0xed, 0x1d, 0xe2, 0x12, 0x1b, 0x73, 0x87, 0x7a, 0xff, 0xd2, + 0xcb, 0x74, 0x00, 0x46, 0xd8, 0xed, 0xc6, 0x74, 0x32, 0x97, 0xa2, 0x93, 0x1f, 0x61, 0xb7, 0x29, + 0x18, 0xad, 0x41, 0x89, 0x71, 0x1c, 0xf0, 0x6e, 0x3c, 0xf3, 0xf9, 0x68, 0xaa, 0x22, 0xf6, 0x34, + 0x1a, 0xfc, 0x35, 0x00, 0xe2, 0xf5, 0x92, 0x82, 0xac, 0x28, 0x28, 0x10, 0xaf, 0x17, 0xa7, 0xaf, + 0x42, 0x81, 0x53, 0x8e, 0xdd, 0x2e, 0xc3, 0x5c, 0xce, 0x89, 0x6c, 0x5e, 0x04, 0x0c, 0xcc, 0xd1, + 0x23, 0x80, 0x58, 0x59, 0x97, 0x8f, 0xe5, 0xbc, 0xd0, 0x5d, 0xbd, 0x40, 0xb7, 0x11, 0x99, 0x9d, + 0xb1, 0x5e, 0x60, 0x89, 0x89, 0x1a, 0x50, 0x14, 0x0b, 0x18, 0x23, 0x14, 0x84, 0xec, 0xe5, 0xe3, + 0x93, 0x4a, 0xf8, 0xd0, 0x46, 0x9c, 0xe9, 0x8c, 0x75, 0x60, 0x53, 0x1b, 0xbd, 0x85, 0x85, 0x5e, + 0xb4, 0x02, 0x34, 0xe8, 0x32, 0xc7, 0x96, 0x41, 0x74, 0xdd, 0x3b, 0x3e, 0xa9, 0x6c, 0xfe, 0xc9, + 0xb0, 0x0c, 0xc7, 0xf6, 0x30, 0x1f, 0x06, 0x44, 0x2f, 0x4d, 0xf1, 0x0c, 0xc7, 0x46, 0x1d, 0xc8, + 0xbf, 0x1b, 0x06, 0x13, 0x01, 0x5d, 0xbc, 0x2c, 0x74, 0x2e, 0x84, 0x32, 0x1c, 0xbb, 0xf6, 0x0c, + 0x16, 0xcf, 0xed, 0x2e, 0x43, 0x77, 0x21, 0xd3, 0x23, 0x2e, 0x93, 0xa5, 0xea, 0x5c, 0xbd, 0xd8, + 0xb8, 0x7e, 0xc1, 0xd8, 0xce, 0x35, 0xe9, 0xa2, 0xa3, 0xf6, 0x41, 0x82, 0xe5, 0x9f, 0xd6, 0x08, + 0x55, 0xa0, 0x98, 0x1c, 0x43, 0x48, 0x5d, 0x5c, 0xab, 0x9e, 0xdc, 0x47, 0x28, 0x4c, 0x87, 0x5c, + 0xb8, 0x5e, 0x61, 0x32, 0x7d, 0x59, 0x5d, 0xe1, 0xdd, 0x84, 0xb2, 0x9a, 0x50, 0x98, 0x3e, 0x2c, + 0x5a, 0x84, 0x34, 0x1f, 0xc7, 0x1f, 0x4e, 0xf3, 0x31, 0xba, 0x01, 0x8b, 0xc9, 0x7a, 0x30, 0x2b, + 0x70, 0xfc, 0xe8, 0xe6, 0x4b, 0xfa, 0x42, 0x1c, 0x35, 0x44, 0xf0, 0xd6, 0x03, 0xf8, 0xef, 0x9c, + 0x4a, 0x83, 0x63, 0x3e, 0x64, 0xa8, 0x08, 0x39, 0xad, 0xb5, 0xb7, 0xd3, 0xde, 0x7b, 0xb2, 0x94, + 0x42, 0x00, 0xd9, 0xc7, 0xdb, 0x9d, 0xf6, 0x7e, 0x6b, 0x49, 0x0a, 0x13, 0xad, 0xd7, 0x5a, 0x5b, + 0x6f, 0xed, 0x2c, 0xa5, 0x9b, 0x2f, 0xbe, 0x9c, 0x96, 0xa5, 0xa3, 0xd3, 0xb2, 0xf4, 0xed, 0xb4, + 0x2c, 0x7d, 0x3c, 0x2b, 0xa7, 0x8e, 0xce, 0xca, 0xa9, 0xaf, 0x67, 0xe5, 0xd4, 0x9b, 0xdf, 0x5e, + 0xce, 0x78, 0xf6, 0xd7, 0x22, 0x64, 0x9a, 0x59, 0xf1, 0x0b, 0xd8, 0xf8, 0x11, 0x00, 0x00, 0xff, + 0xff, 0x1f, 0xf7, 0x07, 0x09, 0x7d, 0x06, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -781,7 +757,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *BTCDelegationWithMeta) Marshal() (dAtA []byte, err error) { +func (m *BTCDelegations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -791,42 +767,29 @@ func (m *BTCDelegationWithMeta) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BTCDelegationWithMeta) MarshalTo(dAtA []byte) (int, error) { +func (m *BTCDelegations) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BTCDelegationWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *BTCDelegations) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.VotingPower != 0 { - i = encodeVarintBtcstaking(dAtA, i, uint64(m.VotingPower)) - i-- - dAtA[i] = 0x20 - } - if m.EndHeight != 0 { - i = encodeVarintBtcstaking(dAtA, i, uint64(m.EndHeight)) - i-- - dAtA[i] = 0x18 - } - if m.StartHeight != 0 { - i = encodeVarintBtcstaking(dAtA, i, uint64(m.StartHeight)) - i-- - dAtA[i] = 0x10 - } - if m.BtcPk != nil { - { - size := m.BtcPk.Size() - i -= size - if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + if len(m.Dels) > 0 { + for iNdEx := len(m.Dels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Dels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa } - i-- - dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -1014,24 +977,17 @@ func (m *BTCDelegation) Size() (n int) { return n } -func (m *BTCDelegationWithMeta) Size() (n int) { +func (m *BTCDelegations) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.BtcPk != nil { - l = m.BtcPk.Size() - n += 1 + l + sovBtcstaking(uint64(l)) - } - if m.StartHeight != 0 { - n += 1 + sovBtcstaking(uint64(m.StartHeight)) - } - if m.EndHeight != 0 { - n += 1 + sovBtcstaking(uint64(m.EndHeight)) - } - if m.VotingPower != 0 { - n += 1 + sovBtcstaking(uint64(m.VotingPower)) + if len(m.Dels) > 0 { + for _, e := range m.Dels { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } return n } @@ -1766,7 +1722,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } return nil } -func (m *BTCDelegationWithMeta) Unmarshal(dAtA []byte) error { +func (m *BTCDelegations) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1789,17 +1745,17 @@ func (m *BTCDelegationWithMeta) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BTCDelegationWithMeta: wiretype end group for non-group") + return fmt.Errorf("proto: BTCDelegations: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BTCDelegationWithMeta: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: BTCDelegations: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Dels", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -1809,84 +1765,26 @@ func (m *BTCDelegationWithMeta) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.BtcPk = &v - if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Dels = append(m.Dels, &BTCDelegation{}) + if err := m.Dels[len(m.Dels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) - } - m.StartHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.StartHeight |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field EndHeight", wireType) - } - m.EndHeight = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.EndHeight |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) - } - m.VotingPower = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.VotingPower |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index cc9c3506d..b562daac9 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -132,6 +133,9 @@ func (m *MsgAddJurySig) ValidateBasic() error { if m.Sig == nil { return fmt.Errorf("empty jury signature") } + if len(m.StakingTxHash) != chainhash.MaxHashStringSize { + return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) + } return nil } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 80642d75c..9edf8b292 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -613,10 +613,8 @@ type QueryBTCValidatorDelegationsRequest struct { // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` - // delegation_status is the status of the delegations we are filtering for - DelStatus BTCDelegationStatus `protobuf:"varint,2,opt,name=del_status,json=delStatus,proto3,enum=babylon.btcstaking.v1.BTCDelegationStatus" json:"del_status,omitempty"` // pagination defines an optional pagination for the request. - Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCValidatorDelegationsRequest{} } @@ -659,13 +657,6 @@ func (m *QueryBTCValidatorDelegationsRequest) GetValBtcPkHex() string { return "" } -func (m *QueryBTCValidatorDelegationsRequest) GetDelStatus() BTCDelegationStatus { - if m != nil { - return m.DelStatus - } - return BTCDelegationStatus_PENDING -} - func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination @@ -677,7 +668,7 @@ func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsResponse struct { // btc_delegations contains all the queried BTC delegations. - BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + BtcDelegations []*BTCDelegations `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -715,7 +706,7 @@ func (m *QueryBTCValidatorDelegationsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryBTCValidatorDelegationsResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegation { +func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegations { if m != nil { return m.BtcDelegations } @@ -749,64 +740,62 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 897 bytes of a gzipped FileDescriptorProto + // 876 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdf, 0x6f, 0xdb, 0x54, - 0x14, 0xce, 0xed, 0x4a, 0xa4, 0x9d, 0xb0, 0x4e, 0xba, 0x6c, 0xb0, 0x79, 0x6b, 0xb6, 0x3a, 0x6b, - 0xd3, 0x15, 0x61, 0x2f, 0x99, 0x34, 0x01, 0xe3, 0x87, 0x96, 0x0d, 0x36, 0x06, 0x95, 0x82, 0x99, - 0x40, 0xe2, 0x25, 0xba, 0x76, 0xae, 0x1c, 0xab, 0xae, 0xaf, 0x17, 0xdf, 0x98, 0x56, 0x68, 0x2f, - 0x3c, 0x20, 0xf1, 0x86, 0xc4, 0x9f, 0xc0, 0x23, 0x4f, 0xfc, 0x03, 0x3c, 0x21, 0xb1, 0x37, 0x26, - 0x21, 0x21, 0x5e, 0x98, 0x50, 0x8b, 0xc4, 0xbf, 0x81, 0x72, 0x7d, 0x53, 0xdb, 0x89, 0xed, 0xb8, - 0xd1, 0xfa, 0x96, 0xfa, 0x9e, 0xef, 0x9c, 0xef, 0xfb, 0xee, 0xb9, 0xe7, 0x14, 0xd6, 0x4c, 0x62, - 0xee, 0xbb, 0xcc, 0xd3, 0x4d, 0x6e, 0x05, 0x9c, 0xec, 0x38, 0x9e, 0xad, 0x87, 0x2d, 0xfd, 0xf1, - 0x88, 0x0e, 0xf7, 0x35, 0x7f, 0xc8, 0x38, 0xc3, 0xe7, 0x65, 0x88, 0x16, 0x87, 0x68, 0x61, 0x4b, - 0x39, 0x67, 0x33, 0x9b, 0x89, 0x08, 0x7d, 0xfc, 0x2b, 0x0a, 0x56, 0x2e, 0xdb, 0x8c, 0xd9, 0x2e, - 0xd5, 0x89, 0xef, 0xe8, 0xc4, 0xf3, 0x18, 0x27, 0xdc, 0x61, 0x5e, 0x20, 0x4f, 0xb7, 0x2c, 0x16, - 0xec, 0xb2, 0x40, 0x37, 0x49, 0x40, 0xa3, 0x1a, 0x7a, 0xd8, 0x32, 0x29, 0x27, 0x2d, 0xdd, 0x27, - 0xb6, 0xe3, 0x89, 0x60, 0x19, 0xab, 0x66, 0x33, 0xf3, 0xc9, 0x90, 0xec, 0x4e, 0xf2, 0x6d, 0x64, - 0xc7, 0x24, 0x88, 0x8a, 0x38, 0xf5, 0x1c, 0xe0, 0x4f, 0xc7, 0xd5, 0xba, 0x02, 0x6c, 0xd0, 0xc7, - 0x23, 0x1a, 0x70, 0xd5, 0x80, 0x57, 0x52, 0x5f, 0x03, 0x9f, 0x79, 0x01, 0xc5, 0xb7, 0xa1, 0x1a, - 0x15, 0xb9, 0x80, 0xae, 0xa2, 0xcd, 0x5a, 0x7b, 0x55, 0xcb, 0x34, 0x40, 0x8b, 0x60, 0x9d, 0xe5, - 0xa7, 0xcf, 0xaf, 0x54, 0x0c, 0x09, 0x51, 0x2d, 0xb8, 0x28, 0x72, 0x76, 0x1e, 0xdd, 0xfd, 0x9c, - 0xb8, 0x4e, 0x9f, 0x70, 0x36, 0x9c, 0x14, 0xc4, 0x1f, 0x02, 0xc4, 0x32, 0x65, 0xf6, 0x0d, 0x2d, - 0xf2, 0x44, 0x1b, 0x7b, 0xa2, 0x45, 0xbe, 0x4b, 0x4f, 0xb4, 0x2e, 0xb1, 0xa9, 0xc4, 0x1a, 0x09, - 0xa4, 0xfa, 0x33, 0x02, 0x25, 0xab, 0x8a, 0x14, 0xf0, 0x10, 0x56, 0x4c, 0x6e, 0xf5, 0xc2, 0xa3, - 0x93, 0x0b, 0xe8, 0xea, 0xa9, 0xcd, 0x5a, 0xbb, 0x91, 0x23, 0x24, 0x99, 0xc5, 0x38, 0x63, 0x72, - 0x2b, 0xce, 0x89, 0xef, 0xa7, 0x28, 0x2f, 0x09, 0xca, 0xcd, 0xb9, 0x94, 0x23, 0x22, 0x29, 0xce, - 0x0d, 0x58, 0x8b, 0xcc, 0xa6, 0x5e, 0xdf, 0xf1, 0xec, 0xce, 0xa3, 0xbb, 0xf7, 0xa8, 0x4b, 0xed, - 0xa8, 0x3d, 0x26, 0x37, 0x12, 0x80, 0x5a, 0x14, 0x24, 0xf5, 0x6d, 0xc3, 0xd9, 0xb1, 0xbe, 0x7e, - 0x7c, 0x24, 0x05, 0x5e, 0xcb, 0x17, 0x18, 0xe7, 0x31, 0xc6, 0xe6, 0x24, 0xd2, 0xaa, 0x7d, 0x58, - 0x9f, 0x31, 0xb3, 0xcb, 0xbe, 0xa2, 0xc3, 0x3b, 0xfc, 0x01, 0x75, 0xec, 0x01, 0x9f, 0x5c, 0x5f, - 0x03, 0x56, 0x42, 0xe2, 0xf6, 0xc6, 0xb5, 0xfd, 0x9d, 0xde, 0x80, 0xee, 0x89, 0x2b, 0x3c, 0x6d, - 0xd4, 0x42, 0xe2, 0x76, 0xb8, 0xd5, 0xdd, 0x79, 0x40, 0xf7, 0xf0, 0xab, 0x50, 0x1d, 0x08, 0x94, - 0x30, 0x6b, 0xd9, 0x90, 0x7f, 0xa9, 0x1f, 0xc3, 0xc6, 0xbc, 0x2a, 0x52, 0xde, 0x1a, 0xbc, 0x1c, - 0x32, 0xee, 0x78, 0x76, 0xcf, 0x1f, 0x9f, 0x8b, 0x22, 0xcb, 0x46, 0x2d, 0xfa, 0x26, 0x20, 0xea, - 0x77, 0x08, 0x9a, 0x22, 0xdb, 0x1d, 0x8b, 0x3b, 0x21, 0x4d, 0xb5, 0xc1, 0x34, 0xeb, 0x98, 0x10, - 0x4a, 0x12, 0x9a, 0x6a, 0xc6, 0xa5, 0x85, 0x9b, 0xf1, 0x37, 0x04, 0x9b, 0xf3, 0xb9, 0x48, 0x6d, - 0x46, 0x4e, 0x6b, 0xbe, 0x5e, 0xa2, 0x35, 0xbf, 0x70, 0xf8, 0x60, 0x9b, 0x72, 0x72, 0x62, 0x2d, - 0xba, 0x0a, 0x97, 0x62, 0x21, 0x84, 0xd3, 0x7e, 0xca, 0x48, 0xf5, 0x16, 0x5c, 0xce, 0x3e, 0x96, - 0xda, 0x72, 0x8c, 0x56, 0xff, 0x46, 0xd0, 0x98, 0xb9, 0xfa, 0xd9, 0xe6, 0x2f, 0xd7, 0x5e, 0x1f, - 0x01, 0xf4, 0xa9, 0xdb, 0x0b, 0x38, 0xe1, 0xa3, 0x40, 0x88, 0x5d, 0x69, 0x6f, 0x95, 0x69, 0xfb, - 0xcf, 0x04, 0xc2, 0x38, 0xdd, 0xa7, 0x6e, 0xf4, 0x73, 0xaa, 0x01, 0x4e, 0x2d, 0xdc, 0x00, 0xbf, - 0x20, 0xb8, 0x56, 0xac, 0xef, 0x44, 0xde, 0xed, 0x0b, 0xbb, 0xf7, 0xf6, 0xef, 0x00, 0x2f, 0x09, - 0x01, 0xf8, 0x5b, 0x04, 0xd5, 0x68, 0xac, 0xe3, 0xeb, 0x39, 0x9c, 0x66, 0xf7, 0x88, 0xb2, 0x55, - 0x26, 0x34, 0xaa, 0xab, 0xae, 0x7f, 0xf3, 0xc7, 0xbf, 0x3f, 0x2c, 0x5d, 0xc1, 0xab, 0x7a, 0xd1, - 0x7a, 0xc3, 0x3f, 0x22, 0x38, 0x93, 0x7a, 0x49, 0xf8, 0x46, 0x51, 0x91, 0xac, 0x6d, 0xa3, 0xb4, - 0x8e, 0x81, 0x90, 0xec, 0xde, 0x10, 0xec, 0x9a, 0x78, 0x5d, 0xcf, 0x5d, 0xac, 0x89, 0xb7, 0x8b, - 0x7f, 0x45, 0x70, 0x3e, 0x73, 0x54, 0xe3, 0x37, 0x0b, 0x2d, 0x29, 0x58, 0x01, 0xca, 0x5b, 0x0b, - 0x20, 0x25, 0xfb, 0x5b, 0x82, 0xfd, 0x0d, 0xac, 0xe5, 0x79, 0x1b, 0xa1, 0x7b, 0x53, 0x4d, 0x88, - 0xff, 0x44, 0x70, 0xa9, 0x60, 0x78, 0xe1, 0xf7, 0x8a, 0x28, 0xcd, 0x9f, 0xc0, 0xca, 0xfb, 0x0b, - 0xe3, 0x4b, 0x0a, 0x4b, 0x5f, 0x8b, 0xfe, 0x75, 0x34, 0x78, 0x9e, 0xe0, 0xff, 0x10, 0x5c, 0xcc, - 0xdd, 0x37, 0xf8, 0x9d, 0xb2, 0xfd, 0x91, 0xb5, 0x0c, 0x95, 0x77, 0x17, 0x44, 0x4b, 0x49, 0xdb, - 0x42, 0xd2, 0x7d, 0xfc, 0x41, 0x49, 0x49, 0xe9, 0xc9, 0xf8, 0x44, 0x17, 0xab, 0x31, 0x56, 0xfa, - 0x13, 0x82, 0xb3, 0x53, 0x73, 0x19, 0xb7, 0xe7, 0xda, 0x3e, 0x33, 0xe3, 0x95, 0x9b, 0xc7, 0xc2, - 0x48, 0x2d, 0xba, 0xd0, 0x72, 0x1d, 0x37, 0x73, 0xb4, 0x90, 0x09, 0xae, 0x27, 0x57, 0xef, 0x73, - 0x04, 0xaf, 0xe5, 0x0c, 0x4b, 0xfc, 0x76, 0x59, 0x5f, 0x33, 0xde, 0xce, 0xed, 0x85, 0xb0, 0x52, - 0xc5, 0x43, 0xa1, 0xe2, 0x1e, 0xee, 0x2c, 0x78, 0x23, 0x89, 0x17, 0xd5, 0xf9, 0xe4, 0xe9, 0x41, - 0x1d, 0x3d, 0x3b, 0xa8, 0xa3, 0x7f, 0x0e, 0xea, 0xe8, 0xfb, 0xc3, 0x7a, 0xe5, 0xd9, 0x61, 0xbd, - 0xf2, 0xd7, 0x61, 0xbd, 0xf2, 0x65, 0xdb, 0x76, 0xf8, 0x60, 0x64, 0x6a, 0x16, 0xdb, 0x9d, 0xd4, - 0xb1, 0x06, 0xc4, 0xf1, 0x8e, 0x8a, 0xee, 0x25, 0xcb, 0xf2, 0x7d, 0x9f, 0x06, 0x66, 0x55, 0xfc, - 0x13, 0x7f, 0xf3, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xc9, 0xa4, 0x87, 0xac, 0x0c, 0x00, - 0x00, + 0x14, 0xce, 0x2d, 0x25, 0x12, 0x27, 0x6c, 0x93, 0x2e, 0x1b, 0x6c, 0xde, 0x9a, 0xad, 0xce, 0xd2, + 0x74, 0x43, 0xd8, 0x4b, 0x26, 0x4d, 0xc0, 0xf8, 0xa1, 0x65, 0x83, 0x55, 0x85, 0xa2, 0x60, 0x55, + 0x20, 0xf1, 0x12, 0x5d, 0x3b, 0x57, 0x8e, 0xd5, 0xd4, 0xd7, 0x8d, 0x6f, 0x4d, 0x2b, 0xd4, 0x17, + 0x1e, 0x90, 0x78, 0x43, 0xf0, 0x27, 0xf0, 0xc8, 0x13, 0x7f, 0x01, 0x2f, 0x48, 0xf4, 0x8d, 0x4a, + 0x48, 0x88, 0xa7, 0x0a, 0xb5, 0x48, 0xfc, 0x1b, 0x28, 0xd7, 0x37, 0xb5, 0x9d, 0xd8, 0x8e, 0x1b, + 0x95, 0xb7, 0xd6, 0xf7, 0x7c, 0xe7, 0x7c, 0xdf, 0x77, 0xce, 0x3d, 0x37, 0xb0, 0x6c, 0x12, 0x73, + 0x7f, 0xc0, 0x5c, 0xdd, 0xe4, 0x96, 0xcf, 0xc9, 0x96, 0xe3, 0xda, 0x7a, 0xd0, 0xd4, 0x77, 0x76, + 0xe9, 0x70, 0x5f, 0xf3, 0x86, 0x8c, 0x33, 0x7c, 0x4d, 0x86, 0x68, 0x51, 0x88, 0x16, 0x34, 0x95, + 0xab, 0x36, 0xb3, 0x99, 0x88, 0xd0, 0x47, 0x7f, 0x85, 0xc1, 0xca, 0x2d, 0x9b, 0x31, 0x7b, 0x40, + 0x75, 0xe2, 0x39, 0x3a, 0x71, 0x5d, 0xc6, 0x09, 0x77, 0x98, 0xeb, 0xcb, 0xd3, 0xfb, 0x16, 0xf3, + 0xb7, 0x99, 0xaf, 0x9b, 0xc4, 0xa7, 0x61, 0x0d, 0x3d, 0x68, 0x9a, 0x94, 0x93, 0xa6, 0xee, 0x11, + 0xdb, 0x71, 0x45, 0xb0, 0x8c, 0x55, 0xd3, 0x99, 0x79, 0x64, 0x48, 0xb6, 0xc7, 0xf9, 0x56, 0xd2, + 0x63, 0x62, 0x44, 0x45, 0x9c, 0x7a, 0x15, 0xf0, 0xa7, 0xa3, 0x6a, 0x1d, 0x01, 0x36, 0xe8, 0xce, + 0x2e, 0xf5, 0xb9, 0x6a, 0xc0, 0x2b, 0x89, 0xaf, 0xbe, 0xc7, 0x5c, 0x9f, 0xe2, 0xc7, 0x50, 0x0e, + 0x8b, 0x5c, 0x47, 0x77, 0xd0, 0x6a, 0xa5, 0xb5, 0xa4, 0xa5, 0x1a, 0xa0, 0x85, 0xb0, 0xf6, 0xe2, + 0xe1, 0xf1, 0xed, 0x92, 0x21, 0x21, 0xaa, 0x05, 0x37, 0x44, 0xce, 0xf6, 0xe6, 0xd3, 0xcf, 0xc8, + 0xc0, 0xe9, 0x11, 0xce, 0x86, 0xe3, 0x82, 0xf8, 0x43, 0x80, 0x48, 0xa6, 0xcc, 0xbe, 0xa2, 0x85, + 0x9e, 0x68, 0x23, 0x4f, 0xb4, 0xd0, 0x77, 0xe9, 0x89, 0xd6, 0x21, 0x36, 0x95, 0x58, 0x23, 0x86, + 0x54, 0x7f, 0x46, 0xa0, 0xa4, 0x55, 0x91, 0x02, 0xd6, 0xe1, 0xb2, 0xc9, 0xad, 0x6e, 0x70, 0x76, + 0x72, 0x1d, 0xdd, 0x79, 0x61, 0xb5, 0xd2, 0xaa, 0x65, 0x08, 0x89, 0x67, 0x31, 0x2e, 0x99, 0xdc, + 0x8a, 0x72, 0xe2, 0xe7, 0x09, 0xca, 0x0b, 0x82, 0x72, 0x63, 0x26, 0xe5, 0x90, 0x48, 0x82, 0x73, + 0x0d, 0x96, 0x43, 0xb3, 0xa9, 0xdb, 0x73, 0x5c, 0xbb, 0xbd, 0xf9, 0xf4, 0x19, 0x1d, 0x50, 0x3b, + 0x1c, 0x8f, 0x71, 0x47, 0x7c, 0x50, 0xf3, 0x82, 0xa4, 0xbe, 0x0d, 0xb8, 0x32, 0xd2, 0xd7, 0x8b, + 0x8e, 0xa4, 0xc0, 0xbb, 0xd9, 0x02, 0xa3, 0x3c, 0xc6, 0xc8, 0x9c, 0x58, 0x5a, 0xb5, 0x07, 0xf5, + 0x29, 0x33, 0x3b, 0xec, 0x4b, 0x3a, 0x7c, 0xc2, 0xd7, 0xa8, 0x63, 0xf7, 0xf9, 0xb8, 0x7d, 0x35, + 0xb8, 0x1c, 0x90, 0x41, 0x77, 0x54, 0xdb, 0xdb, 0xea, 0xf6, 0xe9, 0x9e, 0x68, 0xe1, 0x4b, 0x46, + 0x25, 0x20, 0x83, 0x36, 0xb7, 0x3a, 0x5b, 0x6b, 0x74, 0x0f, 0xbf, 0x0a, 0xe5, 0xbe, 0x40, 0x09, + 0xb3, 0x16, 0x0d, 0xf9, 0x9f, 0xfa, 0x11, 0xac, 0xcc, 0xaa, 0x22, 0xe5, 0x2d, 0xc3, 0xcb, 0x01, + 0xe3, 0x8e, 0x6b, 0x77, 0xbd, 0xd1, 0xb9, 0x28, 0xb2, 0x68, 0x54, 0xc2, 0x6f, 0x02, 0xa2, 0x7e, + 0x8b, 0xa0, 0x21, 0xb2, 0x3d, 0xb1, 0xb8, 0x13, 0xd0, 0xc4, 0x18, 0x4c, 0xb2, 0x8e, 0x08, 0xa1, + 0x38, 0xa1, 0x89, 0x61, 0x5c, 0x98, 0x7b, 0x18, 0x7f, 0x43, 0xb0, 0x3a, 0x9b, 0x8b, 0xd4, 0x66, + 0x64, 0x8c, 0xe6, 0xeb, 0x05, 0x46, 0xf3, 0x73, 0x87, 0xf7, 0x37, 0x28, 0x27, 0xff, 0xdb, 0x88, + 0x2e, 0xc1, 0xcd, 0x48, 0x08, 0xe1, 0xb4, 0x97, 0x30, 0x52, 0x7d, 0x04, 0xb7, 0xd2, 0x8f, 0xa5, + 0xb6, 0x0c, 0xa3, 0xd5, 0xef, 0x11, 0xd4, 0xa6, 0x5a, 0x3f, 0x3d, 0xfc, 0xc5, 0xc6, 0xeb, 0xa2, + 0xba, 0xf6, 0x0b, 0x82, 0xbb, 0xf9, 0xa4, 0xa4, 0xaa, 0x4f, 0xb2, 0x2e, 0x5b, 0xbd, 0xc8, 0x65, + 0xf3, 0x27, 0x6f, 0xdb, 0x85, 0x75, 0xab, 0xf5, 0x3b, 0xc0, 0x8b, 0x42, 0x01, 0xfe, 0x06, 0x41, + 0x39, 0x5c, 0xc6, 0xf8, 0x5e, 0x06, 0xa9, 0xe9, 0xed, 0xaf, 0xdc, 0x2f, 0x12, 0x1a, 0xd6, 0x55, + 0xeb, 0x5f, 0xff, 0xf1, 0xcf, 0x0f, 0x0b, 0xb7, 0xf1, 0x92, 0x9e, 0xf7, 0x28, 0xe1, 0x1f, 0x11, + 0x5c, 0x4a, 0xcc, 0x3f, 0x7e, 0x90, 0x57, 0x24, 0xed, 0x8d, 0x50, 0x9a, 0xe7, 0x40, 0x48, 0x76, + 0x6f, 0x08, 0x76, 0x0d, 0x5c, 0xd7, 0x33, 0x9f, 0xc3, 0xd8, 0x8d, 0xc3, 0xbf, 0x22, 0xb8, 0x96, + 0xba, 0x60, 0xf1, 0x9b, 0xb9, 0x96, 0xe4, 0x2c, 0x6e, 0xe5, 0xad, 0x39, 0x90, 0x92, 0xfd, 0x23, + 0xc1, 0xfe, 0x01, 0xd6, 0xb2, 0xbc, 0x0d, 0xd1, 0xdd, 0x89, 0x29, 0xc4, 0x7f, 0x22, 0xb8, 0x99, + 0xb3, 0x72, 0xf0, 0x7b, 0x79, 0x94, 0x66, 0xef, 0x4d, 0xe5, 0xfd, 0xb9, 0xf1, 0x05, 0x85, 0x25, + 0xdb, 0xa2, 0x7f, 0x15, 0xae, 0x8b, 0x03, 0xfc, 0x2f, 0x82, 0x1b, 0x99, 0xaf, 0x04, 0x7e, 0xa7, + 0xe8, 0x7c, 0xa4, 0x3d, 0x61, 0xca, 0xbb, 0x73, 0xa2, 0xa5, 0xa4, 0x0d, 0x21, 0xe9, 0x39, 0xfe, + 0xa0, 0xa0, 0xa4, 0xe4, 0x3e, 0x3b, 0xd0, 0xc5, 0x83, 0x16, 0x29, 0xfd, 0x09, 0xc1, 0x95, 0x89, + 0x6d, 0x8a, 0x5b, 0x33, 0x6d, 0x9f, 0xda, 0xcc, 0xca, 0xc3, 0x73, 0x61, 0xa4, 0x16, 0x5d, 0x68, + 0xb9, 0x87, 0x1b, 0x19, 0x5a, 0xc8, 0x18, 0xd7, 0x95, 0x0f, 0xe6, 0x31, 0x82, 0xd7, 0x32, 0xb6, + 0x25, 0x7e, 0xbb, 0xa8, 0xaf, 0x29, 0x77, 0xe7, 0xf1, 0x5c, 0x58, 0xa9, 0x62, 0x5d, 0xa8, 0x78, + 0x86, 0xdb, 0x73, 0x76, 0x24, 0x76, 0xa3, 0xda, 0x1f, 0x1f, 0x9e, 0x54, 0xd1, 0xd1, 0x49, 0x15, + 0xfd, 0x7d, 0x52, 0x45, 0xdf, 0x9d, 0x56, 0x4b, 0x47, 0xa7, 0xd5, 0xd2, 0x5f, 0xa7, 0xd5, 0xd2, + 0x17, 0x2d, 0xdb, 0xe1, 0xfd, 0x5d, 0x53, 0xb3, 0xd8, 0xf6, 0xb8, 0x8e, 0xd5, 0x27, 0x8e, 0x7b, + 0x56, 0x74, 0x2f, 0x5e, 0x96, 0xef, 0x7b, 0xd4, 0x37, 0xcb, 0xe2, 0xa7, 0xf7, 0xc3, 0xff, 0x02, + 0x00, 0x00, 0xff, 0xff, 0xce, 0x80, 0x35, 0x06, 0x62, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1554,12 +1543,7 @@ func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - if m.DelStatus != 0 { - i = encodeVarintQuery(dAtA, i, uint64(m.DelStatus)) - i-- - dAtA[i] = 0x10 + dAtA[i] = 0x12 } if len(m.ValBtcPkHex) > 0 { i -= len(m.ValBtcPkHex) @@ -1801,9 +1785,6 @@ func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } - if m.DelStatus != 0 { - n += 1 + sovQuery(uint64(m.DelStatus)) - } if m.Pagination != nil { l = m.Pagination.Size() n += 1 + l + sovQuery(uint64(l)) @@ -2885,25 +2866,6 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DelStatus", wireType) - } - m.DelStatus = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DelStatus |= BTCDelegationStatus(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) } @@ -3018,7 +2980,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegations{}) if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index b75d9282a..50c3fec42 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -271,9 +271,12 @@ type MsgAddJurySig struct { // del_pk is the Bitcoin secp256k1 PK of the BTC delegation // the PK follows encoding in BIP-340 spec DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // sig is the signature of the jury // the signature follows encoding in BIP-340 spec - Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` + Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` } func (m *MsgAddJurySig) Reset() { *m = MsgAddJurySig{} } @@ -316,6 +319,13 @@ func (m *MsgAddJurySig) GetSigner() string { return "" } +func (m *MsgAddJurySig) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + // MsgAddJurySigResponse is the response for MsgAddJurySig type MsgAddJurySigResponse struct { } @@ -464,57 +474,58 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 789 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0x4d, 0x6f, 0xe3, 0x44, - 0x18, 0x8e, 0x9b, 0x36, 0x28, 0xd3, 0x86, 0x0a, 0xd3, 0x8f, 0x34, 0x52, 0x9d, 0xc8, 0x42, 0x55, - 0x40, 0xad, 0x4d, 0xd2, 0x0f, 0x89, 0x22, 0x81, 0x9a, 0x72, 0x29, 0x25, 0x22, 0x38, 0x81, 0x03, - 0x07, 0xc2, 0xd8, 0x9e, 0x4c, 0xac, 0x24, 0x1e, 0xcb, 0x33, 0x89, 0x12, 0x71, 0xe3, 0x17, 0x20, - 0x0e, 0xfc, 0x0e, 0x0e, 0xfc, 0x06, 0xd4, 0x63, 0xb5, 0xda, 0xc3, 0xaa, 0x87, 0x68, 0xd5, 0x1e, - 0xf6, 0x3f, 0xec, 0x69, 0x65, 0x7b, 0xfc, 0xb1, 0x2b, 0x47, 0xdb, 0x6e, 0x77, 0x6f, 0x33, 0x7e, - 0x9f, 0xf7, 0x99, 0xf7, 0x7d, 0xde, 0x0f, 0x03, 0x49, 0x87, 0xfa, 0x6c, 0x48, 0x6c, 0x55, 0x67, - 0x06, 0x65, 0x70, 0x60, 0xd9, 0x58, 0x9d, 0xd4, 0x54, 0x36, 0x55, 0x1c, 0x97, 0x30, 0x22, 0x6e, - 0x72, 0xbb, 0x12, 0xdb, 0x95, 0x49, 0xad, 0xb4, 0x81, 0x09, 0x26, 0x3e, 0x42, 0xf5, 0x4e, 0x01, - 0xb8, 0xb4, 0x63, 0x10, 0x3a, 0x22, 0xb4, 0x1b, 0x18, 0x82, 0x0b, 0x37, 0x6d, 0x07, 0x37, 0x75, - 0x44, 0x7d, 0xfe, 0x11, 0xc5, 0xdc, 0x20, 0x73, 0x83, 0xe1, 0xce, 0x1c, 0x46, 0x54, 0x8a, 0x0c, - 0xa7, 0x7e, 0x7c, 0x32, 0xa8, 0xa9, 0x03, 0x34, 0x0b, 0x9d, 0xe5, 0xf4, 0x20, 0x1d, 0xe8, 0xc2, - 0x51, 0x88, 0xd9, 0x4b, 0xc7, 0x24, 0xc2, 0x0e, 0x70, 0xfb, 0x09, 0x9c, 0xd1, 0x47, 0xc6, 0xc0, - 0x21, 0x96, 0xcd, 0x38, 0x34, 0xfe, 0x10, 0xa0, 0xe5, 0x97, 0x02, 0xd8, 0x6c, 0x52, 0x7c, 0xee, - 0x22, 0xc8, 0x50, 0xa3, 0x73, 0xfe, 0x0b, 0x1c, 0x5a, 0x26, 0x64, 0xc4, 0x15, 0xb7, 0x40, 0x8e, - 0x5a, 0xd8, 0x46, 0x6e, 0x51, 0xa8, 0x08, 0xd5, 0xbc, 0xc6, 0x6f, 0xe2, 0x37, 0x00, 0xf0, 0x17, - 0xba, 0xce, 0xa0, 0xb8, 0x54, 0x11, 0xaa, 0xab, 0xf5, 0xb2, 0xc2, 0xb5, 0x08, 0x92, 0x54, 0xa2, - 0x24, 0x95, 0xd6, 0x58, 0xbf, 0x44, 0x33, 0x2d, 0xcf, 0x5d, 0x5a, 0x03, 0xb1, 0x09, 0x72, 0x3a, - 0x33, 0x3c, 0xdf, 0x6c, 0x45, 0xa8, 0xae, 0x35, 0x4e, 0x6e, 0xe6, 0xe5, 0x3a, 0xb6, 0x58, 0x7f, - 0xac, 0x2b, 0x06, 0x19, 0xa9, 0x1c, 0x69, 0xf4, 0xa1, 0x65, 0x87, 0x17, 0x95, 0xcd, 0x1c, 0x44, - 0x95, 0xc6, 0x45, 0xeb, 0xf0, 0xe8, 0x4b, 0x4e, 0xb9, 0xa2, 0x33, 0xa3, 0x35, 0x10, 0x4f, 0x41, - 0xd6, 0x21, 0x4e, 0x71, 0xd9, 0x8f, 0xa3, 0xaa, 0xa4, 0x56, 0x53, 0x69, 0xb9, 0x84, 0xf4, 0x7e, - 0xec, 0xb5, 0x08, 0xa5, 0x88, 0x52, 0x8b, 0xd8, 0x9a, 0xe7, 0x24, 0x97, 0xc1, 0x6e, 0x6a, 0xee, - 0x1a, 0xa2, 0x0e, 0xb1, 0x29, 0x92, 0x9f, 0x66, 0xc1, 0x56, 0x12, 0xf1, 0x1d, 0x1a, 0x22, 0x0c, - 0x99, 0x45, 0xec, 0x0f, 0x26, 0x0f, 0xcf, 0x27, 0xfb, 0x0e, 0xf9, 0x88, 0xdf, 0x02, 0xc0, 0x41, - 0x5d, 0x36, 0xe5, 0x92, 0x54, 0x16, 0x50, 0xb4, 0x83, 0x63, 0x67, 0xaa, 0xe5, 0x69, 0x78, 0x14, - 0x7f, 0x02, 0xeb, 0x31, 0x41, 0xd7, 0xb2, 0x7b, 0xa4, 0xb8, 0xe2, 0xb3, 0x7c, 0x9e, 0x64, 0x49, - 0x34, 0xd1, 0xa4, 0xa6, 0x74, 0x5c, 0x68, 0x53, 0x68, 0x78, 0xa2, 0x5c, 0xd8, 0x3d, 0xa2, 0x15, - 0x22, 0x3a, 0xef, 0x2a, 0xd6, 0xc1, 0x2a, 0x1d, 0x42, 0xda, 0xe7, 0x41, 0xe5, 0xfc, 0x9a, 0x7f, - 0x72, 0x33, 0x2f, 0x17, 0x1a, 0x9d, 0xf3, 0x36, 0xb7, 0x74, 0xa6, 0x1a, 0xa0, 0xd1, 0x59, 0xfc, - 0x0d, 0x14, 0xcc, 0x40, 0x69, 0xe2, 0x76, 0xa9, 0x85, 0x8b, 0x1f, 0xf9, 0x5e, 0x5f, 0xdd, 0xcc, - 0xcb, 0xc7, 0x0f, 0xe9, 0x94, 0xb6, 0x85, 0x6d, 0xc8, 0xc6, 0x2e, 0xd2, 0xd6, 0x22, 0xbe, 0xb6, - 0x85, 0xe5, 0x0a, 0x90, 0xd2, 0xab, 0x1a, 0x15, 0xfe, 0xef, 0x25, 0x50, 0x68, 0x52, 0x7c, 0x66, - 0x9a, 0xdf, 0x8f, 0xdd, 0x59, 0xdb, 0xc2, 0x0b, 0xeb, 0xdd, 0x04, 0xb9, 0x09, 0x1c, 0x86, 0xb5, - 0x7e, 0x44, 0x3b, 0x4f, 0xe0, 0x30, 0x98, 0x0e, 0x13, 0x0d, 0xdf, 0xc3, 0x74, 0x98, 0xc8, 0xa3, - 0xbb, 0x04, 0x59, 0x4f, 0xbf, 0xe5, 0xc7, 0xea, 0xe7, 0xb1, 0xc8, 0xdb, 0xfe, 0xaa, 0x88, 0x35, - 0x89, 0xd4, 0xfa, 0x47, 0x00, 0xeb, 0x4d, 0x8a, 0x7f, 0x76, 0x4c, 0xc8, 0x50, 0xcb, 0x5f, 0x5a, - 0xe2, 0x09, 0xc8, 0xc3, 0x31, 0xeb, 0x13, 0xd7, 0x62, 0xb3, 0x40, 0xb2, 0x46, 0xf1, 0xc9, 0x7f, - 0x07, 0x1b, 0x7c, 0x12, 0xce, 0x4c, 0xd3, 0x45, 0x94, 0xb6, 0x99, 0x6b, 0xd9, 0x58, 0x8b, 0xa1, - 0xe2, 0xd7, 0x20, 0x17, 0xac, 0x3d, 0x3e, 0x3b, 0xbb, 0x8b, 0x46, 0xc0, 0x07, 0x35, 0x96, 0xaf, - 0xe6, 0xe5, 0x8c, 0xc6, 0x5d, 0x4e, 0x3f, 0xfe, 0xf3, 0xc5, 0xbf, 0x5f, 0xc4, 0x64, 0xf2, 0x0e, - 0xd8, 0x7e, 0x23, 0xae, 0x30, 0xe6, 0xfa, 0xff, 0x59, 0x90, 0x6d, 0x52, 0x2c, 0x4e, 0x81, 0x98, - 0xb2, 0xfc, 0xf6, 0x17, 0xbc, 0x9a, 0xba, 0x2e, 0x4a, 0x47, 0x0f, 0x41, 0x87, 0x11, 0x88, 0x7f, - 0x80, 0x4f, 0xd3, 0x16, 0xcb, 0xc1, 0x3d, 0xc8, 0x62, 0x78, 0xe9, 0xf8, 0x41, 0xf0, 0xe8, 0xf1, - 0xdf, 0x01, 0x48, 0x34, 0xf7, 0x67, 0x8b, 0x49, 0x62, 0x54, 0x69, 0xff, 0x3e, 0xa8, 0xe8, 0x85, - 0x1e, 0x58, 0x7b, 0xad, 0x21, 0xf6, 0x16, 0x7b, 0x27, 0x71, 0x25, 0xe5, 0x7e, 0xb8, 0xf0, 0x9d, - 0xc6, 0x0f, 0x57, 0xb7, 0x92, 0x70, 0x7d, 0x2b, 0x09, 0xcf, 0x6f, 0x25, 0xe1, 0xaf, 0x3b, 0x29, - 0x73, 0x7d, 0x27, 0x65, 0x9e, 0xdd, 0x49, 0x99, 0x5f, 0xdf, 0x3a, 0x37, 0xd3, 0xe4, 0xbf, 0xd4, - 0x6f, 0x7c, 0x3d, 0xe7, 0xff, 0x16, 0x0f, 0x5f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1d, 0xcd, 0x67, - 0xc1, 0x37, 0x08, 0x00, 0x00, + // 802 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcb, 0x6e, 0xf3, 0x44, + 0x14, 0x8e, 0x9b, 0x36, 0x28, 0xd3, 0x86, 0x0a, 0xd3, 0x4b, 0x1a, 0xa9, 0x4e, 0x64, 0xa1, 0x2a, + 0xa0, 0xd6, 0x26, 0xe9, 0x45, 0xa2, 0x48, 0xa0, 0xa6, 0x2c, 0x28, 0x25, 0x22, 0x38, 0x81, 0x05, + 0x0b, 0xc2, 0xd8, 0x9e, 0x8c, 0xad, 0x24, 0x1e, 0xcb, 0x33, 0x89, 0x12, 0xb1, 0xe3, 0x09, 0x58, + 0xf1, 0x1c, 0x2c, 0x58, 0xf1, 0x00, 0xa8, 0xcb, 0x0a, 0xb1, 0x40, 0x5d, 0x44, 0xa8, 0x5d, 0xf0, + 0x0e, 0xac, 0x90, 0xed, 0xf1, 0xa5, 0xc8, 0x11, 0x2d, 0xe5, 0xdf, 0xcd, 0xf8, 0x7c, 0xe7, 0x9b, + 0x73, 0xbe, 0x73, 0x31, 0x90, 0x74, 0xa8, 0xcf, 0x47, 0xc4, 0x51, 0x75, 0x66, 0x50, 0x06, 0x87, + 0xb6, 0x83, 0xd5, 0x69, 0x43, 0x65, 0x33, 0xc5, 0xf5, 0x08, 0x23, 0xe2, 0x36, 0xb7, 0x2b, 0x89, + 0x5d, 0x99, 0x36, 0x2a, 0x5b, 0x98, 0x60, 0x12, 0x20, 0x54, 0xff, 0x14, 0x82, 0x2b, 0x7b, 0x06, + 0xa1, 0x63, 0x42, 0xfb, 0xa1, 0x21, 0xbc, 0x70, 0xd3, 0x6e, 0x78, 0x53, 0xc7, 0x34, 0xe0, 0x1f, + 0x53, 0xcc, 0x0d, 0x32, 0x37, 0x18, 0xde, 0xdc, 0x65, 0x44, 0xa5, 0xc8, 0x70, 0x9b, 0xa7, 0x67, + 0xc3, 0x86, 0x3a, 0x44, 0xf3, 0xc8, 0x59, 0xce, 0x0e, 0xd2, 0x85, 0x1e, 0x1c, 0x47, 0x98, 0x83, + 0x6c, 0x4c, 0x2a, 0xec, 0x10, 0x77, 0x98, 0xc2, 0x19, 0x16, 0x32, 0x86, 0x2e, 0xb1, 0x1d, 0xc6, + 0xa1, 0xc9, 0x87, 0x10, 0x2d, 0xff, 0x25, 0x80, 0xed, 0x36, 0xc5, 0x97, 0x1e, 0x82, 0x0c, 0xb5, + 0x7a, 0x97, 0x5f, 0xc2, 0x91, 0x6d, 0x42, 0x46, 0x3c, 0x71, 0x07, 0x14, 0xa8, 0x8d, 0x1d, 0xe4, + 0x95, 0x85, 0x9a, 0x50, 0x2f, 0x6a, 0xfc, 0x26, 0x7e, 0x00, 0x00, 0x7f, 0xa1, 0xef, 0x0e, 0xcb, + 0x2b, 0x35, 0xa1, 0xbe, 0xde, 0xac, 0x2a, 0x5c, 0x8b, 0x30, 0x49, 0x25, 0x4e, 0x52, 0xe9, 0x4c, + 0xf4, 0x6b, 0x34, 0xd7, 0x8a, 0xdc, 0xa5, 0x33, 0x14, 0xdb, 0xa0, 0xa0, 0x33, 0xc3, 0xf7, 0xcd, + 0xd7, 0x84, 0xfa, 0x46, 0xeb, 0xec, 0x6e, 0x51, 0x6d, 0x62, 0x9b, 0x59, 0x13, 0x5d, 0x31, 0xc8, + 0x58, 0xe5, 0x48, 0xc3, 0x82, 0xb6, 0x13, 0x5d, 0x54, 0x36, 0x77, 0x11, 0x55, 0x5a, 0x57, 0x9d, + 0xe3, 0x93, 0x77, 0x39, 0xe5, 0x9a, 0xce, 0x8c, 0xce, 0x50, 0x3c, 0x07, 0x79, 0x97, 0xb8, 0xe5, + 0xd5, 0x20, 0x8e, 0xba, 0x92, 0x59, 0x4d, 0xa5, 0xe3, 0x11, 0x32, 0xf8, 0x6c, 0xd0, 0x21, 0x94, + 0x22, 0x4a, 0x6d, 0xe2, 0x68, 0xbe, 0x93, 0x5c, 0x05, 0xfb, 0x99, 0xb9, 0x6b, 0x88, 0xba, 0xc4, + 0xa1, 0x48, 0xfe, 0x2d, 0x0f, 0x76, 0xd2, 0x88, 0x8f, 0xd0, 0x08, 0x61, 0xc8, 0x6c, 0xe2, 0xbc, + 0x32, 0x79, 0x78, 0x3e, 0xf9, 0xff, 0x90, 0x8f, 0xf8, 0x21, 0x00, 0x1c, 0xd4, 0x67, 0x33, 0x2e, + 0x49, 0x6d, 0x09, 0x45, 0x37, 0x3c, 0xf6, 0x66, 0x5a, 0x91, 0x46, 0x47, 0xf1, 0x73, 0xb0, 0x99, + 0x10, 0xf4, 0x6d, 0x67, 0x40, 0xca, 0x6b, 0x01, 0xcb, 0xdb, 0x69, 0x96, 0x54, 0x13, 0x4d, 0x1b, + 0x4a, 0xcf, 0x83, 0x0e, 0x85, 0x86, 0x2f, 0xca, 0x95, 0x33, 0x20, 0x5a, 0x29, 0xa6, 0xf3, 0xaf, + 0x62, 0x13, 0xac, 0xd3, 0x11, 0xa4, 0x16, 0x0f, 0xaa, 0x10, 0xd4, 0xfc, 0x8d, 0xbb, 0x45, 0xb5, + 0xd4, 0xea, 0x5d, 0x76, 0xb9, 0xa5, 0x37, 0xd3, 0x00, 0x8d, 0xcf, 0xe2, 0xd7, 0xa0, 0x64, 0x86, + 0x4a, 0x13, 0xaf, 0x4f, 0x6d, 0x5c, 0x7e, 0x2d, 0xf0, 0x7a, 0xef, 0x6e, 0x51, 0x3d, 0x7d, 0x4e, + 0xa7, 0x74, 0x6d, 0xec, 0x40, 0x36, 0xf1, 0x90, 0xb6, 0x11, 0xf3, 0x75, 0x6d, 0x2c, 0xd7, 0x80, + 0x94, 0x5d, 0xd5, 0xb8, 0xf0, 0x3f, 0xaf, 0x80, 0x52, 0x9b, 0xe2, 0x0b, 0xd3, 0xfc, 0x64, 0xe2, + 0xcd, 0xbb, 0x36, 0x5e, 0x5a, 0xef, 0x36, 0x28, 0x4c, 0xe1, 0x28, 0xaa, 0xf5, 0x0b, 0xda, 0x79, + 0x0a, 0x47, 0xe1, 0x74, 0x98, 0x68, 0xf4, 0x3f, 0x4c, 0x87, 0x89, 0x7c, 0xba, 0x83, 0x47, 0x05, + 0xb5, 0x20, 0xb5, 0x82, 0xb6, 0x28, 0xa6, 0xaa, 0xf4, 0x31, 0xa4, 0x96, 0x78, 0x0d, 0xf2, 0xbe, + 0xce, 0x6b, 0x2f, 0xd5, 0xd9, 0x67, 0x91, 0x77, 0x83, 0x95, 0x92, 0x68, 0x17, 0xab, 0xfa, 0x83, + 0x00, 0x36, 0xdb, 0x14, 0x7f, 0xe1, 0x9a, 0x90, 0xa1, 0x4e, 0xb0, 0xdc, 0xc4, 0x33, 0x50, 0x84, + 0x13, 0x66, 0x11, 0xcf, 0x66, 0xf3, 0x50, 0xda, 0x56, 0xf9, 0xd7, 0x9f, 0x8e, 0xb6, 0xf8, 0xc4, + 0x5c, 0x98, 0xa6, 0x87, 0x28, 0xed, 0x32, 0xcf, 0x76, 0xb0, 0x96, 0x40, 0xc5, 0xf7, 0x41, 0x21, + 0x5c, 0x8f, 0x7c, 0xc6, 0xf6, 0x97, 0x8d, 0x4a, 0x00, 0x6a, 0xad, 0xde, 0x2c, 0xaa, 0x39, 0x8d, + 0xbb, 0x9c, 0xbf, 0xfe, 0xdd, 0x9f, 0x3f, 0xbe, 0x93, 0x90, 0xc9, 0x7b, 0x60, 0xf7, 0x1f, 0x71, + 0x45, 0x31, 0x37, 0x7f, 0xc9, 0x83, 0x7c, 0x9b, 0x62, 0x71, 0x06, 0xc4, 0x8c, 0x25, 0x79, 0xb8, + 0xe4, 0xd5, 0xcc, 0xb5, 0x52, 0x39, 0x79, 0x0e, 0x3a, 0x8a, 0x40, 0xfc, 0x16, 0xbc, 0x99, 0xb5, + 0x80, 0x8e, 0x9e, 0x40, 0x96, 0xc0, 0x2b, 0xa7, 0xcf, 0x82, 0xc7, 0x8f, 0x7f, 0x03, 0x40, 0x6a, + 0x08, 0xde, 0x5a, 0x4e, 0x92, 0xa0, 0x2a, 0x87, 0x4f, 0x41, 0xc5, 0x2f, 0x0c, 0xc0, 0xc6, 0xa3, + 0x86, 0x38, 0x58, 0xee, 0x9d, 0xc6, 0x55, 0x94, 0xa7, 0xe1, 0xa2, 0x77, 0x5a, 0x9f, 0xde, 0xdc, + 0x4b, 0xc2, 0xed, 0xbd, 0x24, 0xfc, 0x71, 0x2f, 0x09, 0xdf, 0x3f, 0x48, 0xb9, 0xdb, 0x07, 0x29, + 0xf7, 0xfb, 0x83, 0x94, 0xfb, 0xea, 0x5f, 0xe7, 0x6b, 0x96, 0xfe, 0xe7, 0x06, 0x8d, 0xaf, 0x17, + 0x82, 0xdf, 0xe7, 0xf1, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x98, 0xbb, 0xd7, 0x70, 0x5f, 0x08, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -957,6 +968,13 @@ func (m *MsgAddJurySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x2a + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) + i-- dAtA[i] = 0x22 } if m.DelPk != nil { @@ -1188,6 +1206,10 @@ func (m *MsgAddJurySig) Size() (n int) { l = m.DelPk.Size() n += 1 + l + sovTx(uint64(l)) } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } if m.Sig != nil { l = m.Sig.Size() n += 1 + l + sovTx(uint64(l)) @@ -1951,6 +1973,38 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) } From 26331f3a7594c8f468e0f5a8f56ab1532cf833bf Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 4 Aug 2023 16:38:31 +0800 Subject: [PATCH 052/202] btcstaking: recording slashed height and disallowing slashed validator to vote (#50) --- proto/babylon/btcstaking/v1/btcstaking.proto | 18 +- x/btcstaking/keeper/btc_validators.go | 10 +- x/btcstaking/keeper/grpc_query.go | 8 +- x/btcstaking/keeper/voting_power_table.go | 2 +- .../keeper/voting_power_table_test.go | 26 +- x/btcstaking/types/btcstaking.go | 4 + x/btcstaking/types/btcstaking.pb.go | 238 +++++++++++++----- x/finality/keeper/msg_server.go | 28 ++- x/finality/keeper/msg_server_test.go | 15 ++ x/finality/types/expected_keepers.go | 2 + x/finality/types/mocked_keepers.go | 38 ++- 11 files changed, 297 insertions(+), 92 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index ce8fde88e..5542c03df 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -15,8 +15,14 @@ message BTCValidator { bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // slashed indicates whether the BTC validator is slashed or not - bool slashed = 4; + // slashed_babylon_height indicates the Babylon height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + uint64 slashed_babylon_height = 4; + // slashed_btc_height indicates the BTC height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + uint64 slashed_btc_height = 5; } // BTCValidatorWithMeta wraps the BTCValidator with meta data. @@ -28,6 +34,14 @@ message BTCValidatorWithMeta { uint64 height = 2; // voting_power is the voting power of this BTC validator at the given height uint64 voting_power = 3; + // slashed_babylon_height indicates the Babylon height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + uint64 slashed_babylon_height = 4; + // slashed_btc_height indicates the BTC height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + uint64 slashed_btc_height = 5; } // BTCDelegation defines a BTC delegation diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 40b361d5d..b440451dd 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -38,10 +40,14 @@ func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { if err != nil { return err } - if btcVal.Slashed { + if btcVal.IsSlashed() { return types.ErrBTCValAlreadySlashed } - btcVal.Slashed = true + btcVal.SlashedBabylonHeight = uint64(ctx.BlockHeight()) + btcVal.SlashedBtcHeight, err = k.GetCurrentBTCHeight(ctx) + if err != nil { + panic(fmt.Errorf("failed to get current BTC height: %w", err)) + } k.SetBTCValidator(ctx, btcVal) return nil } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index cd9420d6a..797a2cf24 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -127,9 +127,11 @@ func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.Quer votingPower := k.GetVotingPower(sdkCtx, key, req.Height) if votingPower > 0 { btcValidatorWithMeta := types.BTCValidatorWithMeta{ - BtcPk: btcValidator.BtcPk, - Height: req.Height, - VotingPower: votingPower, + BtcPk: btcValidator.BtcPk, + Height: req.Height, + VotingPower: votingPower, + SlashedBabylonHeight: btcValidator.SlashedBabylonHeight, + SlashedBtcHeight: btcValidator.SlashedBtcHeight, } btcValidatorsWithMeta = append(btcValidatorsWithMeta, &btcValidatorWithMeta) } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 7da3983a6..5a90b0539 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -37,7 +37,7 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // failed to get a BTC validator with voting power is a programming error panic(err) } - if btcVal.Slashed { + if btcVal.IsSlashed() { // slashed BTC validator is removed from BTC validator set continue } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index aaf4eff87..c33722d35 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -60,7 +60,9 @@ func FuzzVotingPowerTable(f *testing.F) { } } - // Case 1: assert none of validators has voting power (since BTC height is 0) + /* + Case 1: assert none of validators has voting power (since BTC height is 0) + */ babylonHeight := datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) @@ -75,7 +77,9 @@ func FuzzVotingPowerTable(f *testing.F) { _, err = keeper.GetBTCStakingActivatedHeight(ctx) require.Error(t, err) - // Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power + /* + Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power + */ babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) @@ -102,19 +106,23 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) require.Equal(t, babylonHeight, activatedHeight) - // Case 3: move to 2nd BTC block and slash a random BTC validator, - // then assert the slashed BTC validator does not have voting power - babylonHeight += datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1) + /* + Case 3: slash a random BTC validator and move on + then assert the slashed BTC validator does not have voting power + */ // slash a random BTC validator slashedIdx := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) slashedVal := btcVals[slashedIdx] err = keeper.SlashBTCValidator(ctx, slashedVal.BtcPk.MustMarshal()) require.NoError(t, err) + // move to later Babylon height and 2nd BTC height + babylonHeight += datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1) // index height and record power table keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) + // check if the slashed BTC validator's voting power becomes zero for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) if i == slashedIdx { @@ -139,7 +147,9 @@ func FuzzVotingPowerTable(f *testing.F) { require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power) } - // Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + /* + Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + */ babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7000bf924..e5e5def96 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -12,6 +12,10 @@ import ( "github.com/btcsuite/btcd/wire" ) +func (v *BTCValidator) IsSlashed() bool { + return v.SlashedBabylonHeight > 0 +} + func (v *BTCValidator) ValidateBasic() error { // ensure fields are non-empty and well-formatted if v.BabylonPk == nil { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index ecfb16ab5..c0c1908c7 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -66,8 +66,14 @@ type BTCValidator struct { BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` - // slashed indicates whether the BTC validator is slashed or not - Slashed bool `protobuf:"varint,4,opt,name=slashed,proto3" json:"slashed,omitempty"` + // slashed_babylon_height indicates the Babylon height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + SlashedBabylonHeight uint64 `protobuf:"varint,4,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` + // slashed_btc_height indicates the BTC height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + SlashedBtcHeight uint64 `protobuf:"varint,5,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` } func (m *BTCValidator) Reset() { *m = BTCValidator{} } @@ -117,11 +123,18 @@ func (m *BTCValidator) GetPop() *ProofOfPossession { return nil } -func (m *BTCValidator) GetSlashed() bool { +func (m *BTCValidator) GetSlashedBabylonHeight() uint64 { if m != nil { - return m.Slashed + return m.SlashedBabylonHeight } - return false + return 0 +} + +func (m *BTCValidator) GetSlashedBtcHeight() uint64 { + if m != nil { + return m.SlashedBtcHeight + } + return 0 } // BTCValidatorWithMeta wraps the BTCValidator with meta data. @@ -133,6 +146,14 @@ type BTCValidatorWithMeta struct { Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` // voting_power is the voting power of this BTC validator at the given height VotingPower uint64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + // slashed_babylon_height indicates the Babylon height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + SlashedBabylonHeight uint64 `protobuf:"varint,4,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` + // slashed_btc_height indicates the BTC height when + // the BTC validator is slashed. + // if it's 0 then the BTC validator is not slashed + SlashedBtcHeight uint64 `protobuf:"varint,5,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` } func (m *BTCValidatorWithMeta) Reset() { *m = BTCValidatorWithMeta{} } @@ -182,6 +203,20 @@ func (m *BTCValidatorWithMeta) GetVotingPower() uint64 { return 0 } +func (m *BTCValidatorWithMeta) GetSlashedBabylonHeight() uint64 { + if m != nil { + return m.SlashedBabylonHeight + } + return 0 +} + +func (m *BTCValidatorWithMeta) GetSlashedBtcHeight() uint64 { + if m != nil { + return m.SlashedBtcHeight + } + return 0 +} + // BTCDelegation defines a BTC delegation type BTCDelegation struct { // babylon_pk is the Babylon secp256k1 PK of this BTC delegation @@ -461,52 +496,54 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 713 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcb, 0x6e, 0xd3, 0x4c, - 0x14, 0x8e, 0xd3, 0x34, 0x97, 0x93, 0xb4, 0x6a, 0xe7, 0xef, 0x8f, 0xac, 0x22, 0x92, 0x34, 0x02, - 0x14, 0xb1, 0xb0, 0x69, 0x4a, 0x2b, 0x2e, 0x12, 0x88, 0xb4, 0x11, 0x04, 0x68, 0xb1, 0xec, 0xa8, - 0x20, 0x16, 0x44, 0x63, 0x67, 0xea, 0x98, 0xb8, 0x1e, 0xcb, 0x33, 0x09, 0xc9, 0x1b, 0xb0, 0xe4, - 0x11, 0x78, 0x0e, 0x9e, 0x80, 0x65, 0x97, 0xa8, 0x8b, 0x0a, 0xb5, 0x8f, 0xc0, 0x0b, 0x20, 0x8f, - 0xed, 0x28, 0x15, 0x54, 0x08, 0x95, 0x15, 0xbb, 0x73, 0xfd, 0xfc, 0x7d, 0x67, 0xce, 0x91, 0xe1, - 0xa6, 0x89, 0xcd, 0x89, 0x4b, 0x3d, 0xd5, 0xe4, 0x16, 0xe3, 0x78, 0xe0, 0x78, 0xb6, 0x3a, 0x5a, - 0x9f, 0xf1, 0x14, 0x3f, 0xa0, 0x9c, 0xa2, 0xff, 0xe3, 0x3a, 0x65, 0x26, 0x33, 0x5a, 0x5f, 0x5d, - 0xb1, 0xa9, 0x4d, 0x45, 0x85, 0x1a, 0x5a, 0x51, 0xf1, 0x6a, 0xcd, 0xa2, 0xec, 0x90, 0x32, 0xd5, - 0x0a, 0x26, 0x3e, 0xa7, 0x2a, 0x23, 0x96, 0xdf, 0xd8, 0xdc, 0x1a, 0xac, 0xab, 0x03, 0x32, 0x61, - 0x51, 0x4d, 0xed, 0xbb, 0x04, 0xa5, 0x66, 0x67, 0x7b, 0x1f, 0xbb, 0x4e, 0x0f, 0x73, 0x1a, 0xa0, - 0x87, 0x00, 0xf1, 0x37, 0xba, 0xfe, 0x40, 0x96, 0xaa, 0x52, 0xbd, 0xd8, 0xa8, 0x28, 0x11, 0x92, - 0x12, 0x21, 0x29, 0x53, 0x24, 0x45, 0x1b, 0x9a, 0xcf, 0xc9, 0x44, 0x2f, 0xc4, 0x2d, 0xda, 0x00, - 0xed, 0x42, 0xd6, 0xe4, 0x56, 0xd8, 0x9b, 0xae, 0x4a, 0xf5, 0x52, 0x73, 0xeb, 0xf8, 0xa4, 0xd2, - 0xb0, 0x1d, 0xde, 0x1f, 0x9a, 0x8a, 0x45, 0x0f, 0xd5, 0xb8, 0xd2, 0xea, 0x63, 0xc7, 0x4b, 0x1c, - 0x95, 0x4f, 0x7c, 0xc2, 0x94, 0x66, 0x5b, 0xdb, 0xb8, 0x73, 0x3b, 0x86, 0x9c, 0x37, 0xb9, 0xa5, - 0x0d, 0xd0, 0x7d, 0x98, 0xf3, 0xa9, 0x2f, 0xcf, 0x09, 0x1e, 0x75, 0xe5, 0x97, 0xf2, 0x15, 0x2d, - 0xa0, 0xf4, 0xe0, 0xe5, 0x81, 0x46, 0x19, 0x23, 0x8c, 0x39, 0xd4, 0xd3, 0xc3, 0x26, 0x24, 0x43, - 0x8e, 0xb9, 0x98, 0xf5, 0x49, 0x4f, 0xce, 0x54, 0xa5, 0x7a, 0x5e, 0x4f, 0xdc, 0xda, 0x27, 0x09, - 0x56, 0x66, 0x55, 0xbf, 0x72, 0x78, 0x7f, 0x97, 0x70, 0x3c, 0xc3, 0x5e, 0xfa, 0x1b, 0xec, 0xaf, - 0x40, 0xb6, 0x4f, 0x1c, 0xbb, 0xcf, 0xc5, 0x30, 0x32, 0x7a, 0xec, 0xa1, 0x35, 0x28, 0x8d, 0x28, - 0x77, 0x3c, 0xbb, 0xeb, 0xd3, 0xf7, 0x24, 0x10, 0xf2, 0x32, 0x7a, 0x31, 0x8a, 0x69, 0x61, 0xa8, - 0xf6, 0x79, 0x1e, 0x16, 0x9a, 0x9d, 0xed, 0x1d, 0xe2, 0x12, 0x1b, 0x73, 0x87, 0x7a, 0xff, 0xd2, - 0xcb, 0x74, 0x00, 0x46, 0xd8, 0xed, 0xc6, 0x74, 0x32, 0x97, 0xa2, 0x93, 0x1f, 0x61, 0xb7, 0x29, - 0x18, 0xad, 0x41, 0x89, 0x71, 0x1c, 0xf0, 0x6e, 0x3c, 0xf3, 0xf9, 0x68, 0xaa, 0x22, 0xf6, 0x34, - 0x1a, 0xfc, 0x35, 0x00, 0xe2, 0xf5, 0x92, 0x82, 0xac, 0x28, 0x28, 0x10, 0xaf, 0x17, 0xa7, 0xaf, - 0x42, 0x81, 0x53, 0x8e, 0xdd, 0x2e, 0xc3, 0x5c, 0xce, 0x89, 0x6c, 0x5e, 0x04, 0x0c, 0xcc, 0xd1, - 0x23, 0x80, 0x58, 0x59, 0x97, 0x8f, 0xe5, 0xbc, 0xd0, 0x5d, 0xbd, 0x40, 0xb7, 0x11, 0x99, 0x9d, - 0xb1, 0x5e, 0x60, 0x89, 0x89, 0x1a, 0x50, 0x14, 0x0b, 0x18, 0x23, 0x14, 0x84, 0xec, 0xe5, 0xe3, - 0x93, 0x4a, 0xf8, 0xd0, 0x46, 0x9c, 0xe9, 0x8c, 0x75, 0x60, 0x53, 0x1b, 0xbd, 0x85, 0x85, 0x5e, - 0xb4, 0x02, 0x34, 0xe8, 0x32, 0xc7, 0x96, 0x41, 0x74, 0xdd, 0x3b, 0x3e, 0xa9, 0x6c, 0xfe, 0xc9, - 0xb0, 0x0c, 0xc7, 0xf6, 0x30, 0x1f, 0x06, 0x44, 0x2f, 0x4d, 0xf1, 0x0c, 0xc7, 0x46, 0x1d, 0xc8, - 0xbf, 0x1b, 0x06, 0x13, 0x01, 0x5d, 0xbc, 0x2c, 0x74, 0x2e, 0x84, 0x32, 0x1c, 0xbb, 0xf6, 0x0c, - 0x16, 0xcf, 0xed, 0x2e, 0x43, 0x77, 0x21, 0xd3, 0x23, 0x2e, 0x93, 0xa5, 0xea, 0x5c, 0xbd, 0xd8, - 0xb8, 0x7e, 0xc1, 0xd8, 0xce, 0x35, 0xe9, 0xa2, 0xa3, 0xf6, 0x41, 0x82, 0xe5, 0x9f, 0xd6, 0x08, - 0x55, 0xa0, 0x98, 0x1c, 0x43, 0x48, 0x5d, 0x5c, 0xab, 0x9e, 0xdc, 0x47, 0x28, 0x4c, 0x87, 0x5c, - 0xb8, 0x5e, 0x61, 0x32, 0x7d, 0x59, 0x5d, 0xe1, 0xdd, 0x84, 0xb2, 0x9a, 0x50, 0x98, 0x3e, 0x2c, - 0x5a, 0x84, 0x34, 0x1f, 0xc7, 0x1f, 0x4e, 0xf3, 0x31, 0xba, 0x01, 0x8b, 0xc9, 0x7a, 0x30, 0x2b, - 0x70, 0xfc, 0xe8, 0xe6, 0x4b, 0xfa, 0x42, 0x1c, 0x35, 0x44, 0xf0, 0xd6, 0x03, 0xf8, 0xef, 0x9c, - 0x4a, 0x83, 0x63, 0x3e, 0x64, 0xa8, 0x08, 0x39, 0xad, 0xb5, 0xb7, 0xd3, 0xde, 0x7b, 0xb2, 0x94, - 0x42, 0x00, 0xd9, 0xc7, 0xdb, 0x9d, 0xf6, 0x7e, 0x6b, 0x49, 0x0a, 0x13, 0xad, 0xd7, 0x5a, 0x5b, - 0x6f, 0xed, 0x2c, 0xa5, 0x9b, 0x2f, 0xbe, 0x9c, 0x96, 0xa5, 0xa3, 0xd3, 0xb2, 0xf4, 0xed, 0xb4, - 0x2c, 0x7d, 0x3c, 0x2b, 0xa7, 0x8e, 0xce, 0xca, 0xa9, 0xaf, 0x67, 0xe5, 0xd4, 0x9b, 0xdf, 0x5e, - 0xce, 0x78, 0xf6, 0xd7, 0x22, 0x64, 0x9a, 0x59, 0xf1, 0x0b, 0xd8, 0xf8, 0x11, 0x00, 0x00, 0xff, - 0xff, 0x1f, 0xf7, 0x07, 0x09, 0x7d, 0x06, 0x00, 0x00, + // 744 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcf, 0x6f, 0xe3, 0x44, + 0x14, 0x8e, 0xdd, 0x34, 0x3f, 0x9e, 0xd3, 0x2a, 0x1d, 0x4a, 0x15, 0x15, 0x91, 0xa4, 0x11, 0xa0, + 0x08, 0x21, 0x9b, 0xa6, 0x3f, 0xc4, 0x0f, 0x09, 0x84, 0xdb, 0x08, 0x02, 0xb4, 0x58, 0x76, 0x54, + 0x10, 0x07, 0xa2, 0xb1, 0xe3, 0x3a, 0x26, 0xae, 0xc7, 0xf2, 0x4c, 0x42, 0x72, 0xe7, 0xc0, 0x91, + 0xbf, 0x07, 0x89, 0xfb, 0x1e, 0x7b, 0x5c, 0xf5, 0x50, 0xad, 0xda, 0x7f, 0x64, 0xe5, 0xf1, 0x38, + 0x4a, 0xb4, 0x5b, 0xad, 0x56, 0x5d, 0xed, 0x61, 0x6f, 0x33, 0xef, 0x7b, 0xdf, 0xe7, 0xf7, 0xbd, + 0x79, 0xe3, 0x81, 0x4f, 0x6c, 0x6c, 0xcf, 0x03, 0x12, 0x6a, 0x36, 0x73, 0x28, 0xc3, 0x63, 0x3f, + 0xf4, 0xb4, 0xe9, 0xfe, 0xd2, 0x4e, 0x8d, 0x62, 0xc2, 0x08, 0x7a, 0x5f, 0xe4, 0xa9, 0x4b, 0xc8, + 0x74, 0x7f, 0x77, 0xdb, 0x23, 0x1e, 0xe1, 0x19, 0x5a, 0xb2, 0x4a, 0x93, 0x77, 0x5b, 0x0e, 0xa1, + 0x57, 0x84, 0x6a, 0x4e, 0x3c, 0x8f, 0x18, 0xd1, 0xa8, 0xeb, 0x44, 0x9d, 0xa3, 0xe3, 0xf1, 0xbe, + 0x36, 0x76, 0xe7, 0x34, 0xcd, 0x69, 0xfd, 0x2f, 0x43, 0x45, 0xef, 0x9f, 0x5c, 0xe0, 0xc0, 0x1f, + 0x62, 0x46, 0x62, 0xf4, 0x0d, 0x80, 0xf8, 0xc6, 0x20, 0x1a, 0xd7, 0xa4, 0xa6, 0xd4, 0x56, 0x3a, + 0x0d, 0x35, 0x55, 0x52, 0x53, 0x25, 0x75, 0xa1, 0xa4, 0x1a, 0x13, 0xfb, 0x27, 0x77, 0x6e, 0x96, + 0x05, 0xc5, 0x18, 0xa3, 0x33, 0x28, 0xd8, 0xcc, 0x49, 0xb8, 0x72, 0x53, 0x6a, 0x57, 0xf4, 0xe3, + 0x9b, 0xdb, 0x46, 0xc7, 0xf3, 0xd9, 0x68, 0x62, 0xab, 0x0e, 0xb9, 0xd2, 0x44, 0xa6, 0x33, 0xc2, + 0x7e, 0x98, 0x6d, 0x34, 0x36, 0x8f, 0x5c, 0xaa, 0xea, 0x3d, 0xe3, 0xe0, 0xf0, 0x73, 0x21, 0xb9, + 0x6e, 0x33, 0xc7, 0x18, 0xa3, 0xaf, 0x60, 0x2d, 0x22, 0x51, 0x6d, 0x8d, 0xd7, 0xd1, 0x56, 0x5f, + 0x6a, 0x5f, 0x35, 0x62, 0x42, 0x2e, 0x7f, 0xb9, 0x34, 0x08, 0xa5, 0x2e, 0xa5, 0x3e, 0x09, 0xcd, + 0x84, 0x84, 0x0e, 0x61, 0x87, 0x06, 0x98, 0x8e, 0xdc, 0xe1, 0x20, 0xb3, 0x34, 0x72, 0x7d, 0x6f, + 0xc4, 0x6a, 0xf9, 0xa6, 0xd4, 0xce, 0x9b, 0xdb, 0x02, 0xd5, 0x53, 0xf0, 0x07, 0x8e, 0xa1, 0xcf, + 0x00, 0x2d, 0x58, 0xcc, 0xc9, 0x18, 0xeb, 0x9c, 0x51, 0xcd, 0x18, 0xcc, 0x49, 0xb3, 0x5b, 0x7f, + 0xcb, 0xb0, 0xbd, 0xdc, 0xbf, 0x5f, 0x7d, 0x36, 0x3a, 0x73, 0x19, 0x5e, 0xea, 0x83, 0xf4, 0x26, + 0xfa, 0xb0, 0x03, 0x05, 0x51, 0x89, 0xcc, 0x2b, 0x11, 0x3b, 0xb4, 0x07, 0x95, 0x29, 0x61, 0x7e, + 0xe8, 0x0d, 0x22, 0xf2, 0x97, 0x1b, 0xf3, 0x46, 0xe5, 0x4d, 0x25, 0x8d, 0x19, 0x49, 0xe8, 0xad, + 0xb4, 0xe1, 0xbf, 0x75, 0xd8, 0xd0, 0xfb, 0x27, 0xa7, 0x6e, 0xe0, 0x7a, 0x98, 0xf9, 0x24, 0x7c, + 0x97, 0xe6, 0xa8, 0x0f, 0x30, 0xc5, 0xc1, 0x40, 0x94, 0x93, 0x7f, 0x54, 0x39, 0xa5, 0x29, 0x0e, + 0x74, 0x5e, 0xd1, 0x1e, 0x54, 0x28, 0xc3, 0x31, 0x5b, 0x6d, 0xad, 0xc2, 0x63, 0xe2, 0x0c, 0x3e, + 0x04, 0x70, 0xc3, 0x61, 0x96, 0x50, 0xe0, 0x09, 0x65, 0x37, 0x1c, 0x0a, 0xf8, 0x03, 0x28, 0x33, + 0xc2, 0x70, 0x30, 0xa0, 0x98, 0xd5, 0x8a, 0x1c, 0x2d, 0xf1, 0x80, 0x85, 0x19, 0xfa, 0x16, 0x40, + 0x38, 0x1b, 0xb0, 0x59, 0xad, 0xc4, 0x7d, 0x37, 0x1f, 0xf0, 0x6d, 0xa5, 0xcb, 0xfe, 0xcc, 0x2c, + 0xd3, 0x6c, 0x89, 0x3a, 0xa0, 0xf0, 0x63, 0x16, 0x0a, 0x65, 0x6e, 0x7b, 0xeb, 0xe6, 0xb6, 0x91, + 0x1c, 0xb4, 0x25, 0x90, 0xfe, 0xcc, 0x04, 0xba, 0x58, 0xa3, 0x3f, 0x60, 0x63, 0x98, 0x8e, 0x00, + 0x89, 0x07, 0xd4, 0xf7, 0x6a, 0xc0, 0x59, 0x5f, 0xde, 0xdc, 0x36, 0x8e, 0x5e, 0xa7, 0x59, 0x96, + 0xef, 0x85, 0x98, 0x4d, 0x62, 0xd7, 0xac, 0x2c, 0xf4, 0x2c, 0xdf, 0x43, 0x7d, 0x28, 0xfd, 0x39, + 0x89, 0xe7, 0x5c, 0x5a, 0x79, 0xac, 0x74, 0x31, 0x91, 0xb2, 0x7c, 0xaf, 0xf5, 0x23, 0x6c, 0xae, + 0xcc, 0x2e, 0x45, 0x5f, 0x40, 0x7e, 0xe8, 0x06, 0xb4, 0x26, 0x35, 0xd7, 0xda, 0x4a, 0xe7, 0xa3, + 0x07, 0xda, 0xb6, 0x42, 0x32, 0x39, 0xa3, 0xf5, 0x8f, 0x04, 0x5b, 0x2f, 0x8c, 0x11, 0x6a, 0x80, + 0x92, 0x5d, 0x86, 0xa4, 0x74, 0xfe, 0x47, 0x30, 0xb3, 0xfb, 0x91, 0x18, 0x33, 0xa1, 0x98, 0x8c, + 0x57, 0x02, 0xca, 0x8f, 0xf5, 0x95, 0xdc, 0x9b, 0xc4, 0x96, 0x0e, 0xe5, 0xc5, 0xc1, 0xa2, 0x4d, + 0x90, 0xd9, 0x4c, 0x7c, 0x58, 0x66, 0x33, 0xf4, 0x31, 0x6c, 0x66, 0xe3, 0x41, 0x9d, 0xd8, 0x8f, + 0xd2, 0xff, 0x4a, 0xc5, 0xdc, 0x10, 0x51, 0x8b, 0x07, 0x3f, 0xfd, 0x1a, 0xde, 0x5b, 0x71, 0x69, + 0x31, 0xcc, 0x26, 0x14, 0x29, 0x50, 0x34, 0xba, 0xe7, 0xa7, 0xbd, 0xf3, 0xef, 0xab, 0x39, 0x04, + 0x50, 0xf8, 0xee, 0xa4, 0xdf, 0xbb, 0xe8, 0x56, 0xa5, 0x04, 0xe8, 0xfe, 0x66, 0xf4, 0xcc, 0xee, + 0x69, 0x55, 0xd6, 0x7f, 0x7e, 0x72, 0x57, 0x97, 0xae, 0xef, 0xea, 0xd2, 0xb3, 0xbb, 0xba, 0xf4, + 0xef, 0x7d, 0x3d, 0x77, 0x7d, 0x5f, 0xcf, 0x3d, 0xbd, 0xaf, 0xe7, 0x7e, 0x7f, 0xe5, 0xcd, 0x99, + 0x2d, 0x3f, 0x84, 0xdc, 0xa6, 0x5d, 0xe0, 0x0f, 0xd6, 0xc1, 0xf3, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x85, 0x8c, 0x11, 0xbb, 0x2b, 0x07, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -529,13 +566,13 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Slashed { + if m.SlashedBtcHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBtcHeight)) i-- - if m.Slashed { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } + dAtA[i] = 0x28 + } + if m.SlashedBabylonHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBabylonHeight)) i-- dAtA[i] = 0x20 } @@ -598,6 +635,16 @@ func (m *BTCValidatorWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.SlashedBtcHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBtcHeight)) + i-- + dAtA[i] = 0x28 + } + if m.SlashedBabylonHeight != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBabylonHeight)) + i-- + dAtA[i] = 0x20 + } if m.VotingPower != 0 { i = encodeVarintBtcstaking(dAtA, i, uint64(m.VotingPower)) i-- @@ -902,8 +949,11 @@ func (m *BTCValidator) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.Slashed { - n += 2 + if m.SlashedBabylonHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.SlashedBabylonHeight)) + } + if m.SlashedBtcHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.SlashedBtcHeight)) } return n } @@ -924,6 +974,12 @@ func (m *BTCValidatorWithMeta) Size() (n int) { if m.VotingPower != 0 { n += 1 + sovBtcstaking(uint64(m.VotingPower)) } + if m.SlashedBabylonHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.SlashedBabylonHeight)) + } + if m.SlashedBtcHeight != 0 { + n += 1 + sovBtcstaking(uint64(m.SlashedBtcHeight)) + } return n } @@ -1170,9 +1226,28 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Slashed", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashedBabylonHeight", wireType) + } + m.SlashedBabylonHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SlashedBabylonHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashedBtcHeight", wireType) } - var v int + m.SlashedBtcHeight = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -1182,12 +1257,11 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + m.SlashedBtcHeight |= uint64(b&0x7F) << shift if b < 0x80 { break } } - m.Slashed = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) @@ -1311,6 +1385,44 @@ func (m *BTCValidatorWithMeta) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashedBabylonHeight", wireType) + } + m.SlashedBabylonHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SlashedBabylonHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashedBtcHeight", wireType) + } + m.SlashedBtcHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SlashedBtcHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index a25cdfe48..8d180d7db 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -44,9 +44,33 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinalitySig) (*types.MsgAddFinalitySigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // ensure the BTC validator exists + btcVal, err := ms.BTCStakingKeeper.GetBTCValidator(ctx, req.ValBtcPk.MustMarshal()) + if err != nil { + return nil, err + } + // ensure the BTC validator is not slashed at this time point + // NOTE: it's possible that the BTC validator equivocates for height h, and the signature is processed at + // height h' > h. In this case: + // - Babylon should reject any new signature from this BTC validator, since it's known to be adversarial + // - Babylon should set its voting power since height h'+1 to be zero, due to the same reason + // - Babylon should NOT set its voting power between [h, h'] to be zero, since + // - Babylon BTC staking ensures safety upon 2f+1 votes, *even if* f of them are adversarial. This is + // because as long as a block gets 2f+1 votes, any other block with 2f+1 votes has a f+1 quorum + // intersection with this block, contradicting to the assumption and leading to the safety proof. + // This ensures slashable safety together with EOTS, thus does not undermine Babylon's security guarantee. + // - Due to this reason, when tallying a block, Babylon finalises this block upon 2f+1 votes. If we + // modify voting power table in the history, some finality decisions might be contradicting to the + // signature set and voting power table. + // - To fix the above issue, Babylon has to allow finalise and unfinalise blocks. However, this means + // Babylon will lose safety under an adaptive adversary corrupting even 1 validator. It can simply + // corrupt a new validator and equivocate a historical block over and over again, making a previous block + // unfinalisable forever + if btcVal.IsSlashed() { + return nil, bstypes.ErrBTCValAlreadySlashed + } + // ensure the BTC validator has voting power at this height - // NOTE: if a BTC validator has signed two different blocks at the same height, - // then it will be slashed and its voting power will be reduced to zero valPK := req.ValBtcPk if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { return nil, types.ErrInvalidFinalitySig.Wrapf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 3b097cee3..b90fc85a2 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -8,6 +8,7 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -112,6 +113,9 @@ func FuzzAddFinalitySig(f *testing.F) { // create and register a random BTC validator btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, btcSK) + require.NoError(t, err) valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) valBTCPKBytes := valBTCPK.MustMarshal() require.NoError(t, err) @@ -134,6 +138,7 @@ func FuzzAddFinalitySig(f *testing.F) { // Case 1: fail if the BTC validator does not have voting power bsKeeper.EXPECT().GetVotingPower(gomock.Any(), gomock.Eq(valBTCPKBytes), gomock.Eq(blockHeight)).Return(uint64(0)).Times(1) + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) _, err = ms.AddFinalitySig(ctx, msg) require.Error(t, err) @@ -143,6 +148,7 @@ func FuzzAddFinalitySig(f *testing.F) { // Case 2: fail if the BTC validator has not committed public randomness at that height blockHeight2 := startHeight + numPubRand + 1 bsKeeper.EXPECT().GetVotingPower(gomock.Any(), gomock.Eq(valBTCPKBytes), gomock.Eq(blockHeight2)).Return(uint64(1)).Times(1) + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) msg.BlockHeight = blockHeight2 _, err = ms.AddFinalitySig(ctx, msg) require.Error(t, err) @@ -153,6 +159,7 @@ func FuzzAddFinalitySig(f *testing.F) { // index this block first ctx = ctx.WithBlockHeader(tmproto.Header{Height: int64(blockHeight), LastCommitHash: blockHash}) fKeeper.IndexBlock(ctx) + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) // add vote and it should work _, err = ms.AddFinalitySig(ctx, msg) require.NoError(t, err) @@ -162,6 +169,7 @@ func FuzzAddFinalitySig(f *testing.F) { require.Equal(t, msg.FinalitySig.MustMarshal(), sig.MustMarshal()) // Case 4: fail if duplicate vote + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) _, err = ms.AddFinalitySig(ctx, msg) require.Error(t, err) @@ -169,6 +177,7 @@ func FuzzAddFinalitySig(f *testing.F) { blockHash2 := datagen.GenRandomByteArray(r, 32) msg2, err := types.NewMsgAddFinalitySig(signer, btcSK, sr, blockHeight, blockHash2) require.NoError(t, err) + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) // mock slashing interface bsKeeper.EXPECT().SlashBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(nil).Times(1) // NOTE: even though this BTC validator is slashed, the msg should be successful @@ -192,5 +201,11 @@ func FuzzAddFinalitySig(f *testing.F) { // not affect verification require.True(t, btcSK.Key.Equals(&btcSK2.Key) || btcSK.Key.Negate().Equals(&btcSK2.Key)) require.Equal(t, btcSK.PubKey().SerializeCompressed()[1:], btcSK2.PubKey().SerializeCompressed()[1:]) + + // Case 6: slashed BTC validator cannot vote + btcVal.SlashedBabylonHeight = blockHeight + bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) + _, err = ms.AddFinalitySig(ctx, msg) + require.Equal(t, bstypes.ErrBTCValAlreadySlashed, err) }) } diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index f9d6ccd9a..3b4ac9869 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -1,11 +1,13 @@ package types import ( + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" ) type BTCStakingKeeper interface { + GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*bstypes.BTCValidator, error) HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index ead17c340..3948629f7 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -7,8 +7,9 @@ package types import ( reflect "reflect" - types "github.com/cosmos/cosmos-sdk/types" - types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + types "github.com/babylonchain/babylon/x/btcstaking/types" + types0 "github.com/cosmos/cosmos-sdk/types" + types1 "github.com/cosmos/cosmos-sdk/x/auth/types" gomock "github.com/golang/mock/gomock" ) @@ -36,7 +37,7 @@ func (m *MockBTCStakingKeeper) EXPECT() *MockBTCStakingKeeperMockRecorder { } // GetBTCStakingActivatedHeight mocks base method. -func (m *MockBTCStakingKeeper) GetBTCStakingActivatedHeight(ctx types.Context) (uint64, error) { +func (m *MockBTCStakingKeeper) GetBTCStakingActivatedHeight(ctx types0.Context) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBTCStakingActivatedHeight", ctx) ret0, _ := ret[0].(uint64) @@ -50,8 +51,23 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetBTCStakingActivatedHeight(ctx int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCStakingActivatedHeight", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCStakingActivatedHeight), ctx) } +// GetBTCValidator mocks base method. +func (m *MockBTCStakingKeeper) GetBTCValidator(ctx types0.Context, valBTCPK []byte) (*types.BTCValidator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBTCValidator", ctx, valBTCPK) + ret0, _ := ret[0].(*types.BTCValidator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBTCValidator indicates an expected call of GetBTCValidator. +func (mr *MockBTCStakingKeeperMockRecorder) GetBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCValidator), ctx, valBTCPK) +} + // GetVotingPower mocks base method. -func (m *MockBTCStakingKeeper) GetVotingPower(ctx types.Context, valBTCPK []byte, height uint64) uint64 { +func (m *MockBTCStakingKeeper) GetVotingPower(ctx types0.Context, valBTCPK []byte, height uint64) uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetVotingPower", ctx, valBTCPK, height) ret0, _ := ret[0].(uint64) @@ -65,7 +81,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, valBTCPK, height } // GetVotingPowerTable mocks base method. -func (m *MockBTCStakingKeeper) GetVotingPowerTable(ctx types.Context, height uint64) map[string]uint64 { +func (m *MockBTCStakingKeeper) GetVotingPowerTable(ctx types0.Context, height uint64) map[string]uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetVotingPowerTable", ctx, height) ret0, _ := ret[0].(map[string]uint64) @@ -79,7 +95,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPowerTable(ctx, height inte } // HasBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) HasBTCValidator(ctx types.Context, valBTCPK []byte) bool { +func (m *MockBTCStakingKeeper) HasBTCValidator(ctx types0.Context, valBTCPK []byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasBTCValidator", ctx, valBTCPK) ret0, _ := ret[0].(bool) @@ -93,7 +109,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interf } // SlashBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx types.Context, valBTCPK []byte) error { +func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx types0.Context, valBTCPK []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashBTCValidator", ctx, valBTCPK) ret0, _ := ret[0].(error) @@ -130,10 +146,10 @@ func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { } // GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) types0.AccountI { +func (m *MockAccountKeeper) GetAccount(ctx types0.Context, addr types0.AccAddress) types1.AccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types0.AccountI) + ret0, _ := ret[0].(types1.AccountI) return ret0 } @@ -167,10 +183,10 @@ func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { } // SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { +func (m *MockBankKeeper) SpendableCoins(ctx types0.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) - ret0, _ := ret[0].(types.Coins) + ret0, _ := ret[0].(types0.Coins) return ret0 } From 3ce700f2a94cc5f1abc6ddd6d13627502a633629 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 4 Aug 2023 12:34:46 +0300 Subject: [PATCH 053/202] nit: Rename BTCDelegations to BTCDelegatorDelegations (#52) --- proto/babylon/btcstaking/v1/btcstaking.proto | 4 +- proto/babylon/btcstaking/v1/query.proto | 2 +- .../configurer/chain/queries_btcstaking.go | 2 +- x/btcstaking/keeper/btc_delegations.go | 8 +- x/btcstaking/keeper/grpc_query.go | 6 +- x/btcstaking/keeper/voting_power_table.go | 2 +- x/btcstaking/types/btcstaking.go | 14 +- x/btcstaking/types/btcstaking.pb.go | 130 +++++++++--------- x/btcstaking/types/query.pb.go | 118 ++++++++-------- 9 files changed, 143 insertions(+), 143 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 5542c03df..259d43bc0 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -82,8 +82,8 @@ message BTCDelegation { bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// BTCDelegations is a collection of BTC delegations, typically from the same delegator. -message BTCDelegations { +// BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. +message BTCDelegatorDelegations { repeated BTCDelegation dels = 1; } diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index fbefb23b6..e8be1055a 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -149,7 +149,7 @@ message QueryBTCValidatorDelegationsRequest { // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsResponse { // btc_delegations contains all the queried BTC delegations. - repeated BTCDelegations btc_delegations = 1; + repeated BTCDelegatorDelegations btc_delegations = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 7b846f343..462a76b97 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -46,7 +46,7 @@ func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes. return resp.BtcValidators } -func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegations { +func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegatorDelegations { path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index d1fe738fb..f05225a23 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -11,7 +11,7 @@ import ( func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { var ( - btcDels = types.NewBTCDelegations() + btcDels = types.NewBTCDelegatorDelegations() err error ) if k.hasBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) { @@ -44,7 +44,7 @@ func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340P } // setBTCDelegations sets the given BTC delegation to KVStore -func (k Keeper) setBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, btcDels *types.BTCDelegations) { +func (k Keeper) setBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, btcDels *types.BTCDelegatorDelegations) { delBTCPKBytes := delBTCPK.MustMarshal() store := k.btcDelegationStore(ctx, valBTCPK) @@ -74,7 +74,7 @@ func (k Keeper) HasBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, de } // getBTCDelegations gets the BTC delegations with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegations, error) { +func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { valBTCPKBytes := valBTCPK.MustMarshal() delBTCPKBytes := delBTCPK.MustMarshal() @@ -89,7 +89,7 @@ func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, d return nil, types.ErrBTCDelNotFound } // get and unmarshal - var btcDels types.BTCDelegations + var btcDels types.BTCDelegatorDelegations btcDelsBytes := store.Get(delBTCPKBytes) k.cdc.MustUnmarshal(btcDelsBytes, &btcDels) return &btcDels, nil diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 797a2cf24..f63309643 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -73,7 +73,7 @@ func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendi // iterate over each BTC delegation under this BTC validator for ; delIter.Valid(); delIter.Next() { - var curBTCDels types.BTCDelegations + var curBTCDels types.BTCDelegatorDelegations btcDelsBytes := delIter.Value() k.cdc.MustUnmarshal(btcDelsBytes, &curBTCDels) for i, btcDel := range curBTCDels.Dels { @@ -179,9 +179,9 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC sdkCtx := sdk.UnwrapSDKContext(ctx) btcDelStore := k.btcDelegationStore(sdkCtx, valPK) - btcDels := []*types.BTCDelegations{} + btcDels := []*types.BTCDelegatorDelegations{} pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { - var curBTCDels types.BTCDelegations + var curBTCDels types.BTCDelegatorDelegations k.cdc.MustUnmarshal(value, &curBTCDels) btcDels = append(btcDels, &curBTCDels) return nil diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 5a90b0539..1544513d6 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -48,7 +48,7 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // to calculate this validator's total voting power btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { - var btcDels types.BTCDelegations + var btcDels types.BTCDelegatorDelegations k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) valPower += btcDels.VotingPower(btcTipHeight, wValue) } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index e5e5def96..c10487b50 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -125,8 +125,8 @@ func (d *BTCDelegation) MustGetStakingTxHash() string { return d.StakingTx.MustGetTxHash() } -func NewBTCDelegations() *BTCDelegations { - return &BTCDelegations{ +func NewBTCDelegatorDelegations() *BTCDelegatorDelegations { + return &BTCDelegatorDelegations{ Dels: []*BTCDelegation{}, } } @@ -134,7 +134,7 @@ func NewBTCDelegations() *BTCDelegations { // Add appends a given BTC delegation to the BTC delegations // It requires the given BTC delegation is not in the list yet // TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegations) Add(del *BTCDelegation) error { +func (dels *BTCDelegatorDelegations) Add(del *BTCDelegation) error { stakingTxHash, err := del.GetStakingTxHash() if err != nil { return fmt.Errorf("failed to add BTC delegation to BTC delegations: %w", err) @@ -150,7 +150,7 @@ func (dels *BTCDelegations) Add(del *BTCDelegation) error { // AddJurySig adds a jury signature to an existing BTC delegation in the BTC delegations // TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { +func (dels *BTCDelegatorDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { del, err := dels.Get(stakingTxHash) if err != nil { return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) @@ -163,7 +163,7 @@ func (dels *BTCDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Sign } // TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegations) Has(stakingTxHash string) bool { +func (dels *BTCDelegatorDelegations) Has(stakingTxHash string) bool { for _, d := range dels.Dels { dStakingTxHash := d.MustGetStakingTxHash() if dStakingTxHash == stakingTxHash { @@ -174,7 +174,7 @@ func (dels *BTCDelegations) Has(stakingTxHash string) bool { } // TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegations) Get(stakingTxHash string) (*BTCDelegation, error) { +func (dels *BTCDelegatorDelegations) Get(stakingTxHash string) (*BTCDelegation, error) { for _, d := range dels.Dels { dStakingTxHash := d.MustGetStakingTxHash() if dStakingTxHash == stakingTxHash { @@ -185,7 +185,7 @@ func (dels *BTCDelegations) Get(stakingTxHash string) (*BTCDelegation, error) { } // VotingPower calculates the total voting power of all BTC delegations -func (dels *BTCDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { +func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { power := uint64(0) for _, del := range dels.Dels { power += del.VotingPower(btcHeight, w) diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index c0c1908c7..a24cc25b8 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -331,22 +331,22 @@ func (m *BTCDelegation) GetStakingTx() *StakingTx { } // BTCDelegations is a collection of BTC delegations, typically from the same delegator. -type BTCDelegations struct { +type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` } -func (m *BTCDelegations) Reset() { *m = BTCDelegations{} } -func (m *BTCDelegations) String() string { return proto.CompactTextString(m) } -func (*BTCDelegations) ProtoMessage() {} -func (*BTCDelegations) Descriptor() ([]byte, []int) { +func (m *BTCDelegatorDelegations) Reset() { *m = BTCDelegatorDelegations{} } +func (m *BTCDelegatorDelegations) String() string { return proto.CompactTextString(m) } +func (*BTCDelegatorDelegations) ProtoMessage() {} +func (*BTCDelegatorDelegations) Descriptor() ([]byte, []int) { return fileDescriptor_3851ae95ccfaf7db, []int{3} } -func (m *BTCDelegations) XXX_Unmarshal(b []byte) error { +func (m *BTCDelegatorDelegations) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BTCDelegations) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *BTCDelegatorDelegations) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BTCDelegations.Marshal(b, m, deterministic) + return xxx_messageInfo_BTCDelegatorDelegations.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -356,19 +356,19 @@ func (m *BTCDelegations) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro return b[:n], nil } } -func (m *BTCDelegations) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCDelegations.Merge(m, src) +func (m *BTCDelegatorDelegations) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegatorDelegations.Merge(m, src) } -func (m *BTCDelegations) XXX_Size() int { +func (m *BTCDelegatorDelegations) XXX_Size() int { return m.Size() } -func (m *BTCDelegations) XXX_DiscardUnknown() { - xxx_messageInfo_BTCDelegations.DiscardUnknown(m) +func (m *BTCDelegatorDelegations) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegatorDelegations.DiscardUnknown(m) } -var xxx_messageInfo_BTCDelegations proto.InternalMessageInfo +var xxx_messageInfo_BTCDelegatorDelegations proto.InternalMessageInfo -func (m *BTCDelegations) GetDels() []*BTCDelegation { +func (m *BTCDelegatorDelegations) GetDels() []*BTCDelegation { if m != nil { return m.Dels } @@ -486,7 +486,7 @@ func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") - proto.RegisterType((*BTCDelegations)(nil), "babylon.btcstaking.v1.BTCDelegations") + proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") } @@ -496,54 +496,54 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 744 bytes of a gzipped FileDescriptorProto + // 748 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0x8e, 0xdd, 0x34, 0x3f, 0x9e, 0xd3, 0x2a, 0x1d, 0x4a, 0x15, 0x15, 0x91, 0xa4, 0x11, 0xa0, - 0x08, 0x21, 0x9b, 0xa6, 0x3f, 0xc4, 0x0f, 0x09, 0x84, 0xdb, 0x08, 0x02, 0xb4, 0x58, 0x76, 0x54, + 0x14, 0x8e, 0xdd, 0x34, 0x3f, 0x9e, 0xd3, 0x2a, 0x1d, 0x4a, 0x89, 0x8a, 0x48, 0xd2, 0x08, 0x50, + 0x84, 0x90, 0x4d, 0xd3, 0x1f, 0xe2, 0x87, 0x04, 0xc2, 0x6d, 0x04, 0x11, 0xb4, 0x58, 0x76, 0x54, 0x10, 0x07, 0xa2, 0xb1, 0xe3, 0x3a, 0x26, 0xae, 0xc7, 0xf2, 0x4c, 0x42, 0x72, 0xe7, 0xc0, 0x91, 0xbf, 0x07, 0x89, 0xfb, 0x1e, 0x7b, 0x5c, 0xf5, 0x50, 0xad, 0xda, 0x7f, 0x64, 0xe5, 0xf1, 0x38, - 0x4a, 0xb4, 0x5b, 0xad, 0x56, 0x5d, 0xed, 0x61, 0x6f, 0x33, 0xef, 0x7b, 0xdf, 0xe7, 0xf7, 0xbd, - 0x79, 0xe3, 0x81, 0x4f, 0x6c, 0x6c, 0xcf, 0x03, 0x12, 0x6a, 0x36, 0x73, 0x28, 0xc3, 0x63, 0x3f, - 0xf4, 0xb4, 0xe9, 0xfe, 0xd2, 0x4e, 0x8d, 0x62, 0xc2, 0x08, 0x7a, 0x5f, 0xe4, 0xa9, 0x4b, 0xc8, - 0x74, 0x7f, 0x77, 0xdb, 0x23, 0x1e, 0xe1, 0x19, 0x5a, 0xb2, 0x4a, 0x93, 0x77, 0x5b, 0x0e, 0xa1, - 0x57, 0x84, 0x6a, 0x4e, 0x3c, 0x8f, 0x18, 0xd1, 0xa8, 0xeb, 0x44, 0x9d, 0xa3, 0xe3, 0xf1, 0xbe, - 0x36, 0x76, 0xe7, 0x34, 0xcd, 0x69, 0xfd, 0x2f, 0x43, 0x45, 0xef, 0x9f, 0x5c, 0xe0, 0xc0, 0x1f, - 0x62, 0x46, 0x62, 0xf4, 0x0d, 0x80, 0xf8, 0xc6, 0x20, 0x1a, 0xd7, 0xa4, 0xa6, 0xd4, 0x56, 0x3a, + 0x4d, 0xb5, 0x5b, 0xad, 0x56, 0x5d, 0xed, 0x61, 0x6f, 0x33, 0xef, 0x7b, 0xdf, 0xe7, 0xf7, 0xbd, + 0x79, 0xe3, 0x81, 0x4f, 0x6d, 0x6c, 0xcf, 0x03, 0x12, 0x6a, 0x36, 0x73, 0x28, 0xc3, 0x63, 0x3f, + 0xf4, 0xb4, 0xe9, 0xee, 0xd2, 0x4e, 0x8d, 0x62, 0xc2, 0x08, 0x7a, 0x5f, 0xe4, 0xa9, 0x4b, 0xc8, + 0x74, 0x77, 0x7b, 0xd3, 0x23, 0x1e, 0xe1, 0x19, 0x5a, 0xb2, 0x4a, 0x93, 0xb7, 0x5b, 0x0e, 0xa1, + 0x17, 0x84, 0x6a, 0x4e, 0x3c, 0x8f, 0x18, 0xd1, 0xa8, 0xeb, 0x44, 0x9d, 0x83, 0xc3, 0xf1, 0xae, + 0x36, 0x76, 0xe7, 0x34, 0xcd, 0x69, 0xfd, 0x2f, 0x43, 0x45, 0xef, 0x1f, 0x9d, 0xe1, 0xc0, 0x1f, + 0x62, 0x46, 0x62, 0xf4, 0x2d, 0x80, 0xf8, 0xc6, 0x20, 0x1a, 0xd7, 0xa4, 0xa6, 0xd4, 0x56, 0x3a, 0x0d, 0x35, 0x55, 0x52, 0x53, 0x25, 0x75, 0xa1, 0xa4, 0x1a, 0x13, 0xfb, 0x27, 0x77, 0x6e, 0x96, - 0x05, 0xc5, 0x18, 0xa3, 0x33, 0x28, 0xd8, 0xcc, 0x49, 0xb8, 0x72, 0x53, 0x6a, 0x57, 0xf4, 0xe3, - 0x9b, 0xdb, 0x46, 0xc7, 0xf3, 0xd9, 0x68, 0x62, 0xab, 0x0e, 0xb9, 0xd2, 0x44, 0xa6, 0x33, 0xc2, - 0x7e, 0x98, 0x6d, 0x34, 0x36, 0x8f, 0x5c, 0xaa, 0xea, 0x3d, 0xe3, 0xe0, 0xf0, 0x73, 0x21, 0xb9, - 0x6e, 0x33, 0xc7, 0x18, 0xa3, 0xaf, 0x60, 0x2d, 0x22, 0x51, 0x6d, 0x8d, 0xd7, 0xd1, 0x56, 0x5f, - 0x6a, 0x5f, 0x35, 0x62, 0x42, 0x2e, 0x7f, 0xb9, 0x34, 0x08, 0xa5, 0x2e, 0xa5, 0x3e, 0x09, 0xcd, - 0x84, 0x84, 0x0e, 0x61, 0x87, 0x06, 0x98, 0x8e, 0xdc, 0xe1, 0x20, 0xb3, 0x34, 0x72, 0x7d, 0x6f, - 0xc4, 0x6a, 0xf9, 0xa6, 0xd4, 0xce, 0x9b, 0xdb, 0x02, 0xd5, 0x53, 0xf0, 0x07, 0x8e, 0xa1, 0xcf, - 0x00, 0x2d, 0x58, 0xcc, 0xc9, 0x18, 0xeb, 0x9c, 0x51, 0xcd, 0x18, 0xcc, 0x49, 0xb3, 0x5b, 0x7f, - 0xcb, 0xb0, 0xbd, 0xdc, 0xbf, 0x5f, 0x7d, 0x36, 0x3a, 0x73, 0x19, 0x5e, 0xea, 0x83, 0xf4, 0x26, - 0xfa, 0xb0, 0x03, 0x05, 0x51, 0x89, 0xcc, 0x2b, 0x11, 0x3b, 0xb4, 0x07, 0x95, 0x29, 0x61, 0x7e, + 0x05, 0xc5, 0x18, 0xa3, 0x13, 0x28, 0xd8, 0xcc, 0x49, 0xb8, 0x72, 0x53, 0x6a, 0x57, 0xf4, 0xc3, + 0xab, 0xeb, 0x46, 0xc7, 0xf3, 0xd9, 0x68, 0x62, 0xab, 0x0e, 0xb9, 0xd0, 0x44, 0xa6, 0x33, 0xc2, + 0x7e, 0x98, 0x6d, 0x34, 0x36, 0x8f, 0x5c, 0xaa, 0xea, 0x3d, 0x63, 0x6f, 0xff, 0x0b, 0x21, 0xb9, + 0x6a, 0x33, 0xc7, 0x18, 0xa3, 0xaf, 0x61, 0x25, 0x22, 0x51, 0x6d, 0x85, 0xd7, 0xd1, 0x56, 0x5f, + 0x6a, 0x5f, 0x35, 0x62, 0x42, 0xce, 0x7f, 0x39, 0x37, 0x08, 0xa5, 0x2e, 0xa5, 0x3e, 0x09, 0xcd, + 0x84, 0x84, 0xf6, 0x61, 0x8b, 0x06, 0x98, 0x8e, 0xdc, 0xe1, 0x20, 0xb3, 0x34, 0x72, 0x7d, 0x6f, + 0xc4, 0x6a, 0xf9, 0xa6, 0xd4, 0xce, 0x9b, 0x9b, 0x02, 0xd5, 0x53, 0xf0, 0x47, 0x8e, 0xa1, 0xcf, + 0x01, 0x2d, 0x58, 0xcc, 0xc9, 0x18, 0xab, 0x9c, 0x51, 0xcd, 0x18, 0xcc, 0x49, 0xb3, 0x5b, 0x7f, + 0xcb, 0xb0, 0xb9, 0xdc, 0xbf, 0x5f, 0x7d, 0x36, 0x3a, 0x71, 0x19, 0x5e, 0xea, 0x83, 0xf4, 0x26, + 0xfa, 0xb0, 0x05, 0x05, 0x51, 0x89, 0xcc, 0x2b, 0x11, 0x3b, 0xb4, 0x03, 0x95, 0x29, 0x61, 0x7e, 0xe8, 0x0d, 0x22, 0xf2, 0x97, 0x1b, 0xf3, 0x46, 0xe5, 0x4d, 0x25, 0x8d, 0x19, 0x49, 0xe8, 0xad, - 0xb4, 0xe1, 0xbf, 0x75, 0xd8, 0xd0, 0xfb, 0x27, 0xa7, 0x6e, 0xe0, 0x7a, 0x98, 0xf9, 0x24, 0x7c, + 0xb4, 0xe1, 0xbf, 0x55, 0x58, 0xd3, 0xfb, 0x47, 0xc7, 0x6e, 0xe0, 0x7a, 0x98, 0xf9, 0x24, 0x7c, 0x97, 0xe6, 0xa8, 0x0f, 0x30, 0xc5, 0xc1, 0x40, 0x94, 0x93, 0x7f, 0x54, 0x39, 0xa5, 0x29, 0x0e, - 0x74, 0x5e, 0xd1, 0x1e, 0x54, 0x28, 0xc3, 0x31, 0x5b, 0x6d, 0xad, 0xc2, 0x63, 0xe2, 0x0c, 0x3e, - 0x04, 0x70, 0xc3, 0x61, 0x96, 0x50, 0xe0, 0x09, 0x65, 0x37, 0x1c, 0x0a, 0xf8, 0x03, 0x28, 0x33, - 0xc2, 0x70, 0x30, 0xa0, 0x98, 0xd5, 0x8a, 0x1c, 0x2d, 0xf1, 0x80, 0x85, 0x19, 0xfa, 0x16, 0x40, - 0x38, 0x1b, 0xb0, 0x59, 0xad, 0xc4, 0x7d, 0x37, 0x1f, 0xf0, 0x6d, 0xa5, 0xcb, 0xfe, 0xcc, 0x2c, - 0xd3, 0x6c, 0x89, 0x3a, 0xa0, 0xf0, 0x63, 0x16, 0x0a, 0x65, 0x6e, 0x7b, 0xeb, 0xe6, 0xb6, 0x91, - 0x1c, 0xb4, 0x25, 0x90, 0xfe, 0xcc, 0x04, 0xba, 0x58, 0xa3, 0x3f, 0x60, 0x63, 0x98, 0x8e, 0x00, - 0x89, 0x07, 0xd4, 0xf7, 0x6a, 0xc0, 0x59, 0x5f, 0xde, 0xdc, 0x36, 0x8e, 0x5e, 0xa7, 0x59, 0x96, - 0xef, 0x85, 0x98, 0x4d, 0x62, 0xd7, 0xac, 0x2c, 0xf4, 0x2c, 0xdf, 0x43, 0x7d, 0x28, 0xfd, 0x39, - 0x89, 0xe7, 0x5c, 0x5a, 0x79, 0xac, 0x74, 0x31, 0x91, 0xb2, 0x7c, 0xaf, 0xf5, 0x23, 0x6c, 0xae, - 0xcc, 0x2e, 0x45, 0x5f, 0x40, 0x7e, 0xe8, 0x06, 0xb4, 0x26, 0x35, 0xd7, 0xda, 0x4a, 0xe7, 0xa3, - 0x07, 0xda, 0xb6, 0x42, 0x32, 0x39, 0xa3, 0xf5, 0x8f, 0x04, 0x5b, 0x2f, 0x8c, 0x11, 0x6a, 0x80, - 0x92, 0x5d, 0x86, 0xa4, 0x74, 0xfe, 0x47, 0x30, 0xb3, 0xfb, 0x91, 0x18, 0x33, 0xa1, 0x98, 0x8c, - 0x57, 0x02, 0xca, 0x8f, 0xf5, 0x95, 0xdc, 0x9b, 0xc4, 0x96, 0x0e, 0xe5, 0xc5, 0xc1, 0xa2, 0x4d, - 0x90, 0xd9, 0x4c, 0x7c, 0x58, 0x66, 0x33, 0xf4, 0x31, 0x6c, 0x66, 0xe3, 0x41, 0x9d, 0xd8, 0x8f, - 0xd2, 0xff, 0x4a, 0xc5, 0xdc, 0x10, 0x51, 0x8b, 0x07, 0x3f, 0xfd, 0x1a, 0xde, 0x5b, 0x71, 0x69, - 0x31, 0xcc, 0x26, 0x14, 0x29, 0x50, 0x34, 0xba, 0xe7, 0xa7, 0xbd, 0xf3, 0xef, 0xab, 0x39, 0x04, - 0x50, 0xf8, 0xee, 0xa4, 0xdf, 0xbb, 0xe8, 0x56, 0xa5, 0x04, 0xe8, 0xfe, 0x66, 0xf4, 0xcc, 0xee, - 0x69, 0x55, 0xd6, 0x7f, 0x7e, 0x72, 0x57, 0x97, 0xae, 0xef, 0xea, 0xd2, 0xb3, 0xbb, 0xba, 0xf4, - 0xef, 0x7d, 0x3d, 0x77, 0x7d, 0x5f, 0xcf, 0x3d, 0xbd, 0xaf, 0xe7, 0x7e, 0x7f, 0xe5, 0xcd, 0x99, - 0x2d, 0x3f, 0x84, 0xdc, 0xa6, 0x5d, 0xe0, 0x0f, 0xd6, 0xc1, 0xf3, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x85, 0x8c, 0x11, 0xbb, 0x2b, 0x07, 0x00, 0x00, + 0x74, 0x5e, 0xd1, 0x0e, 0x54, 0x28, 0xc3, 0x31, 0xbb, 0xdf, 0x5a, 0x85, 0xc7, 0xc4, 0x19, 0x7c, + 0x04, 0xe0, 0x86, 0xc3, 0x2c, 0xa1, 0xc0, 0x13, 0xca, 0x6e, 0x38, 0x14, 0xf0, 0x87, 0x50, 0x66, + 0x84, 0xe1, 0x60, 0x40, 0x31, 0xab, 0x15, 0x39, 0x5a, 0xe2, 0x01, 0x0b, 0x33, 0xf4, 0x1d, 0x80, + 0x70, 0x36, 0x60, 0xb3, 0x5a, 0x89, 0xfb, 0x6e, 0x3e, 0xe0, 0xdb, 0x4a, 0x97, 0xfd, 0x99, 0x59, + 0xa6, 0xd9, 0x12, 0x75, 0x40, 0xe1, 0xc7, 0x2c, 0x14, 0xca, 0xdc, 0xf6, 0xc6, 0xd5, 0x75, 0x23, + 0x39, 0x68, 0x4b, 0x20, 0xfd, 0x99, 0x09, 0x74, 0xb1, 0x46, 0x7f, 0xc0, 0xda, 0x30, 0x1d, 0x01, + 0x12, 0x0f, 0xa8, 0xef, 0xd5, 0x80, 0xb3, 0xbe, 0xba, 0xba, 0x6e, 0x1c, 0xbc, 0x4e, 0xb3, 0x2c, + 0xdf, 0x0b, 0x31, 0x9b, 0xc4, 0xae, 0x59, 0x59, 0xe8, 0x59, 0xbe, 0x87, 0xfa, 0x50, 0xfa, 0x73, + 0x12, 0xcf, 0xb9, 0xb4, 0xf2, 0x58, 0xe9, 0x62, 0x22, 0x65, 0xf9, 0x5e, 0xcb, 0x82, 0x0f, 0xee, + 0x66, 0x97, 0xc4, 0x77, 0x43, 0x4c, 0xd1, 0x97, 0x90, 0x1f, 0xba, 0x01, 0xad, 0x49, 0xcd, 0x95, + 0xb6, 0xd2, 0xf9, 0xf8, 0x81, 0xfe, 0xdd, 0x9b, 0x7c, 0x93, 0x33, 0x5a, 0xff, 0x48, 0xb0, 0xf1, + 0xc2, 0x3c, 0xa1, 0x06, 0x28, 0xd9, 0xad, 0x48, 0x3c, 0xf0, 0x5f, 0x83, 0x99, 0x5d, 0x94, 0xc4, + 0xa1, 0x09, 0xc5, 0x64, 0xce, 0x12, 0x50, 0x7e, 0xac, 0xc1, 0xe4, 0x02, 0x25, 0xfe, 0x74, 0x28, + 0x2f, 0x4e, 0x18, 0xad, 0x83, 0xcc, 0x66, 0xe2, 0xc3, 0x32, 0x9b, 0xa1, 0x4f, 0x60, 0x3d, 0x9b, + 0x13, 0xea, 0xc4, 0x7e, 0x94, 0xfe, 0x60, 0x2a, 0xe6, 0x9a, 0x88, 0x5a, 0x3c, 0xf8, 0xd9, 0x37, + 0xf0, 0xde, 0x3d, 0x97, 0x16, 0xc3, 0x6c, 0x42, 0x91, 0x02, 0x45, 0xa3, 0x7b, 0x7a, 0xdc, 0x3b, + 0xfd, 0xa1, 0x9a, 0x43, 0x00, 0x85, 0xef, 0x8f, 0xfa, 0xbd, 0xb3, 0x6e, 0x55, 0x4a, 0x80, 0xee, + 0x6f, 0x46, 0xcf, 0xec, 0x1e, 0x57, 0x65, 0xfd, 0xe7, 0x27, 0x37, 0x75, 0xe9, 0xf2, 0xa6, 0x2e, + 0x3d, 0xbb, 0xa9, 0x4b, 0xff, 0xde, 0xd6, 0x73, 0x97, 0xb7, 0xf5, 0xdc, 0xd3, 0xdb, 0x7a, 0xee, + 0xf7, 0x57, 0x5e, 0xa1, 0xd9, 0xf2, 0x8b, 0xc8, 0x6d, 0xda, 0x05, 0xfe, 0x72, 0xed, 0x3d, 0x0f, + 0x00, 0x00, 0xff, 0xff, 0xdd, 0x6a, 0x0d, 0x4f, 0x34, 0x07, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -804,7 +804,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *BTCDelegations) Marshal() (dAtA []byte, err error) { +func (m *BTCDelegatorDelegations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -814,12 +814,12 @@ func (m *BTCDelegations) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BTCDelegations) MarshalTo(dAtA []byte) (int, error) { +func (m *BTCDelegatorDelegations) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BTCDelegations) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *BTCDelegatorDelegations) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1033,7 +1033,7 @@ func (m *BTCDelegation) Size() (n int) { return n } -func (m *BTCDelegations) Size() (n int) { +func (m *BTCDelegatorDelegations) Size() (n int) { if m == nil { return 0 } @@ -1834,7 +1834,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } return nil } -func (m *BTCDelegations) Unmarshal(dAtA []byte) error { +func (m *BTCDelegatorDelegations) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1857,10 +1857,10 @@ func (m *BTCDelegations) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BTCDelegations: wiretype end group for non-group") + return fmt.Errorf("proto: BTCDelegatorDelegations: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BTCDelegations: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: BTCDelegatorDelegations: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 9edf8b292..6c5c2973f 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -668,7 +668,7 @@ func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsResponse struct { // btc_delegations contains all the queried BTC delegations. - BtcDelegations []*BTCDelegations `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + BtcDelegations []*BTCDelegatorDelegations `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -706,7 +706,7 @@ func (m *QueryBTCValidatorDelegationsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryBTCValidatorDelegationsResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegations { +func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegatorDelegations { if m != nil { return m.BtcDelegations } @@ -740,62 +740,62 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 876 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdf, 0x6f, 0xdb, 0x54, - 0x14, 0xce, 0x2d, 0x25, 0x12, 0x27, 0x6c, 0x93, 0x2e, 0x1b, 0x6c, 0xde, 0x9a, 0xad, 0xce, 0xd2, - 0x74, 0x43, 0xd8, 0x4b, 0x26, 0x4d, 0xc0, 0xf8, 0xa1, 0x65, 0x83, 0x55, 0x85, 0xa2, 0x60, 0x55, - 0x20, 0xf1, 0x12, 0x5d, 0x3b, 0x57, 0x8e, 0xd5, 0xd4, 0xd7, 0x8d, 0x6f, 0x4d, 0x2b, 0xd4, 0x17, - 0x1e, 0x90, 0x78, 0x43, 0xf0, 0x27, 0xf0, 0xc8, 0x13, 0x7f, 0x01, 0x2f, 0x48, 0xf4, 0x8d, 0x4a, - 0x48, 0x88, 0xa7, 0x0a, 0xb5, 0x48, 0xfc, 0x1b, 0x28, 0xd7, 0x37, 0xb5, 0x9d, 0xd8, 0x8e, 0x1b, - 0x95, 0xb7, 0xd6, 0xf7, 0x7c, 0xe7, 0x7c, 0xdf, 0x77, 0xce, 0x3d, 0x37, 0xb0, 0x6c, 0x12, 0x73, - 0x7f, 0xc0, 0x5c, 0xdd, 0xe4, 0x96, 0xcf, 0xc9, 0x96, 0xe3, 0xda, 0x7a, 0xd0, 0xd4, 0x77, 0x76, - 0xe9, 0x70, 0x5f, 0xf3, 0x86, 0x8c, 0x33, 0x7c, 0x4d, 0x86, 0x68, 0x51, 0x88, 0x16, 0x34, 0x95, - 0xab, 0x36, 0xb3, 0x99, 0x88, 0xd0, 0x47, 0x7f, 0x85, 0xc1, 0xca, 0x2d, 0x9b, 0x31, 0x7b, 0x40, - 0x75, 0xe2, 0x39, 0x3a, 0x71, 0x5d, 0xc6, 0x09, 0x77, 0x98, 0xeb, 0xcb, 0xd3, 0xfb, 0x16, 0xf3, - 0xb7, 0x99, 0xaf, 0x9b, 0xc4, 0xa7, 0x61, 0x0d, 0x3d, 0x68, 0x9a, 0x94, 0x93, 0xa6, 0xee, 0x11, - 0xdb, 0x71, 0x45, 0xb0, 0x8c, 0x55, 0xd3, 0x99, 0x79, 0x64, 0x48, 0xb6, 0xc7, 0xf9, 0x56, 0xd2, - 0x63, 0x62, 0x44, 0x45, 0x9c, 0x7a, 0x15, 0xf0, 0xa7, 0xa3, 0x6a, 0x1d, 0x01, 0x36, 0xe8, 0xce, - 0x2e, 0xf5, 0xb9, 0x6a, 0xc0, 0x2b, 0x89, 0xaf, 0xbe, 0xc7, 0x5c, 0x9f, 0xe2, 0xc7, 0x50, 0x0e, - 0x8b, 0x5c, 0x47, 0x77, 0xd0, 0x6a, 0xa5, 0xb5, 0xa4, 0xa5, 0x1a, 0xa0, 0x85, 0xb0, 0xf6, 0xe2, - 0xe1, 0xf1, 0xed, 0x92, 0x21, 0x21, 0xaa, 0x05, 0x37, 0x44, 0xce, 0xf6, 0xe6, 0xd3, 0xcf, 0xc8, - 0xc0, 0xe9, 0x11, 0xce, 0x86, 0xe3, 0x82, 0xf8, 0x43, 0x80, 0x48, 0xa6, 0xcc, 0xbe, 0xa2, 0x85, - 0x9e, 0x68, 0x23, 0x4f, 0xb4, 0xd0, 0x77, 0xe9, 0x89, 0xd6, 0x21, 0x36, 0x95, 0x58, 0x23, 0x86, - 0x54, 0x7f, 0x46, 0xa0, 0xa4, 0x55, 0x91, 0x02, 0xd6, 0xe1, 0xb2, 0xc9, 0xad, 0x6e, 0x70, 0x76, - 0x72, 0x1d, 0xdd, 0x79, 0x61, 0xb5, 0xd2, 0xaa, 0x65, 0x08, 0x89, 0x67, 0x31, 0x2e, 0x99, 0xdc, - 0x8a, 0x72, 0xe2, 0xe7, 0x09, 0xca, 0x0b, 0x82, 0x72, 0x63, 0x26, 0xe5, 0x90, 0x48, 0x82, 0x73, - 0x0d, 0x96, 0x43, 0xb3, 0xa9, 0xdb, 0x73, 0x5c, 0xbb, 0xbd, 0xf9, 0xf4, 0x19, 0x1d, 0x50, 0x3b, - 0x1c, 0x8f, 0x71, 0x47, 0x7c, 0x50, 0xf3, 0x82, 0xa4, 0xbe, 0x0d, 0xb8, 0x32, 0xd2, 0xd7, 0x8b, - 0x8e, 0xa4, 0xc0, 0xbb, 0xd9, 0x02, 0xa3, 0x3c, 0xc6, 0xc8, 0x9c, 0x58, 0x5a, 0xb5, 0x07, 0xf5, - 0x29, 0x33, 0x3b, 0xec, 0x4b, 0x3a, 0x7c, 0xc2, 0xd7, 0xa8, 0x63, 0xf7, 0xf9, 0xb8, 0x7d, 0x35, - 0xb8, 0x1c, 0x90, 0x41, 0x77, 0x54, 0xdb, 0xdb, 0xea, 0xf6, 0xe9, 0x9e, 0x68, 0xe1, 0x4b, 0x46, - 0x25, 0x20, 0x83, 0x36, 0xb7, 0x3a, 0x5b, 0x6b, 0x74, 0x0f, 0xbf, 0x0a, 0xe5, 0xbe, 0x40, 0x09, - 0xb3, 0x16, 0x0d, 0xf9, 0x9f, 0xfa, 0x11, 0xac, 0xcc, 0xaa, 0x22, 0xe5, 0x2d, 0xc3, 0xcb, 0x01, - 0xe3, 0x8e, 0x6b, 0x77, 0xbd, 0xd1, 0xb9, 0x28, 0xb2, 0x68, 0x54, 0xc2, 0x6f, 0x02, 0xa2, 0x7e, - 0x8b, 0xa0, 0x21, 0xb2, 0x3d, 0xb1, 0xb8, 0x13, 0xd0, 0xc4, 0x18, 0x4c, 0xb2, 0x8e, 0x08, 0xa1, - 0x38, 0xa1, 0x89, 0x61, 0x5c, 0x98, 0x7b, 0x18, 0x7f, 0x43, 0xb0, 0x3a, 0x9b, 0x8b, 0xd4, 0x66, - 0x64, 0x8c, 0xe6, 0xeb, 0x05, 0x46, 0xf3, 0x73, 0x87, 0xf7, 0x37, 0x28, 0x27, 0xff, 0xdb, 0x88, - 0x2e, 0xc1, 0xcd, 0x48, 0x08, 0xe1, 0xb4, 0x97, 0x30, 0x52, 0x7d, 0x04, 0xb7, 0xd2, 0x8f, 0xa5, - 0xb6, 0x0c, 0xa3, 0xd5, 0xef, 0x11, 0xd4, 0xa6, 0x5a, 0x3f, 0x3d, 0xfc, 0xc5, 0xc6, 0xeb, 0xa2, - 0xba, 0xf6, 0x0b, 0x82, 0xbb, 0xf9, 0xa4, 0xa4, 0xaa, 0x4f, 0xb2, 0x2e, 0x5b, 0xbd, 0xc8, 0x65, - 0xf3, 0x27, 0x6f, 0xdb, 0x85, 0x75, 0xab, 0xf5, 0x3b, 0xc0, 0x8b, 0x42, 0x01, 0xfe, 0x06, 0x41, - 0x39, 0x5c, 0xc6, 0xf8, 0x5e, 0x06, 0xa9, 0xe9, 0xed, 0xaf, 0xdc, 0x2f, 0x12, 0x1a, 0xd6, 0x55, - 0xeb, 0x5f, 0xff, 0xf1, 0xcf, 0x0f, 0x0b, 0xb7, 0xf1, 0x92, 0x9e, 0xf7, 0x28, 0xe1, 0x1f, 0x11, - 0x5c, 0x4a, 0xcc, 0x3f, 0x7e, 0x90, 0x57, 0x24, 0xed, 0x8d, 0x50, 0x9a, 0xe7, 0x40, 0x48, 0x76, - 0x6f, 0x08, 0x76, 0x0d, 0x5c, 0xd7, 0x33, 0x9f, 0xc3, 0xd8, 0x8d, 0xc3, 0xbf, 0x22, 0xb8, 0x96, - 0xba, 0x60, 0xf1, 0x9b, 0xb9, 0x96, 0xe4, 0x2c, 0x6e, 0xe5, 0xad, 0x39, 0x90, 0x92, 0xfd, 0x23, - 0xc1, 0xfe, 0x01, 0xd6, 0xb2, 0xbc, 0x0d, 0xd1, 0xdd, 0x89, 0x29, 0xc4, 0x7f, 0x22, 0xb8, 0x99, - 0xb3, 0x72, 0xf0, 0x7b, 0x79, 0x94, 0x66, 0xef, 0x4d, 0xe5, 0xfd, 0xb9, 0xf1, 0x05, 0x85, 0x25, - 0xdb, 0xa2, 0x7f, 0x15, 0xae, 0x8b, 0x03, 0xfc, 0x2f, 0x82, 0x1b, 0x99, 0xaf, 0x04, 0x7e, 0xa7, - 0xe8, 0x7c, 0xa4, 0x3d, 0x61, 0xca, 0xbb, 0x73, 0xa2, 0xa5, 0xa4, 0x0d, 0x21, 0xe9, 0x39, 0xfe, - 0xa0, 0xa0, 0xa4, 0xe4, 0x3e, 0x3b, 0xd0, 0xc5, 0x83, 0x16, 0x29, 0xfd, 0x09, 0xc1, 0x95, 0x89, - 0x6d, 0x8a, 0x5b, 0x33, 0x6d, 0x9f, 0xda, 0xcc, 0xca, 0xc3, 0x73, 0x61, 0xa4, 0x16, 0x5d, 0x68, - 0xb9, 0x87, 0x1b, 0x19, 0x5a, 0xc8, 0x18, 0xd7, 0x95, 0x0f, 0xe6, 0x31, 0x82, 0xd7, 0x32, 0xb6, - 0x25, 0x7e, 0xbb, 0xa8, 0xaf, 0x29, 0x77, 0xe7, 0xf1, 0x5c, 0x58, 0xa9, 0x62, 0x5d, 0xa8, 0x78, - 0x86, 0xdb, 0x73, 0x76, 0x24, 0x76, 0xa3, 0xda, 0x1f, 0x1f, 0x9e, 0x54, 0xd1, 0xd1, 0x49, 0x15, - 0xfd, 0x7d, 0x52, 0x45, 0xdf, 0x9d, 0x56, 0x4b, 0x47, 0xa7, 0xd5, 0xd2, 0x5f, 0xa7, 0xd5, 0xd2, - 0x17, 0x2d, 0xdb, 0xe1, 0xfd, 0x5d, 0x53, 0xb3, 0xd8, 0xf6, 0xb8, 0x8e, 0xd5, 0x27, 0x8e, 0x7b, - 0x56, 0x74, 0x2f, 0x5e, 0x96, 0xef, 0x7b, 0xd4, 0x37, 0xcb, 0xe2, 0xa7, 0xf7, 0xc3, 0xff, 0x02, - 0x00, 0x00, 0xff, 0xff, 0xce, 0x80, 0x35, 0x06, 0x62, 0x0c, 0x00, 0x00, + // 875 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcf, 0x6f, 0xe3, 0x44, + 0x14, 0xce, 0x94, 0x12, 0x89, 0x17, 0x76, 0x57, 0x1a, 0x76, 0x61, 0xd7, 0xbb, 0xcd, 0x6e, 0x9d, + 0x6d, 0xd3, 0x5d, 0x84, 0xbd, 0xc9, 0x4a, 0x2b, 0xa0, 0xfc, 0x50, 0xd3, 0x42, 0xab, 0x42, 0xa5, + 0x60, 0x55, 0x54, 0xe2, 0x12, 0x8d, 0x9d, 0x91, 0x63, 0x35, 0xf5, 0xb8, 0xf1, 0xd4, 0xb4, 0x42, + 0xbd, 0x70, 0x40, 0xe2, 0x86, 0xe0, 0x4f, 0xe0, 0xc8, 0x89, 0xff, 0x01, 0x89, 0xde, 0xa8, 0x84, + 0x84, 0x38, 0x55, 0xa8, 0x45, 0xe2, 0xdf, 0x40, 0x19, 0x4f, 0x6a, 0x3b, 0xb1, 0x9d, 0x34, 0xea, + 0xde, 0x5a, 0xcf, 0xfb, 0xde, 0xfb, 0xbe, 0x6f, 0xde, 0xbc, 0x17, 0x98, 0x37, 0x89, 0x79, 0xd4, + 0x65, 0xae, 0x6e, 0x72, 0xcb, 0xe7, 0x64, 0xd7, 0x71, 0x6d, 0x3d, 0xa8, 0xe9, 0xfb, 0x07, 0xb4, + 0x77, 0xa4, 0x79, 0x3d, 0xc6, 0x19, 0xbe, 0x23, 0x43, 0xb4, 0x28, 0x44, 0x0b, 0x6a, 0xca, 0x6d, + 0x9b, 0xd9, 0x4c, 0x44, 0xe8, 0xfd, 0xbf, 0xc2, 0x60, 0xe5, 0x81, 0xcd, 0x98, 0xdd, 0xa5, 0x3a, + 0xf1, 0x1c, 0x9d, 0xb8, 0x2e, 0xe3, 0x84, 0x3b, 0xcc, 0xf5, 0xe5, 0xe9, 0x53, 0x8b, 0xf9, 0x7b, + 0xcc, 0xd7, 0x4d, 0xe2, 0xd3, 0xb0, 0x86, 0x1e, 0xd4, 0x4c, 0xca, 0x49, 0x4d, 0xf7, 0x88, 0xed, + 0xb8, 0x22, 0x58, 0xc6, 0xaa, 0xe9, 0xcc, 0x3c, 0xd2, 0x23, 0x7b, 0x83, 0x7c, 0x8b, 0xe9, 0x31, + 0x31, 0xa2, 0x22, 0x4e, 0xbd, 0x0d, 0xf8, 0x8b, 0x7e, 0xb5, 0xa6, 0x00, 0x1b, 0x74, 0xff, 0x80, + 0xfa, 0x5c, 0x35, 0xe0, 0x8d, 0xc4, 0x57, 0xdf, 0x63, 0xae, 0x4f, 0xf1, 0x32, 0x14, 0xc3, 0x22, + 0x77, 0xd1, 0x23, 0xb4, 0x54, 0xaa, 0xcf, 0x69, 0xa9, 0x06, 0x68, 0x21, 0xac, 0x31, 0x7b, 0x72, + 0xf6, 0xb0, 0x60, 0x48, 0x88, 0x6a, 0xc1, 0x3d, 0x91, 0xb3, 0xb1, 0xbd, 0xfa, 0x25, 0xe9, 0x3a, + 0x6d, 0xc2, 0x59, 0x6f, 0x50, 0x10, 0x7f, 0x0a, 0x10, 0xc9, 0x94, 0xd9, 0x17, 0xb5, 0xd0, 0x13, + 0xad, 0xef, 0x89, 0x16, 0xfa, 0x2e, 0x3d, 0xd1, 0x9a, 0xc4, 0xa6, 0x12, 0x6b, 0xc4, 0x90, 0xea, + 0xaf, 0x08, 0x94, 0xb4, 0x2a, 0x52, 0xc0, 0x26, 0xdc, 0x34, 0xb9, 0xd5, 0x0a, 0x2e, 0x4f, 0xee, + 0xa2, 0x47, 0xaf, 0x2c, 0x95, 0xea, 0x95, 0x0c, 0x21, 0xf1, 0x2c, 0xc6, 0x0d, 0x93, 0x5b, 0x51, + 0x4e, 0xbc, 0x9e, 0xa0, 0x3c, 0x23, 0x28, 0x57, 0xc7, 0x52, 0x0e, 0x89, 0x24, 0x38, 0x57, 0x60, + 0x3e, 0x34, 0x9b, 0xba, 0x6d, 0xc7, 0xb5, 0x1b, 0xdb, 0xab, 0x6b, 0xb4, 0x4b, 0xed, 0xb0, 0x3d, + 0x06, 0x37, 0xe2, 0x83, 0x9a, 0x17, 0x24, 0xf5, 0x6d, 0xc1, 0xad, 0xbe, 0xbe, 0x76, 0x74, 0x24, + 0x05, 0x3e, 0xce, 0x16, 0x18, 0xe5, 0x31, 0xfa, 0xe6, 0xc4, 0xd2, 0xaa, 0x6d, 0x58, 0x18, 0x31, + 0xb3, 0xc9, 0xbe, 0xa6, 0xbd, 0x15, 0xbe, 0x41, 0x1d, 0xbb, 0xc3, 0x07, 0xd7, 0x57, 0x81, 0x9b, + 0x01, 0xe9, 0xb6, 0xfa, 0xb5, 0xbd, 0xdd, 0x56, 0x87, 0x1e, 0x8a, 0x2b, 0x7c, 0xcd, 0x28, 0x05, + 0xa4, 0xdb, 0xe0, 0x56, 0x73, 0x77, 0x83, 0x1e, 0xe2, 0x37, 0xa1, 0xd8, 0x11, 0x28, 0x61, 0xd6, + 0xac, 0x21, 0xff, 0x53, 0x3f, 0x83, 0xc5, 0x71, 0x55, 0xa4, 0xbc, 0x79, 0x78, 0x3d, 0x60, 0xdc, + 0x71, 0xed, 0x96, 0xd7, 0x3f, 0x17, 0x45, 0x66, 0x8d, 0x52, 0xf8, 0x4d, 0x40, 0xd4, 0xef, 0x11, + 0x54, 0x45, 0xb6, 0x15, 0x8b, 0x3b, 0x01, 0x4d, 0xb4, 0xc1, 0x30, 0xeb, 0x88, 0x10, 0x8a, 0x13, + 0x1a, 0x6a, 0xc6, 0x99, 0xa9, 0x9b, 0xf1, 0x77, 0x04, 0x4b, 0xe3, 0xb9, 0x48, 0x6d, 0x46, 0x46, + 0x6b, 0xbe, 0x3d, 0x41, 0x6b, 0xee, 0x38, 0xbc, 0xb3, 0x45, 0x39, 0x79, 0x69, 0x2d, 0x3a, 0x07, + 0xf7, 0x23, 0x21, 0x84, 0xd3, 0x76, 0xc2, 0x48, 0xf5, 0x05, 0x3c, 0x48, 0x3f, 0x96, 0xda, 0x32, + 0x8c, 0x56, 0x7f, 0x44, 0x50, 0x19, 0xb9, 0xfa, 0xd1, 0xe6, 0x9f, 0xac, 0xbd, 0xae, 0xeb, 0xd6, + 0x4e, 0x10, 0x3c, 0xce, 0x27, 0x25, 0x55, 0xed, 0x64, 0x3d, 0x36, 0x6d, 0xec, 0x63, 0x4b, 0x26, + 0x1c, 0x7a, 0x76, 0xd7, 0x76, 0x6d, 0xf5, 0x3f, 0x00, 0x5e, 0x15, 0x52, 0xf0, 0x77, 0x08, 0x8a, + 0xe1, 0x54, 0xc6, 0x4f, 0x32, 0xd8, 0x8d, 0xae, 0x01, 0xe5, 0xe9, 0x24, 0xa1, 0x61, 0x5d, 0x75, + 0xe1, 0xdb, 0x3f, 0xff, 0xfd, 0x69, 0xe6, 0x21, 0x9e, 0xd3, 0xf3, 0xb6, 0x13, 0xfe, 0x19, 0xc1, + 0x8d, 0xc4, 0x43, 0xc0, 0xcf, 0xf2, 0x8a, 0xa4, 0x2d, 0x0b, 0xa5, 0x76, 0x05, 0x84, 0x64, 0xf7, + 0x8e, 0x60, 0x57, 0xc5, 0x0b, 0x7a, 0xe6, 0x5e, 0x8c, 0x3d, 0x3d, 0xfc, 0x1b, 0x82, 0x3b, 0xa9, + 0x93, 0x16, 0xbf, 0x9b, 0x6b, 0x49, 0xce, 0x04, 0x57, 0xde, 0x9b, 0x02, 0x29, 0xd9, 0xbf, 0x10, + 0xec, 0x9f, 0x61, 0x2d, 0xcb, 0xdb, 0x10, 0xdd, 0x1a, 0x6a, 0x47, 0xfc, 0x17, 0x82, 0xfb, 0x39, + 0xb3, 0x07, 0x7f, 0x94, 0x47, 0x69, 0xfc, 0x00, 0x55, 0x3e, 0x9e, 0x1a, 0x3f, 0xa1, 0xb0, 0xe4, + 0xb5, 0xe8, 0xdf, 0x84, 0x73, 0xe3, 0x18, 0xff, 0x87, 0xe0, 0x5e, 0xe6, 0xba, 0xc0, 0x1f, 0x4c, + 0xda, 0x1f, 0x69, 0xbb, 0x4c, 0xf9, 0x70, 0x4a, 0xb4, 0x94, 0xb4, 0x25, 0x24, 0xad, 0xe3, 0x4f, + 0x26, 0x94, 0x94, 0x1c, 0x6c, 0xc7, 0xba, 0xd8, 0x6c, 0x91, 0xd2, 0x5f, 0x10, 0xdc, 0x1a, 0x1a, + 0xab, 0xb8, 0x3e, 0xd6, 0xf6, 0x91, 0x11, 0xad, 0x3c, 0xbf, 0x12, 0x46, 0x6a, 0xd1, 0x85, 0x96, + 0x27, 0xb8, 0x9a, 0xa1, 0x85, 0x0c, 0x70, 0x2d, 0xb9, 0x39, 0xcf, 0x10, 0xbc, 0x95, 0x31, 0x36, + 0xf1, 0xfb, 0x93, 0xfa, 0x9a, 0xf2, 0x76, 0x96, 0xa7, 0xc2, 0x4a, 0x15, 0x9b, 0x42, 0xc5, 0x1a, + 0x6e, 0x4c, 0x79, 0x23, 0xb1, 0x17, 0xd5, 0xf8, 0xfc, 0xe4, 0xbc, 0x8c, 0x4e, 0xcf, 0xcb, 0xe8, + 0x9f, 0xf3, 0x32, 0xfa, 0xe1, 0xa2, 0x5c, 0x38, 0xbd, 0x28, 0x17, 0xfe, 0xbe, 0x28, 0x17, 0xbe, + 0xaa, 0xdb, 0x0e, 0xef, 0x1c, 0x98, 0x9a, 0xc5, 0xf6, 0x06, 0x75, 0xac, 0x0e, 0x71, 0xdc, 0xcb, + 0xa2, 0x87, 0xf1, 0xb2, 0xfc, 0xc8, 0xa3, 0xbe, 0x59, 0x14, 0xbf, 0xc1, 0x9f, 0xff, 0x1f, 0x00, + 0x00, 0xff, 0xff, 0x33, 0x29, 0x8c, 0xc2, 0x6b, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2980,7 +2980,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegations{}) + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegatorDelegations{}) if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } From 2099738a779770f81eb1c4f1502e5ef415263698 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 4 Aug 2023 12:57:43 +0300 Subject: [PATCH 054/202] fix: Missed btc_delegations -> btc_delegator_delegations (#53) --- proto/babylon/btcstaking/v1/query.proto | 4 +- .../configurer/chain/queries_btcstaking.go | 2 +- x/btcstaking/keeper/grpc_query.go | 2 +- x/btcstaking/keeper/grpc_query_test.go | 2 +- x/btcstaking/types/btcstaking.pb.go | 2 +- x/btcstaking/types/query.pb.go | 137 +++++++++--------- 6 files changed, 75 insertions(+), 74 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index e8be1055a..8e4f99569 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -148,8 +148,8 @@ message QueryBTCValidatorDelegationsRequest { // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. message QueryBTCValidatorDelegationsResponse { - // btc_delegations contains all the queried BTC delegations. - repeated BTCDelegatorDelegations btc_delegations = 1; + // btc_delegator_delegations contains all the queried BTC delegations. + repeated BTCDelegatorDelegations btc_delegator_delegations = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 462a76b97..ffc00effc 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -55,7 +55,7 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BT err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) - return resp.BtcDelegations + return resp.BtcDelegatorDelegations } func (n *NodeConfig) QueryActivatedHeight() uint64 { diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index f63309643..aa56870dd 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -190,5 +190,5 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC return nil, err } - return &types.QueryBTCValidatorDelegationsResponse{BtcDelegations: btcDels, Pagination: pageRes}, nil + return &types.QueryBTCValidatorDelegationsResponse{BtcDelegatorDelegations: btcDels, Pagination: pageRes}, nil } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 2d1e3bc72..958d6602b 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -361,7 +361,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { resp, err = keeper.BTCValidatorDelegations(ctx, &req) require.NoError(t, err) require.NotNil(t, resp) - for _, btcDels := range resp.BtcDelegations { + for _, btcDels := range resp.BtcDelegatorDelegations { require.Len(t, btcDels.Dels, 1) btcDel := btcDels.Dels[0] require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index a24cc25b8..02d939903 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -330,7 +330,7 @@ func (m *BTCDelegation) GetStakingTx() *StakingTx { return nil } -// BTCDelegations is a collection of BTC delegations, typically from the same delegator. +// BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 6c5c2973f..c842d05f9 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -667,8 +667,8 @@ func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest // QueryBTCValidatorDelegationsResponse is the response type for the // Query/BTCValidatorDelegations RPC method. type QueryBTCValidatorDelegationsResponse struct { - // btc_delegations contains all the queried BTC delegations. - BtcDelegations []*BTCDelegatorDelegations `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + // btc_delegator_delegations contains all the queried BTC delegations. + BtcDelegatorDelegations []*BTCDelegatorDelegations `protobuf:"bytes,1,rep,name=btc_delegator_delegations,json=btcDelegatorDelegations,proto3" json:"btc_delegator_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -706,9 +706,9 @@ func (m *QueryBTCValidatorDelegationsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryBTCValidatorDelegationsResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegations() []*BTCDelegatorDelegations { +func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegatorDelegations() []*BTCDelegatorDelegations { if m != nil { - return m.BtcDelegations + return m.BtcDelegatorDelegations } return nil } @@ -740,62 +740,63 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 875 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0xce, 0x94, 0x12, 0x89, 0x17, 0x76, 0x57, 0x1a, 0x76, 0x61, 0xd7, 0xbb, 0xcd, 0x6e, 0x9d, - 0x6d, 0xd3, 0x5d, 0x84, 0xbd, 0xc9, 0x4a, 0x2b, 0xa0, 0xfc, 0x50, 0xd3, 0x42, 0xab, 0x42, 0xa5, - 0x60, 0x55, 0x54, 0xe2, 0x12, 0x8d, 0x9d, 0x91, 0x63, 0x35, 0xf5, 0xb8, 0xf1, 0xd4, 0xb4, 0x42, - 0xbd, 0x70, 0x40, 0xe2, 0x86, 0xe0, 0x4f, 0xe0, 0xc8, 0x89, 0xff, 0x01, 0x89, 0xde, 0xa8, 0x84, - 0x84, 0x38, 0x55, 0xa8, 0x45, 0xe2, 0xdf, 0x40, 0x19, 0x4f, 0x6a, 0x3b, 0xb1, 0x9d, 0x34, 0xea, - 0xde, 0x5a, 0xcf, 0xfb, 0xde, 0xfb, 0xbe, 0x6f, 0xde, 0xbc, 0x17, 0x98, 0x37, 0x89, 0x79, 0xd4, - 0x65, 0xae, 0x6e, 0x72, 0xcb, 0xe7, 0x64, 0xd7, 0x71, 0x6d, 0x3d, 0xa8, 0xe9, 0xfb, 0x07, 0xb4, - 0x77, 0xa4, 0x79, 0x3d, 0xc6, 0x19, 0xbe, 0x23, 0x43, 0xb4, 0x28, 0x44, 0x0b, 0x6a, 0xca, 0x6d, - 0x9b, 0xd9, 0x4c, 0x44, 0xe8, 0xfd, 0xbf, 0xc2, 0x60, 0xe5, 0x81, 0xcd, 0x98, 0xdd, 0xa5, 0x3a, - 0xf1, 0x1c, 0x9d, 0xb8, 0x2e, 0xe3, 0x84, 0x3b, 0xcc, 0xf5, 0xe5, 0xe9, 0x53, 0x8b, 0xf9, 0x7b, - 0xcc, 0xd7, 0x4d, 0xe2, 0xd3, 0xb0, 0x86, 0x1e, 0xd4, 0x4c, 0xca, 0x49, 0x4d, 0xf7, 0x88, 0xed, - 0xb8, 0x22, 0x58, 0xc6, 0xaa, 0xe9, 0xcc, 0x3c, 0xd2, 0x23, 0x7b, 0x83, 0x7c, 0x8b, 0xe9, 0x31, - 0x31, 0xa2, 0x22, 0x4e, 0xbd, 0x0d, 0xf8, 0x8b, 0x7e, 0xb5, 0xa6, 0x00, 0x1b, 0x74, 0xff, 0x80, - 0xfa, 0x5c, 0x35, 0xe0, 0x8d, 0xc4, 0x57, 0xdf, 0x63, 0xae, 0x4f, 0xf1, 0x32, 0x14, 0xc3, 0x22, - 0x77, 0xd1, 0x23, 0xb4, 0x54, 0xaa, 0xcf, 0x69, 0xa9, 0x06, 0x68, 0x21, 0xac, 0x31, 0x7b, 0x72, - 0xf6, 0xb0, 0x60, 0x48, 0x88, 0x6a, 0xc1, 0x3d, 0x91, 0xb3, 0xb1, 0xbd, 0xfa, 0x25, 0xe9, 0x3a, - 0x6d, 0xc2, 0x59, 0x6f, 0x50, 0x10, 0x7f, 0x0a, 0x10, 0xc9, 0x94, 0xd9, 0x17, 0xb5, 0xd0, 0x13, - 0xad, 0xef, 0x89, 0x16, 0xfa, 0x2e, 0x3d, 0xd1, 0x9a, 0xc4, 0xa6, 0x12, 0x6b, 0xc4, 0x90, 0xea, - 0xaf, 0x08, 0x94, 0xb4, 0x2a, 0x52, 0xc0, 0x26, 0xdc, 0x34, 0xb9, 0xd5, 0x0a, 0x2e, 0x4f, 0xee, - 0xa2, 0x47, 0xaf, 0x2c, 0x95, 0xea, 0x95, 0x0c, 0x21, 0xf1, 0x2c, 0xc6, 0x0d, 0x93, 0x5b, 0x51, - 0x4e, 0xbc, 0x9e, 0xa0, 0x3c, 0x23, 0x28, 0x57, 0xc7, 0x52, 0x0e, 0x89, 0x24, 0x38, 0x57, 0x60, - 0x3e, 0x34, 0x9b, 0xba, 0x6d, 0xc7, 0xb5, 0x1b, 0xdb, 0xab, 0x6b, 0xb4, 0x4b, 0xed, 0xb0, 0x3d, - 0x06, 0x37, 0xe2, 0x83, 0x9a, 0x17, 0x24, 0xf5, 0x6d, 0xc1, 0xad, 0xbe, 0xbe, 0x76, 0x74, 0x24, - 0x05, 0x3e, 0xce, 0x16, 0x18, 0xe5, 0x31, 0xfa, 0xe6, 0xc4, 0xd2, 0xaa, 0x6d, 0x58, 0x18, 0x31, - 0xb3, 0xc9, 0xbe, 0xa6, 0xbd, 0x15, 0xbe, 0x41, 0x1d, 0xbb, 0xc3, 0x07, 0xd7, 0x57, 0x81, 0x9b, - 0x01, 0xe9, 0xb6, 0xfa, 0xb5, 0xbd, 0xdd, 0x56, 0x87, 0x1e, 0x8a, 0x2b, 0x7c, 0xcd, 0x28, 0x05, - 0xa4, 0xdb, 0xe0, 0x56, 0x73, 0x77, 0x83, 0x1e, 0xe2, 0x37, 0xa1, 0xd8, 0x11, 0x28, 0x61, 0xd6, - 0xac, 0x21, 0xff, 0x53, 0x3f, 0x83, 0xc5, 0x71, 0x55, 0xa4, 0xbc, 0x79, 0x78, 0x3d, 0x60, 0xdc, - 0x71, 0xed, 0x96, 0xd7, 0x3f, 0x17, 0x45, 0x66, 0x8d, 0x52, 0xf8, 0x4d, 0x40, 0xd4, 0xef, 0x11, - 0x54, 0x45, 0xb6, 0x15, 0x8b, 0x3b, 0x01, 0x4d, 0xb4, 0xc1, 0x30, 0xeb, 0x88, 0x10, 0x8a, 0x13, - 0x1a, 0x6a, 0xc6, 0x99, 0xa9, 0x9b, 0xf1, 0x77, 0x04, 0x4b, 0xe3, 0xb9, 0x48, 0x6d, 0x46, 0x46, - 0x6b, 0xbe, 0x3d, 0x41, 0x6b, 0xee, 0x38, 0xbc, 0xb3, 0x45, 0x39, 0x79, 0x69, 0x2d, 0x3a, 0x07, - 0xf7, 0x23, 0x21, 0x84, 0xd3, 0x76, 0xc2, 0x48, 0xf5, 0x05, 0x3c, 0x48, 0x3f, 0x96, 0xda, 0x32, - 0x8c, 0x56, 0x7f, 0x44, 0x50, 0x19, 0xb9, 0xfa, 0xd1, 0xe6, 0x9f, 0xac, 0xbd, 0xae, 0xeb, 0xd6, - 0x4e, 0x10, 0x3c, 0xce, 0x27, 0x25, 0x55, 0xed, 0x64, 0x3d, 0x36, 0x6d, 0xec, 0x63, 0x4b, 0x26, - 0x1c, 0x7a, 0x76, 0xd7, 0x76, 0x6d, 0xf5, 0x3f, 0x00, 0x5e, 0x15, 0x52, 0xf0, 0x77, 0x08, 0x8a, - 0xe1, 0x54, 0xc6, 0x4f, 0x32, 0xd8, 0x8d, 0xae, 0x01, 0xe5, 0xe9, 0x24, 0xa1, 0x61, 0x5d, 0x75, - 0xe1, 0xdb, 0x3f, 0xff, 0xfd, 0x69, 0xe6, 0x21, 0x9e, 0xd3, 0xf3, 0xb6, 0x13, 0xfe, 0x19, 0xc1, - 0x8d, 0xc4, 0x43, 0xc0, 0xcf, 0xf2, 0x8a, 0xa4, 0x2d, 0x0b, 0xa5, 0x76, 0x05, 0x84, 0x64, 0xf7, - 0x8e, 0x60, 0x57, 0xc5, 0x0b, 0x7a, 0xe6, 0x5e, 0x8c, 0x3d, 0x3d, 0xfc, 0x1b, 0x82, 0x3b, 0xa9, - 0x93, 0x16, 0xbf, 0x9b, 0x6b, 0x49, 0xce, 0x04, 0x57, 0xde, 0x9b, 0x02, 0x29, 0xd9, 0xbf, 0x10, - 0xec, 0x9f, 0x61, 0x2d, 0xcb, 0xdb, 0x10, 0xdd, 0x1a, 0x6a, 0x47, 0xfc, 0x17, 0x82, 0xfb, 0x39, - 0xb3, 0x07, 0x7f, 0x94, 0x47, 0x69, 0xfc, 0x00, 0x55, 0x3e, 0x9e, 0x1a, 0x3f, 0xa1, 0xb0, 0xe4, - 0xb5, 0xe8, 0xdf, 0x84, 0x73, 0xe3, 0x18, 0xff, 0x87, 0xe0, 0x5e, 0xe6, 0xba, 0xc0, 0x1f, 0x4c, - 0xda, 0x1f, 0x69, 0xbb, 0x4c, 0xf9, 0x70, 0x4a, 0xb4, 0x94, 0xb4, 0x25, 0x24, 0xad, 0xe3, 0x4f, - 0x26, 0x94, 0x94, 0x1c, 0x6c, 0xc7, 0xba, 0xd8, 0x6c, 0x91, 0xd2, 0x5f, 0x10, 0xdc, 0x1a, 0x1a, - 0xab, 0xb8, 0x3e, 0xd6, 0xf6, 0x91, 0x11, 0xad, 0x3c, 0xbf, 0x12, 0x46, 0x6a, 0xd1, 0x85, 0x96, - 0x27, 0xb8, 0x9a, 0xa1, 0x85, 0x0c, 0x70, 0x2d, 0xb9, 0x39, 0xcf, 0x10, 0xbc, 0x95, 0x31, 0x36, - 0xf1, 0xfb, 0x93, 0xfa, 0x9a, 0xf2, 0x76, 0x96, 0xa7, 0xc2, 0x4a, 0x15, 0x9b, 0x42, 0xc5, 0x1a, - 0x6e, 0x4c, 0x79, 0x23, 0xb1, 0x17, 0xd5, 0xf8, 0xfc, 0xe4, 0xbc, 0x8c, 0x4e, 0xcf, 0xcb, 0xe8, - 0x9f, 0xf3, 0x32, 0xfa, 0xe1, 0xa2, 0x5c, 0x38, 0xbd, 0x28, 0x17, 0xfe, 0xbe, 0x28, 0x17, 0xbe, - 0xaa, 0xdb, 0x0e, 0xef, 0x1c, 0x98, 0x9a, 0xc5, 0xf6, 0x06, 0x75, 0xac, 0x0e, 0x71, 0xdc, 0xcb, - 0xa2, 0x87, 0xf1, 0xb2, 0xfc, 0xc8, 0xa3, 0xbe, 0x59, 0x14, 0xbf, 0xc1, 0x9f, 0xff, 0x1f, 0x00, - 0x00, 0xff, 0xff, 0x33, 0x29, 0x8c, 0xc2, 0x6b, 0x0c, 0x00, 0x00, + // 882 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5d, 0x6b, 0x23, 0x55, + 0x18, 0xce, 0xa9, 0x35, 0xe0, 0x1b, 0x77, 0x17, 0x8e, 0xbb, 0xee, 0x76, 0x76, 0x9b, 0xdd, 0x4e, + 0xb6, 0x4d, 0x77, 0xc5, 0x99, 0x4d, 0x16, 0x16, 0x75, 0xfd, 0xa0, 0x69, 0xb5, 0xa5, 0x5a, 0x88, + 0x43, 0x51, 0xf0, 0x26, 0x9c, 0x99, 0x1c, 0x26, 0x63, 0xd3, 0x39, 0xd3, 0xcc, 0xe9, 0xd8, 0x22, + 0xbd, 0xf1, 0x42, 0xf0, 0x4e, 0xf4, 0x27, 0x78, 0xe9, 0x95, 0xff, 0x41, 0xb0, 0x77, 0x16, 0x04, + 0x11, 0x84, 0x22, 0xad, 0xe0, 0xdf, 0x90, 0x9c, 0x39, 0xe9, 0xcc, 0x24, 0x33, 0x93, 0x34, 0x74, + 0xef, 0xda, 0x39, 0xef, 0xc7, 0xf3, 0x3c, 0xef, 0x57, 0x60, 0xc1, 0x24, 0xe6, 0x61, 0x97, 0xb9, + 0xba, 0xc9, 0x2d, 0x9f, 0x93, 0x1d, 0xc7, 0xb5, 0xf5, 0xa0, 0xa6, 0xef, 0xed, 0xd3, 0xde, 0xa1, + 0xe6, 0xf5, 0x18, 0x67, 0xf8, 0x96, 0x34, 0xd1, 0x22, 0x13, 0x2d, 0xa8, 0x29, 0x37, 0x6d, 0x66, + 0x33, 0x61, 0xa1, 0xf7, 0xff, 0x0a, 0x8d, 0x95, 0x7b, 0x36, 0x63, 0x76, 0x97, 0xea, 0xc4, 0x73, + 0x74, 0xe2, 0xba, 0x8c, 0x13, 0xee, 0x30, 0xd7, 0x97, 0xaf, 0x8f, 0x2d, 0xe6, 0xef, 0x32, 0x5f, + 0x37, 0x89, 0x4f, 0xc3, 0x1c, 0x7a, 0x50, 0x33, 0x29, 0x27, 0x35, 0xdd, 0x23, 0xb6, 0xe3, 0x0a, + 0x63, 0x69, 0xab, 0xa6, 0x23, 0xf3, 0x48, 0x8f, 0xec, 0x0e, 0xe2, 0x2d, 0xa5, 0xdb, 0xc4, 0x80, + 0x0a, 0x3b, 0xf5, 0x26, 0xe0, 0x4f, 0xfb, 0xd9, 0x9a, 0xc2, 0xd9, 0xa0, 0x7b, 0xfb, 0xd4, 0xe7, + 0xaa, 0x01, 0xaf, 0x25, 0xbe, 0xfa, 0x1e, 0x73, 0x7d, 0x8a, 0x9f, 0x43, 0x31, 0x4c, 0x72, 0x07, + 0x3d, 0x40, 0xcb, 0xa5, 0xfa, 0xbc, 0x96, 0x2a, 0x80, 0x16, 0xba, 0x35, 0x66, 0x8f, 0x4f, 0xef, + 0x17, 0x0c, 0xe9, 0xa2, 0x5a, 0x30, 0x27, 0x62, 0x36, 0xb6, 0x57, 0x3f, 0x23, 0x5d, 0xa7, 0x4d, + 0x38, 0xeb, 0x0d, 0x12, 0xe2, 0x8f, 0x00, 0x22, 0x9a, 0x32, 0xfa, 0x92, 0x16, 0x6a, 0xa2, 0xf5, + 0x35, 0xd1, 0x42, 0xdd, 0xa5, 0x26, 0x5a, 0x93, 0xd8, 0x54, 0xfa, 0x1a, 0x31, 0x4f, 0xf5, 0x17, + 0x04, 0x4a, 0x5a, 0x16, 0x49, 0x60, 0x13, 0xae, 0x9b, 0xdc, 0x6a, 0x05, 0x17, 0x2f, 0x77, 0xd0, + 0x83, 0x97, 0x96, 0x4b, 0xf5, 0x4a, 0x06, 0x91, 0x78, 0x14, 0xe3, 0x9a, 0xc9, 0xad, 0x28, 0x26, + 0x5e, 0x4f, 0x40, 0x9e, 0x11, 0x90, 0xab, 0x63, 0x21, 0x87, 0x40, 0x12, 0x98, 0x2b, 0xb0, 0x10, + 0x8a, 0x4d, 0xdd, 0xb6, 0xe3, 0xda, 0x8d, 0xed, 0xd5, 0x35, 0xda, 0xa5, 0x76, 0xd8, 0x1e, 0x83, + 0x8a, 0xf8, 0xa0, 0xe6, 0x19, 0x49, 0x7e, 0x5b, 0x70, 0xa3, 0xcf, 0xaf, 0x1d, 0x3d, 0x49, 0x82, + 0x0f, 0xb3, 0x09, 0x46, 0x71, 0x8c, 0xbe, 0x38, 0xb1, 0xb0, 0x6a, 0x1b, 0x16, 0x47, 0xc4, 0x6c, + 0xb2, 0xaf, 0x68, 0x6f, 0x85, 0x6f, 0x50, 0xc7, 0xee, 0xf0, 0x41, 0xf9, 0x2a, 0x70, 0x3d, 0x20, + 0xdd, 0x56, 0x3f, 0xb7, 0xb7, 0xd3, 0xea, 0xd0, 0x03, 0x51, 0xc2, 0x57, 0x8c, 0x52, 0x40, 0xba, + 0x0d, 0x6e, 0x35, 0x77, 0x36, 0xe8, 0x01, 0x7e, 0x1d, 0x8a, 0x1d, 0xe1, 0x25, 0xc4, 0x9a, 0x35, + 0xe4, 0x7f, 0xea, 0xc7, 0xb0, 0x34, 0x2e, 0x8b, 0xa4, 0xb7, 0x00, 0xaf, 0x06, 0x8c, 0x3b, 0xae, + 0xdd, 0xf2, 0xfa, 0xef, 0x22, 0xc9, 0xac, 0x51, 0x0a, 0xbf, 0x09, 0x17, 0xf5, 0x3b, 0x04, 0x55, + 0x11, 0x6d, 0xc5, 0xe2, 0x4e, 0x40, 0x13, 0x6d, 0x30, 0x8c, 0x3a, 0x02, 0x84, 0xe2, 0x80, 0x86, + 0x9a, 0x71, 0x66, 0xea, 0x66, 0xfc, 0x0d, 0xc1, 0xf2, 0x78, 0x2c, 0x92, 0x9b, 0x91, 0xd1, 0x9a, + 0x6f, 0x4c, 0xd0, 0x9a, 0x9f, 0x3b, 0xbc, 0xb3, 0x45, 0x39, 0x79, 0x61, 0x2d, 0x3a, 0x0f, 0x77, + 0x23, 0x22, 0x84, 0xd3, 0x76, 0x42, 0x48, 0xf5, 0x19, 0xdc, 0x4b, 0x7f, 0x96, 0xdc, 0x32, 0x84, + 0x56, 0x7f, 0x40, 0x50, 0x19, 0x29, 0xfd, 0x68, 0xf3, 0x4f, 0xd6, 0x5e, 0x57, 0x55, 0xb5, 0xbf, + 0x11, 0x3c, 0xcc, 0x07, 0x25, 0x59, 0x7d, 0x09, 0x73, 0xb1, 0x61, 0x63, 0xbd, 0x94, 0xb1, 0xd3, + 0xc6, 0x8e, 0x5d, 0x32, 0xf4, 0xed, 0x68, 0x00, 0x13, 0x0f, 0x57, 0x56, 0xc9, 0xfa, 0xef, 0x00, + 0x2f, 0x0b, 0x76, 0xf8, 0x5b, 0x04, 0xc5, 0x70, 0x51, 0xe3, 0x47, 0x19, 0x30, 0x47, 0x2f, 0x83, + 0xf2, 0x78, 0x12, 0xd3, 0x30, 0xaf, 0xba, 0xf8, 0xcd, 0x1f, 0xff, 0xfe, 0x38, 0x73, 0x1f, 0xcf, + 0xeb, 0x79, 0x07, 0x0b, 0xff, 0x84, 0xe0, 0x5a, 0x62, 0x36, 0xf0, 0x93, 0xbc, 0x24, 0x69, 0xf7, + 0x43, 0xa9, 0x5d, 0xc2, 0x43, 0xa2, 0x7b, 0x53, 0xa0, 0xab, 0xe2, 0x45, 0x3d, 0xf3, 0x54, 0xc6, + 0xa6, 0x11, 0xff, 0x8a, 0xe0, 0x56, 0xea, 0xf2, 0xc5, 0x6f, 0xe5, 0x4a, 0x92, 0xb3, 0xd4, 0x95, + 0xb7, 0xa7, 0xf0, 0x94, 0xe8, 0x9f, 0x09, 0xf4, 0x4f, 0xb0, 0x96, 0xa5, 0x6d, 0xe8, 0xdd, 0x1a, + 0x3a, 0x07, 0xf8, 0x4f, 0x04, 0x77, 0x73, 0xd6, 0x11, 0x7e, 0x3f, 0x0f, 0xd2, 0xf8, 0x9d, 0xaa, + 0x7c, 0x30, 0xb5, 0xff, 0x84, 0xc4, 0x92, 0x65, 0xd1, 0xbf, 0x0e, 0x57, 0xc9, 0x11, 0xfe, 0x0f, + 0xc1, 0x5c, 0xe6, 0x05, 0xc1, 0xef, 0x4e, 0xda, 0x1f, 0x69, 0xe7, 0x4d, 0x79, 0x6f, 0x4a, 0x6f, + 0x49, 0x69, 0x4b, 0x50, 0x5a, 0xc7, 0x1f, 0x4e, 0x48, 0x29, 0xb9, 0xeb, 0x8e, 0x74, 0x71, 0xec, + 0x22, 0xa6, 0x3f, 0x23, 0xb8, 0x31, 0xb4, 0x69, 0x71, 0x7d, 0xac, 0xec, 0x23, 0x5b, 0x5b, 0x79, + 0x7a, 0x29, 0x1f, 0xc9, 0x45, 0x17, 0x5c, 0x1e, 0xe1, 0x6a, 0x06, 0x17, 0x32, 0xf0, 0x6b, 0xc9, + 0x63, 0x7a, 0x8a, 0xe0, 0x76, 0xc6, 0x26, 0xc5, 0xef, 0x4c, 0xaa, 0x6b, 0xca, 0xec, 0x3c, 0x9f, + 0xca, 0x57, 0xb2, 0xd8, 0x14, 0x2c, 0xd6, 0x70, 0x63, 0xca, 0x8a, 0xc4, 0x26, 0xaa, 0xf1, 0xc9, + 0xf1, 0x59, 0x19, 0x9d, 0x9c, 0x95, 0xd1, 0x3f, 0x67, 0x65, 0xf4, 0xfd, 0x79, 0xb9, 0x70, 0x72, + 0x5e, 0x2e, 0xfc, 0x75, 0x5e, 0x2e, 0x7c, 0x51, 0xb7, 0x1d, 0xde, 0xd9, 0x37, 0x35, 0x8b, 0xed, + 0x0e, 0xf2, 0x58, 0x1d, 0xe2, 0xb8, 0x17, 0x49, 0x0f, 0xe2, 0x69, 0xf9, 0xa1, 0x47, 0x7d, 0xb3, + 0x28, 0x7e, 0x96, 0x3f, 0xfd, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xb1, 0x05, 0x1d, 0xba, 0x7e, 0x0c, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1587,10 +1588,10 @@ func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) i-- dAtA[i] = 0x12 } - if len(m.BtcDelegations) > 0 { - for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { + if len(m.BtcDelegatorDelegations) > 0 { + for iNdEx := len(m.BtcDelegatorDelegations) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.BtcDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.BtcDelegatorDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1798,8 +1799,8 @@ func (m *QueryBTCValidatorDelegationsResponse) Size() (n int) { } var l int _ = l - if len(m.BtcDelegations) > 0 { - for _, e := range m.BtcDelegations { + if len(m.BtcDelegatorDelegations) > 0 { + for _, e := range m.BtcDelegatorDelegations { l = e.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -2953,7 +2954,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegatorDelegations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2980,8 +2981,8 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegatorDelegations{}) - if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.BtcDelegatorDelegations = append(m.BtcDelegatorDelegations, &BTCDelegatorDelegations{}) + if err := m.BtcDelegatorDelegations[len(m.BtcDelegatorDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex From 27bf4a8847ca6f766fae0510a2183574980040b7 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 4 Aug 2023 13:23:55 +0200 Subject: [PATCH 055/202] Add query for validator (#51) * Add query for validator --- proto/babylon/btcstaking/v1/query.proto | 17 + x/btcstaking/keeper/grpc_query.go | 27 ++ x/btcstaking/keeper/grpc_query_test.go | 51 +++ x/btcstaking/types/query.pb.go | 528 +++++++++++++++++++++--- x/btcstaking/types/query.pb.gw.go | 101 +++++ 5 files changed, 657 insertions(+), 67 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 8e4f99569..8bca2917b 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -21,6 +21,11 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators"; } + // BTCValidator info about one validator + rpc BTCValidator(QueryBTCValidatorRequest) returns (QueryBTCValidatorResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/validator"; + } + // PendingBTCDelegations queries all pending BTC delegations rpc PendingBTCDelegations(QueryPendingBTCDelegationsRequest) returns (QueryPendingBTCDelegationsResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/pending_btc_delegations"; @@ -75,6 +80,18 @@ message QueryBTCValidatorsResponse { } +// QueryBTCValidatorsRequest requests information about a BTC validator +message QueryBTCValidatorRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + string val_btc_pk_hex = 1; +} + +// QueryBTCValidatorsResponse resoponse contains information about a BTC validator +message QueryBTCValidatorResponse { + // btc_validator contains the BTC validator + BTCValidator btc_validator = 1; +} + // QueryPendingBTCDelegationsRequest is the request type for the // Query/PendingBTCDelegations RPC method. message QueryPendingBTCDelegationsRequest {} diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index aa56870dd..9e1c2c1a8 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -38,6 +38,33 @@ func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidators return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil } +// BTCValidator returns the validator with the specified validator BTC PK +func (k Keeper) BTCValidator(ctx context.Context, req *types.QueryBTCValidatorRequest) (*types.QueryBTCValidatorResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if len(req.ValBtcPkHex) == 0 { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidRequest, "validator BTC public key cannot be empty") + } + + valPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + if err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + val, err := k.GetBTCValidator(sdkCtx, valPK.MustMarshal()) + + if err != nil { + return nil, err + } + + return &types.QueryBTCValidatorResponse{BtcValidator: val}, nil +} + // PendingBTCDelegations returns all pending BTC delegations // TODO: find a good way to support pagination of this query func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendingBTCDelegationsRequest) (*types.QueryPendingBTCDelegationsResponse, error) { diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 958d6602b..149d2677f 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "math/rand" "testing" @@ -107,6 +108,56 @@ func FuzzBTCValidators(f *testing.F) { }) } +func FuzzBTCValidator(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // Generate random btc validators and add them to kv store + btcValsMap := make(map[string]*types.BTCValidator) + for i := 0; i < int(datagen.RandomInt(r, 10)+1); i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + + keeper.SetBTCValidator(ctx, btcVal) + btcValsMap[btcVal.BtcPk.MarshalHex()] = btcVal + } + + // Test nil request + resp, err := keeper.BTCValidators(ctx, nil) + require.Error(t, err) + require.Nil(t, resp) + + for k, v := range btcValsMap { + // Generate a request with a valid key + req := types.QueryBTCValidatorRequest{ValBtcPkHex: k} + resp, err := keeper.BTCValidator(ctx, &req) + if err != nil { + t.Errorf("Valid request led to an error %s", err) + } + if resp == nil { + t.Fatalf("Valid request led to a nil response") + } + + // check keys from map matches those in returned response + require.Equal(t, v.BtcPk.MarshalHex(), resp.BtcValidator.BtcPk.MarshalHex()) + require.Equal(t, v.BabylonPk, resp.BtcValidator.BabylonPk) + } + + // check some random non exsisting guy + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + req := types.QueryBTCValidatorRequest{ValBtcPkHex: btcVal.BtcPk.MarshalHex()} + respNonExists, err := keeper.BTCValidator(ctx, &req) + require.Error(t, err) + require.Nil(t, respNonExists) + require.True(t, errors.Is(err, types.ErrBTCValNotFound)) + }) +} + func FuzzBTCDelegations(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index c842d05f9..3cf44878d 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -216,6 +216,98 @@ func (m *QueryBTCValidatorsResponse) GetPagination() *query.PageResponse { return nil } +// QueryBTCValidatorsRequest requests information about a BTC validator +type QueryBTCValidatorRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` +} + +func (m *QueryBTCValidatorRequest) Reset() { *m = QueryBTCValidatorRequest{} } +func (m *QueryBTCValidatorRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorRequest) ProtoMessage() {} +func (*QueryBTCValidatorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{4} +} +func (m *QueryBTCValidatorRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorRequest.Merge(m, src) +} +func (m *QueryBTCValidatorRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorRequest) GetValBtcPkHex() string { + if m != nil { + return m.ValBtcPkHex + } + return "" +} + +// QueryBTCValidatorsResponse resoponse contains information about a BTC validator +type QueryBTCValidatorResponse struct { + // btc_validator contains the BTC validator + BtcValidator *BTCValidator `protobuf:"bytes,1,opt,name=btc_validator,json=btcValidator,proto3" json:"btc_validator,omitempty"` +} + +func (m *QueryBTCValidatorResponse) Reset() { *m = QueryBTCValidatorResponse{} } +func (m *QueryBTCValidatorResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorResponse) ProtoMessage() {} +func (*QueryBTCValidatorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{5} +} +func (m *QueryBTCValidatorResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorResponse.Merge(m, src) +} +func (m *QueryBTCValidatorResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorResponse) GetBtcValidator() *BTCValidator { + if m != nil { + return m.BtcValidator + } + return nil +} + // QueryPendingBTCDelegationsRequest is the request type for the // Query/PendingBTCDelegations RPC method. type QueryPendingBTCDelegationsRequest struct { @@ -225,7 +317,7 @@ func (m *QueryPendingBTCDelegationsRequest) Reset() { *m = QueryPendingB func (m *QueryPendingBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryPendingBTCDelegationsRequest) ProtoMessage() {} func (*QueryPendingBTCDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{4} + return fileDescriptor_74d49d26f7429697, []int{6} } func (m *QueryPendingBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -265,7 +357,7 @@ func (m *QueryPendingBTCDelegationsResponse) Reset() { *m = QueryPending func (m *QueryPendingBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryPendingBTCDelegationsResponse) ProtoMessage() {} func (*QueryPendingBTCDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{5} + return fileDescriptor_74d49d26f7429697, []int{7} } func (m *QueryPendingBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -316,7 +408,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{6} + return fileDescriptor_74d49d26f7429697, []int{8} } func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -372,7 +464,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{7} + return fileDescriptor_74d49d26f7429697, []int{9} } func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -423,7 +515,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{8} + return fileDescriptor_74d49d26f7429697, []int{10} } func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -481,7 +573,7 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{9} + return fileDescriptor_74d49d26f7429697, []int{11} } func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -532,7 +624,7 @@ func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeigh func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightRequest) ProtoMessage() {} func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{10} + return fileDescriptor_74d49d26f7429697, []int{12} } func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -570,7 +662,7 @@ func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeig func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightResponse) ProtoMessage() {} func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{11} + return fileDescriptor_74d49d26f7429697, []int{13} } func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -621,7 +713,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCVal func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{12} + return fileDescriptor_74d49d26f7429697, []int{14} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -677,7 +769,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{13} + return fileDescriptor_74d49d26f7429697, []int{15} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -725,6 +817,8 @@ func init() { proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") proto.RegisterType((*QueryBTCValidatorsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsRequest") proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") + proto.RegisterType((*QueryBTCValidatorRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorRequest") + proto.RegisterType((*QueryBTCValidatorResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorResponse") proto.RegisterType((*QueryPendingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsRequest") proto.RegisterType((*QueryPendingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") @@ -740,63 +834,66 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 882 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5d, 0x6b, 0x23, 0x55, - 0x18, 0xce, 0xa9, 0x35, 0xe0, 0x1b, 0x77, 0x17, 0x8e, 0xbb, 0xee, 0x76, 0x76, 0x9b, 0xdd, 0x4e, - 0xb6, 0x4d, 0x77, 0xc5, 0x99, 0x4d, 0x16, 0x16, 0x75, 0xfd, 0xa0, 0x69, 0xb5, 0xa5, 0x5a, 0x88, - 0x43, 0x51, 0xf0, 0x26, 0x9c, 0x99, 0x1c, 0x26, 0x63, 0xd3, 0x39, 0xd3, 0xcc, 0xe9, 0xd8, 0x22, - 0xbd, 0xf1, 0x42, 0xf0, 0x4e, 0xf4, 0x27, 0x78, 0xe9, 0x95, 0xff, 0x41, 0xb0, 0x77, 0x16, 0x04, - 0x11, 0x84, 0x22, 0xad, 0xe0, 0xdf, 0x90, 0x9c, 0x39, 0xe9, 0xcc, 0x24, 0x33, 0x93, 0x34, 0x74, - 0xef, 0xda, 0x39, 0xef, 0xc7, 0xf3, 0x3c, 0xef, 0x57, 0x60, 0xc1, 0x24, 0xe6, 0x61, 0x97, 0xb9, - 0xba, 0xc9, 0x2d, 0x9f, 0x93, 0x1d, 0xc7, 0xb5, 0xf5, 0xa0, 0xa6, 0xef, 0xed, 0xd3, 0xde, 0xa1, - 0xe6, 0xf5, 0x18, 0x67, 0xf8, 0x96, 0x34, 0xd1, 0x22, 0x13, 0x2d, 0xa8, 0x29, 0x37, 0x6d, 0x66, - 0x33, 0x61, 0xa1, 0xf7, 0xff, 0x0a, 0x8d, 0x95, 0x7b, 0x36, 0x63, 0x76, 0x97, 0xea, 0xc4, 0x73, - 0x74, 0xe2, 0xba, 0x8c, 0x13, 0xee, 0x30, 0xd7, 0x97, 0xaf, 0x8f, 0x2d, 0xe6, 0xef, 0x32, 0x5f, - 0x37, 0x89, 0x4f, 0xc3, 0x1c, 0x7a, 0x50, 0x33, 0x29, 0x27, 0x35, 0xdd, 0x23, 0xb6, 0xe3, 0x0a, - 0x63, 0x69, 0xab, 0xa6, 0x23, 0xf3, 0x48, 0x8f, 0xec, 0x0e, 0xe2, 0x2d, 0xa5, 0xdb, 0xc4, 0x80, - 0x0a, 0x3b, 0xf5, 0x26, 0xe0, 0x4f, 0xfb, 0xd9, 0x9a, 0xc2, 0xd9, 0xa0, 0x7b, 0xfb, 0xd4, 0xe7, - 0xaa, 0x01, 0xaf, 0x25, 0xbe, 0xfa, 0x1e, 0x73, 0x7d, 0x8a, 0x9f, 0x43, 0x31, 0x4c, 0x72, 0x07, - 0x3d, 0x40, 0xcb, 0xa5, 0xfa, 0xbc, 0x96, 0x2a, 0x80, 0x16, 0xba, 0x35, 0x66, 0x8f, 0x4f, 0xef, - 0x17, 0x0c, 0xe9, 0xa2, 0x5a, 0x30, 0x27, 0x62, 0x36, 0xb6, 0x57, 0x3f, 0x23, 0x5d, 0xa7, 0x4d, - 0x38, 0xeb, 0x0d, 0x12, 0xe2, 0x8f, 0x00, 0x22, 0x9a, 0x32, 0xfa, 0x92, 0x16, 0x6a, 0xa2, 0xf5, - 0x35, 0xd1, 0x42, 0xdd, 0xa5, 0x26, 0x5a, 0x93, 0xd8, 0x54, 0xfa, 0x1a, 0x31, 0x4f, 0xf5, 0x17, - 0x04, 0x4a, 0x5a, 0x16, 0x49, 0x60, 0x13, 0xae, 0x9b, 0xdc, 0x6a, 0x05, 0x17, 0x2f, 0x77, 0xd0, - 0x83, 0x97, 0x96, 0x4b, 0xf5, 0x4a, 0x06, 0x91, 0x78, 0x14, 0xe3, 0x9a, 0xc9, 0xad, 0x28, 0x26, - 0x5e, 0x4f, 0x40, 0x9e, 0x11, 0x90, 0xab, 0x63, 0x21, 0x87, 0x40, 0x12, 0x98, 0x2b, 0xb0, 0x10, - 0x8a, 0x4d, 0xdd, 0xb6, 0xe3, 0xda, 0x8d, 0xed, 0xd5, 0x35, 0xda, 0xa5, 0x76, 0xd8, 0x1e, 0x83, - 0x8a, 0xf8, 0xa0, 0xe6, 0x19, 0x49, 0x7e, 0x5b, 0x70, 0xa3, 0xcf, 0xaf, 0x1d, 0x3d, 0x49, 0x82, - 0x0f, 0xb3, 0x09, 0x46, 0x71, 0x8c, 0xbe, 0x38, 0xb1, 0xb0, 0x6a, 0x1b, 0x16, 0x47, 0xc4, 0x6c, - 0xb2, 0xaf, 0x68, 0x6f, 0x85, 0x6f, 0x50, 0xc7, 0xee, 0xf0, 0x41, 0xf9, 0x2a, 0x70, 0x3d, 0x20, - 0xdd, 0x56, 0x3f, 0xb7, 0xb7, 0xd3, 0xea, 0xd0, 0x03, 0x51, 0xc2, 0x57, 0x8c, 0x52, 0x40, 0xba, - 0x0d, 0x6e, 0x35, 0x77, 0x36, 0xe8, 0x01, 0x7e, 0x1d, 0x8a, 0x1d, 0xe1, 0x25, 0xc4, 0x9a, 0x35, - 0xe4, 0x7f, 0xea, 0xc7, 0xb0, 0x34, 0x2e, 0x8b, 0xa4, 0xb7, 0x00, 0xaf, 0x06, 0x8c, 0x3b, 0xae, - 0xdd, 0xf2, 0xfa, 0xef, 0x22, 0xc9, 0xac, 0x51, 0x0a, 0xbf, 0x09, 0x17, 0xf5, 0x3b, 0x04, 0x55, - 0x11, 0x6d, 0xc5, 0xe2, 0x4e, 0x40, 0x13, 0x6d, 0x30, 0x8c, 0x3a, 0x02, 0x84, 0xe2, 0x80, 0x86, - 0x9a, 0x71, 0x66, 0xea, 0x66, 0xfc, 0x0d, 0xc1, 0xf2, 0x78, 0x2c, 0x92, 0x9b, 0x91, 0xd1, 0x9a, - 0x6f, 0x4c, 0xd0, 0x9a, 0x9f, 0x3b, 0xbc, 0xb3, 0x45, 0x39, 0x79, 0x61, 0x2d, 0x3a, 0x0f, 0x77, - 0x23, 0x22, 0x84, 0xd3, 0x76, 0x42, 0x48, 0xf5, 0x19, 0xdc, 0x4b, 0x7f, 0x96, 0xdc, 0x32, 0x84, - 0x56, 0x7f, 0x40, 0x50, 0x19, 0x29, 0xfd, 0x68, 0xf3, 0x4f, 0xd6, 0x5e, 0x57, 0x55, 0xb5, 0xbf, - 0x11, 0x3c, 0xcc, 0x07, 0x25, 0x59, 0x7d, 0x09, 0x73, 0xb1, 0x61, 0x63, 0xbd, 0x94, 0xb1, 0xd3, - 0xc6, 0x8e, 0x5d, 0x32, 0xf4, 0xed, 0x68, 0x00, 0x13, 0x0f, 0x57, 0x56, 0xc9, 0xfa, 0xef, 0x00, - 0x2f, 0x0b, 0x76, 0xf8, 0x5b, 0x04, 0xc5, 0x70, 0x51, 0xe3, 0x47, 0x19, 0x30, 0x47, 0x2f, 0x83, - 0xf2, 0x78, 0x12, 0xd3, 0x30, 0xaf, 0xba, 0xf8, 0xcd, 0x1f, 0xff, 0xfe, 0x38, 0x73, 0x1f, 0xcf, - 0xeb, 0x79, 0x07, 0x0b, 0xff, 0x84, 0xe0, 0x5a, 0x62, 0x36, 0xf0, 0x93, 0xbc, 0x24, 0x69, 0xf7, - 0x43, 0xa9, 0x5d, 0xc2, 0x43, 0xa2, 0x7b, 0x53, 0xa0, 0xab, 0xe2, 0x45, 0x3d, 0xf3, 0x54, 0xc6, - 0xa6, 0x11, 0xff, 0x8a, 0xe0, 0x56, 0xea, 0xf2, 0xc5, 0x6f, 0xe5, 0x4a, 0x92, 0xb3, 0xd4, 0x95, - 0xb7, 0xa7, 0xf0, 0x94, 0xe8, 0x9f, 0x09, 0xf4, 0x4f, 0xb0, 0x96, 0xa5, 0x6d, 0xe8, 0xdd, 0x1a, - 0x3a, 0x07, 0xf8, 0x4f, 0x04, 0x77, 0x73, 0xd6, 0x11, 0x7e, 0x3f, 0x0f, 0xd2, 0xf8, 0x9d, 0xaa, - 0x7c, 0x30, 0xb5, 0xff, 0x84, 0xc4, 0x92, 0x65, 0xd1, 0xbf, 0x0e, 0x57, 0xc9, 0x11, 0xfe, 0x0f, - 0xc1, 0x5c, 0xe6, 0x05, 0xc1, 0xef, 0x4e, 0xda, 0x1f, 0x69, 0xe7, 0x4d, 0x79, 0x6f, 0x4a, 0x6f, - 0x49, 0x69, 0x4b, 0x50, 0x5a, 0xc7, 0x1f, 0x4e, 0x48, 0x29, 0xb9, 0xeb, 0x8e, 0x74, 0x71, 0xec, - 0x22, 0xa6, 0x3f, 0x23, 0xb8, 0x31, 0xb4, 0x69, 0x71, 0x7d, 0xac, 0xec, 0x23, 0x5b, 0x5b, 0x79, - 0x7a, 0x29, 0x1f, 0xc9, 0x45, 0x17, 0x5c, 0x1e, 0xe1, 0x6a, 0x06, 0x17, 0x32, 0xf0, 0x6b, 0xc9, - 0x63, 0x7a, 0x8a, 0xe0, 0x76, 0xc6, 0x26, 0xc5, 0xef, 0x4c, 0xaa, 0x6b, 0xca, 0xec, 0x3c, 0x9f, - 0xca, 0x57, 0xb2, 0xd8, 0x14, 0x2c, 0xd6, 0x70, 0x63, 0xca, 0x8a, 0xc4, 0x26, 0xaa, 0xf1, 0xc9, - 0xf1, 0x59, 0x19, 0x9d, 0x9c, 0x95, 0xd1, 0x3f, 0x67, 0x65, 0xf4, 0xfd, 0x79, 0xb9, 0x70, 0x72, - 0x5e, 0x2e, 0xfc, 0x75, 0x5e, 0x2e, 0x7c, 0x51, 0xb7, 0x1d, 0xde, 0xd9, 0x37, 0x35, 0x8b, 0xed, - 0x0e, 0xf2, 0x58, 0x1d, 0xe2, 0xb8, 0x17, 0x49, 0x0f, 0xe2, 0x69, 0xf9, 0xa1, 0x47, 0x7d, 0xb3, - 0x28, 0x7e, 0x96, 0x3f, 0xfd, 0x3f, 0x00, 0x00, 0xff, 0xff, 0xb1, 0x05, 0x1d, 0xba, 0x7e, 0x0c, - 0x00, 0x00, + // 939 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xdf, 0x6f, 0xdb, 0x54, + 0x14, 0xee, 0x2d, 0xa5, 0x12, 0x27, 0xe9, 0x26, 0x5d, 0x36, 0xd6, 0x7a, 0x6b, 0xb6, 0x3a, 0x6b, + 0xd3, 0x0d, 0x61, 0x37, 0x99, 0x34, 0x01, 0x03, 0xc6, 0xb2, 0xc1, 0xa2, 0x41, 0xa5, 0x60, 0x4d, + 0x20, 0xf1, 0x12, 0x5d, 0x3b, 0x57, 0x8e, 0x69, 0xea, 0xeb, 0xc5, 0xb7, 0xa6, 0x15, 0xda, 0x0b, + 0x0f, 0x48, 0x3c, 0x20, 0x21, 0xf8, 0x13, 0x78, 0xe4, 0x89, 0x67, 0x5e, 0x91, 0xd8, 0xe3, 0x24, + 0x24, 0x84, 0x84, 0x34, 0xa1, 0x16, 0x89, 0x7f, 0x03, 0xe5, 0xfa, 0x26, 0xb6, 0x13, 0xdb, 0x71, + 0xa3, 0xed, 0xad, 0xcd, 0x3d, 0xdf, 0x39, 0xdf, 0x77, 0x7e, 0xe4, 0x6b, 0x61, 0xc3, 0x24, 0xe6, + 0x51, 0x9f, 0xb9, 0xba, 0xc9, 0x2d, 0x9f, 0x93, 0x3d, 0xc7, 0xb5, 0xf5, 0xa0, 0xae, 0x3f, 0x3a, + 0xa0, 0x83, 0x23, 0xcd, 0x1b, 0x30, 0xce, 0xf0, 0x79, 0x19, 0xa2, 0x45, 0x21, 0x5a, 0x50, 0x57, + 0xce, 0xd9, 0xcc, 0x66, 0x22, 0x42, 0x1f, 0xfe, 0x14, 0x06, 0x2b, 0x97, 0x6c, 0xc6, 0xec, 0x3e, + 0xd5, 0x89, 0xe7, 0xe8, 0xc4, 0x75, 0x19, 0x27, 0xdc, 0x61, 0xae, 0x2f, 0x5f, 0xaf, 0x5b, 0xcc, + 0xdf, 0x67, 0xbe, 0x6e, 0x12, 0x9f, 0x86, 0x35, 0xf4, 0xa0, 0x6e, 0x52, 0x4e, 0xea, 0xba, 0x47, + 0x6c, 0xc7, 0x15, 0xc1, 0x32, 0x56, 0x4d, 0x67, 0xe6, 0x91, 0x01, 0xd9, 0x1f, 0xe5, 0xdb, 0x4a, + 0x8f, 0x89, 0x11, 0x15, 0x71, 0xea, 0x39, 0xc0, 0x9f, 0x0c, 0xab, 0xb5, 0x05, 0xd8, 0xa0, 0x8f, + 0x0e, 0xa8, 0xcf, 0x55, 0x03, 0x5e, 0x4d, 0x7c, 0xea, 0x7b, 0xcc, 0xf5, 0x29, 0xbe, 0x05, 0xcb, + 0x61, 0x91, 0x55, 0x74, 0x05, 0x6d, 0x97, 0x1a, 0xeb, 0x5a, 0x6a, 0x03, 0xb4, 0x10, 0xd6, 0x5c, + 0x7a, 0xf2, 0xec, 0xf2, 0x82, 0x21, 0x21, 0xaa, 0x05, 0x6b, 0x22, 0x67, 0xf3, 0xe1, 0xdd, 0x4f, + 0x49, 0xdf, 0xe9, 0x12, 0xce, 0x06, 0xa3, 0x82, 0xf8, 0x43, 0x80, 0x48, 0xa6, 0xcc, 0xbe, 0xa5, + 0x85, 0x3d, 0xd1, 0x86, 0x3d, 0xd1, 0xc2, 0xbe, 0xcb, 0x9e, 0x68, 0x6d, 0x62, 0x53, 0x89, 0x35, + 0x62, 0x48, 0xf5, 0x17, 0x04, 0x4a, 0x5a, 0x15, 0x29, 0xe0, 0x01, 0x9c, 0x31, 0xb9, 0xd5, 0x09, + 0xc6, 0x2f, 0xab, 0xe8, 0xca, 0x4b, 0xdb, 0xa5, 0x46, 0x35, 0x43, 0x48, 0x3c, 0x8b, 0xb1, 0x62, + 0x72, 0x2b, 0xca, 0x89, 0xef, 0x27, 0x28, 0x2f, 0x0a, 0xca, 0xb5, 0x99, 0x94, 0x43, 0x22, 0x09, + 0xce, 0xb7, 0x61, 0x75, 0x8a, 0xf2, 0xa8, 0x2f, 0x55, 0x38, 0x13, 0x90, 0x7e, 0x67, 0x48, 0xda, + 0xdb, 0xeb, 0xf4, 0xe8, 0xa1, 0xe8, 0xcd, 0x2b, 0x46, 0x29, 0x20, 0xfd, 0x26, 0xb7, 0xda, 0x7b, + 0x2d, 0x7a, 0xa8, 0xd2, 0x94, 0xce, 0x8e, 0x25, 0xb7, 0x60, 0x25, 0x21, 0x59, 0x36, 0xb7, 0x90, + 0xe2, 0x72, 0x5c, 0xb1, 0x5a, 0x85, 0x8d, 0x70, 0x29, 0xa8, 0xdb, 0x75, 0x5c, 0xbb, 0xf9, 0xf0, + 0xee, 0x3d, 0xda, 0xa7, 0x76, 0xb8, 0xc6, 0xa3, 0xcd, 0xf1, 0x41, 0xcd, 0x0b, 0x92, 0xa4, 0x76, + 0xe1, 0xec, 0x90, 0x54, 0x37, 0x7a, 0x92, 0x83, 0xb8, 0x9a, 0x4d, 0x2b, 0xca, 0x63, 0x0c, 0x87, + 0x18, 0x4b, 0xab, 0x76, 0x61, 0x73, 0xaa, 0x01, 0x6d, 0xf6, 0x25, 0x1d, 0xdc, 0xe1, 0x2d, 0xea, + 0xd8, 0x3d, 0x7e, 0x9a, 0x76, 0xe2, 0xd7, 0x60, 0xb9, 0x27, 0x50, 0x62, 0xa8, 0x4b, 0x86, 0xfc, + 0x4d, 0xfd, 0x08, 0xb6, 0x66, 0x55, 0x91, 0xf2, 0x36, 0xa0, 0x1c, 0x30, 0xee, 0xb8, 0x76, 0xc7, + 0x1b, 0xbe, 0x8b, 0x22, 0x4b, 0x46, 0x29, 0xfc, 0x4c, 0x40, 0xd4, 0x6f, 0x11, 0xd4, 0x44, 0xb6, + 0x3b, 0x16, 0x77, 0x02, 0x9a, 0x58, 0xd7, 0x49, 0xd6, 0x11, 0x21, 0x14, 0x27, 0x34, 0x71, 0x34, + 0x8b, 0x73, 0x1f, 0xcd, 0xef, 0x08, 0xb6, 0x67, 0x73, 0x91, 0xda, 0x8c, 0x8c, 0x13, 0x7a, 0xbd, + 0xc0, 0x42, 0x7d, 0xe6, 0xf0, 0xde, 0x2e, 0xe5, 0xe4, 0x85, 0x9d, 0xd2, 0x3a, 0x5c, 0x8c, 0x84, + 0x10, 0x4e, 0xbb, 0x89, 0x46, 0xaa, 0x37, 0xe1, 0x52, 0xfa, 0xb3, 0xd4, 0x96, 0xd1, 0x68, 0xf5, + 0x07, 0x04, 0xd5, 0xa9, 0xd1, 0x4f, 0x2f, 0x7f, 0xb1, 0xf5, 0x7a, 0x5e, 0x53, 0xfb, 0x1b, 0xc1, + 0xd5, 0x7c, 0x52, 0x52, 0xd5, 0x17, 0xb0, 0x16, 0x3b, 0x36, 0x36, 0x48, 0x39, 0x3b, 0x6d, 0xe6, + 0xd9, 0x25, 0x53, 0x5f, 0x88, 0x0e, 0x30, 0xf1, 0xf0, 0xdc, 0x26, 0xd9, 0xf8, 0xae, 0x0c, 0x2f, + 0x0b, 0x75, 0xf8, 0x1b, 0x04, 0xcb, 0xa1, 0xa1, 0xe0, 0x6b, 0x19, 0x34, 0xa7, 0x1d, 0x4c, 0xb9, + 0x5e, 0x24, 0x34, 0xac, 0xab, 0x6e, 0x7e, 0xfd, 0xc7, 0xbf, 0x3f, 0x2e, 0x5e, 0xc6, 0xeb, 0x7a, + 0x9e, 0xb1, 0xe2, 0x9f, 0x10, 0xac, 0x24, 0x6e, 0x03, 0xef, 0xe4, 0x15, 0x49, 0xf3, 0x39, 0xa5, + 0x7e, 0x0a, 0x84, 0x64, 0xf7, 0x86, 0x60, 0x57, 0xc3, 0x9b, 0x7a, 0xa6, 0xa5, 0xc7, 0xae, 0x11, + 0xff, 0x8a, 0xa0, 0x1c, 0x4f, 0x84, 0xf5, 0xa2, 0x25, 0x47, 0x1c, 0x77, 0x8a, 0x03, 0x24, 0xc5, + 0x96, 0xa0, 0xd8, 0xc4, 0xef, 0x17, 0xa2, 0xa8, 0x7f, 0x95, 0x3c, 0x92, 0xc7, 0xfa, 0xf8, 0x0d, + 0xff, 0x86, 0xe0, 0x7c, 0xaa, 0x75, 0xe0, 0x37, 0x73, 0x07, 0x9a, 0x63, 0x49, 0xca, 0x5b, 0x73, + 0x20, 0xa5, 0xb0, 0x9b, 0x42, 0xd8, 0x0e, 0xd6, 0xb2, 0x36, 0x23, 0x44, 0x77, 0x26, 0xcc, 0x0c, + 0xff, 0x89, 0xe0, 0x62, 0xce, 0x97, 0x29, 0x7e, 0x2f, 0x8f, 0xd2, 0x6c, 0x47, 0x50, 0x6e, 0xcf, + 0x8d, 0x2f, 0x28, 0x6c, 0x72, 0x62, 0xe1, 0x17, 0xe1, 0x63, 0xfc, 0x1f, 0x82, 0xb5, 0x4c, 0xff, + 0xc3, 0xef, 0x14, 0xdd, 0x9c, 0x34, 0x73, 0x56, 0xde, 0x9d, 0x13, 0x2d, 0x25, 0xed, 0x0a, 0x49, + 0xf7, 0xf1, 0x07, 0x73, 0x2e, 0xa1, 0xb0, 0xea, 0x48, 0xe9, 0xcf, 0x08, 0xce, 0x4e, 0xf8, 0x04, + 0x6e, 0xcc, 0x6c, 0xfb, 0x94, 0xe7, 0x28, 0x37, 0x4e, 0x85, 0x91, 0x5a, 0x74, 0xa1, 0xe5, 0x1a, + 0xae, 0x65, 0x68, 0x21, 0x23, 0x5c, 0x47, 0xfe, 0x29, 0xf0, 0x0c, 0xc1, 0x85, 0x0c, 0x1f, 0xc0, + 0x6f, 0x17, 0xed, 0x6b, 0xca, 0xed, 0xdc, 0x9a, 0x0b, 0x2b, 0x55, 0x3c, 0x10, 0x2a, 0xee, 0xe1, + 0xe6, 0x9c, 0x13, 0x89, 0x5d, 0x54, 0xf3, 0xe3, 0x27, 0xc7, 0x15, 0xf4, 0xf4, 0xb8, 0x82, 0xfe, + 0x39, 0xae, 0xa0, 0xef, 0x4f, 0x2a, 0x0b, 0x4f, 0x4f, 0x2a, 0x0b, 0x7f, 0x9d, 0x54, 0x16, 0x3e, + 0x6f, 0xd8, 0x0e, 0xef, 0x1d, 0x98, 0x9a, 0xc5, 0xf6, 0x47, 0x75, 0xac, 0x1e, 0x71, 0xdc, 0x71, + 0xd1, 0xc3, 0x78, 0x59, 0x7e, 0xe4, 0x51, 0xdf, 0x5c, 0x16, 0xff, 0xfc, 0xdc, 0xf8, 0x3f, 0x00, + 0x00, 0xff, 0xff, 0x36, 0x6b, 0x31, 0xed, 0xe4, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -815,6 +912,8 @@ type QueryClient interface { Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) // BTCValidators queries all BTC validators BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) + // BTCValidator info about one validator + BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) // PendingBTCDelegations queries all pending BTC delegations PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. @@ -854,6 +953,15 @@ func (c *queryClient) BTCValidators(ctx context.Context, in *QueryBTCValidatorsR return out, nil } +func (c *queryClient) BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) { + out := new(QueryBTCValidatorResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) { out := new(QueryPendingBTCDelegationsResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/PendingBTCDelegations", in, out, opts...) @@ -905,6 +1013,8 @@ type QueryServer interface { Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) // BTCValidators queries all BTC validators BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) + // BTCValidator info about one validator + BTCValidator(context.Context, *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) // PendingBTCDelegations queries all pending BTC delegations PendingBTCDelegations(context.Context, *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. @@ -928,6 +1038,9 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidators not implemented") } +func (*UnimplementedQueryServer) BTCValidator(ctx context.Context, req *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidator not implemented") +} func (*UnimplementedQueryServer) PendingBTCDelegations(ctx context.Context, req *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PendingBTCDelegations not implemented") } @@ -984,6 +1097,24 @@ func _Query_BTCValidators_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Query_BTCValidator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidator(ctx, req.(*QueryBTCValidatorRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_PendingBTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryPendingBTCDelegationsRequest) if err := dec(in); err != nil { @@ -1086,6 +1217,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BTCValidators", Handler: _Query_BTCValidators_Handler, }, + { + MethodName: "BTCValidator", + Handler: _Query_BTCValidator_Handler, + }, { MethodName: "PendingBTCDelegations", Handler: _Query_PendingBTCDelegations_Handler, @@ -1251,6 +1386,71 @@ func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *QueryBTCValidatorRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BtcValidator != nil { + { + size, err := m.BtcValidator.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *QueryPendingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1668,6 +1868,32 @@ func (m *QueryBTCValidatorsResponse) Size() (n int) { return n } +func (m *QueryBTCValidatorRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValBtcPkHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcValidator != nil { + l = m.BtcValidator.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func (m *QueryPendingBTCDelegationsRequest) Size() (n int) { if m == nil { return 0 @@ -2157,6 +2383,174 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCValidatorRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcValidator", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BtcValidator == nil { + m.BtcValidator = &BTCValidator{} + } + if err := m.BtcValidator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryPendingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index e24feb212..93af02ba0 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -87,6 +87,60 @@ func local_request_Query_BTCValidators_0(ctx context.Context, marshaler runtime. } +func request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := client.BTCValidator(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := server.BTCValidator(ctx, &protoReq) + return msg, metadata, err + +} + func request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryPendingBTCDelegationsRequest var metadata runtime.ServerMetadata @@ -395,6 +449,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCValidator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidator_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -591,6 +668,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCValidator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidator_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -699,6 +796,8 @@ var ( pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "validator"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_PendingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "pending_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -715,6 +814,8 @@ var ( forward_Query_BTCValidators_0 = runtime.ForwardResponseMessage + forward_Query_BTCValidator_0 = runtime.ForwardResponseMessage + forward_Query_PendingBTCDelegations_0 = runtime.ForwardResponseMessage forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage From ea4563252e97d471028643a740bad520a965ab34 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 4 Aug 2023 17:35:07 +0300 Subject: [PATCH 056/202] fix: btcstaking: Get the BTC tip directly instead of from index (#56) --- x/btcstaking/keeper/btc_validators.go | 7 ++++--- x/btcstaking/keeper/voting_power_table_test.go | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index b440451dd..5efc1fab2 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -44,10 +44,11 @@ func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { return types.ErrBTCValAlreadySlashed } btcVal.SlashedBabylonHeight = uint64(ctx.BlockHeight()) - btcVal.SlashedBtcHeight, err = k.GetCurrentBTCHeight(ctx) - if err != nil { - panic(fmt.Errorf("failed to get current BTC height: %w", err)) + btcTip := k.btclcKeeper.GetTipInfo(ctx) + if btcTip == nil { + panic(fmt.Errorf("failed to get current BTC tip")) } + btcVal.SlashedBtcHeight = btcTip.Height k.SetBTCValidator(ctx, btcVal) return nil } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index c33722d35..3f9c330f8 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -113,6 +113,8 @@ func FuzzVotingPowerTable(f *testing.F) { // slash a random BTC validator slashedIdx := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) slashedVal := btcVals[slashedIdx] + // This will be called to get the slashed height + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) err = keeper.SlashBTCValidator(ctx, slashedVal.BtcPk.MustMarshal()) require.NoError(t, err) // move to later Babylon height and 2nd BTC height From f03712d410b2da30ac16424bb48211d103d6e23e Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 4 Aug 2023 17:40:08 +0300 Subject: [PATCH 057/202] fix: Flaky test asks for random number up to 0 (#55) --- x/finality/keeper/grpc_query_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index f7c4519a4..28998e37c 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -27,7 +27,7 @@ func FuzzListPublicRandomness(f *testing.F) { valBTCPK, err := datagen.GenRandomBIP340PubKey(r) require.NoError(t, err) startHeight := datagen.RandomInt(r, 100) - numPubRand := datagen.RandomInt(r, 1000) + 1 + numPubRand := datagen.RandomInt(r, 1000) + 2 _, prList, err := datagen.GenRandomPubRandList(r, numPubRand) require.NoError(t, err) keeper.SetPubRandList(ctx, valBTCPK, startHeight, prList) From 6ae786bb092b2abc6d17eca8b4acda19a61146e2 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 7 Aug 2023 11:08:56 +0200 Subject: [PATCH 058/202] Fix off by one error in min fee (#57) --- btcstaking/staking.go | 2 +- btcstaking/staking_test.go | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 76340a9b0..63c207011 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -726,7 +726,7 @@ func CheckTransactions( return nil, fmt.Errorf("slashing transaction must not spend more than staking transaction") } - if stakingOutput.Value-slashingTx.TxOut[0].Value <= slashingTxMinFee { + if stakingOutput.Value-slashingTx.TxOut[0].Value < slashingTxMinFee { return nil, fmt.Errorf("slashing transaction fee must be larger than %d", slashingTxMinFee) } diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 0bffc9f20..2ffb05e25 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -93,9 +93,32 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { } } - fee := int64(r.Intn(1000) + minFee) - + // Always check case with min fee slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( + stakingTx, + uint32(stakingOutputIdx), + slashingAddress, + int64(minFee), + script, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + _, err = btcstaking.CheckTransactions( + slashingTx, + stakingTx, + int64(minFee), + slashingAddress, + script, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + // Check case with some random fee + fee := int64(r.Intn(1000) + minFee) + slashingTx, err = btcstaking.BuildSlashingTxFromStakingTxStrict( stakingTx, uint32(stakingOutputIdx), slashingAddress, From 19d0fb78d5839b665467af0e7b328ab1faf862c7 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 7 Aug 2023 19:36:38 +0800 Subject: [PATCH 059/202] btcstaking: API for querying voting power at current height (#54) --- proto/babylon/btcstaking/v1/query.proto | 23 + x/btcstaking/keeper/grpc_query.go | 30 ++ x/btcstaking/keeper/grpc_query_test.go | 45 ++ x/btcstaking/keeper/voting_power_table.go | 8 + x/btcstaking/types/query.pb.go | 542 +++++++++++++++++++--- x/btcstaking/types/query.pb.gw.go | 101 ++++ 6 files changed, 683 insertions(+), 66 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 8bca2917b..4c05f7c5c 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -41,6 +41,11 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; } + // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height + rpc BTCValidatorCurrentPower(QueryBTCValidatorCurrentPowerRequest) returns (QueryBTCValidatorCurrentPowerResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power"; + } + // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power rpc ActivatedHeight(QueryActivatedHeightRequest) returns (QueryActivatedHeightResponse) { @@ -122,6 +127,24 @@ message QueryBTCValidatorPowerAtHeightResponse { uint64 voting_power = 1; } +// QueryBTCValidatorCurrentPowerRequest is the request type for the +// Query/BTCValidatorCurrentPower RPC method. +message QueryBTCValidatorCurrentPowerRequest { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + string val_btc_pk_hex = 1; +} + +// QueryBTCValidatorCurrentPowerResponse is the response type for the +// Query/BTCValidatorCurrentPower RPC method. +message QueryBTCValidatorCurrentPowerResponse { + // height is the current height + uint64 height = 1; + // voting_power is the voting power of the BTC validator + uint64 voting_power = 2; +} + // QueryActiveBTCValidatorsAtHeightRequest is the request type for the // Query/ActiveBTCValidatorsAtHeight RPC method. message QueryActiveBTCValidatorsAtHeightRequest { diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 9e1c2c1a8..9fdad6d41 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -135,6 +135,36 @@ func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryB return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil } +// BTCValidatorCurrentPower returns the voting power of the specified validator +// at the current height +func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBTCValidatorCurrentPowerRequest) (*types.QueryBTCValidatorCurrentPowerResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + power := uint64(0) + curHeight := uint64(sdkCtx.BlockHeight()) + + // if voting power table is recorded at the current height, use this voting power + if k.HasVotingPowerTable(sdkCtx, curHeight) { + power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) + } else { + // NOTE: it's possible that the voting power is not recorded at the current height, + // e.g., `EndBlock` is not reached yet + // in this case, we use the last height + curHeight -= 1 + power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) + } + + return &types.QueryBTCValidatorCurrentPowerResponse{Height: curHeight, VotingPower: power}, nil +} + // ActiveBTCValidatorsAtHeight returns the active BTC validators at the provided height func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.QueryActiveBTCValidatorsAtHeightRequest) (*types.QueryActiveBTCValidatorsAtHeightResponse, error) { if req == nil { diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 149d2677f..9ebf82df1 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -253,6 +253,51 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { }) } +func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) + + // random BTC validator + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + // add this BTC validator + keeper.SetBTCValidator(ctx, btcVal) + // set random voting power at random height + randomHeight := datagen.RandomInt(r, 100) + 1 + ctx = ctx.WithBlockHeight(int64(randomHeight)) + randomPower := datagen.RandomInt(r, 100) + 1 + keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) + + // assert voting power at current height + req := &types.QueryBTCValidatorCurrentPowerRequest{ + ValBtcPkHex: btcVal.BtcPk.MarshalHex(), + } + resp, err := keeper.BTCValidatorCurrentPower(ctx, req) + require.NoError(t, err) + require.Equal(t, randomHeight, resp.Height) + require.Equal(t, randomPower, resp.VotingPower) + + // if height increments but voting power hasn't recorded yet, then + // we need to return the height and voting power at the last height + ctx = ctx.WithBlockHeight(int64(randomHeight + 1)) + resp, err = keeper.BTCValidatorCurrentPower(ctx, req) + require.NoError(t, err) + require.Equal(t, randomHeight, resp.Height) + require.Equal(t, randomPower, resp.VotingPower) + + // but no more + ctx = ctx.WithBlockHeight(int64(randomHeight + 2)) + resp, err = keeper.BTCValidatorCurrentPower(ctx, req) + require.NoError(t, err) + require.Equal(t, randomHeight+1, resp.Height) + require.Equal(t, uint64(0), resp.VotingPower) + }) +} + func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 1544513d6..631cdf187 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -79,6 +79,14 @@ func (k Keeper) GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) return sdk.BigEndianToUint64(powerBytes) } +// HasVotingPowerTable checks if the voting power table exists at a given height +func (k Keeper) HasVotingPowerTable(ctx sdk.Context, height uint64) bool { + store := k.votingPowerStore(ctx, height) + iter := store.Iterator(nil, nil) + defer iter.Close() + return iter.Valid() +} + // GetVotingPowerTable gets the voting power table, i.e., validator set at a given height func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 { store := k.votingPowerStore(ctx, height) diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 3cf44878d..ae21dda38 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -500,6 +500,111 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { return 0 } +// QueryBTCValidatorCurrentPowerRequest is the request type for the +// Query/BTCValidatorCurrentPower RPC method. +type QueryBTCValidatorCurrentPowerRequest struct { + // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` +} + +func (m *QueryBTCValidatorCurrentPowerRequest) Reset() { *m = QueryBTCValidatorCurrentPowerRequest{} } +func (m *QueryBTCValidatorCurrentPowerRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorCurrentPowerRequest) ProtoMessage() {} +func (*QueryBTCValidatorCurrentPowerRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{10} +} +func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.Merge(m, src) +} +func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorCurrentPowerRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest proto.InternalMessageInfo + +func (m *QueryBTCValidatorCurrentPowerRequest) GetValBtcPkHex() string { + if m != nil { + return m.ValBtcPkHex + } + return "" +} + +// QueryBTCValidatorCurrentPowerResponse is the response type for the +// Query/BTCValidatorCurrentPower RPC method. +type QueryBTCValidatorCurrentPowerResponse struct { + // height is the current height + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // voting_power is the voting power of the BTC validator + VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +} + +func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCValidatorCurrentPowerResponse{} } +func (m *QueryBTCValidatorCurrentPowerResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCValidatorCurrentPowerResponse) ProtoMessage() {} +func (*QueryBTCValidatorCurrentPowerResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{11} +} +func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.Merge(m, src) +} +func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCValidatorCurrentPowerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse proto.InternalMessageInfo + +func (m *QueryBTCValidatorCurrentPowerResponse) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *QueryBTCValidatorCurrentPowerResponse) GetVotingPower() uint64 { + if m != nil { + return m.VotingPower + } + return 0 +} + // QueryActiveBTCValidatorsAtHeightRequest is the request type for the // Query/ActiveBTCValidatorsAtHeight RPC method. type QueryActiveBTCValidatorsAtHeightRequest struct { @@ -515,7 +620,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{10} + return fileDescriptor_74d49d26f7429697, []int{12} } func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -573,7 +678,7 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{11} + return fileDescriptor_74d49d26f7429697, []int{13} } func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -624,7 +729,7 @@ func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeigh func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightRequest) ProtoMessage() {} func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{12} + return fileDescriptor_74d49d26f7429697, []int{14} } func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -662,7 +767,7 @@ func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeig func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightResponse) ProtoMessage() {} func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{13} + return fileDescriptor_74d49d26f7429697, []int{15} } func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -713,7 +818,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCVal func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{14} + return fileDescriptor_74d49d26f7429697, []int{16} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -769,7 +874,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{15} + return fileDescriptor_74d49d26f7429697, []int{17} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -823,6 +928,8 @@ func init() { proto.RegisterType((*QueryPendingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") + proto.RegisterType((*QueryBTCValidatorCurrentPowerRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerRequest") + proto.RegisterType((*QueryBTCValidatorCurrentPowerResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerResponse") proto.RegisterType((*QueryActiveBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightRequest") proto.RegisterType((*QueryActiveBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightResponse") proto.RegisterType((*QueryActivatedHeightRequest)(nil), "babylon.btcstaking.v1.QueryActivatedHeightRequest") @@ -834,66 +941,70 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 939 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xdf, 0x6f, 0xdb, 0x54, - 0x14, 0xee, 0x2d, 0xa5, 0x12, 0x27, 0xe9, 0x26, 0x5d, 0x36, 0xd6, 0x7a, 0x6b, 0xb6, 0x3a, 0x6b, - 0xd3, 0x0d, 0x61, 0x37, 0x99, 0x34, 0x01, 0x03, 0xc6, 0xb2, 0xc1, 0xa2, 0x41, 0xa5, 0x60, 0x4d, - 0x20, 0xf1, 0x12, 0x5d, 0x3b, 0x57, 0x8e, 0x69, 0xea, 0xeb, 0xc5, 0xb7, 0xa6, 0x15, 0xda, 0x0b, - 0x0f, 0x48, 0x3c, 0x20, 0x21, 0xf8, 0x13, 0x78, 0xe4, 0x89, 0x67, 0x5e, 0x91, 0xd8, 0xe3, 0x24, - 0x24, 0x84, 0x84, 0x34, 0xa1, 0x16, 0x89, 0x7f, 0x03, 0xe5, 0xfa, 0x26, 0xb6, 0x13, 0xdb, 0x71, - 0xa3, 0xed, 0xad, 0xcd, 0x3d, 0xdf, 0x39, 0xdf, 0x77, 0x7e, 0xe4, 0x6b, 0x61, 0xc3, 0x24, 0xe6, - 0x51, 0x9f, 0xb9, 0xba, 0xc9, 0x2d, 0x9f, 0x93, 0x3d, 0xc7, 0xb5, 0xf5, 0xa0, 0xae, 0x3f, 0x3a, - 0xa0, 0x83, 0x23, 0xcd, 0x1b, 0x30, 0xce, 0xf0, 0x79, 0x19, 0xa2, 0x45, 0x21, 0x5a, 0x50, 0x57, - 0xce, 0xd9, 0xcc, 0x66, 0x22, 0x42, 0x1f, 0xfe, 0x14, 0x06, 0x2b, 0x97, 0x6c, 0xc6, 0xec, 0x3e, - 0xd5, 0x89, 0xe7, 0xe8, 0xc4, 0x75, 0x19, 0x27, 0xdc, 0x61, 0xae, 0x2f, 0x5f, 0xaf, 0x5b, 0xcc, - 0xdf, 0x67, 0xbe, 0x6e, 0x12, 0x9f, 0x86, 0x35, 0xf4, 0xa0, 0x6e, 0x52, 0x4e, 0xea, 0xba, 0x47, - 0x6c, 0xc7, 0x15, 0xc1, 0x32, 0x56, 0x4d, 0x67, 0xe6, 0x91, 0x01, 0xd9, 0x1f, 0xe5, 0xdb, 0x4a, - 0x8f, 0x89, 0x11, 0x15, 0x71, 0xea, 0x39, 0xc0, 0x9f, 0x0c, 0xab, 0xb5, 0x05, 0xd8, 0xa0, 0x8f, - 0x0e, 0xa8, 0xcf, 0x55, 0x03, 0x5e, 0x4d, 0x7c, 0xea, 0x7b, 0xcc, 0xf5, 0x29, 0xbe, 0x05, 0xcb, - 0x61, 0x91, 0x55, 0x74, 0x05, 0x6d, 0x97, 0x1a, 0xeb, 0x5a, 0x6a, 0x03, 0xb4, 0x10, 0xd6, 0x5c, - 0x7a, 0xf2, 0xec, 0xf2, 0x82, 0x21, 0x21, 0xaa, 0x05, 0x6b, 0x22, 0x67, 0xf3, 0xe1, 0xdd, 0x4f, - 0x49, 0xdf, 0xe9, 0x12, 0xce, 0x06, 0xa3, 0x82, 0xf8, 0x43, 0x80, 0x48, 0xa6, 0xcc, 0xbe, 0xa5, - 0x85, 0x3d, 0xd1, 0x86, 0x3d, 0xd1, 0xc2, 0xbe, 0xcb, 0x9e, 0x68, 0x6d, 0x62, 0x53, 0x89, 0x35, - 0x62, 0x48, 0xf5, 0x17, 0x04, 0x4a, 0x5a, 0x15, 0x29, 0xe0, 0x01, 0x9c, 0x31, 0xb9, 0xd5, 0x09, - 0xc6, 0x2f, 0xab, 0xe8, 0xca, 0x4b, 0xdb, 0xa5, 0x46, 0x35, 0x43, 0x48, 0x3c, 0x8b, 0xb1, 0x62, - 0x72, 0x2b, 0xca, 0x89, 0xef, 0x27, 0x28, 0x2f, 0x0a, 0xca, 0xb5, 0x99, 0x94, 0x43, 0x22, 0x09, - 0xce, 0xb7, 0x61, 0x75, 0x8a, 0xf2, 0xa8, 0x2f, 0x55, 0x38, 0x13, 0x90, 0x7e, 0x67, 0x48, 0xda, - 0xdb, 0xeb, 0xf4, 0xe8, 0xa1, 0xe8, 0xcd, 0x2b, 0x46, 0x29, 0x20, 0xfd, 0x26, 0xb7, 0xda, 0x7b, - 0x2d, 0x7a, 0xa8, 0xd2, 0x94, 0xce, 0x8e, 0x25, 0xb7, 0x60, 0x25, 0x21, 0x59, 0x36, 0xb7, 0x90, - 0xe2, 0x72, 0x5c, 0xb1, 0x5a, 0x85, 0x8d, 0x70, 0x29, 0xa8, 0xdb, 0x75, 0x5c, 0xbb, 0xf9, 0xf0, - 0xee, 0x3d, 0xda, 0xa7, 0x76, 0xb8, 0xc6, 0xa3, 0xcd, 0xf1, 0x41, 0xcd, 0x0b, 0x92, 0xa4, 0x76, - 0xe1, 0xec, 0x90, 0x54, 0x37, 0x7a, 0x92, 0x83, 0xb8, 0x9a, 0x4d, 0x2b, 0xca, 0x63, 0x0c, 0x87, - 0x18, 0x4b, 0xab, 0x76, 0x61, 0x73, 0xaa, 0x01, 0x6d, 0xf6, 0x25, 0x1d, 0xdc, 0xe1, 0x2d, 0xea, - 0xd8, 0x3d, 0x7e, 0x9a, 0x76, 0xe2, 0xd7, 0x60, 0xb9, 0x27, 0x50, 0x62, 0xa8, 0x4b, 0x86, 0xfc, - 0x4d, 0xfd, 0x08, 0xb6, 0x66, 0x55, 0x91, 0xf2, 0x36, 0xa0, 0x1c, 0x30, 0xee, 0xb8, 0x76, 0xc7, - 0x1b, 0xbe, 0x8b, 0x22, 0x4b, 0x46, 0x29, 0xfc, 0x4c, 0x40, 0xd4, 0x6f, 0x11, 0xd4, 0x44, 0xb6, - 0x3b, 0x16, 0x77, 0x02, 0x9a, 0x58, 0xd7, 0x49, 0xd6, 0x11, 0x21, 0x14, 0x27, 0x34, 0x71, 0x34, - 0x8b, 0x73, 0x1f, 0xcd, 0xef, 0x08, 0xb6, 0x67, 0x73, 0x91, 0xda, 0x8c, 0x8c, 0x13, 0x7a, 0xbd, - 0xc0, 0x42, 0x7d, 0xe6, 0xf0, 0xde, 0x2e, 0xe5, 0xe4, 0x85, 0x9d, 0xd2, 0x3a, 0x5c, 0x8c, 0x84, - 0x10, 0x4e, 0xbb, 0x89, 0x46, 0xaa, 0x37, 0xe1, 0x52, 0xfa, 0xb3, 0xd4, 0x96, 0xd1, 0x68, 0xf5, - 0x07, 0x04, 0xd5, 0xa9, 0xd1, 0x4f, 0x2f, 0x7f, 0xb1, 0xf5, 0x7a, 0x5e, 0x53, 0xfb, 0x1b, 0xc1, - 0xd5, 0x7c, 0x52, 0x52, 0xd5, 0x17, 0xb0, 0x16, 0x3b, 0x36, 0x36, 0x48, 0x39, 0x3b, 0x6d, 0xe6, - 0xd9, 0x25, 0x53, 0x5f, 0x88, 0x0e, 0x30, 0xf1, 0xf0, 0xdc, 0x26, 0xd9, 0xf8, 0xae, 0x0c, 0x2f, - 0x0b, 0x75, 0xf8, 0x1b, 0x04, 0xcb, 0xa1, 0xa1, 0xe0, 0x6b, 0x19, 0x34, 0xa7, 0x1d, 0x4c, 0xb9, - 0x5e, 0x24, 0x34, 0xac, 0xab, 0x6e, 0x7e, 0xfd, 0xc7, 0xbf, 0x3f, 0x2e, 0x5e, 0xc6, 0xeb, 0x7a, - 0x9e, 0xb1, 0xe2, 0x9f, 0x10, 0xac, 0x24, 0x6e, 0x03, 0xef, 0xe4, 0x15, 0x49, 0xf3, 0x39, 0xa5, - 0x7e, 0x0a, 0x84, 0x64, 0xf7, 0x86, 0x60, 0x57, 0xc3, 0x9b, 0x7a, 0xa6, 0xa5, 0xc7, 0xae, 0x11, - 0xff, 0x8a, 0xa0, 0x1c, 0x4f, 0x84, 0xf5, 0xa2, 0x25, 0x47, 0x1c, 0x77, 0x8a, 0x03, 0x24, 0xc5, - 0x96, 0xa0, 0xd8, 0xc4, 0xef, 0x17, 0xa2, 0xa8, 0x7f, 0x95, 0x3c, 0x92, 0xc7, 0xfa, 0xf8, 0x0d, - 0xff, 0x86, 0xe0, 0x7c, 0xaa, 0x75, 0xe0, 0x37, 0x73, 0x07, 0x9a, 0x63, 0x49, 0xca, 0x5b, 0x73, - 0x20, 0xa5, 0xb0, 0x9b, 0x42, 0xd8, 0x0e, 0xd6, 0xb2, 0x36, 0x23, 0x44, 0x77, 0x26, 0xcc, 0x0c, - 0xff, 0x89, 0xe0, 0x62, 0xce, 0x97, 0x29, 0x7e, 0x2f, 0x8f, 0xd2, 0x6c, 0x47, 0x50, 0x6e, 0xcf, - 0x8d, 0x2f, 0x28, 0x6c, 0x72, 0x62, 0xe1, 0x17, 0xe1, 0x63, 0xfc, 0x1f, 0x82, 0xb5, 0x4c, 0xff, - 0xc3, 0xef, 0x14, 0xdd, 0x9c, 0x34, 0x73, 0x56, 0xde, 0x9d, 0x13, 0x2d, 0x25, 0xed, 0x0a, 0x49, - 0xf7, 0xf1, 0x07, 0x73, 0x2e, 0xa1, 0xb0, 0xea, 0x48, 0xe9, 0xcf, 0x08, 0xce, 0x4e, 0xf8, 0x04, - 0x6e, 0xcc, 0x6c, 0xfb, 0x94, 0xe7, 0x28, 0x37, 0x4e, 0x85, 0x91, 0x5a, 0x74, 0xa1, 0xe5, 0x1a, - 0xae, 0x65, 0x68, 0x21, 0x23, 0x5c, 0x47, 0xfe, 0x29, 0xf0, 0x0c, 0xc1, 0x85, 0x0c, 0x1f, 0xc0, - 0x6f, 0x17, 0xed, 0x6b, 0xca, 0xed, 0xdc, 0x9a, 0x0b, 0x2b, 0x55, 0x3c, 0x10, 0x2a, 0xee, 0xe1, - 0xe6, 0x9c, 0x13, 0x89, 0x5d, 0x54, 0xf3, 0xe3, 0x27, 0xc7, 0x15, 0xf4, 0xf4, 0xb8, 0x82, 0xfe, - 0x39, 0xae, 0xa0, 0xef, 0x4f, 0x2a, 0x0b, 0x4f, 0x4f, 0x2a, 0x0b, 0x7f, 0x9d, 0x54, 0x16, 0x3e, - 0x6f, 0xd8, 0x0e, 0xef, 0x1d, 0x98, 0x9a, 0xc5, 0xf6, 0x47, 0x75, 0xac, 0x1e, 0x71, 0xdc, 0x71, - 0xd1, 0xc3, 0x78, 0x59, 0x7e, 0xe4, 0x51, 0xdf, 0x5c, 0x16, 0xff, 0xfc, 0xdc, 0xf8, 0x3f, 0x00, - 0x00, 0xff, 0xff, 0x36, 0x6b, 0x31, 0xed, 0xe4, 0x0d, 0x00, 0x00, + // 998 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcf, 0x6f, 0xdc, 0x44, + 0x14, 0xce, 0x84, 0x10, 0x89, 0x97, 0xa4, 0x95, 0x86, 0x96, 0x26, 0x6e, 0xb3, 0x6d, 0x9c, 0xe6, + 0x47, 0x8b, 0xb0, 0xb3, 0x5b, 0xa9, 0x02, 0x12, 0x5a, 0xba, 0x09, 0x6d, 0xd4, 0x12, 0x29, 0x58, + 0x15, 0x48, 0x5c, 0x56, 0x63, 0xef, 0xc8, 0x6b, 0xb2, 0xf1, 0x6c, 0xd7, 0x93, 0x25, 0x11, 0xea, + 0x85, 0x03, 0x12, 0x37, 0x04, 0x7f, 0x02, 0x47, 0x4e, 0x9c, 0xb9, 0x22, 0xd1, 0x63, 0x25, 0x24, + 0x84, 0x54, 0xa9, 0x42, 0x09, 0x12, 0x07, 0xfe, 0x09, 0xb4, 0xe3, 0xd9, 0xd8, 0x8e, 0xc7, 0x5e, + 0xc7, 0xa4, 0xb7, 0xc4, 0xf3, 0xbe, 0xf7, 0xbe, 0xef, 0xcd, 0xfb, 0x31, 0x0b, 0x73, 0x36, 0xb1, + 0x0f, 0xda, 0xcc, 0x37, 0x6d, 0xee, 0x04, 0x9c, 0xec, 0x78, 0xbe, 0x6b, 0xf6, 0xaa, 0xe6, 0x93, + 0x3d, 0xda, 0x3d, 0x30, 0x3a, 0x5d, 0xc6, 0x19, 0xbe, 0x28, 0x4d, 0x8c, 0xc8, 0xc4, 0xe8, 0x55, + 0xb5, 0x0b, 0x2e, 0x73, 0x99, 0xb0, 0x30, 0xfb, 0x7f, 0x85, 0xc6, 0xda, 0x15, 0x97, 0x31, 0xb7, + 0x4d, 0x4d, 0xd2, 0xf1, 0x4c, 0xe2, 0xfb, 0x8c, 0x13, 0xee, 0x31, 0x3f, 0x90, 0xa7, 0x37, 0x1d, + 0x16, 0xec, 0xb2, 0xc0, 0xb4, 0x49, 0x40, 0xc3, 0x18, 0x66, 0xaf, 0x6a, 0x53, 0x4e, 0xaa, 0x66, + 0x87, 0xb8, 0x9e, 0x2f, 0x8c, 0xa5, 0xad, 0xae, 0x66, 0xd6, 0x21, 0x5d, 0xb2, 0x3b, 0xf0, 0xb7, + 0xa8, 0xb6, 0x89, 0x11, 0x15, 0x76, 0xfa, 0x05, 0xc0, 0x9f, 0xf4, 0xa3, 0x6d, 0x0b, 0xb0, 0x45, + 0x9f, 0xec, 0xd1, 0x80, 0xeb, 0x16, 0xbc, 0x99, 0xf8, 0x1a, 0x74, 0x98, 0x1f, 0x50, 0xbc, 0x0a, + 0xe3, 0x61, 0x90, 0x69, 0x74, 0x0d, 0x2d, 0x4f, 0xd4, 0x66, 0x0d, 0x65, 0x02, 0x8c, 0x10, 0x56, + 0x1f, 0x7b, 0xf6, 0xf2, 0xea, 0x88, 0x25, 0x21, 0xba, 0x03, 0x33, 0xc2, 0x67, 0xfd, 0xf1, 0xfa, + 0xa7, 0xa4, 0xed, 0x35, 0x09, 0x67, 0xdd, 0x41, 0x40, 0x7c, 0x1f, 0x20, 0x92, 0x29, 0xbd, 0x2f, + 0x1a, 0x61, 0x4e, 0x8c, 0x7e, 0x4e, 0x8c, 0x30, 0xef, 0x32, 0x27, 0xc6, 0x36, 0x71, 0xa9, 0xc4, + 0x5a, 0x31, 0xa4, 0xfe, 0x33, 0x02, 0x4d, 0x15, 0x45, 0x0a, 0x78, 0x08, 0xe7, 0x6c, 0xee, 0x34, + 0x7a, 0xc7, 0x27, 0xd3, 0xe8, 0xda, 0x6b, 0xcb, 0x13, 0xb5, 0xf9, 0x0c, 0x21, 0x71, 0x2f, 0xd6, + 0x94, 0xcd, 0x9d, 0xc8, 0x27, 0x7e, 0x90, 0xa0, 0x3c, 0x2a, 0x28, 0x2f, 0x0d, 0xa5, 0x1c, 0x12, + 0x49, 0x70, 0xbe, 0x0b, 0xd3, 0x29, 0xca, 0x83, 0xbc, 0xcc, 0xc3, 0xb9, 0x1e, 0x69, 0x37, 0xfa, + 0xa4, 0x3b, 0x3b, 0x8d, 0x16, 0xdd, 0x17, 0xb9, 0x79, 0xc3, 0x9a, 0xe8, 0x91, 0x76, 0x9d, 0x3b, + 0xdb, 0x3b, 0x9b, 0x74, 0x5f, 0xa7, 0x8a, 0xcc, 0x1e, 0x4b, 0xde, 0x84, 0xa9, 0x84, 0x64, 0x99, + 0xdc, 0x42, 0x8a, 0x27, 0xe3, 0x8a, 0xf5, 0x79, 0x98, 0x0b, 0x8b, 0x82, 0xfa, 0x4d, 0xcf, 0x77, + 0xeb, 0x8f, 0xd7, 0x37, 0x68, 0x9b, 0xba, 0x61, 0x19, 0x0f, 0x2a, 0x27, 0x00, 0x3d, 0xcf, 0x48, + 0x92, 0xda, 0x82, 0xf3, 0x7d, 0x52, 0xcd, 0xe8, 0x48, 0x5e, 0xc4, 0xf5, 0x6c, 0x5a, 0x91, 0x1f, + 0xab, 0x7f, 0x89, 0x31, 0xb7, 0x7a, 0x13, 0x16, 0x52, 0x09, 0xd8, 0x66, 0x5f, 0xd2, 0xee, 0x3d, + 0xbe, 0x49, 0x3d, 0xb7, 0xc5, 0x4f, 0x93, 0x4e, 0xfc, 0x16, 0x8c, 0xb7, 0x04, 0x4a, 0x5c, 0xea, + 0x98, 0x25, 0xff, 0xd3, 0x1f, 0xc1, 0xe2, 0xb0, 0x28, 0x52, 0xde, 0x1c, 0x4c, 0xf6, 0x18, 0xf7, + 0x7c, 0xb7, 0xd1, 0xe9, 0x9f, 0x8b, 0x20, 0x63, 0xd6, 0x44, 0xf8, 0x4d, 0x40, 0xf4, 0x47, 0x70, + 0x3d, 0xe5, 0x6c, 0x7d, 0xaf, 0xdb, 0xa5, 0x3e, 0x17, 0x06, 0xa7, 0x2a, 0x00, 0x5b, 0xa1, 0x3f, + 0xe9, 0x4c, 0x12, 0x8b, 0xa4, 0xa1, 0xb8, 0xb4, 0x14, 0xe1, 0xd1, 0x34, 0xe1, 0x6f, 0x11, 0x2c, + 0x89, 0x20, 0xf7, 0x1c, 0xee, 0xf5, 0x68, 0xa2, 0xbf, 0x4e, 0xa6, 0x39, 0x2b, 0xcc, 0x7d, 0x45, + 0xcb, 0x94, 0xe9, 0xf2, 0xdf, 0x10, 0x2c, 0x0f, 0xe7, 0x22, 0x35, 0x5b, 0x19, 0x3d, 0xff, 0x76, + 0x81, 0x0e, 0xf8, 0xcc, 0xe3, 0xad, 0x2d, 0xca, 0xc9, 0x2b, 0xeb, 0xfd, 0x59, 0xb8, 0x1c, 0x09, + 0x21, 0x9c, 0x36, 0x13, 0x89, 0xd4, 0x6f, 0xc3, 0x15, 0xf5, 0x71, 0xfe, 0x7d, 0xea, 0xdf, 0x23, + 0x98, 0x4f, 0x55, 0x44, 0xba, 0x5b, 0x8b, 0xf5, 0xc3, 0x59, 0xdd, 0xda, 0x0b, 0xa4, 0xa8, 0x79, + 0xd5, 0x74, 0xf8, 0x02, 0x66, 0x62, 0xd3, 0x81, 0x75, 0x15, 0x73, 0xc2, 0x18, 0x3a, 0x27, 0x92, + 0xae, 0x2f, 0x45, 0x13, 0x23, 0x71, 0x70, 0x66, 0x37, 0x59, 0xfb, 0x77, 0x0a, 0x5e, 0x17, 0xea, + 0xf0, 0x37, 0x08, 0xc6, 0xc3, 0x0d, 0x88, 0x6f, 0x64, 0xd0, 0x4c, 0xaf, 0x5c, 0xed, 0x66, 0x11, + 0xd3, 0x30, 0xae, 0xbe, 0xf0, 0xf5, 0xef, 0x7f, 0xff, 0x30, 0x7a, 0x15, 0xcf, 0x9a, 0x79, 0x2f, + 0x01, 0xfc, 0x23, 0x82, 0xa9, 0x44, 0x6f, 0xe0, 0x95, 0xbc, 0x20, 0xaa, 0xc5, 0xac, 0x55, 0x4f, + 0x81, 0x90, 0xec, 0xde, 0x11, 0xec, 0x96, 0xf0, 0x82, 0x99, 0xf9, 0x06, 0x89, 0x75, 0x23, 0xfe, + 0x05, 0xc1, 0x64, 0xdc, 0x11, 0x36, 0x8b, 0x86, 0x1c, 0x70, 0x5c, 0x29, 0x0e, 0x90, 0x14, 0x37, + 0x05, 0xc5, 0x3a, 0xfe, 0xb0, 0x10, 0x45, 0xf3, 0xab, 0x64, 0x93, 0x3c, 0x35, 0x8f, 0xcf, 0xf0, + 0xaf, 0x08, 0x2e, 0x2a, 0x77, 0x1d, 0x7e, 0x37, 0xf7, 0x42, 0x73, 0x76, 0xa8, 0xf6, 0x5e, 0x09, + 0xa4, 0x14, 0x76, 0x5b, 0x08, 0x5b, 0xc1, 0x46, 0x56, 0x65, 0x84, 0xe8, 0xc6, 0x89, 0xed, 0x8b, + 0xff, 0x40, 0x70, 0x39, 0x67, 0x98, 0xe2, 0x3b, 0x79, 0x94, 0x86, 0x6f, 0x04, 0xed, 0x6e, 0x69, + 0x7c, 0x41, 0x61, 0x27, 0x6f, 0x2c, 0x1c, 0x84, 0x4f, 0xf1, 0x3f, 0x08, 0x66, 0x32, 0x17, 0x36, + 0x5e, 0x2b, 0x5a, 0x39, 0xaa, 0xd7, 0x84, 0xf6, 0x41, 0x49, 0xb4, 0x94, 0xb4, 0x25, 0x24, 0x3d, + 0xc0, 0x1f, 0x95, 0x2c, 0x42, 0xb1, 0xaa, 0x23, 0xa5, 0x2f, 0x10, 0x4c, 0x67, 0x3d, 0x00, 0xf0, + 0x6a, 0x51, 0xaa, 0x8a, 0x37, 0x88, 0xb6, 0x56, 0x0e, 0x2c, 0x65, 0x6e, 0x08, 0x99, 0x77, 0xf0, + 0xda, 0xff, 0x91, 0x89, 0x7f, 0x42, 0x70, 0xfe, 0xc4, 0x16, 0xc4, 0xb5, 0xa1, 0x45, 0x95, 0xda, + 0xa8, 0xda, 0xad, 0x53, 0x61, 0xa4, 0x04, 0x53, 0x48, 0xb8, 0x81, 0x97, 0x32, 0x24, 0x90, 0x01, + 0xae, 0x21, 0x1f, 0x3a, 0x2f, 0x11, 0x5c, 0xca, 0xd8, 0x72, 0xf8, 0xfd, 0xa2, 0xd9, 0x54, 0x4c, + 0x86, 0xd5, 0x52, 0x58, 0xa9, 0xe2, 0xa1, 0x50, 0xb1, 0x81, 0xeb, 0x25, 0x2f, 0x22, 0x36, 0x2f, + 0xea, 0x1f, 0x3f, 0x3b, 0xac, 0xa0, 0xe7, 0x87, 0x15, 0xf4, 0xd7, 0x61, 0x05, 0x7d, 0x77, 0x54, + 0x19, 0x79, 0x7e, 0x54, 0x19, 0xf9, 0xf3, 0xa8, 0x32, 0xf2, 0x79, 0xcd, 0xf5, 0x78, 0x6b, 0xcf, + 0x36, 0x1c, 0xb6, 0x3b, 0x88, 0xe3, 0xb4, 0x88, 0xe7, 0x1f, 0x07, 0xdd, 0x8f, 0x87, 0xe5, 0x07, + 0x1d, 0x1a, 0xd8, 0xe3, 0xe2, 0xb7, 0xe8, 0xad, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x03, 0xab, + 0x0b, 0x81, 0x73, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -920,6 +1031,8 @@ type QueryClient interface { ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) + // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height + BTCValidatorCurrentPower(ctx context.Context, in *QueryBTCValidatorCurrentPowerRequest, opts ...grpc.CallOption) (*QueryBTCValidatorCurrentPowerResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) @@ -989,6 +1102,15 @@ func (c *queryClient) BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBT return out, nil } +func (c *queryClient) BTCValidatorCurrentPower(ctx context.Context, in *QueryBTCValidatorCurrentPowerRequest, opts ...grpc.CallOption) (*QueryBTCValidatorCurrentPowerResponse, error) { + out := new(QueryBTCValidatorCurrentPowerResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorCurrentPower", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) { out := new(QueryActivatedHeightResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActivatedHeight", in, out, opts...) @@ -1021,6 +1143,8 @@ type QueryServer interface { ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height BTCValidatorPowerAtHeight(context.Context, *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) + // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height + BTCValidatorCurrentPower(context.Context, *QueryBTCValidatorCurrentPowerRequest) (*QueryBTCValidatorCurrentPowerResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when // there exists 1 BTC validator with voting power ActivatedHeight(context.Context, *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) @@ -1050,6 +1174,9 @@ func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context func (*UnimplementedQueryServer) BTCValidatorPowerAtHeight(ctx context.Context, req *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorPowerAtHeight not implemented") } +func (*UnimplementedQueryServer) BTCValidatorCurrentPower(ctx context.Context, req *QueryBTCValidatorCurrentPowerRequest) (*QueryBTCValidatorCurrentPowerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorCurrentPower not implemented") +} func (*UnimplementedQueryServer) ActivatedHeight(ctx context.Context, req *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ActivatedHeight not implemented") } @@ -1169,6 +1296,24 @@ func _Query_BTCValidatorPowerAtHeight_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _Query_BTCValidatorCurrentPower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCValidatorCurrentPowerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCValidatorCurrentPower(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorCurrentPower", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCValidatorCurrentPower(ctx, req.(*QueryBTCValidatorCurrentPowerRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_ActivatedHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryActivatedHeightRequest) if err := dec(in); err != nil { @@ -1233,6 +1378,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BTCValidatorPowerAtHeight", Handler: _Query_BTCValidatorPowerAtHeight_Handler, }, + { + MethodName: "BTCValidatorCurrentPower", + Handler: _Query_BTCValidatorCurrentPower_Handler, + }, { MethodName: "ActivatedHeight", Handler: _Query_ActivatedHeight_Handler, @@ -1574,6 +1723,69 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } +func (m *QueryBTCValidatorCurrentPowerRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorCurrentPowerRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorCurrentPowerRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValBtcPkHex) > 0 { + i -= len(m.ValBtcPkHex) + copy(dAtA[i:], m.ValBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCValidatorCurrentPowerResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCValidatorCurrentPowerResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCValidatorCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.VotingPower != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x10 + } + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *QueryActiveBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1946,6 +2158,34 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { return n } +func (m *QueryBTCValidatorCurrentPowerRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValBtcPkHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCValidatorCurrentPowerResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + if m.VotingPower != 0 { + n += 1 + sovQuery(uint64(m.VotingPower)) + } + return n +} + func (m *QueryActiveBTCValidatorsAtHeightRequest) Size() (n int) { if m == nil { return 0 @@ -2855,6 +3095,176 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCValidatorCurrentPowerRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryActiveBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 93af02ba0..03f33ca38 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -307,6 +307,60 @@ func local_request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marsha } +func request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorCurrentPowerRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := client.BTCValidatorCurrentPower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCValidatorCurrentPowerRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["val_btc_pk_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + } + + protoReq.ValBtcPkHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + } + + msg, err := server.BTCValidatorCurrentPower(ctx, &protoReq) + return msg, metadata, err + +} + func request_Query_ActivatedHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryActivatedHeightRequest var metadata runtime.ServerMetadata @@ -541,6 +595,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCValidatorCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCValidatorCurrentPower_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActivatedHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -748,6 +825,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCValidatorCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCValidatorCurrentPower_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCValidatorCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActivatedHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -804,6 +901,8 @@ var ( pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCValidatorCurrentPower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) @@ -822,6 +921,8 @@ var ( forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_BTCValidatorCurrentPower_0 = runtime.ForwardResponseMessage + forward_Query_ActivatedHeight_0 = runtime.ForwardResponseMessage forward_Query_BTCValidatorDelegations_0 = runtime.ForwardResponseMessage From 198c0e84869bfd8f9836c82cb632c4a2f2a85d71 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 7 Aug 2023 15:48:36 +0200 Subject: [PATCH 060/202] Fix bug allowing creating delegations to non existing validators (#58) --- x/btcstaking/keeper/btc_delegations.go | 29 ++++----- x/btcstaking/keeper/msg_server.go | 16 ++++- x/btcstaking/keeper/msg_server_test.go | 82 ++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 19 deletions(-) diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index f05225a23..bb7c01514 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -64,13 +64,19 @@ func (k Keeper) hasBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, d return store.Has(delBTCPKBytes) } -// HasBTCDelegation checks if the given BTC delegator has a BTC delegation with the given staking tx hash under a given BTC validator -func (k Keeper) HasBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string) bool { - btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) - if err != nil { - return false +// validatorDelegations gets the BTC delegations with a given BTC PK under a given BTC validator +// NOTE: Internal function which assumes that the validator exists +func (k Keeper) validatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPKBytes []byte) (*types.BTCDelegatorDelegations, error) { + store := k.btcDelegationStore(ctx, valBTCPK) + // ensure BTC delegation exists + if !store.Has(delBTCPKBytes) { + return nil, types.ErrBTCDelNotFound } - return btcDels.Has(stakingTxHash) + // get and unmarshal + var btcDels types.BTCDelegatorDelegations + btcDelsBytes := store.Get(delBTCPKBytes) + k.cdc.MustUnmarshal(btcDelsBytes, &btcDels) + return &btcDels, nil } // getBTCDelegations gets the BTC delegations with a given BTC PK under a given BTC validator @@ -83,16 +89,7 @@ func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, d return nil, types.ErrBTCValNotFound } - store := k.btcDelegationStore(ctx, valBTCPK) - // ensure BTC delegation exists - if !store.Has(delBTCPKBytes) { - return nil, types.ErrBTCDelNotFound - } - // get and unmarshal - var btcDels types.BTCDelegatorDelegations - btcDelsBytes := store.Get(delBTCPKBytes) - k.cdc.MustUnmarshal(btcDelsBytes, &btcDels) - return &btcDels, nil + return k.validatorDelegations(ctx, valBTCPK, delBTCPKBytes) } // GetBTCDelegation gets the BTC delegation with a given BTC PK and staking tx hash under a given BTC validator diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index fc9dc6b5c..17108bcc7 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -86,9 +86,19 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } stakingTxHash := stakingMsgTx.TxHash().String() - // ensure the staking tx is not duplicated - if ms.HasBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash) { - return nil, types.ErrReusedStakingTx + // ensure the validator exists + if !ms.HasBTCValidator(ctx, *valBTCPK) { + return nil, types.ErrBTCValNotFound + } + + delegations, err := ms.validatorDelegations(ctx, valBTCPK, delBTCPK.MustMarshal()) + + if err == nil { + // err is nil, meaning there exists a BTC delegation for this validator and delegator + // ensure the staking tx is not duplicated + if delegations.Has(stakingTxHash) { + return nil, types.ErrReusedStakingTx + } } // ensure staking tx is using correct jury PK diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 13eaf0f6b..a715beda3 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -2,8 +2,10 @@ package keeper_test import ( "context" + "errors" "math/rand" "testing" + "time" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" @@ -218,3 +220,83 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { require.True(t, actualDelWithJurySig.HasJurySig()) }) } + +func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + ms := keeper.NewMsgServerImpl(*bsKeeper) + goCtx := sdk.WrapSDKContext(ctx) + + // set jury PK to params + _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + err = bsKeeper.SetParams(ctx, types.Params{ + JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + SlashingAddress: slashingAddr, + MinSlashingTxFeeSat: 10, + }) + require.NoError(t, err) + + // We only generate a validator, but not insert it into KVStore. So later + // insertion of delegation should fail. + _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + /* + generate and insert valid new BTC delegation + */ + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + stakingTimeBlocks := uint16(5) + stakingValue := int64(2 * 10e8) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + require.NoError(t, err) + // get msgTx + stakingMsgTx, err := stakingTx.ToMsgTx() + require.NoError(t, err) + + // random signer + signer := datagen.GenRandomAccount().Address + // random Babylon SK + delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + require.NoError(t, err) + // PoP + pop, err := types.NewPoP(delBabylonSK, delSK) + require.NoError(t, err) + // generate staking tx info + prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) + btcHeader := btcHeaderWithProof.HeaderBytes + txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) + + // generate proper delegator sig + delegatorSig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.StakingScript, + delSK, + &chaincfg.SimNetParams, + ) + require.NoError(t, err) + + // all good, construct and send MsgCreateBTCDelegation message + msgCreateBTCDel := &types.MsgCreateBTCDelegation{ + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + Pop: pop, + StakingTx: stakingTx, + StakingTxInfo: txInfo, + SlashingTx: slashingTx, + DelegatorSig: delegatorSig, + } + _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) + require.Error(t, err) + require.True(t, errors.Is(err, types.ErrBTCValNotFound)) +} From 01ba4698494b55a5e71ffb553da6494cfb685830 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 9 Aug 2023 11:53:29 +0800 Subject: [PATCH 061/202] chore: removing unnecessary fields in `EventSlashedBTCValidator` (#59) --- proto/babylon/finality/v1/events.proto | 9 +- x/finality/keeper/msg_server.go | 8 +- x/finality/types/events.go | 11 +- x/finality/types/events.pb.go | 139 +++---------------------- 4 files changed, 18 insertions(+), 149 deletions(-) diff --git a/proto/babylon/finality/v1/events.proto b/proto/babylon/finality/v1/events.proto index c71ba10ce..37e6354d8 100644 --- a/proto/babylon/finality/v1/events.proto +++ b/proto/babylon/finality/v1/events.proto @@ -1,18 +1,13 @@ syntax = "proto3"; package babylon.finality.v1; -import "gogoproto/gogo.proto"; import "babylon/finality/v1/finality.proto"; option go_package = "github.com/babylonchain/babylon/x/finality/types"; // EventSlashedBTCValidator is the event emitted when a BTC validator is slashed // due to signing two conflicting blocks -message EventSlashedBTCValidator { - // val_btc_pk is the BTC validator's BTC PK - bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; +message EventSlashedBTCValidator { // evidence is the evidence that the BTC validator double signs - Evidence evidence = 2; - // extracted_btc_sk is the extracted BTC SK of this BTC validator - bytes extracted_btc_sk = 4; + Evidence evidence = 1; } diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 8d180d7db..db406c442 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -208,12 +208,8 @@ func (k Keeper) slashBTCValidator(ctx sdk.Context, valBtcPk *bbn.BIP340PubKey, e panic(fmt.Errorf("failed to slash BTC validator: %v", err)) } - // extract SK and emit slashing event - btcSK, err := evidence.ExtractBTCSK() - if err != nil { - panic(fmt.Errorf("failed to extract secret key from two EOTS signatures with the same public randomness: %v", err)) - } - eventSlashing := types.NewEventSlashedBTCValidator(valBtcPk, evidence, btcSK) + // emit slashing event + eventSlashing := types.NewEventSlashedBTCValidator(evidence) if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) } diff --git a/x/finality/types/events.go b/x/finality/types/events.go index d2a1f4b10..2b7ac88da 100644 --- a/x/finality/types/events.go +++ b/x/finality/types/events.go @@ -1,14 +1,7 @@ package types -import ( - bbn "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/btcec/v2" -) - -func NewEventSlashedBTCValidator(valBTCPK *bbn.BIP340PubKey, evidence *Evidence, btcSK *btcec.PrivateKey) *EventSlashedBTCValidator { +func NewEventSlashedBTCValidator(evidence *Evidence) *EventSlashedBTCValidator { return &EventSlashedBTCValidator{ - ValBtcPk: valBTCPK, - Evidence: evidence, - ExtractedBtcSk: btcSK.Serialize(), + Evidence: evidence, } } diff --git a/x/finality/types/events.pb.go b/x/finality/types/events.pb.go index 8296e8e48..68c30c37b 100644 --- a/x/finality/types/events.pb.go +++ b/x/finality/types/events.pb.go @@ -5,8 +5,6 @@ package types import ( fmt "fmt" - github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" - _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -27,12 +25,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // EventSlashedBTCValidator is the event emitted when a BTC validator is slashed // due to signing two conflicting blocks type EventSlashedBTCValidator struct { - // val_btc_pk is the BTC validator's BTC PK - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` // evidence is the evidence that the BTC validator double signs - Evidence *Evidence `protobuf:"bytes,2,opt,name=evidence,proto3" json:"evidence,omitempty"` - // extracted_btc_sk is the extracted BTC SK of this BTC validator - ExtractedBtcSk []byte `protobuf:"bytes,4,opt,name=extracted_btc_sk,json=extractedBtcSk,proto3" json:"extracted_btc_sk,omitempty"` + Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` } func (m *EventSlashedBTCValidator) Reset() { *m = EventSlashedBTCValidator{} } @@ -75,13 +69,6 @@ func (m *EventSlashedBTCValidator) GetEvidence() *Evidence { return nil } -func (m *EventSlashedBTCValidator) GetExtractedBtcSk() []byte { - if m != nil { - return m.ExtractedBtcSk - } - return nil -} - func init() { proto.RegisterType((*EventSlashedBTCValidator)(nil), "babylon.finality.v1.EventSlashedBTCValidator") } @@ -89,26 +76,20 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/events.proto", fileDescriptor_c34c03aae5e3e6bf) } var fileDescriptor_c34c03aae5e3e6bf = []byte{ - // 295 bytes of a gzipped FileDescriptorProto + // 197 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xaa, - 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, - 0x58, 0x10, 0xa5, 0x52, 0x4a, 0xd8, 0x0c, 0x83, 0x6b, 0x03, 0xab, 0x51, 0xba, 0xca, 0xc8, 0x25, - 0xe1, 0x0a, 0x32, 0x3f, 0x38, 0x27, 0xb1, 0x38, 0x23, 0x35, 0xc5, 0x29, 0xc4, 0x39, 0x2c, 0x31, - 0x27, 0x33, 0x25, 0xb1, 0x24, 0xbf, 0x48, 0x28, 0x84, 0x8b, 0xab, 0x2c, 0x31, 0x27, 0x3e, 0xa9, - 0x24, 0x39, 0xbe, 0x20, 0x5b, 0x82, 0x51, 0x81, 0x51, 0x83, 0xc7, 0xc9, 0xec, 0xd6, 0x3d, 0x79, - 0xa3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x1d, 0xc9, 0x19, - 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x93, 0x67, 0x80, 0xb1, - 0x89, 0x41, 0x40, 0x69, 0x92, 0x77, 0x6a, 0x65, 0x10, 0x47, 0x59, 0x62, 0x8e, 0x53, 0x49, 0x72, - 0x40, 0xb6, 0x90, 0x25, 0x17, 0x47, 0x6a, 0x59, 0x66, 0x4a, 0x6a, 0x5e, 0x72, 0xaa, 0x04, 0x93, - 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xac, 0x1e, 0x16, 0x4f, 0xe9, 0xb9, 0x42, 0x15, 0x05, 0xc1, 0x95, - 0x0b, 0x69, 0x70, 0x09, 0xa4, 0x56, 0x94, 0x14, 0x25, 0x26, 0x97, 0xa4, 0xa6, 0x80, 0x9d, 0x55, - 0x9c, 0x2d, 0xc1, 0x02, 0x72, 0x56, 0x10, 0x1f, 0x5c, 0xdc, 0xa9, 0x24, 0x39, 0x38, 0xdb, 0xc9, - 0xeb, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, - 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0x08, 0x39, 0xbe, 0x02, - 0x11, 0x5e, 0x60, 0x7f, 0x24, 0xb1, 0x81, 0x83, 0xca, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0b, - 0x3c, 0x79, 0xa2, 0x9d, 0x01, 0x00, 0x00, + 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x52, 0xc2, 0xa6, 0x0d, 0xae, 0x00, 0xac, 0x51, 0x29, + 0x94, 0x4b, 0xc2, 0x15, 0x64, 0x50, 0x70, 0x4e, 0x62, 0x71, 0x46, 0x6a, 0x8a, 0x53, 0x88, 0x73, + 0x58, 0x62, 0x4e, 0x66, 0x4a, 0x62, 0x49, 0x7e, 0x91, 0x90, 0x25, 0x17, 0x47, 0x6a, 0x59, 0x66, + 0x4a, 0x6a, 0x5e, 0x72, 0xaa, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xac, 0x1e, 0x16, 0x7b, + 0xf4, 0x5c, 0xa1, 0x8a, 0x82, 0xe0, 0xca, 0x9d, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, + 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, + 0x58, 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, + 0x6a, 0x58, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x70, 0x6e, 0x49, 0x65, 0x41, + 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0xa5, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, 0x4e, 0x21, + 0xed, 0x06, 0x01, 0x00, 0x00, } func (m *EventSlashedBTCValidator) Marshal() (dAtA []byte, err error) { @@ -131,13 +112,6 @@ func (m *EventSlashedBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error _ = i var l int _ = l - if len(m.ExtractedBtcSk) > 0 { - i -= len(m.ExtractedBtcSk) - copy(dAtA[i:], m.ExtractedBtcSk) - i = encodeVarintEvents(dAtA, i, uint64(len(m.ExtractedBtcSk))) - i-- - dAtA[i] = 0x22 - } if m.Evidence != nil { { size, err := m.Evidence.MarshalToSizedBuffer(dAtA[:i]) @@ -148,18 +122,6 @@ func (m *EventSlashedBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error i = encodeVarintEvents(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintEvents(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0xa } return len(dAtA) - i, nil @@ -182,18 +144,10 @@ func (m *EventSlashedBTCValidator) Size() (n int) { } var l int _ = l - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovEvents(uint64(l)) - } if m.Evidence != nil { l = m.Evidence.Size() n += 1 + l + sovEvents(uint64(l)) } - l = len(m.ExtractedBtcSk) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } return n } @@ -233,41 +187,6 @@ func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) } @@ -303,40 +222,6 @@ func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExtractedBtcSk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ExtractedBtcSk = append(m.ExtractedBtcSk[:0], dAtA[iNdEx:postIndex]...) - if m.ExtractedBtcSk == nil { - m.ExtractedBtcSk = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) From 7f3489719d2b28464a57bde114ef45b2f1a5aca7 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 11 Aug 2023 16:28:56 +0800 Subject: [PATCH 062/202] chore: generalise datagen function for generating staking/slashing txs (#60) --- test/e2e/btc_staking_e2e_test.go | 8 ++-- testutil/datagen/btc_address.go | 22 ++++++++-- testutil/datagen/btcstaking.go | 44 ++++++++++++++----- x/btcstaking/keeper/grpc_query_test.go | 6 +-- x/btcstaking/keeper/msg_server_test.go | 22 +++++----- .../keeper/voting_power_table_test.go | 4 +- x/btcstaking/types/btc_slashing_tx_test.go | 2 +- x/btcstaking/types/btcstaking_test.go | 3 +- 8 files changed, 75 insertions(+), 36 deletions(-) diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 57282048c..fde77f966 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -23,7 +23,8 @@ import ( ) var ( - r = rand.New(rand.NewSource(time.Now().Unix())) + r = rand.New(rand.NewSource(time.Now().Unix())) + net = &chaincfg.SimNetParams // BTC validator valSK, _, _ = datagen.GenRandomBTCKeyPair(r) btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) @@ -96,6 +97,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { stakingValue := int64(2 * 10e8) stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( r, + net, delBTCSK, btcVal.BtcPk.MustToBTCPK(), params.JuryPk.MustToBTCPK(), @@ -111,7 +113,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { stakingMsgTx, stakingTx.StakingScript, delBTCSK, - &chaincfg.SimNetParams, + net, ) s.NoError(err) @@ -169,7 +171,7 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { stakingMsgTx, stakingTx.StakingScript, jurySK, - &chaincfg.SimNetParams, + net, ) s.NoError(err) nonValidatorNode.AddJurySig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, jurySig) diff --git a/testutil/datagen/btc_address.go b/testutil/datagen/btc_address.go index 2f4f232d4..9b8b76f78 100644 --- a/testutil/datagen/btc_address.go +++ b/testutil/datagen/btc_address.go @@ -5,12 +5,26 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" ) -func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (string, error) { - addr, err := btcutil.NewAddressWitnessPubKeyHash(GenRandomByteArray(r, 20), net) +func GenRandomPkScript(r *rand.Rand) []byte { + // NOTE: this generates non-standard pkscript + return GenRandomByteArray(r, 20) +} + +func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (btcutil.Address, error) { + addr, err := btcutil.NewAddressPubKeyHash(GenRandomByteArray(r, 20), net) + if err != nil { + return nil, err + } + return addr, nil +} + +func GenRandomPubKeyHashScript(r *rand.Rand, net *chaincfg.Params) ([]byte, error) { + addr, err := btcutil.NewAddressPubKeyHash(GenRandomByteArray(r, 20), net) if err != nil { - return "", err + return nil, err } - return addr.EncodeAddress(), nil + return txscript.PayToAddrScript(addr) } diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index d6cd982ca..d4bcc59b7 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" ) @@ -73,7 +74,7 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc return nil, err } // staking/slashing tx - stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, delSK, valPK, juryBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) + stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, net, delSK, valPK, juryBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) if err != nil { return nil, err } @@ -107,8 +108,10 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc }, nil } -func GenBTCStakingSlashingTx( +func GenBTCStakingSlashingTxWithOutPoint( r *rand.Rand, + btcNet *chaincfg.Params, + outPoint *wire.OutPoint, stakerSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, juryPK *btcec.PublicKey, @@ -116,8 +119,6 @@ func GenBTCStakingSlashingTx( stakingValue int64, slashingAddress string, ) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { - btcNet := &chaincfg.SimNetParams - stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerSK.PubKey(), validatorPK, @@ -131,15 +132,18 @@ func GenBTCStakingSlashingTx( } tx := wire.NewMsgTx(2) - // an arbitrary input - spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) - tx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: spend.prevOut, - Sequence: wire.MaxTxInSequenceNum, - SignatureScript: nil, - }) + // add the given tx input + txIn := wire.NewTxIn(outPoint, nil, nil) + tx.AddTxIn(txIn) // 2 outputs for changes and staking output - tx.AddTxOut(wire.NewTxOut(100, []byte{1, 2, 3})) // output for change, doesn't matter + changeAddrScript, err := GenRandomPubKeyHashScript(r, btcNet) + if err != nil { + return nil, nil, err + } + if txscript.GetScriptClass(changeAddrScript) == txscript.NonStandardTy { + return nil, nil, fmt.Errorf("change address script is non-standard") + } + tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change tx.AddTxOut(stakingOutput) // construct staking tx @@ -169,3 +173,19 @@ func GenBTCStakingSlashingTx( return stakingTx, slashingTx, nil } + +func GenBTCStakingSlashingTx( + r *rand.Rand, + btcNet *chaincfg.Params, + stakerSK *btcec.PrivateKey, + validatorPK *btcec.PublicKey, + juryPK *btcec.PublicKey, + stakingTimeBlocks uint16, + stakingValue int64, + slashingAddress string, +) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { + // an arbitrary input + spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) + outPoint := &spend.prevOut + return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress) +} diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 9ebf82df1..59ac059b0 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -196,7 +196,7 @@ func FuzzBTCDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr, startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { // remove jury sig in random BTC delegations to make them inactive @@ -335,7 +335,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr, 1, 1000, 10000) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, 10000) // timelock period: 1-1000 require.NoError(t, err) err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -423,7 +423,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr, startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel err = keeper.SetBTCDelegation(ctx, btcDel) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index a715beda3..bbd24f0e3 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -81,6 +81,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -94,11 +95,11 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { // set jury PK to params jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), - SlashingAddress: slashingAddr, + SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, }) require.NoError(t, err) @@ -127,7 +128,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() @@ -148,7 +149,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { btcHeader := btcHeaderWithProof.HeaderBytes txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) // mock for testing k-deep stuff - btccKeeper.EXPECT().GetPowLimit().Return(chaincfg.SimNetParams.PowLimit).AnyTimes() + btccKeeper.EXPECT().GetPowLimit().Return(net.PowLimit).AnyTimes() btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) @@ -158,7 +159,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { stakingMsgTx, stakingTx.StakingScript, delSK, - &chaincfg.SimNetParams, + net, ) require.NoError(t, err) @@ -198,7 +199,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { stakingMsgTx, stakingTx.StakingScript, jurySK, - &chaincfg.SimNetParams, + net, ) require.NoError(t, err) msgAddJurySig := &types.MsgAddJurySig{ @@ -223,6 +224,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) + net := &chaincfg.SimNetParams ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -236,11 +238,11 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // set jury PK to params _, juryPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), - SlashingAddress: slashingAddr, + SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, }) require.NoError(t, err) @@ -257,7 +259,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() @@ -282,7 +284,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { stakingMsgTx, stakingTx.StakingScript, delSK, - &chaincfg.SimNetParams, + net, ) require.NoError(t, err) diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 3f9c330f8..b75b222c7 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -31,7 +31,7 @@ func FuzzVotingPowerTable(f *testing.F) { // jury and slashing addr jurySK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SigNetParams) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) // generate a random batch of validators @@ -53,7 +53,7 @@ func FuzzVotingPowerTable(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr, 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 require.NoError(t, err) err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 6537d72b8..4b7e22966 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -33,7 +33,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) // generate staking/slashing tx - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, delSK, valPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, valPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) stakingOutInfo, err := stakingTx.GetStakingOutputInfo(net) require.NoError(t, err) diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index f934b1ed0..3dc9937b8 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -16,6 +16,7 @@ func FuzzStakingTx(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams stakerSK, stakerPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) @@ -28,7 +29,7 @@ func FuzzStakingTx(f *testing.F) { stakingValue := int64(2 * 10e8) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr) + stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, net, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) err = stakingTx.ValidateBasic() From 98891296ba86767ec9dfb9a3021c4de620875c95 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Tue, 22 Aug 2023 09:55:11 +0200 Subject: [PATCH 063/202] Add api to check delegation by staking tx hash (#61) * Add api to check delegation by staking tx hash --- proto/babylon/btcstaking/v1/btcstaking.proto | 1 + proto/babylon/btcstaking/v1/query.proto | 37 + test/e2e/btc_staking_e2e_test.go | 7 + .../configurer/chain/queries_btcstaking.go | 12 + x/btcstaking/keeper/grpc_query.go | 66 ++ x/btcstaking/keeper/grpc_query_test.go | 8 + x/btcstaking/types/query.pb.go | 797 ++++++++++++++++-- x/btcstaking/types/query.pb.gw.go | 101 +++ 8 files changed, 965 insertions(+), 64 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 259d43bc0..a4cd3448d 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -115,3 +115,4 @@ enum BTCDelegationStatus { // EXPIRED defines a delegation that has expired EXPIRED = 2; } + diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 4c05f7c5c..1d82197bd 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -56,6 +56,11 @@ service Query { rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; } + + // BTCDelegation retrieves delegation by corresponding staking tx hash + rpc BTCDelegation(QueryBTCDelegationRequest) returns (QueryBTCDelegationResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/btc_delegations/{staking_tx_hash_hex}"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -194,3 +199,35 @@ message QueryBTCValidatorDelegationsResponse { // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; } + +// QueryBTCDelegationRequest is the request type to retrieve a BTC delegation by +// staking tx hash +message QueryBTCDelegationRequest { + // Hash of staking transaction in btc format + string staking_tx_hash_hex = 1; +} + +// QueryBTCDelegationResponse is response type matching QueryBTCDelegationRequest +// and containing BTC delegation information +message QueryBTCDelegationResponse { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + uint64 start_height = 3; + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + uint64 end_height = 4; + // total_sat is the total amount of BTC stakes in this delegation + // quantified in satoshi + uint64 total_sat = 5; + // staking_tx is the staking tx + string staking_tx = 6; + // staking_script is the script commited to in staking output + string staking_script = 7; +} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index fde77f966..cfc4aa73c 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -1,6 +1,7 @@ package e2e import ( + "encoding/hex" "math" "math/rand" "time" @@ -140,6 +141,12 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.Len(pendingDels.Dels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels.Dels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) s.Nil(pendingDels.Dels[0].JurySig) + + // check delegation + delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHash()) + s.NotNil(delegation) + expectedScript := hex.EncodeToString(stakingTx.StakingScript) + s.Equal(expectedScript, delegation.StakingScript) } // Test2SubmitJurySignature is an end-to-end test for user diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index ffc00effc..a83482a68 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -58,6 +58,18 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BT return resp.BtcDelegatorDelegations } +func (n *NodeConfig) QueryBtcDelegation(stakingTxHash string) *bstypes.QueryBTCDelegationResponse { + path := fmt.Sprintf("/babylon/btcstaking/v1/btc_delegations/%s", stakingTxHash) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryBTCDelegationResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return &resp +} + func (n *NodeConfig) QueryActivatedHeight() uint64 { bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/activated_height", url.Values{}) require.NoError(n.t, err) diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 9fdad6d41..c650439f0 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -2,10 +2,12 @@ package keeper import ( "context" + "encoding/hex" errorsmod "cosmossdk.io/errors" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/query" @@ -249,3 +251,67 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC return &types.QueryBTCValidatorDelegationsResponse{BtcDelegatorDelegations: btcDels, Pagination: pageRes}, nil } + +func (k Keeper) delegationView( + ctx sdk.Context, + validatorBtcPubKey *bbn.BIP340PubKey, + stakingTxHash *chainhash.Hash) *types.QueryBTCDelegationResponse { + + btcDelIter := k.btcDelegationStore(ctx, validatorBtcPubKey).Iterator(nil, nil) + defer btcDelIter.Close() + for ; btcDelIter.Valid(); btcDelIter.Next() { + var btcDels types.BTCDelegatorDelegations + k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) + delegation, err := btcDels.Get(stakingTxHash.String()) + if err != nil { + continue + } + + return &types.QueryBTCDelegationResponse{ + BtcPk: delegation.BtcPk, + ValBtcPk: delegation.ValBtcPk, + StartHeight: delegation.StartHeight, + EndHeight: delegation.EndHeight, + TotalSat: delegation.TotalSat, + StakingTx: hex.EncodeToString(delegation.StakingTx.Tx), + StakingScript: hex.EncodeToString(delegation.StakingTx.StakingScript), + } + } + return nil +} + +// BTCDelegation returns existing btc delegation by staking tx hash +func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegationRequest) (*types.QueryBTCDelegationResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + stakingTxHash, err := chainhash.NewHashFromStr(req.StakingTxHashHex) + + if err != nil { + return nil, err + } + + btcValIter := k.btcValidatorStore(sdkCtx).Iterator(nil, nil) + defer btcValIter.Close() + for ; btcValIter.Valid(); btcValIter.Next() { + valBTCPKBytes := btcValIter.Key() + valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + + if err != nil { + // failed to unmarshal BTC validator PK in KVStore is a programming error + panic(err) + } + + response := k.delegationView(sdkCtx, valBTCPK, stakingTxHash) + + if response == nil { + continue + } + + return response, nil + } + + return nil, types.ErrBTCDelNotFound +} diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 59ac059b0..7daa78c05 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -205,6 +205,14 @@ func FuzzBTCDelegations(f *testing.F) { } err = keeper.SetBTCDelegation(ctx, btcDel) require.NoError(t, err) + + txHash := btcDel.StakingTx.MustGetTxHash() + + delView, err := keeper.BTCDelegation(ctx, &types.QueryBTCDelegationRequest{ + StakingTxHashHex: txHash, + }) + require.NoError(t, err) + require.NotNil(t, delView) } } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index ae21dda38..603025961 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -6,6 +6,7 @@ package types import ( context "context" fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" query "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" @@ -917,6 +918,146 @@ func (m *QueryBTCValidatorDelegationsResponse) GetPagination() *query.PageRespon return nil } +// QueryBTCDelegationRequest is the request type to retrieve a BTC delegation by +// staking tx hash +type QueryBTCDelegationRequest struct { + // Hash of staking transaction in btc format + StakingTxHashHex string `protobuf:"bytes,1,opt,name=staking_tx_hash_hex,json=stakingTxHashHex,proto3" json:"staking_tx_hash_hex,omitempty"` +} + +func (m *QueryBTCDelegationRequest) Reset() { *m = QueryBTCDelegationRequest{} } +func (m *QueryBTCDelegationRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCDelegationRequest) ProtoMessage() {} +func (*QueryBTCDelegationRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{18} +} +func (m *QueryBTCDelegationRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCDelegationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCDelegationRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCDelegationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCDelegationRequest.Merge(m, src) +} +func (m *QueryBTCDelegationRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCDelegationRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCDelegationRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCDelegationRequest proto.InternalMessageInfo + +func (m *QueryBTCDelegationRequest) GetStakingTxHashHex() string { + if m != nil { + return m.StakingTxHashHex + } + return "" +} + +// QueryBTCDelegationResponse is response type matching QueryBTCDelegationRequest +// and containing BTC delegation information +type QueryBTCDelegationResponse struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // start_height is the start BTC height of the BTC delegation + // it is the start BTC height of the timelock + StartHeight uint64 `protobuf:"varint,3,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // end_height is the end height of the BTC delegation + // it is the end BTC height of the timelock - w + EndHeight uint64 `protobuf:"varint,4,opt,name=end_height,json=endHeight,proto3" json:"end_height,omitempty"` + // total_sat is the total amount of BTC stakes in this delegation + // quantified in satoshi + TotalSat uint64 `protobuf:"varint,5,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` + // staking_tx is the staking tx + StakingTx string `protobuf:"bytes,6,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + // staking_script is the script commited to in staking output + StakingScript string `protobuf:"bytes,7,opt,name=staking_script,json=stakingScript,proto3" json:"staking_script,omitempty"` +} + +func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationResponse{} } +func (m *QueryBTCDelegationResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCDelegationResponse) ProtoMessage() {} +func (*QueryBTCDelegationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{19} +} +func (m *QueryBTCDelegationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCDelegationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCDelegationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCDelegationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCDelegationResponse.Merge(m, src) +} +func (m *QueryBTCDelegationResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCDelegationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCDelegationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCDelegationResponse proto.InternalMessageInfo + +func (m *QueryBTCDelegationResponse) GetStartHeight() uint64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +func (m *QueryBTCDelegationResponse) GetEndHeight() uint64 { + if m != nil { + return m.EndHeight + } + return 0 +} + +func (m *QueryBTCDelegationResponse) GetTotalSat() uint64 { + if m != nil { + return m.TotalSat + } + return 0 +} + +func (m *QueryBTCDelegationResponse) GetStakingTx() string { + if m != nil { + return m.StakingTx + } + return "" +} + +func (m *QueryBTCDelegationResponse) GetStakingScript() string { + if m != nil { + return m.StakingScript + } + return "" +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btcstaking.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") @@ -936,75 +1077,89 @@ func init() { proto.RegisterType((*QueryActivatedHeightResponse)(nil), "babylon.btcstaking.v1.QueryActivatedHeightResponse") proto.RegisterType((*QueryBTCValidatorDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsRequest") proto.RegisterType((*QueryBTCValidatorDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsResponse") + proto.RegisterType((*QueryBTCDelegationRequest)(nil), "babylon.btcstaking.v1.QueryBTCDelegationRequest") + proto.RegisterType((*QueryBTCDelegationResponse)(nil), "babylon.btcstaking.v1.QueryBTCDelegationResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 998 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcf, 0x6f, 0xdc, 0x44, - 0x14, 0xce, 0x84, 0x10, 0x89, 0x97, 0xa4, 0x95, 0x86, 0x96, 0x26, 0x6e, 0xb3, 0x6d, 0x9c, 0xe6, - 0x47, 0x8b, 0xb0, 0xb3, 0x5b, 0xa9, 0x02, 0x12, 0x5a, 0xba, 0x09, 0x6d, 0xd4, 0x12, 0x29, 0x58, - 0x15, 0x48, 0x5c, 0x56, 0x63, 0xef, 0xc8, 0x6b, 0xb2, 0xf1, 0x6c, 0xd7, 0x93, 0x25, 0x11, 0xea, - 0x85, 0x03, 0x12, 0x37, 0x04, 0x7f, 0x02, 0x47, 0x4e, 0x9c, 0xb9, 0x22, 0xd1, 0x63, 0x25, 0x24, - 0x84, 0x54, 0xa9, 0x42, 0x09, 0x12, 0x07, 0xfe, 0x09, 0xb4, 0xe3, 0xd9, 0xd8, 0x8e, 0xc7, 0x5e, - 0xc7, 0xa4, 0xb7, 0xc4, 0xf3, 0xbe, 0xf7, 0xbe, 0xef, 0xcd, 0xfb, 0x31, 0x0b, 0x73, 0x36, 0xb1, - 0x0f, 0xda, 0xcc, 0x37, 0x6d, 0xee, 0x04, 0x9c, 0xec, 0x78, 0xbe, 0x6b, 0xf6, 0xaa, 0xe6, 0x93, - 0x3d, 0xda, 0x3d, 0x30, 0x3a, 0x5d, 0xc6, 0x19, 0xbe, 0x28, 0x4d, 0x8c, 0xc8, 0xc4, 0xe8, 0x55, - 0xb5, 0x0b, 0x2e, 0x73, 0x99, 0xb0, 0x30, 0xfb, 0x7f, 0x85, 0xc6, 0xda, 0x15, 0x97, 0x31, 0xb7, - 0x4d, 0x4d, 0xd2, 0xf1, 0x4c, 0xe2, 0xfb, 0x8c, 0x13, 0xee, 0x31, 0x3f, 0x90, 0xa7, 0x37, 0x1d, - 0x16, 0xec, 0xb2, 0xc0, 0xb4, 0x49, 0x40, 0xc3, 0x18, 0x66, 0xaf, 0x6a, 0x53, 0x4e, 0xaa, 0x66, - 0x87, 0xb8, 0x9e, 0x2f, 0x8c, 0xa5, 0xad, 0xae, 0x66, 0xd6, 0x21, 0x5d, 0xb2, 0x3b, 0xf0, 0xb7, - 0xa8, 0xb6, 0x89, 0x11, 0x15, 0x76, 0xfa, 0x05, 0xc0, 0x9f, 0xf4, 0xa3, 0x6d, 0x0b, 0xb0, 0x45, - 0x9f, 0xec, 0xd1, 0x80, 0xeb, 0x16, 0xbc, 0x99, 0xf8, 0x1a, 0x74, 0x98, 0x1f, 0x50, 0xbc, 0x0a, - 0xe3, 0x61, 0x90, 0x69, 0x74, 0x0d, 0x2d, 0x4f, 0xd4, 0x66, 0x0d, 0x65, 0x02, 0x8c, 0x10, 0x56, - 0x1f, 0x7b, 0xf6, 0xf2, 0xea, 0x88, 0x25, 0x21, 0xba, 0x03, 0x33, 0xc2, 0x67, 0xfd, 0xf1, 0xfa, - 0xa7, 0xa4, 0xed, 0x35, 0x09, 0x67, 0xdd, 0x41, 0x40, 0x7c, 0x1f, 0x20, 0x92, 0x29, 0xbd, 0x2f, - 0x1a, 0x61, 0x4e, 0x8c, 0x7e, 0x4e, 0x8c, 0x30, 0xef, 0x32, 0x27, 0xc6, 0x36, 0x71, 0xa9, 0xc4, - 0x5a, 0x31, 0xa4, 0xfe, 0x33, 0x02, 0x4d, 0x15, 0x45, 0x0a, 0x78, 0x08, 0xe7, 0x6c, 0xee, 0x34, - 0x7a, 0xc7, 0x27, 0xd3, 0xe8, 0xda, 0x6b, 0xcb, 0x13, 0xb5, 0xf9, 0x0c, 0x21, 0x71, 0x2f, 0xd6, - 0x94, 0xcd, 0x9d, 0xc8, 0x27, 0x7e, 0x90, 0xa0, 0x3c, 0x2a, 0x28, 0x2f, 0x0d, 0xa5, 0x1c, 0x12, - 0x49, 0x70, 0xbe, 0x0b, 0xd3, 0x29, 0xca, 0x83, 0xbc, 0xcc, 0xc3, 0xb9, 0x1e, 0x69, 0x37, 0xfa, - 0xa4, 0x3b, 0x3b, 0x8d, 0x16, 0xdd, 0x17, 0xb9, 0x79, 0xc3, 0x9a, 0xe8, 0x91, 0x76, 0x9d, 0x3b, - 0xdb, 0x3b, 0x9b, 0x74, 0x5f, 0xa7, 0x8a, 0xcc, 0x1e, 0x4b, 0xde, 0x84, 0xa9, 0x84, 0x64, 0x99, - 0xdc, 0x42, 0x8a, 0x27, 0xe3, 0x8a, 0xf5, 0x79, 0x98, 0x0b, 0x8b, 0x82, 0xfa, 0x4d, 0xcf, 0x77, - 0xeb, 0x8f, 0xd7, 0x37, 0x68, 0x9b, 0xba, 0x61, 0x19, 0x0f, 0x2a, 0x27, 0x00, 0x3d, 0xcf, 0x48, - 0x92, 0xda, 0x82, 0xf3, 0x7d, 0x52, 0xcd, 0xe8, 0x48, 0x5e, 0xc4, 0xf5, 0x6c, 0x5a, 0x91, 0x1f, - 0xab, 0x7f, 0x89, 0x31, 0xb7, 0x7a, 0x13, 0x16, 0x52, 0x09, 0xd8, 0x66, 0x5f, 0xd2, 0xee, 0x3d, - 0xbe, 0x49, 0x3d, 0xb7, 0xc5, 0x4f, 0x93, 0x4e, 0xfc, 0x16, 0x8c, 0xb7, 0x04, 0x4a, 0x5c, 0xea, - 0x98, 0x25, 0xff, 0xd3, 0x1f, 0xc1, 0xe2, 0xb0, 0x28, 0x52, 0xde, 0x1c, 0x4c, 0xf6, 0x18, 0xf7, - 0x7c, 0xb7, 0xd1, 0xe9, 0x9f, 0x8b, 0x20, 0x63, 0xd6, 0x44, 0xf8, 0x4d, 0x40, 0xf4, 0x47, 0x70, - 0x3d, 0xe5, 0x6c, 0x7d, 0xaf, 0xdb, 0xa5, 0x3e, 0x17, 0x06, 0xa7, 0x2a, 0x00, 0x5b, 0xa1, 0x3f, - 0xe9, 0x4c, 0x12, 0x8b, 0xa4, 0xa1, 0xb8, 0xb4, 0x14, 0xe1, 0xd1, 0x34, 0xe1, 0x6f, 0x11, 0x2c, - 0x89, 0x20, 0xf7, 0x1c, 0xee, 0xf5, 0x68, 0xa2, 0xbf, 0x4e, 0xa6, 0x39, 0x2b, 0xcc, 0x7d, 0x45, - 0xcb, 0x94, 0xe9, 0xf2, 0xdf, 0x10, 0x2c, 0x0f, 0xe7, 0x22, 0x35, 0x5b, 0x19, 0x3d, 0xff, 0x76, - 0x81, 0x0e, 0xf8, 0xcc, 0xe3, 0xad, 0x2d, 0xca, 0xc9, 0x2b, 0xeb, 0xfd, 0x59, 0xb8, 0x1c, 0x09, - 0x21, 0x9c, 0x36, 0x13, 0x89, 0xd4, 0x6f, 0xc3, 0x15, 0xf5, 0x71, 0xfe, 0x7d, 0xea, 0xdf, 0x23, - 0x98, 0x4f, 0x55, 0x44, 0xba, 0x5b, 0x8b, 0xf5, 0xc3, 0x59, 0xdd, 0xda, 0x0b, 0xa4, 0xa8, 0x79, - 0xd5, 0x74, 0xf8, 0x02, 0x66, 0x62, 0xd3, 0x81, 0x75, 0x15, 0x73, 0xc2, 0x18, 0x3a, 0x27, 0x92, - 0xae, 0x2f, 0x45, 0x13, 0x23, 0x71, 0x70, 0x66, 0x37, 0x59, 0xfb, 0x77, 0x0a, 0x5e, 0x17, 0xea, - 0xf0, 0x37, 0x08, 0xc6, 0xc3, 0x0d, 0x88, 0x6f, 0x64, 0xd0, 0x4c, 0xaf, 0x5c, 0xed, 0x66, 0x11, - 0xd3, 0x30, 0xae, 0xbe, 0xf0, 0xf5, 0xef, 0x7f, 0xff, 0x30, 0x7a, 0x15, 0xcf, 0x9a, 0x79, 0x2f, - 0x01, 0xfc, 0x23, 0x82, 0xa9, 0x44, 0x6f, 0xe0, 0x95, 0xbc, 0x20, 0xaa, 0xc5, 0xac, 0x55, 0x4f, - 0x81, 0x90, 0xec, 0xde, 0x11, 0xec, 0x96, 0xf0, 0x82, 0x99, 0xf9, 0x06, 0x89, 0x75, 0x23, 0xfe, - 0x05, 0xc1, 0x64, 0xdc, 0x11, 0x36, 0x8b, 0x86, 0x1c, 0x70, 0x5c, 0x29, 0x0e, 0x90, 0x14, 0x37, - 0x05, 0xc5, 0x3a, 0xfe, 0xb0, 0x10, 0x45, 0xf3, 0xab, 0x64, 0x93, 0x3c, 0x35, 0x8f, 0xcf, 0xf0, - 0xaf, 0x08, 0x2e, 0x2a, 0x77, 0x1d, 0x7e, 0x37, 0xf7, 0x42, 0x73, 0x76, 0xa8, 0xf6, 0x5e, 0x09, - 0xa4, 0x14, 0x76, 0x5b, 0x08, 0x5b, 0xc1, 0x46, 0x56, 0x65, 0x84, 0xe8, 0xc6, 0x89, 0xed, 0x8b, - 0xff, 0x40, 0x70, 0x39, 0x67, 0x98, 0xe2, 0x3b, 0x79, 0x94, 0x86, 0x6f, 0x04, 0xed, 0x6e, 0x69, - 0x7c, 0x41, 0x61, 0x27, 0x6f, 0x2c, 0x1c, 0x84, 0x4f, 0xf1, 0x3f, 0x08, 0x66, 0x32, 0x17, 0x36, - 0x5e, 0x2b, 0x5a, 0x39, 0xaa, 0xd7, 0x84, 0xf6, 0x41, 0x49, 0xb4, 0x94, 0xb4, 0x25, 0x24, 0x3d, - 0xc0, 0x1f, 0x95, 0x2c, 0x42, 0xb1, 0xaa, 0x23, 0xa5, 0x2f, 0x10, 0x4c, 0x67, 0x3d, 0x00, 0xf0, - 0x6a, 0x51, 0xaa, 0x8a, 0x37, 0x88, 0xb6, 0x56, 0x0e, 0x2c, 0x65, 0x6e, 0x08, 0x99, 0x77, 0xf0, - 0xda, 0xff, 0x91, 0x89, 0x7f, 0x42, 0x70, 0xfe, 0xc4, 0x16, 0xc4, 0xb5, 0xa1, 0x45, 0x95, 0xda, - 0xa8, 0xda, 0xad, 0x53, 0x61, 0xa4, 0x04, 0x53, 0x48, 0xb8, 0x81, 0x97, 0x32, 0x24, 0x90, 0x01, - 0xae, 0x21, 0x1f, 0x3a, 0x2f, 0x11, 0x5c, 0xca, 0xd8, 0x72, 0xf8, 0xfd, 0xa2, 0xd9, 0x54, 0x4c, - 0x86, 0xd5, 0x52, 0x58, 0xa9, 0xe2, 0xa1, 0x50, 0xb1, 0x81, 0xeb, 0x25, 0x2f, 0x22, 0x36, 0x2f, - 0xea, 0x1f, 0x3f, 0x3b, 0xac, 0xa0, 0xe7, 0x87, 0x15, 0xf4, 0xd7, 0x61, 0x05, 0x7d, 0x77, 0x54, - 0x19, 0x79, 0x7e, 0x54, 0x19, 0xf9, 0xf3, 0xa8, 0x32, 0xf2, 0x79, 0xcd, 0xf5, 0x78, 0x6b, 0xcf, - 0x36, 0x1c, 0xb6, 0x3b, 0x88, 0xe3, 0xb4, 0x88, 0xe7, 0x1f, 0x07, 0xdd, 0x8f, 0x87, 0xe5, 0x07, - 0x1d, 0x1a, 0xd8, 0xe3, 0xe2, 0xb7, 0xe8, 0xad, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x03, 0xab, - 0x0b, 0x81, 0x73, 0x0f, 0x00, 0x00, + // 1194 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x6f, 0xdc, 0x44, + 0x14, 0xcf, 0xa4, 0xc9, 0xd2, 0xbc, 0x7c, 0xa1, 0x69, 0x4b, 0x37, 0x9b, 0x66, 0xd3, 0x38, 0xcd, + 0x47, 0x8b, 0x6a, 0xe7, 0x03, 0x45, 0x40, 0x42, 0x4b, 0x37, 0xa1, 0x0d, 0x09, 0x91, 0x16, 0x37, + 0x02, 0x89, 0xcb, 0x6a, 0xbc, 0x3b, 0xf2, 0x9a, 0x6c, 0xec, 0xed, 0x7a, 0xb2, 0x24, 0xaa, 0x72, + 0xe1, 0x80, 0xc4, 0x0d, 0xc1, 0x9f, 0xc0, 0x05, 0x89, 0x13, 0x57, 0xb8, 0x22, 0xd1, 0x63, 0x25, + 0x24, 0x84, 0xa8, 0x14, 0xa1, 0x04, 0xc1, 0xbf, 0x81, 0x3c, 0x1e, 0xaf, 0xed, 0xd8, 0xde, 0x75, + 0x96, 0x72, 0xcb, 0x7a, 0xde, 0xc7, 0xef, 0xf7, 0x7b, 0x6f, 0xde, 0x3c, 0x05, 0xa6, 0x34, 0xa2, + 0x1d, 0xd5, 0x2c, 0x53, 0xd1, 0x58, 0xd9, 0x66, 0x64, 0xcf, 0x30, 0x75, 0xa5, 0xb9, 0xa8, 0x3c, + 0x39, 0xa0, 0x8d, 0x23, 0xb9, 0xde, 0xb0, 0x98, 0x85, 0xaf, 0x09, 0x13, 0xd9, 0x37, 0x91, 0x9b, + 0x8b, 0xb9, 0xab, 0xba, 0xa5, 0x5b, 0xdc, 0x42, 0x71, 0xfe, 0x72, 0x8d, 0x73, 0x37, 0x74, 0xcb, + 0xd2, 0x6b, 0x54, 0x21, 0x75, 0x43, 0x21, 0xa6, 0x69, 0x31, 0xc2, 0x0c, 0xcb, 0xb4, 0xc5, 0xe9, + 0x9d, 0xb2, 0x65, 0xef, 0x5b, 0xb6, 0xa2, 0x11, 0x9b, 0xba, 0x39, 0x94, 0xe6, 0xa2, 0x46, 0x19, + 0x59, 0x54, 0xea, 0x44, 0x37, 0x4c, 0x6e, 0x2c, 0x6c, 0xa5, 0x78, 0x64, 0x75, 0xd2, 0x20, 0xfb, + 0x5e, 0xbc, 0xd9, 0x78, 0x9b, 0x00, 0x50, 0x6e, 0x27, 0x5d, 0x05, 0xfc, 0xa1, 0x93, 0xad, 0xc8, + 0x9d, 0x55, 0xfa, 0xe4, 0x80, 0xda, 0x4c, 0x52, 0xe1, 0x4a, 0xe8, 0xab, 0x5d, 0xb7, 0x4c, 0x9b, + 0xe2, 0x55, 0xc8, 0xb8, 0x49, 0xb2, 0xe8, 0x26, 0x9a, 0x1f, 0x5c, 0x9a, 0x90, 0x63, 0x05, 0x90, + 0x5d, 0xb7, 0x42, 0xdf, 0xb3, 0x93, 0xc9, 0x1e, 0x55, 0xb8, 0x48, 0x65, 0x18, 0xe3, 0x31, 0x0b, + 0xbb, 0xeb, 0x1f, 0x91, 0x9a, 0x51, 0x21, 0xcc, 0x6a, 0x78, 0x09, 0xf1, 0x43, 0x00, 0x9f, 0xa6, + 0x88, 0x3e, 0x2b, 0xbb, 0x9a, 0xc8, 0x8e, 0x26, 0xb2, 0xab, 0xbb, 0xd0, 0x44, 0x2e, 0x12, 0x9d, + 0x0a, 0x5f, 0x35, 0xe0, 0x29, 0xfd, 0x80, 0x20, 0x17, 0x97, 0x45, 0x10, 0xd8, 0x82, 0x11, 0x8d, + 0x95, 0x4b, 0xcd, 0xd6, 0x49, 0x16, 0xdd, 0xbc, 0x34, 0x3f, 0xb8, 0x34, 0x9d, 0x40, 0x24, 0x18, + 0x45, 0x1d, 0xd6, 0x58, 0xd9, 0x8f, 0x89, 0x1f, 0x85, 0x20, 0xf7, 0x72, 0xc8, 0x73, 0x1d, 0x21, + 0xbb, 0x40, 0x42, 0x98, 0xef, 0x43, 0x36, 0x02, 0xd9, 0xd3, 0x65, 0x1a, 0x46, 0x9a, 0xa4, 0x56, + 0x72, 0x40, 0xd7, 0xf7, 0x4a, 0x55, 0x7a, 0xc8, 0xb5, 0x19, 0x50, 0x07, 0x9b, 0xa4, 0x56, 0x60, + 0xe5, 0xe2, 0xde, 0x26, 0x3d, 0x94, 0x68, 0x8c, 0xb2, 0x2d, 0xca, 0x9b, 0x30, 0x1c, 0xa2, 0x2c, + 0xc4, 0x4d, 0xc5, 0x78, 0x28, 0xc8, 0x58, 0x9a, 0x86, 0x29, 0xb7, 0x29, 0xa8, 0x59, 0x31, 0x4c, + 0xbd, 0xb0, 0xbb, 0xbe, 0x41, 0x6b, 0x54, 0x77, 0xdb, 0xd8, 0xeb, 0x1c, 0x1b, 0xa4, 0x76, 0x46, + 0x02, 0xd4, 0x0e, 0x8c, 0x3a, 0xa0, 0x2a, 0xfe, 0x91, 0x28, 0xc4, 0xad, 0x64, 0x58, 0x7e, 0x1c, + 0xd5, 0x29, 0x62, 0x20, 0xac, 0x54, 0x81, 0x99, 0x88, 0x00, 0x45, 0xeb, 0x33, 0xda, 0x78, 0xc0, + 0x36, 0xa9, 0xa1, 0x57, 0xd9, 0x45, 0xe4, 0xc4, 0xaf, 0x41, 0xa6, 0xca, 0xbd, 0x78, 0x51, 0xfb, + 0x54, 0xf1, 0x4b, 0xda, 0x86, 0xd9, 0x4e, 0x59, 0x04, 0xbd, 0x29, 0x18, 0x6a, 0x5a, 0xcc, 0x30, + 0xf5, 0x52, 0xdd, 0x39, 0xe7, 0x49, 0xfa, 0xd4, 0x41, 0xf7, 0x1b, 0x77, 0x91, 0xb6, 0xe1, 0x56, + 0x24, 0xd8, 0xfa, 0x41, 0xa3, 0x41, 0x4d, 0xc6, 0x0d, 0x2e, 0xd4, 0x00, 0x5a, 0x0c, 0xff, 0x70, + 0x30, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x22, 0x80, 0x7b, 0xa3, 0x80, 0xbf, 0x44, 0x30, 0xc7, + 0x93, 0x3c, 0x28, 0x33, 0xa3, 0x49, 0x43, 0xf7, 0xeb, 0xbc, 0xcc, 0x49, 0x69, 0x1e, 0xc6, 0x5c, + 0x99, 0x6e, 0x6e, 0xf9, 0x2f, 0x08, 0xe6, 0x3b, 0x63, 0x11, 0x9c, 0xd5, 0x84, 0x3b, 0xff, 0x7a, + 0x8a, 0x1b, 0xf0, 0xb1, 0xc1, 0xaa, 0x3b, 0x94, 0x91, 0xff, 0xed, 0xee, 0x4f, 0xc0, 0xb8, 0x4f, + 0x84, 0x30, 0x5a, 0x09, 0x09, 0x29, 0xad, 0xc0, 0x8d, 0xf8, 0xe3, 0xf6, 0xf5, 0x94, 0xbe, 0x46, + 0x30, 0x1d, 0xe9, 0x88, 0xe8, 0x6d, 0x4d, 0x77, 0x1f, 0x5e, 0x56, 0xd5, 0x5e, 0xa0, 0x98, 0x9e, + 0x8f, 0x9b, 0x0e, 0x9f, 0xc2, 0x58, 0x60, 0x3a, 0x58, 0x8d, 0x98, 0x39, 0x21, 0x77, 0x9c, 0x13, + 0xe1, 0xd0, 0xd7, 0xfd, 0x89, 0x11, 0x3a, 0x78, 0x79, 0x95, 0xdc, 0xf2, 0x87, 0x70, 0x60, 0x52, + 0x09, 0x9d, 0xef, 0xc2, 0x15, 0x01, 0xb2, 0xc4, 0x0e, 0x4b, 0x55, 0x62, 0x57, 0x03, 0x62, 0xbf, + 0x2a, 0x8e, 0x76, 0x0f, 0x37, 0x89, 0x5d, 0x75, 0xee, 0xf3, 0xdf, 0xbd, 0xfe, 0x2b, 0x16, 0x0c, + 0xd6, 0x9a, 0x9e, 0x19, 0xb7, 0x62, 0x3c, 0xc0, 0x50, 0x61, 0xe5, 0x8f, 0x93, 0xc9, 0x25, 0xdd, + 0x60, 0xd5, 0x03, 0x4d, 0x2e, 0x5b, 0xfb, 0x8a, 0x90, 0xa6, 0x5c, 0x25, 0x86, 0xe9, 0xfd, 0x50, + 0xd8, 0x51, 0x9d, 0xda, 0x72, 0xe1, 0xfd, 0xe2, 0xf2, 0x1b, 0x0b, 0xc5, 0x03, 0x6d, 0x9b, 0x1e, + 0xa9, 0xfd, 0x9a, 0x53, 0x62, 0xbc, 0x0b, 0xe0, 0x37, 0x01, 0x97, 0xa0, 0xfb, 0x90, 0x97, 0xbd, + 0xc6, 0x71, 0x46, 0x8a, 0xcd, 0x48, 0x83, 0x95, 0x44, 0x83, 0x5e, 0x72, 0x47, 0x0a, 0xff, 0xe6, + 0x76, 0x31, 0x9e, 0x00, 0xa0, 0x66, 0xc5, 0x33, 0xe8, 0xe3, 0x06, 0x03, 0xd4, 0x14, 0x4d, 0x8e, + 0xc7, 0x61, 0x80, 0x59, 0x8c, 0xd4, 0x4a, 0x36, 0x61, 0xd9, 0x7e, 0x7e, 0x7a, 0x99, 0x7f, 0x78, + 0x4c, 0xb8, 0xaf, 0xaf, 0x68, 0x36, 0xc3, 0x85, 0x1c, 0x68, 0x09, 0x89, 0x67, 0x60, 0xc4, 0x3b, + 0xb6, 0xcb, 0x0d, 0xa3, 0xce, 0xb2, 0xaf, 0x70, 0x93, 0x61, 0xf1, 0xf5, 0x31, 0xff, 0xb8, 0xf4, + 0xdd, 0x28, 0xf4, 0x73, 0xa1, 0xf1, 0x17, 0x08, 0x32, 0xee, 0xda, 0x82, 0x6f, 0x27, 0xf4, 0x56, + 0x74, 0x4f, 0xca, 0xdd, 0x49, 0x63, 0xea, 0x56, 0x4d, 0x9a, 0xf9, 0xfc, 0xd7, 0xbf, 0xbe, 0xe9, + 0x9d, 0xc4, 0x13, 0x4a, 0xbb, 0xf5, 0x0d, 0x7f, 0x8b, 0x60, 0x38, 0x34, 0xd0, 0xf0, 0x42, 0xbb, + 0x24, 0x71, 0xdb, 0x54, 0x6e, 0xf1, 0x02, 0x1e, 0x02, 0xdd, 0x5d, 0x8e, 0x6e, 0x0e, 0xcf, 0x28, + 0x89, 0x8b, 0x63, 0x60, 0x84, 0xe2, 0x9f, 0x10, 0x0c, 0x05, 0x03, 0x61, 0x25, 0x6d, 0x4a, 0x0f, + 0xe3, 0x42, 0x7a, 0x07, 0x01, 0x71, 0x93, 0x43, 0x2c, 0xe0, 0x77, 0x53, 0x41, 0x54, 0x9e, 0x86, + 0x27, 0xdb, 0xb1, 0xd2, 0x3a, 0xc3, 0x3f, 0x23, 0xb8, 0x16, 0xbb, 0xa0, 0xe0, 0x37, 0xdb, 0x16, + 0xb4, 0xcd, 0xe2, 0x93, 0x7b, 0xab, 0x0b, 0x4f, 0x41, 0x6c, 0x85, 0x13, 0x5b, 0xc0, 0x72, 0x52, + 0x67, 0xb8, 0xde, 0xa5, 0x73, 0x2b, 0x13, 0xfe, 0x0d, 0xc1, 0x78, 0x9b, 0x17, 0x10, 0xdf, 0x6b, + 0x07, 0xa9, 0xf3, 0x33, 0x9e, 0xbb, 0xdf, 0xb5, 0x7f, 0x4a, 0x62, 0xe7, 0x2b, 0xe6, 0x4e, 0x82, + 0x63, 0xfc, 0x0f, 0x82, 0xb1, 0xc4, 0x2d, 0x0b, 0xaf, 0xa5, 0xed, 0x9c, 0xb8, 0x15, 0x30, 0xf7, + 0x4e, 0x97, 0xde, 0x82, 0xd2, 0x0e, 0xa7, 0xf4, 0x08, 0xbf, 0xd7, 0x65, 0x13, 0xf2, 0xfd, 0xca, + 0x67, 0xfa, 0x02, 0x41, 0x36, 0x69, 0x6b, 0xc3, 0xab, 0x69, 0xa1, 0xc6, 0x2c, 0x8e, 0xb9, 0xb5, + 0xee, 0x9c, 0x05, 0xcd, 0x0d, 0x4e, 0xf3, 0x1e, 0x5e, 0xfb, 0x2f, 0x34, 0xf1, 0xf7, 0x08, 0x46, + 0xcf, 0xad, 0x2e, 0x78, 0xa9, 0x63, 0x53, 0x45, 0xd6, 0xa0, 0xdc, 0xf2, 0x85, 0x7c, 0x04, 0x05, + 0x85, 0x53, 0xb8, 0x8d, 0xe7, 0x12, 0x28, 0x10, 0xcf, 0x4f, 0x3c, 0x40, 0xf8, 0x04, 0xc1, 0xf5, + 0x84, 0xd5, 0x04, 0xbf, 0x9d, 0x56, 0xcd, 0x98, 0xc9, 0xb0, 0xda, 0x95, 0xaf, 0x60, 0xb1, 0xc5, + 0x59, 0x6c, 0xe0, 0x42, 0x97, 0x85, 0x08, 0xce, 0x8b, 0x1f, 0xdd, 0xa7, 0xc5, 0x4f, 0xd3, 0xf1, + 0x69, 0x89, 0x6c, 0x32, 0x1d, 0x9f, 0x96, 0xe8, 0xba, 0x92, 0xaa, 0x97, 0x02, 0x30, 0x95, 0xa7, + 0x31, 0xab, 0xd2, 0x71, 0xe1, 0x83, 0x67, 0xa7, 0x79, 0xf4, 0xfc, 0x34, 0x8f, 0xfe, 0x3c, 0xcd, + 0xa3, 0xaf, 0xce, 0xf2, 0x3d, 0xcf, 0xcf, 0xf2, 0x3d, 0xbf, 0x9f, 0xe5, 0x7b, 0x3e, 0xe9, 0xb8, + 0xa7, 0x1c, 0x06, 0x13, 0xf2, 0xa5, 0x45, 0xcb, 0xf0, 0xff, 0x7e, 0x2c, 0xff, 0x1b, 0x00, 0x00, + 0xff, 0xff, 0xc3, 0x09, 0x06, 0xb0, 0xe5, 0x11, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1038,6 +1193,8 @@ type QueryClient interface { ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) // BTCValidatorDelegations queries all BTC delegations of the given BTC validator BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) + // BTCDelegation retrieves delegation by corresponding staking tx hash + BTCDelegation(ctx context.Context, in *QueryBTCDelegationRequest, opts ...grpc.CallOption) (*QueryBTCDelegationResponse, error) } type queryClient struct { @@ -1129,6 +1286,15 @@ func (c *queryClient) BTCValidatorDelegations(ctx context.Context, in *QueryBTCV return out, nil } +func (c *queryClient) BTCDelegation(ctx context.Context, in *QueryBTCDelegationRequest, opts ...grpc.CallOption) (*QueryBTCDelegationResponse, error) { + out := new(QueryBTCDelegationResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCDelegation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. @@ -1150,6 +1316,8 @@ type QueryServer interface { ActivatedHeight(context.Context, *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) // BTCValidatorDelegations queries all BTC delegations of the given BTC validator BTCValidatorDelegations(context.Context, *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) + // BTCDelegation retrieves delegation by corresponding staking tx hash + BTCDelegation(context.Context, *QueryBTCDelegationRequest) (*QueryBTCDelegationResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -1183,6 +1351,9 @@ func (*UnimplementedQueryServer) ActivatedHeight(ctx context.Context, req *Query func (*UnimplementedQueryServer) BTCValidatorDelegations(ctx context.Context, req *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegations not implemented") } +func (*UnimplementedQueryServer) BTCDelegation(ctx context.Context, req *QueryBTCDelegationRequest) (*QueryBTCDelegationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCDelegation not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1350,6 +1521,24 @@ func _Query_BTCValidatorDelegations_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _Query_BTCDelegation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCDelegationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCDelegation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/BTCDelegation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCDelegation(ctx, req.(*QueryBTCDelegationRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btcstaking.v1.Query", HandlerType: (*QueryServer)(nil), @@ -1390,6 +1579,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "BTCValidatorDelegations", Handler: _Query_BTCValidatorDelegations_Handler, }, + { + MethodName: "BTCDelegation", + Handler: _Query_BTCDelegation_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btcstaking/v1/query.proto", @@ -2017,6 +2210,112 @@ func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) return len(dAtA) - i, nil } +func (m *QueryBTCDelegationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCDelegationRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCDelegationRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.StakingTxHashHex) > 0 { + i -= len(m.StakingTxHashHex) + copy(dAtA[i:], m.StakingTxHashHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingTxHashHex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCDelegationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCDelegationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.StakingScript) > 0 { + i -= len(m.StakingScript) + copy(dAtA[i:], m.StakingScript) + i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingScript))) + i-- + dAtA[i] = 0x3a + } + if len(m.StakingTx) > 0 { + i -= len(m.StakingTx) + copy(dAtA[i:], m.StakingTx) + i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingTx))) + i-- + dAtA[i] = 0x32 + } + if m.TotalSat != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.TotalSat)) + i-- + dAtA[i] = 0x28 + } + if m.EndHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.EndHeight)) + i-- + dAtA[i] = 0x20 + } + if m.StartHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x18 + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -2278,6 +2577,53 @@ func (m *QueryBTCValidatorDelegationsResponse) Size() (n int) { return n } +func (m *QueryBTCDelegationRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.StakingTxHashHex) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCDelegationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if m.StartHeight != 0 { + n += 1 + sovQuery(uint64(m.StartHeight)) + } + if m.EndHeight != 0 { + n += 1 + sovQuery(uint64(m.EndHeight)) + } + if m.TotalSat != 0 { + n += 1 + sovQuery(uint64(m.TotalSat)) + } + l = len(m.StakingTx) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.StakingScript) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3847,6 +4193,329 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCDelegationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCDelegationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCDelegationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHashHex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHashHex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCDelegationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EndHeight", wireType) + } + m.EndHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EndHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalSat", wireType) + } + m.TotalSat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalSat |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTx = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingScript", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingScript = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 03f33ca38..afb8be0bd 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -451,6 +451,60 @@ func local_request_Query_BTCValidatorDelegations_0(ctx context.Context, marshale } +func request_Query_BTCDelegation_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCDelegationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["staking_tx_hash_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "staking_tx_hash_hex") + } + + protoReq.StakingTxHashHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "staking_tx_hash_hex", err) + } + + msg, err := client.BTCDelegation(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCDelegation_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCDelegationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["staking_tx_hash_hex"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "staking_tx_hash_hex") + } + + protoReq.StakingTxHashHex, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "staking_tx_hash_hex", err) + } + + msg, err := server.BTCDelegation(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -664,6 +718,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCDelegation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCDelegation_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCDelegation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -885,6 +962,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCDelegation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCDelegation_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCDelegation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -906,6 +1003,8 @@ var ( pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCDelegation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_delegations", "staking_tx_hash_hex"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -926,4 +1025,6 @@ var ( forward_Query_ActivatedHeight_0 = runtime.ForwardResponseMessage forward_Query_BTCValidatorDelegations_0 = runtime.ForwardResponseMessage + + forward_Query_BTCDelegation_0 = runtime.ForwardResponseMessage ) From 06f5157633ff2b45fcb68f70f89390e48af26ca3 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 28 Aug 2023 09:47:19 +1000 Subject: [PATCH 064/202] finality: range query for evidences (#62) --- proto/babylon/finality/v1/query.proto | 26 ++ types/btc_schnorr_pk.go | 7 +- x/finality/client/cli/query.go | 40 ++ x/finality/keeper/grpc_query.go | 44 ++ x/finality/keeper/grpc_query_test.go | 64 +++ x/finality/types/query.pb.go | 629 +++++++++++++++++++++++--- x/finality/types/query.pb.gw.go | 83 ++++ 7 files changed, 828 insertions(+), 65 deletions(-) diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index f9df8dcdb..f1fb38d7b 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -40,6 +40,11 @@ service Query { rpc Evidence(QueryEvidenceRequest) returns (QueryEvidenceResponse) { option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/evidence"; } + + // ListEvidences queries is a range query for evidences + rpc ListEvidences(QueryListEvidencesRequest) returns (QueryListEvidencesResponse) { + option (google.api.http).get = "/babylon/finality/v1/vidences"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -145,3 +150,24 @@ message QueryEvidenceRequest { message QueryEvidenceResponse { Evidence evidence = 1; } + +// QueryListEvidencesRequest is the request type for the +// Query/ListEvidences RPC method. +message QueryListEvidencesRequest { + // start_height is the starting height that the querier specifies + // such that the RPC will only return evidences since this height + uint64 start_height = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryListEvidencesResponse is the response type for the +// Query/ListEvidences RPC method. +message QueryListEvidencesResponse { + // blocks is the list of evidences + repeated Evidence evidences = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} \ No newline at end of file diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index b321e030f..4745fce45 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" - "errors" + "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -88,9 +88,8 @@ func (pk *BIP340PubKey) Unmarshal(data []byte) error { // ensure that the bytes can be transformed to a *btcec.PublicKey object // this includes all format checks - _, err := newPK.ToBTCPK() - if err != nil { - return errors.New("bytes cannot be converted to a *btcec.PublicKey object") + if _, err := newPK.ToBTCPK(); err != nil { + return fmt.Errorf("bytes cannot be converted to a *btcec.PublicKey object: %w", err) } *pk = data diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index 61c74f38b..abf2faa59 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -14,6 +14,7 @@ import ( const ( flagQueriedBlockStatus = "queried-block-status" + flagStartHeight = "start-height" ) // GetQueryCmd returns the cli query commands for this module @@ -32,6 +33,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdBlock()) cmd.AddCommand(CmdListBlocks()) cmd.AddCommand(CmdVotesAtHeight()) + cmd.AddCommand(CmdListEvidences()) return cmd } @@ -170,3 +172,41 @@ func CmdBlock() *cobra.Command { return cmd } + +func CmdListEvidences() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-evidences", + Short: "list equivocation evidences since a given height", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + startHeight, err := cmd.Flags().GetUint64(flagStartHeight) + if err != nil { + return err + } + + res, err := queryClient.ListEvidences(cmd.Context(), &types.QueryListEvidencesRequest{ + StartHeight: startHeight, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "list-evidences") + cmd.Flags().Uint64(flagStartHeight, 0, "Starting height for scanning evidences") + + return cmd +} diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index cf9aca842..27b7bfde1 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" @@ -145,3 +146,46 @@ func (k Keeper) Evidence(ctx context.Context, req *types.QueryEvidenceRequest) ( } return resp, nil } + +// ListEvidences returns a list of evidences +func (k Keeper) ListEvidences(ctx context.Context, req *types.QueryListEvidencesRequest) (*types.QueryListEvidencesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + sdkCtx := sdk.UnwrapSDKContext(ctx) + var evidences []*types.Evidence + + store := sdkCtx.KVStore(k.storeKey) + eStore := prefix.NewStore(store, types.EvidenceKey) + + pageRes, err := query.FilteredPaginate(eStore, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) { + // NOTE: we have to strip the rest bytes after the first 32 bytes + // since there is another layer of KVStore (height -> evidence) under eStore + // in which height is uint64 thus takes 8 bytes + strippedKey := key[:bbn.BIP340PubKeyLen] + valBTCPK, err := bbn.NewBIP340PubKey(strippedKey) + if err != nil { + panic(err) // failing to unmarshal valBTCPK in KVStore can only be a programming error + } + evidence := k.GetFirstSlashableEvidence(sdkCtx, valBTCPK) + + // hit if the BTC validator has a full evidence of equivocation + if evidence != nil && evidence.BlockHeight >= req.StartHeight { + if accumulate { + evidences = append(evidences, evidence) + } + return true, nil + } + + return false, nil + }) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + resp := &types.QueryListEvidencesResponse{ + Evidences: evidences, + Pagination: pageRes, + } + return resp, nil +} diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 28998e37c..b3da6f783 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -263,3 +263,67 @@ func FuzzQueryEvidence(f *testing.F) { } }) } + +func FuzzListEvidences(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // Setup keeper and context + keeper, ctx := testkeeper.FinalityKeeper(t, nil) + ctx = sdk.UnwrapSDKContext(ctx) + + // generate a random list of evidences since startHeight + startHeight := datagen.RandomInt(r, 1000) + 100 + numEvidences := datagen.RandomInt(r, 100) + 10 + evidences := map[string]*types.Evidence{} + for i := uint64(0); i < numEvidences; i++ { + // random key pair + sk, pk, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcPK := bbn.NewBIP340PubKeyFromBTCPK(pk) + // random height + height := datagen.RandomInt(r, 100) + startHeight + 1 + // generate evidence + evidence, err := datagen.GenRandomEvidence(r, sk, height) + require.NoError(t, err) + // add evidence to map and finlaity keeper + evidences[btcPK.MarshalHex()] = evidence + keeper.SetEvidence(ctx, evidence) + } + + // generate another list of evidences before startHeight + // these evidences will not be included in the response if + // the request specifies the above startHeight + for i := uint64(0); i < numEvidences; i++ { + // random key pair + sk, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + // random height before startHeight + height := datagen.RandomInt(r, int(startHeight)) + // generate evidence + evidence, err := datagen.GenRandomEvidence(r, sk, height) + require.NoError(t, err) + // add evidence to finlaity keeper + keeper.SetEvidence(ctx, evidence) + } + + // perform a query to fetch all evidences and assert consistency + limit := datagen.RandomInt(r, int(numEvidences)) + 1 + req := &types.QueryListEvidencesRequest{ + StartHeight: startHeight, + Pagination: &query.PageRequest{ + CountTotal: true, + Limit: limit, + }, + } + resp, err := keeper.ListEvidences(ctx, req) + require.NoError(t, err) + require.LessOrEqual(t, len(resp.Evidences), int(limit)) // check if pagination takes effect + require.EqualValues(t, resp.Pagination.Total, numEvidences) // ensure evidences before startHeight are not included + for _, actualEvidence := range resp.Evidences { + require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].CanonicalLastCommitHash, actualEvidence.CanonicalLastCommitHash) + require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].ForkLastCommitHash, actualEvidence.ForkLastCommitHash) + } + }) +} diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index b853c6b5d..f4acb6468 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -640,6 +640,119 @@ func (m *QueryEvidenceResponse) GetEvidence() *Evidence { return nil } +// QueryListEvidencesRequest is the request type for the +// Query/ListEvidences RPC method. +type QueryListEvidencesRequest struct { + // start_height is the starting height that the querier specifies + // such that the RPC will only return evidences since this height + StartHeight uint64 `protobuf:"varint,1,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListEvidencesRequest) Reset() { *m = QueryListEvidencesRequest{} } +func (m *QueryListEvidencesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryListEvidencesRequest) ProtoMessage() {} +func (*QueryListEvidencesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{12} +} +func (m *QueryListEvidencesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListEvidencesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListEvidencesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListEvidencesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListEvidencesRequest.Merge(m, src) +} +func (m *QueryListEvidencesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryListEvidencesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListEvidencesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListEvidencesRequest proto.InternalMessageInfo + +func (m *QueryListEvidencesRequest) GetStartHeight() uint64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +func (m *QueryListEvidencesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryListEvidencesResponse is the response type for the +// Query/ListEvidences RPC method. +type QueryListEvidencesResponse struct { + // blocks is the list of evidences + Evidences []*Evidence `protobuf:"bytes,1,rep,name=evidences,proto3" json:"evidences,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryListEvidencesResponse) Reset() { *m = QueryListEvidencesResponse{} } +func (m *QueryListEvidencesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryListEvidencesResponse) ProtoMessage() {} +func (*QueryListEvidencesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_32bddab77af6fdae, []int{13} +} +func (m *QueryListEvidencesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryListEvidencesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryListEvidencesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryListEvidencesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryListEvidencesResponse.Merge(m, src) +} +func (m *QueryListEvidencesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryListEvidencesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryListEvidencesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryListEvidencesResponse proto.InternalMessageInfo + +func (m *QueryListEvidencesResponse) GetEvidences() []*Evidence { + if m != nil { + return m.Evidences + } + return nil +} + +func (m *QueryListEvidencesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + func init() { proto.RegisterEnum("babylon.finality.v1.QueriedBlockStatus", QueriedBlockStatus_name, QueriedBlockStatus_value) proto.RegisterType((*QueryParamsRequest)(nil), "babylon.finality.v1.QueryParamsRequest") @@ -655,72 +768,79 @@ func init() { proto.RegisterType((*QueryVotesAtHeightResponse)(nil), "babylon.finality.v1.QueryVotesAtHeightResponse") proto.RegisterType((*QueryEvidenceRequest)(nil), "babylon.finality.v1.QueryEvidenceRequest") proto.RegisterType((*QueryEvidenceResponse)(nil), "babylon.finality.v1.QueryEvidenceResponse") + proto.RegisterType((*QueryListEvidencesRequest)(nil), "babylon.finality.v1.QueryListEvidencesRequest") + proto.RegisterType((*QueryListEvidencesResponse)(nil), "babylon.finality.v1.QueryListEvidencesResponse") } func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 951 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x41, 0x4f, 0x1b, 0x47, - 0x14, 0xf6, 0x98, 0x60, 0x92, 0x07, 0xa4, 0x64, 0xe2, 0xa6, 0x74, 0xd3, 0x18, 0xb2, 0xb4, 0x40, - 0x21, 0xda, 0x0d, 0x26, 0x4d, 0xd3, 0x56, 0x11, 0xc2, 0x2a, 0x34, 0x34, 0x81, 0xb8, 0x8b, 0x54, - 0xa9, 0xb9, 0x58, 0xb3, 0xeb, 0xa9, 0xbd, 0x62, 0xbd, 0xb3, 0xf1, 0xce, 0xae, 0xb0, 0xa2, 0x48, - 0x55, 0x0f, 0x3d, 0x55, 0x6a, 0xa5, 0x9e, 0x73, 0x68, 0x4f, 0xfd, 0x29, 0x51, 0x4f, 0x48, 0xbd, - 0x54, 0x1c, 0x50, 0x05, 0xfd, 0x21, 0xd5, 0xce, 0x8c, 0x6d, 0x4c, 0xd6, 0xb1, 0x13, 0x71, 0xf3, - 0xcc, 0x7e, 0xef, 0xbd, 0xef, 0x7d, 0xef, 0xcd, 0x07, 0x30, 0x63, 0x13, 0xbb, 0xe5, 0x31, 0xdf, - 0xfc, 0xde, 0xf5, 0x89, 0xe7, 0xf2, 0x96, 0x19, 0xaf, 0x98, 0x4f, 0x23, 0xda, 0x6c, 0x19, 0x41, - 0x93, 0x71, 0x86, 0xaf, 0x2a, 0x80, 0xd1, 0x06, 0x18, 0xf1, 0x8a, 0x96, 0xaf, 0xb1, 0x1a, 0x13, - 0xdf, 0xcd, 0xe4, 0x97, 0x84, 0x6a, 0x1f, 0xd4, 0x18, 0xab, 0x79, 0xd4, 0x24, 0x81, 0x6b, 0x12, - 0xdf, 0x67, 0x9c, 0x70, 0x97, 0xf9, 0xa1, 0xfa, 0xba, 0xe4, 0xb0, 0xb0, 0xc1, 0x42, 0xd3, 0x26, - 0x21, 0x95, 0x15, 0xcc, 0x78, 0xc5, 0xa6, 0x9c, 0xac, 0x98, 0x01, 0xa9, 0xb9, 0xbe, 0x00, 0x2b, - 0xec, 0x6c, 0x1a, 0xab, 0x80, 0x34, 0x49, 0xa3, 0x9d, 0x4d, 0x4f, 0x43, 0x74, 0x28, 0x0a, 0x8c, - 0x9e, 0x07, 0xfc, 0x4d, 0x52, 0xa7, 0x2c, 0x02, 0x2d, 0xfa, 0x34, 0xa2, 0x21, 0xd7, 0xcb, 0x70, - 0xb5, 0xe7, 0x36, 0x0c, 0x98, 0x1f, 0x52, 0xfc, 0x19, 0xe4, 0x64, 0x81, 0x69, 0x34, 0x8b, 0x16, - 0xc7, 0x8b, 0xd7, 0x8d, 0x94, 0xc6, 0x0d, 0x19, 0x54, 0xba, 0xf0, 0xf2, 0x68, 0x26, 0x63, 0xa9, - 0x00, 0xfd, 0x17, 0x04, 0xb3, 0x22, 0xe5, 0x23, 0x37, 0xe4, 0xe5, 0xc8, 0xf6, 0x5c, 0xc7, 0x22, - 0x7e, 0x95, 0x35, 0x7c, 0x1a, 0xb6, 0xcb, 0xe2, 0x39, 0xb8, 0x1c, 0x13, 0xaf, 0x62, 0x73, 0xa7, - 0x12, 0xec, 0x55, 0xea, 0x74, 0x5f, 0xd4, 0xb9, 0x64, 0x8d, 0xc7, 0xc4, 0x2b, 0x71, 0xa7, 0xbc, - 0xf7, 0x80, 0xee, 0xe3, 0x4d, 0x80, 0xae, 0x16, 0xd3, 0x59, 0x41, 0x64, 0xde, 0x90, 0xc2, 0x19, - 0x89, 0x70, 0x86, 0x1c, 0x8d, 0x12, 0xce, 0x28, 0x93, 0x1a, 0x55, 0x05, 0xac, 0x53, 0x91, 0xfa, - 0x41, 0x16, 0x6e, 0xbe, 0x86, 0x91, 0x6a, 0xf9, 0x0f, 0x04, 0x13, 0x41, 0x64, 0x57, 0x9a, 0xc4, - 0xaf, 0x56, 0x1a, 0x24, 0x98, 0x46, 0xb3, 0x23, 0x8b, 0xe3, 0xc5, 0xcd, 0xd4, 0xce, 0x07, 0xa6, - 0x33, 0xca, 0x91, 0x9d, 0xdc, 0x6e, 0x93, 0x60, 0xc3, 0xe7, 0xcd, 0x56, 0xe9, 0xde, 0xe1, 0xd1, - 0xcc, 0x9d, 0x9a, 0xcb, 0xeb, 0x91, 0x6d, 0x38, 0xac, 0x61, 0xaa, 0xac, 0x4e, 0x9d, 0xb8, 0x7e, - 0xfb, 0x60, 0xf2, 0x56, 0x40, 0x43, 0x63, 0xd7, 0xa9, 0xfb, 0xac, 0xd9, 0x54, 0x19, 0x2c, 0x08, - 0x3a, 0xa9, 0xf0, 0x57, 0x29, 0x92, 0x2c, 0x0c, 0x94, 0x44, 0x52, 0x3a, 0xad, 0x89, 0x76, 0x1f, - 0xde, 0x39, 0xc3, 0x10, 0x4f, 0xc1, 0xc8, 0x1e, 0x6d, 0x89, 0x41, 0x5c, 0xb0, 0x92, 0x9f, 0x38, - 0x0f, 0xa3, 0x31, 0xf1, 0x22, 0x2a, 0x0a, 0x4d, 0x58, 0xf2, 0xf0, 0x79, 0xf6, 0x1e, 0xd2, 0x97, - 0xe1, 0x8a, 0x90, 0xa0, 0xe4, 0x31, 0x67, 0xaf, 0x3d, 0xd4, 0x6b, 0x90, 0xab, 0x53, 0xb7, 0x56, - 0xe7, 0x2a, 0x87, 0x3a, 0xe9, 0xdb, 0x6a, 0xf3, 0x14, 0x58, 0xe9, 0xfd, 0x29, 0x8c, 0xda, 0xc9, - 0x85, 0xda, 0xb0, 0x9b, 0xa9, 0x3a, 0x6f, 0xf9, 0x55, 0xba, 0x4f, 0xab, 0x32, 0x52, 0xe2, 0xf5, - 0xdf, 0x11, 0x5c, 0xeb, 0xe8, 0x2f, 0xbe, 0x74, 0xd6, 0x6a, 0x0d, 0x72, 0x21, 0x27, 0x3c, 0x92, - 0x6b, 0x7b, 0xb9, 0xb8, 0xd0, 0x77, 0x78, 0xae, 0x4a, 0xba, 0x2b, 0xe0, 0x96, 0x0a, 0x3b, 0xb7, - 0x95, 0x7b, 0x81, 0xe0, 0xbd, 0x57, 0x38, 0x76, 0xdf, 0x96, 0x68, 0x24, 0x54, 0x1b, 0x36, 0x44, - 0xe7, 0x2a, 0xe0, 0xdc, 0xc6, 0xaf, 0xaf, 0xc2, 0xfb, 0x82, 0xde, 0xb7, 0x8c, 0xd3, 0x70, 0x9d, - 0x3f, 0x10, 0x83, 0x1a, 0x34, 0xc7, 0x06, 0x68, 0x69, 0x41, 0xaa, 0xad, 0xc7, 0x30, 0x26, 0x9f, - 0xb3, 0xec, 0x6b, 0xa2, 0x74, 0xf7, 0xf0, 0x68, 0xa6, 0x38, 0xdc, 0xc6, 0x97, 0xb6, 0xca, 0xab, - 0x77, 0x6e, 0x97, 0x23, 0xfb, 0x21, 0x6d, 0x59, 0x39, 0x3b, 0x31, 0x80, 0x50, 0xff, 0x02, 0xf2, - 0xa2, 0xdc, 0x46, 0xec, 0x56, 0xa9, 0xef, 0xd0, 0x37, 0xf1, 0x0e, 0xdd, 0x82, 0x77, 0xcf, 0x04, - 0x77, 0xd4, 0xbf, 0x48, 0xd5, 0x9d, 0xda, 0xbc, 0x1b, 0xa9, 0xfa, 0x77, 0x02, 0x3b, 0xf0, 0xa5, - 0x35, 0xb9, 0xc7, 0xbd, 0xab, 0x83, 0xaf, 0xc0, 0xe4, 0xce, 0xe3, 0x9d, 0xca, 0xe6, 0xd6, 0xce, - 0xfa, 0xa3, 0xad, 0x27, 0x1b, 0x5f, 0x4e, 0x65, 0xf0, 0x24, 0x5c, 0xea, 0x1e, 0x11, 0x1e, 0x83, - 0x91, 0xf5, 0x9d, 0xef, 0xa6, 0xb2, 0xc5, 0xbf, 0xc6, 0x60, 0x54, 0xb0, 0xc2, 0x3f, 0x20, 0xc8, - 0x49, 0xf7, 0xc4, 0xfd, 0x77, 0xb4, 0xd7, 0xaa, 0xb5, 0xc5, 0xc1, 0x40, 0xd9, 0xa3, 0x3e, 0xf7, - 0xe3, 0xdf, 0xff, 0xfd, 0x96, 0xbd, 0x81, 0xaf, 0x9b, 0xfd, 0xff, 0x72, 0xe0, 0x43, 0x04, 0xf9, - 0x34, 0x07, 0xc3, 0x9f, 0xbc, 0xa9, 0xe3, 0x49, 0x7a, 0x77, 0xdf, 0xce, 0x28, 0xf5, 0x5d, 0x41, - 0x76, 0x1b, 0x3f, 0x4c, 0x25, 0x9b, 0x4c, 0x39, 0x26, 0x9e, 0x5b, 0x25, 0x9c, 0x35, 0x43, 0xf3, - 0x59, 0xef, 0xe4, 0x9f, 0x9b, 0x81, 0x48, 0x2b, 0x4c, 0x5b, 0xe6, 0xad, 0x78, 0x6e, 0xc8, 0xf1, - 0x4f, 0x08, 0x46, 0xc5, 0x90, 0xf0, 0x7c, 0x7f, 0x5a, 0xa7, 0xcd, 0x4b, 0x5b, 0x18, 0x88, 0x53, - 0x7c, 0x6f, 0x09, 0xbe, 0xf3, 0xf8, 0xc3, 0x74, 0xbe, 0xe2, 0xa1, 0x9a, 0xcf, 0xe4, 0x93, 0x79, - 0x8e, 0x7f, 0x46, 0x00, 0x5d, 0x0f, 0xc0, 0xcb, 0xaf, 0x17, 0xa9, 0xc7, 0xcd, 0xb4, 0x5b, 0xc3, - 0x81, 0x87, 0x1a, 0xba, 0x32, 0x90, 0x17, 0x08, 0x26, 0x7b, 0x9e, 0x2f, 0x36, 0xfa, 0x17, 0x49, - 0x33, 0x07, 0xcd, 0x1c, 0x1a, 0xaf, 0x78, 0x2d, 0x0b, 0x5e, 0x1f, 0xe1, 0xb9, 0x54, 0x5e, 0x71, - 0x12, 0xd3, 0x95, 0xeb, 0x4f, 0x04, 0x17, 0xdb, 0x2f, 0x0f, 0x7f, 0xdc, 0xbf, 0xd4, 0x19, 0x4f, - 0xd0, 0x96, 0x86, 0x81, 0x2a, 0x42, 0x1b, 0x82, 0xd0, 0x1a, 0xbe, 0xff, 0x56, 0x0b, 0xd7, 0x76, - 0x83, 0xd2, 0xd7, 0x2f, 0x8f, 0x0b, 0xe8, 0xe0, 0xb8, 0x80, 0xfe, 0x3d, 0x2e, 0xa0, 0x5f, 0x4f, - 0x0a, 0x99, 0x83, 0x93, 0x42, 0xe6, 0x9f, 0x93, 0x42, 0xe6, 0xc9, 0xed, 0x41, 0xa6, 0xb7, 0xdf, - 0xad, 0x28, 0xfc, 0xcf, 0xce, 0x89, 0x7f, 0xd1, 0x56, 0xff, 0x0f, 0x00, 0x00, 0xff, 0xff, 0xf5, - 0xfd, 0x8b, 0x8b, 0x80, 0x0a, 0x00, 0x00, + // 1029 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x4f, 0x1b, 0x47, + 0x14, 0xf6, 0x98, 0xe0, 0xc0, 0x03, 0x52, 0x32, 0x71, 0x53, 0xea, 0x14, 0x1b, 0x96, 0x06, 0x28, + 0x44, 0xbb, 0xc1, 0xa4, 0x69, 0xda, 0x28, 0x42, 0x58, 0x85, 0x86, 0x26, 0x10, 0x77, 0x91, 0x2a, + 0x35, 0x17, 0x6b, 0xd6, 0x9e, 0xda, 0x2b, 0xd6, 0x3b, 0x1b, 0xef, 0x78, 0x85, 0x15, 0x45, 0xaa, + 0x7a, 0xc8, 0xa9, 0x52, 0x2b, 0xf5, 0xd0, 0x53, 0x0e, 0xcd, 0xa9, 0x7f, 0x4a, 0x8e, 0x48, 0xbd, + 0x54, 0x1c, 0x50, 0x05, 0xf9, 0x43, 0xaa, 0x9d, 0x99, 0xb5, 0x31, 0x5d, 0x63, 0x27, 0xe2, 0xe6, + 0x9d, 0xf9, 0xde, 0x7b, 0xdf, 0xfb, 0xde, 0x8f, 0x31, 0xe4, 0x2c, 0x62, 0xb5, 0x1c, 0xe6, 0x1a, + 0x3f, 0xda, 0x2e, 0x71, 0x6c, 0xde, 0x32, 0x82, 0x15, 0xe3, 0x59, 0x93, 0x36, 0x5a, 0xba, 0xd7, + 0x60, 0x9c, 0xe1, 0x6b, 0x0a, 0xa0, 0x47, 0x00, 0x3d, 0x58, 0xc9, 0xa4, 0xab, 0xac, 0xca, 0xc4, + 0xbd, 0x11, 0xfe, 0x92, 0xd0, 0xcc, 0x27, 0x55, 0xc6, 0xaa, 0x0e, 0x35, 0x88, 0x67, 0x1b, 0xc4, + 0x75, 0x19, 0x27, 0xdc, 0x66, 0xae, 0xaf, 0x6e, 0x97, 0xca, 0xcc, 0xaf, 0x33, 0xdf, 0xb0, 0x88, + 0x4f, 0x65, 0x04, 0x23, 0x58, 0xb1, 0x28, 0x27, 0x2b, 0x86, 0x47, 0xaa, 0xb6, 0x2b, 0xc0, 0x0a, + 0x3b, 0x13, 0xc7, 0xca, 0x23, 0x0d, 0x52, 0x8f, 0xbc, 0x69, 0x71, 0x88, 0x36, 0x45, 0x81, 0xd1, + 0xd2, 0x80, 0xbf, 0x0b, 0xe3, 0x14, 0x85, 0xa1, 0x49, 0x9f, 0x35, 0xa9, 0xcf, 0xb5, 0x22, 0x5c, + 0xeb, 0x3a, 0xf5, 0x3d, 0xe6, 0xfa, 0x14, 0x7f, 0x09, 0x29, 0x19, 0x60, 0x0a, 0xcd, 0xa0, 0xc5, + 0xb1, 0xfc, 0x0d, 0x3d, 0x26, 0x71, 0x5d, 0x1a, 0x15, 0x2e, 0xbd, 0x39, 0xca, 0x25, 0x4c, 0x65, + 0xa0, 0xfd, 0x8a, 0x60, 0x46, 0xb8, 0x7c, 0x6c, 0xfb, 0xbc, 0xd8, 0xb4, 0x1c, 0xbb, 0x6c, 0x12, + 0xb7, 0xc2, 0xea, 0x2e, 0xf5, 0xa3, 0xb0, 0x78, 0x0e, 0xae, 0x04, 0xc4, 0x29, 0x59, 0xbc, 0x5c, + 0xf2, 0xf6, 0x4a, 0x35, 0xba, 0x2f, 0xe2, 0x8c, 0x9a, 0x63, 0x01, 0x71, 0x0a, 0xbc, 0x5c, 0xdc, + 0x7b, 0x48, 0xf7, 0xf1, 0x26, 0x40, 0x47, 0x8b, 0xa9, 0xa4, 0x20, 0x32, 0xaf, 0x4b, 0xe1, 0xf4, + 0x50, 0x38, 0x5d, 0x96, 0x46, 0x09, 0xa7, 0x17, 0x49, 0x95, 0xaa, 0x00, 0xe6, 0x29, 0x4b, 0xed, + 0x20, 0x09, 0xb3, 0xe7, 0x30, 0x52, 0x29, 0xbf, 0x46, 0x30, 0xee, 0x35, 0xad, 0x52, 0x83, 0xb8, + 0x95, 0x52, 0x9d, 0x78, 0x53, 0x68, 0x66, 0x68, 0x71, 0x2c, 0xbf, 0x19, 0x9b, 0x79, 0x5f, 0x77, + 0x7a, 0xb1, 0x69, 0x85, 0xa7, 0xdb, 0xc4, 0xdb, 0x70, 0x79, 0xa3, 0x55, 0xb8, 0x77, 0x78, 0x94, + 0xbb, 0x53, 0xb5, 0x79, 0xad, 0x69, 0xe9, 0x65, 0x56, 0x37, 0x94, 0xd7, 0x72, 0x8d, 0xd8, 0x6e, + 0xf4, 0x61, 0xf0, 0x96, 0x47, 0x7d, 0x7d, 0xb7, 0x5c, 0x73, 0x59, 0xa3, 0xa1, 0x3c, 0x98, 0xe0, + 0xb5, 0x5d, 0xe1, 0x6f, 0x62, 0x24, 0x59, 0xe8, 0x2b, 0x89, 0xa4, 0x74, 0x5a, 0x93, 0xcc, 0x03, + 0xf8, 0xe0, 0x0c, 0x43, 0x3c, 0x09, 0x43, 0x7b, 0xb4, 0x25, 0x0a, 0x71, 0xc9, 0x0c, 0x7f, 0xe2, + 0x34, 0x0c, 0x07, 0xc4, 0x69, 0x52, 0x11, 0x68, 0xdc, 0x94, 0x1f, 0x5f, 0x25, 0xef, 0x21, 0x6d, + 0x19, 0xae, 0x0a, 0x09, 0x0a, 0x0e, 0x2b, 0xef, 0x45, 0x45, 0xbd, 0x0e, 0xa9, 0x1a, 0xb5, 0xab, + 0x35, 0xae, 0x7c, 0xa8, 0x2f, 0x6d, 0x5b, 0x75, 0x9e, 0x02, 0x2b, 0xbd, 0xbf, 0x80, 0x61, 0x2b, + 0x3c, 0x50, 0x1d, 0x36, 0x1b, 0xab, 0xf3, 0x96, 0x5b, 0xa1, 0xfb, 0xb4, 0x22, 0x2d, 0x25, 0x5e, + 0xfb, 0x13, 0xc1, 0xf5, 0xb6, 0xfe, 0xe2, 0xa6, 0xdd, 0x56, 0x6b, 0x90, 0xf2, 0x39, 0xe1, 0x4d, + 0xd9, 0xb6, 0x57, 0xf2, 0x0b, 0x3d, 0x8b, 0x67, 0x2b, 0xa7, 0xbb, 0x02, 0x6e, 0x2a, 0xb3, 0x0b, + 0x6b, 0xb9, 0x57, 0x08, 0x3e, 0xfa, 0x1f, 0xc7, 0xce, 0x6c, 0x89, 0x44, 0x7c, 0xd5, 0x61, 0x03, + 0x64, 0xae, 0x0c, 0x2e, 0xac, 0xfc, 0xda, 0x2a, 0x7c, 0x2c, 0xe8, 0x7d, 0xcf, 0x38, 0xf5, 0xd7, + 0xf9, 0x43, 0x51, 0xa8, 0x7e, 0x75, 0xac, 0x43, 0x26, 0xce, 0x48, 0xa5, 0xf5, 0x04, 0x2e, 0xcb, + 0x71, 0x96, 0x79, 0x8d, 0x17, 0xee, 0x1e, 0x1e, 0xe5, 0xf2, 0x83, 0x75, 0x7c, 0x61, 0xab, 0xb8, + 0x7a, 0xe7, 0x76, 0xb1, 0x69, 0x3d, 0xa2, 0x2d, 0x33, 0x65, 0x85, 0x0b, 0xc0, 0xd7, 0xee, 0x43, + 0x5a, 0x84, 0xdb, 0x08, 0xec, 0x0a, 0x75, 0xcb, 0xf4, 0x5d, 0x76, 0x87, 0x66, 0xc2, 0x87, 0x67, + 0x8c, 0xdb, 0xea, 0x8f, 0x50, 0x75, 0xa6, 0x3a, 0x6f, 0x3a, 0x56, 0xff, 0xb6, 0x61, 0x1b, 0xae, + 0xbd, 0x44, 0x4a, 0xb5, 0xb0, 0xa8, 0xd1, 0x7d, 0xbb, 0xf7, 0x66, 0x61, 0xdc, 0xe7, 0xa4, 0xc1, + 0x4b, 0x5d, 0xda, 0x8d, 0x89, 0x33, 0x29, 0xd5, 0x85, 0x75, 0xd7, 0x6b, 0xa4, 0x2a, 0x71, 0x86, + 0x88, 0x4a, 0xf1, 0x3e, 0x8c, 0x46, 0x9c, 0xa3, 0x1e, 0xeb, 0x93, 0x63, 0x07, 0x7f, 0x61, 0x2d, + 0xb6, 0xb4, 0x26, 0xa7, 0xbe, 0x7b, 0xd0, 0xf0, 0x55, 0x98, 0xd8, 0x79, 0xb2, 0x53, 0xda, 0xdc, + 0xda, 0x59, 0x7f, 0xbc, 0xf5, 0x74, 0xe3, 0xeb, 0xc9, 0x04, 0x9e, 0x80, 0xd1, 0xce, 0x27, 0xc2, + 0x97, 0x61, 0x68, 0x7d, 0xe7, 0x87, 0xc9, 0x64, 0xfe, 0xed, 0x08, 0x0c, 0x8b, 0x2c, 0xf1, 0x4f, + 0x08, 0x52, 0xf2, 0xad, 0xc1, 0xbd, 0x27, 0xba, 0xfb, 0x61, 0xcb, 0x2c, 0xf6, 0x07, 0x4a, 0xd2, + 0xda, 0xdc, 0xcf, 0x7f, 0xbf, 0xfd, 0x3d, 0x39, 0x8d, 0x6f, 0x18, 0xbd, 0xdf, 0x59, 0x7c, 0x88, + 0x20, 0x1d, 0xb7, 0xef, 0xf1, 0xe7, 0xef, 0xfa, 0x3e, 0x48, 0x7a, 0x77, 0xdf, 0xef, 0x59, 0xd1, + 0x76, 0x05, 0xd9, 0x6d, 0xfc, 0x28, 0x96, 0x6c, 0x38, 0x13, 0x01, 0x71, 0xec, 0x0a, 0xe1, 0xac, + 0xe1, 0x1b, 0xcf, 0xbb, 0xe7, 0xe4, 0x85, 0xe1, 0x09, 0xb7, 0xe2, 0x89, 0x93, 0x7e, 0x4b, 0x8e, + 0xed, 0x73, 0xfc, 0x12, 0xc1, 0xb0, 0x28, 0x12, 0x9e, 0xef, 0x4d, 0xeb, 0xf4, 0xaa, 0xcf, 0x2c, + 0xf4, 0xc5, 0x29, 0xbe, 0xb7, 0x04, 0xdf, 0x79, 0xfc, 0x69, 0x3c, 0x5f, 0xb1, 0xd6, 0x8c, 0xe7, + 0x72, 0x64, 0x5e, 0xe0, 0x5f, 0x10, 0x40, 0x67, 0x63, 0xe2, 0xe5, 0xf3, 0x45, 0xea, 0xda, 0xfd, + 0x99, 0x5b, 0x83, 0x81, 0x07, 0x2a, 0xba, 0x5a, 0xb7, 0xaf, 0x10, 0x4c, 0x74, 0x2d, 0x3b, 0xac, + 0xf7, 0x0e, 0x12, 0xb7, 0x4a, 0x33, 0xc6, 0xc0, 0x78, 0xc5, 0x6b, 0x59, 0xf0, 0xba, 0x89, 0xe7, + 0x62, 0x79, 0x05, 0xa1, 0x4d, 0x47, 0xae, 0xbf, 0x10, 0x8c, 0x44, 0x33, 0x8c, 0x3f, 0xeb, 0x1d, + 0xea, 0xcc, 0x06, 0xcd, 0x2c, 0x0d, 0x02, 0x55, 0x84, 0x36, 0x04, 0xa1, 0x35, 0xfc, 0xe0, 0xbd, + 0x1a, 0x2e, 0xda, 0x2b, 0xf8, 0x0f, 0x04, 0x13, 0x5d, 0xdb, 0xea, 0x3c, 0x29, 0xe3, 0xf6, 0xeb, + 0x79, 0x52, 0xc6, 0xae, 0x41, 0xed, 0xa6, 0x60, 0x9e, 0xc3, 0xd3, 0xf1, 0x52, 0x2a, 0x78, 0xe1, + 0xdb, 0x37, 0xc7, 0x59, 0x74, 0x70, 0x9c, 0x45, 0xff, 0x1e, 0x67, 0xd1, 0x6f, 0x27, 0xd9, 0xc4, + 0xc1, 0x49, 0x36, 0xf1, 0xcf, 0x49, 0x36, 0xf1, 0xf4, 0x76, 0xbf, 0xc7, 0x6b, 0xbf, 0xe3, 0x51, + 0xbc, 0x63, 0x56, 0x4a, 0xfc, 0xd5, 0x5e, 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xf7, 0x84, + 0x5e, 0x48, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -747,6 +867,8 @@ type QueryClient interface { VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) // Evidence queries the first evidence which can be used for extracting the BTC SK Evidence(ctx context.Context, in *QueryEvidenceRequest, opts ...grpc.CallOption) (*QueryEvidenceResponse, error) + // ListEvidences queries is a range query for evidences + ListEvidences(ctx context.Context, in *QueryListEvidencesRequest, opts ...grpc.CallOption) (*QueryListEvidencesResponse, error) } type queryClient struct { @@ -811,6 +933,15 @@ func (c *queryClient) Evidence(ctx context.Context, in *QueryEvidenceRequest, op return out, nil } +func (c *queryClient) ListEvidences(ctx context.Context, in *QueryListEvidencesRequest, opts ...grpc.CallOption) (*QueryListEvidencesResponse, error) { + out := new(QueryListEvidencesResponse) + err := c.cc.Invoke(ctx, "/babylon.finality.v1.Query/ListEvidences", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. @@ -825,6 +956,8 @@ type QueryServer interface { VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) // Evidence queries the first evidence which can be used for extracting the BTC SK Evidence(context.Context, *QueryEvidenceRequest) (*QueryEvidenceResponse, error) + // ListEvidences queries is a range query for evidences + ListEvidences(context.Context, *QueryListEvidencesRequest) (*QueryListEvidencesResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -849,6 +982,9 @@ func (*UnimplementedQueryServer) VotesAtHeight(ctx context.Context, req *QueryVo func (*UnimplementedQueryServer) Evidence(ctx context.Context, req *QueryEvidenceRequest) (*QueryEvidenceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Evidence not implemented") } +func (*UnimplementedQueryServer) ListEvidences(ctx context.Context, req *QueryListEvidencesRequest) (*QueryListEvidencesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListEvidences not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -962,6 +1098,24 @@ func _Query_Evidence_Handler(srv interface{}, ctx context.Context, dec func(inte return interceptor(ctx, in, info, handler) } +func _Query_ListEvidences_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryListEvidencesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ListEvidences(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.finality.v1.Query/ListEvidences", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ListEvidences(ctx, req.(*QueryListEvidencesRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.finality.v1.Query", HandlerType: (*QueryServer)(nil), @@ -990,6 +1144,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Evidence", Handler: _Query_Evidence_Handler, }, + { + MethodName: "ListEvidences", + Handler: _Query_ListEvidences_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/finality/v1/query.proto", @@ -1434,6 +1592,95 @@ func (m *QueryEvidenceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *QueryListEvidencesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryListEvidencesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryListEvidencesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.StartHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryListEvidencesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryListEvidencesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryListEvidencesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Evidences) > 0 { + for iNdEx := len(m.Evidences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Evidences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1621,6 +1868,41 @@ func (m *QueryEvidenceResponse) Size() (n int) { return n } +func (m *QueryListEvidencesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.StartHeight != 0 { + n += 1 + sovQuery(uint64(m.StartHeight)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryListEvidencesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Evidences) > 0 { + for _, e := range m.Evidences { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2782,6 +3064,231 @@ func (m *QueryEvidenceResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryListEvidencesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListEvidencesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListEvidencesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryListEvidencesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryListEvidencesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryListEvidencesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Evidences = append(m.Evidences, &Evidence{}) + if err := m.Evidences[len(m.Evidences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index c1c2903d0..eb8a719fa 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -321,6 +321,42 @@ func local_request_Query_Evidence_0(ctx context.Context, marshaler runtime.Marsh } +var ( + filter_Query_ListEvidences_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_ListEvidences_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListEvidencesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListEvidences_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListEvidences(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ListEvidences_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryListEvidencesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ListEvidences_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListEvidences(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -465,6 +501,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_ListEvidences_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ListEvidences_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListEvidences_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -626,6 +685,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_ListEvidences_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ListEvidences_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ListEvidences_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -641,6 +720,8 @@ var ( pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Evidence_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "evidence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ListEvidences_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "vidences"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -655,4 +736,6 @@ var ( forward_Query_VotesAtHeight_0 = runtime.ForwardResponseMessage forward_Query_Evidence_0 = runtime.ForwardResponseMessage + + forward_Query_ListEvidences_0 = runtime.ForwardResponseMessage ) From 015aadff5748133b2a652c435043860f2264e2d4 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 29 Aug 2023 15:43:33 +1000 Subject: [PATCH 065/202] fix: verifying a timelock of BTC delegation has more than w BTC blocks left (#63) --- x/btcstaking/keeper/keeper.go | 26 -------------------------- x/btcstaking/keeper/msg_server.go | 26 +++++++++++++++++++------- x/btcstaking/keeper/msg_server_test.go | 3 ++- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 0d8b889ac..217755381 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -3,8 +3,6 @@ package keeper import ( "fmt" - bbn "github.com/babylonchain/babylon/types" - btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" "github.com/cometbft/cometbft/libs/log" @@ -56,27 +54,3 @@ func NewKeeper( func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } - -func (k Keeper) getHeaderAndDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (*btclctypes.BTCHeaderInfo, uint64, error) { - if headerHash == nil { - return nil, 0, fmt.Errorf("headerHash is nil") - } - // get the header - header := k.btclcKeeper.GetHeaderByHash(ctx, headerHash) - if header == nil { - return nil, 0, fmt.Errorf("header that includes the staking tx is not found") - } - // get the tip - tip := k.btclcKeeper.GetTipInfo(ctx) - // If the height of the requested header is larger than the tip, return error - if tip.Height < header.Height { - return nil, 0, fmt.Errorf("header is higher than the tip in BTC light client") - } - // The depth is the number of blocks that have been build on top of the header - // For example: - // Tip: 0-deep - // Tip height is 10, headerInfo height is 5: 5-deep etc. - headerDepth := tip.Height - header.Height - - return header, headerDepth, nil -} diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 17108bcc7..57b18bcdc 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -69,6 +69,8 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre ctx := sdk.UnwrapSDKContext(goCtx) params := ms.GetParams(ctx) + btccParams := ms.btccKeeper.GetParams(ctx) + kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout // extract staking script from staking tx stakingOutputInfo, err := req.StakingTx.GetStakingOutputInfo(ms.btcNet) @@ -107,15 +109,25 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidJuryPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramJuryPK), hex.EncodeToString(*juryPK)) } - // ensure staking tx is k-deep - stakingTxHeader, stakingTxDepth, err := ms.getHeaderAndDepth(ctx, req.StakingTxInfo.Key.Hash) - if err != nil { - return nil, err + // get startheight and endheight of the timelock + stakingTxHeader := ms.btclcKeeper.GetHeaderByHash(ctx, req.StakingTxInfo.Key.Hash) + if stakingTxHeader == nil { + return nil, fmt.Errorf("header that includes the staking tx is not found") } - kValue := ms.btccKeeper.GetParams(ctx).BtcConfirmationDepth + startHeight := stakingTxHeader.Height + endHeight := stakingTxHeader.Height + uint64(stakingOutputInfo.StakingScriptData.StakingTime) + + // ensure staking tx is k-deep + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + stakingTxDepth := btcTip.Height - stakingTxHeader.Height if stakingTxDepth < kValue { return nil, types.ErrInvalidStakingTx.Wrapf("not k-deep: k=%d; depth=%d", kValue, stakingTxDepth) } + // ensure staking tx's timelock has more than w BTC blocks left + if btcTip.Height+wValue >= endHeight { + return nil, types.ErrInvalidStakingTx.Wrapf("staking tx's timelock has no more than w(=%d) blocks left", wValue) + } + // verify staking tx info, i.e., inclusion proof if err := req.StakingTxInfo.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain: %v", err) @@ -162,8 +174,8 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre BtcPk: delBTCPK, Pop: req.Pop, ValBtcPk: valBTCPK, - StartHeight: stakingTxHeader.Height, - EndHeight: stakingTxHeader.Height + uint64(stakingOutputInfo.StakingScriptData.StakingTime), + StartHeight: startHeight, + EndHeight: endHeight, TotalSat: uint64(stakingOutputInfo.StakingAmount), StakingTx: req.StakingTx, SlashingTx: req.SlashingTx, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index bbd24f0e3..080c89924 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -126,7 +126,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { // key pairs, staking tx and slashing tx delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - stakingTimeBlocks := uint16(5) + stakingTimeBlocks := uint16(1000) stakingValue := int64(2 * 10e8) stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) @@ -231,6 +231,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) From 9e4c1c2c4ec4cc7805f65487e6908c8e05c88214 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 30 Aug 2023 10:45:58 +1000 Subject: [PATCH 066/202] incentive: vanilla incentive module (#64) --- app/app.go | 35 +- proto/babylon/incentive/genesis.proto | 12 + proto/babylon/incentive/params.proto | 12 + proto/babylon/incentive/query.proto | 25 ++ proto/babylon/incentive/tx.proto | 7 + testutil/keeper/incentive.go | 50 +++ x/incentive/client/cli/query.go | 30 ++ x/incentive/client/cli/query_params.go | 36 ++ x/incentive/client/cli/tx.go | 29 ++ x/incentive/genesis.go | 20 + x/incentive/genesis_test.go | 25 ++ x/incentive/keeper/keeper.go | 54 +++ x/incentive/keeper/msg_server.go | 17 + x/incentive/keeper/msg_server_test.go | 23 + x/incentive/keeper/params.go | 16 + x/incentive/keeper/params_test.go | 18 + x/incentive/keeper/query.go | 7 + x/incentive/keeper/query_params.go | 19 + x/incentive/keeper/query_params_test.go | 21 + x/incentive/module.go | 145 +++++++ x/incentive/module_simulation.go | 55 +++ x/incentive/simulation/helpers.go | 15 + x/incentive/types/codec.go | 19 + x/incentive/types/errors.go | 12 + x/incentive/types/expected_keepers.go | 18 + x/incentive/types/genesis.go | 17 + x/incentive/types/genesis.pb.go | 321 ++++++++++++++ x/incentive/types/genesis_test.go | 37 ++ x/incentive/types/keys.go | 19 + x/incentive/types/params.go | 39 ++ x/incentive/types/params.pb.go | 264 ++++++++++++ x/incentive/types/query.pb.go | 535 ++++++++++++++++++++++++ x/incentive/types/query.pb.gw.go | 153 +++++++ x/incentive/types/tx.pb.go | 80 ++++ x/incentive/types/types.go | 1 + 35 files changed, 2182 insertions(+), 4 deletions(-) create mode 100644 proto/babylon/incentive/genesis.proto create mode 100644 proto/babylon/incentive/params.proto create mode 100644 proto/babylon/incentive/query.proto create mode 100644 proto/babylon/incentive/tx.proto create mode 100644 testutil/keeper/incentive.go create mode 100644 x/incentive/client/cli/query.go create mode 100644 x/incentive/client/cli/query_params.go create mode 100644 x/incentive/client/cli/tx.go create mode 100644 x/incentive/genesis.go create mode 100644 x/incentive/genesis_test.go create mode 100644 x/incentive/keeper/keeper.go create mode 100644 x/incentive/keeper/msg_server.go create mode 100644 x/incentive/keeper/msg_server_test.go create mode 100644 x/incentive/keeper/params.go create mode 100644 x/incentive/keeper/params_test.go create mode 100644 x/incentive/keeper/query.go create mode 100644 x/incentive/keeper/query_params.go create mode 100644 x/incentive/keeper/query_params_test.go create mode 100644 x/incentive/module.go create mode 100644 x/incentive/module_simulation.go create mode 100644 x/incentive/simulation/helpers.go create mode 100644 x/incentive/types/codec.go create mode 100644 x/incentive/types/errors.go create mode 100644 x/incentive/types/expected_keepers.go create mode 100644 x/incentive/types/genesis.go create mode 100644 x/incentive/types/genesis.pb.go create mode 100644 x/incentive/types/genesis_test.go create mode 100644 x/incentive/types/keys.go create mode 100644 x/incentive/types/params.go create mode 100644 x/incentive/types/params.pb.go create mode 100644 x/incentive/types/query.pb.go create mode 100644 x/incentive/types/query.pb.gw.go create mode 100644 x/incentive/types/tx.pb.go create mode 100644 x/incentive/types/types.go diff --git a/app/app.go b/app/app.go index 502e9cd44..289e1e9fa 100644 --- a/app/app.go +++ b/app/app.go @@ -120,6 +120,9 @@ import ( "github.com/babylonchain/babylon/x/finality" finalitykeeper "github.com/babylonchain/babylon/x/finality/keeper" finalitytypes "github.com/babylonchain/babylon/x/finality/types" + "github.com/babylonchain/babylon/x/incentive" + incentivekeeper "github.com/babylonchain/babylon/x/incentive/keeper" + incentivetypes "github.com/babylonchain/babylon/x/incentive/types" "github.com/babylonchain/babylon/x/monitor" monitorkeeper "github.com/babylonchain/babylon/x/monitor/keeper" monitortypes "github.com/babylonchain/babylon/x/monitor/types" @@ -218,6 +221,9 @@ var ( // BTC staking related btcstaking.AppModuleBasic{}, finality.AppModuleBasic{}, + + // tokenomics-related + incentive.AppModuleBasic{}, ) // module account permissions @@ -233,6 +239,7 @@ var ( // TODO: decide ZonConcierge's permissions here zctypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // TODO: decide BTCStaking and Finality's permissiones here + // TODO: decide incentive module's permission here } ) @@ -333,7 +340,12 @@ type BabylonApp struct { BTCStakingKeeper btcstakingkeeper.Keeper FinalityKeeper finalitykeeper.Keeper + // wasm smart contract module WasmKeeper wasm.Keeper + + // tokenomics-related modules + IncentiveKeeper incentivekeeper.Keeper + // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper @@ -407,6 +419,8 @@ func NewBabylonApp( finalitytypes.StoreKey, // WASM wasm.StoreKey, + // tokenomics-related modules + incentivetypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys( @@ -444,8 +458,6 @@ func NewBabylonApp( // their scoped modules in `NewApp` with `ScopeToModule` app.CapabilityKeeper.Seal() - // TODO: Grant capabilities for the ibc and ibc-transfer modules - // add keepers app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, keys[authtypes.StoreKey], authtypes.ProtoBaseAccount, maccPerms, appparams.Bech32PrefixAccAddr, authtypes.NewModuleAddress(govtypes.ModuleName).String()) @@ -499,7 +511,6 @@ func NewBabylonApp( ) // ... other modules keepers - // TODO: Create IBC keeper // register the proposal types // Deprecated: Avoid adding new handlers, instead use the new proposal flow @@ -622,11 +633,17 @@ func NewBabylonApp( btcNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) + // set up finality keeper app.FinalityKeeper = finalitykeeper.NewKeeper( appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.BTCStakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) + // set up incentive keeper + app.IncentiveKeeper = incentivekeeper.NewKeeper( + appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, + ) + // add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter()) // make ZoneConcierge to subscribe to the epoching's hooks @@ -752,6 +769,8 @@ func NewBabylonApp( // BTC staking related modules btcstaking.NewAppModule(appCodec, app.BTCStakingKeeper, app.AccountKeeper, app.BankKeeper), finality.NewAppModule(appCodec, app.FinalityKeeper, app.AccountKeeper, app.BankKeeper), + // tokenomics related modules + incentive.NewAppModule(appCodec, app.IncentiveKeeper, app.AccountKeeper, app.BankKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -760,7 +779,11 @@ func NewBabylonApp( // NOTE: staking module is required if HistoricalEntries param > 0 // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) app.mm.SetOrderBeginBlockers( - upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + upgradetypes.ModuleName, capabilitytypes.ModuleName, + // NOTE: incentive module's BeginBlock has to be after mint but before distribution + // so that it can intercept a part of new inflation to reward BTC staking/timestamping stakeholders + minttypes.ModuleName, incentivetypes.ModuleName, distrtypes.ModuleName, + slashingtypes.ModuleName, evidencetypes.ModuleName, stakingtypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, govtypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, feegrant.ModuleName, @@ -808,6 +831,8 @@ func NewBabylonApp( // BTC staking related modules btcstakingtypes.ModuleName, finalitytypes.ModuleName, + // tokenomics related modules + incentivetypes.ModuleName, // EndBlock of incentive module does not matter ) // Babylon does not want EndBlock processing in staking app.mm.OrderEndBlockers = append(app.mm.OrderEndBlockers[:2], app.mm.OrderEndBlockers[2+1:]...) // remove stakingtypes.ModuleName @@ -838,6 +863,8 @@ func NewBabylonApp( // BTC staking related modules btcstakingtypes.ModuleName, finalitytypes.ModuleName, + // tokenomics-related modules + incentivetypes.ModuleName, ) // Uncomment if you want to set a custom migration order here. diff --git a/proto/babylon/incentive/genesis.proto b/proto/babylon/incentive/genesis.proto new file mode 100644 index 000000000..7e5a33f3a --- /dev/null +++ b/proto/babylon/incentive/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package babylon.incentive; + +import "gogoproto/gogo.proto"; +import "babylon/incentive/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/incentive/types"; + +// GenesisState defines the incentive module's genesis state. +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/babylon/incentive/params.proto b/proto/babylon/incentive/params.proto new file mode 100644 index 000000000..c7e8f30ad --- /dev/null +++ b/proto/babylon/incentive/params.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package babylon.incentive; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/babylonchain/babylon/x/incentive/types"; + +// Params defines the parameters for the module. +message Params { + option (gogoproto.goproto_stringer) = false; + +} diff --git a/proto/babylon/incentive/query.proto b/proto/babylon/incentive/query.proto new file mode 100644 index 000000000..def0375a4 --- /dev/null +++ b/proto/babylon/incentive/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package babylon.incentive; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "babylon/incentive/params.proto"; + +option go_package = "github.com/babylonchain/babylon/x/incentive/types"; + +// Query defines the gRPC querier service. +service Query { + // Parameters queries the parameters of the module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/params"; + } +} + +// QueryParamsRequest is request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is response type for the Query/Params RPC method. +message QueryParamsResponse { + // params holds all the parameters of this module. + Params params = 1 [(gogoproto.nullable) = false]; +} \ No newline at end of file diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto new file mode 100644 index 000000000..8a67fe5af --- /dev/null +++ b/proto/babylon/incentive/tx.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package babylon.incentive; + +option go_package = "github.com/babylonchain/babylon/x/incentive/types"; + +// Msg defines the Msg service. +service Msg {} \ No newline at end of file diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go new file mode 100644 index 000000000..71c54c5cf --- /dev/null +++ b/testutil/keeper/incentive.go @@ -0,0 +1,50 @@ +package keeper + +import ( + "testing" + + "github.com/babylonchain/babylon/x/incentive/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + tmdb "github.com/cometbft/cometbft-db" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/stretchr/testify/require" +) + +func IncentiveKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { + storeKey := sdk.NewKVStoreKey(types.StoreKey) + memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) + + registry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(registry) + + k := keeper.NewKeeper( + cdc, + storeKey, + memStoreKey, + nil, + nil, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authtypes.FeeCollectorName, + ) + + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + // Initialize params + k.SetParams(ctx, types.DefaultParams()) + + return &k, ctx +} diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go new file mode 100644 index 000000000..bdb1a13af --- /dev/null +++ b/x/incentive/client/cli/query.go @@ -0,0 +1,30 @@ +package cli + +import ( + "fmt" + // "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + // "github.com/cosmos/cosmos-sdk/client/flags" + // sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/x/incentive/types" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd(queryRoute string) *cobra.Command { + // Group incentive queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(CmdQueryParams()) + + return cmd +} diff --git a/x/incentive/client/cli/query_params.go b/x/incentive/client/cli/query_params.go new file mode 100644 index 000000000..f6c20a168 --- /dev/null +++ b/x/incentive/client/cli/query_params.go @@ -0,0 +1,36 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + + "github.com/babylonchain/babylon/x/incentive/types" +) + +func CmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "shows the parameters of the module", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go new file mode 100644 index 000000000..6bde3d489 --- /dev/null +++ b/x/incentive/client/cli/tx.go @@ -0,0 +1,29 @@ +package cli + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + // "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/babylonchain/babylon/x/incentive/types" +) + +var ( + DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + return cmd +} diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go new file mode 100644 index 000000000..64b03328d --- /dev/null +++ b/x/incentive/genesis.go @@ -0,0 +1,20 @@ +package incentive + +import ( + "github.com/babylonchain/babylon/x/incentive/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.SetParams(ctx, genState.Params) +} + +// ExportGenesis returns the module's exported genesis +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + + return genesis +} diff --git a/x/incentive/genesis_test.go b/x/incentive/genesis_test.go new file mode 100644 index 000000000..1fbe61bd0 --- /dev/null +++ b/x/incentive/genesis_test.go @@ -0,0 +1,25 @@ +package incentive_test + +import ( + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/testutil/nullify" + "github.com/babylonchain/babylon/x/incentive" + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/stretchr/testify/require" +) + +func TestGenesis(t *testing.T) { + genesisState := types.GenesisState{ + Params: types.DefaultParams(), + } + + k, ctx := keepertest.IncentiveKeeper(t) + incentive.InitGenesis(ctx, *k, genesisState) + got := incentive.ExportGenesis(ctx, *k) + require.NotNil(t, got) + + nullify.Fill(&genesisState) + nullify.Fill(got) +} diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go new file mode 100644 index 000000000..084c1a8a3 --- /dev/null +++ b/x/incentive/keeper/keeper.go @@ -0,0 +1,54 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/babylonchain/babylon/x/incentive/types" +) + +type ( + Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey + paramstore paramtypes.Subspace + + bankKeeper types.BankKeeper + accountKeeper types.AccountKeeper + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string + // name of the FeeCollector ModuleAccount + feeCollectorName string + } +) + +func NewKeeper( + cdc codec.BinaryCodec, + storeKey, + memKey storetypes.StoreKey, + bankKeeper types.BankKeeper, + accountKeeper types.AccountKeeper, + authority string, + feeCollectorName string, +) Keeper { + return Keeper{ + cdc: cdc, + storeKey: storeKey, + memKey: memKey, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + authority: authority, + feeCollectorName: feeCollectorName, + } +} + +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} diff --git a/x/incentive/keeper/msg_server.go b/x/incentive/keeper/msg_server.go new file mode 100644 index 000000000..63b3be97d --- /dev/null +++ b/x/incentive/keeper/msg_server.go @@ -0,0 +1,17 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go new file mode 100644 index 000000000..81b38fa6f --- /dev/null +++ b/x/incentive/keeper/msg_server_test.go @@ -0,0 +1,23 @@ +package keeper_test + +import ( + "context" + "testing" + + keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { + k, ctx := keepertest.IncentiveKeeper(t) + return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) +} + +func TestMsgServer(t *testing.T) { + ms, ctx := setupMsgServer(t) + require.NotNil(t, ms) + require.NotNil(t, ctx) +} diff --git a/x/incentive/keeper/params.go b/x/incentive/keeper/params.go new file mode 100644 index 000000000..cfde60970 --- /dev/null +++ b/x/incentive/keeper/params.go @@ -0,0 +1,16 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetParams get all parameters as types.Params +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + return types.NewParams() +} + +// SetParams set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramstore.SetParamSet(ctx, ¶ms) +} diff --git a/x/incentive/keeper/params_test.go b/x/incentive/keeper/params_test.go new file mode 100644 index 000000000..18f4f273f --- /dev/null +++ b/x/incentive/keeper/params_test.go @@ -0,0 +1,18 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/stretchr/testify/require" +) + +func TestGetParams(t *testing.T) { + k, ctx := testkeeper.IncentiveKeeper(t) + params := types.DefaultParams() + + k.SetParams(ctx, params) + + require.EqualValues(t, params, k.GetParams(ctx)) +} diff --git a/x/incentive/keeper/query.go b/x/incentive/keeper/query.go new file mode 100644 index 000000000..07a2c5eb4 --- /dev/null +++ b/x/incentive/keeper/query.go @@ -0,0 +1,7 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" +) + +var _ types.QueryServer = Keeper{} diff --git a/x/incentive/keeper/query_params.go b/x/incentive/keeper/query_params.go new file mode 100644 index 000000000..b58c8c1ec --- /dev/null +++ b/x/incentive/keeper/query_params.go @@ -0,0 +1,19 @@ +package keeper + +import ( + "context" + + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/x/incentive/keeper/query_params_test.go b/x/incentive/keeper/query_params_test.go new file mode 100644 index 000000000..0c7561f00 --- /dev/null +++ b/x/incentive/keeper/query_params_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestParamsQuery(t *testing.T) { + keeper, ctx := testkeeper.IncentiveKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + params := types.DefaultParams() + keeper.SetParams(ctx, params) + + response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + require.NoError(t, err) + require.Equal(t, &types.QueryParamsResponse{Params: params}, response) +} diff --git a/x/incentive/module.go b/x/incentive/module.go new file mode 100644 index 000000000..e983d802e --- /dev/null +++ b/x/incentive/module.go @@ -0,0 +1,145 @@ +package incentive + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/babylonchain/babylon/x/incentive/client/cli" + "github.com/babylonchain/babylon/x/incentive/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface that defines the independent methods a Cosmos SDK module needs to implement. +type AppModuleBasic struct { + cdc codec.BinaryCodec +} + +func NewAppModuleBasic(cdc codec.BinaryCodec) AppModuleBasic { + return AppModuleBasic{cdc: cdc} +} + +// Name returns the name of the module as a string +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the amino codec for the module, which is used to marshal and unmarshal structs to/from []byte in order to persist them in the module's KVStore +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns a default GenesisState for the module, marshalled to json.RawMessage. The default GenesisState need to be defined by the module developer and is primarily used for testing +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck +} + +// GetTxCmd returns the root Tx command for the module. The subcommands of this root command are used by end-users to generate new transactions containing messages defined in the module +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the root query command for the module. The subcommands of this root command are used by end-users to generate new queries to the subset of the state defined by the module +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd(types.StoreKey) +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(cdc), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the module's genesis initialization. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(gs, &genState) + + InitGenesis(ctx, am.keeper, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock contains the logic that is automatically triggered at the beginning of each block +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock contains the logic that is automatically triggered at the end of each block +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/incentive/module_simulation.go b/x/incentive/module_simulation.go new file mode 100644 index 000000000..0f62adfc4 --- /dev/null +++ b/x/incentive/module_simulation.go @@ -0,0 +1,55 @@ +package incentive + +import ( + "math/rand" + + "github.com/babylonchain/babylon/testutil/sample" + incentivesimulation "github.com/babylonchain/babylon/x/incentive/simulation" + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// avoid unused import issue +var ( + _ = sample.AccAddress + _ = incentivesimulation.FindAccount + _ = simulation.MsgEntryKind + _ = baseapp.Paramspace + _ = rand.Rand{} +) + +// GenerateGenesisState creates a randomized GenState of the module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + accs := make([]string, len(simState.Accounts)) + for i, acc := range simState.Accounts { + accs[i] = acc.Address.String() + } + incentiveGenesis := types.GenesisState{ + Params: types.DefaultParams(), + } + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&incentiveGenesis) +} + +// RegisterStoreDecoder registers a decoder. +func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { + return nil +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + operations := make([]simtypes.WeightedOperation, 0) + + return operations +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{} +} diff --git a/x/incentive/simulation/helpers.go b/x/incentive/simulation/helpers.go new file mode 100644 index 000000000..92c437c0d --- /dev/null +++ b/x/incentive/simulation/helpers.go @@ -0,0 +1,15 @@ +package simulation + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// FindAccount find a specific address from an account list +func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { + creator, err := sdk.AccAddressFromBech32(address) + if err != nil { + panic(err) + } + return simtypes.FindAccount(accs, creator) +} diff --git a/x/incentive/types/codec.go b/x/incentive/types/codec.go new file mode 100644 index 000000000..b70e2a444 --- /dev/null +++ b/x/incentive/types/codec.go @@ -0,0 +1,19 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) +) diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go new file mode 100644 index 000000000..108698076 --- /dev/null +++ b/x/incentive/types/errors.go @@ -0,0 +1,12 @@ +package types + +// DONTCOVER + +import ( + errorsmod "cosmossdk.io/errors" +) + +// x/incentive module sentinel errors +var ( + ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") +) diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go new file mode 100644 index 000000000..6aa6e9778 --- /dev/null +++ b/x/incentive/types/expected_keepers.go @@ -0,0 +1,18 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// AccountKeeper defines the expected account keeper used for simulations (noalias) +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI + // Methods imported from account should be defined here +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + // Methods imported from bank should be defined here +} diff --git a/x/incentive/types/genesis.go b/x/incentive/types/genesis.go new file mode 100644 index 000000000..bdb9f4151 --- /dev/null +++ b/x/incentive/types/genesis.go @@ -0,0 +1,17 @@ +package types + +// DefaultIndex is the default global index +const DefaultIndex uint64 = 1 + +// DefaultGenesis returns the default genesis state +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + return gs.Params.Validate() +} diff --git a/x/incentive/types/genesis.pb.go b/x/incentive/types/genesis.pb.go new file mode 100644 index 000000000..d2c7b135c --- /dev/null +++ b/x/incentive/types/genesis.pb.go @@ -0,0 +1,321 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the incentive module's genesis state. +type GenesisState struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_41d5400dc6b4b931, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "babylon.incentive.GenesisState") +} + +func init() { proto.RegisterFile("babylon/incentive/genesis.proto", fileDescriptor_41d5400dc6b4b931) } + +var fileDescriptor_41d5400dc6b4b931 = []byte{ + // 195 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x4f, 0x4f, + 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0x2a, 0xd0, + 0x83, 0x2b, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xea, 0x83, 0x58, 0x10, 0x85, 0x52, + 0x72, 0x98, 0x26, 0x15, 0x24, 0x16, 0x25, 0xe6, 0x42, 0x0d, 0x52, 0x72, 0xe7, 0xe2, 0x71, 0x87, + 0x98, 0x1c, 0x5c, 0x92, 0x58, 0x92, 0x2a, 0x64, 0xce, 0xc5, 0x06, 0x91, 0x97, 0x60, 0x54, 0x60, + 0xd4, 0xe0, 0x36, 0x92, 0xd4, 0xc3, 0xb0, 0x49, 0x2f, 0x00, 0xac, 0xc0, 0x89, 0xe5, 0xc4, 0x3d, + 0x79, 0x86, 0x20, 0xa8, 0x72, 0x27, 0xef, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, + 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, + 0x88, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, 0x1a, 0x96, + 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x57, 0x20, 0x39, 0xae, 0xa4, 0xb2, 0x20, 0xb5, 0x38, + 0x89, 0x0d, 0xec, 0x38, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xa1, 0x97, 0x7e, 0x08, + 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/incentive/types/genesis_test.go b/x/incentive/types/genesis_test.go new file mode 100644 index 000000000..877e58a57 --- /dev/null +++ b/x/incentive/types/genesis_test.go @@ -0,0 +1,37 @@ +package types_test + +import ( + "testing" + + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/stretchr/testify/require" +) + +func TestGenesisState_Validate(t *testing.T) { + tests := []struct { + desc string + genState *types.GenesisState + valid bool + }{ + { + desc: "default is valid", + genState: types.DefaultGenesis(), + valid: true, + }, + { + desc: "valid genesis state", + genState: &types.GenesisState{}, + valid: true, + }, + } + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + err := tc.genState.Validate() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go new file mode 100644 index 000000000..6972fd356 --- /dev/null +++ b/x/incentive/types/keys.go @@ -0,0 +1,19 @@ +package types + +const ( + // ModuleName defines the module name + ModuleName = "incentive" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // RouterKey defines the module's message routing key + RouterKey = ModuleName + + // MemStoreKey defines the in-memory store key + MemStoreKey = "mem_incentive" +) + +func KeyPrefix(p string) []byte { + return []byte(p) +} diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go new file mode 100644 index 000000000..357196ad6 --- /dev/null +++ b/x/incentive/types/params.go @@ -0,0 +1,39 @@ +package types + +import ( + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "gopkg.in/yaml.v2" +) + +var _ paramtypes.ParamSet = (*Params)(nil) + +// ParamKeyTable the param key table for launch module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// NewParams creates a new Params instance +func NewParams() Params { + return Params{} +} + +// DefaultParams returns a default set of parameters +func DefaultParams() Params { + return NewParams() +} + +// ParamSetPairs get the params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{} +} + +// Validate validates the set of params +func (p Params) Validate() error { + return nil +} + +// String implements the Stringer interface. +func (p Params) String() string { + out, _ := yaml.Marshal(p) + return string(out) +} diff --git a/x/incentive/types/params.pb.go b/x/incentive/types/params.pb.go new file mode 100644 index 000000000..6c1f5fbe0 --- /dev/null +++ b/x/incentive/types/params.pb.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the module. +type Params struct { +} + +func (m *Params) Reset() { *m = Params{} } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_c42276168f0adf4b, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Params)(nil), "babylon.incentive.Params") +} + +func init() { proto.RegisterFile("babylon/incentive/params.proto", fileDescriptor_c42276168f0adf4b) } + +var fileDescriptor_c42276168f0adf4b = []byte{ + // 153 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0x48, + 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0xca, 0xeb, 0xc1, + 0xe5, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x12, 0x1f, + 0x17, 0x5b, 0x00, 0x58, 0xa3, 0x15, 0xcb, 0x8c, 0x05, 0xf2, 0x0c, 0x4e, 0xde, 0x27, 0x1e, 0xc9, + 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, + 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x98, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, + 0x9c, 0x9f, 0xab, 0x0f, 0x35, 0x3d, 0x39, 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0xaf, 0x40, 0x72, + 0x4c, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x0e, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x94, 0x16, 0x53, 0xd2, 0xae, 0x00, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/incentive/types/query.pb.go b/x/incentive/types/query.pb.go new file mode 100644 index 000000000..41eb14534 --- /dev/null +++ b/x/incentive/types/query.pb.go @@ -0,0 +1,535 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params holds all the parameters of this module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "babylon.incentive.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "babylon.incentive.QueryParamsResponse") +} + +func init() { proto.RegisterFile("babylon/incentive/query.proto", fileDescriptor_e1a59cc0c7c44135) } + +var fileDescriptor_e1a59cc0c7c44135 = []byte{ + // 274 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0x2c, + 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0x4a, 0xeb, 0xc1, 0xa5, + 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x94, 0x4c, 0x7a, + 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x7e, 0x62, 0x41, 0xa6, 0x7e, 0x62, 0x5e, 0x5e, 0x7e, 0x49, 0x62, + 0x49, 0x66, 0x7e, 0x5e, 0x31, 0x54, 0x56, 0x0e, 0xd3, 0x96, 0x82, 0xc4, 0xa2, 0xc4, 0x5c, 0xa8, + 0xbc, 0x92, 0x08, 0x97, 0x50, 0x20, 0xc8, 0xd6, 0x00, 0xb0, 0x60, 0x50, 0x6a, 0x61, 0x69, 0x6a, + 0x71, 0x89, 0x92, 0x1f, 0x97, 0x30, 0x8a, 0x68, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x39, + 0x17, 0x1b, 0x44, 0xb3, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xa4, 0x1e, 0x86, 0x23, 0xf5, + 0x20, 0x5a, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0x2a, 0x37, 0x9a, 0xc8, 0xc8, 0xc5, + 0x0a, 0x36, 0x50, 0xa8, 0x9d, 0x91, 0x8b, 0x0d, 0xa2, 0x44, 0x48, 0x15, 0x8b, 0x6e, 0x4c, 0xb7, + 0x48, 0xa9, 0x11, 0x52, 0x06, 0x71, 0x9c, 0x92, 0x5e, 0xd3, 0xe5, 0x27, 0x93, 0x99, 0x34, 0x84, + 0xd4, 0xf4, 0xa1, 0xea, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0x71, 0xf9, 0xdf, 0xc9, 0xfb, 0xc4, + 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, + 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, + 0xf4, 0x92, 0xf3, 0x73, 0xb1, 0x9b, 0x55, 0x81, 0x64, 0x5a, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, + 0x1b, 0x38, 0x34, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x50, 0x5c, 0x8e, 0xd3, 0xd5, 0x01, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Parameters queries the parameters of the module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Parameters queries the parameters of the module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.incentive.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/incentive/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/incentive/types/query.pb.gw.go b/x/incentive/types/query.pb.gw.go new file mode 100644 index 000000000..93b6b7002 --- /dev/null +++ b/x/incentive/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: babylon/incentive/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylonchain", "babylon", "incentive", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/incentive/types/tx.pb.go b/x/incentive/types/tx.pb.go new file mode 100644 index 000000000..cd3ebaade --- /dev/null +++ b/x/incentive/types/tx.pb.go @@ -0,0 +1,80 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +func init() { proto.RegisterFile("babylon/incentive/tx.proto", fileDescriptor_b4de6776d39a3a22) } + +var fileDescriptor_b4de6776d39a3a22 = []byte{ + // 128 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0xa9, + 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0xca, 0xe9, 0xc1, 0xe5, 0x8c, 0x58, 0xb9, + 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0xbc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, + 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, + 0xca, 0x30, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xaa, 0x3d, 0x39, + 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0xaf, 0x40, 0xb6, 0xa9, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, + 0x6c, 0x9b, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x97, 0x5a, 0x57, 0x4d, 0x8b, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "babylon.incentive.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/incentive/tx.proto", +} diff --git a/x/incentive/types/types.go b/x/incentive/types/types.go new file mode 100644 index 000000000..ab1254f4c --- /dev/null +++ b/x/incentive/types/types.go @@ -0,0 +1 @@ +package types From de37367a869682f440b37b01fdfab8eb391c6443 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 1 Sep 2023 09:18:34 +1000 Subject: [PATCH 067/202] btcstaking: add commission and description to BTC validator (#67) --- proto/babylon/btcstaking/v1/btcstaking.proto | 19 +- proto/babylon/btcstaking/v1/params.proto | 5 + proto/babylon/btcstaking/v1/tx.proto | 15 +- test/e2e/btc_staking_e2e_test.go | 2 +- .../configurer/chain/commands_btcstaking.go | 7 +- testutil/datagen/btcstaking.go | 14 +- x/btcstaking/client/cli/tx.go | 51 +++- x/btcstaking/keeper/msg_server.go | 15 +- x/btcstaking/keeper/msg_server_test.go | 30 ++- x/btcstaking/keeper/params.go | 6 + x/btcstaking/types/btcstaking.pb.go | 246 ++++++++++++----- x/btcstaking/types/errors.go | 1 + x/btcstaking/types/msg.go | 9 + x/btcstaking/types/params.go | 2 + x/btcstaking/types/params.pb.go | 91 +++++-- x/btcstaking/types/tx.pb.go | 248 +++++++++++++----- 16 files changed, 585 insertions(+), 176 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index a4cd3448d..584f00126 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -2,27 +2,36 @@ syntax = "proto3"; package babylon.btcstaking.v1; import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; import "cosmos/crypto/secp256k1/keys.proto"; +import "cosmos/staking/v1beta1/staking.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; // BTCValidator defines a BTC validator message BTCValidator { + // description defines the description terms for the BTC validator. + cosmos.staking.v1beta1.Description description = 1; + // commission defines the commission rate of BTC validator. + string commission = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + ]; // babylon_pk is the Babylon secp256k1 PK of this BTC validator - cosmos.crypto.secp256k1.PubKey babylon_pk = 1; + cosmos.crypto.secp256k1.PubKey babylon_pk = 3; // btc_pk is the Bitcoin secp256k1 PK of this BTC validator // the PK follows encoding in BIP-340 spec - bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + bytes btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk - ProofOfPossession pop = 3; + ProofOfPossession pop = 5; // slashed_babylon_height indicates the Babylon height when // the BTC validator is slashed. // if it's 0 then the BTC validator is not slashed - uint64 slashed_babylon_height = 4; + uint64 slashed_babylon_height = 6; // slashed_btc_height indicates the BTC height when // the BTC validator is slashed. // if it's 0 then the BTC validator is not slashed - uint64 slashed_btc_height = 5; + uint64 slashed_btc_height = 7; } // BTCValidatorWithMeta wraps the BTCValidator with meta data. diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 3bb753220..af8806a8f 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -19,4 +19,9 @@ message Params { // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? int64 min_slashing_tx_fee_sat = 3; + // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators + string min_commission_rate = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 94a1aa9f0..09e7412c4 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -8,6 +8,7 @@ import "cosmos/crypto/secp256k1/keys.proto"; import "babylon/btcstaking/v1/params.proto"; import "babylon/btcstaking/v1/btcstaking.proto"; import "babylon/btccheckpoint/v1/btccheckpoint.proto"; +import "cosmos/staking/v1beta1/staking.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; @@ -26,13 +27,21 @@ service Msg { // MsgCreateBTCValidator is the message for creating a BTC validator message MsgCreateBTCValidator { string signer = 1; + + // description defines the description terms for the BTC validator. + cosmos.staking.v1beta1.Description description = 2; + // commission defines the commission rate of BTC validator. + string commission = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + ]; // babylon_pk is the Babylon secp256k1 PK of this BTC validator - cosmos.crypto.secp256k1.PubKey babylon_pk = 2; + cosmos.crypto.secp256k1.PubKey babylon_pk = 4; // btc_pk is the Bitcoin secp256k1 PK of this BTC validator // the PK follows encoding in BIP-340 spec - bytes btc_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + bytes btc_pk = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk - ProofOfPossession pop = 4; + ProofOfPossession pop = 6; } // MsgCreateBTCValidatorResponse is the response for MsgCreateBTCValidator message MsgCreateBTCValidatorResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index cfc4aa73c..b56dfd888 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -76,7 +76,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { /* create a random BTC validator on Babylon */ - nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop) + nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop, btcVal.Description.Moniker, btcVal.Description.Identity, btcVal.Description.Website, btcVal.Description.SecurityContact, btcVal.Description.Details, btcVal.Commission) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 644fd69c7..8c66a94b5 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "strconv" + "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -11,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession) { +func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *math.LegacyDec) { n.LogActionF("creating BTC validator") // get babylon PK hex @@ -24,7 +25,9 @@ func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn. popHex, err := pop.ToHexStr() require.NoError(n.t, err) - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-validator", babylonPKHex, btcPKHex, popHex, "--from=val"} + cmd := []string{ + "babylond", "tx", "btcstaking", "create-btc-validator", babylonPKHex, btcPKHex, popHex, "--from=val", "--moniker", moniker, "--identity", identity, "--website", website, "--security-contact", securityContract, "--details", details, "--commission-rate", commission.String(), + } _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully created BTC validator") diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index d4bcc59b7..a812e850a 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" + "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -14,6 +15,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { @@ -26,6 +28,10 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { } func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { + // commission + zeroCommission := math.LegacyZeroDec() + // description + description := stakingtypes.Description{} // key pairs btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) @@ -43,9 +49,11 @@ func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst return nil, err } return &bstypes.BTCValidator{ - BabylonPk: secp256k1PK, - BtcPk: bip340PK, - Pop: pop, + Description: &description, + Commission: &zeroCommission, + BabylonPk: secp256k1PK, + BtcPk: bip340PK, + Pop: pop, }, nil } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index a29a7bce5..48cd7fbd1 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -12,9 +12,20 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/spf13/cobra" ) +const ( + FlagMoniker = "moniker" + FlagIdentity = "identity" + FlagWebsite = "website" + FlagSecurityContact = "security-contact" + FlagDetails = "details" + FlagCommissionRate = "commission-rate" +) + // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -48,6 +59,28 @@ func NewCreateBTCValidatorCmd() *cobra.Command { return err } + fs := cmd.Flags() + + // get description + moniker, _ := fs.GetString(FlagMoniker) + identity, _ := fs.GetString(FlagIdentity) + website, _ := fs.GetString(FlagWebsite) + security, _ := fs.GetString(FlagSecurityContact) + details, _ := fs.GetString(FlagDetails) + description := stakingtypes.NewDescription( + moniker, + identity, + website, + security, + details, + ) + // get commission + rateStr, _ := fs.GetString(FlagCommissionRate) + rate, err := sdk.NewDecFromStr(rateStr) + if err != nil { + return err + } + // get Babylon PK babylonPKBytes, err := hex.DecodeString(args[0]) if err != nil { @@ -71,16 +104,26 @@ func NewCreateBTCValidatorCmd() *cobra.Command { } msg := types.MsgCreateBTCValidator{ - Signer: clientCtx.FromAddress.String(), - BabylonPk: &babylonPK, - BtcPk: btcPK, - Pop: pop, + Signer: clientCtx.FromAddress.String(), + Description: &description, + Commission: &rate, + BabylonPk: &babylonPK, + BtcPk: btcPK, + Pop: pop, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, } + fs := cmd.Flags() + fs.String(FlagMoniker, "", "The validator's (optional) moniker") + fs.String(FlagWebsite, "", "The validator's (optional) website") + fs.String(FlagSecurityContact, "", "The validator's (optional) security contact email") + fs.String(FlagDetails, "", "The validator's (optional) details") + fs.String(FlagIdentity, "", "The (optional) identity signature (ex. UPort or Keybase)") + fs.String(FlagCommissionRate, "0", "The initial commission rate percentage") + flags.AddTxFlagsToCmd(cmd) return cmd diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 57b18bcdc..3cb9087f3 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -44,15 +44,24 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { // ensure the validator address does not exist before ctx := sdk.UnwrapSDKContext(goCtx) + + // ensure commission rate is at least the minimum commission rate in parameters + if req.Commission.LT(ms.MinCommissionRate(ctx)) { + return nil, types.ErrCommissionLTMinRate.Wrapf("cannot set validator commission to less than minimum rate of %s", ms.MinCommissionRate(ctx)) + } + + // ensure BTC validator does not exist before if ms.HasBTCValidator(ctx, *req.BtcPk) { return nil, types.ErrDuplicatedBTCVal } // all good, add this validator btcVal := types.BTCValidator{ - BabylonPk: req.BabylonPk, - BtcPk: req.BtcPk, - Pop: req.Pop, + Description: req.Description, + Commission: req.Commission, + BabylonPk: req.BabylonPk, + BtcPk: req.BtcPk, + Pop: req.Pop, } ms.SetBTCValidator(ctx, &btcVal) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 080c89924..3e5934b15 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -46,10 +46,12 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { btcVal, err := datagen.GenRandomBTCValidator(r) require.NoError(t, err) msg := &types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, + Signer: datagen.GenRandomAccount().Address, + Description: btcVal.Description, + Commission: btcVal.Commission, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, } _, err = ms.CreateBTCValidator(goCtx, msg) require.NoError(t, err) @@ -65,10 +67,12 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { // duplicated BTC validators should not pass for _, btcVal2 := range btcVals { msg := &types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - BabylonPk: btcVal2.BabylonPk, - BtcPk: btcVal2.BtcPk, - Pop: btcVal2.Pop, + Signer: datagen.GenRandomAccount().Address, + Description: btcVal2.Description, + Commission: btcVal2.Commission, + BabylonPk: btcVal2.BabylonPk, + BtcPk: btcVal2.BtcPk, + Pop: btcVal2.Pop, } _, err := ms.CreateBTCValidator(goCtx, msg) require.Error(t, err) @@ -112,10 +116,12 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) require.NoError(t, err) msgNewVal := types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, + Signer: datagen.GenRandomAccount().Address, + Description: btcVal.Description, + Commission: btcVal.Commission, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, } _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) require.NoError(t, err) diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index 0ddaf62e1..bd119d8d0 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -1,6 +1,7 @@ package keeper import ( + "cosmossdk.io/math" "github.com/babylonchain/babylon/x/btcstaking/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -26,3 +27,8 @@ func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { k.cdc.MustUnmarshal(bz, &p) return p } + +// MinCommissionRate returns the minimal commission rate of BTC validators +func (k Keeper) MinCommissionRate(ctx sdk.Context) math.LegacyDec { + return k.GetParams(ctx).MinCommissionRate +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 02d939903..48ac3f83b 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -6,7 +6,10 @@ package types import ( fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/cosmos-proto" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -59,21 +62,25 @@ func (BTCDelegationStatus) EnumDescriptor() ([]byte, []int) { // BTCValidator defines a BTC validator type BTCValidator struct { + // description defines the description terms for the BTC validator. + Description *types.Description `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // commission defines the commission rate of BTC validator. + Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` // babylon_pk is the Babylon secp256k1 PK of this BTC validator - BabylonPk *secp256k1.PubKey `protobuf:"bytes,1,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + BabylonPk *secp256k1.PubKey `protobuf:"bytes,3,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // btc_pk is the Bitcoin secp256k1 PK of this BTC validator // the PK follows encoding in BIP-340 spec - BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk - Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` + Pop *ProofOfPossession `protobuf:"bytes,5,opt,name=pop,proto3" json:"pop,omitempty"` // slashed_babylon_height indicates the Babylon height when // the BTC validator is slashed. // if it's 0 then the BTC validator is not slashed - SlashedBabylonHeight uint64 `protobuf:"varint,4,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` + SlashedBabylonHeight uint64 `protobuf:"varint,6,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` // slashed_btc_height indicates the BTC height when // the BTC validator is slashed. // if it's 0 then the BTC validator is not slashed - SlashedBtcHeight uint64 `protobuf:"varint,5,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` + SlashedBtcHeight uint64 `protobuf:"varint,7,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` } func (m *BTCValidator) Reset() { *m = BTCValidator{} } @@ -109,6 +116,13 @@ func (m *BTCValidator) XXX_DiscardUnknown() { var xxx_messageInfo_BTCValidator proto.InternalMessageInfo +func (m *BTCValidator) GetDescription() *types.Description { + if m != nil { + return m.Description + } + return nil +} + func (m *BTCValidator) GetBabylonPk() *secp256k1.PubKey { if m != nil { return m.BabylonPk @@ -496,54 +510,60 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 748 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0x8e, 0xdd, 0x34, 0x3f, 0x9e, 0xd3, 0x2a, 0x1d, 0x4a, 0x89, 0x8a, 0x48, 0xd2, 0x08, 0x50, - 0x84, 0x90, 0x4d, 0xd3, 0x1f, 0xe2, 0x87, 0x04, 0xc2, 0x6d, 0x04, 0x11, 0xb4, 0x58, 0x76, 0x54, - 0x10, 0x07, 0xa2, 0xb1, 0xe3, 0x3a, 0x26, 0xae, 0xc7, 0xf2, 0x4c, 0x42, 0x72, 0xe7, 0xc0, 0x91, - 0xbf, 0x07, 0x89, 0xfb, 0x1e, 0x7b, 0x5c, 0xf5, 0x50, 0xad, 0xda, 0x7f, 0x64, 0xe5, 0xf1, 0x38, - 0x4d, 0xb5, 0x5b, 0xad, 0x56, 0x5d, 0xed, 0x61, 0x6f, 0x33, 0xef, 0x7b, 0xdf, 0xe7, 0xf7, 0xbd, - 0x79, 0xe3, 0x81, 0x4f, 0x6d, 0x6c, 0xcf, 0x03, 0x12, 0x6a, 0x36, 0x73, 0x28, 0xc3, 0x63, 0x3f, - 0xf4, 0xb4, 0xe9, 0xee, 0xd2, 0x4e, 0x8d, 0x62, 0xc2, 0x08, 0x7a, 0x5f, 0xe4, 0xa9, 0x4b, 0xc8, - 0x74, 0x77, 0x7b, 0xd3, 0x23, 0x1e, 0xe1, 0x19, 0x5a, 0xb2, 0x4a, 0x93, 0xb7, 0x5b, 0x0e, 0xa1, - 0x17, 0x84, 0x6a, 0x4e, 0x3c, 0x8f, 0x18, 0xd1, 0xa8, 0xeb, 0x44, 0x9d, 0x83, 0xc3, 0xf1, 0xae, - 0x36, 0x76, 0xe7, 0x34, 0xcd, 0x69, 0xfd, 0x2f, 0x43, 0x45, 0xef, 0x1f, 0x9d, 0xe1, 0xc0, 0x1f, - 0x62, 0x46, 0x62, 0xf4, 0x2d, 0x80, 0xf8, 0xc6, 0x20, 0x1a, 0xd7, 0xa4, 0xa6, 0xd4, 0x56, 0x3a, - 0x0d, 0x35, 0x55, 0x52, 0x53, 0x25, 0x75, 0xa1, 0xa4, 0x1a, 0x13, 0xfb, 0x27, 0x77, 0x6e, 0x96, - 0x05, 0xc5, 0x18, 0xa3, 0x13, 0x28, 0xd8, 0xcc, 0x49, 0xb8, 0x72, 0x53, 0x6a, 0x57, 0xf4, 0xc3, - 0xab, 0xeb, 0x46, 0xc7, 0xf3, 0xd9, 0x68, 0x62, 0xab, 0x0e, 0xb9, 0xd0, 0x44, 0xa6, 0x33, 0xc2, - 0x7e, 0x98, 0x6d, 0x34, 0x36, 0x8f, 0x5c, 0xaa, 0xea, 0x3d, 0x63, 0x6f, 0xff, 0x0b, 0x21, 0xb9, - 0x6a, 0x33, 0xc7, 0x18, 0xa3, 0xaf, 0x61, 0x25, 0x22, 0x51, 0x6d, 0x85, 0xd7, 0xd1, 0x56, 0x5f, - 0x6a, 0x5f, 0x35, 0x62, 0x42, 0xce, 0x7f, 0x39, 0x37, 0x08, 0xa5, 0x2e, 0xa5, 0x3e, 0x09, 0xcd, - 0x84, 0x84, 0xf6, 0x61, 0x8b, 0x06, 0x98, 0x8e, 0xdc, 0xe1, 0x20, 0xb3, 0x34, 0x72, 0x7d, 0x6f, - 0xc4, 0x6a, 0xf9, 0xa6, 0xd4, 0xce, 0x9b, 0x9b, 0x02, 0xd5, 0x53, 0xf0, 0x47, 0x8e, 0xa1, 0xcf, - 0x01, 0x2d, 0x58, 0xcc, 0xc9, 0x18, 0xab, 0x9c, 0x51, 0xcd, 0x18, 0xcc, 0x49, 0xb3, 0x5b, 0x7f, - 0xcb, 0xb0, 0xb9, 0xdc, 0xbf, 0x5f, 0x7d, 0x36, 0x3a, 0x71, 0x19, 0x5e, 0xea, 0x83, 0xf4, 0x26, - 0xfa, 0xb0, 0x05, 0x05, 0x51, 0x89, 0xcc, 0x2b, 0x11, 0x3b, 0xb4, 0x03, 0x95, 0x29, 0x61, 0x7e, - 0xe8, 0x0d, 0x22, 0xf2, 0x97, 0x1b, 0xf3, 0x46, 0xe5, 0x4d, 0x25, 0x8d, 0x19, 0x49, 0xe8, 0xad, - 0xb4, 0xe1, 0xbf, 0x55, 0x58, 0xd3, 0xfb, 0x47, 0xc7, 0x6e, 0xe0, 0x7a, 0x98, 0xf9, 0x24, 0x7c, - 0x97, 0xe6, 0xa8, 0x0f, 0x30, 0xc5, 0xc1, 0x40, 0x94, 0x93, 0x7f, 0x54, 0x39, 0xa5, 0x29, 0x0e, - 0x74, 0x5e, 0xd1, 0x0e, 0x54, 0x28, 0xc3, 0x31, 0xbb, 0xdf, 0x5a, 0x85, 0xc7, 0xc4, 0x19, 0x7c, - 0x04, 0xe0, 0x86, 0xc3, 0x2c, 0xa1, 0xc0, 0x13, 0xca, 0x6e, 0x38, 0x14, 0xf0, 0x87, 0x50, 0x66, - 0x84, 0xe1, 0x60, 0x40, 0x31, 0xab, 0x15, 0x39, 0x5a, 0xe2, 0x01, 0x0b, 0x33, 0xf4, 0x1d, 0x80, - 0x70, 0x36, 0x60, 0xb3, 0x5a, 0x89, 0xfb, 0x6e, 0x3e, 0xe0, 0xdb, 0x4a, 0x97, 0xfd, 0x99, 0x59, - 0xa6, 0xd9, 0x12, 0x75, 0x40, 0xe1, 0xc7, 0x2c, 0x14, 0xca, 0xdc, 0xf6, 0xc6, 0xd5, 0x75, 0x23, - 0x39, 0x68, 0x4b, 0x20, 0xfd, 0x99, 0x09, 0x74, 0xb1, 0x46, 0x7f, 0xc0, 0xda, 0x30, 0x1d, 0x01, - 0x12, 0x0f, 0xa8, 0xef, 0xd5, 0x80, 0xb3, 0xbe, 0xba, 0xba, 0x6e, 0x1c, 0xbc, 0x4e, 0xb3, 0x2c, - 0xdf, 0x0b, 0x31, 0x9b, 0xc4, 0xae, 0x59, 0x59, 0xe8, 0x59, 0xbe, 0x87, 0xfa, 0x50, 0xfa, 0x73, - 0x12, 0xcf, 0xb9, 0xb4, 0xf2, 0x58, 0xe9, 0x62, 0x22, 0x65, 0xf9, 0x5e, 0xcb, 0x82, 0x0f, 0xee, - 0x66, 0x97, 0xc4, 0x77, 0x43, 0x4c, 0xd1, 0x97, 0x90, 0x1f, 0xba, 0x01, 0xad, 0x49, 0xcd, 0x95, - 0xb6, 0xd2, 0xf9, 0xf8, 0x81, 0xfe, 0xdd, 0x9b, 0x7c, 0x93, 0x33, 0x5a, 0xff, 0x48, 0xb0, 0xf1, - 0xc2, 0x3c, 0xa1, 0x06, 0x28, 0xd9, 0xad, 0x48, 0x3c, 0xf0, 0x5f, 0x83, 0x99, 0x5d, 0x94, 0xc4, - 0xa1, 0x09, 0xc5, 0x64, 0xce, 0x12, 0x50, 0x7e, 0xac, 0xc1, 0xe4, 0x02, 0x25, 0xfe, 0x74, 0x28, - 0x2f, 0x4e, 0x18, 0xad, 0x83, 0xcc, 0x66, 0xe2, 0xc3, 0x32, 0x9b, 0xa1, 0x4f, 0x60, 0x3d, 0x9b, - 0x13, 0xea, 0xc4, 0x7e, 0x94, 0xfe, 0x60, 0x2a, 0xe6, 0x9a, 0x88, 0x5a, 0x3c, 0xf8, 0xd9, 0x37, - 0xf0, 0xde, 0x3d, 0x97, 0x16, 0xc3, 0x6c, 0x42, 0x91, 0x02, 0x45, 0xa3, 0x7b, 0x7a, 0xdc, 0x3b, - 0xfd, 0xa1, 0x9a, 0x43, 0x00, 0x85, 0xef, 0x8f, 0xfa, 0xbd, 0xb3, 0x6e, 0x55, 0x4a, 0x80, 0xee, - 0x6f, 0x46, 0xcf, 0xec, 0x1e, 0x57, 0x65, 0xfd, 0xe7, 0x27, 0x37, 0x75, 0xe9, 0xf2, 0xa6, 0x2e, - 0x3d, 0xbb, 0xa9, 0x4b, 0xff, 0xde, 0xd6, 0x73, 0x97, 0xb7, 0xf5, 0xdc, 0xd3, 0xdb, 0x7a, 0xee, - 0xf7, 0x57, 0x5e, 0xa1, 0xd9, 0xf2, 0x8b, 0xc8, 0x6d, 0xda, 0x05, 0xfe, 0x72, 0xed, 0x3d, 0x0f, - 0x00, 0x00, 0xff, 0xff, 0xdd, 0x6a, 0x0d, 0x4f, 0x34, 0x07, 0x00, 0x00, + // 845 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4d, 0x8f, 0xdb, 0x44, + 0x18, 0x5e, 0x27, 0xd9, 0xec, 0xe6, 0x75, 0xb6, 0xda, 0x0e, 0x4b, 0x09, 0x45, 0x24, 0x69, 0x28, + 0x55, 0x84, 0xa8, 0xcd, 0xa6, 0x1f, 0x2a, 0x20, 0x81, 0xf0, 0x26, 0x82, 0x15, 0xb4, 0x58, 0x76, + 0x54, 0x2a, 0x0e, 0x44, 0x63, 0x7b, 0xea, 0x98, 0x78, 0x3d, 0x96, 0x67, 0x12, 0x92, 0x3b, 0x07, + 0x8e, 0xfc, 0x18, 0x4e, 0xfc, 0x02, 0x8e, 0x15, 0x27, 0xb4, 0x87, 0x08, 0xed, 0xfe, 0x11, 0xe4, + 0xf1, 0x38, 0x71, 0x54, 0x16, 0xd4, 0xee, 0x9e, 0x32, 0x33, 0xef, 0xc7, 0x3c, 0xcf, 0xf3, 0x3e, + 0x13, 0xc3, 0x1d, 0x07, 0x3b, 0x8b, 0x90, 0x46, 0xba, 0xc3, 0x5d, 0xc6, 0xf1, 0x24, 0x88, 0x7c, + 0x7d, 0x76, 0x58, 0xd8, 0x69, 0x71, 0x42, 0x39, 0x45, 0x6f, 0xca, 0x3c, 0xad, 0x10, 0x99, 0x1d, + 0xde, 0x3c, 0xf0, 0xa9, 0x4f, 0x45, 0x86, 0x9e, 0xae, 0xb2, 0xe4, 0x9b, 0x6f, 0xbb, 0x94, 0x9d, + 0x50, 0x36, 0xca, 0x02, 0xd9, 0x46, 0x86, 0x3a, 0xd9, 0x4e, 0x77, 0x93, 0x45, 0xcc, 0xa9, 0xce, + 0x88, 0x1b, 0xf7, 0x1e, 0x3c, 0x9c, 0x1c, 0xea, 0x13, 0xb2, 0xc8, 0x73, 0x6e, 0xcb, 0x9c, 0x35, + 0x1e, 0x87, 0x70, 0x7c, 0xa8, 0x6f, 0x20, 0xea, 0x2c, 0xcb, 0x50, 0x37, 0x86, 0x47, 0x4f, 0x71, + 0x18, 0x78, 0x98, 0xd3, 0x04, 0x0d, 0x40, 0xf5, 0x08, 0x73, 0x93, 0x20, 0xe6, 0x01, 0x8d, 0x1a, + 0x4a, 0x5b, 0xe9, 0xaa, 0xbd, 0xf7, 0x34, 0x79, 0xfd, 0x1a, 0xb4, 0x68, 0xa6, 0xf5, 0xd7, 0xa9, + 0x56, 0xb1, 0x0e, 0x3d, 0x03, 0x70, 0xe9, 0xc9, 0x49, 0xc0, 0x58, 0xda, 0xa5, 0xd4, 0x56, 0xba, + 0x35, 0xe3, 0xd1, 0xe9, 0xb2, 0x75, 0xc7, 0x0f, 0xf8, 0x78, 0xea, 0x68, 0x2e, 0x3d, 0xd1, 0x73, + 0x12, 0xe2, 0xe7, 0x2e, 0xf3, 0x26, 0x3a, 0x5f, 0xc4, 0x84, 0x69, 0x7d, 0xe2, 0xfe, 0xf9, 0xdb, + 0x5d, 0x90, 0x57, 0xf6, 0x89, 0x6b, 0x15, 0x7a, 0xa1, 0xcf, 0x00, 0xa4, 0x8a, 0xa3, 0x78, 0xd2, + 0x28, 0x0b, 0x7c, 0xad, 0x1c, 0x5f, 0x26, 0x88, 0xb6, 0x12, 0x44, 0x33, 0xa7, 0xce, 0xd7, 0x64, + 0x61, 0xd5, 0x64, 0x89, 0x39, 0x41, 0x8f, 0xa1, 0xea, 0x70, 0x37, 0xad, 0xad, 0xb4, 0x95, 0x6e, + 0xdd, 0x78, 0x78, 0xba, 0x6c, 0xf5, 0x0a, 0xa8, 0x64, 0xa6, 0x3b, 0xc6, 0x41, 0x94, 0x6f, 0x24, + 0x30, 0xe3, 0xd8, 0xbc, 0x77, 0xff, 0x23, 0xd9, 0x72, 0xdb, 0xe1, 0xae, 0x39, 0x41, 0x9f, 0x40, + 0x39, 0xa6, 0x71, 0x63, 0x5b, 0xe0, 0xe8, 0x6a, 0xff, 0x3a, 0x60, 0xcd, 0x4c, 0x28, 0x7d, 0xfe, + 0xed, 0x73, 0x93, 0x32, 0x46, 0x04, 0x0b, 0x2b, 0x2d, 0x42, 0xf7, 0xe1, 0x06, 0x0b, 0x31, 0x1b, + 0x13, 0x6f, 0x94, 0x53, 0x1a, 0x93, 0xc0, 0x1f, 0xf3, 0x46, 0xb5, 0xad, 0x74, 0x2b, 0xd6, 0x81, + 0x8c, 0x1a, 0x59, 0xf0, 0x2b, 0x11, 0x43, 0x1f, 0x02, 0x5a, 0x55, 0x71, 0x37, 0xaf, 0xd8, 0x11, + 0x15, 0xfb, 0x79, 0x05, 0x77, 0xb3, 0xec, 0xce, 0xcf, 0x25, 0x38, 0x28, 0x0e, 0xf8, 0xbb, 0x80, + 0x8f, 0x1f, 0x13, 0x8e, 0x0b, 0x3a, 0x28, 0x57, 0xa1, 0xc3, 0x0d, 0xa8, 0x4a, 0x24, 0x25, 0x81, + 0x44, 0xee, 0xd0, 0x2d, 0xa8, 0xcf, 0x28, 0x0f, 0x22, 0x7f, 0x14, 0xd3, 0x9f, 0x48, 0x22, 0x06, + 0x56, 0xb1, 0xd4, 0xec, 0xcc, 0x4c, 0x8f, 0xfe, 0x43, 0x86, 0xca, 0x2b, 0xcb, 0xb0, 0x7d, 0x81, + 0x0c, 0xbf, 0x6f, 0xc3, 0x9e, 0x31, 0x3c, 0xea, 0x93, 0x90, 0xf8, 0x98, 0xbf, 0xec, 0x23, 0xe5, + 0x12, 0x3e, 0x2a, 0x5d, 0xa1, 0x8f, 0xca, 0xaf, 0xe3, 0xa3, 0x21, 0xc0, 0x0c, 0x87, 0xa3, 0x2b, + 0xb1, 0xf5, 0xee, 0x0c, 0x87, 0x86, 0x40, 0x74, 0x0b, 0xea, 0x8c, 0xe3, 0x84, 0x6f, 0x4a, 0xab, + 0x8a, 0x33, 0x39, 0x83, 0x77, 0x01, 0x48, 0xe4, 0x6d, 0x9a, 0xb6, 0x46, 0x22, 0x4f, 0x86, 0xdf, + 0x81, 0x1a, 0xa7, 0x1c, 0x87, 0x23, 0x86, 0x73, 0x83, 0xee, 0x8a, 0x03, 0x1b, 0x73, 0xf4, 0x39, + 0x80, 0x64, 0x36, 0xe2, 0xf3, 0xc6, 0xae, 0xe0, 0xdd, 0xbe, 0x80, 0xb7, 0x9d, 0x2d, 0x87, 0x73, + 0xab, 0xc6, 0xf2, 0x25, 0xea, 0x81, 0x2a, 0xc6, 0x2c, 0x3b, 0xd4, 0x04, 0xed, 0xeb, 0xa7, 0xcb, + 0x56, 0x3a, 0x68, 0x5b, 0x46, 0x86, 0x73, 0x0b, 0xd8, 0x6a, 0x8d, 0x7e, 0x80, 0x3d, 0x2f, 0xb3, + 0x00, 0x4d, 0x46, 0x2c, 0xf0, 0x1b, 0x20, 0xaa, 0x3e, 0x3e, 0x5d, 0xb6, 0x1e, 0xbc, 0x8a, 0x58, + 0x76, 0xe0, 0x47, 0x98, 0x4f, 0x13, 0x62, 0xd5, 0x57, 0xfd, 0xec, 0xc0, 0x47, 0x43, 0xd8, 0xfd, + 0x71, 0x9a, 0x2c, 0x44, 0x6b, 0xf5, 0xb2, 0xad, 0x77, 0xd2, 0x56, 0x76, 0xe0, 0x77, 0x6c, 0x78, + 0x6b, 0xed, 0x5d, 0x9a, 0xac, 0x4d, 0xcc, 0xd0, 0x23, 0xa8, 0x78, 0x24, 0x64, 0x0d, 0xa5, 0x5d, + 0xee, 0xaa, 0xbd, 0xdb, 0x17, 0xe8, 0xb7, 0xe1, 0x7c, 0x4b, 0x54, 0x74, 0x7e, 0x51, 0xe0, 0xfa, + 0x4b, 0x7e, 0x42, 0x2d, 0x50, 0xf3, 0x57, 0x91, 0x72, 0x10, 0x7f, 0x0d, 0x56, 0xfe, 0x50, 0x52, + 0x86, 0x16, 0xec, 0xa4, 0x3e, 0x4b, 0x83, 0xa5, 0xcb, 0x12, 0x4c, 0x1f, 0x50, 0xca, 0xcf, 0x80, + 0xda, 0x6a, 0xc2, 0xe8, 0x1a, 0x94, 0xf8, 0x5c, 0x5e, 0x5c, 0xe2, 0x73, 0xf4, 0x3e, 0x5c, 0xcb, + 0x7d, 0x92, 0x7d, 0x5e, 0xb2, 0x7b, 0xad, 0x3d, 0x79, 0x6a, 0x8b, 0xc3, 0x0f, 0x3e, 0x85, 0x37, + 0x36, 0x58, 0xda, 0x1c, 0xf3, 0x29, 0x43, 0x2a, 0xec, 0x98, 0x83, 0x27, 0xfd, 0xe3, 0x27, 0x5f, + 0xee, 0x6f, 0x21, 0x80, 0xea, 0x17, 0x47, 0xc3, 0xe3, 0xa7, 0x83, 0x7d, 0x25, 0x0d, 0x0c, 0x9e, + 0x99, 0xc7, 0xd6, 0xa0, 0xbf, 0x5f, 0x32, 0xbe, 0xf9, 0xe3, 0xac, 0xa9, 0xbc, 0x38, 0x6b, 0x2a, + 0x7f, 0x9f, 0x35, 0x95, 0x5f, 0xcf, 0x9b, 0x5b, 0x2f, 0xce, 0x9b, 0x5b, 0x7f, 0x9d, 0x37, 0xb7, + 0xbe, 0xff, 0xdf, 0x27, 0x34, 0x2f, 0x7e, 0xf3, 0x05, 0x4d, 0xa7, 0x2a, 0x3e, 0xad, 0xf7, 0xfe, + 0x09, 0x00, 0x00, 0xff, 0xff, 0x6f, 0x4d, 0x54, 0x1d, 0x16, 0x08, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -569,12 +589,12 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { if m.SlashedBtcHeight != 0 { i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBtcHeight)) i-- - dAtA[i] = 0x28 + dAtA[i] = 0x38 } if m.SlashedBabylonHeight != 0 { i = encodeVarintBtcstaking(dAtA, i, uint64(m.SlashedBabylonHeight)) i-- - dAtA[i] = 0x20 + dAtA[i] = 0x30 } if m.Pop != nil { { @@ -586,7 +606,7 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x2a } if m.BtcPk != nil { { @@ -598,7 +618,7 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0x22 } if m.BabylonPk != nil { { @@ -610,6 +630,30 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x1a + } + if m.Commission != nil { + { + size := m.Commission.Size() + i -= size + if _, err := m.Commission.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Description != nil { + { + size, err := m.Description.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0xa } return len(dAtA) - i, nil @@ -937,6 +981,14 @@ func (m *BTCValidator) Size() (n int) { } var l int _ = l + if m.Description != nil { + l = m.Description.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.Commission != nil { + l = m.Commission.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } if m.BabylonPk != nil { l = m.BabylonPk.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -1118,6 +1170,78 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Description == nil { + m.Description = &types.Description{} + } + if err := m.Description.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commission", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_cosmos_cosmos_sdk_types.Dec + m.Commission = &v + if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) } @@ -1153,7 +1277,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 2: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) } @@ -1188,7 +1312,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 3: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) } @@ -1224,7 +1348,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field SlashedBabylonHeight", wireType) } @@ -1243,7 +1367,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { break } } - case 5: + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field SlashedBtcHeight", wireType) } diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index eb70ee98b..c013cf96e 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -18,4 +18,5 @@ var ( ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1109, "the BTC slashing tx is not valid") ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") + ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1112, "commission cannot be less than min rate") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index b562daac9..03094b6e1 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -45,6 +45,15 @@ func (m *MsgCreateBTCValidator) GetSigners() []sdk.AccAddress { } func (m *MsgCreateBTCValidator) ValidateBasic() error { + if m.Commission == nil { + return fmt.Errorf("empty commission") + } + if m.Description == nil { + return fmt.Errorf("empty description") + } + if _, err := m.Description.EnsureLength(); err != nil { + return err + } if m.BabylonPk == nil { return fmt.Errorf("empty Babylon public key") } diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index c5ba3216f..fbc50a6ce 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -1,6 +1,7 @@ package types import ( + "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" @@ -39,6 +40,7 @@ func DefaultParams() Params { JuryPk: defaultJuryPk(), SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, + MinCommissionRate: math.LegacyZeroDec(), } } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 99bd52c13..ab6c064c8 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -6,6 +6,7 @@ package types import ( fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -36,6 +37,8 @@ type Params struct { // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? MinSlashingTxFeeSat int64 `protobuf:"varint,3,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` + // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators + MinCommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_commission_rate"` } func (m *Params) Reset() { *m = Params{} } @@ -93,25 +96,29 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 282 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, - 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, - 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, - 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0x95, 0x0e, 0x30, 0x72, 0xb1, 0x05, 0x80, 0x75, 0x0b, 0xf9, 0x73, - 0xb1, 0x67, 0x95, 0x16, 0x55, 0xc6, 0x17, 0x64, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x38, 0x99, - 0xdd, 0xba, 0x27, 0x6f, 0x94, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, - 0x35, 0x37, 0x39, 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x73, - 0xf2, 0x0c, 0x30, 0x36, 0x31, 0x08, 0x28, 0x4d, 0xf2, 0x4e, 0xad, 0x0c, 0x62, 0x03, 0x19, 0x13, - 0x90, 0x2d, 0xa4, 0xc9, 0x25, 0x50, 0x9c, 0x93, 0x58, 0x9c, 0x91, 0x99, 0x97, 0x1e, 0x9f, 0x98, - 0x92, 0x52, 0x94, 0x5a, 0x5c, 0x2c, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x19, 0xc4, 0x0f, 0x13, 0x77, - 0x84, 0x08, 0x0b, 0x99, 0x70, 0x89, 0xe7, 0x66, 0xe6, 0xc5, 0xc3, 0x95, 0x97, 0x54, 0xc4, 0xa7, - 0xa5, 0xa6, 0xc6, 0x17, 0x27, 0x96, 0x48, 0x30, 0x2b, 0x30, 0x6a, 0x30, 0x07, 0x09, 0xe7, 0x66, - 0xe6, 0x05, 0x43, 0x65, 0x43, 0x2a, 0xdc, 0x52, 0x53, 0x83, 0x13, 0x4b, 0xac, 0x58, 0x66, 0x2c, - 0x90, 0x67, 0x70, 0xf2, 0x39, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, - 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0x82, - 0x8e, 0xaf, 0x40, 0x0e, 0x47, 0xb0, 0x4f, 0x92, 0xd8, 0xc0, 0xe1, 0x62, 0x0c, 0x08, 0x00, 0x00, - 0xff, 0xff, 0x7e, 0x83, 0xd9, 0x99, 0x6a, 0x01, 0x00, 0x00, + // 348 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xc1, 0x4f, 0xea, 0x30, + 0x00, 0xc6, 0x57, 0x20, 0xbc, 0xbc, 0xe5, 0x25, 0x4f, 0xa7, 0xc6, 0xc5, 0xc3, 0x46, 0x38, 0x18, + 0x3c, 0xb8, 0x89, 0x10, 0x0f, 0xde, 0x9c, 0xc6, 0xc4, 0x68, 0xe2, 0x32, 0x3c, 0x79, 0xb0, 0xe9, + 0x46, 0x1d, 0x75, 0xb4, 0x5d, 0xd6, 0x42, 0xd8, 0x7f, 0xe1, 0xd1, 0x23, 0x7f, 0x0e, 0x47, 0x8e, + 0x86, 0x03, 0x31, 0xf0, 0x8f, 0x98, 0x8d, 0x81, 0xdc, 0x3c, 0xb5, 0xfd, 0xfa, 0xeb, 0xf7, 0xb5, + 0x5f, 0xd5, 0xba, 0x8f, 0xfc, 0xb4, 0xcf, 0x99, 0xed, 0xcb, 0x40, 0x48, 0x14, 0x11, 0x16, 0xda, + 0xc3, 0xa6, 0x1d, 0xa3, 0x04, 0x51, 0x61, 0xc5, 0x09, 0x97, 0x5c, 0x3b, 0x28, 0x18, 0xeb, 0x87, + 0xb1, 0x86, 0xcd, 0xa3, 0xfd, 0x90, 0x87, 0x3c, 0x27, 0xec, 0x6c, 0xb6, 0x82, 0xeb, 0xe3, 0x92, + 0x5a, 0x75, 0xf3, 0xd3, 0xda, 0xa3, 0xfa, 0xe7, 0x6d, 0x90, 0xa4, 0x30, 0x8e, 0x74, 0x50, 0x03, + 0x8d, 0x7f, 0xce, 0xc5, 0x6c, 0x6e, 0x9e, 0x87, 0x44, 0xf6, 0x06, 0xbe, 0x15, 0x70, 0x6a, 0x17, + 0xbe, 0x41, 0x0f, 0x11, 0xb6, 0x5e, 0xd8, 0x32, 0x8d, 0xb1, 0xb0, 0x9c, 0x3b, 0xb7, 0xd5, 0x3e, + 0x73, 0x07, 0xfe, 0x3d, 0x4e, 0xbd, 0x6a, 0x66, 0xe3, 0x46, 0xda, 0x89, 0xba, 0x23, 0xfa, 0x48, + 0xf4, 0x08, 0x0b, 0x21, 0xea, 0x76, 0x13, 0x2c, 0x84, 0x5e, 0xaa, 0x81, 0xc6, 0x5f, 0xef, 0xff, + 0x5a, 0xbf, 0x5a, 0xc9, 0x5a, 0x5b, 0x3d, 0xa4, 0x84, 0xc1, 0x0d, 0x2e, 0x47, 0xf0, 0x15, 0x63, + 0x28, 0x90, 0xd4, 0xcb, 0x35, 0xd0, 0x28, 0x7b, 0x7b, 0x94, 0xb0, 0x4e, 0xb1, 0xfb, 0x34, 0xba, + 0xc5, 0xb8, 0x83, 0xa4, 0xf6, 0xa2, 0x66, 0x32, 0x0c, 0x38, 0xa5, 0x44, 0x08, 0xc2, 0x19, 0x4c, + 0x90, 0xc4, 0x7a, 0x25, 0xcb, 0x70, 0xac, 0xc9, 0xdc, 0x54, 0x66, 0x73, 0xf3, 0x78, 0xeb, 0x05, + 0x01, 0x17, 0x94, 0x8b, 0x62, 0x38, 0x15, 0xdd, 0xa8, 0xb8, 0xfe, 0x0d, 0x0e, 0xbc, 0x5d, 0x4a, + 0xd8, 0xf5, 0xc6, 0xc9, 0x43, 0x12, 0x5f, 0x56, 0x3e, 0xc6, 0xa6, 0xe2, 0x3c, 0x4c, 0x16, 0x06, + 0x98, 0x2e, 0x0c, 0xf0, 0xb5, 0x30, 0xc0, 0xfb, 0xd2, 0x50, 0xa6, 0x4b, 0x43, 0xf9, 0x5c, 0x1a, + 0xca, 0xf3, 0xaf, 0xe5, 0x8c, 0xb6, 0xff, 0x29, 0x8f, 0xf2, 0xab, 0x79, 0xef, 0xad, 0xef, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x26, 0xeb, 0xc5, 0x78, 0xca, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -134,6 +141,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.MinCommissionRate.Size() + i -= size + if _, err := m.MinCommissionRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 if m.MinSlashingTxFeeSat != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MinSlashingTxFeeSat)) i-- @@ -189,6 +206,8 @@ func (m *Params) Size() (n int) { if m.MinSlashingTxFeeSat != 0 { n += 1 + sovParams(uint64(m.MinSlashingTxFeeSat)) } + l = m.MinCommissionRate.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -313,6 +332,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinCommissionRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinCommissionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 50c3fec42..4286e356d 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -7,10 +7,12 @@ import ( context "context" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" - types "github.com/babylonchain/babylon/x/btccheckpoint/types" + types1 "github.com/babylonchain/babylon/x/btccheckpoint/types" _ "github.com/cosmos/cosmos-proto" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/cosmos-sdk/types/msgservice" + types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -36,13 +38,17 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgCreateBTCValidator is the message for creating a BTC validator type MsgCreateBTCValidator struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // description defines the description terms for the BTC validator. + Description *types.Description `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // commission defines the commission rate of BTC validator. + Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` // babylon_pk is the Babylon secp256k1 PK of this BTC validator - BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + BabylonPk *secp256k1.PubKey `protobuf:"bytes,4,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // btc_pk is the Bitcoin secp256k1 PK of this BTC validator // the PK follows encoding in BIP-340 spec - BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk - Pop *ProofOfPossession `protobuf:"bytes,4,opt,name=pop,proto3" json:"pop,omitempty"` + Pop *ProofOfPossession `protobuf:"bytes,6,opt,name=pop,proto3" json:"pop,omitempty"` } func (m *MsgCreateBTCValidator) Reset() { *m = MsgCreateBTCValidator{} } @@ -85,6 +91,13 @@ func (m *MsgCreateBTCValidator) GetSigner() string { return "" } +func (m *MsgCreateBTCValidator) GetDescription() *types.Description { + if m != nil { + return m.Description + } + return nil +} + func (m *MsgCreateBTCValidator) GetBabylonPk() *secp256k1.PubKey { if m != nil { return m.BabylonPk @@ -146,7 +159,7 @@ type MsgCreateBTCDelegation struct { // staking_tx is the staking tx StakingTx *StakingTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // staking_tx_info is the tx info of the staking tx, including the Merkle proof - StakingTxInfo *types.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` + StakingTxInfo *types1.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. SlashingTx *BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` @@ -218,7 +231,7 @@ func (m *MsgCreateBTCDelegation) GetStakingTx() *StakingTx { return nil } -func (m *MsgCreateBTCDelegation) GetStakingTxInfo() *types.TransactionInfo { +func (m *MsgCreateBTCDelegation) GetStakingTxInfo() *types1.TransactionInfo { if m != nil { return m.StakingTxInfo } @@ -474,58 +487,63 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 802 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcb, 0x6e, 0xf3, 0x44, - 0x14, 0x8e, 0x9b, 0x36, 0x28, 0xd3, 0x86, 0x0a, 0xd3, 0x4b, 0x1a, 0xa9, 0x4e, 0x64, 0xa1, 0x2a, - 0xa0, 0xd6, 0x26, 0xe9, 0x45, 0xa2, 0x48, 0xa0, 0xa6, 0x2c, 0x28, 0x25, 0x22, 0x38, 0x81, 0x05, - 0x0b, 0xc2, 0xd8, 0x9e, 0x8c, 0xad, 0x24, 0x1e, 0xcb, 0x33, 0x89, 0x12, 0xb1, 0xe3, 0x09, 0x58, - 0xf1, 0x1c, 0x2c, 0x58, 0xf1, 0x00, 0xa8, 0xcb, 0x0a, 0xb1, 0x40, 0x5d, 0x44, 0xa8, 0x5d, 0xf0, - 0x0e, 0xac, 0x90, 0xed, 0xf1, 0xa5, 0xc8, 0x11, 0x2d, 0xe5, 0xdf, 0xcd, 0xf8, 0x7c, 0xe7, 0x9b, - 0x73, 0xbe, 0x73, 0x31, 0x90, 0x74, 0xa8, 0xcf, 0x47, 0xc4, 0x51, 0x75, 0x66, 0x50, 0x06, 0x87, - 0xb6, 0x83, 0xd5, 0x69, 0x43, 0x65, 0x33, 0xc5, 0xf5, 0x08, 0x23, 0xe2, 0x36, 0xb7, 0x2b, 0x89, - 0x5d, 0x99, 0x36, 0x2a, 0x5b, 0x98, 0x60, 0x12, 0x20, 0x54, 0xff, 0x14, 0x82, 0x2b, 0x7b, 0x06, - 0xa1, 0x63, 0x42, 0xfb, 0xa1, 0x21, 0xbc, 0x70, 0xd3, 0x6e, 0x78, 0x53, 0xc7, 0x34, 0xe0, 0x1f, - 0x53, 0xcc, 0x0d, 0x32, 0x37, 0x18, 0xde, 0xdc, 0x65, 0x44, 0xa5, 0xc8, 0x70, 0x9b, 0xa7, 0x67, - 0xc3, 0x86, 0x3a, 0x44, 0xf3, 0xc8, 0x59, 0xce, 0x0e, 0xd2, 0x85, 0x1e, 0x1c, 0x47, 0x98, 0x83, - 0x6c, 0x4c, 0x2a, 0xec, 0x10, 0x77, 0x98, 0xc2, 0x19, 0x16, 0x32, 0x86, 0x2e, 0xb1, 0x1d, 0xc6, - 0xa1, 0xc9, 0x87, 0x10, 0x2d, 0xff, 0x25, 0x80, 0xed, 0x36, 0xc5, 0x97, 0x1e, 0x82, 0x0c, 0xb5, - 0x7a, 0x97, 0x5f, 0xc2, 0x91, 0x6d, 0x42, 0x46, 0x3c, 0x71, 0x07, 0x14, 0xa8, 0x8d, 0x1d, 0xe4, - 0x95, 0x85, 0x9a, 0x50, 0x2f, 0x6a, 0xfc, 0x26, 0x7e, 0x00, 0x00, 0x7f, 0xa1, 0xef, 0x0e, 0xcb, - 0x2b, 0x35, 0xa1, 0xbe, 0xde, 0xac, 0x2a, 0x5c, 0x8b, 0x30, 0x49, 0x25, 0x4e, 0x52, 0xe9, 0x4c, - 0xf4, 0x6b, 0x34, 0xd7, 0x8a, 0xdc, 0xa5, 0x33, 0x14, 0xdb, 0xa0, 0xa0, 0x33, 0xc3, 0xf7, 0xcd, - 0xd7, 0x84, 0xfa, 0x46, 0xeb, 0xec, 0x6e, 0x51, 0x6d, 0x62, 0x9b, 0x59, 0x13, 0x5d, 0x31, 0xc8, - 0x58, 0xe5, 0x48, 0xc3, 0x82, 0xb6, 0x13, 0x5d, 0x54, 0x36, 0x77, 0x11, 0x55, 0x5a, 0x57, 0x9d, - 0xe3, 0x93, 0x77, 0x39, 0xe5, 0x9a, 0xce, 0x8c, 0xce, 0x50, 0x3c, 0x07, 0x79, 0x97, 0xb8, 0xe5, - 0xd5, 0x20, 0x8e, 0xba, 0x92, 0x59, 0x4d, 0xa5, 0xe3, 0x11, 0x32, 0xf8, 0x6c, 0xd0, 0x21, 0x94, - 0x22, 0x4a, 0x6d, 0xe2, 0x68, 0xbe, 0x93, 0x5c, 0x05, 0xfb, 0x99, 0xb9, 0x6b, 0x88, 0xba, 0xc4, - 0xa1, 0x48, 0xfe, 0x2d, 0x0f, 0x76, 0xd2, 0x88, 0x8f, 0xd0, 0x08, 0x61, 0xc8, 0x6c, 0xe2, 0xbc, - 0x32, 0x79, 0x78, 0x3e, 0xf9, 0xff, 0x90, 0x8f, 0xf8, 0x21, 0x00, 0x1c, 0xd4, 0x67, 0x33, 0x2e, - 0x49, 0x6d, 0x09, 0x45, 0x37, 0x3c, 0xf6, 0x66, 0x5a, 0x91, 0x46, 0x47, 0xf1, 0x73, 0xb0, 0x99, - 0x10, 0xf4, 0x6d, 0x67, 0x40, 0xca, 0x6b, 0x01, 0xcb, 0xdb, 0x69, 0x96, 0x54, 0x13, 0x4d, 0x1b, - 0x4a, 0xcf, 0x83, 0x0e, 0x85, 0x86, 0x2f, 0xca, 0x95, 0x33, 0x20, 0x5a, 0x29, 0xa6, 0xf3, 0xaf, - 0x62, 0x13, 0xac, 0xd3, 0x11, 0xa4, 0x16, 0x0f, 0xaa, 0x10, 0xd4, 0xfc, 0x8d, 0xbb, 0x45, 0xb5, - 0xd4, 0xea, 0x5d, 0x76, 0xb9, 0xa5, 0x37, 0xd3, 0x00, 0x8d, 0xcf, 0xe2, 0xd7, 0xa0, 0x64, 0x86, - 0x4a, 0x13, 0xaf, 0x4f, 0x6d, 0x5c, 0x7e, 0x2d, 0xf0, 0x7a, 0xef, 0x6e, 0x51, 0x3d, 0x7d, 0x4e, - 0xa7, 0x74, 0x6d, 0xec, 0x40, 0x36, 0xf1, 0x90, 0xb6, 0x11, 0xf3, 0x75, 0x6d, 0x2c, 0xd7, 0x80, - 0x94, 0x5d, 0xd5, 0xb8, 0xf0, 0x3f, 0xaf, 0x80, 0x52, 0x9b, 0xe2, 0x0b, 0xd3, 0xfc, 0x64, 0xe2, - 0xcd, 0xbb, 0x36, 0x5e, 0x5a, 0xef, 0x36, 0x28, 0x4c, 0xe1, 0x28, 0xaa, 0xf5, 0x0b, 0xda, 0x79, - 0x0a, 0x47, 0xe1, 0x74, 0x98, 0x68, 0xf4, 0x3f, 0x4c, 0x87, 0x89, 0x7c, 0xba, 0x83, 0x47, 0x05, - 0xb5, 0x20, 0xb5, 0x82, 0xb6, 0x28, 0xa6, 0xaa, 0xf4, 0x31, 0xa4, 0x96, 0x78, 0x0d, 0xf2, 0xbe, - 0xce, 0x6b, 0x2f, 0xd5, 0xd9, 0x67, 0x91, 0x77, 0x83, 0x95, 0x92, 0x68, 0x17, 0xab, 0xfa, 0x83, - 0x00, 0x36, 0xdb, 0x14, 0x7f, 0xe1, 0x9a, 0x90, 0xa1, 0x4e, 0xb0, 0xdc, 0xc4, 0x33, 0x50, 0x84, - 0x13, 0x66, 0x11, 0xcf, 0x66, 0xf3, 0x50, 0xda, 0x56, 0xf9, 0xd7, 0x9f, 0x8e, 0xb6, 0xf8, 0xc4, - 0x5c, 0x98, 0xa6, 0x87, 0x28, 0xed, 0x32, 0xcf, 0x76, 0xb0, 0x96, 0x40, 0xc5, 0xf7, 0x41, 0x21, - 0x5c, 0x8f, 0x7c, 0xc6, 0xf6, 0x97, 0x8d, 0x4a, 0x00, 0x6a, 0xad, 0xde, 0x2c, 0xaa, 0x39, 0x8d, - 0xbb, 0x9c, 0xbf, 0xfe, 0xdd, 0x9f, 0x3f, 0xbe, 0x93, 0x90, 0xc9, 0x7b, 0x60, 0xf7, 0x1f, 0x71, - 0x45, 0x31, 0x37, 0x7f, 0xc9, 0x83, 0x7c, 0x9b, 0x62, 0x71, 0x06, 0xc4, 0x8c, 0x25, 0x79, 0xb8, - 0xe4, 0xd5, 0xcc, 0xb5, 0x52, 0x39, 0x79, 0x0e, 0x3a, 0x8a, 0x40, 0xfc, 0x16, 0xbc, 0x99, 0xb5, - 0x80, 0x8e, 0x9e, 0x40, 0x96, 0xc0, 0x2b, 0xa7, 0xcf, 0x82, 0xc7, 0x8f, 0x7f, 0x03, 0x40, 0x6a, - 0x08, 0xde, 0x5a, 0x4e, 0x92, 0xa0, 0x2a, 0x87, 0x4f, 0x41, 0xc5, 0x2f, 0x0c, 0xc0, 0xc6, 0xa3, - 0x86, 0x38, 0x58, 0xee, 0x9d, 0xc6, 0x55, 0x94, 0xa7, 0xe1, 0xa2, 0x77, 0x5a, 0x9f, 0xde, 0xdc, - 0x4b, 0xc2, 0xed, 0xbd, 0x24, 0xfc, 0x71, 0x2f, 0x09, 0xdf, 0x3f, 0x48, 0xb9, 0xdb, 0x07, 0x29, - 0xf7, 0xfb, 0x83, 0x94, 0xfb, 0xea, 0x5f, 0xe7, 0x6b, 0x96, 0xfe, 0xe7, 0x06, 0x8d, 0xaf, 0x17, - 0x82, 0xdf, 0xe7, 0xf1, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x98, 0xbb, 0xd7, 0x70, 0x5f, 0x08, - 0x00, 0x00, + // 894 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0x8e, 0xb3, 0x8d, 0x91, 0x5f, 0x12, 0x2a, 0x86, 0xb6, 0x71, 0x2d, 0xd5, 0xb6, 0x4c, 0x15, + 0x05, 0x94, 0xec, 0x62, 0xb7, 0x89, 0xa0, 0x48, 0xa0, 0x3a, 0x41, 0xa2, 0x14, 0x0b, 0xb3, 0x36, + 0x08, 0x71, 0xc0, 0xcc, 0xee, 0x8e, 0xd7, 0x23, 0xdb, 0x3b, 0xab, 0x9d, 0xb1, 0x65, 0x8b, 0x1b, + 0xbf, 0x80, 0x13, 0x17, 0xfe, 0x04, 0x87, 0x9e, 0xf8, 0x01, 0xa8, 0xc7, 0x2a, 0xe2, 0x80, 0x72, + 0xb0, 0x50, 0x72, 0xe0, 0x6f, 0xa0, 0x9d, 0x9d, 0xf5, 0x6e, 0xd0, 0x5a, 0x24, 0xcd, 0xc9, 0x33, + 0x7e, 0xdf, 0xfb, 0xde, 0x9b, 0xef, 0x9b, 0x37, 0x36, 0x94, 0x2d, 0x6c, 0xcd, 0x47, 0xcc, 0x33, + 0x2c, 0x61, 0x73, 0x81, 0x87, 0xd4, 0x73, 0x8d, 0x69, 0xdd, 0x10, 0x33, 0xdd, 0x0f, 0x98, 0x60, + 0xe8, 0xae, 0x8a, 0xeb, 0x49, 0x5c, 0x9f, 0xd6, 0x4b, 0x77, 0x5c, 0xe6, 0x32, 0x89, 0x30, 0xc2, + 0x55, 0x04, 0x2e, 0xdd, 0xb7, 0x19, 0x1f, 0x33, 0xde, 0x8b, 0x02, 0xd1, 0x46, 0x85, 0x76, 0xa2, + 0x9d, 0x31, 0xe6, 0x92, 0x7f, 0xcc, 0x5d, 0x15, 0xa8, 0xa9, 0x80, 0x1d, 0xcc, 0x7d, 0xc1, 0x0c, + 0x4e, 0x6c, 0xbf, 0x71, 0x78, 0x34, 0xac, 0x1b, 0x43, 0x32, 0x8f, 0x93, 0x6b, 0xd9, 0x4d, 0xfa, + 0x38, 0xc0, 0xe3, 0x18, 0xb3, 0x9b, 0x8d, 0x49, 0xb5, 0x1d, 0xe1, 0xf6, 0x53, 0x38, 0x7b, 0x40, + 0xec, 0xa1, 0xcf, 0xa8, 0x27, 0x14, 0x34, 0xf9, 0x42, 0xa1, 0x1f, 0xaa, 0xee, 0x12, 0x46, 0x8b, + 0x08, 0x5c, 0x37, 0x2e, 0x71, 0xd6, 0x7e, 0xd5, 0xe0, 0x6e, 0x8b, 0xbb, 0xc7, 0x01, 0xc1, 0x82, + 0x34, 0xbb, 0xc7, 0xdf, 0xe0, 0x11, 0x75, 0xb0, 0x60, 0x01, 0xba, 0x07, 0x79, 0x4e, 0x5d, 0x8f, + 0x04, 0xc5, 0x5c, 0x35, 0xb7, 0x57, 0x30, 0xd5, 0x0e, 0x7d, 0x0a, 0x9b, 0x0e, 0xe1, 0x76, 0x40, + 0x7d, 0x41, 0x99, 0x57, 0x5c, 0xaf, 0xe6, 0xf6, 0x36, 0x1b, 0xef, 0xe8, 0x4a, 0xb2, 0x44, 0x68, + 0x59, 0x4d, 0x3f, 0x49, 0xa0, 0x66, 0x3a, 0x0f, 0x7d, 0x0b, 0x60, 0xb3, 0xf1, 0x98, 0x72, 0x1e, + 0xb2, 0x68, 0x61, 0x89, 0xe6, 0x07, 0x67, 0x8b, 0xca, 0xae, 0x4b, 0xc5, 0x60, 0x62, 0xe9, 0x36, + 0x1b, 0x1b, 0xb1, 0xbe, 0xf2, 0xe3, 0x80, 0x3b, 0x43, 0x43, 0xcc, 0x7d, 0xc2, 0xf5, 0x13, 0x62, + 0x9f, 0xbe, 0x38, 0x00, 0x55, 0xf2, 0x84, 0xd8, 0x66, 0x8a, 0x0b, 0x7d, 0x0c, 0xa0, 0x84, 0xea, + 0xf9, 0xc3, 0xe2, 0x2d, 0xd9, 0x5f, 0x25, 0xee, 0x2f, 0xf2, 0x4a, 0x5f, 0x7a, 0xa5, 0xb7, 0x27, + 0xd6, 0x73, 0x32, 0x37, 0x0b, 0x2a, 0xa5, 0x3d, 0x44, 0x2d, 0xc8, 0x5b, 0xc2, 0x0e, 0x73, 0x37, + 0xaa, 0xb9, 0xbd, 0xad, 0xe6, 0xd1, 0xd9, 0xa2, 0xd2, 0x48, 0x75, 0xa5, 0x90, 0xf6, 0x00, 0x53, + 0x2f, 0xde, 0xa8, 0xc6, 0x9a, 0xcf, 0xda, 0x8f, 0x1e, 0xbf, 0xaf, 0x28, 0x37, 0x2c, 0x61, 0xb7, + 0x87, 0xe8, 0x09, 0x68, 0x3e, 0xf3, 0x8b, 0x79, 0xd9, 0xc7, 0x9e, 0x9e, 0x79, 0x29, 0xf5, 0x76, + 0xc0, 0x58, 0xff, 0xcb, 0x7e, 0x9b, 0x71, 0x4e, 0xe4, 0x29, 0xcc, 0x30, 0xa9, 0x56, 0x81, 0x07, + 0x99, 0xe6, 0x98, 0x84, 0xfb, 0xcc, 0xe3, 0xa4, 0xf6, 0xa7, 0x06, 0xf7, 0xd2, 0x88, 0x13, 0x32, + 0x22, 0x2e, 0x96, 0x02, 0xaf, 0xf2, 0xef, 0xb2, 0x3c, 0xeb, 0xd7, 0x96, 0x47, 0x9d, 0x47, 0x7b, + 0x8d, 0xf3, 0xa0, 0x4f, 0x00, 0x14, 0xa8, 0x27, 0x66, 0xca, 0x9a, 0xea, 0x0a, 0x8a, 0x4e, 0xb4, + 0xec, 0xce, 0xcc, 0x02, 0x8f, 0x97, 0xe8, 0x2b, 0xb8, 0x9d, 0x10, 0xf4, 0xa8, 0xd7, 0x67, 0xd2, + 0xa4, 0xcd, 0xc6, 0xbb, 0x69, 0x96, 0xd4, 0x2c, 0x4c, 0xeb, 0x7a, 0x37, 0xc0, 0x1e, 0xc7, 0x76, + 0x28, 0xca, 0x33, 0xaf, 0xcf, 0xcc, 0xed, 0x25, 0x5d, 0xb8, 0x45, 0x0d, 0xd8, 0xe4, 0x23, 0xcc, + 0x07, 0xaa, 0xa9, 0xbc, 0xf4, 0xfc, 0xad, 0xb3, 0x45, 0x65, 0xbb, 0xd9, 0x3d, 0xee, 0xa8, 0x48, + 0x77, 0x66, 0x02, 0x5f, 0xae, 0xd1, 0xf7, 0xb0, 0xed, 0x44, 0x4a, 0xb3, 0xa0, 0xc7, 0xa9, 0x5b, + 0x7c, 0x43, 0x66, 0x7d, 0x78, 0xb6, 0xa8, 0x1c, 0x5e, 0xe7, 0xa6, 0x74, 0xa8, 0xeb, 0x61, 0x31, + 0x09, 0x88, 0xb9, 0xb5, 0xe4, 0xeb, 0x50, 0xb7, 0x56, 0x85, 0x72, 0xb6, 0xab, 0x4b, 0xe3, 0x7f, + 0x5f, 0x87, 0xed, 0x16, 0x77, 0x9f, 0x3a, 0xce, 0xe7, 0x93, 0x60, 0xde, 0xa1, 0xee, 0x4a, 0xbf, + 0x5b, 0x90, 0x9f, 0xe2, 0x51, 0xec, 0xf5, 0x0d, 0xae, 0xf3, 0x14, 0x8f, 0xa2, 0xe9, 0x70, 0x88, + 0xa4, 0xd3, 0x6e, 0x46, 0xe7, 0x90, 0x90, 0x6e, 0xf7, 0x92, 0xa1, 0x03, 0xcc, 0x07, 0xf2, 0x5a, + 0x14, 0x52, 0x2e, 0x7d, 0x86, 0xf9, 0x00, 0x3d, 0x07, 0x2d, 0xd4, 0x79, 0xe3, 0xa6, 0x3a, 0x87, + 0x2c, 0xb5, 0x1d, 0xf9, 0xe6, 0x25, 0xda, 0x2d, 0x55, 0xfd, 0x25, 0x07, 0xb7, 0x5b, 0xdc, 0xfd, + 0xda, 0x77, 0xb0, 0x20, 0x6d, 0xf9, 0x46, 0xa3, 0x23, 0x28, 0xe0, 0x89, 0x18, 0xb0, 0x80, 0x8a, + 0x79, 0x24, 0x6d, 0xb3, 0x78, 0xfa, 0xe2, 0xe0, 0x8e, 0x9a, 0x98, 0xa7, 0x8e, 0x13, 0x10, 0xce, + 0x3b, 0x22, 0xa0, 0x9e, 0x6b, 0x26, 0x50, 0xf4, 0x11, 0xe4, 0xa3, 0x57, 0x5e, 0xcd, 0xd8, 0x83, + 0x55, 0xa3, 0x22, 0x41, 0xcd, 0x5b, 0x2f, 0x17, 0x95, 0x35, 0x53, 0xa5, 0x3c, 0x79, 0xf3, 0xa7, + 0x7f, 0x7e, 0x7b, 0x2f, 0x21, 0xab, 0xdd, 0x87, 0x9d, 0xff, 0xf4, 0x15, 0xf7, 0xdc, 0xf8, 0x43, + 0x03, 0xad, 0xc5, 0x5d, 0x34, 0x03, 0x94, 0xf1, 0x8a, 0xef, 0xaf, 0xa8, 0x9a, 0xf9, 0xac, 0x94, + 0x1e, 0x5f, 0x07, 0x1d, 0x77, 0x80, 0x7e, 0x84, 0xb7, 0xb3, 0x1e, 0xa0, 0x83, 0x2b, 0x90, 0x25, + 0xf0, 0xd2, 0xe1, 0xb5, 0xe0, 0xcb, 0xe2, 0x3f, 0x00, 0xa4, 0x86, 0xe0, 0xe1, 0x6a, 0x92, 0x04, + 0x55, 0xda, 0xbf, 0x0a, 0x6a, 0x59, 0xa1, 0x0f, 0x5b, 0x97, 0x2e, 0xc4, 0xee, 0xea, 0xec, 0x34, + 0xae, 0xa4, 0x5f, 0x0d, 0x17, 0xd7, 0x69, 0x7e, 0xf1, 0xf2, 0xbc, 0x9c, 0x7b, 0x75, 0x5e, 0xce, + 0xfd, 0x7d, 0x5e, 0xce, 0xfd, 0x7c, 0x51, 0x5e, 0x7b, 0x75, 0x51, 0x5e, 0xfb, 0xeb, 0xa2, 0xbc, + 0xf6, 0xdd, 0xff, 0xce, 0xd7, 0x2c, 0xfd, 0xd7, 0x41, 0x5e, 0x7c, 0x2b, 0x2f, 0x7f, 0xdf, 0x1f, + 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x1c, 0xdd, 0xea, 0x26, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -754,7 +772,7 @@ func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x32 } if m.BtcPk != nil { { @@ -766,7 +784,7 @@ func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x2a } if m.BabylonPk != nil { { @@ -778,6 +796,30 @@ func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x22 + } + if m.Commission != nil { + { + size := m.Commission.Size() + i -= size + if _, err := m.Commission.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Description != nil { + { + size, err := m.Description.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x12 } if len(m.Signer) > 0 { @@ -1118,6 +1160,14 @@ func (m *MsgCreateBTCValidator) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + if m.Description != nil { + l = m.Description.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Commission != nil { + l = m.Commission.Size() + n += 1 + l + sovTx(uint64(l)) + } if m.BabylonPk != nil { l = m.BabylonPk.Size() n += 1 + l + sovTx(uint64(l)) @@ -1318,6 +1368,78 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Description == nil { + m.Description = &types.Description{} + } + if err := m.Description.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commission", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_cosmos_cosmos_sdk_types.Dec + m.Commission = &v + if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) } @@ -1353,7 +1475,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 3: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) } @@ -1388,7 +1510,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) } @@ -1694,7 +1816,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.StakingTxInfo == nil { - m.StakingTxInfo = &types.TransactionInfo{} + m.StakingTxInfo = &types1.TransactionInfo{} } if err := m.StakingTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err From 5a92ef6b0e002c30d6e44587a3023727b20a8610 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 1 Sep 2023 10:00:45 +1000 Subject: [PATCH 068/202] incentive: set up KVStore schema, parameters and query API (#66) --- proto/babylon/incentive/genesis.proto | 2 +- proto/babylon/incentive/incentive.proto | 39 ++ proto/babylon/incentive/params.proto | 30 +- proto/babylon/incentive/query.proto | 32 +- testutil/datagen/incentive.go | 46 ++ testutil/keeper/incentive.go | 4 +- x/incentive/genesis.go | 4 +- x/incentive/keeper/btc_staking_gauge.go | 35 + x/incentive/keeper/btc_timestamping_gauge.go | 35 + x/incentive/keeper/grpc_query.go | 37 ++ x/incentive/keeper/grpc_query_test.go | 49 ++ x/incentive/keeper/keeper.go | 8 +- x/incentive/keeper/params.go | 24 +- x/incentive/keeper/params_test.go | 3 +- x/incentive/keeper/query.go | 7 - x/incentive/keeper/query_params_test.go | 3 +- x/incentive/keeper/reward_gauge.go | 36 + x/incentive/types/errors.go | 4 +- x/incentive/types/genesis_test.go | 5 - x/incentive/types/incentive.go | 72 ++ x/incentive/types/incentive.pb.go | 652 +++++++++++++++++++ x/incentive/types/keys.go | 9 +- x/incentive/types/params.go | 32 +- x/incentive/types/params.pb.go | 177 ++++- x/incentive/types/query.pb.go | 490 +++++++++++++- x/incentive/types/query.pb.gw.go | 123 ++++ 26 files changed, 1883 insertions(+), 75 deletions(-) create mode 100644 proto/babylon/incentive/incentive.proto create mode 100644 testutil/datagen/incentive.go create mode 100644 x/incentive/keeper/btc_staking_gauge.go create mode 100644 x/incentive/keeper/btc_timestamping_gauge.go create mode 100644 x/incentive/keeper/grpc_query.go create mode 100644 x/incentive/keeper/grpc_query_test.go delete mode 100644 x/incentive/keeper/query.go create mode 100644 x/incentive/keeper/reward_gauge.go create mode 100644 x/incentive/types/incentive.go create mode 100644 x/incentive/types/incentive.pb.go diff --git a/proto/babylon/incentive/genesis.proto b/proto/babylon/incentive/genesis.proto index 7e5a33f3a..04f82d21a 100644 --- a/proto/babylon/incentive/genesis.proto +++ b/proto/babylon/incentive/genesis.proto @@ -8,5 +8,5 @@ option go_package = "github.com/babylonchain/babylon/x/incentive/types"; // GenesisState defines the incentive module's genesis state. message GenesisState { - Params params = 1 [(gogoproto.nullable) = false]; + Params params = 1 [(gogoproto.nullable) = false]; } diff --git a/proto/babylon/incentive/incentive.proto b/proto/babylon/incentive/incentive.proto new file mode 100644 index 000000000..03c9980f1 --- /dev/null +++ b/proto/babylon/incentive/incentive.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package babylon.incentive; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/babylonchain/babylon/x/incentive/types"; + +// Gauge is an object that stores rewards to be distributed +// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto +message Gauge { + // coins are coins that have been in the gauge + // Can have multiple coin denoms + repeated cosmos.base.v1beta1.Coin coins = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // distributed_coins are coins that have been distributed already + repeated cosmos.base.v1beta1.Coin distributed_coins = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// RewardGauge is an object that stores rewards distributed to a BTC staking/timestamping stakeholder +// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto +message RewardGauge { + // coins are coins that have been in the gauge + // Can have multiple coin denoms + repeated cosmos.base.v1beta1.Coin coins = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // withdrawn_coins are coins that have been withdrawn by the stakeholder already + repeated cosmos.base.v1beta1.Coin withdrawn_coins = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} \ No newline at end of file diff --git a/proto/babylon/incentive/params.proto b/proto/babylon/incentive/params.proto index c7e8f30ad..99e619e77 100644 --- a/proto/babylon/incentive/params.proto +++ b/proto/babylon/incentive/params.proto @@ -2,11 +2,35 @@ syntax = "proto3"; package babylon.incentive; import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; option go_package = "github.com/babylonchain/babylon/x/incentive/types"; -// Params defines the parameters for the module. +// Params defines the parameters for the module, including portions of rewards +// distributed to each type of stakeholder. Note that sum of the portions should +// be strictly less than 1 so that the rest will go to Tendermint validators/delegations +// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto message Params { - option (gogoproto.goproto_stringer) = false; - + option (gogoproto.goproto_stringer) = false; + + // submitter_portion is the portion of rewards that goes to submitter + string submitter_portion = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // reporter_portion is the portion of rewards that goes to reporter + string reporter_portion = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations + // NOTE: the portion of each BTC validator/delegation is calculated by using its voting + // power and BTC validator's commission + string btc_staking_portion = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; } diff --git a/proto/babylon/incentive/query.proto b/proto/babylon/incentive/query.proto index def0375a4..46ba6e19d 100644 --- a/proto/babylon/incentive/query.proto +++ b/proto/babylon/incentive/query.proto @@ -4,15 +4,20 @@ package babylon.incentive; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "babylon/incentive/params.proto"; +import "babylon/incentive/incentive.proto"; option go_package = "github.com/babylonchain/babylon/x/incentive/types"; // Query defines the gRPC querier service. service Query { - // Parameters queries the parameters of the module. - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/babylonchain/babylon/incentive/params"; - } + // Parameters queries the parameters of the module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/params"; + } + // RewardGauge queries the reward gauge of a given stakeholder in a given type + rpc RewardGauge(QueryRewardGaugeRequest) returns (QueryRewardGaugeResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/{type}/address/{address}/reward_gauge"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -20,6 +25,21 @@ message QueryParamsRequest {} // QueryParamsResponse is response type for the Query/Params RPC method. message QueryParamsResponse { - // params holds all the parameters of this module. - Params params = 1 [(gogoproto.nullable) = false]; + // params holds all the parameters of this module. + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryRewardGaugeRequest is request type for the Query/RewardGauge RPC method. +message QueryRewardGaugeRequest { + // type is the type of the stakeholder, can be one of + // {submitter, reporter, btc_validator, btc_delegation} + string type = 1; + // address is the address of the stakeholder in bech32 string + string address = 2; +} + +// QueryParamsResponse is response type for the Query/RewardGauge RPC method. +message QueryRewardGaugeResponse { + // reward_gauge is the reward gauge holding all rewards for the stakeholder + RewardGauge reward_gauge = 1; } \ No newline at end of file diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go new file mode 100644 index 000000000..c596ef123 --- /dev/null +++ b/testutil/datagen/incentive.go @@ -0,0 +1,46 @@ +package datagen + +import ( + "math/rand" + + itypes "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + denomLen = 5 +) + +func GenRandomDenom(r *rand.Rand) string { + var result string + // Generate the random string + for i := 0; i < denomLen; i++ { + // Generate a random index within the range of the character set + index := r.Intn(len(characters)) + // Add the randomly selected character to the result + result += string(characters[index]) + } + return result +} + +func GenRandomStakeholderType(r *rand.Rand) itypes.StakeholderType { + stBytes := []byte{byte(RandomInt(r, 4))} + st, err := itypes.NewStakeHolderType(stBytes) + if err != nil { + panic(err) // only programming error is possible + } + return st +} + +func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge { + numCoins := r.Int31n(10) + 10 + coins := sdk.NewCoins() + for i := int32(0); i < numCoins; i++ { + demon := GenRandomDenom(r) + amount := r.Int63n(10000) + coin := sdk.NewInt64Coin(demon, amount) + coins = coins.Add(coin) + } + return itypes.NewRewardGauge(coins...) +} diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go index 71c54c5cf..a3399a5e4 100644 --- a/testutil/keeper/incentive.go +++ b/testutil/keeper/incentive.go @@ -44,7 +44,9 @@ func IncentiveKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) // Initialize params - k.SetParams(ctx, types.DefaultParams()) + if err := k.SetParams(ctx, types.DefaultParams()); err != nil { + panic(err) + } return &k, ctx } diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go index 64b03328d..91a195219 100644 --- a/x/incentive/genesis.go +++ b/x/incentive/genesis.go @@ -8,7 +8,9 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { - k.SetParams(ctx, genState.Params) + if err := k.SetParams(ctx, genState.Params); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go new file mode 100644 index 000000000..c5d6f7fea --- /dev/null +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -0,0 +1,35 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) SetBTCStakingGauge(ctx sdk.Context, height uint64, gauge *types.Gauge) { + store := k.btcStakingGaugeStore(ctx) + gaugeBytes := k.cdc.MustMarshal(gauge) + store.Set(sdk.Uint64ToBigEndian(height), gaugeBytes) +} + +func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) (*types.Gauge, error) { + store := k.btcStakingGaugeStore(ctx) + gaugeBytes := store.Get(sdk.Uint64ToBigEndian(height)) + if len(gaugeBytes) == 0 { + return nil, types.ErrBTCStakingGaugeNotFound + } + + var gauge types.Gauge + k.cdc.MustUnmarshal(gaugeBytes, &gauge) + return &gauge, nil +} + +// btcStakingGaugeStore returns the KVStore of the gauge of total reward for +// BTC staking at each height +// prefix: BTCStakingGaugeKey +// key: gauge height +// value: gauge of rewards for BTC staking at this height +func (k Keeper) btcStakingGaugeStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.BTCStakingGaugeKey) +} diff --git a/x/incentive/keeper/btc_timestamping_gauge.go b/x/incentive/keeper/btc_timestamping_gauge.go new file mode 100644 index 000000000..9d5462d1f --- /dev/null +++ b/x/incentive/keeper/btc_timestamping_gauge.go @@ -0,0 +1,35 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) SetBTCTimestampingGauge(ctx sdk.Context, epoch uint64, gauge *types.Gauge) { + store := k.btcTimestampingGaugeStore(ctx) + gaugeBytes := k.cdc.MustMarshal(gauge) + store.Set(sdk.Uint64ToBigEndian(epoch), gaugeBytes) +} + +func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) (*types.Gauge, error) { + store := k.btcTimestampingGaugeStore(ctx) + gaugeBytes := store.Get(sdk.Uint64ToBigEndian(epoch)) + if len(gaugeBytes) == 0 { + return nil, types.ErrBTCTimestampingGaugeNotFound + } + + var gauge types.Gauge + k.cdc.MustUnmarshal(gaugeBytes, &gauge) + return &gauge, nil +} + +// btcTimestampingGaugeStore returns the KVStore of the gauge of total reward for +// BTC timestamping at each epoch +// prefix: BTCTimestampingGaugeKey +// key: epoch number +// value: gauge of rewards for BTC timestamping at this epoch +func (k Keeper) btcTimestampingGaugeStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.BTCTimestampingGaugeKey) +} diff --git a/x/incentive/keeper/grpc_query.go b/x/incentive/keeper/grpc_query.go new file mode 100644 index 000000000..0a4473887 --- /dev/null +++ b/x/incentive/keeper/grpc_query.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "context" + + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ types.QueryServer = Keeper{} + +func (k Keeper) RewardGauge(goCtx context.Context, req *types.QueryRewardGaugeRequest) (*types.QueryRewardGaugeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + // try to cast types for fields in the request + sType, err := types.NewStakeHolderTypeFromString(req.Type) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + address, err := sdk.AccAddressFromBech32(req.Address) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // find reward gauge + rg, err := k.GetRewardGauge(ctx, sType, address) + if err != nil { + return nil, err + } + + return &types.QueryRewardGaugeResponse{RewardGauge: rg}, nil +} diff --git a/x/incentive/keeper/grpc_query_test.go b/x/incentive/keeper/grpc_query_test.go new file mode 100644 index 000000000..70598ee02 --- /dev/null +++ b/x/incentive/keeper/grpc_query_test.go @@ -0,0 +1,49 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func FuzzRewardGaugeQuery(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + keeper, ctx := testkeeper.IncentiveKeeper(t) + wctx := sdk.WrapSDKContext(ctx) + + // generate a list of random RewardGauges and insert them to KVStore + rgList := []*types.RewardGauge{} + sTypeList := []types.StakeholderType{} + sAddrList := []sdk.AccAddress{} + numRgs := datagen.RandomInt(r, 100) + for i := uint64(0); i < numRgs; i++ { + sType := datagen.GenRandomStakeholderType(r) + sTypeList = append(sTypeList, sType) + sAddr := datagen.GenRandomAccount().GetAddress() + sAddrList = append(sAddrList, sAddr) + rg := datagen.GenRandomRewardGauge(r) + rgList = append(rgList, rg) + + keeper.SetRewardGauge(ctx, sType, sAddr, rg) + } + + // query existence and assert consistency + for i := range rgList { + req := &types.QueryRewardGaugeRequest{ + Type: sTypeList[i].String(), + Address: sAddrList[i].String(), + } + resp, err := keeper.RewardGauge(wctx, req) + require.NoError(t, err) + require.True(t, resp.RewardGauge.Coins.IsEqual(rgList[i].Coins)) + } + }) +} diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 084c1a8a3..67aaceed9 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -7,17 +7,15 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/babylonchain/babylon/x/incentive/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - paramstore paramtypes.Subspace + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + memKey storetypes.StoreKey bankKeeper types.BankKeeper accountKeeper types.AccountKeeper diff --git a/x/incentive/keeper/params.go b/x/incentive/keeper/params.go index cfde60970..3ea5346a3 100644 --- a/x/incentive/keeper/params.go +++ b/x/incentive/keeper/params.go @@ -5,12 +5,24 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) types.Params { - return types.NewParams() +// SetParams sets the x/incentive module parameters. +func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { + if err := p.Validate(); err != nil { + return err + } + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&p) + store.Set(types.ParamsKey, bz) + return nil } -// SetParams set the params -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) +// GetParams returns the current x/incentive module parameters. +func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.ParamsKey) + if bz == nil { + return p + } + k.cdc.MustUnmarshal(bz, &p) + return p } diff --git a/x/incentive/keeper/params_test.go b/x/incentive/keeper/params_test.go index 18f4f273f..f31fd88ab 100644 --- a/x/incentive/keeper/params_test.go +++ b/x/incentive/keeper/params_test.go @@ -12,7 +12,8 @@ func TestGetParams(t *testing.T) { k, ctx := testkeeper.IncentiveKeeper(t) params := types.DefaultParams() - k.SetParams(ctx, params) + err := k.SetParams(ctx, params) + require.NoError(t, err) require.EqualValues(t, params, k.GetParams(ctx)) } diff --git a/x/incentive/keeper/query.go b/x/incentive/keeper/query.go deleted file mode 100644 index 07a2c5eb4..000000000 --- a/x/incentive/keeper/query.go +++ /dev/null @@ -1,7 +0,0 @@ -package keeper - -import ( - "github.com/babylonchain/babylon/x/incentive/types" -) - -var _ types.QueryServer = Keeper{} diff --git a/x/incentive/keeper/query_params_test.go b/x/incentive/keeper/query_params_test.go index 0c7561f00..100ee9a7b 100644 --- a/x/incentive/keeper/query_params_test.go +++ b/x/incentive/keeper/query_params_test.go @@ -13,7 +13,8 @@ func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.IncentiveKeeper(t) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() - keeper.SetParams(ctx, params) + err := keeper.SetParams(ctx, params) + require.NoError(t, err) response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) require.NoError(t, err) diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go new file mode 100644 index 000000000..49c8301cf --- /dev/null +++ b/x/incentive/keeper/reward_gauge.go @@ -0,0 +1,36 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) SetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { + store := k.rewardGaugeStore(ctx, sType) + rgBytes := k.cdc.MustMarshal(rg) + store.Set(addr, rgBytes) +} + +func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (*types.RewardGauge, error) { + store := k.rewardGaugeStore(ctx, sType) + rgBytes := store.Get(addr) + if len(rgBytes) == 0 { + return nil, types.ErrRewardGaugeNotFound + } + + var rg types.RewardGauge + k.cdc.MustUnmarshal(rgBytes, &rg) + return &rg, nil +} + +// rewardGaugeStore returns the KVStore of the reward gauge of a stakeholder +// of a given type {submitter, reporter, BTC validator, BTC delegation} +// prefix: RewardGaugeKey +// key: (stakeholder type || stakeholder address) +// value: reward gauge +func (k Keeper) rewardGaugeStore(ctx sdk.Context, sType types.StakeholderType) prefix.Store { + store := ctx.KVStore(k.storeKey) + rgStore := prefix.NewStore(store, types.RewardGaugeKey) + return prefix.NewStore(rgStore, sType.Bytes()) +} diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go index 108698076..2de00b49d 100644 --- a/x/incentive/types/errors.go +++ b/x/incentive/types/errors.go @@ -8,5 +8,7 @@ import ( // x/incentive module sentinel errors var ( - ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") + ErrBTCStakingGaugeNotFound = errorsmod.Register(ModuleName, 1100, "BTC staking gauge not found") + ErrBTCTimestampingGaugeNotFound = errorsmod.Register(ModuleName, 1101, "BTC timestamping gauge not found") + ErrRewardGaugeNotFound = errorsmod.Register(ModuleName, 1102, "reward gauge not found") ) diff --git a/x/incentive/types/genesis_test.go b/x/incentive/types/genesis_test.go index 877e58a57..230e2c670 100644 --- a/x/incentive/types/genesis_test.go +++ b/x/incentive/types/genesis_test.go @@ -18,11 +18,6 @@ func TestGenesisState_Validate(t *testing.T) { genState: types.DefaultGenesis(), valid: true, }, - { - desc: "valid genesis state", - genState: &types.GenesisState{}, - valid: true, - }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go new file mode 100644 index 000000000..9221c553d --- /dev/null +++ b/x/incentive/types/incentive.go @@ -0,0 +1,72 @@ +package types + +import ( + fmt "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func NewRewardGauge(coins ...sdk.Coin) *RewardGauge { + return &RewardGauge{ + Coins: sdk.NewCoins(coins...), + WithdrawnCoins: sdk.NewCoins(coins...), + } +} + +// enum for stakeholder type, used as key prefix in KVStore +type StakeholderType byte + +const ( + SubmitterType StakeholderType = iota + ReporterType + BTCValidatorType + BTCDelegationType +) + +func NewStakeHolderType(stBytes []byte) (StakeholderType, error) { + if len(stBytes) != 1 { + return SubmitterType, fmt.Errorf("invalid format for stBytes") + } + if stBytes[0] == byte(SubmitterType) { + return SubmitterType, nil + } else if stBytes[0] == byte(ReporterType) { + return ReporterType, nil + } else if stBytes[0] == byte(BTCValidatorType) { + return BTCValidatorType, nil + } else if stBytes[0] == byte(BTCDelegationType) { + return BTCDelegationType, nil + } else { + return SubmitterType, fmt.Errorf("invalid stBytes") + } +} + +func NewStakeHolderTypeFromString(stStr string) (StakeholderType, error) { + if stStr == "submitter" { + return SubmitterType, nil + } else if stStr == "reporter" { + return ReporterType, nil + } else if stStr == "btc_validator" { + return BTCValidatorType, nil + } else if stStr == "btc_delegation" { + return BTCDelegationType, nil + } else { + return SubmitterType, fmt.Errorf("invalid stStr") + } +} + +func (st *StakeholderType) Bytes() []byte { + return []byte{byte(*st)} +} + +func (st *StakeholderType) String() string { + if *st == SubmitterType { + return "submitter" + } else if *st == ReporterType { + return "reporter" + } else if *st == BTCValidatorType { + return "btc_validator" + } else if *st == BTCDelegationType { + return "btc_delegation" + } + panic("invalid stakeholder type") +} diff --git a/x/incentive/types/incentive.pb.go b/x/incentive/types/incentive.pb.go new file mode 100644 index 000000000..98ac831b5 --- /dev/null +++ b/x/incentive/types/incentive.pb.go @@ -0,0 +1,652 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/incentive/incentive.proto + +package types + +import ( + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Gauge is an object that stores rewards to be distributed +// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto +type Gauge struct { + // coins are coins that have been in the gauge + // Can have multiple coin denoms + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` + // distributed_coins are coins that have been distributed already + DistributedCoins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=distributed_coins,json=distributedCoins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"distributed_coins"` +} + +func (m *Gauge) Reset() { *m = Gauge{} } +func (m *Gauge) String() string { return proto.CompactTextString(m) } +func (*Gauge) ProtoMessage() {} +func (*Gauge) Descriptor() ([]byte, []int) { + return fileDescriptor_3954bc4942045a7a, []int{0} +} +func (m *Gauge) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Gauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Gauge.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Gauge) XXX_Merge(src proto.Message) { + xxx_messageInfo_Gauge.Merge(m, src) +} +func (m *Gauge) XXX_Size() int { + return m.Size() +} +func (m *Gauge) XXX_DiscardUnknown() { + xxx_messageInfo_Gauge.DiscardUnknown(m) +} + +var xxx_messageInfo_Gauge proto.InternalMessageInfo + +func (m *Gauge) GetCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.Coins + } + return nil +} + +func (m *Gauge) GetDistributedCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.DistributedCoins + } + return nil +} + +// RewardGauge is an object that stores rewards distributed to a BTC staking/timestamping stakeholder +// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto +type RewardGauge struct { + // coins are coins that have been in the gauge + // Can have multiple coin denoms + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` + // withdrawn_coins are coins that have been withdrawn by the stakeholder already + WithdrawnCoins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=withdrawn_coins,json=withdrawnCoins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"withdrawn_coins"` +} + +func (m *RewardGauge) Reset() { *m = RewardGauge{} } +func (m *RewardGauge) String() string { return proto.CompactTextString(m) } +func (*RewardGauge) ProtoMessage() {} +func (*RewardGauge) Descriptor() ([]byte, []int) { + return fileDescriptor_3954bc4942045a7a, []int{1} +} +func (m *RewardGauge) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RewardGauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RewardGauge.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RewardGauge) XXX_Merge(src proto.Message) { + xxx_messageInfo_RewardGauge.Merge(m, src) +} +func (m *RewardGauge) XXX_Size() int { + return m.Size() +} +func (m *RewardGauge) XXX_DiscardUnknown() { + xxx_messageInfo_RewardGauge.DiscardUnknown(m) +} + +var xxx_messageInfo_RewardGauge proto.InternalMessageInfo + +func (m *RewardGauge) GetCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.Coins + } + return nil +} + +func (m *RewardGauge) GetWithdrawnCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.WithdrawnCoins + } + return nil +} + +func init() { + proto.RegisterType((*Gauge)(nil), "babylon.incentive.Gauge") + proto.RegisterType((*RewardGauge)(nil), "babylon.incentive.RewardGauge") +} + +func init() { proto.RegisterFile("babylon/incentive/incentive.proto", fileDescriptor_3954bc4942045a7a) } + +var fileDescriptor_3954bc4942045a7a = []byte{ + // 292 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0x45, 0xb0, 0xf4, + 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x04, 0xa1, 0x4a, 0xf4, 0xe0, 0x12, 0x52, 0x22, 0xe9, 0xf9, + 0xe9, 0xf9, 0x60, 0x59, 0x7d, 0x10, 0x0b, 0xa2, 0x50, 0x4a, 0x2e, 0x39, 0xbf, 0x38, 0x37, 0xbf, + 0x58, 0x3f, 0x29, 0xb1, 0x38, 0x55, 0xbf, 0xcc, 0x30, 0x29, 0xb5, 0x24, 0xd1, 0x50, 0x3f, 0x39, + 0x3f, 0x33, 0x0f, 0x22, 0xaf, 0xf4, 0x84, 0x91, 0x8b, 0xd5, 0x3d, 0xb1, 0x34, 0x3d, 0x55, 0x28, + 0x91, 0x8b, 0x15, 0x24, 0x5e, 0x2c, 0xc1, 0xa8, 0xc0, 0xac, 0xc1, 0x6d, 0x24, 0xa9, 0x07, 0xd1, + 0xa9, 0x07, 0xd2, 0xa9, 0x07, 0xd5, 0xa9, 0xe7, 0x9c, 0x9f, 0x99, 0xe7, 0x64, 0x70, 0xe2, 0x9e, + 0x3c, 0xc3, 0xaa, 0xfb, 0xf2, 0x1a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, + 0xfa, 0x50, 0x6b, 0x20, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0x31, 0x58, + 0x43, 0x71, 0x10, 0xc4, 0x64, 0xa1, 0x0a, 0x2e, 0xc1, 0x94, 0xcc, 0xe2, 0x92, 0xa2, 0xcc, 0xa4, + 0xd2, 0x92, 0xd4, 0x94, 0x78, 0x88, 0x75, 0x4c, 0xd4, 0xb7, 0x4e, 0x00, 0xc9, 0x16, 0xb0, 0x88, + 0xd2, 0x33, 0x46, 0x2e, 0xee, 0xa0, 0xd4, 0xf2, 0xc4, 0xa2, 0x14, 0xba, 0x79, 0xb6, 0x84, 0x8b, + 0xbf, 0x3c, 0xb3, 0x24, 0x23, 0xa5, 0x28, 0xb1, 0x3c, 0x8f, 0x76, 0x5e, 0xe5, 0x83, 0xdb, 0x01, + 0xe6, 0x3b, 0x79, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, + 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x21, 0x92, + 0x99, 0xd0, 0xd4, 0x93, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x57, 0x20, 0xa5, 0x37, 0xb0, + 0x15, 0x49, 0x6c, 0xe0, 0x34, 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x84, 0x8c, 0xb9, + 0x91, 0x02, 0x00, 0x00, +} + +func (m *Gauge) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Gauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Gauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DistributedCoins) > 0 { + for iNdEx := len(m.DistributedCoins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DistributedCoins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *RewardGauge) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RewardGauge) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RewardGauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.WithdrawnCoins) > 0 { + for iNdEx := len(m.WithdrawnCoins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.WithdrawnCoins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintIncentive(dAtA []byte, offset int, v uint64) int { + offset -= sovIncentive(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Gauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + if len(m.DistributedCoins) > 0 { + for _, e := range m.DistributedCoins { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + return n +} + +func (m *RewardGauge) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + if len(m.WithdrawnCoins) > 0 { + for _, e := range m.WithdrawnCoins { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + return n +} + +func sovIncentive(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozIncentive(x uint64) (n int) { + return sovIncentive(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Gauge) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Gauge: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Gauge: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DistributedCoins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DistributedCoins = append(m.DistributedCoins, types.Coin{}) + if err := m.DistributedCoins[len(m.DistributedCoins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipIncentive(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIncentive + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RewardGauge) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RewardGauge: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RewardGauge: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WithdrawnCoins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WithdrawnCoins = append(m.WithdrawnCoins, types.Coin{}) + if err := m.WithdrawnCoins[len(m.WithdrawnCoins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipIncentive(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIncentive + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipIncentive(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthIncentive + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupIncentive + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthIncentive + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthIncentive = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowIncentive = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupIncentive = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go index 6972fd356..83bb1ea32 100644 --- a/x/incentive/types/keys.go +++ b/x/incentive/types/keys.go @@ -14,6 +14,9 @@ const ( MemStoreKey = "mem_incentive" ) -func KeyPrefix(p string) []byte { - return []byte(p) -} +var ( + ParamsKey = []byte{0x01} // key prefix for the parameters + BTCStakingGaugeKey = []byte{0x02} // key prefix for BTC staking gauge at each height + BTCTimestampingGaugeKey = []byte{0x03} // key prefix for BTC timestamping gauge at each height + RewardGaugeKey = []byte{0x04} // key prefix for reward gauge for a given stakeholder in a given type +) diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index 357196ad6..3d3822b67 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -1,6 +1,9 @@ package types import ( + "fmt" + + "cosmossdk.io/math" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -12,14 +15,13 @@ func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -// NewParams creates a new Params instance -func NewParams() Params { - return Params{} -} - // DefaultParams returns a default set of parameters func DefaultParams() Params { - return NewParams() + return Params{ + SubmitterPortion: math.LegacyNewDecWithPrec(5, 2), // 5 * 10^{-2} = 0.05 + ReporterPortion: math.LegacyNewDecWithPrec(5, 2), // 5 * 10^{-2} = 0.05 + BtcStakingPortion: math.LegacyNewDecWithPrec(2, 1), // 2 * 10^{-1} = 0.2 + } } // ParamSetPairs get the params.ParamSet @@ -29,6 +31,24 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { // Validate validates the set of params func (p Params) Validate() error { + if p.SubmitterPortion.IsNil() { + return fmt.Errorf("SubmitterPortion should not be nil") + } + if p.ReporterPortion.IsNil() { + return fmt.Errorf("ReporterPortion should not be nil") + } + if p.BtcStakingPortion.IsNil() { + return fmt.Errorf("BtcStakingPortion should not be nil") + } + + // sum of all portions should be less than 1 + sum := p.SubmitterPortion + sum = sum.Add(p.ReporterPortion) + sum = sum.Add(p.BtcStakingPortion) + if sum.GTE(math.LegacyOneDec()) { + return fmt.Errorf("sum of all portions should be less than 1") + } + return nil } diff --git a/x/incentive/types/params.pb.go b/x/incentive/types/params.pb.go index 6c1f5fbe0..cb5ba22b3 100644 --- a/x/incentive/types/params.pb.go +++ b/x/incentive/types/params.pb.go @@ -5,6 +5,8 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -23,8 +25,19 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// Params defines the parameters for the module. +// Params defines the parameters for the module, including portions of rewards +// distributed to each type of stakeholder. Note that sum of the portions should +// be strictly less than 1 so that the rest will go to Tendermint validators/delegations +// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto type Params struct { + // submitter_portion is the portion of rewards that goes to submitter + SubmitterPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=submitter_portion,json=submitterPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"submitter_portion"` + // reporter_portion is the portion of rewards that goes to reporter + ReporterPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=reporter_portion,json=reporterPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"reporter_portion"` + // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations + // NOTE: the portion of each BTC validator/delegation is calculated by using its voting + // power and BTC validator's commission + BtcStakingPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=btc_staking_portion,json=btcStakingPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"btc_staking_portion"` } func (m *Params) Reset() { *m = Params{} } @@ -66,17 +79,25 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/params.proto", fileDescriptor_c42276168f0adf4b) } var fileDescriptor_c42276168f0adf4b = []byte{ - // 153 bytes of a gzipped FileDescriptorProto + // 287 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0xca, 0xeb, 0xc1, - 0xe5, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x12, 0x1f, - 0x17, 0x5b, 0x00, 0x58, 0xa3, 0x15, 0xcb, 0x8c, 0x05, 0xf2, 0x0c, 0x4e, 0xde, 0x27, 0x1e, 0xc9, - 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, - 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x98, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, - 0x9c, 0x9f, 0xab, 0x0f, 0x35, 0x3d, 0x39, 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0xaf, 0x40, 0x72, - 0x4c, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x0e, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x94, 0x16, 0x53, 0xd2, 0xae, 0x00, 0x00, 0x00, + 0xe5, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x94, 0x64, + 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x48, 0x29, 0x5d, 0x60, 0xe2, + 0x62, 0x0b, 0x00, 0x1b, 0x2a, 0x94, 0xc9, 0x25, 0x58, 0x5c, 0x9a, 0x94, 0x9b, 0x59, 0x52, 0x92, + 0x5a, 0x14, 0x5f, 0x90, 0x5f, 0x54, 0x92, 0x99, 0x9f, 0x27, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe9, + 0x64, 0x73, 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0x6a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, + 0x7a, 0xc9, 0xf9, 0xb9, 0x50, 0x63, 0xa0, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, + 0x6a, 0xb1, 0x9e, 0x4b, 0x6a, 0xf2, 0xa5, 0x2d, 0xba, 0x5c, 0x50, 0x5b, 0x5c, 0x52, 0x93, 0x83, + 0x04, 0xe0, 0xc6, 0x06, 0x40, 0x4c, 0x15, 0x4a, 0xe7, 0x12, 0x28, 0x4a, 0x05, 0x59, 0x81, 0x64, + 0x13, 0x13, 0x15, 0x6c, 0xe2, 0x87, 0x99, 0x0a, 0xb3, 0x28, 0x87, 0x4b, 0x38, 0xa9, 0x24, 0x39, + 0xbe, 0xb8, 0x24, 0x31, 0x3b, 0x33, 0x2f, 0x1d, 0x6e, 0x17, 0x33, 0x15, 0xec, 0x12, 0x4c, 0x2a, + 0x49, 0x0e, 0x86, 0x98, 0x0b, 0xb5, 0xcd, 0x8a, 0x65, 0xc6, 0x02, 0x79, 0x06, 0x27, 0xef, 0x13, + 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, + 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x44, 0xb2, 0x08, 0x1a, 0x77, 0xc9, + 0x19, 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0x52, 0x54, 0x83, 0xed, 0x4d, 0x62, 0x03, 0x47, + 0x93, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x1b, 0x30, 0x4e, 0x0c, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -99,6 +120,36 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.BtcStakingPortion.Size() + i -= size + if _, err := m.BtcStakingPortion.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.ReporterPortion.Size() + i -= size + if _, err := m.ReporterPortion.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.SubmitterPortion.Size() + i -= size + if _, err := m.SubmitterPortion.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -119,6 +170,12 @@ func (m *Params) Size() (n int) { } var l int _ = l + l = m.SubmitterPortion.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.ReporterPortion.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.BtcStakingPortion.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -157,6 +214,108 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubmitterPortion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SubmitterPortion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReporterPortion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ReporterPortion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcStakingPortion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.BtcStakingPortion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/incentive/types/query.pb.go b/x/incentive/types/query.pb.go index 41eb14534..c3b85dc57 100644 --- a/x/incentive/types/query.pb.go +++ b/x/incentive/types/query.pb.go @@ -112,33 +112,145 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +// QueryRewardGaugeRequest is request type for the Query/RewardGauge RPC method. +type QueryRewardGaugeRequest struct { + // type is the type of the stakeholder, can be one of + // {submitter, reporter, btc_validator, btc_delegation} + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // address is the address of the stakeholder in bech32 string + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` +} + +func (m *QueryRewardGaugeRequest) Reset() { *m = QueryRewardGaugeRequest{} } +func (m *QueryRewardGaugeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRewardGaugeRequest) ProtoMessage() {} +func (*QueryRewardGaugeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{2} +} +func (m *QueryRewardGaugeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRewardGaugeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRewardGaugeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRewardGaugeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRewardGaugeRequest.Merge(m, src) +} +func (m *QueryRewardGaugeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryRewardGaugeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRewardGaugeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRewardGaugeRequest proto.InternalMessageInfo + +func (m *QueryRewardGaugeRequest) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *QueryRewardGaugeRequest) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +// QueryParamsResponse is response type for the Query/RewardGauge RPC method. +type QueryRewardGaugeResponse struct { + // reward_gauge is the reward gauge holding all rewards for the stakeholder + RewardGauge *RewardGauge `protobuf:"bytes,1,opt,name=reward_gauge,json=rewardGauge,proto3" json:"reward_gauge,omitempty"` +} + +func (m *QueryRewardGaugeResponse) Reset() { *m = QueryRewardGaugeResponse{} } +func (m *QueryRewardGaugeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryRewardGaugeResponse) ProtoMessage() {} +func (*QueryRewardGaugeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{3} +} +func (m *QueryRewardGaugeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRewardGaugeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRewardGaugeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRewardGaugeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRewardGaugeResponse.Merge(m, src) +} +func (m *QueryRewardGaugeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryRewardGaugeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRewardGaugeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRewardGaugeResponse proto.InternalMessageInfo + +func (m *QueryRewardGaugeResponse) GetRewardGauge() *RewardGauge { + if m != nil { + return m.RewardGauge + } + return nil +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.incentive.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.incentive.QueryParamsResponse") + proto.RegisterType((*QueryRewardGaugeRequest)(nil), "babylon.incentive.QueryRewardGaugeRequest") + proto.RegisterType((*QueryRewardGaugeResponse)(nil), "babylon.incentive.QueryRewardGaugeResponse") } func init() { proto.RegisterFile("babylon/incentive/query.proto", fileDescriptor_e1a59cc0c7c44135) } var fileDescriptor_e1a59cc0c7c44135 = []byte{ - // 274 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0x2c, - 0x4d, 0x2d, 0xaa, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0x4a, 0xeb, 0xc1, 0xa5, - 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x94, 0x4c, 0x7a, - 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x7e, 0x62, 0x41, 0xa6, 0x7e, 0x62, 0x5e, 0x5e, 0x7e, 0x49, 0x62, - 0x49, 0x66, 0x7e, 0x5e, 0x31, 0x54, 0x56, 0x0e, 0xd3, 0x96, 0x82, 0xc4, 0xa2, 0xc4, 0x5c, 0xa8, - 0xbc, 0x92, 0x08, 0x97, 0x50, 0x20, 0xc8, 0xd6, 0x00, 0xb0, 0x60, 0x50, 0x6a, 0x61, 0x69, 0x6a, - 0x71, 0x89, 0x92, 0x1f, 0x97, 0x30, 0x8a, 0x68, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x90, 0x39, - 0x17, 0x1b, 0x44, 0xb3, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xa4, 0x1e, 0x86, 0x23, 0xf5, - 0x20, 0x5a, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0x2a, 0x37, 0x9a, 0xc8, 0xc8, 0xc5, - 0x0a, 0x36, 0x50, 0xa8, 0x9d, 0x91, 0x8b, 0x0d, 0xa2, 0x44, 0x48, 0x15, 0x8b, 0x6e, 0x4c, 0xb7, - 0x48, 0xa9, 0x11, 0x52, 0x06, 0x71, 0x9c, 0x92, 0x5e, 0xd3, 0xe5, 0x27, 0x93, 0x99, 0x34, 0x84, - 0xd4, 0xf4, 0xa1, 0xea, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0x71, 0xf9, 0xdf, 0xc9, 0xfb, 0xc4, - 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, - 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x0c, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, - 0xf4, 0x92, 0xf3, 0x73, 0xb1, 0x9b, 0x55, 0x81, 0x64, 0x5a, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, - 0x1b, 0x38, 0x34, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x50, 0x5c, 0x8e, 0xd3, 0xd5, 0x01, - 0x00, 0x00, + // 404 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x4d, 0x4b, 0xeb, 0x40, + 0x14, 0x4d, 0x4a, 0x5f, 0x1f, 0x6f, 0xfa, 0x36, 0x6f, 0x5e, 0xc1, 0x18, 0x74, 0xd4, 0x80, 0xa5, + 0x28, 0x64, 0xb0, 0x2e, 0x5c, 0x5b, 0x90, 0x2e, 0x44, 0xd1, 0x2c, 0x05, 0x91, 0x49, 0x3b, 0xa4, + 0x81, 0x76, 0x26, 0xcd, 0x87, 0x5a, 0x4a, 0x37, 0x6e, 0xdc, 0x0a, 0xfe, 0x16, 0xff, 0x43, 0xdd, + 0x15, 0xdc, 0xb8, 0x12, 0x69, 0xfd, 0x21, 0x92, 0xc9, 0xb4, 0x46, 0xd2, 0x52, 0x77, 0x37, 0xf7, + 0x9c, 0x7b, 0xce, 0x99, 0x7b, 0x03, 0xd6, 0x6d, 0x62, 0xf7, 0xda, 0x9c, 0x61, 0x97, 0x35, 0x28, + 0x0b, 0xdd, 0x6b, 0x8a, 0xbb, 0x11, 0xf5, 0x7b, 0xa6, 0xe7, 0xf3, 0x90, 0xc3, 0x7f, 0x12, 0x36, + 0x67, 0xb0, 0x5e, 0x72, 0xb8, 0xc3, 0x05, 0x8a, 0xe3, 0x2a, 0x21, 0xea, 0x6b, 0x0e, 0xe7, 0x4e, + 0x9b, 0x62, 0xe2, 0xb9, 0x98, 0x30, 0xc6, 0x43, 0x12, 0xba, 0x9c, 0x05, 0x12, 0x45, 0x59, 0x17, + 0x8f, 0xf8, 0xa4, 0x33, 0xc5, 0xb7, 0xb2, 0xf8, 0xac, 0x4a, 0x28, 0x46, 0x09, 0xc0, 0xf3, 0x38, + 0xd8, 0x99, 0x98, 0xb3, 0x68, 0x37, 0xa2, 0x41, 0x68, 0x9c, 0x82, 0xff, 0xdf, 0xba, 0x81, 0xc7, + 0x59, 0x40, 0xe1, 0x01, 0x28, 0x24, 0xfa, 0x9a, 0xba, 0xa9, 0x56, 0x8a, 0xd5, 0x55, 0x33, 0xf3, + 0x0e, 0x33, 0x19, 0xa9, 0xe5, 0x87, 0x6f, 0x1b, 0x8a, 0x25, 0xe9, 0x46, 0x1d, 0xac, 0x08, 0x3d, + 0x8b, 0xde, 0x10, 0xbf, 0x59, 0x27, 0x91, 0x43, 0xa5, 0x15, 0x84, 0x20, 0x1f, 0xf6, 0x3c, 0x2a, + 0x14, 0xff, 0x58, 0xa2, 0x86, 0x1a, 0xf8, 0x4d, 0x9a, 0x4d, 0x9f, 0x06, 0x81, 0x96, 0x13, 0xed, + 0xe9, 0xa7, 0x71, 0x09, 0xb4, 0xac, 0x90, 0x4c, 0x77, 0x08, 0xfe, 0xfa, 0xa2, 0x7d, 0xe5, 0xc4, + 0x7d, 0x99, 0x11, 0xcd, 0xc9, 0x98, 0x9e, 0x2e, 0xfa, 0x5f, 0x1f, 0xd5, 0xe7, 0x1c, 0xf8, 0x25, + 0xf4, 0xe1, 0xbd, 0x0a, 0x0a, 0xc9, 0x53, 0xe0, 0xf6, 0x1c, 0x85, 0xec, 0xce, 0xf4, 0xf2, 0x32, + 0x5a, 0x12, 0xd3, 0x30, 0xef, 0x5e, 0x3e, 0x1e, 0x73, 0x15, 0x58, 0xc6, 0x92, 0xdf, 0x68, 0x11, + 0x97, 0xe1, 0x45, 0xa7, 0x84, 0x4f, 0x2a, 0x28, 0xa6, 0x02, 0xc3, 0x9d, 0x45, 0x3e, 0xd9, 0xe5, + 0xea, 0xbb, 0x3f, 0xe2, 0xca, 0x60, 0x27, 0x22, 0x58, 0x1d, 0x1e, 0x2d, 0x0b, 0xd6, 0x8f, 0x8f, + 0x34, 0xc0, 0xf2, 0x26, 0xb8, 0x2f, 0x8b, 0x01, 0x4e, 0xaf, 0xbf, 0x76, 0x3c, 0x1c, 0x23, 0x75, + 0x34, 0x46, 0xea, 0xfb, 0x18, 0xa9, 0x0f, 0x13, 0xa4, 0x8c, 0x26, 0x48, 0x79, 0x9d, 0x20, 0xe5, + 0x62, 0xcf, 0x71, 0xc3, 0x56, 0x64, 0x9b, 0x0d, 0xde, 0x99, 0x6f, 0x75, 0x9b, 0x32, 0x8b, 0xbd, + 0x02, 0xbb, 0x20, 0xfe, 0xd6, 0xfd, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x44, 0x9a, 0xf9, 0xe8, + 0x58, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -155,6 +267,8 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // RewardGauge queries the reward gauge of a given stakeholder in a given type + RewardGauge(ctx context.Context, in *QueryRewardGaugeRequest, opts ...grpc.CallOption) (*QueryRewardGaugeResponse, error) } type queryClient struct { @@ -174,10 +288,21 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } +func (c *queryClient) RewardGauge(ctx context.Context, in *QueryRewardGaugeRequest, opts ...grpc.CallOption) (*QueryRewardGaugeResponse, error) { + out := new(QueryRewardGaugeResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Query/RewardGauge", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // RewardGauge queries the reward gauge of a given stakeholder in a given type + RewardGauge(context.Context, *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -187,6 +312,9 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) RewardGauge(ctx context.Context, req *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RewardGauge not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -210,6 +338,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Query_RewardGauge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRewardGaugeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).RewardGauge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Query/RewardGauge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).RewardGauge(ctx, req.(*QueryRewardGaugeRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.incentive.Query", HandlerType: (*QueryServer)(nil), @@ -218,6 +364,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "RewardGauge", + Handler: _Query_RewardGauge_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/incentive/query.proto", @@ -279,6 +429,78 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *QueryRewardGaugeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRewardGaugeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRewardGaugeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0x12 + } + if len(m.Type) > 0 { + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryRewardGaugeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRewardGaugeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRewardGaugeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RewardGauge != nil { + { + size, err := m.RewardGauge.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -310,6 +532,36 @@ func (m *QueryParamsResponse) Size() (n int) { return n } +func (m *QueryRewardGaugeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Address) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryRewardGaugeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RewardGauge != nil { + l = m.RewardGauge.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -449,6 +701,206 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryRewardGaugeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRewardGaugeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRewardGaugeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRewardGaugeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRewardGaugeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRewardGaugeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RewardGauge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RewardGauge == nil { + m.RewardGauge = &RewardGauge{} + } + if err := m.RewardGauge.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/incentive/types/query.pb.gw.go b/x/incentive/types/query.pb.gw.go index 93b6b7002..842193594 100644 --- a/x/incentive/types/query.pb.gw.go +++ b/x/incentive/types/query.pb.gw.go @@ -51,6 +51,82 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +func request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRewardGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["type"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") + } + + protoReq.Type, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) + } + + val, ok = pathParams["address"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address") + } + + protoReq.Address, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err) + } + + msg, err := client.RewardGauge(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRewardGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["type"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") + } + + protoReq.Type, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) + } + + val, ok = pathParams["address"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address") + } + + protoReq.Address, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err) + } + + msg, err := server.RewardGauge(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -80,6 +156,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_RewardGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_RewardGauge_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RewardGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -141,13 +240,37 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_RewardGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_RewardGauge_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RewardGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylonchain", "babylon", "incentive", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_RewardGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylonchain", "babylon", "incentive", "type", "address", "reward_gauge"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_RewardGauge_0 = runtime.ForwardResponseMessage ) From c7b3ca6fc5a8cb0bb2b1a2d8332eb7ba336c3fa9 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 1 Sep 2023 09:57:37 +0200 Subject: [PATCH 069/202] Staker undelegation (#65) - Add handler to enable fast unbonding. --- btcstaking/staking.go | 78 +- proto/babylon/btcstaking/v1/btcstaking.proto | 48 +- proto/babylon/btcstaking/v1/tx.proto | 28 +- test/e2e/btc_staking_e2e_test.go | 80 +- .../configurer/chain/commands_btcstaking.go | 18 +- testutil/datagen/btcstaking.go | 55 +- x/btcstaking/client/cli/tx.go | 51 +- x/btcstaking/keeper/btc_delegations.go | 19 + x/btcstaking/keeper/grpc_query.go | 2 +- x/btcstaking/keeper/msg_server.go | 194 ++++- x/btcstaking/keeper/msg_server_test.go | 405 +++++++--- x/btcstaking/types/btc_slashing_tx.go | 6 +- x/btcstaking/types/btc_slashing_tx_test.go | 14 +- x/btcstaking/types/btcstaking.go | 55 +- x/btcstaking/types/btcstaking.pb.go | 728 +++++++++++++++--- x/btcstaking/types/btcstaking_test.go | 2 +- x/btcstaking/types/codec.go | 2 + x/btcstaking/types/errors.go | 2 + x/btcstaking/types/msg.go | 44 +- x/btcstaking/types/tx.pb.go | 632 +++++++++++++-- x/btcstaking/types/types.go | 19 + 21 files changed, 2102 insertions(+), 380 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 63c207011..3b147824d 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -105,8 +105,6 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (int64, erro return result, nil } -//End of copied methods - // StakingScriptData is a struct that holds data parsed from staking script type StakingScriptData struct { StakerKey *btcec.PublicKey @@ -589,33 +587,55 @@ func BuildSlashingTxFromStakingTxStrict( return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee) } -// CheckSlashingTx perform basic checks on slashing transaction: -// - slashing transaction is not nil -// - slashing transaction has exactly one input -// - slashing transaction is not replacable -// - slashing transaction has exactly one output -// - slashing transaction locktime is 0 -// - slashing transaction output is simple pay to address script paying to provided slashing address -func CheckSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error { +// Transfer transaction is a transaction which: +// - has exactly one input +// - has exactly one output +func IsTransferTx(tx *wire.MsgTx) error { + if tx == nil { + return fmt.Errorf("transfer transaction must have cannot be nil") + } - if slashingTx == nil { - return fmt.Errorf("provided slashing transaction must not be nil") + if len(tx.TxIn) != 1 { + return fmt.Errorf("transfer transaction must have exactly one input") } - if len(slashingTx.TxIn) != 1 { - return fmt.Errorf("slashing transaction must have exactly one input") + if len(tx.TxOut) != 1 { + return fmt.Errorf("transfer transaction must have exactly one output") } - if slashingTx.TxIn[0].Sequence != wire.MaxTxInSequenceNum { - return fmt.Errorf("slashing transaction must be not replacable") + return nil +} + +// Simple transfer transaction is a transaction which: +// - has exactly one input +// - has exactly one output +// - is not replacable +// - does not have any locktime +func IsSimpleTransfer(tx *wire.MsgTx) error { + if err := IsTransferTx(tx); err != nil { + return fmt.Errorf("invalid simple tansfer tx: %w", err) } - if len(slashingTx.TxOut) != 1 { - return fmt.Errorf("slashing transaction must have exactly one output") + if tx.TxIn[0].Sequence != wire.MaxTxInSequenceNum { + return fmt.Errorf("simple transfer tx must not be replacable") } - if slashingTx.LockTime != 0 { - return fmt.Errorf("slashing transaction locktime must be 0") + if tx.LockTime != 0 { + return fmt.Errorf("simple transfer tx must not have locktime") + } + return nil +} + +// IsSlashingTx perform basic checks on slashing transaction: +// - slashing transaction is not nil +// - slashing transaction has exactly one input +// - slashing transaction is not replacable +// - slashing transaction has exactly one output +// - slashing transaction locktime is 0 +// - slashing transaction output is simple pay to address script paying to provided slashing address +func IsSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error { + if err := IsSimpleTransfer(slashingTx); err != nil { + return fmt.Errorf("invalid slashing tx: %w", err) } pkScript, err := txscript.PayToAddrScript(slashingAddress) @@ -666,16 +686,16 @@ func GetIdxOutputCommitingToScript( return comittingOutputIdx, nil } -// CheckTransactions validates all relevant data of slashing and staking transaction: +// CheckTransactions validates all relevant data of slashing and funding transaction. // - slashing transaction is valid -// - staking transaction script is valid -// - staking transaction has output committing to the provided script -// - slashing transaction input is pointing to staking transaction staking output +// - funding transaction script is valid +// - funding transaction has output committing to the provided script +// - slashing transaction input is pointing to funding transaction output commiting to the script // - that min fee for slashing tx is preserved // In case of success, it returns data extracted from valid staking script and staking amount. func CheckTransactions( slashingTx *wire.MsgTx, - stakingTx *wire.MsgTx, + fundingTransaction *wire.MsgTx, slashingTxMinFee int64, slashingAddress btcutil.Address, script []byte, @@ -686,7 +706,7 @@ func CheckTransactions( } //1. Check slashing tx - if err := CheckSlashingTx(slashingTx, slashingAddress); err != nil { + if err := IsSlashingTx(slashingTx, slashingAddress); err != nil { return nil, err } @@ -698,14 +718,14 @@ func CheckTransactions( } //3. Check that staking transaction has output committing to the provided script - stakingOutputIdx, err := GetIdxOutputCommitingToScript(stakingTx, script, net) + stakingOutputIdx, err := GetIdxOutputCommitingToScript(fundingTransaction, script, net) if err != nil { return nil, err } //4. Check that slashing transaction input is pointing to staking transaction - stakingTxHash := stakingTx.TxHash() + stakingTxHash := fundingTransaction.TxHash() if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { return nil, fmt.Errorf("slashing transaction must spend staking output") } @@ -715,7 +735,7 @@ func CheckTransactions( return nil, fmt.Errorf("slashing transaction input must spend staking output") } - stakingOutput := stakingTx.TxOut[stakingOutputIdx] + stakingOutput := fundingTransaction.TxOut[stakingOutputIdx] //6. Check fees if slashingTx.TxOut[0].Value <= 0 || stakingOutput.Value <= 0 { diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 584f00126..62588c739 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -76,7 +76,7 @@ message BTCDelegation { // quantified in satoshi uint64 total_sat = 7; // staking_tx is the staking tx - StakingTx staking_tx = 8; + BabylonBTCTaprootTx staking_tx = 8; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. @@ -89,6 +89,41 @@ message BTCDelegation { // by the jury (i.e., SK corresponding to jury_pk in params) // It will be a part of the witness for the staking tx output. bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + + // if this object is present it menans that staker requested undelegation, and whole + // delegation is being undelegated. + // TODO: Consider whether it would be better to store it in separate store, and not + // directly in delegation object + BTCUndelegation btc_undelegation = 12; +} + +// BTCUndelegation signalizes that the delegation is being undelegated +message BTCUndelegation { + // unbonding_tx is the transaction which will transfer the funds from staking + // output to unbonding output. Unbonding output will usually have lower timelock + // than staking output. + BabylonBTCTaprootTx unbonding_tx = 1; + // slashing_tx is the slashing tx for unbodning transactions + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + bytes slashing_tx = 2 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + // delegator_slashing_sig is the signature on the slashing tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the unbodning tx output. + bytes delegator_slashing_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // jury_slashing_sig is the signature on the slashing tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon + bytes jury_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // jury_unbonding_sig is the signature on the unbonding tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon and after + // validator sig will be provided by validator + bytes jury_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // validator_unbonding_sig is the signature on the unbonding tx + // by the validator (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon + bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. @@ -107,12 +142,13 @@ message ProofOfPossession { bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// StakingTx is the tx for delegating BTC -message StakingTx { - // tx is the staking tx in bytes +// BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and +// transaction which commits to the provided script. +message BabylonBTCTaprootTx { + // tx is the transaction bytes bytes tx = 1; - // staking_script is the script for the staking tx's output - bytes staking_script = 2; + // script is the script recognized by Babylon + bytes script = 2; } // BTCDelegationStatus is the status of a delegation. diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 09e7412c4..8fc75517b 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -18,6 +18,8 @@ service Msg { rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); // CreateBTCDelegation creates a new BTC delegation rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); + // BtcUndelegate undelegates funds from exsitng btc delegation + rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); // AddJurySig handles a signature from jury rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse); // UpdateParams updates the btcstaking module parameters. @@ -53,14 +55,14 @@ message MsgCreateBTCDelegation { cosmos.crypto.secp256k1.PubKey babylon_pk = 2; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // staking_tx is the staking tx - StakingTx staking_tx = 4; + // staking_tx is the staking tx + BabylonBTCTaprootTx staking_tx = 4; // staking_tx_info is the tx info of the staking tx, including the Merkle proof babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 5; // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. bytes slashing_tx = 6 [ (gogoproto.customtype) = "BTCSlashingTx" ]; - // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from jury and validator in // order to be spendable. @@ -69,6 +71,23 @@ message MsgCreateBTCDelegation { // MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation message MsgCreateBTCDelegationResponse {} + +// MsgBTCUndelegate is the message undelegating existing and active delegation +message MsgBTCUndelegate { + string signer = 1; + // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends + // staking output and sends it to the unbonding output + BabylonBTCTaprootTx unbonding_tx = 2; + // slashing_tx is the slashing tx which slash unbonding contract + // Note that the tx itself does not contain signatures, which are off-chain. + bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} + +// MsgBtcUndelegateResponse is the response for MsgBtcUndelegate +message MsgBTCUndelegateResponse {} + // MsgAddJurySig is the message for handling a signature from jury message MsgAddJurySig { string signer = 1; @@ -78,7 +97,7 @@ message MsgAddJurySig { // del_pk is the Bitcoin secp256k1 PK of the BTC delegation // the PK follows encoding in BIP-340 spec bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // staking_tx_hash is the hash of the staking tx. + // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 4; // sig is the signature of the jury @@ -107,4 +126,3 @@ message MsgUpdateParams { // MsgUpdateParamsResponse is the response to the MsgUpdateParams message. message MsgUpdateParamsResponse {} - \ No newline at end of file diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index b56dfd888..b5a7c4fcc 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -8,10 +8,13 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" + "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/test/e2e/configurer" "github.com/babylonchain/babylon/test/e2e/initialization" @@ -36,6 +39,8 @@ var ( jurySK, _ = btcec.PrivKeyFromBytes( []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, ) + + stakingValue = int64(2 * 10e8) ) type BTCStakingTestSuite struct { @@ -95,7 +100,6 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.NoError(err) // generate staking tx and slashing tx stakingTimeBlocks := uint16(math.MaxUint16) - stakingValue := int64(2 * 10e8) stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( r, net, @@ -112,7 +116,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { // generate proper delegator sig delegatorSig, err := slashingTx.Sign( stakingMsgTx, - stakingTx.StakingScript, + stakingTx.Script, delBTCSK, net, ) @@ -145,7 +149,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { // check delegation delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHash()) s.NotNil(delegation) - expectedScript := hex.EncodeToString(stakingTx.StakingScript) + expectedScript := hex.EncodeToString(stakingTx.Script) s.Equal(expectedScript, delegation.StakingScript) } @@ -176,7 +180,7 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { */ jurySig, err := slashingTx.Sign( stakingMsgTx, - stakingTx.StakingScript, + stakingTx.Script, jurySK, net, ) @@ -271,3 +275,71 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NotEmpty(finalizedBlocks) s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) } + +// Test4SubmitStakerUnbonding is an end-to-end test for user unbodning +func (s *BTCStakingTestSuite) Test4SubmitStakerUnbonding() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(activeDelsSet, 1) + activeDels := activeDelsSet[0] + s.Len(activeDels.Dels, 1) + activeDel := activeDels.Dels[0] + s.NotNil(activeDel.JurySig) + + // params for juryPk and slashing address + params := nonValidatorNode.QueryBTCStakingParams() + + stakingTx := activeDel.StakingTx + stakingMsgTx, err := stakingTx.ToMsgTx() + s.NoError(err) + stakingTxHash := stakingTx.MustGetTxHash() + stakingTxChainHash, err := chainhash.NewHashFromStr(stakingTxHash) + s.NoError(err) + + stakingOutputIdx, err := btcstaking.GetIdxOutputCommitingToScript( + stakingMsgTx, activeDel.StakingTx.Script, net, + ) + s.NoError(err) + + fee := int64(1000) + unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + r, + net, + delBTCSK, + btcVal.BtcPk.MustToBTCPK(), + params.JuryPk.MustToBTCPK(), + wire.NewOutPoint(stakingTxChainHash, uint32(stakingOutputIdx)), + initialization.BabylonBtcFinalizationPeriod+1, + stakingValue-fee, + params.SlashingAddress, + ) + s.NoError(err) + + unbondingTxMsg, err := unbondingTx.ToMsgTx() + s.NoError(err) + + slashingTxSig, err := slashUnbondingTx.Sign( + unbondingTxMsg, + unbondingTx.Script, + delBTCSK, + net, + ) + s.NoError(err) + + // submit the message for creating BTC undelegation + nonValidatorNode.CreateBTCUndelegation(unbondingTx, slashUnbondingTx, slashingTxSig) + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + valDelegations := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(valDelegations, 1) + s.Len(valDelegations[0].Dels, 1) + delegation := valDelegations[0].Dels[0] + s.NotNil(delegation.BtcUndelegation) +} diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 8c66a94b5..93d59534e 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -33,7 +33,7 @@ func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn. n.LogActionF("successfully created BTC validator") } -func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstypes.ProofOfPossession, stakingTx *bstypes.StakingTx, stakingTxInfo *btcctypes.TransactionInfo, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { +func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstypes.ProofOfPossession, stakingTx *bstypes.BabylonBTCTaprootTx, stakingTxInfo *btcctypes.TransactionInfo, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { n.LogActionF("creating BTC delegation") // get babylon PK hex @@ -120,3 +120,19 @@ func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint require.NoError(n.t, err) n.LogActionF("successfully added finality signature") } + +func (n *NodeConfig) CreateBTCUndelegation(unbondingTx *bstypes.BabylonBTCTaprootTx, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { + n.LogActionF("creating BTC undelegation") + // get staking tx hex + unbondingTxHex, err := unbondingTx.ToHexStr() + require.NoError(n.t, err) + // get slashing tx hex + slashingTxHex := slashingTx.ToHexStr() + // get delegator sig hex + delegatorSigHex := delegatorSig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-undelegation", unbondingTxHex, slashingTxHex, delegatorSigHex, "--from=val"} + _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully created BTC delegation") +} diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index a812e850a..19ca1a131 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -92,11 +92,11 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc if err != nil { return nil, err } - jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, jurySK, net) + jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, jurySK, net) if err != nil { return nil, err } - delegatorSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, delSK, net) + delegatorSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, delSK, net) if err != nil { return nil, err } @@ -126,7 +126,8 @@ func GenBTCStakingSlashingTxWithOutPoint( stakingTimeBlocks uint16, stakingValue int64, slashingAddress string, -) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { + withChange bool, +) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerSK.PubKey(), validatorPK, @@ -143,26 +144,30 @@ func GenBTCStakingSlashingTxWithOutPoint( // add the given tx input txIn := wire.NewTxIn(outPoint, nil, nil) tx.AddTxIn(txIn) - // 2 outputs for changes and staking output - changeAddrScript, err := GenRandomPubKeyHashScript(r, btcNet) - if err != nil { - return nil, nil, err - } - if txscript.GetScriptClass(changeAddrScript) == txscript.NonStandardTy { - return nil, nil, fmt.Errorf("change address script is non-standard") - } - tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change + tx.AddTxOut(stakingOutput) + if withChange { + // 2 outputs for changes and staking output + changeAddrScript, err := GenRandomPubKeyHashScript(r, btcNet) + if err != nil { + return nil, nil, err + } + if txscript.GetScriptClass(changeAddrScript) == txscript.NonStandardTy { + return nil, nil, fmt.Errorf("change address script is non-standard") + } + tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change + } + // construct staking tx var buf bytes.Buffer err = tx.Serialize(&buf) if err != nil { return nil, nil, err } - stakingTx := &bstypes.StakingTx{ - Tx: buf.Bytes(), - StakingScript: stakingScript, + stakingTx := &bstypes.BabylonBTCTaprootTx{ + Tx: buf.Bytes(), + Script: stakingScript, } // construct slashing tx @@ -170,7 +175,7 @@ func GenBTCStakingSlashingTxWithOutPoint( if err != nil { return nil, nil, err } - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 1, slashingAddrBtc, 2000, stakingScript, btcNet) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 0, slashingAddrBtc, 2000, stakingScript, btcNet) if err != nil { return nil, nil, err } @@ -191,9 +196,23 @@ func GenBTCStakingSlashingTx( stakingTimeBlocks uint16, stakingValue int64, slashingAddress string, -) (*bstypes.StakingTx, *bstypes.BTCSlashingTx, error) { +) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) outPoint := &spend.prevOut - return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress) + return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress, true) +} + +func GenBTCUnbondingSlashingTx( + r *rand.Rand, + btcNet *chaincfg.Params, + stakerSK *btcec.PrivateKey, + validatorPK *btcec.PublicKey, + juryPK *btcec.PublicKey, + stakingTransactionOutpoint *wire.OutPoint, + stakingTimeBlocks uint16, + stakingValue int64, + slashingAddress string, +) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { + return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, stakingTransactionOutpoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress, false) } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 48cd7fbd1..fb4244ddf 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -40,6 +40,7 @@ func GetTxCmd() *cobra.Command { NewCreateBTCValidatorCmd(), NewCreateBTCDelegationCmd(), NewAddJurySigCmd(), + NewCreateBTCUndelegationCmd(), ) return cmd @@ -160,7 +161,7 @@ func NewCreateBTCDelegationCmd() *cobra.Command { } // get staking tx - stakingTx, err := types.NewStakingTxFromHex(args[2]) + stakingTx, err := types.NewBabylonTaprootTxFromHex(args[2]) if err != nil { return err } @@ -253,3 +254,51 @@ func NewAddJurySigCmd() *cobra.Command { return cmd } + +func NewCreateBTCUndelegationCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-btc-undelegation [unbonding_tx] [slashing_tx] [delegator_sig]", + Args: cobra.ExactArgs(3), + Short: "Create a BTC undelegation", + Long: strings.TrimSpace( + `Create a BTC undelegation.`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get staking tx + unbondingTx, err := types.NewBabylonTaprootTxFromHex(args[0]) + if err != nil { + return err + } + + // get slashing tx + slashingTx, err := types.NewBTCSlashingTxFromHex(args[1]) + if err != nil { + return err + } + + // get delegator sig + delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[2]) + if err != nil { + return err + } + + msg := types.MsgBTCUndelegate{ + Signer: clientCtx.FromAddress.String(), + UnbondingTx: unbondingTx, + SlashingTx: slashingTx, + DelegatorSlashingSig: delegatorSig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index bb7c01514..0f7579365 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -43,6 +43,25 @@ func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340P return nil } +func (k Keeper) AddUndelegationToBTCDelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + ud *types.BTCUndelegation, +) error { + btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) + if err != nil { + return err + } + + if err := btcDels.AddUndelegation(stakingTxHash, ud); err != nil { + return types.ErrInvalidDelegationState.Wrapf(err.Error()) + } + k.setBTCDelegations(ctx, valBTCPK, delBTCPK, btcDels) + return nil +} + // setBTCDelegations sets the given BTC delegation to KVStore func (k Keeper) setBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, btcDels *types.BTCDelegatorDelegations) { delBTCPKBytes := delBTCPK.MustMarshal() diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index c650439f0..238742448 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -274,7 +274,7 @@ func (k Keeper) delegationView( EndHeight: delegation.EndHeight, TotalSat: delegation.TotalSat, StakingTx: hex.EncodeToString(delegation.StakingTx.Tx), - StakingScript: hex.EncodeToString(delegation.StakingTx.StakingScript), + StakingScript: hex.EncodeToString(delegation.StakingTx.Script), } } return nil diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 3cb9087f3..c26a21b2d 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -10,6 +10,8 @@ import ( bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -26,6 +28,34 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} +func mustGetSlashingAddress(params *types.Params, btcParams *chaincfg.Params) btcutil.Address { + slashingAddr, err := btcutil.DecodeAddress(params.SlashingAddress, btcParams) + if err != nil { + panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) + } + return slashingAddr +} + +func mustGetStakingTxInfo(del *types.BTCDelegation, params *chaincfg.Params) (*wire.MsgTx, uint32) { + stakingTxMsg, err := del.StakingTx.ToMsgTx() + + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed deserialize staking tx from db")) + } + + stakingOutputIndex, err := btcstaking.GetIdxOutputCommitingToScript( + stakingTxMsg, + del.StakingTx.Script, + params, + ) + + if err != nil { + panic(fmt.Errorf("script not matching staking tx in database")) + } + return stakingTxMsg, uint32(stakingOutputIndex) +} + // UpdateParams updates the params func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if ms.authority != req.Authority { @@ -82,7 +112,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout // extract staking script from staking tx - stakingOutputInfo, err := req.StakingTx.GetStakingOutputInfo(ms.btcNet) + stakingOutputInfo, err := req.StakingTx.GetBabylonOutputInfo(ms.btcNet) if err != nil { return nil, err } @@ -156,7 +186,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre stakingMsgTx, params.MinSlashingTxFeeSat, slashingAddr, - req.StakingTx.StakingScript, + req.StakingTx.Script, ms.btcNet, ); err != nil { return nil, types.ErrInvalidStakingTx.Wrap(err.Error()) @@ -166,7 +196,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre err = req.SlashingTx.VerifySignature( stakingOutputInfo.StakingPkScript, int64(stakingOutputInfo.StakingAmount), - req.StakingTx.StakingScript, + req.StakingTx.Script, stakingOutputInfo.StakingScriptData.StakerKey, req.DelegatorSig, ) @@ -179,17 +209,18 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // have voting power only when 1) its corresponding staking tx is k-deep, // and 2) it receives a jury signature newBTCDel := &types.BTCDelegation{ - BabylonPk: req.BabylonPk, - BtcPk: delBTCPK, - Pop: req.Pop, - ValBtcPk: valBTCPK, - StartHeight: startHeight, - EndHeight: endHeight, - TotalSat: uint64(stakingOutputInfo.StakingAmount), - StakingTx: req.StakingTx, - SlashingTx: req.SlashingTx, - DelegatorSig: req.DelegatorSig, - JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury + BabylonPk: req.BabylonPk, + BtcPk: delBTCPK, + Pop: req.Pop, + ValBtcPk: valBTCPK, + StartHeight: startHeight, + EndHeight: endHeight, + TotalSat: uint64(stakingOutputInfo.StakingAmount), + StakingTx: req.StakingTx, + SlashingTx: req.SlashingTx, + DelegatorSig: req.DelegatorSig, + JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury + BtcUndelegation: nil, } if err := ms.SetBTCDelegation(ctx, newBTCDel); err != nil { panic("failed to set BTC delegation that has passed verification") @@ -203,6 +234,137 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return &types.MsgCreateBTCDelegationResponse{}, nil } +// BtcUndelegate undelegates funds from existing delegation +func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := ms.GetParams(ctx) + slashingAddress := mustGetSlashingAddress(¶ms, ms.btcNet) + wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + // 1. deserialize provided transactions + slashingMsgTx, err := req.SlashingTx.ToMsgTx() + if err != nil { + return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + } + + unbondingMsgTx, err := req.UnbondingTx.ToMsgTx() + if err != nil { + return nil, types.ErrInvalidUnbodningTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + } + + // 2. basic stateless checks for unbodning tx + if err := btcstaking.IsSimpleTransfer(unbondingMsgTx); err != nil { + return nil, types.ErrInvalidUnbodningTx.Wrapf("invalid unbonding tx: %v", err) + } + + // retrieve staking tx hash from unbonding tx, at this point we know that unbonding tx is a simple transfer with + // one input and one output + unbondingTxFundingOutpoint := unbondingMsgTx.TxIn[0].PreviousOutPoint + stakingTxHash := unbondingTxFundingOutpoint.Hash.String() + + // 3. Check that slashing tx and unbonding tx are valid and consistent + unbondingOutputInfo, err := btcstaking.CheckTransactions( + slashingMsgTx, + unbondingMsgTx, + params.MinSlashingTxFeeSat, + slashingAddress, + req.UnbondingTx.Script, + ms.btcNet, + ) + + if err != nil { + return nil, types.ErrInvalidUnbodningTx.Wrapf("invalid unbonding tx: %v", err) + } + + err = req.SlashingTx.VerifySignature( + unbondingOutputInfo.StakingPkScript, + int64(unbondingOutputInfo.StakingAmount), + req.UnbondingTx.Script, + unbondingOutputInfo.StakingScriptData.StakerKey, + req.DelegatorSlashingSig, + ) + if err != nil { + return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) + } + + // 4. Check unbonding time (staking time from unbonding tx) is larger than finalization time + // Unbodning time must be strictly larger that babylon finalization time. + if uint64(unbondingOutputInfo.StakingScriptData.StakingTime) <= wValue { + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time must be larger than finalization time") + } + + // 5. Check Jury Key from script is consistent with params + publicKeyInfos := types.KeyDataFromScript(unbondingOutputInfo.StakingScriptData) + if !publicKeyInfos.JuryKey.Equals(params.JuryPk) { + return nil, types.ErrInvalidJuryPK.Wrapf( + "expected: %s; actual: %s", + hex.EncodeToString(*params.JuryPk), + hex.EncodeToString(*publicKeyInfos.JuryKey), + ) + } + + // 6. Check delegation exists for the given validator and delegator and given staking tx hash + // as all keys are taken from script, it effectively check that values in delegation staking script + // matches the values in the unbonding tx staking script + del, err := ms.GetBTCDelegation(ctx, publicKeyInfos.ValidatorKey, publicKeyInfos.StakerKey, stakingTxHash) + + if err != nil { + return nil, err + } + + // 7. Check delegation state. Only active delegations can be unbonded. + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + status := del.GetStatus(btcTip.Height, wValue) + + if status != types.BTCDelegationStatus_ACTIVE { + return nil, types.ErrInvalidDelegationState + } + + // 8. Check unbonding tx against staking tx. + // - that input points to the staking tx, staking output + // - fee is larger than 0 + stakingTxMsg, stakingOutputIndex := mustGetStakingTxInfo(del, ms.btcNet) + + // we only check index of the staking output, as we already retrieved delegation + // by stakingTxHash computed from unbonding tx input + if unbondingTxFundingOutpoint.Index != uint32(stakingOutputIndex) { + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx does not point to staking tx staking output") + } + + if unbondingMsgTx.TxOut[0].Value >= stakingTxMsg.TxOut[stakingOutputIndex].Value { + // Note: we do not enfore any minimum fee for unbonding tx, we only require that it is larger than 0 + // Given that unbonding tx must not be replacable and we do not allow sending it second time, it places + // burden on staker to choose right fee. + // Unbonding tx should not be replaceable at babylon level (and by extension on btc level), as this would + // allow staker to spam the network with unbonding txs, which would force jury and validator to send signatures. + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx fee must be larger that 0") + } + + ud := types.BTCUndelegation{ + UnbondingTx: req.UnbondingTx, + SlashingTx: req.SlashingTx, + DelegatorSlashingSig: req.DelegatorSlashingSig, + // following objects needs to be filled by jury and validator + // Jurry needs to provide two sigs: + // - one for unbonding tx + // - one for slashing tx of unbonding tx + JurySlashingSig: nil, + JuryUnbondingSig: nil, + ValidatorUnbondingSig: nil, + } + + if err := ms.AddUndelegationToBTCDelegation( + ctx, + publicKeyInfos.ValidatorKey, + publicKeyInfos.StakerKey, + stakingTxHash, + &ud); err != nil { + panic(fmt.Errorf("failed to set BTC delegation that has passed verification: %w", err)) + } + + return &types.MsgBTCUndelegateResponse{}, nil +} + // AddJurySig adds a signature from jury to a BTC delegation func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) (*types.MsgAddJurySigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -216,7 +378,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) return nil, types.ErrDuplicatedJurySig } - stakingOutputInfo, err := btcDel.StakingTx.GetStakingOutputInfo(ms.btcNet) + stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) if err != nil { // failing to get staking output info from a verified staking tx is a programming error panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) @@ -232,7 +394,7 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) err = btcDel.SlashingTx.VerifySignature( stakingOutputInfo.StakingPkScript, int64(stakingOutputInfo.StakingAmount), - btcDel.StakingTx.StakingScript, + btcDel.StakingTx.Script, juryPK, req.Sig, ) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 3e5934b15..b79e0484e 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -14,7 +14,11 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -80,6 +84,193 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { }) } +func getJuryInfo(t *testing.T, + r *rand.Rand, + goCtx context.Context, + ms types.MsgServer, + net *chaincfg.Params, + bsKeeper *keeper.Keeper, + sdkCtx sdk.Context) (*btcec.PrivateKey, *btcec.PublicKey, btcutil.Address) { + jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + err = bsKeeper.SetParams(sdkCtx, types.Params{ + JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + SlashingAddress: slashingAddr.String(), + MinSlashingTxFeeSat: 10, + }) + require.NoError(t, err) + return jurySK, juryPK, slashingAddr + +} + +func createValidator( + t *testing.T, + r *rand.Rand, + goCtx context.Context, + ms types.MsgServer, +) (*btcec.PrivateKey, *btcec.PublicKey, *types.BTCValidator) { + validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) + require.NoError(t, err) + msgNewVal := types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + Description: btcVal.Description, + Commission: btcVal.Commission, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, + } + _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) + require.NoError(t, err) + return validatorSK, validatorPK, btcVal +} + +func createDelegation( + t *testing.T, + r *rand.Rand, + goCtx context.Context, + ms types.MsgServer, + btccKeeper *types.MockBtcCheckpointKeeper, + btclcKeeper *types.MockBTCLightClientKeeper, + net *chaincfg.Params, + validatorPK *btcec.PublicKey, + juryPK *btcec.PublicKey, + slashingAddr string, + stakingTime uint16, +) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { + delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + stakingTimeBlocks := stakingTime + stakingValue := int64(2 * 10e8) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + r, + net, + delSK, + validatorPK, + juryPK, + stakingTimeBlocks, + stakingValue, + slashingAddr, + ) + require.NoError(t, err) + // get msgTx + stakingMsgTx, err := stakingTx.ToMsgTx() + require.NoError(t, err) + stakingTxHash := stakingTx.MustGetTxHash() + + // random signer + signer := datagen.GenRandomAccount().Address + // random Babylon SK + delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + require.NoError(t, err) + // PoP + pop, err := types.NewPoP(delBabylonSK, delSK) + require.NoError(t, err) + // generate staking tx info + prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) + btcHeader := btcHeaderWithProof.HeaderBytes + txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) + + // mock for testing k-deep stuff + btccKeeper.EXPECT().GetPowLimit().Return(net.PowLimit).AnyTimes() + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) + + // generate proper delegator sig + delegatorSig, err := slashingTx.Sign( + stakingMsgTx, + stakingTx.Script, + delSK, + net, + ) + require.NoError(t, err) + + // all good, construct and send MsgCreateBTCDelegation message + msgCreateBTCDel := &types.MsgCreateBTCDelegation{ + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + Pop: pop, + StakingTx: stakingTx, + StakingTxInfo: txInfo, + SlashingTx: slashingTx, + DelegatorSig: delegatorSig, + } + _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) + require.NoError(t, err) + return stakingTxHash, delSK, delPK, msgCreateBTCDel +} + +func createJurySig( + t *testing.T, + r *rand.Rand, + goCtx context.Context, + ms types.MsgServer, + bsKeeper *keeper.Keeper, + sdkCtx sdk.Context, + net *chaincfg.Params, + jurySK *btcec.PrivateKey, + msgCreateBTCDel *types.MsgCreateBTCDelegation, + delegation *types.BTCDelegation, +) { + stakingMsgTx, err := msgCreateBTCDel.StakingTx.ToMsgTx() + require.NoError(t, err) + stakingTxHash := msgCreateBTCDel.StakingTx.MustGetTxHash() + jurySig, err := msgCreateBTCDel.SlashingTx.Sign( + stakingMsgTx, + msgCreateBTCDel.StakingTx.Script, + jurySK, + net, + ) + require.NoError(t, err) + msgAddJurySig := &types.MsgAddJurySig{ + Signer: msgCreateBTCDel.Signer, + ValPk: delegation.ValBtcPk, + DelPk: delegation.BtcPk, + StakingTxHash: stakingTxHash, + Sig: jurySig, + } + _, err = ms.AddJurySig(goCtx, msgAddJurySig) + require.NoError(t, err) + + /* + ensure jury sig is added successfully + */ + actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(sdkCtx, delegation.ValBtcPk, delegation.BtcPk, stakingTxHash) + require.NoError(t, err) + require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) + require.True(t, actualDelWithJurySig.HasJurySig()) +} + +func getDelegationAndCheckValues( + t *testing.T, + r *rand.Rand, + ms types.MsgServer, + bsKeeper *keeper.Keeper, + sdkCtx sdk.Context, + msgCreateBTCDel *types.MsgCreateBTCDelegation, + validatorPK *btcec.PublicKey, + delegatorPK *btcec.PublicKey, + stakingTxHash string, +) *types.BTCDelegation { + actualDel, err := bsKeeper.GetBTCDelegation(sdkCtx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delegatorPK), stakingTxHash) + require.NoError(t, err) + require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) + require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) + require.Equal(t, msgCreateBTCDel.StakingTx, actualDel.StakingTx) + require.Equal(t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) + // ensure the BTC delegation in DB is correctly formatted + err = actualDel.ValidateBasic() + require.NoError(t, err) + // delegation is not activated by jury yet + require.False(t, actualDel.HasJurySig()) + return actualDel +} + func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) @@ -97,134 +288,40 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { goCtx := sdk.WrapSDKContext(ctx) // set jury PK to params - jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) - err = bsKeeper.SetParams(ctx, types.Params{ - JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), - SlashingAddress: slashingAddr.String(), - MinSlashingTxFeeSat: 10, - }) - require.NoError(t, err) + jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) /* generate and insert new BTC validator */ - validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) - require.NoError(t, err) - msgNewVal := types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - Description: btcVal.Description, - Commission: btcVal.Commission, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, - } - _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) - require.NoError(t, err) + _, validatorPK, _ := createValidator(t, r, goCtx, ms) /* generate and insert new BTC delegation */ - // key pairs, staking tx and slashing tx - delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - stakingTimeBlocks := uint16(1000) - stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) - require.NoError(t, err) - // get msgTx - stakingMsgTx, err := stakingTx.ToMsgTx() - require.NoError(t, err) - stakingTxHash := stakingTx.MustGetTxHash() - - // random signer - signer := datagen.GenRandomAccount().Address - // random Babylon SK - delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) - require.NoError(t, err) - // PoP - pop, err := types.NewPoP(delBabylonSK, delSK) - require.NoError(t, err) - // generate staking tx info - prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) - btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) - btcHeader := btcHeaderWithProof.HeaderBytes - txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) - // mock for testing k-deep stuff - btccKeeper.EXPECT().GetPowLimit().Return(net.PowLimit).AnyTimes() - btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) - - // generate proper delegator sig - delegatorSig, err := slashingTx.Sign( - stakingMsgTx, - stakingTx.StakingScript, - delSK, + stakingTxHash, _, delPK, msgCreateBTCDel := createDelegation( + t, + r, + goCtx, + ms, + btccKeeper, + btclcKeeper, net, + validatorPK, + juryPK, + slashingAddr.String(), + 1000, ) - require.NoError(t, err) - - // all good, construct and send MsgCreateBTCDelegation message - msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - Pop: pop, - StakingTx: stakingTx, - StakingTxInfo: txInfo, - SlashingTx: slashingTx, - DelegatorSig: delegatorSig, - } - _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) - require.NoError(t, err) /* verify the new BTC delegation */ // check existence - actualDel, err := bsKeeper.GetBTCDelegation(ctx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delPK), stakingTxHash) - require.NoError(t, err) - require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) - require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) - require.Equal(t, msgCreateBTCDel.StakingTx, actualDel.StakingTx) - require.Equal(t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) - // ensure the BTC delegation in DB is correctly formatted - err = actualDel.ValidateBasic() - require.NoError(t, err) - // delegation is not activated by jury yet - require.False(t, actualDel.HasJurySig()) + actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) /* generate and insert new jury signature */ - jurySig, err := slashingTx.Sign( - stakingMsgTx, - stakingTx.StakingScript, - jurySK, - net, - ) - require.NoError(t, err) - msgAddJurySig := &types.MsgAddJurySig{ - Signer: signer, - ValPk: btcVal.BtcPk, - DelPk: actualDel.BtcPk, - StakingTxHash: stakingTxHash, - Sig: jurySig, - } - _, err = ms.AddJurySig(ctx, msgAddJurySig) - require.NoError(t, err) - - /* - ensure jury sig is added successfully - */ - actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(ctx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delPK), stakingTxHash) - require.NoError(t, err) - require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) - require.True(t, actualDelWithJurySig.HasJurySig()) + createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) }) } @@ -289,7 +386,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // generate proper delegator sig delegatorSig, err := slashingTx.Sign( stakingMsgTx, - stakingTx.StakingScript, + stakingTx.Script, delSK, net, ) @@ -309,3 +406,91 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.Error(t, err) require.True(t, errors.Is(err, types.ErrBTCValNotFound)) } + +func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + ms := keeper.NewMsgServerImpl(*bsKeeper) + goCtx := sdk.WrapSDKContext(ctx) + + jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + _, validatorPK, _ := createValidator(t, r, goCtx, ms) + stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( + t, + r, + goCtx, + ms, + btccKeeper, + btclcKeeper, + net, + validatorPK, + juryPK, + slashingAddr.String(), + 1000, + ) + actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) + + stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) + require.NoError(t, err) + stkOutputIdx := uint32(0) + defaultParams := btcctypes.DefaultParams() + + unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + r, + net, + delSK, + validatorPK, + juryPK, + wire.NewOutPoint(stkTxHash, stkOutputIdx), + uint16(defaultParams.CheckpointFinalizationTimeout)+1, + int64(actualDel.TotalSat)-1000, + slashingAddr.String(), + ) + require.NoError(t, err) + // random signer + signer := datagen.GenRandomAccount().Address + unbondingTxMsg, err := unbondingTx.ToMsgTx() + require.NoError(t, err) + + sig, err := slashUnbondingTx.Sign( + unbondingTxMsg, + unbondingTx.Script, + delSK, + net, + ) + require.NoError(t, err) + + msg := &types.MsgBTCUndelegate{ + Signer: signer, + UnbondingTx: unbondingTx, + SlashingTx: slashUnbondingTx, + DelegatorSlashingSig: sig, + } + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) + _, err = ms.BTCUndelegate(goCtx, msg) + require.NoError(t, err) + + actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + require.NoError(t, err) + + require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, unbondingTx) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, slashUnbondingTx) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, sig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JurySlashingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JuryUnbondingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.ValidatorUnbondingSig) + + }) +} diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index f83ae92ac..8b6434e75 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -97,7 +97,7 @@ func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) if err != nil { return err } - return btcstaking.CheckSlashingTx(msgTx, decodedAddr) + return btcstaking.IsSlashingTx(msgTx, decodedAddr) } // Sign generates a signature on the slashing tx signed by staker, validator or jury @@ -141,9 +141,9 @@ func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount i // - validator signature // - delegator signature // - jury signature -func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *StakingTx, valSig, delSig, jurySig *bbn.BIP340Signature) (*wire.MsgTx, error) { +func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valSig, delSig, jurySig *bbn.BIP340Signature) (*wire.MsgTx, error) { // get staking script - stakingScript := stakingTx.StakingScript + stakingScript := stakingTx.Script // get Schnorr signatures valSchnorrSig, err := valSig.ToBTCSig() diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 4b7e22966..195598480 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -35,26 +35,26 @@ func FuzzSlashingTxWithWitness(f *testing.F) { // generate staking/slashing tx stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, valPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) - stakingOutInfo, err := stakingTx.GetStakingOutputInfo(net) + stakingOutInfo, err := stakingTx.GetBabylonOutputInfo(net) require.NoError(t, err) stakingPkScript := stakingOutInfo.StakingPkScript stakingMsgTx, err := stakingTx.ToMsgTx() require.NoError(t, err) // sign slashing tx - valSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, valSK, net) + valSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, valSK, net) require.NoError(t, err) - delSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, delSK, net) + delSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, delSK, net) require.NoError(t, err) - jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.StakingScript, jurySK, net) + jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, jurySK, net) require.NoError(t, err) // verify signatures first - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, valPK, valSig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, valPK, valSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, delPK, delSig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, delPK, delSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.StakingScript, juryPK, jurySig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, juryPK, jurySig) require.NoError(t, err) // build slashing tx with witness diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index c10487b50..ab6199d22 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -95,6 +95,17 @@ func (d *BTCDelegation) HasJurySig() bool { // Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a jury sig // Expired: Delegation timelock func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { + if d.BtcUndelegation != nil { + // If we received an undelegation, delegation becomes expired which means that staker + // voting power from this delegation is removed from the total voting power + // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain + // we may consider adding unbonding time on Babylon chain later to avoid situation where + // we can lose to much voting power in to short time. + // TODO: maybe it is worth having separte status for this case help jury/validators to + // filter for unbodning delegations + return BTCDelegationStatus_EXPIRED + } + if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { if d.HasJurySig() { return BTCDelegationStatus_ACTIVE @@ -162,6 +173,20 @@ func (dels *BTCDelegatorDelegations) AddJurySig(stakingTxHash string, sig *bbn.B return nil } +func (dels *BTCDelegatorDelegations) AddUndelegation(stakingTxHash string, ud *BTCUndelegation) error { + del, err := dels.Get(stakingTxHash) + if err != nil { + return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) + } + + if del.BtcUndelegation != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has valid undelegation object", stakingTxHash) + } + + del.BtcUndelegation = ud + return nil +} + // TODO: this is an O(n) operation. Consider optimisation later func (dels *BTCDelegatorDelegations) Has(stakingTxHash string) bool { for _, d := range dels.Dels { @@ -207,19 +232,19 @@ func (p *ProofOfPossession) ValidateBasic() error { return nil } -func NewStakingTxFromHex(txHex string) (*StakingTx, error) { +func NewBabylonTaprootTxFromHex(txHex string) (*BabylonBTCTaprootTx, error) { txBytes, err := hex.DecodeString(txHex) if err != nil { return nil, err } - var tx StakingTx + var tx BabylonBTCTaprootTx if err := tx.Unmarshal(txBytes); err != nil { return nil, err } return &tx, nil } -func (tx *StakingTx) ToHexStr() (string, error) { +func (tx *BabylonBTCTaprootTx) ToHexStr() (string, error) { txBytes, err := tx.Marshal() if err != nil { return "", err @@ -227,11 +252,11 @@ func (tx *StakingTx) ToHexStr() (string, error) { return hex.EncodeToString(txBytes), nil } -func (tx *StakingTx) Equals(tx2 *StakingTx) bool { - return bytes.Equal(tx.Tx, tx2.Tx) && bytes.Equal(tx.StakingScript, tx2.StakingScript) +func (tx *BabylonBTCTaprootTx) Equals(tx2 *BabylonBTCTaprootTx) bool { + return bytes.Equal(tx.Tx, tx2.Tx) && bytes.Equal(tx.Script, tx2.Script) } -func (tx *StakingTx) ValidateBasic() error { +func (tx *BabylonBTCTaprootTx) ValidateBasic() error { // unmarshal tx bytes to MsgTx var msgTx wire.MsgTx rbuf := bytes.NewReader(tx.Tx) @@ -240,14 +265,14 @@ func (tx *StakingTx) ValidateBasic() error { } // parse staking script - if _, err := btcstaking.ParseStakingTransactionScript(tx.StakingScript); err != nil { + if _, err := btcstaking.ParseStakingTransactionScript(tx.Script); err != nil { return err } return nil } -func (tx *StakingTx) ToMsgTx() (*wire.MsgTx, error) { +func (tx *BabylonBTCTaprootTx) ToMsgTx() (*wire.MsgTx, error) { var msgTx wire.MsgTx rbuf := bytes.NewReader(tx.Tx) if err := msgTx.Deserialize(rbuf); err != nil { @@ -256,7 +281,7 @@ func (tx *StakingTx) ToMsgTx() (*wire.MsgTx, error) { return &msgTx, nil } -func (tx *StakingTx) GetTxHash() (string, error) { +func (tx *BabylonBTCTaprootTx) GetTxHash() (string, error) { msgTx, err := tx.ToMsgTx() if err != nil { return "", err @@ -264,7 +289,7 @@ func (tx *StakingTx) GetTxHash() (string, error) { return msgTx.TxHash().String(), nil } -func (tx *StakingTx) MustGetTxHash() string { +func (tx *BabylonBTCTaprootTx) MustGetTxHash() string { msgTx, err := tx.ToMsgTx() if err != nil { panic(err) @@ -272,11 +297,11 @@ func (tx *StakingTx) MustGetTxHash() string { return msgTx.TxHash().String() } -func (tx *StakingTx) GetStakingScriptData() (*btcstaking.StakingScriptData, error) { - return btcstaking.ParseStakingTransactionScript(tx.StakingScript) +func (tx *BabylonBTCTaprootTx) GetScriptData() (*btcstaking.StakingScriptData, error) { + return btcstaking.ParseStakingTransactionScript(tx.Script) } -func (tx *StakingTx) GetStakingOutputInfo(net *chaincfg.Params) (*btcstaking.StakingOutputInfo, error) { +func (tx *BabylonBTCTaprootTx) GetBabylonOutputInfo(net *chaincfg.Params) (*btcstaking.StakingOutputInfo, error) { var ( scriptData *btcstaking.StakingScriptData outValue int64 @@ -291,11 +316,11 @@ func (tx *StakingTx) GetStakingOutputInfo(net *chaincfg.Params) (*btcstaking.Sta } // parse staking script - scriptData, err = btcstaking.ParseStakingTransactionScript(tx.StakingScript) + scriptData, err = btcstaking.ParseStakingTransactionScript(tx.Script) if err != nil { return nil, err } - expectedPkScript, err := btcstaking.BuildUnspendableTaprootPkScript(tx.StakingScript, net) + expectedPkScript, err := btcstaking.BuildUnspendableTaprootPkScript(tx.Script, net) if err != nil { return nil, err } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 48ac3f83b..9c6455a75 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -254,7 +254,7 @@ type BTCDelegation struct { // quantified in satoshi TotalSat uint64 `protobuf:"varint,7,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` // staking_tx is the staking tx - StakingTx *StakingTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + StakingTx *BabylonBTCTaprootTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or jury yet. @@ -267,6 +267,11 @@ type BTCDelegation struct { // by the jury (i.e., SK corresponding to jury_pk in params) // It will be a part of the witness for the staking tx output. JurySig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=jury_sig,json=jurySig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_sig,omitempty"` + // if this object is present it menans that staker requested undelegation, and whole + // delegation is being undelegated. + // TODO: Consider whether it would be better to store it in separate store, and not + // directly in delegation object + BtcUndelegation *BTCUndelegation `protobuf:"bytes,12,opt,name=btc_undelegation,json=btcUndelegation,proto3" json:"btc_undelegation,omitempty"` } func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } @@ -337,13 +342,89 @@ func (m *BTCDelegation) GetTotalSat() uint64 { return 0 } -func (m *BTCDelegation) GetStakingTx() *StakingTx { +func (m *BTCDelegation) GetStakingTx() *BabylonBTCTaprootTx { if m != nil { return m.StakingTx } return nil } +func (m *BTCDelegation) GetBtcUndelegation() *BTCUndelegation { + if m != nil { + return m.BtcUndelegation + } + return nil +} + +// BTCUndelegation signalizes that the delegation is being undelegated +type BTCUndelegation struct { + // unbonding_tx is the transaction which will transfer the funds from staking + // output to unbonding output. Unbonding output will usually have lower timelock + // than staking output. + UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // slashing_tx is the slashing tx for unbodning transactions + // It is partially signed by SK corresponding to btc_pk, but not signed by + // validator or jury yet. + SlashingTx *BTCSlashingTx `protobuf:"bytes,2,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + // delegator_slashing_sig is the signature on the slashing tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It will be a part of the witness for the unbodning tx output. + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + // jury_slashing_sig is the signature on the slashing tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon + JurySlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=jury_slashing_sig,json=jurySlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_slashing_sig,omitempty"` + // jury_unbonding_sig is the signature on the unbonding tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon and after + // validator sig will be provided by validator + JuryUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=jury_unbonding_sig,json=juryUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_unbonding_sig,omitempty"` + // validator_unbonding_sig is the signature on the unbonding tx + // by the validator (i.e., SK corresponding to jury_pk in params) + // It must be provided after processing undelagate message by Babylon + ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` +} + +func (m *BTCUndelegation) Reset() { *m = BTCUndelegation{} } +func (m *BTCUndelegation) String() string { return proto.CompactTextString(m) } +func (*BTCUndelegation) ProtoMessage() {} +func (*BTCUndelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{3} +} +func (m *BTCUndelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCUndelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCUndelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCUndelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCUndelegation.Merge(m, src) +} +func (m *BTCUndelegation) XXX_Size() int { + return m.Size() +} +func (m *BTCUndelegation) XXX_DiscardUnknown() { + xxx_messageInfo_BTCUndelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCUndelegation proto.InternalMessageInfo + +func (m *BTCUndelegation) GetUnbondingTx() *BabylonBTCTaprootTx { + if m != nil { + return m.UnbondingTx + } + return nil +} + // BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` @@ -353,7 +434,7 @@ func (m *BTCDelegatorDelegations) Reset() { *m = BTCDelegatorDelegations func (m *BTCDelegatorDelegations) String() string { return proto.CompactTextString(m) } func (*BTCDelegatorDelegations) ProtoMessage() {} func (*BTCDelegatorDelegations) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{3} + return fileDescriptor_3851ae95ccfaf7db, []int{4} } func (m *BTCDelegatorDelegations) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -404,7 +485,7 @@ func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } func (*ProofOfPossession) ProtoMessage() {} func (*ProofOfPossession) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{4} + return fileDescriptor_3851ae95ccfaf7db, []int{5} } func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -440,26 +521,27 @@ func (m *ProofOfPossession) GetBabylonSig() []byte { return nil } -// StakingTx is the tx for delegating BTC -type StakingTx struct { - // tx is the staking tx in bytes +// BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and +// transaction which commits to the provided script. +type BabylonBTCTaprootTx struct { + // tx is the transaction bytes Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` - // staking_script is the script for the staking tx's output - StakingScript []byte `protobuf:"bytes,2,opt,name=staking_script,json=stakingScript,proto3" json:"staking_script,omitempty"` + // script is the script recognized by Babylon + Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` } -func (m *StakingTx) Reset() { *m = StakingTx{} } -func (m *StakingTx) String() string { return proto.CompactTextString(m) } -func (*StakingTx) ProtoMessage() {} -func (*StakingTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{5} +func (m *BabylonBTCTaprootTx) Reset() { *m = BabylonBTCTaprootTx{} } +func (m *BabylonBTCTaprootTx) String() string { return proto.CompactTextString(m) } +func (*BabylonBTCTaprootTx) ProtoMessage() {} +func (*BabylonBTCTaprootTx) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{6} } -func (m *StakingTx) XXX_Unmarshal(b []byte) error { +func (m *BabylonBTCTaprootTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *StakingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *BabylonBTCTaprootTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_StakingTx.Marshal(b, m, deterministic) + return xxx_messageInfo_BabylonBTCTaprootTx.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -469,28 +551,28 @@ func (m *StakingTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *StakingTx) XXX_Merge(src proto.Message) { - xxx_messageInfo_StakingTx.Merge(m, src) +func (m *BabylonBTCTaprootTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_BabylonBTCTaprootTx.Merge(m, src) } -func (m *StakingTx) XXX_Size() int { +func (m *BabylonBTCTaprootTx) XXX_Size() int { return m.Size() } -func (m *StakingTx) XXX_DiscardUnknown() { - xxx_messageInfo_StakingTx.DiscardUnknown(m) +func (m *BabylonBTCTaprootTx) XXX_DiscardUnknown() { + xxx_messageInfo_BabylonBTCTaprootTx.DiscardUnknown(m) } -var xxx_messageInfo_StakingTx proto.InternalMessageInfo +var xxx_messageInfo_BabylonBTCTaprootTx proto.InternalMessageInfo -func (m *StakingTx) GetTx() []byte { +func (m *BabylonBTCTaprootTx) GetTx() []byte { if m != nil { return m.Tx } return nil } -func (m *StakingTx) GetStakingScript() []byte { +func (m *BabylonBTCTaprootTx) GetScript() []byte { if m != nil { - return m.StakingScript + return m.Script } return nil } @@ -500,9 +582,10 @@ func init() { proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") + proto.RegisterType((*BTCUndelegation)(nil), "babylon.btcstaking.v1.BTCUndelegation") proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") - proto.RegisterType((*StakingTx)(nil), "babylon.btcstaking.v1.StakingTx") + proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") } func init() { @@ -510,60 +593,68 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 845 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4d, 0x8f, 0xdb, 0x44, - 0x18, 0x5e, 0x27, 0xd9, 0xec, 0xe6, 0x75, 0xb6, 0xda, 0x0e, 0x4b, 0x09, 0x45, 0x24, 0x69, 0x28, - 0x55, 0x84, 0xa8, 0xcd, 0xa6, 0x1f, 0x2a, 0x20, 0x81, 0xf0, 0x26, 0x82, 0x15, 0xb4, 0x58, 0x76, - 0x54, 0x2a, 0x0e, 0x44, 0x63, 0x7b, 0xea, 0x98, 0x78, 0x3d, 0x96, 0x67, 0x12, 0x92, 0x3b, 0x07, - 0x8e, 0xfc, 0x18, 0x4e, 0xfc, 0x02, 0x8e, 0x15, 0x27, 0xb4, 0x87, 0x08, 0xed, 0xfe, 0x11, 0xe4, - 0xf1, 0x38, 0x71, 0x54, 0x16, 0xd4, 0xee, 0x9e, 0x32, 0x33, 0xef, 0xc7, 0x3c, 0xcf, 0xf3, 0x3e, - 0x13, 0xc3, 0x1d, 0x07, 0x3b, 0x8b, 0x90, 0x46, 0xba, 0xc3, 0x5d, 0xc6, 0xf1, 0x24, 0x88, 0x7c, - 0x7d, 0x76, 0x58, 0xd8, 0x69, 0x71, 0x42, 0x39, 0x45, 0x6f, 0xca, 0x3c, 0xad, 0x10, 0x99, 0x1d, - 0xde, 0x3c, 0xf0, 0xa9, 0x4f, 0x45, 0x86, 0x9e, 0xae, 0xb2, 0xe4, 0x9b, 0x6f, 0xbb, 0x94, 0x9d, - 0x50, 0x36, 0xca, 0x02, 0xd9, 0x46, 0x86, 0x3a, 0xd9, 0x4e, 0x77, 0x93, 0x45, 0xcc, 0xa9, 0xce, - 0x88, 0x1b, 0xf7, 0x1e, 0x3c, 0x9c, 0x1c, 0xea, 0x13, 0xb2, 0xc8, 0x73, 0x6e, 0xcb, 0x9c, 0x35, - 0x1e, 0x87, 0x70, 0x7c, 0xa8, 0x6f, 0x20, 0xea, 0x2c, 0xcb, 0x50, 0x37, 0x86, 0x47, 0x4f, 0x71, - 0x18, 0x78, 0x98, 0xd3, 0x04, 0x0d, 0x40, 0xf5, 0x08, 0x73, 0x93, 0x20, 0xe6, 0x01, 0x8d, 0x1a, - 0x4a, 0x5b, 0xe9, 0xaa, 0xbd, 0xf7, 0x34, 0x79, 0xfd, 0x1a, 0xb4, 0x68, 0xa6, 0xf5, 0xd7, 0xa9, - 0x56, 0xb1, 0x0e, 0x3d, 0x03, 0x70, 0xe9, 0xc9, 0x49, 0xc0, 0x58, 0xda, 0xa5, 0xd4, 0x56, 0xba, - 0x35, 0xe3, 0xd1, 0xe9, 0xb2, 0x75, 0xc7, 0x0f, 0xf8, 0x78, 0xea, 0x68, 0x2e, 0x3d, 0xd1, 0x73, - 0x12, 0xe2, 0xe7, 0x2e, 0xf3, 0x26, 0x3a, 0x5f, 0xc4, 0x84, 0x69, 0x7d, 0xe2, 0xfe, 0xf9, 0xdb, - 0x5d, 0x90, 0x57, 0xf6, 0x89, 0x6b, 0x15, 0x7a, 0xa1, 0xcf, 0x00, 0xa4, 0x8a, 0xa3, 0x78, 0xd2, - 0x28, 0x0b, 0x7c, 0xad, 0x1c, 0x5f, 0x26, 0x88, 0xb6, 0x12, 0x44, 0x33, 0xa7, 0xce, 0xd7, 0x64, - 0x61, 0xd5, 0x64, 0x89, 0x39, 0x41, 0x8f, 0xa1, 0xea, 0x70, 0x37, 0xad, 0xad, 0xb4, 0x95, 0x6e, - 0xdd, 0x78, 0x78, 0xba, 0x6c, 0xf5, 0x0a, 0xa8, 0x64, 0xa6, 0x3b, 0xc6, 0x41, 0x94, 0x6f, 0x24, - 0x30, 0xe3, 0xd8, 0xbc, 0x77, 0xff, 0x23, 0xd9, 0x72, 0xdb, 0xe1, 0xae, 0x39, 0x41, 0x9f, 0x40, - 0x39, 0xa6, 0x71, 0x63, 0x5b, 0xe0, 0xe8, 0x6a, 0xff, 0x3a, 0x60, 0xcd, 0x4c, 0x28, 0x7d, 0xfe, - 0xed, 0x73, 0x93, 0x32, 0x46, 0x04, 0x0b, 0x2b, 0x2d, 0x42, 0xf7, 0xe1, 0x06, 0x0b, 0x31, 0x1b, - 0x13, 0x6f, 0x94, 0x53, 0x1a, 0x93, 0xc0, 0x1f, 0xf3, 0x46, 0xb5, 0xad, 0x74, 0x2b, 0xd6, 0x81, - 0x8c, 0x1a, 0x59, 0xf0, 0x2b, 0x11, 0x43, 0x1f, 0x02, 0x5a, 0x55, 0x71, 0x37, 0xaf, 0xd8, 0x11, - 0x15, 0xfb, 0x79, 0x05, 0x77, 0xb3, 0xec, 0xce, 0xcf, 0x25, 0x38, 0x28, 0x0e, 0xf8, 0xbb, 0x80, - 0x8f, 0x1f, 0x13, 0x8e, 0x0b, 0x3a, 0x28, 0x57, 0xa1, 0xc3, 0x0d, 0xa8, 0x4a, 0x24, 0x25, 0x81, - 0x44, 0xee, 0xd0, 0x2d, 0xa8, 0xcf, 0x28, 0x0f, 0x22, 0x7f, 0x14, 0xd3, 0x9f, 0x48, 0x22, 0x06, - 0x56, 0xb1, 0xd4, 0xec, 0xcc, 0x4c, 0x8f, 0xfe, 0x43, 0x86, 0xca, 0x2b, 0xcb, 0xb0, 0x7d, 0x81, - 0x0c, 0xbf, 0x6f, 0xc3, 0x9e, 0x31, 0x3c, 0xea, 0x93, 0x90, 0xf8, 0x98, 0xbf, 0xec, 0x23, 0xe5, - 0x12, 0x3e, 0x2a, 0x5d, 0xa1, 0x8f, 0xca, 0xaf, 0xe3, 0xa3, 0x21, 0xc0, 0x0c, 0x87, 0xa3, 0x2b, - 0xb1, 0xf5, 0xee, 0x0c, 0x87, 0x86, 0x40, 0x74, 0x0b, 0xea, 0x8c, 0xe3, 0x84, 0x6f, 0x4a, 0xab, - 0x8a, 0x33, 0x39, 0x83, 0x77, 0x01, 0x48, 0xe4, 0x6d, 0x9a, 0xb6, 0x46, 0x22, 0x4f, 0x86, 0xdf, - 0x81, 0x1a, 0xa7, 0x1c, 0x87, 0x23, 0x86, 0x73, 0x83, 0xee, 0x8a, 0x03, 0x1b, 0x73, 0xf4, 0x39, - 0x80, 0x64, 0x36, 0xe2, 0xf3, 0xc6, 0xae, 0xe0, 0xdd, 0xbe, 0x80, 0xb7, 0x9d, 0x2d, 0x87, 0x73, - 0xab, 0xc6, 0xf2, 0x25, 0xea, 0x81, 0x2a, 0xc6, 0x2c, 0x3b, 0xd4, 0x04, 0xed, 0xeb, 0xa7, 0xcb, - 0x56, 0x3a, 0x68, 0x5b, 0x46, 0x86, 0x73, 0x0b, 0xd8, 0x6a, 0x8d, 0x7e, 0x80, 0x3d, 0x2f, 0xb3, - 0x00, 0x4d, 0x46, 0x2c, 0xf0, 0x1b, 0x20, 0xaa, 0x3e, 0x3e, 0x5d, 0xb6, 0x1e, 0xbc, 0x8a, 0x58, - 0x76, 0xe0, 0x47, 0x98, 0x4f, 0x13, 0x62, 0xd5, 0x57, 0xfd, 0xec, 0xc0, 0x47, 0x43, 0xd8, 0xfd, - 0x71, 0x9a, 0x2c, 0x44, 0x6b, 0xf5, 0xb2, 0xad, 0x77, 0xd2, 0x56, 0x76, 0xe0, 0x77, 0x6c, 0x78, - 0x6b, 0xed, 0x5d, 0x9a, 0xac, 0x4d, 0xcc, 0xd0, 0x23, 0xa8, 0x78, 0x24, 0x64, 0x0d, 0xa5, 0x5d, - 0xee, 0xaa, 0xbd, 0xdb, 0x17, 0xe8, 0xb7, 0xe1, 0x7c, 0x4b, 0x54, 0x74, 0x7e, 0x51, 0xe0, 0xfa, - 0x4b, 0x7e, 0x42, 0x2d, 0x50, 0xf3, 0x57, 0x91, 0x72, 0x10, 0x7f, 0x0d, 0x56, 0xfe, 0x50, 0x52, - 0x86, 0x16, 0xec, 0xa4, 0x3e, 0x4b, 0x83, 0xa5, 0xcb, 0x12, 0x4c, 0x1f, 0x50, 0xca, 0xcf, 0x80, - 0xda, 0x6a, 0xc2, 0xe8, 0x1a, 0x94, 0xf8, 0x5c, 0x5e, 0x5c, 0xe2, 0x73, 0xf4, 0x3e, 0x5c, 0xcb, - 0x7d, 0x92, 0x7d, 0x5e, 0xb2, 0x7b, 0xad, 0x3d, 0x79, 0x6a, 0x8b, 0xc3, 0x0f, 0x3e, 0x85, 0x37, - 0x36, 0x58, 0xda, 0x1c, 0xf3, 0x29, 0x43, 0x2a, 0xec, 0x98, 0x83, 0x27, 0xfd, 0xe3, 0x27, 0x5f, - 0xee, 0x6f, 0x21, 0x80, 0xea, 0x17, 0x47, 0xc3, 0xe3, 0xa7, 0x83, 0x7d, 0x25, 0x0d, 0x0c, 0x9e, - 0x99, 0xc7, 0xd6, 0xa0, 0xbf, 0x5f, 0x32, 0xbe, 0xf9, 0xe3, 0xac, 0xa9, 0xbc, 0x38, 0x6b, 0x2a, - 0x7f, 0x9f, 0x35, 0x95, 0x5f, 0xcf, 0x9b, 0x5b, 0x2f, 0xce, 0x9b, 0x5b, 0x7f, 0x9d, 0x37, 0xb7, - 0xbe, 0xff, 0xdf, 0x27, 0x34, 0x2f, 0x7e, 0xf3, 0x05, 0x4d, 0xa7, 0x2a, 0x3e, 0xad, 0xf7, 0xfe, - 0x09, 0x00, 0x00, 0xff, 0xff, 0x6f, 0x4d, 0x54, 0x1d, 0x16, 0x08, 0x00, 0x00, + // 976 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0x8e, 0x13, 0xbf, 0x75, 0x89, 0x33, 0x4d, 0x53, 0x53, 0x84, 0x9d, 0x9a, 0x2a, + 0xb2, 0x2a, 0xba, 0x26, 0xee, 0x1f, 0x15, 0x10, 0x48, 0xf8, 0x8f, 0xc0, 0x82, 0x14, 0xb3, 0xde, + 0x96, 0x8a, 0x03, 0xd6, 0xfe, 0x99, 0xae, 0x17, 0x3b, 0x3b, 0xcb, 0xce, 0xd8, 0xd8, 0x77, 0x0e, + 0x1c, 0xb9, 0xf1, 0x45, 0xf8, 0x10, 0x3d, 0x56, 0x9c, 0x50, 0x0e, 0x16, 0x4a, 0xbe, 0x08, 0x9a, + 0xd9, 0x59, 0x7b, 0xd3, 0x34, 0x40, 0x70, 0x4e, 0xf6, 0xcc, 0x7b, 0xef, 0xf7, 0xde, 0xfb, 0xfd, + 0xde, 0xbe, 0x5d, 0xd8, 0xb7, 0x4c, 0x6b, 0x36, 0x22, 0x7e, 0xcd, 0x62, 0x36, 0x65, 0xe6, 0xd0, + 0xf3, 0xdd, 0xda, 0xe4, 0x20, 0x71, 0xd2, 0x82, 0x90, 0x30, 0x82, 0x6e, 0x48, 0x3f, 0x2d, 0x61, + 0x99, 0x1c, 0xdc, 0xda, 0x71, 0x89, 0x4b, 0x84, 0x47, 0x8d, 0xff, 0x8b, 0x9c, 0x6f, 0xbd, 0x6d, + 0x13, 0x7a, 0x44, 0x68, 0x3f, 0x32, 0x44, 0x07, 0x69, 0xaa, 0x44, 0xa7, 0x9a, 0x1d, 0xce, 0x02, + 0x46, 0x6a, 0x14, 0xdb, 0x41, 0xfd, 0xe1, 0xa3, 0xe1, 0x41, 0x6d, 0x88, 0x67, 0xb1, 0xcf, 0x1d, + 0xe9, 0xb3, 0xac, 0xc7, 0xc2, 0xcc, 0x3c, 0xa8, 0x9d, 0xa9, 0xa8, 0x32, 0x4f, 0x43, 0xbe, 0x61, + 0x34, 0x9f, 0x99, 0x23, 0xcf, 0x31, 0x19, 0x09, 0x51, 0x1b, 0x54, 0x07, 0x53, 0x3b, 0xf4, 0x02, + 0xe6, 0x11, 0xbf, 0xa8, 0xec, 0x29, 0x55, 0xb5, 0xfe, 0x9e, 0x26, 0xd3, 0x2f, 0x8b, 0x16, 0x60, + 0x5a, 0x6b, 0xe9, 0xaa, 0x27, 0xe3, 0xd0, 0x73, 0x00, 0x9b, 0x1c, 0x1d, 0x79, 0x94, 0x72, 0x94, + 0xd4, 0x9e, 0x52, 0xcd, 0x35, 0x1e, 0x1f, 0xcf, 0xcb, 0xfb, 0xae, 0xc7, 0x06, 0x63, 0x4b, 0xb3, + 0xc9, 0x51, 0x2d, 0x6e, 0x42, 0xfc, 0xdc, 0xa3, 0xce, 0xb0, 0xc6, 0x66, 0x01, 0xa6, 0x5a, 0x0b, + 0xdb, 0x7f, 0xfc, 0x7e, 0x0f, 0x64, 0xca, 0x16, 0xb6, 0xf5, 0x04, 0x16, 0xfa, 0x14, 0x40, 0xb2, + 0xd8, 0x0f, 0x86, 0xc5, 0xb4, 0xa8, 0xaf, 0x1c, 0xd7, 0x17, 0x11, 0xa2, 0x2d, 0x08, 0xd1, 0xba, + 0x63, 0xeb, 0x4b, 0x3c, 0xd3, 0x73, 0x32, 0xa4, 0x3b, 0x44, 0x87, 0x90, 0xb5, 0x98, 0xcd, 0x63, + 0x33, 0x7b, 0x4a, 0x35, 0xdf, 0x78, 0x74, 0x3c, 0x2f, 0xd7, 0x13, 0x55, 0x49, 0x4f, 0x7b, 0x60, + 0x7a, 0x7e, 0x7c, 0x90, 0x85, 0x35, 0x3a, 0xdd, 0xfb, 0x0f, 0x3e, 0x90, 0x90, 0xeb, 0x16, 0xb3, + 0xbb, 0x43, 0xf4, 0x11, 0xa4, 0x03, 0x12, 0x14, 0xd7, 0x45, 0x1d, 0x55, 0xed, 0x8d, 0x02, 0x6b, + 0xdd, 0x90, 0x90, 0x17, 0x5f, 0xbf, 0xe8, 0x12, 0x4a, 0xb1, 0xe8, 0x42, 0xe7, 0x41, 0xe8, 0x01, + 0xec, 0xd2, 0x91, 0x49, 0x07, 0xd8, 0xe9, 0xc7, 0x2d, 0x0d, 0xb0, 0xe7, 0x0e, 0x58, 0x31, 0xbb, + 0xa7, 0x54, 0x33, 0xfa, 0x8e, 0xb4, 0x36, 0x22, 0xe3, 0x17, 0xc2, 0x86, 0xde, 0x07, 0xb4, 0x88, + 0x62, 0x76, 0x1c, 0xb1, 0x21, 0x22, 0x0a, 0x71, 0x04, 0xb3, 0x23, 0xef, 0xca, 0xcf, 0x29, 0xd8, + 0x49, 0x0a, 0xfc, 0xad, 0xc7, 0x06, 0x87, 0x98, 0x99, 0x09, 0x1e, 0x94, 0xab, 0xe0, 0x61, 0x17, + 0xb2, 0xb2, 0x92, 0x94, 0xa8, 0x44, 0x9e, 0xd0, 0x6d, 0xc8, 0x4f, 0x08, 0xf3, 0x7c, 0xb7, 0x1f, + 0x90, 0x9f, 0x70, 0x28, 0x04, 0xcb, 0xe8, 0x6a, 0x74, 0xd7, 0xe5, 0x57, 0xff, 0x40, 0x43, 0xe6, + 0xd2, 0x34, 0xac, 0x5f, 0x40, 0xc3, 0x6f, 0x59, 0xb8, 0xd6, 0x30, 0x9a, 0x2d, 0x3c, 0xc2, 0xae, + 0xc9, 0xce, 0xcf, 0x91, 0xb2, 0xc2, 0x1c, 0xa5, 0xae, 0x70, 0x8e, 0xd2, 0xff, 0x67, 0x8e, 0x0c, + 0x80, 0x89, 0x39, 0xea, 0x5f, 0xc9, 0x58, 0x6f, 0x4e, 0xcc, 0x51, 0x43, 0x54, 0x74, 0x1b, 0xf2, + 0x94, 0x99, 0x21, 0x3b, 0x4b, 0xad, 0x2a, 0xee, 0xa4, 0x06, 0xef, 0x02, 0x60, 0xdf, 0x39, 0x3b, + 0xb4, 0x39, 0xec, 0x3b, 0xd2, 0xfc, 0x0e, 0xe4, 0x18, 0x61, 0xe6, 0xa8, 0x4f, 0xcd, 0x78, 0x40, + 0x37, 0xc5, 0x45, 0xcf, 0x64, 0xa8, 0x03, 0x20, 0x3b, 0xeb, 0xb3, 0x69, 0x71, 0x53, 0xf4, 0x7d, + 0xf7, 0x82, 0xbe, 0xa5, 0xf2, 0x0d, 0xa3, 0x69, 0x98, 0x41, 0x48, 0x08, 0x33, 0xa6, 0x7a, 0x4e, + 0xda, 0x8d, 0x29, 0xaa, 0x83, 0x2a, 0x04, 0x97, 0x58, 0x39, 0x41, 0xc0, 0xf6, 0xf1, 0xbc, 0xcc, + 0x25, 0xef, 0x49, 0x8b, 0x31, 0xd5, 0x81, 0x2e, 0xfe, 0xa3, 0xef, 0xe1, 0x9a, 0x13, 0x0d, 0x03, + 0x09, 0xfb, 0xd4, 0x73, 0x8b, 0x20, 0xa2, 0x3e, 0x3c, 0x9e, 0x97, 0x1f, 0x5e, 0x86, 0xb6, 0x9e, + 0xe7, 0xfa, 0x26, 0x1b, 0x87, 0x58, 0xcf, 0x2f, 0xf0, 0x7a, 0x9e, 0x8b, 0x0c, 0xd8, 0xfc, 0x61, + 0x1c, 0xce, 0x04, 0xb4, 0xba, 0x2a, 0xf4, 0x06, 0x87, 0xe2, 0xa8, 0xdf, 0x40, 0x81, 0xab, 0x3c, + 0xf6, 0x9d, 0xc5, 0x20, 0x17, 0xf3, 0x82, 0xba, 0xfd, 0x8b, 0xa8, 0x33, 0x9a, 0x4f, 0x13, 0xde, + 0xfa, 0x96, 0xc5, 0xec, 0xe4, 0x45, 0xe5, 0x65, 0x06, 0xb6, 0x5e, 0x73, 0x42, 0x87, 0x90, 0x1f, + 0xfb, 0x16, 0xf1, 0x1d, 0xc9, 0xa8, 0x72, 0x69, 0x75, 0xd4, 0x45, 0xfc, 0x79, 0x7d, 0x52, 0xff, + 0x45, 0x1f, 0x02, 0xbb, 0x09, 0x7d, 0xe2, 0x68, 0xce, 0x66, 0x7a, 0x55, 0x36, 0x77, 0x96, 0x42, + 0x49, 0x5c, 0x4e, 0x2d, 0x86, 0xed, 0x48, 0xb0, 0x64, 0xae, 0xcc, 0xaa, 0xb9, 0xb6, 0x84, 0x72, + 0x89, 0x34, 0x2e, 0x20, 0x91, 0x66, 0xc9, 0x2f, 0xcf, 0xb3, 0xbe, 0x6a, 0x9e, 0x02, 0x07, 0x7d, + 0x1a, 0x63, 0xf2, 0x44, 0x3f, 0xc2, 0xcd, 0x49, 0xbc, 0xf4, 0x5f, 0xcb, 0x96, 0x5d, 0x35, 0xdb, + 0x8d, 0x05, 0x72, 0x32, 0x65, 0xa5, 0x07, 0x37, 0x97, 0x3b, 0x96, 0x84, 0xcb, 0x65, 0x4b, 0xd1, + 0x63, 0xc8, 0x38, 0x78, 0x44, 0x8b, 0xca, 0x5e, 0xba, 0xaa, 0xd6, 0xef, 0x5c, 0x3c, 0xac, 0xcb, + 0x20, 0x5d, 0x44, 0x54, 0x7e, 0x51, 0x60, 0xfb, 0xdc, 0xde, 0x43, 0x65, 0x50, 0xe3, 0xed, 0xcd, + 0x3b, 0x12, 0xaf, 0x30, 0x3d, 0x5e, 0xe8, 0xbc, 0x7d, 0x1d, 0x36, 0xf8, 0x93, 0xc2, 0x8d, 0xa9, + 0x55, 0xdb, 0xe5, 0x8b, 0x9e, 0xf7, 0xf7, 0x09, 0x5c, 0x7f, 0xc3, 0xac, 0xa3, 0xb7, 0x20, 0x25, + 0x9f, 0x91, 0xbc, 0x9e, 0x62, 0x53, 0xfe, 0x2a, 0x8c, 0x3e, 0x84, 0xa2, 0xcc, 0xba, 0x3c, 0xdd, + 0xfd, 0x18, 0xae, 0x9f, 0x69, 0xb0, 0xc7, 0x4c, 0x36, 0xa6, 0x48, 0x85, 0x8d, 0x6e, 0xfb, 0x49, + 0xab, 0xf3, 0xe4, 0xf3, 0xc2, 0x1a, 0x02, 0xc8, 0x7e, 0xd6, 0x34, 0x3a, 0xcf, 0xda, 0x05, 0x85, + 0x1b, 0xda, 0xcf, 0xbb, 0x1d, 0xbd, 0xdd, 0x2a, 0xa4, 0x1a, 0x5f, 0xbd, 0x3c, 0x29, 0x29, 0xaf, + 0x4e, 0x4a, 0xca, 0x5f, 0x27, 0x25, 0xe5, 0xd7, 0xd3, 0xd2, 0xda, 0xab, 0xd3, 0xd2, 0xda, 0x9f, + 0xa7, 0xa5, 0xb5, 0xef, 0xfe, 0x75, 0xcb, 0x4f, 0x93, 0x9f, 0xa5, 0xa2, 0x43, 0x2b, 0x2b, 0xbe, + 0xfe, 0xee, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x59, 0x41, 0x29, 0xb9, 0x0a, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -734,6 +825,18 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.BtcUndelegation != nil { + { + size, err := m.BtcUndelegation.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + } if m.JurySig != nil { { size := m.JurySig.Size() @@ -848,6 +951,101 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BTCUndelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCUndelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ValidatorUnbondingSig != nil { + { + size := m.ValidatorUnbondingSig.Size() + i -= size + if _, err := m.ValidatorUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.JuryUnbondingSig != nil { + { + size := m.JuryUnbondingSig.Size() + i -= size + if _, err := m.JuryUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.JurySlashingSig != nil { + { + size := m.JurySlashingSig.Size() + i -= size + if _, err := m.JurySlashingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.DelegatorSlashingSig != nil { + { + size := m.DelegatorSlashingSig.Size() + i -= size + if _, err := m.DelegatorSlashingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.SlashingTx != nil { + { + size := m.SlashingTx.Size() + i -= size + if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.UnbondingTx != nil { + { + size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *BTCDelegatorDelegations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -927,7 +1125,7 @@ func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *StakingTx) Marshal() (dAtA []byte, err error) { +func (m *BabylonBTCTaprootTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -937,20 +1135,20 @@ func (m *StakingTx) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *StakingTx) MarshalTo(dAtA []byte) (int, error) { +func (m *BabylonBTCTaprootTx) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *StakingTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *BabylonBTCTaprootTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.StakingScript) > 0 { - i -= len(m.StakingScript) - copy(dAtA[i:], m.StakingScript) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.StakingScript))) + if len(m.Script) > 0 { + i -= len(m.Script) + copy(dAtA[i:], m.Script) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Script))) i-- dAtA[i] = 0x12 } @@ -1082,6 +1280,43 @@ func (m *BTCDelegation) Size() (n int) { l = m.JurySig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } + if m.BtcUndelegation != nil { + l = m.BtcUndelegation.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + +func (m *BTCUndelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UnbondingTx != nil { + l = m.UnbondingTx.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.SlashingTx != nil { + l = m.SlashingTx.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.DelegatorSlashingSig != nil { + l = m.DelegatorSlashingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.JurySlashingSig != nil { + l = m.JurySlashingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.JuryUnbondingSig != nil { + l = m.JuryUnbondingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.ValidatorUnbondingSig != nil { + l = m.ValidatorUnbondingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } return n } @@ -1117,7 +1352,7 @@ func (m *ProofOfPossession) Size() (n int) { return n } -func (m *StakingTx) Size() (n int) { +func (m *BabylonBTCTaprootTx) Size() (n int) { if m == nil { return 0 } @@ -1127,7 +1362,7 @@ func (m *StakingTx) Size() (n int) { if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } - l = len(m.StakingScript) + l = len(m.Script) if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } @@ -1826,7 +2061,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.StakingTx == nil { - m.StakingTx = &StakingTx{} + m.StakingTx = &BabylonBTCTaprootTx{} } if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1937,6 +2172,303 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcUndelegation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BtcUndelegation == nil { + m.BtcUndelegation = &BTCUndelegation{} + } + if err := m.BtcUndelegation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCUndelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCUndelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UnbondingTx == nil { + m.UnbondingTx = &BabylonBTCTaprootTx{} + } + if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorSlashingSig = &v + if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JurySlashingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.JurySlashingSig = &v + if err := m.JurySlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JuryUnbondingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.JuryUnbondingSig = &v + if err := m.JuryUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorUnbondingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.ValidatorUnbondingSig = &v + if err := m.ValidatorUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) @@ -2161,7 +2693,7 @@ func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { } return nil } -func (m *StakingTx) Unmarshal(dAtA []byte) error { +func (m *BabylonBTCTaprootTx) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2184,10 +2716,10 @@ func (m *StakingTx) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StakingTx: wiretype end group for non-group") + return fmt.Errorf("proto: BabylonBTCTaprootTx: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StakingTx: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: BabylonBTCTaprootTx: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2226,7 +2758,7 @@ func (m *StakingTx) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingScript", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Script", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2253,9 +2785,9 @@ func (m *StakingTx) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.StakingScript = append(m.StakingScript[:0], dAtA[iNdEx:postIndex]...) - if m.StakingScript == nil { - m.StakingScript = []byte{} + m.Script = append(m.Script[:0], dAtA[iNdEx:postIndex]...) + if m.Script == nil { + m.Script = []byte{} } iNdEx = postIndex default: diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 3dc9937b8..b6d57eef1 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -36,7 +36,7 @@ func FuzzStakingTx(f *testing.F) { require.NoError(t, err) // extract staking script and staked value - stakingOutputInfo, err := stakingTx.GetStakingOutputInfo(&chaincfg.SimNetParams) + stakingOutputInfo, err := stakingTx.GetBabylonOutputInfo(&chaincfg.SimNetParams) require.NoError(t, err) // NOTE: given that PK derived from SK has 2 possibilities on a curve, we can only compare x value but not y value require.Equal(t, stakingOutputInfo.StakingScriptData.StakerKey.SerializeCompressed()[1:], stakerPK.SerializeCompressed()[1:]) diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index 38af96f31..2ed77f6b8 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -12,6 +12,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) cdc.RegisterConcrete(&MsgAddJurySig{}, "btcstaking/MsgAddJurySig", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) + cdc.RegisterConcrete(&MsgBTCUndelegate{}, "btcstaking/MsgBtcUndelegate", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -22,6 +23,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgCreateBTCDelegation{}, &MsgAddJurySig{}, &MsgUpdateParams{}, + &MsgBTCUndelegate{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index c013cf96e..689613006 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -19,4 +19,6 @@ var ( ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1112, "commission cannot be less than min rate") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1113, "Unexpected delegation state") + ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1114, "the BTC unbonding tx is not valid") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 03094b6e1..636f99b2e 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -4,6 +4,7 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" @@ -15,6 +16,7 @@ var ( _ sdk.Msg = &MsgCreateBTCValidator{} _ sdk.Msg = &MsgCreateBTCDelegation{} _ sdk.Msg = &MsgAddJurySig{} + _ sdk.Msg = &MsgBTCUndelegate{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -112,7 +114,7 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if err := m.Pop.ValidateBasic(); err != nil { return err } - stakingScriptData, err := m.StakingTx.GetStakingScriptData() + stakingScriptData, err := m.StakingTx.GetScriptData() if err != nil { return err } @@ -148,3 +150,43 @@ func (m *MsgAddJurySig) ValidateBasic() error { return nil } + +func (m *MsgBTCUndelegate) ValidateBasic() error { + if m.UnbondingTx == nil { + return fmt.Errorf("empty unbodning tx") + } + if m.SlashingTx == nil { + return fmt.Errorf("empty slashing tx") + } + if m.DelegatorSlashingSig == nil { + return fmt.Errorf("empty delegator signature") + } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + return err + } + + unbondingTxMsg, err := m.UnbondingTx.ToMsgTx() + + if err != nil { + return err + } + + if err := btcstaking.IsSimpleTransfer(unbondingTxMsg); err != nil { + return err + } + + // unbodning tx should be correctly formatted - valid transaction and valid script + if err := m.UnbondingTx.ValidateBasic(); err != nil { + return err + } + + return nil +} + +func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 4286e356d..fde9ffcc9 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -157,7 +157,7 @@ type MsgCreateBTCDelegation struct { // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` // staking_tx is the staking tx - StakingTx *StakingTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + StakingTx *BabylonBTCTaprootTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // staking_tx_info is the tx info of the staking tx, including the Merkle proof StakingTxInfo *types1.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` // slashing_tx is the slashing tx @@ -224,7 +224,7 @@ func (m *MsgCreateBTCDelegation) GetPop() *ProofOfPossession { return nil } -func (m *MsgCreateBTCDelegation) GetStakingTx() *StakingTx { +func (m *MsgCreateBTCDelegation) GetStakingTx() *BabylonBTCTaprootTx { if m != nil { return m.StakingTx } @@ -275,6 +275,103 @@ func (m *MsgCreateBTCDelegationResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCreateBTCDelegationResponse proto.InternalMessageInfo +// MsgBTCUndelegate is the message undelegating existing and active delegation +type MsgBTCUndelegate struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends + // staking output and sends it to the unbonding output + UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,2,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // slashing_tx is the slashing tx which slash unbonding contract + // Note that the tx itself does not contain signatures, which are off-chain. + SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` +} + +func (m *MsgBTCUndelegate) Reset() { *m = MsgBTCUndelegate{} } +func (m *MsgBTCUndelegate) String() string { return proto.CompactTextString(m) } +func (*MsgBTCUndelegate) ProtoMessage() {} +func (*MsgBTCUndelegate) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{4} +} +func (m *MsgBTCUndelegate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgBTCUndelegate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgBTCUndelegate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgBTCUndelegate) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBTCUndelegate.Merge(m, src) +} +func (m *MsgBTCUndelegate) XXX_Size() int { + return m.Size() +} +func (m *MsgBTCUndelegate) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBTCUndelegate.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgBTCUndelegate proto.InternalMessageInfo + +func (m *MsgBTCUndelegate) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgBTCUndelegate) GetUnbondingTx() *BabylonBTCTaprootTx { + if m != nil { + return m.UnbondingTx + } + return nil +} + +// MsgBtcUndelegateResponse is the response for MsgBtcUndelegate +type MsgBTCUndelegateResponse struct { +} + +func (m *MsgBTCUndelegateResponse) Reset() { *m = MsgBTCUndelegateResponse{} } +func (m *MsgBTCUndelegateResponse) String() string { return proto.CompactTextString(m) } +func (*MsgBTCUndelegateResponse) ProtoMessage() {} +func (*MsgBTCUndelegateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{5} +} +func (m *MsgBTCUndelegateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgBTCUndelegateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgBTCUndelegateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgBTCUndelegateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBTCUndelegateResponse.Merge(m, src) +} +func (m *MsgBTCUndelegateResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgBTCUndelegateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBTCUndelegateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo + // MsgAddJurySig is the message for handling a signature from jury type MsgAddJurySig struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` @@ -296,7 +393,7 @@ func (m *MsgAddJurySig) Reset() { *m = MsgAddJurySig{} } func (m *MsgAddJurySig) String() string { return proto.CompactTextString(m) } func (*MsgAddJurySig) ProtoMessage() {} func (*MsgAddJurySig) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{4} + return fileDescriptor_4baddb53e97f38f2, []int{6} } func (m *MsgAddJurySig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -347,7 +444,7 @@ func (m *MsgAddJurySigResponse) Reset() { *m = MsgAddJurySigResponse{} } func (m *MsgAddJurySigResponse) String() string { return proto.CompactTextString(m) } func (*MsgAddJurySigResponse) ProtoMessage() {} func (*MsgAddJurySigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{5} + return fileDescriptor_4baddb53e97f38f2, []int{7} } func (m *MsgAddJurySigResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -393,7 +490,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{6} + return fileDescriptor_4baddb53e97f38f2, []int{8} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -444,7 +541,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{7} + return fileDescriptor_4baddb53e97f38f2, []int{9} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -478,6 +575,8 @@ func init() { proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") proto.RegisterType((*MsgCreateBTCDelegation)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegation") proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") + proto.RegisterType((*MsgBTCUndelegate)(nil), "babylon.btcstaking.v1.MsgBTCUndelegate") + proto.RegisterType((*MsgBTCUndelegateResponse)(nil), "babylon.btcstaking.v1.MsgBTCUndelegateResponse") proto.RegisterType((*MsgAddJurySig)(nil), "babylon.btcstaking.v1.MsgAddJurySig") proto.RegisterType((*MsgAddJurySigResponse)(nil), "babylon.btcstaking.v1.MsgAddJurySigResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") @@ -487,63 +586,69 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 894 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x41, 0x6f, 0x1b, 0x45, - 0x14, 0x8e, 0xb3, 0x8d, 0x91, 0x5f, 0x12, 0x2a, 0x86, 0xb6, 0x71, 0x2d, 0xd5, 0xb6, 0x4c, 0x15, - 0x05, 0x94, 0xec, 0x62, 0xb7, 0x89, 0xa0, 0x48, 0xa0, 0x3a, 0x41, 0xa2, 0x14, 0x0b, 0xb3, 0x36, - 0x08, 0x71, 0xc0, 0xcc, 0xee, 0x8e, 0xd7, 0x23, 0xdb, 0x3b, 0xab, 0x9d, 0xb1, 0x65, 0x8b, 0x1b, - 0xbf, 0x80, 0x13, 0x17, 0xfe, 0x04, 0x87, 0x9e, 0xf8, 0x01, 0xa8, 0xc7, 0x2a, 0xe2, 0x80, 0x72, - 0xb0, 0x50, 0x72, 0xe0, 0x6f, 0xa0, 0x9d, 0x9d, 0xf5, 0x6e, 0xd0, 0x5a, 0x24, 0xcd, 0xc9, 0x33, - 0x7e, 0xdf, 0xfb, 0xde, 0x9b, 0xef, 0x9b, 0x37, 0x36, 0x94, 0x2d, 0x6c, 0xcd, 0x47, 0xcc, 0x33, - 0x2c, 0x61, 0x73, 0x81, 0x87, 0xd4, 0x73, 0x8d, 0x69, 0xdd, 0x10, 0x33, 0xdd, 0x0f, 0x98, 0x60, - 0xe8, 0xae, 0x8a, 0xeb, 0x49, 0x5c, 0x9f, 0xd6, 0x4b, 0x77, 0x5c, 0xe6, 0x32, 0x89, 0x30, 0xc2, - 0x55, 0x04, 0x2e, 0xdd, 0xb7, 0x19, 0x1f, 0x33, 0xde, 0x8b, 0x02, 0xd1, 0x46, 0x85, 0x76, 0xa2, - 0x9d, 0x31, 0xe6, 0x92, 0x7f, 0xcc, 0x5d, 0x15, 0xa8, 0xa9, 0x80, 0x1d, 0xcc, 0x7d, 0xc1, 0x0c, - 0x4e, 0x6c, 0xbf, 0x71, 0x78, 0x34, 0xac, 0x1b, 0x43, 0x32, 0x8f, 0x93, 0x6b, 0xd9, 0x4d, 0xfa, - 0x38, 0xc0, 0xe3, 0x18, 0xb3, 0x9b, 0x8d, 0x49, 0xb5, 0x1d, 0xe1, 0xf6, 0x53, 0x38, 0x7b, 0x40, - 0xec, 0xa1, 0xcf, 0xa8, 0x27, 0x14, 0x34, 0xf9, 0x42, 0xa1, 0x1f, 0xaa, 0xee, 0x12, 0x46, 0x8b, - 0x08, 0x5c, 0x37, 0x2e, 0x71, 0xd6, 0x7e, 0xd5, 0xe0, 0x6e, 0x8b, 0xbb, 0xc7, 0x01, 0xc1, 0x82, - 0x34, 0xbb, 0xc7, 0xdf, 0xe0, 0x11, 0x75, 0xb0, 0x60, 0x01, 0xba, 0x07, 0x79, 0x4e, 0x5d, 0x8f, - 0x04, 0xc5, 0x5c, 0x35, 0xb7, 0x57, 0x30, 0xd5, 0x0e, 0x7d, 0x0a, 0x9b, 0x0e, 0xe1, 0x76, 0x40, - 0x7d, 0x41, 0x99, 0x57, 0x5c, 0xaf, 0xe6, 0xf6, 0x36, 0x1b, 0xef, 0xe8, 0x4a, 0xb2, 0x44, 0x68, - 0x59, 0x4d, 0x3f, 0x49, 0xa0, 0x66, 0x3a, 0x0f, 0x7d, 0x0b, 0x60, 0xb3, 0xf1, 0x98, 0x72, 0x1e, - 0xb2, 0x68, 0x61, 0x89, 0xe6, 0x07, 0x67, 0x8b, 0xca, 0xae, 0x4b, 0xc5, 0x60, 0x62, 0xe9, 0x36, - 0x1b, 0x1b, 0xb1, 0xbe, 0xf2, 0xe3, 0x80, 0x3b, 0x43, 0x43, 0xcc, 0x7d, 0xc2, 0xf5, 0x13, 0x62, - 0x9f, 0xbe, 0x38, 0x00, 0x55, 0xf2, 0x84, 0xd8, 0x66, 0x8a, 0x0b, 0x7d, 0x0c, 0xa0, 0x84, 0xea, - 0xf9, 0xc3, 0xe2, 0x2d, 0xd9, 0x5f, 0x25, 0xee, 0x2f, 0xf2, 0x4a, 0x5f, 0x7a, 0xa5, 0xb7, 0x27, - 0xd6, 0x73, 0x32, 0x37, 0x0b, 0x2a, 0xa5, 0x3d, 0x44, 0x2d, 0xc8, 0x5b, 0xc2, 0x0e, 0x73, 0x37, - 0xaa, 0xb9, 0xbd, 0xad, 0xe6, 0xd1, 0xd9, 0xa2, 0xd2, 0x48, 0x75, 0xa5, 0x90, 0xf6, 0x00, 0x53, - 0x2f, 0xde, 0xa8, 0xc6, 0x9a, 0xcf, 0xda, 0x8f, 0x1e, 0xbf, 0xaf, 0x28, 0x37, 0x2c, 0x61, 0xb7, - 0x87, 0xe8, 0x09, 0x68, 0x3e, 0xf3, 0x8b, 0x79, 0xd9, 0xc7, 0x9e, 0x9e, 0x79, 0x29, 0xf5, 0x76, - 0xc0, 0x58, 0xff, 0xcb, 0x7e, 0x9b, 0x71, 0x4e, 0xe4, 0x29, 0xcc, 0x30, 0xa9, 0x56, 0x81, 0x07, - 0x99, 0xe6, 0x98, 0x84, 0xfb, 0xcc, 0xe3, 0xa4, 0xf6, 0xa7, 0x06, 0xf7, 0xd2, 0x88, 0x13, 0x32, - 0x22, 0x2e, 0x96, 0x02, 0xaf, 0xf2, 0xef, 0xb2, 0x3c, 0xeb, 0xd7, 0x96, 0x47, 0x9d, 0x47, 0x7b, - 0x8d, 0xf3, 0xa0, 0x4f, 0x00, 0x14, 0xa8, 0x27, 0x66, 0xca, 0x9a, 0xea, 0x0a, 0x8a, 0x4e, 0xb4, - 0xec, 0xce, 0xcc, 0x02, 0x8f, 0x97, 0xe8, 0x2b, 0xb8, 0x9d, 0x10, 0xf4, 0xa8, 0xd7, 0x67, 0xd2, - 0xa4, 0xcd, 0xc6, 0xbb, 0x69, 0x96, 0xd4, 0x2c, 0x4c, 0xeb, 0x7a, 0x37, 0xc0, 0x1e, 0xc7, 0x76, - 0x28, 0xca, 0x33, 0xaf, 0xcf, 0xcc, 0xed, 0x25, 0x5d, 0xb8, 0x45, 0x0d, 0xd8, 0xe4, 0x23, 0xcc, - 0x07, 0xaa, 0xa9, 0xbc, 0xf4, 0xfc, 0xad, 0xb3, 0x45, 0x65, 0xbb, 0xd9, 0x3d, 0xee, 0xa8, 0x48, - 0x77, 0x66, 0x02, 0x5f, 0xae, 0xd1, 0xf7, 0xb0, 0xed, 0x44, 0x4a, 0xb3, 0xa0, 0xc7, 0xa9, 0x5b, - 0x7c, 0x43, 0x66, 0x7d, 0x78, 0xb6, 0xa8, 0x1c, 0x5e, 0xe7, 0xa6, 0x74, 0xa8, 0xeb, 0x61, 0x31, - 0x09, 0x88, 0xb9, 0xb5, 0xe4, 0xeb, 0x50, 0xb7, 0x56, 0x85, 0x72, 0xb6, 0xab, 0x4b, 0xe3, 0x7f, - 0x5f, 0x87, 0xed, 0x16, 0x77, 0x9f, 0x3a, 0xce, 0xe7, 0x93, 0x60, 0xde, 0xa1, 0xee, 0x4a, 0xbf, - 0x5b, 0x90, 0x9f, 0xe2, 0x51, 0xec, 0xf5, 0x0d, 0xae, 0xf3, 0x14, 0x8f, 0xa2, 0xe9, 0x70, 0x88, - 0xa4, 0xd3, 0x6e, 0x46, 0xe7, 0x90, 0x90, 0x6e, 0xf7, 0x92, 0xa1, 0x03, 0xcc, 0x07, 0xf2, 0x5a, - 0x14, 0x52, 0x2e, 0x7d, 0x86, 0xf9, 0x00, 0x3d, 0x07, 0x2d, 0xd4, 0x79, 0xe3, 0xa6, 0x3a, 0x87, - 0x2c, 0xb5, 0x1d, 0xf9, 0xe6, 0x25, 0xda, 0x2d, 0x55, 0xfd, 0x25, 0x07, 0xb7, 0x5b, 0xdc, 0xfd, - 0xda, 0x77, 0xb0, 0x20, 0x6d, 0xf9, 0x46, 0xa3, 0x23, 0x28, 0xe0, 0x89, 0x18, 0xb0, 0x80, 0x8a, - 0x79, 0x24, 0x6d, 0xb3, 0x78, 0xfa, 0xe2, 0xe0, 0x8e, 0x9a, 0x98, 0xa7, 0x8e, 0x13, 0x10, 0xce, - 0x3b, 0x22, 0xa0, 0x9e, 0x6b, 0x26, 0x50, 0xf4, 0x11, 0xe4, 0xa3, 0x57, 0x5e, 0xcd, 0xd8, 0x83, - 0x55, 0xa3, 0x22, 0x41, 0xcd, 0x5b, 0x2f, 0x17, 0x95, 0x35, 0x53, 0xa5, 0x3c, 0x79, 0xf3, 0xa7, - 0x7f, 0x7e, 0x7b, 0x2f, 0x21, 0xab, 0xdd, 0x87, 0x9d, 0xff, 0xf4, 0x15, 0xf7, 0xdc, 0xf8, 0x43, - 0x03, 0xad, 0xc5, 0x5d, 0x34, 0x03, 0x94, 0xf1, 0x8a, 0xef, 0xaf, 0xa8, 0x9a, 0xf9, 0xac, 0x94, - 0x1e, 0x5f, 0x07, 0x1d, 0x77, 0x80, 0x7e, 0x84, 0xb7, 0xb3, 0x1e, 0xa0, 0x83, 0x2b, 0x90, 0x25, - 0xf0, 0xd2, 0xe1, 0xb5, 0xe0, 0xcb, 0xe2, 0x3f, 0x00, 0xa4, 0x86, 0xe0, 0xe1, 0x6a, 0x92, 0x04, - 0x55, 0xda, 0xbf, 0x0a, 0x6a, 0x59, 0xa1, 0x0f, 0x5b, 0x97, 0x2e, 0xc4, 0xee, 0xea, 0xec, 0x34, - 0xae, 0xa4, 0x5f, 0x0d, 0x17, 0xd7, 0x69, 0x7e, 0xf1, 0xf2, 0xbc, 0x9c, 0x7b, 0x75, 0x5e, 0xce, - 0xfd, 0x7d, 0x5e, 0xce, 0xfd, 0x7c, 0x51, 0x5e, 0x7b, 0x75, 0x51, 0x5e, 0xfb, 0xeb, 0xa2, 0xbc, - 0xf6, 0xdd, 0xff, 0xce, 0xd7, 0x2c, 0xfd, 0xd7, 0x41, 0x5e, 0x7c, 0x2b, 0x2f, 0x7f, 0xdf, 0x1f, - 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x1c, 0xdd, 0xea, 0x26, 0x09, 0x00, 0x00, + // 982 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0x6f, 0xea, 0x36, 0x28, 0x2f, 0x0d, 0x0b, 0xa6, 0xdb, 0x66, 0x23, 0x6d, 0x52, 0x85, 0x55, + 0x29, 0xab, 0xd6, 0x26, 0xd9, 0x6d, 0x05, 0x8b, 0x84, 0xb4, 0x4e, 0x91, 0x28, 0x4b, 0x44, 0x70, + 0xbc, 0x08, 0x71, 0x20, 0x8c, 0xed, 0x89, 0x63, 0x25, 0xf1, 0x58, 0x9e, 0x49, 0x94, 0x88, 0x1b, + 0x9f, 0x80, 0x13, 0x17, 0x24, 0x3e, 0x03, 0x48, 0x7b, 0xe2, 0x13, 0xec, 0x71, 0xb5, 0x27, 0xd4, + 0x43, 0x84, 0xda, 0x03, 0x5f, 0x03, 0xd9, 0x1e, 0xff, 0x49, 0x95, 0x40, 0x42, 0x4f, 0xf6, 0xf8, + 0xfd, 0xde, 0xef, 0xbd, 0xf9, 0xbd, 0x79, 0x6f, 0x0c, 0x65, 0x1d, 0xe9, 0xd3, 0x01, 0x71, 0x64, + 0x9d, 0x19, 0x94, 0xa1, 0xbe, 0xed, 0x58, 0xf2, 0xb8, 0x26, 0xb3, 0x89, 0xe4, 0x7a, 0x84, 0x11, + 0xf1, 0x2e, 0xb7, 0x4b, 0x89, 0x5d, 0x1a, 0xd7, 0x4a, 0xbb, 0x16, 0xb1, 0x48, 0x80, 0x90, 0xfd, + 0xb7, 0x10, 0x5c, 0xba, 0x67, 0x10, 0x3a, 0x24, 0xb4, 0x13, 0x1a, 0xc2, 0x05, 0x37, 0xed, 0x87, + 0x2b, 0x79, 0x48, 0x03, 0xfe, 0x21, 0xb5, 0xb8, 0xa1, 0xca, 0x0d, 0x86, 0x37, 0x75, 0x19, 0x91, + 0x29, 0x36, 0xdc, 0xfa, 0xe9, 0x59, 0xbf, 0x26, 0xf7, 0xf1, 0x34, 0x72, 0xae, 0x2e, 0x4e, 0xd2, + 0x45, 0x1e, 0x1a, 0x46, 0x98, 0xc3, 0xc5, 0x98, 0x54, 0xda, 0x21, 0xee, 0x38, 0x85, 0x33, 0x7a, + 0xd8, 0xe8, 0xbb, 0xc4, 0x76, 0x18, 0x87, 0x26, 0x1f, 0x38, 0xfa, 0x01, 0xcf, 0x2e, 0x61, 0xd4, + 0x31, 0x43, 0x35, 0x79, 0x8e, 0xb3, 0xfa, 0x8b, 0x00, 0x77, 0x9b, 0xd4, 0x6a, 0x78, 0x18, 0x31, + 0xac, 0x68, 0x8d, 0xaf, 0xd1, 0xc0, 0x36, 0x11, 0x23, 0x9e, 0xb8, 0x07, 0x59, 0x6a, 0x5b, 0x0e, + 0xf6, 0x8a, 0x99, 0x83, 0xcc, 0x51, 0x4e, 0xe5, 0x2b, 0xf1, 0x53, 0xc8, 0x9b, 0x98, 0x1a, 0x9e, + 0xed, 0x32, 0x9b, 0x38, 0xc5, 0xcd, 0x83, 0xcc, 0x51, 0xbe, 0xfe, 0xae, 0xc4, 0x25, 0x4b, 0x84, + 0x0e, 0xa2, 0x49, 0xe7, 0x09, 0x54, 0x4d, 0xfb, 0x89, 0xdf, 0x00, 0x18, 0x64, 0x38, 0xb4, 0x29, + 0xf5, 0x59, 0x04, 0x3f, 0x84, 0xf2, 0xe1, 0xe5, 0xac, 0x72, 0x68, 0xd9, 0xac, 0x37, 0xd2, 0x25, + 0x83, 0x0c, 0xe5, 0x48, 0xdf, 0xe0, 0x71, 0x42, 0xcd, 0xbe, 0xcc, 0xa6, 0x2e, 0xa6, 0xd2, 0x39, + 0x36, 0x5e, 0xbf, 0x38, 0x01, 0x1e, 0xf2, 0x1c, 0x1b, 0x6a, 0x8a, 0x4b, 0xfc, 0x04, 0x80, 0x0b, + 0xd5, 0x71, 0xfb, 0xc5, 0xad, 0x20, 0xbf, 0x4a, 0x94, 0x5f, 0x58, 0x2b, 0x29, 0xae, 0x95, 0xd4, + 0x1a, 0xe9, 0xcf, 0xf0, 0x54, 0xcd, 0x71, 0x97, 0x56, 0x5f, 0x6c, 0x42, 0x56, 0x67, 0x86, 0xef, + 0xbb, 0x7d, 0x90, 0x39, 0xda, 0x51, 0xce, 0x2e, 0x67, 0x95, 0x7a, 0x2a, 0x2b, 0x8e, 0x34, 0x7a, + 0xc8, 0x76, 0xa2, 0x05, 0x4f, 0x4c, 0xb9, 0x68, 0x3d, 0x7a, 0xfc, 0x01, 0xa7, 0xdc, 0xd6, 0x99, + 0xd1, 0xea, 0x8b, 0x4f, 0x40, 0x70, 0x89, 0x5b, 0xcc, 0x06, 0x79, 0x1c, 0x49, 0x0b, 0x0f, 0xa5, + 0xd4, 0xf2, 0x08, 0xe9, 0x7e, 0xd9, 0x6d, 0x11, 0x4a, 0x71, 0xb0, 0x0b, 0xd5, 0x77, 0xaa, 0x56, + 0xe0, 0xfe, 0xc2, 0xe2, 0xa8, 0x98, 0xba, 0xc4, 0xa1, 0xb8, 0x3a, 0x13, 0x60, 0x2f, 0x8d, 0x38, + 0xc7, 0x03, 0x6c, 0xa1, 0x40, 0xe0, 0x65, 0xf5, 0x9b, 0x97, 0x67, 0x73, 0x6d, 0x79, 0xf8, 0x7e, + 0x84, 0xff, 0xb1, 0x1f, 0xf1, 0x02, 0x80, 0x83, 0x3a, 0x6c, 0xc2, 0x4b, 0xf3, 0x70, 0x09, 0x85, + 0x12, 0x7e, 0x55, 0xb4, 0x86, 0x86, 0x5c, 0x8f, 0x10, 0xa6, 0x4d, 0xd4, 0x1c, 0xb7, 0x6b, 0x13, + 0xf1, 0x2b, 0xb8, 0x93, 0x50, 0x75, 0x6c, 0xa7, 0x4b, 0x82, 0x72, 0xe5, 0xeb, 0xef, 0xa7, 0xf9, + 0x52, 0x5d, 0x31, 0xae, 0x49, 0x9a, 0x87, 0x1c, 0x8a, 0x0c, 0x5f, 0x9e, 0x0b, 0xa7, 0x4b, 0xd4, + 0x42, 0x4c, 0xe7, 0x2f, 0xc5, 0x3a, 0xe4, 0xe9, 0x00, 0xd1, 0x1e, 0x4f, 0x2f, 0x1b, 0x54, 0xff, + 0xed, 0xcb, 0x59, 0xa5, 0xa0, 0x68, 0x8d, 0x36, 0xb7, 0x68, 0x13, 0x15, 0x68, 0xfc, 0x2e, 0x7e, + 0x07, 0x05, 0x33, 0xd4, 0x9c, 0x78, 0x1d, 0x6a, 0x5b, 0xc5, 0x37, 0x02, 0xaf, 0x8f, 0x2e, 0x67, + 0x95, 0xd3, 0x75, 0xce, 0x4c, 0xdb, 0xb6, 0x1c, 0xc4, 0x46, 0x1e, 0x56, 0x77, 0x62, 0xbe, 0xb6, + 0x6d, 0x55, 0x0f, 0xa0, 0xbc, 0xb8, 0xbe, 0xf1, 0x11, 0xf8, 0x75, 0x13, 0xde, 0x6a, 0x52, 0x4b, + 0xd1, 0x1a, 0xcf, 0x1d, 0xee, 0x8a, 0x97, 0x16, 0xbf, 0x09, 0x3b, 0x23, 0x47, 0x27, 0x8e, 0xc9, + 0xf7, 0xb8, 0xb9, 0x76, 0x09, 0xf2, 0xb1, 0xbf, 0x36, 0xb9, 0xa9, 0x98, 0xb0, 0x8a, 0x62, 0x04, + 0xf6, 0x52, 0x8a, 0x45, 0xde, 0xbe, 0x74, 0x5b, 0xb7, 0x95, 0x6e, 0x37, 0x91, 0x8e, 0xf3, 0xfa, + 0x12, 0x96, 0xa0, 0x78, 0x53, 0x9f, 0x58, 0xbc, 0x3f, 0x36, 0xa1, 0xd0, 0xa4, 0xd6, 0x53, 0xd3, + 0xfc, 0x7c, 0xe4, 0x4d, 0xdb, 0xb6, 0xf5, 0x2f, 0xca, 0x65, 0xc7, 0x68, 0x10, 0xb5, 0xcc, 0x2d, + 0xa6, 0xc2, 0x18, 0x0d, 0xc2, 0x21, 0x63, 0xe2, 0x80, 0x4e, 0xb8, 0x1d, 0x9d, 0x89, 0x7d, 0xba, + 0xc3, 0xb9, 0x6e, 0xe8, 0x21, 0xda, 0x0b, 0xd4, 0xcc, 0xa5, 0x8e, 0xf8, 0x67, 0x88, 0xf6, 0xc4, + 0x67, 0x20, 0xf8, 0x4a, 0x6f, 0xdf, 0x56, 0x69, 0x9f, 0xa5, 0xba, 0x1f, 0x5c, 0x1d, 0x89, 0x76, + 0xb1, 0xaa, 0x3f, 0x67, 0xe0, 0x4e, 0x93, 0x5a, 0xcf, 0x5d, 0x13, 0x31, 0xdc, 0x0a, 0xae, 0x3a, + 0xf1, 0x0c, 0x72, 0x68, 0xc4, 0x7a, 0xc4, 0xb3, 0xd9, 0x34, 0x94, 0x56, 0x29, 0xbe, 0x7e, 0x71, + 0xb2, 0xcb, 0x07, 0xcf, 0x53, 0xd3, 0xf4, 0x30, 0xa5, 0x6d, 0xe6, 0xd9, 0x8e, 0xa5, 0x26, 0x50, + 0xf1, 0x63, 0xc8, 0x86, 0x97, 0x25, 0x3f, 0xab, 0xf7, 0x97, 0x4d, 0x9c, 0x00, 0xa4, 0x6c, 0xbd, + 0x9c, 0x55, 0x36, 0x54, 0xee, 0xf2, 0xe4, 0xcd, 0x1f, 0xff, 0xfe, 0xed, 0x61, 0x42, 0x56, 0xbd, + 0x07, 0xfb, 0x37, 0xf2, 0x8a, 0x72, 0xae, 0xff, 0xbe, 0x05, 0x42, 0x93, 0x5a, 0xe2, 0x04, 0xc4, + 0x05, 0x97, 0xe1, 0xf1, 0x92, 0xa8, 0x0b, 0xa7, 0x73, 0xe9, 0xf1, 0x3a, 0xe8, 0x28, 0x03, 0xf1, + 0x07, 0x78, 0x67, 0xd1, 0x1c, 0x3f, 0x59, 0x81, 0x2c, 0x81, 0x97, 0x4e, 0xd7, 0x82, 0xc7, 0xc1, + 0x6d, 0x28, 0xcc, 0x4f, 0x90, 0xf7, 0x96, 0xf3, 0xcc, 0x01, 0x4b, 0xf2, 0x8a, 0xc0, 0x38, 0xd4, + 0xf7, 0x00, 0xa9, 0x7e, 0x7b, 0xb0, 0xdc, 0x3d, 0x41, 0x95, 0x8e, 0x57, 0x41, 0xc5, 0x11, 0xba, + 0xb0, 0x33, 0x77, 0xf6, 0x0e, 0x97, 0x7b, 0xa7, 0x71, 0x25, 0x69, 0x35, 0x5c, 0x14, 0x47, 0xf9, + 0xe2, 0xe5, 0x55, 0x39, 0xf3, 0xea, 0xaa, 0x9c, 0xf9, 0xeb, 0xaa, 0x9c, 0xf9, 0xe9, 0xba, 0xbc, + 0xf1, 0xea, 0xba, 0xbc, 0xf1, 0xe7, 0x75, 0x79, 0xe3, 0xdb, 0xff, 0x6c, 0xe5, 0x49, 0xfa, 0x67, + 0x2f, 0xe8, 0x31, 0x3d, 0x1b, 0xfc, 0x91, 0x3d, 0xfa, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3a, 0x1b, + 0x89, 0x0a, 0xd8, 0x0a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -562,6 +667,8 @@ type MsgClient interface { CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) + // BtcUndelegate undelegates funds from exsitng btc delegation + BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) // AddJurySig handles a signature from jury AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) // UpdateParams updates the btcstaking module parameters. @@ -594,6 +701,15 @@ func (c *msgClient) CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDel return out, nil } +func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) { + out := new(MsgBTCUndelegateResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/BTCUndelegate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) { out := new(MsgAddJurySigResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddJurySig", in, out, opts...) @@ -618,6 +734,8 @@ type MsgServer interface { CreateBTCValidator(context.Context, *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) + // BtcUndelegate undelegates funds from exsitng btc delegation + BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) // AddJurySig handles a signature from jury AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) // UpdateParams updates the btcstaking module parameters. @@ -634,6 +752,9 @@ func (*UnimplementedMsgServer) CreateBTCValidator(ctx context.Context, req *MsgC func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateBTCDelegation not implemented") } +func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCUndelegate not implemented") +} func (*UnimplementedMsgServer) AddJurySig(ctx context.Context, req *MsgAddJurySig) (*MsgAddJurySigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddJurySig not implemented") } @@ -681,6 +802,24 @@ func _Msg_CreateBTCDelegation_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgBTCUndelegate) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).BTCUndelegate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/BTCUndelegate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).BTCUndelegate(ctx, req.(*MsgBTCUndelegate)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_AddJurySig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgAddJurySig) if err := dec(in); err != nil { @@ -729,6 +868,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "CreateBTCDelegation", Handler: _Msg_CreateBTCDelegation_Handler, }, + { + MethodName: "BTCUndelegate", + Handler: _Msg_BTCUndelegate_Handler, + }, { MethodName: "AddJurySig", Handler: _Msg_AddJurySig_Handler, @@ -980,6 +1123,95 @@ func (m *MsgCreateBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } +func (m *MsgBTCUndelegate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgBTCUndelegate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DelegatorSlashingSig != nil { + { + size := m.DelegatorSlashingSig.Size() + i -= size + if _, err := m.DelegatorSlashingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.SlashingTx != nil { + { + size := m.SlashingTx.Size() + i -= size + if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.UnbondingTx != nil { + { + size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgBTCUndelegateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgBTCUndelegateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgBTCUndelegateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgAddJurySig) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1238,6 +1470,40 @@ func (m *MsgCreateBTCDelegationResponse) Size() (n int) { return n } +func (m *MsgBTCUndelegate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.UnbondingTx != nil { + l = m.UnbondingTx.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.SlashingTx != nil { + l = m.SlashingTx.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.DelegatorSlashingSig != nil { + l = m.DelegatorSlashingSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgBTCUndelegateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgAddJurySig) Size() (n int) { if m == nil { return 0 @@ -1780,7 +2046,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.StakingTx == nil { - m.StakingTx = &StakingTx{} + m.StakingTx = &BabylonBTCTaprootTx{} } if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1963,6 +2229,244 @@ func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgBTCUndelegate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgBTCUndelegate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UnbondingTx == nil { + m.UnbondingTx = &BabylonBTCTaprootTx{} + } + if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorSlashingSig = &v + if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgBTCUndelegateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgBTCUndelegateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go index ab1254f4c..ac33196e6 100644 --- a/x/btcstaking/types/types.go +++ b/x/btcstaking/types/types.go @@ -1 +1,20 @@ package types + +import ( + "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" +) + +type PublicKeyInfo struct { + StakerKey *bbn.BIP340PubKey + ValidatorKey *bbn.BIP340PubKey + JuryKey *bbn.BIP340PubKey +} + +func KeyDataFromScript(scriptData *btcstaking.StakingScriptData) *PublicKeyInfo { + return &PublicKeyInfo{ + StakerKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.StakerKey), + ValidatorKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.ValidatorKey), + JuryKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.JuryKey), + } +} From c2c3cbfabe5ced57f0028a34355e26e8ee45e3cd Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:16:42 -0400 Subject: [PATCH 070/202] fix typo (#70) --- proto/babylon/finality/v1/query.proto | 6 +- x/finality/types/query.pb.go | 130 +++++++++++++------------- x/finality/types/query.pb.gw.go | 2 +- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index f1fb38d7b..0173a0217 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -25,7 +25,7 @@ service Query { rpc Block(QueryBlockRequest) returns (QueryBlockResponse) { option (google.api.http).get = "/babylon/finality/v1/blocks/{height}"; } - + // ListBlocks is a range query for blocks at a given status rpc ListBlocks(QueryListBlocksRequest) returns (QueryListBlocksResponse) { option (google.api.http).get = "/babylon/finality/v1/blocks"; @@ -43,7 +43,7 @@ service Query { // ListEvidences queries is a range query for evidences rpc ListEvidences(QueryListEvidencesRequest) returns (QueryListEvidencesResponse) { - option (google.api.http).get = "/babylon/finality/v1/vidences"; + option (google.api.http).get = "/babylon/finality/v1/evidences"; } } @@ -170,4 +170,4 @@ message QueryListEvidencesResponse { // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; -} \ No newline at end of file +} diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index f4acb6468..3aedb4057 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -775,72 +775,72 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 1029 bytes of a gzipped FileDescriptorProto + // 1031 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x4f, 0x1b, 0x47, - 0x14, 0xf6, 0x98, 0xe0, 0xc0, 0x03, 0x52, 0x32, 0x71, 0x53, 0xea, 0x14, 0x1b, 0x96, 0x06, 0x28, - 0x44, 0xbb, 0xc1, 0xa4, 0x69, 0xda, 0x28, 0x42, 0x58, 0x85, 0x86, 0x26, 0x10, 0x77, 0x91, 0x2a, - 0x35, 0x17, 0x6b, 0xd6, 0x9e, 0xda, 0x2b, 0xd6, 0x3b, 0x1b, 0xef, 0x78, 0x85, 0x15, 0x45, 0xaa, - 0x7a, 0xc8, 0xa9, 0x52, 0x2b, 0xf5, 0xd0, 0x53, 0x0e, 0xcd, 0xa9, 0x7f, 0x4a, 0x8e, 0x48, 0xbd, - 0x54, 0x1c, 0x50, 0x05, 0xf9, 0x43, 0xaa, 0x9d, 0x99, 0xb5, 0x31, 0x5d, 0x63, 0x27, 0xe2, 0xe6, - 0x9d, 0xf9, 0xde, 0x7b, 0xdf, 0xfb, 0xde, 0x8f, 0x31, 0xe4, 0x2c, 0x62, 0xb5, 0x1c, 0xe6, 0x1a, - 0x3f, 0xda, 0x2e, 0x71, 0x6c, 0xde, 0x32, 0x82, 0x15, 0xe3, 0x59, 0x93, 0x36, 0x5a, 0xba, 0xd7, - 0x60, 0x9c, 0xe1, 0x6b, 0x0a, 0xa0, 0x47, 0x00, 0x3d, 0x58, 0xc9, 0xa4, 0xab, 0xac, 0xca, 0xc4, - 0xbd, 0x11, 0xfe, 0x92, 0xd0, 0xcc, 0x27, 0x55, 0xc6, 0xaa, 0x0e, 0x35, 0x88, 0x67, 0x1b, 0xc4, - 0x75, 0x19, 0x27, 0xdc, 0x66, 0xae, 0xaf, 0x6e, 0x97, 0xca, 0xcc, 0xaf, 0x33, 0xdf, 0xb0, 0x88, - 0x4f, 0x65, 0x04, 0x23, 0x58, 0xb1, 0x28, 0x27, 0x2b, 0x86, 0x47, 0xaa, 0xb6, 0x2b, 0xc0, 0x0a, - 0x3b, 0x13, 0xc7, 0xca, 0x23, 0x0d, 0x52, 0x8f, 0xbc, 0x69, 0x71, 0x88, 0x36, 0x45, 0x81, 0xd1, - 0xd2, 0x80, 0xbf, 0x0b, 0xe3, 0x14, 0x85, 0xa1, 0x49, 0x9f, 0x35, 0xa9, 0xcf, 0xb5, 0x22, 0x5c, - 0xeb, 0x3a, 0xf5, 0x3d, 0xe6, 0xfa, 0x14, 0x7f, 0x09, 0x29, 0x19, 0x60, 0x0a, 0xcd, 0xa0, 0xc5, - 0xb1, 0xfc, 0x0d, 0x3d, 0x26, 0x71, 0x5d, 0x1a, 0x15, 0x2e, 0xbd, 0x39, 0xca, 0x25, 0x4c, 0x65, - 0xa0, 0xfd, 0x8a, 0x60, 0x46, 0xb8, 0x7c, 0x6c, 0xfb, 0xbc, 0xd8, 0xb4, 0x1c, 0xbb, 0x6c, 0x12, - 0xb7, 0xc2, 0xea, 0x2e, 0xf5, 0xa3, 0xb0, 0x78, 0x0e, 0xae, 0x04, 0xc4, 0x29, 0x59, 0xbc, 0x5c, - 0xf2, 0xf6, 0x4a, 0x35, 0xba, 0x2f, 0xe2, 0x8c, 0x9a, 0x63, 0x01, 0x71, 0x0a, 0xbc, 0x5c, 0xdc, - 0x7b, 0x48, 0xf7, 0xf1, 0x26, 0x40, 0x47, 0x8b, 0xa9, 0xa4, 0x20, 0x32, 0xaf, 0x4b, 0xe1, 0xf4, - 0x50, 0x38, 0x5d, 0x96, 0x46, 0x09, 0xa7, 0x17, 0x49, 0x95, 0xaa, 0x00, 0xe6, 0x29, 0x4b, 0xed, - 0x20, 0x09, 0xb3, 0xe7, 0x30, 0x52, 0x29, 0xbf, 0x46, 0x30, 0xee, 0x35, 0xad, 0x52, 0x83, 0xb8, - 0x95, 0x52, 0x9d, 0x78, 0x53, 0x68, 0x66, 0x68, 0x71, 0x2c, 0xbf, 0x19, 0x9b, 0x79, 0x5f, 0x77, - 0x7a, 0xb1, 0x69, 0x85, 0xa7, 0xdb, 0xc4, 0xdb, 0x70, 0x79, 0xa3, 0x55, 0xb8, 0x77, 0x78, 0x94, - 0xbb, 0x53, 0xb5, 0x79, 0xad, 0x69, 0xe9, 0x65, 0x56, 0x37, 0x94, 0xd7, 0x72, 0x8d, 0xd8, 0x6e, - 0xf4, 0x61, 0xf0, 0x96, 0x47, 0x7d, 0x7d, 0xb7, 0x5c, 0x73, 0x59, 0xa3, 0xa1, 0x3c, 0x98, 0xe0, - 0xb5, 0x5d, 0xe1, 0x6f, 0x62, 0x24, 0x59, 0xe8, 0x2b, 0x89, 0xa4, 0x74, 0x5a, 0x93, 0xcc, 0x03, - 0xf8, 0xe0, 0x0c, 0x43, 0x3c, 0x09, 0x43, 0x7b, 0xb4, 0x25, 0x0a, 0x71, 0xc9, 0x0c, 0x7f, 0xe2, - 0x34, 0x0c, 0x07, 0xc4, 0x69, 0x52, 0x11, 0x68, 0xdc, 0x94, 0x1f, 0x5f, 0x25, 0xef, 0x21, 0x6d, - 0x19, 0xae, 0x0a, 0x09, 0x0a, 0x0e, 0x2b, 0xef, 0x45, 0x45, 0xbd, 0x0e, 0xa9, 0x1a, 0xb5, 0xab, - 0x35, 0xae, 0x7c, 0xa8, 0x2f, 0x6d, 0x5b, 0x75, 0x9e, 0x02, 0x2b, 0xbd, 0xbf, 0x80, 0x61, 0x2b, - 0x3c, 0x50, 0x1d, 0x36, 0x1b, 0xab, 0xf3, 0x96, 0x5b, 0xa1, 0xfb, 0xb4, 0x22, 0x2d, 0x25, 0x5e, - 0xfb, 0x13, 0xc1, 0xf5, 0xb6, 0xfe, 0xe2, 0xa6, 0xdd, 0x56, 0x6b, 0x90, 0xf2, 0x39, 0xe1, 0x4d, - 0xd9, 0xb6, 0x57, 0xf2, 0x0b, 0x3d, 0x8b, 0x67, 0x2b, 0xa7, 0xbb, 0x02, 0x6e, 0x2a, 0xb3, 0x0b, - 0x6b, 0xb9, 0x57, 0x08, 0x3e, 0xfa, 0x1f, 0xc7, 0xce, 0x6c, 0x89, 0x44, 0x7c, 0xd5, 0x61, 0x03, - 0x64, 0xae, 0x0c, 0x2e, 0xac, 0xfc, 0xda, 0x2a, 0x7c, 0x2c, 0xe8, 0x7d, 0xcf, 0x38, 0xf5, 0xd7, - 0xf9, 0x43, 0x51, 0xa8, 0x7e, 0x75, 0xac, 0x43, 0x26, 0xce, 0x48, 0xa5, 0xf5, 0x04, 0x2e, 0xcb, - 0x71, 0x96, 0x79, 0x8d, 0x17, 0xee, 0x1e, 0x1e, 0xe5, 0xf2, 0x83, 0x75, 0x7c, 0x61, 0xab, 0xb8, - 0x7a, 0xe7, 0x76, 0xb1, 0x69, 0x3d, 0xa2, 0x2d, 0x33, 0x65, 0x85, 0x0b, 0xc0, 0xd7, 0xee, 0x43, - 0x5a, 0x84, 0xdb, 0x08, 0xec, 0x0a, 0x75, 0xcb, 0xf4, 0x5d, 0x76, 0x87, 0x66, 0xc2, 0x87, 0x67, - 0x8c, 0xdb, 0xea, 0x8f, 0x50, 0x75, 0xa6, 0x3a, 0x6f, 0x3a, 0x56, 0xff, 0xb6, 0x61, 0x1b, 0xae, - 0xbd, 0x44, 0x4a, 0xb5, 0xb0, 0xa8, 0xd1, 0x7d, 0xbb, 0xf7, 0x66, 0x61, 0xdc, 0xe7, 0xa4, 0xc1, - 0x4b, 0x5d, 0xda, 0x8d, 0x89, 0x33, 0x29, 0xd5, 0x85, 0x75, 0xd7, 0x6b, 0xa4, 0x2a, 0x71, 0x86, - 0x88, 0x4a, 0xf1, 0x3e, 0x8c, 0x46, 0x9c, 0xa3, 0x1e, 0xeb, 0x93, 0x63, 0x07, 0x7f, 0x61, 0x2d, - 0xb6, 0xb4, 0x26, 0xa7, 0xbe, 0x7b, 0xd0, 0xf0, 0x55, 0x98, 0xd8, 0x79, 0xb2, 0x53, 0xda, 0xdc, - 0xda, 0x59, 0x7f, 0xbc, 0xf5, 0x74, 0xe3, 0xeb, 0xc9, 0x04, 0x9e, 0x80, 0xd1, 0xce, 0x27, 0xc2, - 0x97, 0x61, 0x68, 0x7d, 0xe7, 0x87, 0xc9, 0x64, 0xfe, 0xed, 0x08, 0x0c, 0x8b, 0x2c, 0xf1, 0x4f, - 0x08, 0x52, 0xf2, 0xad, 0xc1, 0xbd, 0x27, 0xba, 0xfb, 0x61, 0xcb, 0x2c, 0xf6, 0x07, 0x4a, 0xd2, - 0xda, 0xdc, 0xcf, 0x7f, 0xbf, 0xfd, 0x3d, 0x39, 0x8d, 0x6f, 0x18, 0xbd, 0xdf, 0x59, 0x7c, 0x88, - 0x20, 0x1d, 0xb7, 0xef, 0xf1, 0xe7, 0xef, 0xfa, 0x3e, 0x48, 0x7a, 0x77, 0xdf, 0xef, 0x59, 0xd1, - 0x76, 0x05, 0xd9, 0x6d, 0xfc, 0x28, 0x96, 0x6c, 0x38, 0x13, 0x01, 0x71, 0xec, 0x0a, 0xe1, 0xac, - 0xe1, 0x1b, 0xcf, 0xbb, 0xe7, 0xe4, 0x85, 0xe1, 0x09, 0xb7, 0xe2, 0x89, 0x93, 0x7e, 0x4b, 0x8e, - 0xed, 0x73, 0xfc, 0x12, 0xc1, 0xb0, 0x28, 0x12, 0x9e, 0xef, 0x4d, 0xeb, 0xf4, 0xaa, 0xcf, 0x2c, - 0xf4, 0xc5, 0x29, 0xbe, 0xb7, 0x04, 0xdf, 0x79, 0xfc, 0x69, 0x3c, 0x5f, 0xb1, 0xd6, 0x8c, 0xe7, - 0x72, 0x64, 0x5e, 0xe0, 0x5f, 0x10, 0x40, 0x67, 0x63, 0xe2, 0xe5, 0xf3, 0x45, 0xea, 0xda, 0xfd, - 0x99, 0x5b, 0x83, 0x81, 0x07, 0x2a, 0xba, 0x5a, 0xb7, 0xaf, 0x10, 0x4c, 0x74, 0x2d, 0x3b, 0xac, - 0xf7, 0x0e, 0x12, 0xb7, 0x4a, 0x33, 0xc6, 0xc0, 0x78, 0xc5, 0x6b, 0x59, 0xf0, 0xba, 0x89, 0xe7, - 0x62, 0x79, 0x05, 0xa1, 0x4d, 0x47, 0xae, 0xbf, 0x10, 0x8c, 0x44, 0x33, 0x8c, 0x3f, 0xeb, 0x1d, - 0xea, 0xcc, 0x06, 0xcd, 0x2c, 0x0d, 0x02, 0x55, 0x84, 0x36, 0x04, 0xa1, 0x35, 0xfc, 0xe0, 0xbd, - 0x1a, 0x2e, 0xda, 0x2b, 0xf8, 0x0f, 0x04, 0x13, 0x5d, 0xdb, 0xea, 0x3c, 0x29, 0xe3, 0xf6, 0xeb, - 0x79, 0x52, 0xc6, 0xae, 0x41, 0xed, 0xa6, 0x60, 0x9e, 0xc3, 0xd3, 0xf1, 0x52, 0x2a, 0x78, 0xe1, - 0xdb, 0x37, 0xc7, 0x59, 0x74, 0x70, 0x9c, 0x45, 0xff, 0x1e, 0x67, 0xd1, 0x6f, 0x27, 0xd9, 0xc4, - 0xc1, 0x49, 0x36, 0xf1, 0xcf, 0x49, 0x36, 0xf1, 0xf4, 0x76, 0xbf, 0xc7, 0x6b, 0xbf, 0xe3, 0x51, - 0xbc, 0x63, 0x56, 0x4a, 0xfc, 0xd5, 0x5e, 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xf7, 0x84, - 0x5e, 0x48, 0x0c, 0x00, 0x00, + 0x14, 0xf6, 0x98, 0xe0, 0xc0, 0x03, 0x52, 0x32, 0x71, 0x53, 0xea, 0x34, 0xc6, 0x2c, 0x2d, 0x50, + 0x88, 0x76, 0x83, 0x49, 0xd3, 0xb4, 0x51, 0x84, 0xb0, 0x0a, 0x0d, 0x4d, 0x20, 0xee, 0x22, 0x55, + 0x6a, 0x2e, 0xd6, 0xac, 0x3d, 0xb5, 0x57, 0xac, 0x77, 0x36, 0xde, 0xb1, 0x85, 0x15, 0x45, 0xaa, + 0x7a, 0xc8, 0xa9, 0x52, 0x2b, 0xf5, 0xd2, 0x4b, 0x0e, 0xcd, 0xa9, 0x7f, 0x4a, 0x8e, 0x48, 0xbd, + 0x54, 0x1c, 0x50, 0x05, 0xed, 0xff, 0x51, 0xed, 0xcc, 0xec, 0x1a, 0x93, 0x35, 0x76, 0x10, 0x37, + 0xef, 0xcc, 0xf7, 0xde, 0xfb, 0xde, 0xf7, 0x7e, 0x8c, 0x61, 0xda, 0x22, 0x56, 0xdb, 0x61, 0xae, + 0xf1, 0x83, 0xed, 0x12, 0xc7, 0xe6, 0x6d, 0xa3, 0xb5, 0x6c, 0x3c, 0x6b, 0xd2, 0x46, 0x5b, 0xf7, + 0x1a, 0x8c, 0x33, 0x7c, 0x4d, 0x01, 0xf4, 0x10, 0xa0, 0xb7, 0x96, 0x33, 0xe9, 0x2a, 0xab, 0x32, + 0x71, 0x6f, 0x04, 0xbf, 0x24, 0x34, 0xf3, 0x51, 0x95, 0xb1, 0xaa, 0x43, 0x0d, 0xe2, 0xd9, 0x06, + 0x71, 0x5d, 0xc6, 0x09, 0xb7, 0x99, 0xeb, 0xab, 0xdb, 0xc5, 0x32, 0xf3, 0xeb, 0xcc, 0x37, 0x2c, + 0xe2, 0x53, 0x19, 0xc1, 0x68, 0x2d, 0x5b, 0x94, 0x93, 0x65, 0xc3, 0x23, 0x55, 0xdb, 0x15, 0x60, + 0x85, 0xcd, 0xc5, 0xb1, 0xf2, 0x48, 0x83, 0xd4, 0x43, 0x6f, 0x5a, 0x1c, 0x22, 0xa2, 0x28, 0x30, + 0x5a, 0x1a, 0xf0, 0xb7, 0x41, 0x9c, 0xa2, 0x30, 0x34, 0xe9, 0xb3, 0x26, 0xf5, 0xb9, 0x56, 0x84, + 0x6b, 0x5d, 0xa7, 0xbe, 0xc7, 0x5c, 0x9f, 0xe2, 0x2f, 0x20, 0x25, 0x03, 0x4c, 0xa1, 0x1c, 0x5a, + 0x18, 0xcb, 0xdf, 0xd0, 0x63, 0x12, 0xd7, 0xa5, 0x51, 0xe1, 0xd2, 0x9b, 0xc3, 0xe9, 0x84, 0xa9, + 0x0c, 0xb4, 0x5f, 0x10, 0xe4, 0x84, 0xcb, 0xc7, 0xb6, 0xcf, 0x8b, 0x4d, 0xcb, 0xb1, 0xcb, 0x26, + 0x71, 0x2b, 0xac, 0xee, 0x52, 0x3f, 0x0c, 0x8b, 0x67, 0xe1, 0x4a, 0x8b, 0x38, 0x25, 0x8b, 0x97, + 0x4b, 0xde, 0x6e, 0xa9, 0x46, 0xf7, 0x44, 0x9c, 0x51, 0x73, 0xac, 0x45, 0x9c, 0x02, 0x2f, 0x17, + 0x77, 0x1f, 0xd2, 0x3d, 0xbc, 0x01, 0xd0, 0xd1, 0x62, 0x2a, 0x29, 0x88, 0xcc, 0xe9, 0x52, 0x38, + 0x3d, 0x10, 0x4e, 0x97, 0xa5, 0x51, 0xc2, 0xe9, 0x45, 0x52, 0xa5, 0x2a, 0x80, 0x79, 0xc2, 0x52, + 0xdb, 0x4f, 0xc2, 0xcc, 0x19, 0x8c, 0x54, 0xca, 0xaf, 0x11, 0x8c, 0x7b, 0x4d, 0xab, 0xd4, 0x20, + 0x6e, 0xa5, 0x54, 0x27, 0xde, 0x14, 0xca, 0x0d, 0x2d, 0x8c, 0xe5, 0x37, 0x62, 0x33, 0xef, 0xeb, + 0x4e, 0x2f, 0x36, 0xad, 0xe0, 0x74, 0x8b, 0x78, 0xeb, 0x2e, 0x6f, 0xb4, 0x0b, 0xf7, 0x0e, 0x0e, + 0xa7, 0xef, 0x54, 0x6d, 0x5e, 0x6b, 0x5a, 0x7a, 0x99, 0xd5, 0x0d, 0xe5, 0xb5, 0x5c, 0x23, 0xb6, + 0x1b, 0x7e, 0x18, 0xbc, 0xed, 0x51, 0x5f, 0xdf, 0x29, 0xd7, 0x5c, 0xd6, 0x68, 0x28, 0x0f, 0x26, + 0x78, 0x91, 0x2b, 0xfc, 0x75, 0x8c, 0x24, 0xf3, 0x7d, 0x25, 0x91, 0x94, 0x4e, 0x6a, 0x92, 0x79, + 0x00, 0xef, 0x9d, 0x62, 0x88, 0x27, 0x61, 0x68, 0x97, 0xb6, 0x45, 0x21, 0x2e, 0x99, 0xc1, 0x4f, + 0x9c, 0x86, 0xe1, 0x16, 0x71, 0x9a, 0x54, 0x04, 0x1a, 0x37, 0xe5, 0xc7, 0x97, 0xc9, 0x7b, 0x48, + 0x5b, 0x82, 0xab, 0x42, 0x82, 0x82, 0xc3, 0xca, 0xbb, 0x61, 0x51, 0xaf, 0x43, 0xaa, 0x46, 0xed, + 0x6a, 0x8d, 0x2b, 0x1f, 0xea, 0x4b, 0xdb, 0x52, 0x9d, 0xa7, 0xc0, 0x4a, 0xef, 0xcf, 0x61, 0xd8, + 0x0a, 0x0e, 0x54, 0x87, 0xcd, 0xc4, 0xea, 0xbc, 0xe9, 0x56, 0xe8, 0x1e, 0xad, 0x48, 0x4b, 0x89, + 0xd7, 0xfe, 0x40, 0x70, 0x3d, 0xd2, 0x5f, 0xdc, 0x44, 0x6d, 0xb5, 0x0a, 0x29, 0x9f, 0x13, 0xde, + 0x94, 0x6d, 0x7b, 0x25, 0x3f, 0xdf, 0xb3, 0x78, 0xb6, 0x72, 0xba, 0x23, 0xe0, 0xa6, 0x32, 0xbb, + 0xb0, 0x96, 0x7b, 0x85, 0xe0, 0x83, 0xb7, 0x38, 0x76, 0x66, 0x4b, 0x24, 0xe2, 0xab, 0x0e, 0x1b, + 0x20, 0x73, 0x65, 0x70, 0x61, 0xe5, 0xd7, 0x56, 0xe0, 0x43, 0x41, 0xef, 0x3b, 0xc6, 0xa9, 0xbf, + 0xc6, 0x1f, 0x8a, 0x42, 0xf5, 0xab, 0x63, 0x1d, 0x32, 0x71, 0x46, 0x2a, 0xad, 0x27, 0x70, 0x59, + 0x8e, 0xb3, 0xcc, 0x6b, 0xbc, 0x70, 0xf7, 0xe0, 0x70, 0x3a, 0x3f, 0x58, 0xc7, 0x17, 0x36, 0x8b, + 0x2b, 0x77, 0x6e, 0x17, 0x9b, 0xd6, 0x23, 0xda, 0x36, 0x53, 0x56, 0xb0, 0x00, 0x7c, 0xed, 0x3e, + 0xa4, 0x45, 0xb8, 0xf5, 0x96, 0x5d, 0xa1, 0x6e, 0x99, 0xbe, 0xcb, 0xee, 0xd0, 0x4c, 0x78, 0xff, + 0x94, 0x71, 0xa4, 0xfe, 0x08, 0x55, 0x67, 0xaa, 0xf3, 0x6e, 0xc6, 0xea, 0x1f, 0x19, 0x46, 0x70, + 0xed, 0x25, 0x52, 0xaa, 0x05, 0x45, 0x0d, 0xef, 0xa3, 0xde, 0x9b, 0x81, 0x71, 0x9f, 0x93, 0x06, + 0x2f, 0x75, 0x69, 0x37, 0x26, 0xce, 0xa4, 0x54, 0x17, 0xd6, 0x5d, 0xaf, 0x91, 0xaa, 0xc4, 0x29, + 0x22, 0x2a, 0xc5, 0xfb, 0x30, 0x1a, 0x72, 0x0e, 0x7b, 0xac, 0x4f, 0x8e, 0x1d, 0xfc, 0x85, 0xb5, + 0xd8, 0xe2, 0xaa, 0x9c, 0xfa, 0xee, 0x41, 0xc3, 0x57, 0x61, 0x62, 0xfb, 0xc9, 0x76, 0x69, 0x63, + 0x73, 0x7b, 0xed, 0xf1, 0xe6, 0xd3, 0xf5, 0xaf, 0x26, 0x13, 0x78, 0x02, 0x46, 0x3b, 0x9f, 0x08, + 0x5f, 0x86, 0xa1, 0xb5, 0xed, 0xef, 0x27, 0x93, 0xf9, 0xff, 0x46, 0x60, 0x58, 0x64, 0x89, 0x7f, + 0x44, 0x90, 0x92, 0x6f, 0x0d, 0xee, 0x3d, 0xd1, 0xdd, 0x0f, 0x5b, 0x66, 0xa1, 0x3f, 0x50, 0x92, + 0xd6, 0x66, 0x7f, 0xfa, 0xeb, 0xdf, 0xdf, 0x92, 0x37, 0xf1, 0x0d, 0xa3, 0xf7, 0x3b, 0x8b, 0x0f, + 0x10, 0xa4, 0xe3, 0xf6, 0x3d, 0xfe, 0xec, 0x5d, 0xdf, 0x07, 0x49, 0xef, 0xee, 0xf9, 0x9e, 0x15, + 0x6d, 0x47, 0x90, 0xdd, 0xc2, 0x8f, 0x62, 0xc9, 0x06, 0x33, 0xd1, 0x22, 0x8e, 0x5d, 0x21, 0x9c, + 0x35, 0x7c, 0xe3, 0x79, 0xf7, 0x9c, 0xbc, 0x30, 0x3c, 0xe1, 0x56, 0x3c, 0x71, 0xd2, 0x6f, 0xc9, + 0xb1, 0x7d, 0x8e, 0x5f, 0x22, 0x18, 0x16, 0x45, 0xc2, 0x73, 0xbd, 0x69, 0x9d, 0x5c, 0xf5, 0x99, + 0xf9, 0xbe, 0x38, 0xc5, 0xf7, 0x96, 0xe0, 0x3b, 0x87, 0x3f, 0x8e, 0xe7, 0x2b, 0xd6, 0x9a, 0xf1, + 0x5c, 0x8e, 0xcc, 0x0b, 0xfc, 0x33, 0x02, 0xe8, 0x6c, 0x4c, 0xbc, 0x74, 0xb6, 0x48, 0x5d, 0xbb, + 0x3f, 0x73, 0x6b, 0x30, 0xf0, 0x40, 0x45, 0x57, 0xeb, 0xf6, 0x15, 0x82, 0x89, 0xae, 0x65, 0x87, + 0xf5, 0xde, 0x41, 0xe2, 0x56, 0x69, 0xc6, 0x18, 0x18, 0xaf, 0x78, 0x2d, 0x09, 0x5e, 0x9f, 0xe0, + 0xd9, 0x58, 0x5e, 0xad, 0xc0, 0xa6, 0x23, 0xd7, 0x9f, 0x08, 0x46, 0xc2, 0x19, 0xc6, 0x9f, 0xf6, + 0x0e, 0x75, 0x6a, 0x83, 0x66, 0x16, 0x07, 0x81, 0x2a, 0x42, 0xeb, 0x82, 0xd0, 0x2a, 0x7e, 0x70, + 0xae, 0x86, 0x0b, 0xf7, 0x0a, 0xfe, 0x1d, 0xc1, 0x44, 0xd7, 0xb6, 0x3a, 0x4b, 0xca, 0xb8, 0xfd, + 0x7a, 0x96, 0x94, 0xb1, 0x6b, 0x50, 0x9b, 0x13, 0xcc, 0x73, 0x38, 0x1b, 0xcb, 0x3c, 0xda, 0x78, + 0x85, 0x6f, 0xde, 0x1c, 0x65, 0xd1, 0xfe, 0x51, 0x16, 0xfd, 0x73, 0x94, 0x45, 0xbf, 0x1e, 0x67, + 0x13, 0xfb, 0xc7, 0xd9, 0xc4, 0xdf, 0xc7, 0xd9, 0xc4, 0xd3, 0xdb, 0xfd, 0x5e, 0xaf, 0xbd, 0x8e, + 0x4b, 0xf1, 0x90, 0x59, 0x29, 0xf1, 0x5f, 0x7b, 0xe5, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x78, + 0x80, 0xbb, 0xc1, 0x49, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index eb8a719fa..68056654e 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -721,7 +721,7 @@ var ( pattern_Query_Evidence_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "evidence"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ListEvidences_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "vidences"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListEvidences_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "evidences"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( From fbc444d7d9a02f2f8863cf0f922543059e0f32de Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 5 Sep 2023 11:04:34 +1000 Subject: [PATCH 071/202] incentive: intercept and handle rewards in fee collector (#68) --- Makefile | 1 + app/app.go | 23 +- proto/babylon/incentive/query.proto | 34 +- test/e2e/btc_timestamping_e2e_test.go | 46 + test/e2e/configurer/chain/queries.go | 20 + .../configurer/chain/queries_btcstaking.go | 3 +- .../e2e/configurer/chain/queries_incentive.go | 63 ++ testutil/datagen/incentive.go | 16 +- testutil/keeper/incentive.go | 7 +- x/incentive/abci.go | 27 + x/incentive/client/cli/query.go | 119 ++- x/incentive/genesis_test.go | 2 +- x/incentive/keeper/btc_staking_gauge.go | 14 + x/incentive/keeper/btc_timestamping_gauge.go | 39 + x/incentive/keeper/grpc_query.go | 30 + x/incentive/keeper/grpc_query_test.go | 66 +- x/incentive/keeper/intercept_fee_collector.go | 40 + .../keeper/intercept_fee_collector_test.go | 88 ++ x/incentive/keeper/keeper.go | 10 +- x/incentive/keeper/msg_server_test.go | 2 +- x/incentive/keeper/params_test.go | 2 +- x/incentive/keeper/query_params_test.go | 2 +- x/incentive/module.go | 10 +- x/incentive/types/expected_keepers.go | 12 +- x/incentive/types/incentive.go | 25 +- x/incentive/types/mocked_keepers.go | 167 ++++ x/incentive/types/params.go | 25 +- x/incentive/types/query.pb.go | 828 +++++++++++++++++- x/incentive/types/query.pb.gw.go | 202 +++++ 29 files changed, 1835 insertions(+), 88 deletions(-) create mode 100644 test/e2e/configurer/chain/queries_incentive.go create mode 100644 x/incentive/abci.go create mode 100644 x/incentive/keeper/intercept_fee_collector.go create mode 100644 x/incentive/keeper/intercept_fee_collector_test.go create mode 100644 x/incentive/types/mocked_keepers.go diff --git a/Makefile b/Makefile index 8de00a1aa..55309e208 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,7 @@ mocks: $(MOCKS_DIR) $(mockgen_cmd) -source=x/zoneconcierge/types/expected_keepers.go -package types -destination x/zoneconcierge/types/mocked_keepers.go $(mockgen_cmd) -source=x/btcstaking/types/expected_keepers.go -package types -destination x/btcstaking/types/mocked_keepers.go $(mockgen_cmd) -source=x/finality/types/expected_keepers.go -package types -destination x/finality/types/mocked_keepers.go + $(mockgen_cmd) -source=x/incentive/types/expected_keepers.go -package types -destination x/incentive/types/mocked_keepers.go .PHONY: mocks $(MOCKS_DIR): diff --git a/app/app.go b/app/app.go index 289e1e9fa..1cd9bb332 100644 --- a/app/app.go +++ b/app/app.go @@ -226,9 +226,9 @@ var ( incentive.AppModuleBasic{}, ) - // module account permissions + // fee collector account, module accounts and their permissions maccPerms = map[string][]string{ - authtypes.FeeCollectorName: nil, + authtypes.FeeCollectorName: nil, // fee collector account distrtypes.ModuleName: nil, minttypes.ModuleName: {authtypes.Minter}, stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, @@ -236,10 +236,7 @@ var ( govtypes.ModuleName: {authtypes.Burner}, ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, ibcfeetypes.ModuleName: nil, - // TODO: decide ZonConcierge's permissions here - zctypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - // TODO: decide BTCStaking and Finality's permissiones here - // TODO: decide incentive module's permission here + incentivetypes.ModuleName: nil, // this line is needed to create an account for incentive module } ) @@ -639,17 +636,19 @@ func NewBabylonApp( app.BTCStakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - // set up incentive keeper - app.IncentiveKeeper = incentivekeeper.NewKeeper( - appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, - ) - // add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter()) - // make ZoneConcierge to subscribe to the epoching's hooks + // make ZoneConcierge and Monitor to subscribe to the epoching's hooks app.EpochingKeeper = *epochingKeeper.SetHooks( epochingtypes.NewMultiEpochingHooks(app.ZoneConciergeKeeper.Hooks(), app.MonitorKeeper.Hooks()), ) + + // set up incentive keeper + app.IncentiveKeeper = incentivekeeper.NewKeeper( + appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, app.EpochingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, + ) + + // set up Checkpointing, BTCCheckpoint, and BTCLightclient keepers app.CheckpointingKeeper = *checkpointingKeeper.SetHooks( checkpointingtypes.NewMultiCheckpointingHooks(app.EpochingKeeper.Hooks(), app.ZoneConciergeKeeper.Hooks(), app.MonitorKeeper.Hooks()), ) diff --git a/proto/babylon/incentive/query.proto b/proto/babylon/incentive/query.proto index 46ba6e19d..78ad67782 100644 --- a/proto/babylon/incentive/query.proto +++ b/proto/babylon/incentive/query.proto @@ -18,6 +18,14 @@ service Query { rpc RewardGauge(QueryRewardGaugeRequest) returns (QueryRewardGaugeResponse) { option (google.api.http).get = "/babylonchain/babylon/incentive/{type}/address/{address}/reward_gauge"; } + // BTCStakingGauge queries the BTC staking gauge of a given height + rpc BTCStakingGauge(QueryBTCStakingGaugeRequest) returns (QueryBTCStakingGaugeResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/btc_staking_gauge/{height}"; + } + // BTCTimestampingGauge queries the BTC timestamping gauge of a given epoch + rpc BTCTimestampingGauge(QueryBTCTimestampingGaugeRequest) returns (QueryBTCTimestampingGaugeResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/btc_timestamping_gauge/{epoch_num}"; + } } // QueryParamsRequest is request type for the Query/Params RPC method. @@ -38,8 +46,32 @@ message QueryRewardGaugeRequest { string address = 2; } -// QueryParamsResponse is response type for the Query/RewardGauge RPC method. +// QueryRewardGaugeResponse is response type for the Query/RewardGauge RPC method. message QueryRewardGaugeResponse { // reward_gauge is the reward gauge holding all rewards for the stakeholder RewardGauge reward_gauge = 1; +} + +// QueryBTCStakingGaugeRequest is request type for the Query/BTCStakingGauge RPC method. +message QueryBTCStakingGaugeRequest { + // height is the queried Babylon height + uint64 height = 1; +} + +// QueryBTCStakingGaugeResponse is response type for the Query/BTCStakingGauge RPC method. +message QueryBTCStakingGaugeResponse { + // gauge is the BTC staking gauge at the queried height + Gauge gauge = 1; +} + +// QueryBTCTimestampingGaugeRequest is request type for the Query/BTCTimestampingGauge RPC method. +message QueryBTCTimestampingGaugeRequest { + // epoch_num is the queried epoch number + uint64 epoch_num = 1; +} + +// QueryBTCTimestampingGaugeResponse is response type for the Query/BTCTimestampingGauge RPC method. +message QueryBTCTimestampingGaugeResponse { + // gauge is the BTC timestamping gauge at the queried epoch + Gauge gauge = 1; } \ No newline at end of file diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go index 85f066a13..bf423cda0 100644 --- a/test/e2e/btc_timestamping_e2e_test.go +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -12,6 +12,7 @@ import ( "github.com/babylonchain/babylon/test/e2e/initialization" bbn "github.com/babylonchain/babylon/types" ct "github.com/babylonchain/babylon/x/checkpointing/types" + incentivetypes "github.com/babylonchain/babylon/x/incentive/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -197,3 +198,48 @@ func (s *BTCTimestampingTestSuite) TestWasm() { // data is not finalized yet, so save epoch should be strictly greater than latest finalized epoch require.Greater(s.T(), saveEpoch, latestFinalizedEpoch) } + +func (s *BTCTimestampingTestSuite) TestInterceptFeeCollector() { + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // ensure incentive module account has positive balance + incentiveModuleAddr, err := nonValidatorNode.QueryModuleAddress(incentivetypes.ModuleName) + s.NoError(err) + incentiveBalance, err := nonValidatorNode.QueryBalances(incentiveModuleAddr.String()) + s.NoError(err) + s.NotEmpty(incentiveBalance) + s.True(incentiveBalance.IsAllPositive()) + + // ensure BTC staking gauge at the current height is non-empty + curHeight, err := nonValidatorNode.QueryCurrentHeight() + s.NoError(err) + btcStakingGauge, err := nonValidatorNode.QueryBTCStakingGauge(uint64(curHeight)) + s.NoError(err) + s.True(len(btcStakingGauge.Coins) >= 1) + s.True(btcStakingGauge.Coins[0].Amount.IsPositive()) + + // ensure BTC timestamping gauge at the current epoch is non-empty + curEpoch, err := nonValidatorNode.QueryCurrentEpoch() + s.NoError(err) + // at the 1st block of an epoch, the gauge does not exist since incentive's BeginBlock + // at this block accumulates rewards for BTC timestamping gauge for the previous block + // need to wait for a block to ensure the gauge is created + nonValidatorNode.WaitForNextBlock() + btcTimestampingGauge, err := nonValidatorNode.QueryBTCTimestampingGauge(curEpoch) + s.NoError(err) + s.NotEmpty(btcTimestampingGauge.Coins) + + // wait for 1 block to see if BTC timestamp gauge has accumulated + nonValidatorNode.WaitForNextBlock() + btcTimestampingGauge2, err := nonValidatorNode.QueryBTCTimestampingGauge(curEpoch) + s.NoError(err) + s.NotEmpty(btcTimestampingGauge2.Coins) + s.True(btcTimestampingGauge2.Coins.IsAllGTE(btcTimestampingGauge.Coins)) + + // after 1 block, incentive's balance has to be accumulated + incentiveBalance2, err := nonValidatorNode.QueryBalances(incentiveModuleAddr.String()) + s.NoError(err) + s.True(incentiveBalance2.IsAllGTE(incentiveBalance)) +} diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index dc0ab768b..524e07451 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -24,6 +24,7 @@ import ( tmabcitypes "github.com/cometbft/cometbft/abci/types" tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/stretchr/testify/require" ) @@ -67,6 +68,25 @@ func (n *NodeConfig) QueryGRPCGateway(path string, queryParams url.Values) ([]by return bz, nil } +// QueryModuleAccoint returns the address of a given module +func (n *NodeConfig) QueryModuleAddress(name string) (sdk.AccAddress, error) { + path := fmt.Sprintf("/cosmos/auth/v1beta1/module_accounts/%s", name) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp authtypes.QueryModuleAccountByNameResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return sdk.AccAddress{}, err + } + // cast to account + var account authtypes.AccountI + if err := util.EncodingConfig.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { + return sdk.AccAddress{}, err + } + + return account.GetAddress(), nil +} + // QueryBalances returns balances at the address. func (n *NodeConfig) QueryBalances(address string) (sdk.Coins, error) { path := fmt.Sprintf("cosmos/bank/v1beta1/balances/%s", address) diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index a83482a68..2a63d2b42 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -4,12 +4,11 @@ import ( "fmt" "net/url" - "github.com/stretchr/testify/require" - "github.com/babylonchain/babylon/test/e2e/util" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ftypes "github.com/babylonchain/babylon/x/finality/types" + "github.com/stretchr/testify/require" ) func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { diff --git a/test/e2e/configurer/chain/queries_incentive.go b/test/e2e/configurer/chain/queries_incentive.go new file mode 100644 index 000000000..d15935be9 --- /dev/null +++ b/test/e2e/configurer/chain/queries_incentive.go @@ -0,0 +1,63 @@ +package chain + +import ( + "fmt" + "net/url" + + "github.com/babylonchain/babylon/test/e2e/util" + incentivetypes "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func (n *NodeConfig) QueryBTCStakingGauge(height uint64) (*incentivetypes.Gauge, error) { + path := fmt.Sprintf("/babylonchain/babylon/incentive/btc_staking_gauge/%d", height) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp incentivetypes.QueryBTCStakingGaugeResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return nil, err + } + + return resp.Gauge, nil +} + +func (n *NodeConfig) QueryIncentiveParams() (*incentivetypes.Params, error) { + path := "/babylonchain/babylon/incentive/params" + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp incentivetypes.QueryParamsResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return nil, err + } + + return &resp.Params, nil +} + +func (n *NodeConfig) QueryRewardGauge(sType *incentivetypes.StakeholderType, sAddr sdk.AccAddress) (*incentivetypes.RewardGauge, error) { + path := fmt.Sprintf("/babylonchain/babylon/incentive/%s/address/%s/reward_gauge", sType.String(), sAddr.String()) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp incentivetypes.QueryRewardGaugeResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return nil, err + } + + return resp.RewardGauge, nil +} + +func (n *NodeConfig) QueryBTCTimestampingGauge(epoch uint64) (*incentivetypes.Gauge, error) { + path := fmt.Sprintf("/babylonchain/babylon/incentive/btc_timestamping_gauge/%d", epoch) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp incentivetypes.QueryBTCTimestampingGaugeResponse + if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { + return nil, err + } + + return resp.Gauge, nil +} diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index c596ef123..85cd61ee5 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -33,14 +33,24 @@ func GenRandomStakeholderType(r *rand.Rand) itypes.StakeholderType { return st } -func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge { +func GenRandomCoins(r *rand.Rand) sdk.Coins { numCoins := r.Int31n(10) + 10 coins := sdk.NewCoins() for i := int32(0); i < numCoins; i++ { demon := GenRandomDenom(r) - amount := r.Int63n(10000) + amount := r.Int63n(10000) + 1 coin := sdk.NewInt64Coin(demon, amount) coins = coins.Add(coin) } - return itypes.NewRewardGauge(coins...) + return coins +} + +func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge { + coins := GenRandomCoins(r) + return itypes.NewRewardGauge(coins) +} + +func GenRandomGauge(r *rand.Rand) *itypes.Gauge { + coins := GenRandomCoins(r) + return itypes.NewGauge(coins) } diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go index a3399a5e4..471402eaf 100644 --- a/testutil/keeper/incentive.go +++ b/testutil/keeper/incentive.go @@ -18,7 +18,7 @@ import ( "github.com/stretchr/testify/require" ) -func IncentiveKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, epochingKeeper types.EpochingKeeper) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -35,8 +35,9 @@ func IncentiveKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { cdc, storeKey, memStoreKey, - nil, - nil, + bankKeeper, + accountKeeper, + epochingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, ) diff --git a/x/incentive/abci.go b/x/incentive/abci.go new file mode 100644 index 000000000..0b092f185 --- /dev/null +++ b/x/incentive/abci.go @@ -0,0 +1,27 @@ +package incentive + +import ( + "time" + + "github.com/babylonchain/babylon/x/incentive/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + // handle coins in the fee collector account, including + // - send a portion of coins in the fee collector account to the incentive module account + // - accumulate BTC staking gauge at the current height + // - accumulate BTC timestamping gauge at the current epoch + k.HandleCoinsInFeeCollector(ctx) +} + +func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + return []abci.ValidatorUpdate{} +} diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go index bdb1a13af..9f34ab8e3 100644 --- a/x/incentive/client/cli/query.go +++ b/x/incentive/client/cli/query.go @@ -2,15 +2,12 @@ package cli import ( "fmt" - // "strings" - - "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/client" - // "github.com/cosmos/cosmos-sdk/client/flags" - // sdk "github.com/cosmos/cosmos-sdk/types" + "strconv" "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" ) // GetQueryCmd returns the cli query commands for this module @@ -24,7 +21,113 @@ func GetQueryCmd(queryRoute string) *cobra.Command { RunE: client.ValidateCmd, } - cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand( + CmdQueryParams(), + CmdQueryRewardGauge(), + CmdQueryBTCStakingGauge(), + CmdQueryBTCTimestampingGauge(), + ) + + return cmd +} + +func CmdQueryRewardGauge() *cobra.Command { + cmd := &cobra.Command{ + Use: "reward-gauge [type] [address]", + Short: "shows reward gauge of a given stakeholder in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryRewardGaugeRequest{ + Type: args[0], + Address: args[1], + } + res, err := queryClient.RewardGauge(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdQueryBTCStakingGauge() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-staking-gauge [height]", + Short: "shows BTC staking gauge of a given height", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryBTCStakingGaugeRequest{ + Height: height, + } + res, err := queryClient.BTCStakingGauge(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdQueryBTCTimestampingGauge() *cobra.Command { + cmd := &cobra.Command{ + Use: "btc-timestamping-gauge [epoch]", + Short: "shows BTC timestamping gauge of a given epoch", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + epoch, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryBTCTimestampingGaugeRequest{ + EpochNum: epoch, + } + res, err := queryClient.BTCTimestampingGauge(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) return cmd } diff --git a/x/incentive/genesis_test.go b/x/incentive/genesis_test.go index 1fbe61bd0..bb3f7e509 100644 --- a/x/incentive/genesis_test.go +++ b/x/incentive/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx := keepertest.IncentiveKeeper(t) + k, ctx := keepertest.IncentiveKeeper(t, nil, nil, nil) incentive.InitGenesis(ctx, *k, genesisState) got := incentive.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index c5d6f7fea..ba5c09b18 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -6,6 +6,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func (k Keeper) accumulateBTCStakingReward(ctx sdk.Context, btcStakingReward sdk.Coins) { + // update BTC staking gauge + height := uint64(ctx.BlockHeight()) + gauge := types.NewGauge(btcStakingReward) + k.SetBTCStakingGauge(ctx, height, gauge) + + // transfer the BTC staking reward from fee collector account to incentive module account + err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcStakingReward) + if err != nil { + // this can only be programming error and is unrecoverable + panic(err) + } +} + func (k Keeper) SetBTCStakingGauge(ctx sdk.Context, height uint64, gauge *types.Gauge) { store := k.btcStakingGaugeStore(ctx) gaugeBytes := k.cdc.MustMarshal(gauge) diff --git a/x/incentive/keeper/btc_timestamping_gauge.go b/x/incentive/keeper/btc_timestamping_gauge.go index 9d5462d1f..e52d2578b 100644 --- a/x/incentive/keeper/btc_timestamping_gauge.go +++ b/x/incentive/keeper/btc_timestamping_gauge.go @@ -6,12 +6,51 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestampingReward sdk.Coins) { + var ( + epoch = k.epochingKeeper.GetEpoch(ctx) + gauge *types.Gauge + err error + ) + + // do nothing at epoch 0 + if epoch.EpochNumber == 0 { + return + } + + // update BTC timestamping reward gauge + if k.HasBTCTimestampingGauge(ctx, epoch.EpochNumber) { + // if this epoch already has an non-empty gauge, accumulate + gauge, err = k.GetBTCTimestampingGauge(ctx, epoch.EpochNumber) + if err != nil { + panic(err) // only programming error is possible + } + gauge.Coins = gauge.Coins.Add(btcTimestampingReward...) // accumulate coins in the gauge + } else { + // if this epoch does not have a gauge yet, create a new one + gauge = types.NewGauge(btcTimestampingReward) + } + k.SetBTCTimestampingGauge(ctx, epoch.EpochNumber, gauge) + + // transfer the BTC timestamping reward from fee collector account to incentive module account + err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcTimestampingReward) + if err != nil { + // this can only be programming error and is unrecoverable + panic(err) + } +} + func (k Keeper) SetBTCTimestampingGauge(ctx sdk.Context, epoch uint64, gauge *types.Gauge) { store := k.btcTimestampingGaugeStore(ctx) gaugeBytes := k.cdc.MustMarshal(gauge) store.Set(sdk.Uint64ToBigEndian(epoch), gaugeBytes) } +func (k Keeper) HasBTCTimestampingGauge(ctx sdk.Context, epoch uint64) bool { + store := k.btcTimestampingGaugeStore(ctx) + return store.Has(sdk.Uint64ToBigEndian(epoch)) +} + func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) (*types.Gauge, error) { store := k.btcTimestampingGaugeStore(ctx) gaugeBytes := store.Get(sdk.Uint64ToBigEndian(epoch)) diff --git a/x/incentive/keeper/grpc_query.go b/x/incentive/keeper/grpc_query.go index 0a4473887..5a2c30f50 100644 --- a/x/incentive/keeper/grpc_query.go +++ b/x/incentive/keeper/grpc_query.go @@ -35,3 +35,33 @@ func (k Keeper) RewardGauge(goCtx context.Context, req *types.QueryRewardGaugeRe return &types.QueryRewardGaugeResponse{RewardGauge: rg}, nil } + +func (k Keeper) BTCStakingGauge(goCtx context.Context, req *types.QueryBTCStakingGaugeRequest) (*types.QueryBTCStakingGaugeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + // find gauge + gauge, err := k.GetBTCStakingGauge(ctx, req.Height) + if err != nil { + return nil, err + } + + return &types.QueryBTCStakingGaugeResponse{Gauge: gauge}, nil +} + +func (k Keeper) BTCTimestampingGauge(goCtx context.Context, req *types.QueryBTCTimestampingGaugeRequest) (*types.QueryBTCTimestampingGaugeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + // find gauge + gauge, err := k.GetBTCTimestampingGauge(ctx, req.EpochNum) + if err != nil { + return nil, err + } + + return &types.QueryBTCTimestampingGaugeResponse{Gauge: gauge}, nil +} diff --git a/x/incentive/keeper/grpc_query_test.go b/x/incentive/keeper/grpc_query_test.go index 70598ee02..de3a48158 100644 --- a/x/incentive/keeper/grpc_query_test.go +++ b/x/incentive/keeper/grpc_query_test.go @@ -16,7 +16,7 @@ func FuzzRewardGaugeQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - keeper, ctx := testkeeper.IncentiveKeeper(t) + keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) // generate a list of random RewardGauges and insert them to KVStore @@ -47,3 +47,67 @@ func FuzzRewardGaugeQuery(f *testing.F) { } }) } + +func FuzzBTCStakingGaugeQuery(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) + wctx := sdk.WrapSDKContext(ctx) + + // generate a list of random Gauges at random heights, then insert them to KVStore + heightList := []uint64{} + gaugeList := []*types.Gauge{} + numGauges := datagen.RandomInt(r, 100) + for i := uint64(0); i < numGauges; i++ { + height := datagen.RandomInt(r, 1000000) + heightList = append(heightList, height) + gauge := datagen.GenRandomGauge(r) + gaugeList = append(gaugeList, gauge) + keeper.SetBTCStakingGauge(ctx, height, gauge) + } + + // query existence and assert consistency + for i := range gaugeList { + req := &types.QueryBTCStakingGaugeRequest{ + Height: heightList[i], + } + resp, err := keeper.BTCStakingGauge(wctx, req) + require.NoError(t, err) + require.True(t, resp.Gauge.Coins.IsEqual(gaugeList[i].Coins)) + } + }) +} + +func FuzzBTCTimestampingGaugeQuery(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) + wctx := sdk.WrapSDKContext(ctx) + + // generate a list of random Gauges at random heights, then insert them to KVStore + epochList := []uint64{} + gaugeList := []*types.Gauge{} + numGauges := datagen.RandomInt(r, 100) + for i := uint64(0); i < numGauges; i++ { + epoch := datagen.RandomInt(r, 1000000) + epochList = append(epochList, epoch) + gauge := datagen.GenRandomGauge(r) + gaugeList = append(gaugeList, gauge) + keeper.SetBTCTimestampingGauge(ctx, epoch, gauge) + } + + // query existence and assert consistency + for i := range gaugeList { + req := &types.QueryBTCTimestampingGaugeRequest{ + EpochNum: epochList[i], + } + resp, err := keeper.BTCTimestampingGauge(wctx, req) + require.NoError(t, err) + require.True(t, resp.Gauge.Coins.IsEqual(gaugeList[i].Coins)) + } + }) +} diff --git a/x/incentive/keeper/intercept_fee_collector.go b/x/incentive/keeper/intercept_fee_collector.go new file mode 100644 index 000000000..5dba537a2 --- /dev/null +++ b/x/incentive/keeper/intercept_fee_collector.go @@ -0,0 +1,40 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// HandleCoinsInFeeCollector intercepts a portion of coins in fee collector, and distributes +// them to BTC staking gauge and BTC timestamping gauge of the current height and epoch, respectively. +// It is invoked upon every `BeginBlock`. +// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L15-L26 +func (k Keeper) HandleCoinsInFeeCollector(ctx sdk.Context) { + params := k.GetParams(ctx) + + // find the fee collector account + feeCollector := k.accountKeeper.GetModuleAccount(ctx, k.feeCollectorName) + // get all balances in the fee collector account, + // where the balance includes minted tokens in the previous block + feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress()) + + // don't intercept if there is no fee in fee collector account + if !feesCollectedInt.IsAllPositive() { + return + } + + // record BTC staking gauge for the current height, and transfer corresponding amount + // from fee collector account to incentive module account + // TODO: maybe we should not transfer reward to BTC staking gauge before BTC staking is activated + // this is tricky to implement since finality module will depend on incentive and incentive cannot + // depend on finality module due to cyclic dependency + btcStakingPortion := params.BTCStakingPortion() + btcStakingReward := types.GetCoinsPortion(feesCollectedInt, btcStakingPortion) + k.accumulateBTCStakingReward(ctx, btcStakingReward) + + // record BTC timestamping gauge for the current epoch, and transfer corresponding amount + // from fee collector account to incentive module account + btcTimestampingPortion := params.BTCTimestampingPortion() + btcTimestampingReward := types.GetCoinsPortion(feesCollectedInt, btcTimestampingPortion) + k.accumulateBTCTimestampingReward(ctx, btcTimestampingReward) +} diff --git a/x/incentive/keeper/intercept_fee_collector_test.go b/x/incentive/keeper/intercept_fee_collector_test.go new file mode 100644 index 000000000..5258ddcda --- /dev/null +++ b/x/incentive/keeper/intercept_fee_collector_test.go @@ -0,0 +1,88 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +var ( + feeCollectorAcc = authtypes.NewEmptyModuleAccount(authtypes.FeeCollectorName) + fees = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))) +) + +func FuzzInterceptFeeCollector(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + ctrl := gomock.NewController(t) + + // mock bank keeper + bankKeeper := types.NewMockBankKeeper(ctrl) + bankKeeper.EXPECT().GetAllBalances(gomock.Any(), feeCollectorAcc.GetAddress()).Return(fees).Times(1) + + // mock account keeper + accountKeeper := types.NewMockAccountKeeper(ctrl) + accountKeeper.EXPECT().GetModuleAccount(gomock.Any(), authtypes.FeeCollectorName).Return(feeCollectorAcc).Times(1) + + // mock epoching keeper + epochNum := datagen.RandomInt(r, 100) + 1 + epochingKeeper := types.NewMockEpochingKeeper(ctrl) + epochingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epochNum}).Times(1) + + keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, accountKeeper, epochingKeeper) + height := datagen.RandomInt(r, 1000) + ctx = ctx.WithBlockHeight(int64(height)) + + // mock (thus ensure) that fees with the exact portion is intercepted + // NOTE: if the actual fees are different from feesForIncentive the test will fail + params := keeper.GetParams(ctx) + feesForBTCStaking := types.GetCoinsPortion(fees, params.BTCStakingPortion()) + feesForBTCTimestamping := types.GetCoinsPortion(fees, params.BTCTimestampingPortion()) + bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), gomock.Eq(authtypes.FeeCollectorName), gomock.Eq(types.ModuleName), gomock.Eq(feesForBTCStaking)).Times(1) + bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), gomock.Eq(authtypes.FeeCollectorName), gomock.Eq(types.ModuleName), gomock.Eq(feesForBTCTimestamping)).Times(1) + + // handle coins in fee collector + keeper.HandleCoinsInFeeCollector(ctx) + + // assert correctness of BTC staking gauge at height + btcStakingFee := types.GetCoinsPortion(fees, params.BTCStakingPortion()) + btcStakingGauge, err := keeper.GetBTCStakingGauge(ctx, height) + require.NoError(t, err) + require.Equal(t, btcStakingFee, btcStakingGauge.Coins) + + // assert correctness of BTC timestamping gauge at epoch + btcTimestampingFee := types.GetCoinsPortion(fees, params.BTCTimestampingPortion()) + btcTimestampingGauge, err := keeper.GetBTCTimestampingGauge(ctx, epochNum) + require.NoError(t, err) + require.Equal(t, btcTimestampingFee, btcTimestampingGauge.Coins) + + // accumulate for this epoch again and see if the epoch's BTC timestamping gauge has accumulated or not + height += 1 + ctx = ctx.WithBlockHeight(int64(height)) + bankKeeper.EXPECT().GetAllBalances(gomock.Any(), feeCollectorAcc.GetAddress()).Return(fees).Times(1) + accountKeeper.EXPECT().GetModuleAccount(gomock.Any(), authtypes.FeeCollectorName).Return(feeCollectorAcc).Times(1) + epochingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epochNum}).Times(1) + bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), gomock.Eq(authtypes.FeeCollectorName), gomock.Eq(types.ModuleName), gomock.Eq(feesForBTCStaking)).Times(1) + bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), gomock.Eq(authtypes.FeeCollectorName), gomock.Eq(types.ModuleName), gomock.Eq(feesForBTCTimestamping)).Times(1) + // handle coins in fee collector + keeper.HandleCoinsInFeeCollector(ctx) + // assert BTC timestamping gauge has doubled + btcTimestampingGauge2, err := keeper.GetBTCTimestampingGauge(ctx, epochNum) + require.NoError(t, err) + for i := range btcTimestampingGauge.Coins { + amount := btcTimestampingGauge.Coins[i].Amount.Uint64() + amount2 := btcTimestampingGauge2.Coins[i].Amount.Uint64() + require.Equal(t, amount*2, amount2) + } + }) +} diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 67aaceed9..3e72c4efa 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -3,12 +3,11 @@ package keeper import ( "fmt" + "github.com/babylonchain/babylon/x/incentive/types" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/babylonchain/babylon/x/incentive/types" ) type ( @@ -17,8 +16,9 @@ type ( storeKey storetypes.StoreKey memKey storetypes.StoreKey - bankKeeper types.BankKeeper - accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + accountKeeper types.AccountKeeper + epochingKeeper types.EpochingKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -33,6 +33,7 @@ func NewKeeper( memKey storetypes.StoreKey, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, + epochingKeeper types.EpochingKeeper, authority string, feeCollectorName string, ) Keeper { @@ -42,6 +43,7 @@ func NewKeeper( memKey: memKey, bankKeeper: bankKeeper, accountKeeper: accountKeeper, + epochingKeeper: epochingKeeper, authority: authority, feeCollectorName: feeCollectorName, } diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go index 81b38fa6f..84d65eebc 100644 --- a/x/incentive/keeper/msg_server_test.go +++ b/x/incentive/keeper/msg_server_test.go @@ -12,7 +12,7 @@ import ( ) func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := keepertest.IncentiveKeeper(t) + k, ctx := keepertest.IncentiveKeeper(t, nil, nil, nil) return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) } diff --git a/x/incentive/keeper/params_test.go b/x/incentive/keeper/params_test.go index f31fd88ab..e2ee0a1da 100644 --- a/x/incentive/keeper/params_test.go +++ b/x/incentive/keeper/params_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.IncentiveKeeper(t) + k, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) params := types.DefaultParams() err := k.SetParams(ctx, params) diff --git a/x/incentive/keeper/query_params_test.go b/x/incentive/keeper/query_params_test.go index 100ee9a7b..5c20279cd 100644 --- a/x/incentive/keeper/query_params_test.go +++ b/x/incentive/keeper/query_params_test.go @@ -10,7 +10,7 @@ import ( ) func TestParamsQuery(t *testing.T) { - keeper, ctx := testkeeper.IncentiveKeeper(t) + keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) diff --git a/x/incentive/module.go b/x/incentive/module.go index e983d802e..8401d3afa 100644 --- a/x/incentive/module.go +++ b/x/incentive/module.go @@ -136,10 +136,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -// BeginBlock contains the logic that is automatically triggered at the beginning of each block -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper, req) +} -// EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return EndBlocker(ctx, am.keeper) } diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index 6aa6e9778..7e7379fce 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -1,18 +1,22 @@ package types import ( + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here + GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI } -// BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error +} + +type EpochingKeeper interface { + GetEpoch(ctx sdk.Context) *epochingtypes.Epoch } diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index 9221c553d..416cb3063 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -3,16 +3,35 @@ package types import ( fmt "fmt" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewRewardGauge(coins ...sdk.Coin) *RewardGauge { +func NewGauge(coins sdk.Coins) *Gauge { + return &Gauge{ + Coins: coins, + DistributedCoins: sdk.NewCoins(), + } +} + +func NewRewardGauge(coins sdk.Coins) *RewardGauge { return &RewardGauge{ - Coins: sdk.NewCoins(coins...), - WithdrawnCoins: sdk.NewCoins(coins...), + Coins: coins, + WithdrawnCoins: sdk.NewCoins(), } } +func GetCoinsPortion(coinsInt sdk.Coins, portion math.LegacyDec) sdk.Coins { + // coins with decimal value + coins := sdk.NewDecCoinsFromCoins(coinsInt...) + // portion of coins with decimal values + portionCoins := coins.MulDecTruncate(portion) + // truncate back + // TODO: how to deal with changes? + portionCoinsInt, _ := portionCoins.TruncateDecimal() + return portionCoinsInt +} + // enum for stakeholder type, used as key prefix in KVStore type StakeholderType byte diff --git a/x/incentive/types/mocked_keepers.go b/x/incentive/types/mocked_keepers.go new file mode 100644 index 000000000..5483f9862 --- /dev/null +++ b/x/incentive/types/mocked_keepers.go @@ -0,0 +1,167 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/incentive/types/expected_keepers.go + +// Package types is a generated GoMock package. +package types + +import ( + reflect "reflect" + + types "github.com/babylonchain/babylon/x/epoching/types" + types0 "github.com/cosmos/cosmos-sdk/types" + types1 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx types0.Context, addr types0.AccAddress) types1.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types1.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// GetModuleAccount mocks base method. +func (m *MockAccountKeeper) GetModuleAccount(ctx types0.Context, name string) types1.ModuleAccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAccount", ctx, name) + ret0, _ := ret[0].(types1.ModuleAccountI) + return ret0 +} + +// GetModuleAccount indicates an expected call of GetModuleAccount. +func (mr *MockAccountKeeperMockRecorder) GetModuleAccount(ctx, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAccount), ctx, name) +} + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// GetAllBalances mocks base method. +func (m *MockBankKeeper) GetAllBalances(ctx types0.Context, addr types0.AccAddress) types0.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) + ret0, _ := ret[0].(types0.Coins) + return ret0 +} + +// GetAllBalances indicates an expected call of GetAllBalances. +func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBalances", reflect.TypeOf((*MockBankKeeper)(nil).GetAllBalances), ctx, addr) +} + +// SendCoinsFromModuleToModule mocks base method. +func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx types0.Context, senderModule, recipientModule string, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromModuleToModule", ctx, senderModule, recipientModule, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromModuleToModule indicates an expected call of SendCoinsFromModuleToModule. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToModule(ctx, senderModule, recipientModule, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToModule), ctx, senderModule, recipientModule, amt) +} + +// SpendableCoins mocks base method. +func (m *MockBankKeeper) SpendableCoins(ctx types0.Context, addr types0.AccAddress) types0.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) + ret0, _ := ret[0].(types0.Coins) + return ret0 +} + +// SpendableCoins indicates an expected call of SpendableCoins. +func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) +} + +// MockEpochingKeeper is a mock of EpochingKeeper interface. +type MockEpochingKeeper struct { + ctrl *gomock.Controller + recorder *MockEpochingKeeperMockRecorder +} + +// MockEpochingKeeperMockRecorder is the mock recorder for MockEpochingKeeper. +type MockEpochingKeeperMockRecorder struct { + mock *MockEpochingKeeper +} + +// NewMockEpochingKeeper creates a new mock instance. +func NewMockEpochingKeeper(ctrl *gomock.Controller) *MockEpochingKeeper { + mock := &MockEpochingKeeper{ctrl: ctrl} + mock.recorder = &MockEpochingKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEpochingKeeper) EXPECT() *MockEpochingKeeperMockRecorder { + return m.recorder +} + +// GetEpoch mocks base method. +func (m *MockEpochingKeeper) GetEpoch(ctx types0.Context) *types.Epoch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEpoch", ctx) + ret0, _ := ret[0].(*types.Epoch) + return ret0 +} + +// GetEpoch indicates an expected call of GetEpoch. +func (mr *MockEpochingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpoch", reflect.TypeOf((*MockEpochingKeeper)(nil).GetEpoch), ctx) +} diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index 3d3822b67..8173d4be2 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -29,6 +29,26 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{} } +// TotalPortion calculates the sum of portions of all stakeholders +func (p *Params) TotalPortion() math.LegacyDec { + sum := p.SubmitterPortion + sum = sum.Add(p.ReporterPortion) + sum = sum.Add(p.BtcStakingPortion) + return sum +} + +// BTCTimestampingPortion calculates the sum of portions of all BTC timestamping stakeholders +func (p *Params) BTCTimestampingPortion() math.LegacyDec { + sum := p.SubmitterPortion + sum = sum.Add(p.ReporterPortion) + return sum +} + +// BTCStakingPortion calculates the sum of portions of all BTC staking stakeholders +func (p *Params) BTCStakingPortion() math.LegacyDec { + return p.BtcStakingPortion +} + // Validate validates the set of params func (p Params) Validate() error { if p.SubmitterPortion.IsNil() { @@ -42,10 +62,7 @@ func (p Params) Validate() error { } // sum of all portions should be less than 1 - sum := p.SubmitterPortion - sum = sum.Add(p.ReporterPortion) - sum = sum.Add(p.BtcStakingPortion) - if sum.GTE(math.LegacyOneDec()) { + if p.TotalPortion().GTE(math.LegacyOneDec()) { return fmt.Errorf("sum of all portions should be less than 1") } diff --git a/x/incentive/types/query.pb.go b/x/incentive/types/query.pb.go index c3b85dc57..5835b727a 100644 --- a/x/incentive/types/query.pb.go +++ b/x/incentive/types/query.pb.go @@ -168,7 +168,7 @@ func (m *QueryRewardGaugeRequest) GetAddress() string { return "" } -// QueryParamsResponse is response type for the Query/RewardGauge RPC method. +// QueryRewardGaugeResponse is response type for the Query/RewardGauge RPC method. type QueryRewardGaugeResponse struct { // reward_gauge is the reward gauge holding all rewards for the stakeholder RewardGauge *RewardGauge `protobuf:"bytes,1,opt,name=reward_gauge,json=rewardGauge,proto3" json:"reward_gauge,omitempty"` @@ -214,43 +214,241 @@ func (m *QueryRewardGaugeResponse) GetRewardGauge() *RewardGauge { return nil } +// QueryBTCStakingGaugeRequest is request type for the Query/BTCStakingGauge RPC method. +type QueryBTCStakingGaugeRequest struct { + // height is the queried Babylon height + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` +} + +func (m *QueryBTCStakingGaugeRequest) Reset() { *m = QueryBTCStakingGaugeRequest{} } +func (m *QueryBTCStakingGaugeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCStakingGaugeRequest) ProtoMessage() {} +func (*QueryBTCStakingGaugeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{4} +} +func (m *QueryBTCStakingGaugeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCStakingGaugeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCStakingGaugeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCStakingGaugeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCStakingGaugeRequest.Merge(m, src) +} +func (m *QueryBTCStakingGaugeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCStakingGaugeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCStakingGaugeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCStakingGaugeRequest proto.InternalMessageInfo + +func (m *QueryBTCStakingGaugeRequest) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +// QueryBTCStakingGaugeResponse is response type for the Query/BTCStakingGauge RPC method. +type QueryBTCStakingGaugeResponse struct { + // gauge is the BTC staking gauge at the queried height + Gauge *Gauge `protobuf:"bytes,1,opt,name=gauge,proto3" json:"gauge,omitempty"` +} + +func (m *QueryBTCStakingGaugeResponse) Reset() { *m = QueryBTCStakingGaugeResponse{} } +func (m *QueryBTCStakingGaugeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCStakingGaugeResponse) ProtoMessage() {} +func (*QueryBTCStakingGaugeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{5} +} +func (m *QueryBTCStakingGaugeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCStakingGaugeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCStakingGaugeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCStakingGaugeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCStakingGaugeResponse.Merge(m, src) +} +func (m *QueryBTCStakingGaugeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCStakingGaugeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCStakingGaugeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCStakingGaugeResponse proto.InternalMessageInfo + +func (m *QueryBTCStakingGaugeResponse) GetGauge() *Gauge { + if m != nil { + return m.Gauge + } + return nil +} + +// QueryBTCTimestampingGaugeRequest is request type for the Query/BTCTimestampingGauge RPC method. +type QueryBTCTimestampingGaugeRequest struct { + // epoch_num is the queried epoch number + EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` +} + +func (m *QueryBTCTimestampingGaugeRequest) Reset() { *m = QueryBTCTimestampingGaugeRequest{} } +func (m *QueryBTCTimestampingGaugeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCTimestampingGaugeRequest) ProtoMessage() {} +func (*QueryBTCTimestampingGaugeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{6} +} +func (m *QueryBTCTimestampingGaugeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCTimestampingGaugeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCTimestampingGaugeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCTimestampingGaugeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCTimestampingGaugeRequest.Merge(m, src) +} +func (m *QueryBTCTimestampingGaugeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCTimestampingGaugeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCTimestampingGaugeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCTimestampingGaugeRequest proto.InternalMessageInfo + +func (m *QueryBTCTimestampingGaugeRequest) GetEpochNum() uint64 { + if m != nil { + return m.EpochNum + } + return 0 +} + +// QueryBTCTimestampingGaugeResponse is response type for the Query/BTCTimestampingGauge RPC method. +type QueryBTCTimestampingGaugeResponse struct { + // gauge is the BTC timestamping gauge at the queried epoch + Gauge *Gauge `protobuf:"bytes,1,opt,name=gauge,proto3" json:"gauge,omitempty"` +} + +func (m *QueryBTCTimestampingGaugeResponse) Reset() { *m = QueryBTCTimestampingGaugeResponse{} } +func (m *QueryBTCTimestampingGaugeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCTimestampingGaugeResponse) ProtoMessage() {} +func (*QueryBTCTimestampingGaugeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e1a59cc0c7c44135, []int{7} +} +func (m *QueryBTCTimestampingGaugeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBTCTimestampingGaugeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBTCTimestampingGaugeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBTCTimestampingGaugeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCTimestampingGaugeResponse.Merge(m, src) +} +func (m *QueryBTCTimestampingGaugeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBTCTimestampingGaugeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCTimestampingGaugeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBTCTimestampingGaugeResponse proto.InternalMessageInfo + +func (m *QueryBTCTimestampingGaugeResponse) GetGauge() *Gauge { + if m != nil { + return m.Gauge + } + return nil +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.incentive.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.incentive.QueryParamsResponse") proto.RegisterType((*QueryRewardGaugeRequest)(nil), "babylon.incentive.QueryRewardGaugeRequest") proto.RegisterType((*QueryRewardGaugeResponse)(nil), "babylon.incentive.QueryRewardGaugeResponse") + proto.RegisterType((*QueryBTCStakingGaugeRequest)(nil), "babylon.incentive.QueryBTCStakingGaugeRequest") + proto.RegisterType((*QueryBTCStakingGaugeResponse)(nil), "babylon.incentive.QueryBTCStakingGaugeResponse") + proto.RegisterType((*QueryBTCTimestampingGaugeRequest)(nil), "babylon.incentive.QueryBTCTimestampingGaugeRequest") + proto.RegisterType((*QueryBTCTimestampingGaugeResponse)(nil), "babylon.incentive.QueryBTCTimestampingGaugeResponse") } func init() { proto.RegisterFile("babylon/incentive/query.proto", fileDescriptor_e1a59cc0c7c44135) } var fileDescriptor_e1a59cc0c7c44135 = []byte{ - // 404 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x4d, 0x4b, 0xeb, 0x40, - 0x14, 0x4d, 0x4a, 0x5f, 0x1f, 0x6f, 0xfa, 0x36, 0x6f, 0x5e, 0xc1, 0x18, 0x74, 0xd4, 0x80, 0xa5, - 0x28, 0x64, 0xb0, 0x2e, 0x5c, 0x5b, 0x90, 0x2e, 0x44, 0xd1, 0x2c, 0x05, 0x91, 0x49, 0x3b, 0xa4, - 0x81, 0x76, 0x26, 0xcd, 0x87, 0x5a, 0x4a, 0x37, 0x6e, 0xdc, 0x0a, 0xfe, 0x16, 0xff, 0x43, 0xdd, - 0x15, 0xdc, 0xb8, 0x12, 0x69, 0xfd, 0x21, 0x92, 0xc9, 0xb4, 0x46, 0xd2, 0x52, 0x77, 0x37, 0xf7, - 0x9c, 0x7b, 0xce, 0x99, 0x7b, 0x03, 0xd6, 0x6d, 0x62, 0xf7, 0xda, 0x9c, 0x61, 0x97, 0x35, 0x28, - 0x0b, 0xdd, 0x6b, 0x8a, 0xbb, 0x11, 0xf5, 0x7b, 0xa6, 0xe7, 0xf3, 0x90, 0xc3, 0x7f, 0x12, 0x36, - 0x67, 0xb0, 0x5e, 0x72, 0xb8, 0xc3, 0x05, 0x8a, 0xe3, 0x2a, 0x21, 0xea, 0x6b, 0x0e, 0xe7, 0x4e, - 0x9b, 0x62, 0xe2, 0xb9, 0x98, 0x30, 0xc6, 0x43, 0x12, 0xba, 0x9c, 0x05, 0x12, 0x45, 0x59, 0x17, - 0x8f, 0xf8, 0xa4, 0x33, 0xc5, 0xb7, 0xb2, 0xf8, 0xac, 0x4a, 0x28, 0x46, 0x09, 0xc0, 0xf3, 0x38, - 0xd8, 0x99, 0x98, 0xb3, 0x68, 0x37, 0xa2, 0x41, 0x68, 0x9c, 0x82, 0xff, 0xdf, 0xba, 0x81, 0xc7, - 0x59, 0x40, 0xe1, 0x01, 0x28, 0x24, 0xfa, 0x9a, 0xba, 0xa9, 0x56, 0x8a, 0xd5, 0x55, 0x33, 0xf3, - 0x0e, 0x33, 0x19, 0xa9, 0xe5, 0x87, 0x6f, 0x1b, 0x8a, 0x25, 0xe9, 0x46, 0x1d, 0xac, 0x08, 0x3d, - 0x8b, 0xde, 0x10, 0xbf, 0x59, 0x27, 0x91, 0x43, 0xa5, 0x15, 0x84, 0x20, 0x1f, 0xf6, 0x3c, 0x2a, - 0x14, 0xff, 0x58, 0xa2, 0x86, 0x1a, 0xf8, 0x4d, 0x9a, 0x4d, 0x9f, 0x06, 0x81, 0x96, 0x13, 0xed, - 0xe9, 0xa7, 0x71, 0x09, 0xb4, 0xac, 0x90, 0x4c, 0x77, 0x08, 0xfe, 0xfa, 0xa2, 0x7d, 0xe5, 0xc4, - 0x7d, 0x99, 0x11, 0xcd, 0xc9, 0x98, 0x9e, 0x2e, 0xfa, 0x5f, 0x1f, 0xd5, 0xe7, 0x1c, 0xf8, 0x25, - 0xf4, 0xe1, 0xbd, 0x0a, 0x0a, 0xc9, 0x53, 0xe0, 0xf6, 0x1c, 0x85, 0xec, 0xce, 0xf4, 0xf2, 0x32, - 0x5a, 0x12, 0xd3, 0x30, 0xef, 0x5e, 0x3e, 0x1e, 0x73, 0x15, 0x58, 0xc6, 0x92, 0xdf, 0x68, 0x11, - 0x97, 0xe1, 0x45, 0xa7, 0x84, 0x4f, 0x2a, 0x28, 0xa6, 0x02, 0xc3, 0x9d, 0x45, 0x3e, 0xd9, 0xe5, - 0xea, 0xbb, 0x3f, 0xe2, 0xca, 0x60, 0x27, 0x22, 0x58, 0x1d, 0x1e, 0x2d, 0x0b, 0xd6, 0x8f, 0x8f, - 0x34, 0xc0, 0xf2, 0x26, 0xb8, 0x2f, 0x8b, 0x01, 0x4e, 0xaf, 0xbf, 0x76, 0x3c, 0x1c, 0x23, 0x75, - 0x34, 0x46, 0xea, 0xfb, 0x18, 0xa9, 0x0f, 0x13, 0xa4, 0x8c, 0x26, 0x48, 0x79, 0x9d, 0x20, 0xe5, - 0x62, 0xcf, 0x71, 0xc3, 0x56, 0x64, 0x9b, 0x0d, 0xde, 0x99, 0x6f, 0x75, 0x9b, 0x32, 0x8b, 0xbd, - 0x02, 0xbb, 0x20, 0xfe, 0xd6, 0xfd, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x44, 0x9a, 0xf9, 0xe8, - 0x58, 0x03, 0x00, 0x00, + // 573 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x4f, 0x6b, 0x13, 0x41, + 0x18, 0xc6, 0xb3, 0x25, 0x89, 0xf6, 0x8d, 0x20, 0x8e, 0x41, 0xe3, 0xb6, 0xae, 0xed, 0x82, 0xa5, + 0x28, 0xec, 0x60, 0xab, 0x08, 0x22, 0x88, 0x51, 0x09, 0x28, 0x06, 0x4d, 0x7b, 0x12, 0x24, 0x4c, + 0x92, 0x61, 0xb3, 0xd8, 0x9d, 0xd9, 0xee, 0xce, 0xaa, 0x21, 0xe4, 0xe2, 0xc5, 0xab, 0xe0, 0x67, + 0xd1, 0xcf, 0xd0, 0x8b, 0x50, 0xf4, 0xe2, 0x49, 0x24, 0xf1, 0x83, 0x48, 0x66, 0x26, 0x71, 0xdb, + 0xdd, 0x34, 0xb1, 0xb7, 0x99, 0xf7, 0xcf, 0xf3, 0xfe, 0x5e, 0xe6, 0xd9, 0x85, 0xab, 0x2d, 0xd2, + 0xea, 0xed, 0x71, 0x86, 0x3d, 0xd6, 0xa6, 0x4c, 0x78, 0x6f, 0x29, 0xde, 0x8f, 0x69, 0xd8, 0x73, + 0x82, 0x90, 0x0b, 0x8e, 0x2e, 0xe8, 0xb4, 0x33, 0x4d, 0x9b, 0x65, 0x97, 0xbb, 0x5c, 0x66, 0xf1, + 0xf8, 0xa4, 0x0a, 0xcd, 0x55, 0x97, 0x73, 0x77, 0x8f, 0x62, 0x12, 0x78, 0x98, 0x30, 0xc6, 0x05, + 0x11, 0x1e, 0x67, 0x91, 0xce, 0x5a, 0xe9, 0x29, 0x01, 0x09, 0x89, 0x3f, 0xc9, 0xaf, 0xa7, 0xf3, + 0xd3, 0x93, 0x2a, 0xb1, 0xcb, 0x80, 0x5e, 0x8e, 0xc1, 0x5e, 0xc8, 0xbe, 0x06, 0xdd, 0x8f, 0x69, + 0x24, 0xec, 0x3a, 0x5c, 0x3c, 0x12, 0x8d, 0x02, 0xce, 0x22, 0x8a, 0xee, 0x42, 0x51, 0xe9, 0x57, + 0x8c, 0x35, 0x63, 0xb3, 0xb4, 0x75, 0xc5, 0x49, 0xed, 0xe1, 0xa8, 0x96, 0x6a, 0xfe, 0xe0, 0xd7, + 0xb5, 0x5c, 0x43, 0x97, 0xdb, 0x35, 0xb8, 0x2c, 0xf5, 0x1a, 0xf4, 0x1d, 0x09, 0x3b, 0x35, 0x12, + 0xbb, 0x54, 0x8f, 0x42, 0x08, 0xf2, 0xa2, 0x17, 0x50, 0xa9, 0xb8, 0xdc, 0x90, 0x67, 0x54, 0x81, + 0x33, 0xa4, 0xd3, 0x09, 0x69, 0x14, 0x55, 0x96, 0x64, 0x78, 0x72, 0xb5, 0x5f, 0x43, 0x25, 0x2d, + 0xa4, 0xe9, 0x1e, 0xc2, 0xb9, 0x50, 0x86, 0x9b, 0xee, 0x38, 0xae, 0x19, 0xad, 0x0c, 0xc6, 0x64, + 0x77, 0x29, 0xfc, 0x77, 0xb1, 0xef, 0xc0, 0x8a, 0x94, 0xaf, 0xee, 0x3e, 0xda, 0x11, 0xe4, 0x8d, + 0xc7, 0xdc, 0x23, 0xac, 0x97, 0xa0, 0xd8, 0xa5, 0x9e, 0xdb, 0x15, 0x52, 0x3b, 0xdf, 0xd0, 0x37, + 0xbb, 0x0e, 0xab, 0xd9, 0x6d, 0x9a, 0xcc, 0x81, 0x42, 0x12, 0xa9, 0x92, 0x81, 0xa4, 0x1a, 0x54, + 0x99, 0xfd, 0x00, 0xd6, 0x26, 0x7a, 0xbb, 0x9e, 0x4f, 0x23, 0x41, 0xfc, 0xe0, 0x38, 0xcb, 0x0a, + 0x2c, 0xd3, 0x80, 0xb7, 0xbb, 0x4d, 0x16, 0xfb, 0x1a, 0xe7, 0xac, 0x0c, 0xd4, 0x63, 0xdf, 0xde, + 0x81, 0xf5, 0x13, 0x04, 0x4e, 0x47, 0xb5, 0xf5, 0xbd, 0x00, 0x05, 0xa9, 0x8a, 0x3e, 0x1a, 0x50, + 0x54, 0xef, 0x8c, 0xae, 0x67, 0x74, 0xa5, 0x0d, 0x65, 0x6e, 0xcc, 0x2b, 0x53, 0x4c, 0xb6, 0xf3, + 0xe1, 0xc7, 0x9f, 0xcf, 0x4b, 0x9b, 0x68, 0x03, 0xeb, 0xfa, 0x76, 0x97, 0x78, 0x0c, 0xcf, 0xf2, + 0x39, 0xfa, 0x62, 0x40, 0x29, 0xf1, 0x9a, 0xe8, 0xc6, 0xac, 0x39, 0x69, 0xe7, 0x99, 0x37, 0x17, + 0xaa, 0xd5, 0x60, 0xcf, 0x25, 0x58, 0x0d, 0x3d, 0x99, 0x07, 0xd6, 0x1f, 0x3b, 0x78, 0x80, 0xb5, + 0x61, 0x71, 0x5f, 0x1f, 0x06, 0x38, 0xe9, 0x4d, 0xf4, 0xd5, 0x80, 0xf3, 0xc7, 0xdc, 0x82, 0x9c, + 0x59, 0x3c, 0xd9, 0x6e, 0x34, 0xf1, 0xc2, 0xf5, 0x7a, 0x87, 0xaa, 0xdc, 0xe1, 0x3e, 0xba, 0x37, + 0x6f, 0x87, 0x96, 0x68, 0x37, 0x23, 0xa5, 0xa0, 0x78, 0x71, 0x5f, 0x39, 0x7d, 0x80, 0xbe, 0x19, + 0x50, 0xce, 0x72, 0x15, 0xda, 0x3e, 0x81, 0x66, 0x96, 0x89, 0xcd, 0xdb, 0xff, 0xd7, 0xa4, 0xf7, + 0x78, 0x2a, 0xf7, 0x78, 0x8c, 0xaa, 0x8b, 0xec, 0x21, 0x12, 0x32, 0x93, 0x65, 0xa6, 0x1f, 0xce, + 0xa0, 0xfa, 0xec, 0x60, 0x68, 0x19, 0x87, 0x43, 0xcb, 0xf8, 0x3d, 0xb4, 0x8c, 0x4f, 0x23, 0x2b, + 0x77, 0x38, 0xb2, 0x72, 0x3f, 0x47, 0x56, 0xee, 0xd5, 0x2d, 0xd7, 0x13, 0xdd, 0xb8, 0xe5, 0xb4, + 0xb9, 0x9f, 0x3d, 0xe7, 0x7d, 0x62, 0xd2, 0xf8, 0xd1, 0xa3, 0x56, 0x51, 0xfe, 0x53, 0xb7, 0xff, + 0x06, 0x00, 0x00, 0xff, 0xff, 0x52, 0xb0, 0xcc, 0xeb, 0xfe, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -269,6 +467,10 @@ type QueryClient interface { Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) // RewardGauge queries the reward gauge of a given stakeholder in a given type RewardGauge(ctx context.Context, in *QueryRewardGaugeRequest, opts ...grpc.CallOption) (*QueryRewardGaugeResponse, error) + // BTCStakingGauge queries the BTC staking gauge of a given height + BTCStakingGauge(ctx context.Context, in *QueryBTCStakingGaugeRequest, opts ...grpc.CallOption) (*QueryBTCStakingGaugeResponse, error) + // BTCTimestampingGauge queries the BTC timestamping gauge of a given epoch + BTCTimestampingGauge(ctx context.Context, in *QueryBTCTimestampingGaugeRequest, opts ...grpc.CallOption) (*QueryBTCTimestampingGaugeResponse, error) } type queryClient struct { @@ -297,12 +499,34 @@ func (c *queryClient) RewardGauge(ctx context.Context, in *QueryRewardGaugeReque return out, nil } +func (c *queryClient) BTCStakingGauge(ctx context.Context, in *QueryBTCStakingGaugeRequest, opts ...grpc.CallOption) (*QueryBTCStakingGaugeResponse, error) { + out := new(QueryBTCStakingGaugeResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Query/BTCStakingGauge", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) BTCTimestampingGauge(ctx context.Context, in *QueryBTCTimestampingGaugeRequest, opts ...grpc.CallOption) (*QueryBTCTimestampingGaugeResponse, error) { + out := new(QueryBTCTimestampingGaugeResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Query/BTCTimestampingGauge", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) // RewardGauge queries the reward gauge of a given stakeholder in a given type RewardGauge(context.Context, *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) + // BTCStakingGauge queries the BTC staking gauge of a given height + BTCStakingGauge(context.Context, *QueryBTCStakingGaugeRequest) (*QueryBTCStakingGaugeResponse, error) + // BTCTimestampingGauge queries the BTC timestamping gauge of a given epoch + BTCTimestampingGauge(context.Context, *QueryBTCTimestampingGaugeRequest) (*QueryBTCTimestampingGaugeResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -315,6 +539,12 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq func (*UnimplementedQueryServer) RewardGauge(ctx context.Context, req *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RewardGauge not implemented") } +func (*UnimplementedQueryServer) BTCStakingGauge(ctx context.Context, req *QueryBTCStakingGaugeRequest) (*QueryBTCStakingGaugeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCStakingGauge not implemented") +} +func (*UnimplementedQueryServer) BTCTimestampingGauge(ctx context.Context, req *QueryBTCTimestampingGaugeRequest) (*QueryBTCTimestampingGaugeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCTimestampingGauge not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -356,6 +586,42 @@ func _Query_RewardGauge_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Query_BTCStakingGauge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCStakingGaugeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCStakingGauge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Query/BTCStakingGauge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCStakingGauge(ctx, req.(*QueryBTCStakingGaugeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_BTCTimestampingGauge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCTimestampingGaugeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).BTCTimestampingGauge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Query/BTCTimestampingGauge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).BTCTimestampingGauge(ctx, req.(*QueryBTCTimestampingGaugeRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.incentive.Query", HandlerType: (*QueryServer)(nil), @@ -368,6 +634,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "RewardGauge", Handler: _Query_RewardGauge_Handler, }, + { + MethodName: "BTCStakingGauge", + Handler: _Query_BTCStakingGauge_Handler, + }, + { + MethodName: "BTCTimestampingGauge", + Handler: _Query_BTCTimestampingGauge_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/incentive/query.proto", @@ -501,6 +775,132 @@ func (m *QueryRewardGaugeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *QueryBTCStakingGaugeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCStakingGaugeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCStakingGaugeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCStakingGaugeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCStakingGaugeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCStakingGaugeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Gauge != nil { + { + size, err := m.Gauge.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCTimestampingGaugeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCTimestampingGaugeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCTimestampingGaugeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EpochNum != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.EpochNum)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryBTCTimestampingGaugeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBTCTimestampingGaugeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBTCTimestampingGaugeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Gauge != nil { + { + size, err := m.Gauge.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -562,12 +962,62 @@ func (m *QueryRewardGaugeResponse) Size() (n int) { return n } -func sovQuery(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozQuery(x uint64) (n int) { - return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} +func (m *QueryBTCStakingGaugeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + return n +} + +func (m *QueryBTCStakingGaugeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Gauge != nil { + l = m.Gauge.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryBTCTimestampingGaugeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.EpochNum != 0 { + n += 1 + sovQuery(uint64(m.EpochNum)) + } + return n +} + +func (m *QueryBTCTimestampingGaugeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Gauge != nil { + l = m.Gauge.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -901,6 +1351,316 @@ func (m *QueryRewardGaugeResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryBTCStakingGaugeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCStakingGaugeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCStakingGaugeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCStakingGaugeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCStakingGaugeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCStakingGaugeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gauge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gauge == nil { + m.Gauge = &Gauge{} + } + if err := m.Gauge.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCTimestampingGaugeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCTimestampingGaugeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCTimestampingGaugeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNum", wireType) + } + m.EpochNum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNum |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBTCTimestampingGaugeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBTCTimestampingGaugeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBTCTimestampingGaugeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gauge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gauge == nil { + m.Gauge = &Gauge{} + } + if err := m.Gauge.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/incentive/types/query.pb.gw.go b/x/incentive/types/query.pb.gw.go index 842193594..a6abdbb28 100644 --- a/x/incentive/types/query.pb.gw.go +++ b/x/incentive/types/query.pb.gw.go @@ -127,6 +127,114 @@ func local_request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Ma } +func request_Query_BTCStakingGauge_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCStakingGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := client.BTCStakingGauge(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCStakingGauge_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCStakingGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height") + } + + protoReq.Height, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) + } + + msg, err := server.BTCStakingGauge(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_BTCTimestampingGauge_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCTimestampingGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["epoch_num"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "epoch_num") + } + + protoReq.EpochNum, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "epoch_num", err) + } + + msg, err := client.BTCTimestampingGauge(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_BTCTimestampingGauge_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCTimestampingGaugeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["epoch_num"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "epoch_num") + } + + protoReq.EpochNum, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "epoch_num", err) + } + + msg, err := server.BTCTimestampingGauge(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -179,6 +287,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_BTCStakingGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCStakingGauge_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCStakingGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCTimestampingGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_BTCTimestampingGauge_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCTimestampingGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -260,6 +414,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_BTCStakingGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCStakingGauge_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCStakingGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_BTCTimestampingGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_BTCTimestampingGauge_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_BTCTimestampingGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -267,10 +461,18 @@ var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylonchain", "babylon", "incentive", "params"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_RewardGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylonchain", "babylon", "incentive", "type", "address", "reward_gauge"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCStakingGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylonchain", "babylon", "incentive", "btc_staking_gauge", "height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_BTCTimestampingGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylonchain", "babylon", "incentive", "btc_timestamping_gauge", "epoch_num"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage forward_Query_RewardGauge_0 = runtime.ForwardResponseMessage + + forward_Query_BTCStakingGauge_0 = runtime.ForwardResponseMessage + + forward_Query_BTCTimestampingGauge_0 = runtime.ForwardResponseMessage ) From 918d8fee9afa842f43805d890db495430f134307 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 5 Sep 2023 11:47:50 +1000 Subject: [PATCH 072/202] incentive: msgs for withdrawing reward and updating params (#71) --- proto/babylon/incentive/query.proto | 24 +- proto/babylon/incentive/tx.proto | 51 +- .../e2e/configurer/chain/queries_incentive.go | 8 +- testutil/datagen/incentive.go | 16 + x/incentive/client/cli/query.go | 17 +- x/incentive/client/cli/tx.go | 35 +- x/incentive/keeper/grpc_query.go | 25 +- x/incentive/keeper/grpc_query_test.go | 36 +- .../keeper/intercept_fee_collector_test.go | 1 + x/incentive/keeper/msg_server.go | 47 + x/incentive/keeper/msg_server_test.go | 48 +- x/incentive/keeper/reward_gauge.go | 27 + x/incentive/types/codec.go | 10 + x/incentive/types/errors.go | 1 + x/incentive/types/expected_keepers.go | 1 + x/incentive/types/incentive.go | 35 + x/incentive/types/mocked_keepers.go | 14 + x/incentive/types/msg.go | 54 + x/incentive/types/query.pb.go | 422 ++++--- x/incentive/types/query.pb.gw.go | 50 +- x/incentive/types/tx.pb.go | 1047 ++++++++++++++++- 21 files changed, 1687 insertions(+), 282 deletions(-) create mode 100644 x/incentive/types/msg.go diff --git a/proto/babylon/incentive/query.proto b/proto/babylon/incentive/query.proto index 78ad67782..22985e390 100644 --- a/proto/babylon/incentive/query.proto +++ b/proto/babylon/incentive/query.proto @@ -14,9 +14,9 @@ service Query { rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/babylonchain/babylon/incentive/params"; } - // RewardGauge queries the reward gauge of a given stakeholder in a given type - rpc RewardGauge(QueryRewardGaugeRequest) returns (QueryRewardGaugeResponse) { - option (google.api.http).get = "/babylonchain/babylon/incentive/{type}/address/{address}/reward_gauge"; + // RewardGauge queries the reward gauge of a given stakeholder address + rpc RewardGauges(QueryRewardGaugesRequest) returns (QueryRewardGaugesResponse) { + option (google.api.http).get = "/babylonchain/babylon/incentive/address/{address}/reward_gauge"; } // BTCStakingGauge queries the BTC staking gauge of a given height rpc BTCStakingGauge(QueryBTCStakingGaugeRequest) returns (QueryBTCStakingGaugeResponse) { @@ -37,19 +37,17 @@ message QueryParamsResponse { Params params = 1 [(gogoproto.nullable) = false]; } -// QueryRewardGaugeRequest is request type for the Query/RewardGauge RPC method. -message QueryRewardGaugeRequest { - // type is the type of the stakeholder, can be one of - // {submitter, reporter, btc_validator, btc_delegation} - string type = 1; +// QueryRewardGaugesRequest is request type for the Query/RewardGauges RPC method. +message QueryRewardGaugesRequest { // address is the address of the stakeholder in bech32 string - string address = 2; + string address = 1; } -// QueryRewardGaugeResponse is response type for the Query/RewardGauge RPC method. -message QueryRewardGaugeResponse { - // reward_gauge is the reward gauge holding all rewards for the stakeholder - RewardGauge reward_gauge = 1; +// QueryRewardGaugesResponse is response type for the Query/RewardGauges RPC method. +message QueryRewardGaugesResponse { + // reward_gauges is the map of reward gauges, where key is the stakeholder type + // and value is the reward gauge holding all rewards for the stakeholder in that type + map reward_gauges = 1; } // QueryBTCStakingGaugeRequest is request type for the Query/BTCStakingGauge RPC method. diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto index 8a67fe5af..cd478e3e6 100644 --- a/proto/babylon/incentive/tx.proto +++ b/proto/babylon/incentive/tx.proto @@ -1,7 +1,56 @@ syntax = "proto3"; package babylon.incentive; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "babylon/incentive/params.proto"; + option go_package = "github.com/babylonchain/babylon/x/incentive/types"; // Msg defines the Msg service. -service Msg {} \ No newline at end of file +service Msg { + // WithdrawReward defines a method to withdraw rewards of a stakeholder + rpc WithdrawReward(MsgWithdrawReward) returns (MsgWithdrawRewardResponse); + // UpdateParams updates the incentive module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + + +// MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. +message MsgWithdrawReward { + string signer = 1; + + // {submitter, reporter, btc_validator, btc_delegation} + string type = 2; + // address is the address of the stakeholder in bech32 string + string address = 3; +} + +// MsgWithdrawRewardResponse is the response to the MsgWithdrawReward message +message MsgWithdrawRewardResponse { + // coins is the withdrawed coins + repeated cosmos.base.v1beta1.Coin coins = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// MsgUpdateParams defines a message for updating incentive module parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the incentive parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +message MsgUpdateParamsResponse {} diff --git a/test/e2e/configurer/chain/queries_incentive.go b/test/e2e/configurer/chain/queries_incentive.go index d15935be9..d9c91eff6 100644 --- a/test/e2e/configurer/chain/queries_incentive.go +++ b/test/e2e/configurer/chain/queries_incentive.go @@ -36,17 +36,17 @@ func (n *NodeConfig) QueryIncentiveParams() (*incentivetypes.Params, error) { return &resp.Params, nil } -func (n *NodeConfig) QueryRewardGauge(sType *incentivetypes.StakeholderType, sAddr sdk.AccAddress) (*incentivetypes.RewardGauge, error) { - path := fmt.Sprintf("/babylonchain/babylon/incentive/%s/address/%s/reward_gauge", sType.String(), sAddr.String()) +func (n *NodeConfig) QueryRewardGauge(sAddr sdk.AccAddress) (map[string]*incentivetypes.RewardGauge, error) { + path := fmt.Sprintf("/babylonchain/babylon/incentive/address/%s/reward_gauge", sAddr.String()) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp incentivetypes.QueryRewardGaugeResponse + var resp incentivetypes.QueryRewardGaugesResponse if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { return nil, err } - return resp.RewardGauge, nil + return resp.RewardGauges, nil } func (n *NodeConfig) QueryBTCTimestampingGauge(epoch uint64) (*incentivetypes.Gauge, error) { diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index 85cd61ee5..e26babb72 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -50,6 +50,22 @@ func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge { return itypes.NewRewardGauge(coins) } +func GenRandomWithdrawnCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins { + withdrawnCoins := sdk.NewCoins() + for _, coin := range coins { + // skip this coin with some probability + if OneInN(r, 3) { + continue + } + // a subset of the coin has been withdrawn + amount := coin.Amount.Uint64() + withdrawnAmount := RandomInt(r, int(amount)-1) + 1 + withdrawnCoin := sdk.NewCoin(coin.Denom, sdk.NewIntFromUint64(withdrawnAmount)) + withdrawnCoins = withdrawnCoins.Add(withdrawnCoin) + } + return withdrawnCoins +} + func GenRandomGauge(r *rand.Rand) *itypes.Gauge { coins := GenRandomCoins(r) return itypes.NewGauge(coins) diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go index 9f34ab8e3..de89a08d9 100644 --- a/x/incentive/client/cli/query.go +++ b/x/incentive/client/cli/query.go @@ -23,7 +23,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand( CmdQueryParams(), - CmdQueryRewardGauge(), + CmdQueryRewardGauges(), CmdQueryBTCStakingGauge(), CmdQueryBTCTimestampingGauge(), ) @@ -31,11 +31,11 @@ func GetQueryCmd(queryRoute string) *cobra.Command { return cmd } -func CmdQueryRewardGauge() *cobra.Command { +func CmdQueryRewardGauges() *cobra.Command { cmd := &cobra.Command{ - Use: "reward-gauge [type] [address]", - Short: "shows reward gauge of a given stakeholder in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", - Args: cobra.ExactArgs(2), + Use: "reward-gauges [address]", + Short: "shows reward gauges of a given stakeholder address", + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { @@ -44,11 +44,10 @@ func CmdQueryRewardGauge() *cobra.Command { queryClient := types.NewQueryClient(clientCtx) - req := &types.QueryRewardGaugeRequest{ - Type: args[0], - Address: args[1], + req := &types.QueryRewardGaugesRequest{ + Address: args[0], } - res, err := queryClient.RewardGauge(cmd.Context(), req) + res, err := queryClient.RewardGauges(cmd.Context(), req) if err != nil { return err } diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go index 6bde3d489..59afa1a9e 100644 --- a/x/incentive/client/cli/tx.go +++ b/x/incentive/client/cli/tx.go @@ -6,9 +6,10 @@ import ( "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" - // "github.com/cosmos/cosmos-sdk/client/flags" "github.com/babylonchain/babylon/x/incentive/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" ) var ( @@ -25,5 +26,35 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } + cmd.AddCommand( + NewWithdrawRewardCmd(), + ) + + return cmd +} + +func NewWithdrawRewardCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "withdraw-reward [type] [address]", + Short: "withdraw reward of a given stakeholder in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msg := types.MsgWithdrawReward{ + Signer: clientCtx.FromAddress.String(), + Type: args[0], + Address: args[1], + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd } diff --git a/x/incentive/keeper/grpc_query.go b/x/incentive/keeper/grpc_query.go index 5a2c30f50..a0d4c6311 100644 --- a/x/incentive/keeper/grpc_query.go +++ b/x/incentive/keeper/grpc_query.go @@ -11,29 +11,34 @@ import ( var _ types.QueryServer = Keeper{} -func (k Keeper) RewardGauge(goCtx context.Context, req *types.QueryRewardGaugeRequest) (*types.QueryRewardGaugeResponse, error) { +func (k Keeper) RewardGauges(goCtx context.Context, req *types.QueryRewardGaugesRequest) (*types.QueryRewardGaugesResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } ctx := sdk.UnwrapSDKContext(goCtx) - // try to cast types for fields in the request - sType, err := types.NewStakeHolderTypeFromString(req.Type) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } + // try to cast address address, err := sdk.AccAddressFromBech32(req.Address) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + rgMap := map[string]*types.RewardGauge{} + // find reward gauge - rg, err := k.GetRewardGauge(ctx, sType, address) - if err != nil { - return nil, err + for _, sType := range types.GetAllStakeholderTypes() { + if !k.HasRewardGauge(ctx, sType, address) { + continue + } + rg, err := k.GetRewardGauge(ctx, sType, address) + if err != nil { + // only programming error is possible + panic("failed to get an existing reward gauge") + } + rgMap[sType.String()] = rg } - return &types.QueryRewardGaugeResponse{RewardGauge: rg}, nil + return &types.QueryRewardGaugesResponse{RewardGauges: rgMap}, nil } func (k Keeper) BTCStakingGauge(goCtx context.Context, req *types.QueryBTCStakingGaugeRequest) (*types.QueryBTCStakingGaugeResponse, error) { diff --git a/x/incentive/keeper/grpc_query_test.go b/x/incentive/keeper/grpc_query_test.go index de3a48158..e9a604b92 100644 --- a/x/incentive/keeper/grpc_query_test.go +++ b/x/incentive/keeper/grpc_query_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func FuzzRewardGaugeQuery(f *testing.F) { +func FuzzRewardGaugesQuery(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -19,31 +19,35 @@ func FuzzRewardGaugeQuery(f *testing.F) { keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) - // generate a list of random RewardGauges and insert them to KVStore - rgList := []*types.RewardGauge{} - sTypeList := []types.StakeholderType{} + // generate a list of random RewardGauge map and insert them to KVStore + // where in each map, key is stakeholder type and address is the reward gauge + rgMaps := []map[string]*types.RewardGauge{} sAddrList := []sdk.AccAddress{} - numRgs := datagen.RandomInt(r, 100) - for i := uint64(0); i < numRgs; i++ { - sType := datagen.GenRandomStakeholderType(r) - sTypeList = append(sTypeList, sType) + numRgMaps := datagen.RandomInt(r, 100) + for i := uint64(0); i < numRgMaps; i++ { + rgMap := map[string]*types.RewardGauge{} sAddr := datagen.GenRandomAccount().GetAddress() sAddrList = append(sAddrList, sAddr) - rg := datagen.GenRandomRewardGauge(r) - rgList = append(rgList, rg) + for i := uint64(0); i < datagen.RandomInt(r, 4); i++ { + sType := datagen.GenRandomStakeholderType(r) + rg := datagen.GenRandomRewardGauge(r) + rgMap[sType.String()] = rg - keeper.SetRewardGauge(ctx, sType, sAddr, rg) + keeper.SetRewardGauge(ctx, sType, sAddr, rg) + } + rgMaps = append(rgMaps, rgMap) } // query existence and assert consistency - for i := range rgList { - req := &types.QueryRewardGaugeRequest{ - Type: sTypeList[i].String(), + for i := range rgMaps { + req := &types.QueryRewardGaugesRequest{ Address: sAddrList[i].String(), } - resp, err := keeper.RewardGauge(wctx, req) + resp, err := keeper.RewardGauges(wctx, req) require.NoError(t, err) - require.True(t, resp.RewardGauge.Coins.IsEqual(rgList[i].Coins)) + for sTypeStr, rg := range rgMaps[i] { + require.Equal(t, rg.Coins, resp.RewardGauges[sTypeStr].Coins) + } } }) } diff --git a/x/incentive/keeper/intercept_fee_collector_test.go b/x/incentive/keeper/intercept_fee_collector_test.go index 5258ddcda..c64973213 100644 --- a/x/incentive/keeper/intercept_fee_collector_test.go +++ b/x/incentive/keeper/intercept_fee_collector_test.go @@ -25,6 +25,7 @@ func FuzzInterceptFeeCollector(f *testing.F) { r := rand.New(rand.NewSource(seed)) ctrl := gomock.NewController(t) + defer ctrl.Finish() // mock bank keeper bankKeeper := types.NewMockBankKeeper(ctrl) diff --git a/x/incentive/keeper/msg_server.go b/x/incentive/keeper/msg_server.go index 63b3be97d..20f77ff23 100644 --- a/x/incentive/keeper/msg_server.go +++ b/x/incentive/keeper/msg_server.go @@ -1,7 +1,14 @@ package keeper import ( + "context" + + errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type msgServer struct { @@ -15,3 +22,43 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { } var _ types.MsgServer = msgServer{} + +// UpdateParams updates the params +func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if ms.authority != req.Authority { + return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := ms.SetParams(ctx, req.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} + +// WithdrawReward withdraws the reward of a given stakeholder +func (ms msgServer) WithdrawReward(goCtx context.Context, req *types.MsgWithdrawReward) (*types.MsgWithdrawRewardResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // get stakeholder type and address + sType, err := types.NewStakeHolderTypeFromString(req.Type) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + addr, err := sdk.AccAddressFromBech32(req.Address) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // withdraw reward, i.e., send withdrawable reward to the stakeholder address and clear the reward gauge + withdrawnCoins, err := ms.withdrawReward(ctx, sType, addr) + if err != nil { + return nil, err + } + + // all good + return &types.MsgWithdrawRewardResponse{ + Coins: withdrawnCoins, + }, nil +} diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go index 84d65eebc..bd724b2a3 100644 --- a/x/incentive/keeper/msg_server_test.go +++ b/x/incentive/keeper/msg_server_test.go @@ -2,17 +2,20 @@ package keeper_test import ( "context" + "math/rand" "testing" - keepertest "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/incentive/keeper" "github.com/babylonchain/babylon/x/incentive/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := keepertest.IncentiveKeeper(t, nil, nil, nil) + k, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) } @@ -21,3 +24,44 @@ func TestMsgServer(t *testing.T) { require.NotNil(t, ms) require.NotNil(t, ctx) } + +func FuzzWithdrawReward(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock bank keeper + bk := types.NewMockBankKeeper(ctrl) + + ik, ctx := testkeeper.IncentiveKeeper(t, bk, nil, nil) + ms := keeper.NewMsgServerImpl(*ik) + + // generate and set a random reward gauge with a random set of withdrawable coins + rg := datagen.GenRandomRewardGauge(r) + rg.WithdrawnCoins = datagen.GenRandomWithdrawnCoins(r, rg.Coins) + sType := datagen.GenRandomStakeholderType(r) + sAddr := datagen.GenRandomAccount().GetAddress() + ik.SetRewardGauge(ctx, sType, sAddr, rg) + + // mock transfer of withdrawable coins + withdrawableCoins := rg.GetWithdrawableCoins() + bk.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(sAddr), gomock.Eq(withdrawableCoins)).Times(1) + + // invoke withdraw and assert consistency + resp, err := ms.WithdrawReward(ctx, &types.MsgWithdrawReward{ + Signer: datagen.GenRandomAccount().Address, + Type: sType.String(), + Address: sAddr.String(), + }) + require.NoError(t, err) + require.Equal(t, withdrawableCoins, resp.Coins) + + // ensure reward gauge is now empty + newRg, err := ik.GetRewardGauge(ctx, sType, sAddr) + require.NoError(t, err) + require.True(t, newRg.IsEmpty()) + }) +} diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go index 49c8301cf..6124760b0 100644 --- a/x/incentive/keeper/reward_gauge.go +++ b/x/incentive/keeper/reward_gauge.go @@ -6,12 +6,39 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func (k Keeper) withdrawReward(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (sdk.Coins, error) { + // retrieve reward gauge of the given stakeholder + rg, err := k.GetRewardGauge(ctx, sType, addr) + if err != nil { + return nil, err + } + // get withdrawable coins + withdrawableCoins := rg.GetWithdrawableCoins() + if len(withdrawableCoins) == 0 { + return nil, types.ErrNoWithdrawableCoins + } + // transfer withdrawable coins from incentive module account to the stakeholder's address + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, withdrawableCoins); err != nil { + return nil, err + } + // empty reward gauge + rg.Clear() + k.SetRewardGauge(ctx, sType, addr, rg) + // all good, return + return withdrawableCoins, nil +} + func (k Keeper) SetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { store := k.rewardGaugeStore(ctx, sType) rgBytes := k.cdc.MustMarshal(rg) store.Set(addr, rgBytes) } +func (k Keeper) HasRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) bool { + store := k.rewardGaugeStore(ctx, sType) + return store.Has(addr) +} + func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (*types.RewardGauge, error) { store := k.rewardGaugeStore(ctx, sType) rgBytes := store.Get(addr) diff --git a/x/incentive/types/codec.go b/x/incentive/types/codec.go index b70e2a444..6abb8fc7f 100644 --- a/x/incentive/types/codec.go +++ b/x/incentive/types/codec.go @@ -3,13 +3,23 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) func RegisterCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgWithdrawReward{}, "incentive/MsgWithdrawReward", nil) + cdc.RegisterConcrete(&MsgUpdateParams{}, "incentive/MsgUpdateParams", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + // Register messages + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgWithdrawReward{}, + &MsgUpdateParams{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go index 2de00b49d..23390f945 100644 --- a/x/incentive/types/errors.go +++ b/x/incentive/types/errors.go @@ -11,4 +11,5 @@ var ( ErrBTCStakingGaugeNotFound = errorsmod.Register(ModuleName, 1100, "BTC staking gauge not found") ErrBTCTimestampingGaugeNotFound = errorsmod.Register(ModuleName, 1101, "BTC timestamping gauge not found") ErrRewardGaugeNotFound = errorsmod.Register(ModuleName, 1102, "reward gauge not found") + ErrNoWithdrawableCoins = errorsmod.Register(ModuleName, 1103, "no coin is withdrawable") ) diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index 7e7379fce..696ac50f9 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -15,6 +15,7 @@ type BankKeeper interface { SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error } type EpochingKeeper interface { diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index 416cb3063..f8535ed73 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -21,6 +21,37 @@ func NewRewardGauge(coins sdk.Coins) *RewardGauge { } } +// GetWithdrawableCoins returns withdrawable coins in this reward gauge +func (rg *RewardGauge) GetWithdrawableCoins() sdk.Coins { + withdrawableCoins := sdk.NewCoins() + for _, coin := range rg.Coins { + found, withdrawnCoin := rg.WithdrawnCoins.Find(coin.Denom) + // if the coin is not found in withdrawn coins, all of this coin is withdrawable + if !found { + withdrawableCoins = withdrawableCoins.Add(coin) + continue + } + // if the withdrawable amount is positive, then the coin with this amount is withdrawable + withdrawableCoinAmount := coin.Amount.Sub(withdrawnCoin.Amount) + if withdrawableCoinAmount.IsPositive() { + withdrawableCoin := sdk.NewCoin(coin.Denom, withdrawableCoinAmount) + withdrawableCoins = withdrawableCoins.Add(withdrawableCoin) + } + } + return withdrawableCoins +} + +// Clear makes the reward gauge to have no withdrawable coins +// typically called after the stakeholder withdraws its reward +func (rg *RewardGauge) Clear() { + rg.WithdrawnCoins = sdk.NewCoins(rg.Coins...) +} + +// IsEmpty returns whether the reward gauge has nothing to withdraw +func (rg *RewardGauge) IsEmpty() bool { + return rg.Coins.IsEqual(rg.WithdrawnCoins) +} + func GetCoinsPortion(coinsInt sdk.Coins, portion math.LegacyDec) sdk.Coins { // coins with decimal value coins := sdk.NewDecCoinsFromCoins(coinsInt...) @@ -42,6 +73,10 @@ const ( BTCDelegationType ) +func GetAllStakeholderTypes() []StakeholderType { + return []StakeholderType{SubmitterType, ReporterType, BTCValidatorType, BTCDelegationType} +} + func NewStakeHolderType(stBytes []byte) (StakeholderType, error) { if len(stBytes) != 1 { return SubmitterType, fmt.Errorf("invalid format for stBytes") diff --git a/x/incentive/types/mocked_keepers.go b/x/incentive/types/mocked_keepers.go index 5483f9862..4cfffc5f9 100644 --- a/x/incentive/types/mocked_keepers.go +++ b/x/incentive/types/mocked_keepers.go @@ -101,6 +101,20 @@ func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBalances", reflect.TypeOf((*MockBankKeeper)(nil).GetAllBalances), ctx, addr) } +// SendCoinsFromModuleToAccount mocks base method. +func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types0.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromModuleToAccount indicates an expected call of SendCoinsFromModuleToAccount. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToAccount", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToAccount), ctx, senderModule, recipientAddr, amt) +} + // SendCoinsFromModuleToModule mocks base method. func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx types0.Context, senderModule, recipientModule string, amt types0.Coins) error { m.ctrl.T.Helper() diff --git a/x/incentive/types/msg.go b/x/incentive/types/msg.go new file mode 100644 index 000000000..9614281f6 --- /dev/null +++ b/x/incentive/types/msg.go @@ -0,0 +1,54 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ensure that these message types implement the sdk.Msg interface +var ( + _ sdk.Msg = &MsgWithdrawReward{} + _ sdk.Msg = &MsgUpdateParams{} +) + +// GetSigners returns the expected signers for a MsgUpdateParams message. +func (m *MsgWithdrawReward) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(m.Signer) + return []sdk.AccAddress{addr} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgWithdrawReward) ValidateBasic() error { + if len(m.Signer) == 0 { + return fmt.Errorf("empty signer") + } + if _, err := NewStakeHolderTypeFromString(m.Type); err != nil { + return err + } + if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { + return err + } + + return nil +} + +// GetSigners returns the expected signers for a MsgUpdateParams message. +func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(m.Authority) + return []sdk.AccAddress{addr} +} + +// ValidateBasic does a sanity check on the provided data. +func (m *MsgUpdateParams) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { + return errorsmod.Wrap(err, "invalid authority address") + } + + if err := m.Params.Validate(); err != nil { + return err + } + + return nil +} diff --git a/x/incentive/types/query.pb.go b/x/incentive/types/query.pb.go index 5835b727a..bacf7178d 100644 --- a/x/incentive/types/query.pb.go +++ b/x/incentive/types/query.pb.go @@ -112,27 +112,24 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } -// QueryRewardGaugeRequest is request type for the Query/RewardGauge RPC method. -type QueryRewardGaugeRequest struct { - // type is the type of the stakeholder, can be one of - // {submitter, reporter, btc_validator, btc_delegation} - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` +// QueryRewardGaugesRequest is request type for the Query/RewardGauges RPC method. +type QueryRewardGaugesRequest struct { // address is the address of the stakeholder in bech32 string - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` } -func (m *QueryRewardGaugeRequest) Reset() { *m = QueryRewardGaugeRequest{} } -func (m *QueryRewardGaugeRequest) String() string { return proto.CompactTextString(m) } -func (*QueryRewardGaugeRequest) ProtoMessage() {} -func (*QueryRewardGaugeRequest) Descriptor() ([]byte, []int) { +func (m *QueryRewardGaugesRequest) Reset() { *m = QueryRewardGaugesRequest{} } +func (m *QueryRewardGaugesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRewardGaugesRequest) ProtoMessage() {} +func (*QueryRewardGaugesRequest) Descriptor() ([]byte, []int) { return fileDescriptor_e1a59cc0c7c44135, []int{2} } -func (m *QueryRewardGaugeRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryRewardGaugesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryRewardGaugeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryRewardGaugesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryRewardGaugeRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryRewardGaugesRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -142,50 +139,44 @@ func (m *QueryRewardGaugeRequest) XXX_Marshal(b []byte, deterministic bool) ([]b return b[:n], nil } } -func (m *QueryRewardGaugeRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryRewardGaugeRequest.Merge(m, src) +func (m *QueryRewardGaugesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRewardGaugesRequest.Merge(m, src) } -func (m *QueryRewardGaugeRequest) XXX_Size() int { +func (m *QueryRewardGaugesRequest) XXX_Size() int { return m.Size() } -func (m *QueryRewardGaugeRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryRewardGaugeRequest.DiscardUnknown(m) +func (m *QueryRewardGaugesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRewardGaugesRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryRewardGaugeRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryRewardGaugesRequest proto.InternalMessageInfo -func (m *QueryRewardGaugeRequest) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *QueryRewardGaugeRequest) GetAddress() string { +func (m *QueryRewardGaugesRequest) GetAddress() string { if m != nil { return m.Address } return "" } -// QueryRewardGaugeResponse is response type for the Query/RewardGauge RPC method. -type QueryRewardGaugeResponse struct { - // reward_gauge is the reward gauge holding all rewards for the stakeholder - RewardGauge *RewardGauge `protobuf:"bytes,1,opt,name=reward_gauge,json=rewardGauge,proto3" json:"reward_gauge,omitempty"` +// QueryRewardGaugesResponse is response type for the Query/RewardGauges RPC method. +type QueryRewardGaugesResponse struct { + // reward_gauges is the map of reward gauges, where key is the stakeholder type + // and value is the reward gauge holding all rewards for the stakeholder in that type + RewardGauges map[string]*RewardGauge `protobuf:"bytes,1,rep,name=reward_gauges,json=rewardGauges,proto3" json:"reward_gauges,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } -func (m *QueryRewardGaugeResponse) Reset() { *m = QueryRewardGaugeResponse{} } -func (m *QueryRewardGaugeResponse) String() string { return proto.CompactTextString(m) } -func (*QueryRewardGaugeResponse) ProtoMessage() {} -func (*QueryRewardGaugeResponse) Descriptor() ([]byte, []int) { +func (m *QueryRewardGaugesResponse) Reset() { *m = QueryRewardGaugesResponse{} } +func (m *QueryRewardGaugesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryRewardGaugesResponse) ProtoMessage() {} +func (*QueryRewardGaugesResponse) Descriptor() ([]byte, []int) { return fileDescriptor_e1a59cc0c7c44135, []int{3} } -func (m *QueryRewardGaugeResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryRewardGaugesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryRewardGaugeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryRewardGaugesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryRewardGaugeResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryRewardGaugesResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -195,21 +186,21 @@ func (m *QueryRewardGaugeResponse) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *QueryRewardGaugeResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryRewardGaugeResponse.Merge(m, src) +func (m *QueryRewardGaugesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRewardGaugesResponse.Merge(m, src) } -func (m *QueryRewardGaugeResponse) XXX_Size() int { +func (m *QueryRewardGaugesResponse) XXX_Size() int { return m.Size() } -func (m *QueryRewardGaugeResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryRewardGaugeResponse.DiscardUnknown(m) +func (m *QueryRewardGaugesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRewardGaugesResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryRewardGaugeResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryRewardGaugesResponse proto.InternalMessageInfo -func (m *QueryRewardGaugeResponse) GetRewardGauge() *RewardGauge { +func (m *QueryRewardGaugesResponse) GetRewardGauges() map[string]*RewardGauge { if m != nil { - return m.RewardGauge + return m.RewardGauges } return nil } @@ -401,8 +392,9 @@ func (m *QueryBTCTimestampingGaugeResponse) GetGauge() *Gauge { func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.incentive.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.incentive.QueryParamsResponse") - proto.RegisterType((*QueryRewardGaugeRequest)(nil), "babylon.incentive.QueryRewardGaugeRequest") - proto.RegisterType((*QueryRewardGaugeResponse)(nil), "babylon.incentive.QueryRewardGaugeResponse") + proto.RegisterType((*QueryRewardGaugesRequest)(nil), "babylon.incentive.QueryRewardGaugesRequest") + proto.RegisterType((*QueryRewardGaugesResponse)(nil), "babylon.incentive.QueryRewardGaugesResponse") + proto.RegisterMapType((map[string]*RewardGauge)(nil), "babylon.incentive.QueryRewardGaugesResponse.RewardGaugesEntry") proto.RegisterType((*QueryBTCStakingGaugeRequest)(nil), "babylon.incentive.QueryBTCStakingGaugeRequest") proto.RegisterType((*QueryBTCStakingGaugeResponse)(nil), "babylon.incentive.QueryBTCStakingGaugeResponse") proto.RegisterType((*QueryBTCTimestampingGaugeRequest)(nil), "babylon.incentive.QueryBTCTimestampingGaugeRequest") @@ -412,43 +404,46 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/query.proto", fileDescriptor_e1a59cc0c7c44135) } var fileDescriptor_e1a59cc0c7c44135 = []byte{ - // 573 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x4f, 0x6b, 0x13, 0x41, - 0x18, 0xc6, 0xb3, 0x25, 0x89, 0xf6, 0x8d, 0x20, 0x8e, 0x41, 0xe3, 0xb6, 0xae, 0xed, 0x82, 0xa5, - 0x28, 0xec, 0x60, 0xab, 0x08, 0x22, 0x88, 0x51, 0x09, 0x28, 0x06, 0x4d, 0x7b, 0x12, 0x24, 0x4c, - 0x92, 0x61, 0xb3, 0xd8, 0x9d, 0xd9, 0xee, 0xce, 0xaa, 0x21, 0xe4, 0xe2, 0xc5, 0xab, 0xe0, 0x67, - 0xd1, 0xcf, 0xd0, 0x8b, 0x50, 0xf4, 0xe2, 0x49, 0x24, 0xf1, 0x83, 0x48, 0x66, 0x26, 0x71, 0xdb, - 0xdd, 0x34, 0xb1, 0xb7, 0x99, 0xf7, 0xcf, 0xf3, 0xfe, 0x5e, 0xe6, 0xd9, 0x85, 0xab, 0x2d, 0xd2, - 0xea, 0xed, 0x71, 0x86, 0x3d, 0xd6, 0xa6, 0x4c, 0x78, 0x6f, 0x29, 0xde, 0x8f, 0x69, 0xd8, 0x73, - 0x82, 0x90, 0x0b, 0x8e, 0x2e, 0xe8, 0xb4, 0x33, 0x4d, 0x9b, 0x65, 0x97, 0xbb, 0x5c, 0x66, 0xf1, - 0xf8, 0xa4, 0x0a, 0xcd, 0x55, 0x97, 0x73, 0x77, 0x8f, 0x62, 0x12, 0x78, 0x98, 0x30, 0xc6, 0x05, - 0x11, 0x1e, 0x67, 0x91, 0xce, 0x5a, 0xe9, 0x29, 0x01, 0x09, 0x89, 0x3f, 0xc9, 0xaf, 0xa7, 0xf3, - 0xd3, 0x93, 0x2a, 0xb1, 0xcb, 0x80, 0x5e, 0x8e, 0xc1, 0x5e, 0xc8, 0xbe, 0x06, 0xdd, 0x8f, 0x69, - 0x24, 0xec, 0x3a, 0x5c, 0x3c, 0x12, 0x8d, 0x02, 0xce, 0x22, 0x8a, 0xee, 0x42, 0x51, 0xe9, 0x57, - 0x8c, 0x35, 0x63, 0xb3, 0xb4, 0x75, 0xc5, 0x49, 0xed, 0xe1, 0xa8, 0x96, 0x6a, 0xfe, 0xe0, 0xd7, - 0xb5, 0x5c, 0x43, 0x97, 0xdb, 0x35, 0xb8, 0x2c, 0xf5, 0x1a, 0xf4, 0x1d, 0x09, 0x3b, 0x35, 0x12, - 0xbb, 0x54, 0x8f, 0x42, 0x08, 0xf2, 0xa2, 0x17, 0x50, 0xa9, 0xb8, 0xdc, 0x90, 0x67, 0x54, 0x81, - 0x33, 0xa4, 0xd3, 0x09, 0x69, 0x14, 0x55, 0x96, 0x64, 0x78, 0x72, 0xb5, 0x5f, 0x43, 0x25, 0x2d, - 0xa4, 0xe9, 0x1e, 0xc2, 0xb9, 0x50, 0x86, 0x9b, 0xee, 0x38, 0xae, 0x19, 0xad, 0x0c, 0xc6, 0x64, - 0x77, 0x29, 0xfc, 0x77, 0xb1, 0xef, 0xc0, 0x8a, 0x94, 0xaf, 0xee, 0x3e, 0xda, 0x11, 0xe4, 0x8d, - 0xc7, 0xdc, 0x23, 0xac, 0x97, 0xa0, 0xd8, 0xa5, 0x9e, 0xdb, 0x15, 0x52, 0x3b, 0xdf, 0xd0, 0x37, - 0xbb, 0x0e, 0xab, 0xd9, 0x6d, 0x9a, 0xcc, 0x81, 0x42, 0x12, 0xa9, 0x92, 0x81, 0xa4, 0x1a, 0x54, - 0x99, 0xfd, 0x00, 0xd6, 0x26, 0x7a, 0xbb, 0x9e, 0x4f, 0x23, 0x41, 0xfc, 0xe0, 0x38, 0xcb, 0x0a, - 0x2c, 0xd3, 0x80, 0xb7, 0xbb, 0x4d, 0x16, 0xfb, 0x1a, 0xe7, 0xac, 0x0c, 0xd4, 0x63, 0xdf, 0xde, - 0x81, 0xf5, 0x13, 0x04, 0x4e, 0x47, 0xb5, 0xf5, 0xbd, 0x00, 0x05, 0xa9, 0x8a, 0x3e, 0x1a, 0x50, - 0x54, 0xef, 0x8c, 0xae, 0x67, 0x74, 0xa5, 0x0d, 0x65, 0x6e, 0xcc, 0x2b, 0x53, 0x4c, 0xb6, 0xf3, - 0xe1, 0xc7, 0x9f, 0xcf, 0x4b, 0x9b, 0x68, 0x03, 0xeb, 0xfa, 0x76, 0x97, 0x78, 0x0c, 0xcf, 0xf2, - 0x39, 0xfa, 0x62, 0x40, 0x29, 0xf1, 0x9a, 0xe8, 0xc6, 0xac, 0x39, 0x69, 0xe7, 0x99, 0x37, 0x17, - 0xaa, 0xd5, 0x60, 0xcf, 0x25, 0x58, 0x0d, 0x3d, 0x99, 0x07, 0xd6, 0x1f, 0x3b, 0x78, 0x80, 0xb5, - 0x61, 0x71, 0x5f, 0x1f, 0x06, 0x38, 0xe9, 0x4d, 0xf4, 0xd5, 0x80, 0xf3, 0xc7, 0xdc, 0x82, 0x9c, - 0x59, 0x3c, 0xd9, 0x6e, 0x34, 0xf1, 0xc2, 0xf5, 0x7a, 0x87, 0xaa, 0xdc, 0xe1, 0x3e, 0xba, 0x37, - 0x6f, 0x87, 0x96, 0x68, 0x37, 0x23, 0xa5, 0xa0, 0x78, 0x71, 0x5f, 0x39, 0x7d, 0x80, 0xbe, 0x19, - 0x50, 0xce, 0x72, 0x15, 0xda, 0x3e, 0x81, 0x66, 0x96, 0x89, 0xcd, 0xdb, 0xff, 0xd7, 0xa4, 0xf7, - 0x78, 0x2a, 0xf7, 0x78, 0x8c, 0xaa, 0x8b, 0xec, 0x21, 0x12, 0x32, 0x93, 0x65, 0xa6, 0x1f, 0xce, - 0xa0, 0xfa, 0xec, 0x60, 0x68, 0x19, 0x87, 0x43, 0xcb, 0xf8, 0x3d, 0xb4, 0x8c, 0x4f, 0x23, 0x2b, - 0x77, 0x38, 0xb2, 0x72, 0x3f, 0x47, 0x56, 0xee, 0xd5, 0x2d, 0xd7, 0x13, 0xdd, 0xb8, 0xe5, 0xb4, - 0xb9, 0x9f, 0x3d, 0xe7, 0x7d, 0x62, 0xd2, 0xf8, 0xd1, 0xa3, 0x56, 0x51, 0xfe, 0x53, 0xb7, 0xff, - 0x06, 0x00, 0x00, 0xff, 0xff, 0x52, 0xb0, 0xcc, 0xeb, 0xfe, 0x05, 0x00, 0x00, + // 615 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x4f, 0x6f, 0xd3, 0x30, + 0x18, 0xc6, 0xeb, 0x6e, 0x2d, 0xcc, 0x1b, 0x82, 0x99, 0x0a, 0x65, 0xd9, 0x08, 0x5d, 0x24, 0xa6, + 0x4a, 0xa0, 0x44, 0x74, 0x45, 0xa0, 0x09, 0x0d, 0x54, 0xfe, 0x49, 0x20, 0x55, 0x90, 0xed, 0xc4, + 0xa5, 0x72, 0x53, 0x2b, 0x8d, 0xd6, 0xc4, 0x59, 0xe2, 0x0c, 0xaa, 0xa9, 0x17, 0x2e, 0x5c, 0x91, + 0xf8, 0x24, 0x5c, 0xf8, 0x0c, 0xbb, 0x80, 0x26, 0x71, 0xe1, 0x84, 0xa0, 0xe5, 0x83, 0xa0, 0xda, + 0x6e, 0x95, 0xad, 0xe9, 0xda, 0xed, 0xe6, 0xfa, 0x7d, 0xfc, 0xbc, 0xbf, 0xd7, 0x79, 0x5c, 0x78, + 0xb3, 0x81, 0x1b, 0x9d, 0x36, 0xf5, 0x4d, 0xd7, 0xb7, 0x89, 0xcf, 0xdc, 0x03, 0x62, 0xee, 0xc7, + 0x24, 0xec, 0x18, 0x41, 0x48, 0x19, 0x45, 0xcb, 0xb2, 0x6c, 0x8c, 0xca, 0x6a, 0xc1, 0xa1, 0x0e, + 0xe5, 0x55, 0x73, 0xb0, 0x12, 0x42, 0x75, 0xcd, 0xa1, 0xd4, 0x69, 0x13, 0x13, 0x07, 0xae, 0x89, + 0x7d, 0x9f, 0x32, 0xcc, 0x5c, 0xea, 0x47, 0xb2, 0xaa, 0x8d, 0x77, 0x09, 0x70, 0x88, 0xbd, 0x61, + 0x7d, 0x7d, 0xbc, 0x3e, 0x5a, 0x09, 0x89, 0x5e, 0x80, 0xe8, 0xed, 0x00, 0xec, 0x0d, 0x3f, 0x67, + 0x91, 0xfd, 0x98, 0x44, 0x4c, 0xaf, 0xc1, 0xeb, 0x27, 0x76, 0xa3, 0x80, 0xfa, 0x11, 0x41, 0x0f, + 0x60, 0x5e, 0xf8, 0x2b, 0xa0, 0x08, 0x4a, 0x8b, 0xe5, 0x15, 0x63, 0x6c, 0x0e, 0x43, 0x1c, 0xa9, + 0xce, 0x1f, 0xfd, 0xbe, 0x95, 0xb1, 0xa4, 0x5c, 0xaf, 0x40, 0x85, 0xfb, 0x59, 0xe4, 0x3d, 0x0e, + 0x9b, 0x2f, 0x71, 0xec, 0x90, 0x61, 0x2f, 0xa4, 0xc0, 0x4b, 0xb8, 0xd9, 0x0c, 0x49, 0x24, 0x5c, + 0x17, 0xac, 0xe1, 0x4f, 0xfd, 0x2f, 0x80, 0x2b, 0x29, 0xc7, 0x24, 0x8c, 0x0d, 0xaf, 0x84, 0x7c, + 0xbf, 0xee, 0xf0, 0x82, 0x02, 0x8a, 0x73, 0xa5, 0xc5, 0xf2, 0x76, 0x0a, 0xd3, 0x44, 0x13, 0x23, + 0xb9, 0xf9, 0xdc, 0x67, 0x61, 0xc7, 0x5a, 0x0a, 0x13, 0x5b, 0x6a, 0x1d, 0x2e, 0x8f, 0x49, 0xd0, + 0x35, 0x38, 0xb7, 0x47, 0x3a, 0x92, 0x76, 0xb0, 0x44, 0x15, 0x98, 0x3b, 0xc0, 0xed, 0x98, 0x28, + 0x59, 0x7e, 0x2f, 0x5a, 0x0a, 0x43, 0xc2, 0xc6, 0x12, 0xe2, 0xad, 0xec, 0x43, 0xa0, 0xdf, 0x87, + 0xab, 0x9c, 0xae, 0xba, 0xfb, 0x74, 0x87, 0xe1, 0x3d, 0xd7, 0x77, 0x84, 0x44, 0x5e, 0xce, 0x0d, + 0x98, 0x6f, 0x11, 0xd7, 0x69, 0x31, 0xde, 0x6d, 0xde, 0x92, 0xbf, 0xf4, 0x1a, 0x5c, 0x4b, 0x3f, + 0x26, 0x2f, 0xc7, 0x80, 0x39, 0x7e, 0x2b, 0xf2, 0x43, 0x29, 0x29, 0x40, 0x12, 0x85, 0xcb, 0xf4, + 0xc7, 0xb0, 0x38, 0xf4, 0xdb, 0x75, 0x3d, 0x12, 0x31, 0xec, 0x05, 0xa7, 0x59, 0x56, 0xe1, 0x02, + 0x09, 0xa8, 0xdd, 0xaa, 0xfb, 0xb1, 0x27, 0x71, 0x2e, 0xf3, 0x8d, 0x5a, 0xec, 0xe9, 0x3b, 0x70, + 0xfd, 0x0c, 0x83, 0x8b, 0x51, 0x95, 0x7f, 0xe4, 0x60, 0x8e, 0xbb, 0xa2, 0x4f, 0x00, 0xe6, 0x45, + 0xb2, 0xd0, 0xed, 0x49, 0x1f, 0xf8, 0x44, 0x84, 0xd5, 0x8d, 0x69, 0x32, 0xc1, 0xa4, 0x1b, 0x1f, + 0x7f, 0xfe, 0xfb, 0x92, 0x2d, 0xa1, 0x0d, 0x53, 0xea, 0xed, 0x16, 0x76, 0x7d, 0x73, 0xd2, 0xcb, + 0x42, 0x5f, 0x01, 0x5c, 0x4a, 0x46, 0x02, 0xdd, 0x99, 0x2d, 0x70, 0x82, 0xea, 0xee, 0x79, 0xd2, + 0xa9, 0xbf, 0xe0, 0x6c, 0x4f, 0xd0, 0xf6, 0x34, 0x36, 0xf9, 0x62, 0xcc, 0x43, 0xb9, 0xe8, 0x9a, + 0xc9, 0xa7, 0x81, 0xbe, 0x01, 0x78, 0xf5, 0x54, 0x52, 0x90, 0x31, 0x89, 0x24, 0x3d, 0x89, 0xaa, + 0x39, 0xb3, 0x5e, 0xc2, 0x57, 0x39, 0xfc, 0x23, 0xb4, 0x35, 0x0d, 0xbe, 0xc1, 0xec, 0x7a, 0x24, + 0x1c, 0x04, 0xaf, 0x79, 0x28, 0x52, 0xde, 0x45, 0xdf, 0x01, 0x2c, 0xa4, 0x25, 0x0a, 0x6d, 0x9e, + 0x41, 0x33, 0x29, 0xc0, 0x6a, 0xe5, 0x7c, 0x87, 0xe4, 0x1c, 0xaf, 0xf8, 0x1c, 0xcf, 0x50, 0x75, + 0x96, 0x39, 0x58, 0xc2, 0x66, 0x38, 0xcc, 0xe8, 0xd1, 0x74, 0xab, 0xaf, 0x8f, 0x7a, 0x1a, 0x38, + 0xee, 0x69, 0xe0, 0x4f, 0x4f, 0x03, 0x9f, 0xfb, 0x5a, 0xe6, 0xb8, 0xaf, 0x65, 0x7e, 0xf5, 0xb5, + 0xcc, 0xbb, 0x7b, 0x8e, 0xcb, 0x5a, 0x71, 0xc3, 0xb0, 0xa9, 0x97, 0xde, 0xe7, 0x43, 0xa2, 0x13, + 0xeb, 0x04, 0x24, 0x6a, 0xe4, 0xf9, 0x3f, 0xf8, 0xe6, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, + 0x2f, 0x3f, 0xc6, 0x6c, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -465,8 +460,8 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // RewardGauge queries the reward gauge of a given stakeholder in a given type - RewardGauge(ctx context.Context, in *QueryRewardGaugeRequest, opts ...grpc.CallOption) (*QueryRewardGaugeResponse, error) + // RewardGauge queries the reward gauge of a given stakeholder address + RewardGauges(ctx context.Context, in *QueryRewardGaugesRequest, opts ...grpc.CallOption) (*QueryRewardGaugesResponse, error) // BTCStakingGauge queries the BTC staking gauge of a given height BTCStakingGauge(ctx context.Context, in *QueryBTCStakingGaugeRequest, opts ...grpc.CallOption) (*QueryBTCStakingGaugeResponse, error) // BTCTimestampingGauge queries the BTC timestamping gauge of a given epoch @@ -490,9 +485,9 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } -func (c *queryClient) RewardGauge(ctx context.Context, in *QueryRewardGaugeRequest, opts ...grpc.CallOption) (*QueryRewardGaugeResponse, error) { - out := new(QueryRewardGaugeResponse) - err := c.cc.Invoke(ctx, "/babylon.incentive.Query/RewardGauge", in, out, opts...) +func (c *queryClient) RewardGauges(ctx context.Context, in *QueryRewardGaugesRequest, opts ...grpc.CallOption) (*QueryRewardGaugesResponse, error) { + out := new(QueryRewardGaugesResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Query/RewardGauges", in, out, opts...) if err != nil { return nil, err } @@ -521,8 +516,8 @@ func (c *queryClient) BTCTimestampingGauge(ctx context.Context, in *QueryBTCTime type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // RewardGauge queries the reward gauge of a given stakeholder in a given type - RewardGauge(context.Context, *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) + // RewardGauge queries the reward gauge of a given stakeholder address + RewardGauges(context.Context, *QueryRewardGaugesRequest) (*QueryRewardGaugesResponse, error) // BTCStakingGauge queries the BTC staking gauge of a given height BTCStakingGauge(context.Context, *QueryBTCStakingGaugeRequest) (*QueryBTCStakingGaugeResponse, error) // BTCTimestampingGauge queries the BTC timestamping gauge of a given epoch @@ -536,8 +531,8 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } -func (*UnimplementedQueryServer) RewardGauge(ctx context.Context, req *QueryRewardGaugeRequest) (*QueryRewardGaugeResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RewardGauge not implemented") +func (*UnimplementedQueryServer) RewardGauges(ctx context.Context, req *QueryRewardGaugesRequest) (*QueryRewardGaugesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RewardGauges not implemented") } func (*UnimplementedQueryServer) BTCStakingGauge(ctx context.Context, req *QueryBTCStakingGaugeRequest) (*QueryBTCStakingGaugeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCStakingGauge not implemented") @@ -568,20 +563,20 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } -func _Query_RewardGauge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryRewardGaugeRequest) +func _Query_RewardGauges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRewardGaugesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).RewardGauge(ctx, in) + return srv.(QueryServer).RewardGauges(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.incentive.Query/RewardGauge", + FullMethod: "/babylon.incentive.Query/RewardGauges", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).RewardGauge(ctx, req.(*QueryRewardGaugeRequest)) + return srv.(QueryServer).RewardGauges(ctx, req.(*QueryRewardGaugesRequest)) } return interceptor(ctx, in, info, handler) } @@ -631,8 +626,8 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_Params_Handler, }, { - MethodName: "RewardGauge", - Handler: _Query_RewardGauge_Handler, + MethodName: "RewardGauges", + Handler: _Query_RewardGauges_Handler, }, { MethodName: "BTCStakingGauge", @@ -703,7 +698,7 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryRewardGaugeRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryRewardGaugesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -713,12 +708,12 @@ func (m *QueryRewardGaugeRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryRewardGaugeRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryRewardGaugesRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryRewardGaugeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryRewardGaugesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -728,19 +723,12 @@ func (m *QueryRewardGaugeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) copy(dAtA[i:], m.Address) i = encodeVarintQuery(dAtA, i, uint64(len(m.Address))) i-- - dAtA[i] = 0x12 - } - if len(m.Type) > 0 { - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Type))) - i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryRewardGaugeResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryRewardGaugesResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -750,27 +738,41 @@ func (m *QueryRewardGaugeResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryRewardGaugeResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryRewardGaugesResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryRewardGaugeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryRewardGaugesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.RewardGauge != nil { - { - size, err := m.RewardGauge.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) + if len(m.RewardGauges) > 0 { + for k := range m.RewardGauges { + v := m.RewardGauges[k] + baseI := i + if v != nil { + { + size, err := v.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintQuery(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintQuery(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa } - i-- - dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -932,16 +934,12 @@ func (m *QueryParamsResponse) Size() (n int) { return n } -func (m *QueryRewardGaugeRequest) Size() (n int) { +func (m *QueryRewardGaugesRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Type) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } l = len(m.Address) if l > 0 { n += 1 + l + sovQuery(uint64(l)) @@ -949,15 +947,24 @@ func (m *QueryRewardGaugeRequest) Size() (n int) { return n } -func (m *QueryRewardGaugeResponse) Size() (n int) { +func (m *QueryRewardGaugesResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.RewardGauge != nil { - l = m.RewardGauge.Size() - n += 1 + l + sovQuery(uint64(l)) + if len(m.RewardGauges) > 0 { + for k, v := range m.RewardGauges { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovQuery(uint64(l)) + } + mapEntrySize := 1 + len(k) + sovQuery(uint64(len(k))) + l + n += mapEntrySize + 1 + sovQuery(uint64(mapEntrySize)) + } } return n } @@ -1151,7 +1158,7 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryRewardGaugeRequest) Unmarshal(dAtA []byte) error { +func (m *QueryRewardGaugesRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1174,45 +1181,13 @@ func (m *QueryRewardGaugeRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryRewardGaugeRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryRewardGaugesRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryRewardGaugeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryRewardGaugesRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) } @@ -1265,7 +1240,7 @@ func (m *QueryRewardGaugeRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryRewardGaugeResponse) Unmarshal(dAtA []byte) error { +func (m *QueryRewardGaugesResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1288,15 +1263,15 @@ func (m *QueryRewardGaugeResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryRewardGaugeResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryRewardGaugesResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryRewardGaugeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryRewardGaugesResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RewardGauge", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RewardGauges", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1323,12 +1298,105 @@ func (m *QueryRewardGaugeResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.RewardGauge == nil { - m.RewardGauge = &RewardGauge{} - } - if err := m.RewardGauge.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + if m.RewardGauges == nil { + m.RewardGauges = make(map[string]*RewardGauge) + } + var mapkey string + var mapvalue *RewardGauge + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthQuery + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthQuery + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthQuery + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthQuery + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &RewardGauge{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } } + m.RewardGauges[mapkey] = mapvalue iNdEx = postIndex default: iNdEx = preIndex diff --git a/x/incentive/types/query.pb.gw.go b/x/incentive/types/query.pb.gw.go index a6abdbb28..5b8468f06 100644 --- a/x/incentive/types/query.pb.gw.go +++ b/x/incentive/types/query.pb.gw.go @@ -51,8 +51,8 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } -func request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryRewardGaugeRequest +func request_Query_RewardGauges_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRewardGaugesRequest var metadata runtime.ServerMetadata var ( @@ -62,17 +62,6 @@ func request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshale _ = err ) - val, ok = pathParams["type"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") - } - - protoReq.Type, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) - } - val, ok = pathParams["address"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address") @@ -84,13 +73,13 @@ func request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshale return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err) } - msg, err := client.RewardGauge(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.RewardGauges(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryRewardGaugeRequest +func local_request_Query_RewardGauges_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRewardGaugesRequest var metadata runtime.ServerMetadata var ( @@ -100,17 +89,6 @@ func local_request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Ma _ = err ) - val, ok = pathParams["type"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") - } - - protoReq.Type, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) - } - val, ok = pathParams["address"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address") @@ -122,7 +100,7 @@ func local_request_Query_RewardGauge_0(ctx context.Context, marshaler runtime.Ma return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err) } - msg, err := server.RewardGauge(ctx, &protoReq) + msg, err := server.RewardGauges(ctx, &protoReq) return msg, metadata, err } @@ -264,7 +242,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_RewardGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_RewardGauges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -275,7 +253,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_RewardGauge_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_RewardGauges_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -283,7 +261,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_RewardGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_RewardGauges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -394,7 +372,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_RewardGauge_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_RewardGauges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -403,14 +381,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_RewardGauge_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_RewardGauges_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_RewardGauge_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_RewardGauges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -460,7 +438,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylonchain", "babylon", "incentive", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_RewardGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylonchain", "babylon", "incentive", "type", "address", "reward_gauge"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_RewardGauges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"babylonchain", "babylon", "incentive", "address", "reward_gauge"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCStakingGauge_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylonchain", "babylon", "incentive", "btc_staking_gauge", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -470,7 +448,7 @@ var ( var ( forward_Query_Params_0 = runtime.ForwardResponseMessage - forward_Query_RewardGauge_0 = runtime.ForwardResponseMessage + forward_Query_RewardGauges_0 = runtime.ForwardResponseMessage forward_Query_BTCStakingGauge_0 = runtime.ForwardResponseMessage diff --git a/x/incentive/types/tx.pb.go b/x/incentive/types/tx.pb.go index cd3ebaade..63bbbc6fb 100644 --- a/x/incentive/types/tx.pb.go +++ b/x/incentive/types/tx.pb.go @@ -6,10 +6,19 @@ package types import ( context "context" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,18 +32,253 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. +type MsgWithdrawReward struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // {submitter, reporter, btc_validator, btc_delegation} + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + // address is the address of the stakeholder in bech32 string + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` +} + +func (m *MsgWithdrawReward) Reset() { *m = MsgWithdrawReward{} } +func (m *MsgWithdrawReward) String() string { return proto.CompactTextString(m) } +func (*MsgWithdrawReward) ProtoMessage() {} +func (*MsgWithdrawReward) Descriptor() ([]byte, []int) { + return fileDescriptor_b4de6776d39a3a22, []int{0} +} +func (m *MsgWithdrawReward) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWithdrawReward) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWithdrawReward.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWithdrawReward) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWithdrawReward.Merge(m, src) +} +func (m *MsgWithdrawReward) XXX_Size() int { + return m.Size() +} +func (m *MsgWithdrawReward) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWithdrawReward.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWithdrawReward proto.InternalMessageInfo + +func (m *MsgWithdrawReward) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgWithdrawReward) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *MsgWithdrawReward) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +// MsgWithdrawRewardResponse is the response to the MsgWithdrawReward message +type MsgWithdrawRewardResponse struct { + // coins is the withdrawed coins + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` +} + +func (m *MsgWithdrawRewardResponse) Reset() { *m = MsgWithdrawRewardResponse{} } +func (m *MsgWithdrawRewardResponse) String() string { return proto.CompactTextString(m) } +func (*MsgWithdrawRewardResponse) ProtoMessage() {} +func (*MsgWithdrawRewardResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b4de6776d39a3a22, []int{1} +} +func (m *MsgWithdrawRewardResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWithdrawRewardResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWithdrawRewardResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWithdrawRewardResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWithdrawRewardResponse.Merge(m, src) +} +func (m *MsgWithdrawRewardResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgWithdrawRewardResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWithdrawRewardResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWithdrawRewardResponse proto.InternalMessageInfo + +func (m *MsgWithdrawRewardResponse) GetCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.Coins + } + return nil +} + +// MsgUpdateParams defines a message for updating incentive module parameters. +type MsgUpdateParams struct { + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params defines the incentive parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_b4de6776d39a3a22, []int{2} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b4de6776d39a3a22, []int{3} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgWithdrawReward)(nil), "babylon.incentive.MsgWithdrawReward") + proto.RegisterType((*MsgWithdrawRewardResponse)(nil), "babylon.incentive.MsgWithdrawRewardResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "babylon.incentive.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.incentive.MsgUpdateParamsResponse") +} + func init() { proto.RegisterFile("babylon/incentive/tx.proto", fileDescriptor_b4de6776d39a3a22) } var fileDescriptor_b4de6776d39a3a22 = []byte{ - // 128 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0xa9, - 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0xca, 0xe9, 0xc1, 0xe5, 0x8c, 0x58, 0xb9, - 0x98, 0x7d, 0x8b, 0xd3, 0x9d, 0xbc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, - 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, - 0xca, 0x30, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xaa, 0x3d, 0x39, - 0x23, 0x31, 0x33, 0x0f, 0xc6, 0xd1, 0xaf, 0x40, 0xb6, 0xa9, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, - 0x6c, 0x9b, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x97, 0x5a, 0x57, 0x4d, 0x8b, 0x00, 0x00, 0x00, + // 468 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x6b, 0x13, 0x41, + 0x1c, 0xcd, 0x9a, 0x1a, 0xe9, 0x28, 0x95, 0x0e, 0xc5, 0x6e, 0xf6, 0xb0, 0x2d, 0x8b, 0x87, 0x50, + 0xec, 0x8e, 0xa9, 0xa0, 0xe0, 0xcd, 0x78, 0x94, 0x80, 0xac, 0x88, 0xe8, 0x41, 0x99, 0xdd, 0x1d, + 0x26, 0x83, 0x66, 0x66, 0xd9, 0xdf, 0x34, 0x6d, 0x2e, 0x1e, 0xfc, 0x04, 0xe2, 0xc7, 0xf0, 0xe4, + 0xc1, 0x0f, 0x51, 0xf0, 0x52, 0x3c, 0x79, 0x52, 0x49, 0x0e, 0x7e, 0x0d, 0x99, 0x3f, 0xdb, 0xc6, + 0x26, 0x60, 0x4f, 0x33, 0xbf, 0x79, 0x6f, 0xde, 0xbc, 0xf7, 0xfb, 0xed, 0xa2, 0x28, 0xa7, 0xf9, + 0xf4, 0x9d, 0x92, 0x44, 0xc8, 0x82, 0x49, 0x2d, 0x26, 0x8c, 0xe8, 0xe3, 0xb4, 0xaa, 0x95, 0x56, + 0x78, 0xd3, 0x63, 0xe9, 0x19, 0x16, 0x6d, 0x71, 0xc5, 0x95, 0x45, 0x89, 0xd9, 0x39, 0x62, 0xd4, + 0x2d, 0x14, 0x8c, 0x15, 0xbc, 0x71, 0x80, 0x2b, 0x3c, 0xb4, 0xed, 0x2a, 0x32, 0x06, 0x4e, 0x26, + 0x7d, 0xb3, 0x78, 0x20, 0xf6, 0x40, 0x4e, 0x81, 0x91, 0x49, 0x3f, 0x67, 0x9a, 0xf6, 0x49, 0xa1, + 0x84, 0x6c, 0xf0, 0x65, 0x63, 0x15, 0xad, 0xe9, 0xd8, 0x0b, 0x27, 0x2f, 0xd1, 0xe6, 0x10, 0xf8, + 0x0b, 0xa1, 0x47, 0x65, 0x4d, 0x8f, 0x32, 0x76, 0x44, 0xeb, 0x12, 0xdf, 0x42, 0x1d, 0x10, 0x5c, + 0xb2, 0x3a, 0x0c, 0x76, 0x83, 0xde, 0x7a, 0xe6, 0x2b, 0x8c, 0xd1, 0x9a, 0x9e, 0x56, 0x2c, 0xbc, + 0x62, 0x4f, 0xed, 0x1e, 0x87, 0xe8, 0x1a, 0x2d, 0xcb, 0x9a, 0x01, 0x84, 0x6d, 0x7b, 0xdc, 0x94, + 0xc9, 0x7b, 0xd4, 0x5d, 0x92, 0xce, 0x18, 0x54, 0x4a, 0x02, 0xc3, 0x14, 0x5d, 0x35, 0x2e, 0x21, + 0x0c, 0x76, 0xdb, 0xbd, 0xeb, 0x07, 0xdd, 0xd4, 0xc7, 0x35, 0x39, 0x52, 0x9f, 0x23, 0x7d, 0xac, + 0x84, 0x1c, 0xdc, 0x3d, 0xf9, 0xb9, 0xd3, 0xfa, 0xfc, 0x6b, 0xa7, 0xc7, 0x85, 0x1e, 0x1d, 0xe6, + 0x69, 0xa1, 0xc6, 0xbe, 0x37, 0x7e, 0xd9, 0x87, 0xf2, 0x2d, 0x31, 0x5e, 0xc0, 0x5e, 0x80, 0xcc, + 0x29, 0x27, 0x9f, 0x02, 0x74, 0x73, 0x08, 0xfc, 0x79, 0x55, 0x52, 0xcd, 0x9e, 0xda, 0xd0, 0xf8, + 0x3e, 0x5a, 0xa7, 0x87, 0x7a, 0xa4, 0x6a, 0xa1, 0xa7, 0x2e, 0xdc, 0x20, 0xfc, 0xfe, 0x75, 0x7f, + 0xcb, 0xbf, 0xfe, 0xc8, 0x59, 0x7f, 0xa6, 0x6b, 0x21, 0x79, 0x76, 0x4e, 0xc5, 0x0f, 0x50, 0xc7, + 0xb5, 0xcd, 0x66, 0x37, 0x7e, 0x97, 0x86, 0x9a, 0xba, 0x27, 0x06, 0x6b, 0xc6, 0x6f, 0xe6, 0xe9, + 0x0f, 0x37, 0x3e, 0xfc, 0xf9, 0xb2, 0x77, 0x2e, 0x94, 0x74, 0xd1, 0xf6, 0x05, 0x4f, 0x4d, 0x4b, + 0x0e, 0xbe, 0x05, 0xa8, 0x3d, 0x04, 0x8e, 0x4b, 0xb4, 0x71, 0x61, 0x1e, 0xb7, 0x57, 0xbc, 0xb6, + 0xd4, 0xda, 0xe8, 0xce, 0x65, 0x58, 0x67, 0x03, 0x78, 0x8d, 0x6e, 0xfc, 0xd3, 0x99, 0x64, 0xf5, + 0xed, 0x45, 0x4e, 0xb4, 0xf7, 0x7f, 0x4e, 0xa3, 0x3f, 0x78, 0x72, 0x32, 0x8b, 0x83, 0xd3, 0x59, + 0x1c, 0xfc, 0x9e, 0xc5, 0xc1, 0xc7, 0x79, 0xdc, 0x3a, 0x9d, 0xc7, 0xad, 0x1f, 0xf3, 0xb8, 0xf5, + 0xaa, 0xbf, 0x30, 0x48, 0xaf, 0x57, 0x8c, 0xa8, 0x90, 0x4d, 0x41, 0x8e, 0x17, 0xff, 0x22, 0x33, + 0xd7, 0xbc, 0x63, 0x3f, 0xd6, 0x7b, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xa2, 0x06, 0x75, + 0x67, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -49,6 +293,10 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { + // WithdrawReward defines a method to withdraw rewards of a stakeholder + WithdrawReward(ctx context.Context, in *MsgWithdrawReward, opts ...grpc.CallOption) (*MsgWithdrawRewardResponse, error) + // UpdateParams updates the incentive module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -59,22 +307,797 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } +func (c *msgClient) WithdrawReward(ctx context.Context, in *MsgWithdrawReward, opts ...grpc.CallOption) (*MsgWithdrawRewardResponse, error) { + out := new(MsgWithdrawRewardResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Msg/WithdrawReward", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.incentive.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { + // WithdrawReward defines a method to withdraw rewards of a stakeholder + WithdrawReward(context.Context, *MsgWithdrawReward) (*MsgWithdrawRewardResponse, error) + // UpdateParams updates the incentive module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } +func (*UnimplementedMsgServer) WithdrawReward(ctx context.Context, req *MsgWithdrawReward) (*MsgWithdrawRewardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawReward not implemented") +} +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } +func _Msg_WithdrawReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgWithdrawReward) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).WithdrawReward(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Msg/WithdrawReward", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).WithdrawReward(ctx, req.(*MsgWithdrawReward)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.incentive.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.incentive.Msg", HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "babylon/incentive/tx.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "WithdrawReward", + Handler: _Msg_WithdrawReward_Handler, + }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "babylon/incentive/tx.proto", +} + +func (m *MsgWithdrawReward) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWithdrawReward) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWithdrawReward) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintTx(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0x1a + } + if len(m.Type) > 0 { + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintTx(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgWithdrawRewardResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWithdrawRewardResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWithdrawRewardResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgWithdrawReward) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Type) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Address) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgWithdrawRewardResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n } + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgWithdrawReward) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWithdrawReward: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWithdrawReward: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgWithdrawRewardResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWithdrawRewardResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWithdrawRewardResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) From 1e8b5fa18429e3a2bfd0200b9f7772d54e4485b3 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Tue, 5 Sep 2023 13:21:23 +0200 Subject: [PATCH 073/202] Unbonding tx signatures (#69) - add handling of jury and validator signatures for unbonding transaction. --- proto/babylon/btcstaking/v1/btcstaking.proto | 6 +- proto/babylon/btcstaking/v1/tx.proto | 49 +- test/e2e/btc_staking_e2e_test.go | 87 + .../configurer/chain/commands_btcstaking.go | 32 + x/btcstaking/client/cli/tx.go | 107 + x/btcstaking/keeper/btc_delegations.go | 75 +- x/btcstaking/keeper/msg_server.go | 153 ++ x/btcstaking/keeper/msg_server_test.go | 218 +- x/btcstaking/types/btcstaking.go | 139 +- x/btcstaking/types/btcstaking.pb.go | 140 +- x/btcstaking/types/codec.go | 4 + x/btcstaking/types/errors.go | 33 +- x/btcstaking/types/msg.go | 54 + x/btcstaking/types/tx.pb.go | 2029 +++++++++++++---- 14 files changed, 2542 insertions(+), 584 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 62588c739..6be49c4c6 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -157,7 +157,11 @@ enum BTCDelegationStatus { PENDING = 0; // ACTIVE defines a delegation that has voting power ACTIVE = 1; + // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx + // from staker, but not yet received signatures from validator and jury. + // Delegation in this state already lost its voting power. + UNBONDING = 2; // EXPIRED defines a delegation that has expired - EXPIRED = 2; + EXPIRED = 3; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 8fc75517b..1632af979 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -20,8 +20,14 @@ service Msg { rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); // BtcUndelegate undelegates funds from exsitng btc delegation rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); - // AddJurySig handles a signature from jury + // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse); + // AddJuryUnbondingSigs handles two signatures from jury for: + // - unbonding tx submitted to babylon by staker + // - slashing tx corresponding to unbodning tx submitted to babylon by staker + rpc AddJuryUnbondingSigs(MsgAddJuryUnbondingSigs) returns (MsgAddJuryUnbondingSigsResponse); + // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker + rpc AddValidatorUnbondingSig(MsgAddValidatorUnbondingSig) returns (MsgAddValidatorUnbondingSigResponse); // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } @@ -126,3 +132,44 @@ message MsgUpdateParams { // MsgUpdateParamsResponse is the response to the MsgUpdateParams message. message MsgUpdateParamsResponse {} +// MsgAddJuryUnbondingSigs is the message for handling a signature from jury +message MsgAddJuryUnbondingSigs { + string signer = 1; + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + string staking_tx_hash = 4; + // unbonding_tx_sig is the signature of the jury on the unbonding tx submitted to babylon + // the signature follows encoding in BIP-340 spec + bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // slashing_unbonding_tx_sig is the signature of the jury on slashing tx corresponding to unbodning tx submitted to babylon + // the signature follows encoding in BIP-340 spec + bytes slashing_unbonding_tx_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + +} +// MsgAddJurySigResponse is the response for MsgAddJurySig +message MsgAddJuryUnbondingSigsResponse {} + +// MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker +message MsgAddValidatorUnbondingSig { + string signer = 1; + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + string staking_tx_hash = 4; + // unbonding_tx_sig is the signature of validator for unbodning tx submitted to babylon by staker + // the signature follows encoding in BIP-340 spec + bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} +// MsgAddJurySigResponse is the response for MsgAddJurySig +message MsgAddValidatorUnbondingSigResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index b5a7c4fcc..90efa2c1c 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -343,3 +343,90 @@ func (s *BTCStakingTestSuite) Test4SubmitStakerUnbonding() { delegation := valDelegations[0].Dels[0] s.NotNil(delegation.BtcUndelegation) } + +// Test5SubmitStakerUnbonding is an end-to-end test for jury and validator submitting signatures +// for unbonding transaction +func (s *BTCStakingTestSuite) Test5SubmitUnbondingSignatures() { + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(1) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() + + allDelegations := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(allDelegations, 1) + delegatorDelegations := allDelegations[0] + s.Len(delegatorDelegations.Dels, 1) + delegation := delegatorDelegations.Dels[0] + + s.NotNil(delegation.BtcUndelegation) + s.Nil(delegation.BtcUndelegation.ValidatorUnbondingSig) + s.Nil(delegation.BtcUndelegation.JuryUnbondingSig) + s.Nil(delegation.BtcUndelegation.JurySlashingSig) + + // First sent validator signature + stakingTxMsg, err := delegation.StakingTx.ToMsgTx() + s.NoError(err) + stakingTxHash := delegation.StakingTx.MustGetTxHash() + + validatorUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( + stakingTxMsg, + delegation.StakingTx.Script, + valSK, + net, + ) + s.NoError(err) + + nonValidatorNode.AddValidatorUnbondingSig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, validatorUnbondingSig) + nonValidatorNode.WaitForNextBlock() + + allDelegationsValSig := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(allDelegationsValSig, 1) + delegationWithValSig := allDelegationsValSig[0].Dels[0] + s.NotNil(delegationWithValSig.BtcUndelegation) + s.NotNil(delegationWithValSig.BtcUndelegation.ValidatorUnbondingSig) + + btcTip, err := nonValidatorNode.QueryTip() + s.NoError(err) + s.Equal( + bstypes.BTCDelegationStatus_UNBONDING, + delegationWithValSig.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), + ) + + // Next send jury signatures + juryUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( + stakingTxMsg, + delegation.StakingTx.Script, + jurySK, + net, + ) + s.NoError(err) + + unbondingTxMsg, err := delegation.BtcUndelegation.UnbondingTx.ToMsgTx() + s.NoError(err) + jurySlashingSig, err := delegation.BtcUndelegation.SlashingTx.Sign( + unbondingTxMsg, + delegation.BtcUndelegation.UnbondingTx.Script, + jurySK, + net, + ) + s.NoError(err) + nonValidatorNode.AddJuryUnbondingSigs(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, juryUnbondingSig, jurySlashingSig) + nonValidatorNode.WaitForNextBlock() + + // Check all signatures are properly registered + allDelegationsWithSigs := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + s.Len(allDelegationsWithSigs, 1) + delegationWithSigs := allDelegationsWithSigs[0].Dels[0] + s.NotNil(delegationWithSigs.BtcUndelegation) + s.NotNil(delegationWithSigs.BtcUndelegation.ValidatorUnbondingSig) + s.NotNil(delegationWithSigs.BtcUndelegation.JuryUnbondingSig) + s.NotNil(delegationWithSigs.BtcUndelegation.JurySlashingSig) + btcTip, err = nonValidatorNode.QueryTip() + s.NoError(err) + s.Equal( + bstypes.BTCDelegationStatus_EXPIRED, + delegationWithSigs.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), + ) +} diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 93d59534e..282309e3a 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -136,3 +136,35 @@ func (n *NodeConfig) CreateBTCUndelegation(unbondingTx *bstypes.BabylonBTCTaproo require.NoError(n.t, err) n.LogActionF("successfully created BTC delegation") } + +func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { + n.LogActionF("adding validator signature") + + valPKHex := valPK.MarshalHex() + delPKHex := delPK.MarshalHex() + sigHex := sig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "add-validator-unbonding-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully added validator unbonding sig") +} + +func (n *NodeConfig) AddJuryUnbondingSigs( + valPK *bbn.BIP340PubKey, + delPK *bbn.BIP340PubKey, + stakingTxHash string, + unbondingTxSig *bbn.BIP340Signature, + slashUnbondingTxSig *bbn.BIP340Signature) { + n.LogActionF("adding validator signature") + + valPKHex := valPK.MarshalHex() + delPKHex := delPK.MarshalHex() + unbondingTxSigHex := unbondingTxSig.ToHexStr() + slashUnbondingTxSigHex := slashUnbondingTxSig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "add-jury-unbonding-sigs", valPKHex, delPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully added jury unbonding sigs") +} diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index fb4244ddf..59dfe3772 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -41,6 +41,8 @@ func GetTxCmd() *cobra.Command { NewCreateBTCDelegationCmd(), NewAddJurySigCmd(), NewCreateBTCUndelegationCmd(), + NewAddJuryUnbondingSigsCmd(), + NewAddValidatorUnbondingSigCmd(), ) return cmd @@ -302,3 +304,108 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { return cmd } + +func NewAddJuryUnbondingSigsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-jury-unbonding-sigs [val_pk] [del_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", + Args: cobra.ExactArgs(5), + Short: "Add jury signatures for unbonding tx and slash unbonding tx", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get validator PK + valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + + // get delegator PK + delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + if err != nil { + return err + } + + // get staking tx hash + stakingTxHash := args[2] + + // get jury sigature for unbonding tx + unbondingSig, err := bbn.NewBIP340SignatureFromHex(args[3]) + if err != nil { + return err + } + + // get jury sigature for slash unbonding tx + slashUnbondingSig, err := bbn.NewBIP340SignatureFromHex(args[4]) + if err != nil { + return err + } + + msg := types.MsgAddJuryUnbondingSigs{ + Signer: clientCtx.FromAddress.String(), + ValPk: valPK, + DelPk: delPK, + StakingTxHash: stakingTxHash, + UnbondingTxSig: unbondingSig, + SlashingUnbondingTxSig: slashUnbondingSig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewAddValidatorUnbondingSigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-validator-unbonding-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", + Args: cobra.ExactArgs(4), + Short: "Add a validator signature for unbonding tx", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get validator PK + valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + if err != nil { + return err + } + + // get delegator PK + delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + if err != nil { + return err + } + + // get staking tx hash + stakingTxHash := args[2] + + // get validator sigature + sig, err := bbn.NewBIP340SignatureFromHex(args[3]) + if err != nil { + return err + } + + msg := types.MsgAddValidatorUnbondingSig{ + Signer: clientCtx.FromAddress.String(), + ValPk: valPK, + DelPk: delPK, + StakingTxHash: stakingTxHash, + UnbondingTxSig: sig, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index 0f7579365..c8f011971 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -29,20 +29,38 @@ func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) e return nil } -// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation -// with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { +func (k Keeper) getAndSetDelegations( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + modifyFn func(*types.BTCDelegatorDelegations) error, +) error { btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) if err != nil { return err } - if err := btcDels.AddJurySig(stakingTxHash, jurySig); err != nil { - return types.ErrInvalidJurySig.Wrapf(err.Error()) + + if err := modifyFn(btcDels); err != nil { + return err } + k.setBTCDelegations(ctx, valBTCPK, delBTCPK, btcDels) return nil } +// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation +// with the given (val PK, del PK, staking tx hash) tuple +func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { + addJurySig := func(btcDels *types.BTCDelegatorDelegations) error { + if err := btcDels.AddJurySig(stakingTxHash, jurySig); err != nil { + return types.ErrInvalidJurySig.Wrapf(err.Error()) + } + return nil + } + + return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addJurySig) +} + func (k Keeper) AddUndelegationToBTCDelegation( ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, @@ -50,16 +68,49 @@ func (k Keeper) AddUndelegationToBTCDelegation( stakingTxHash string, ud *types.BTCUndelegation, ) error { - btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) - if err != nil { - return err + addUndelegation := func(btcDels *types.BTCDelegatorDelegations) error { + if err := btcDels.AddUndelegation(stakingTxHash, ud); err != nil { + return types.ErrInvalidDelegationState.Wrapf(err.Error()) + } + return nil } - if err := btcDels.AddUndelegation(stakingTxHash, ud); err != nil { - return types.ErrInvalidDelegationState.Wrapf(err.Error()) + return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addUndelegation) +} + +func (k Keeper) AddValidatorSigToUndelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + sig *bbn.BIP340Signature, +) error { + addValidatorSig := func(btcDels *types.BTCDelegatorDelegations) error { + if err := btcDels.AddValidatorSigToUndelegation(stakingTxHash, sig); err != nil { + return types.ErrInvalidDelegationState.Wrapf(err.Error()) + } + return nil } - k.setBTCDelegations(ctx, valBTCPK, delBTCPK, btcDels) - return nil + + return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addValidatorSig) +} + +func (k Keeper) AddJurySigsToUndelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + unbondingTxSig *bbn.BIP340Signature, + slashUnbondingTxSig *bbn.BIP340Signature, +) error { + addJurySigs := func(btcDels *types.BTCDelegatorDelegations) error { + if err := btcDels.AddJurySigsToUndelegation(stakingTxHash, unbondingTxSig, slashUnbondingTxSig); err != nil { + return types.ErrInvalidDelegationState.Wrapf(err.Error()) + } + return nil + } + + return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addJurySigs) } // setBTCDelegations sets the given BTC delegation to KVStore diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index c26a21b2d..e9c1c4782 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -414,3 +414,156 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) return &types.MsgAddJurySigResponse{}, nil } + +func (ms msgServer) AddJuryUnbondingSigs( + goCtx context.Context, + req *types.MsgAddJuryUnbondingSigs) (*types.MsgAddJuryUnbondingSigsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + // 1. Check that delegation even exists for provided params + btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) + if err != nil { + return nil, err + } + + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + status := btcDel.GetStatus(btcTip.Height, wValue) + + // 2. Check that we are in proper status + if status != types.BTCDelegationStatus_UNBONDING { + return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) + } + + // 3. Check that we did not recevie jury signature yet + if btcDel.BtcUndelegation.HasJurySigs() { + return nil, types.ErrDuplicatedJurySig.Wrap("Jury signature for undelegation already received") + } + + // 4. Check that we already received validator signature + if !btcDel.BtcUndelegation.HasValidatorSig() { + // Jury should provide signature only after validator to avoid validator and staker + // collusion i.e sending unbonding tx to btc without leaving validator signature on babylon chain. + // TODO: Maybe it is worth accepting signatures and just emmiting some kind of warning event ? as if this msg + // processing fails, it will still be included on babylon chain, so anybody could still retrieve + // all jury signatures included in msg. And with warning we will at least have some kind of + // indication that something is wrong. + return nil, types.ErrUnbondingUnexpectedValidatorSig + } + + // 4. Verify signature of unbodning tx against staking tx output + stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) + } + + juryPK, err := ms.GetParams(ctx).JuryPk.ToBTCPK() + if err != nil { + // failing to cast a verified jury PK is a programming error + panic(fmt.Errorf("failed to cast a verified jury public key")) + } + + // UnbondingTx has exactly one input and one output so we may re-use the same + // machinery as for slashing tx to verify signature + err = btcDel.BtcUndelegation.UnbondingTx.VerifySignature( + stakingOutputInfo.StakingPkScript, + int64(stakingOutputInfo.StakingAmount), + btcDel.StakingTx.Script, + juryPK, + req.UnbondingTxSig, + ) + if err != nil { + return nil, types.ErrInvalidJurySig.Wrap(err.Error()) + } + + // 5. Verify signature of slashing tx against unbonding tx output + unbondingOutputInfo, err := btcDel.BtcUndelegation.UnbondingTx.GetBabylonOutputInfo(ms.btcNet) + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed to get unbonding output info from a verified staking tx")) + } + + err = btcDel.BtcUndelegation.SlashingTx.VerifySignature( + unbondingOutputInfo.StakingPkScript, + int64(unbondingOutputInfo.StakingAmount), + btcDel.BtcUndelegation.UnbondingTx.Script, + juryPK, + req.SlashingUnbondingTxSig, + ) + if err != nil { + return nil, types.ErrUnbodningInvalidValidatorSig.Wrap(err.Error()) + } + + // all good, add signature to BTC delegation and set it back to KVStore + if err := ms.AddJurySigsToUndelegation( + ctx, + req.ValPk, + req.DelPk, + req.StakingTxHash, + req.UnbondingTxSig, + req.SlashingUnbondingTxSig); err != nil { + panic("failed to set BTC delegation that has passed verification") + } + + return nil, nil +} + +func (ms msgServer) AddValidatorUnbondingSig( + goCtx context.Context, + req *types.MsgAddValidatorUnbondingSig) (*types.MsgAddValidatorUnbondingSigResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + // 1. Check that delegation even exists for provided params + btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) + if err != nil { + return nil, err + } + + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + status := btcDel.GetStatus(btcTip.Height, wValue) + + // 2. Check that we are in proper status + if status != types.BTCDelegationStatus_UNBONDING { + return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) + } + + // 3. Check that we did not recevie validator signature yet + if btcDel.BtcUndelegation.HasValidatorSig() { + return nil, types.ErrUnbondingDuplicatedValidatorSig + } + + // 4. Verify signature of unbonding tx against staking tx output + stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) + } + + validatorPK, err := req.ValPk.ToBTCPK() + + if err != nil { + panic(fmt.Errorf("failed to cast a verified validator public key")) + } + + // UnbondingTx has exactly one input and one output so we may re-use the same + // machinery as for slashing tx to verify signature + err = btcDel.BtcUndelegation.UnbondingTx.VerifySignature( + stakingOutputInfo.StakingPkScript, + int64(stakingOutputInfo.StakingAmount), + btcDel.StakingTx.Script, + validatorPK, + req.UnbondingTxSig, + ) + if err != nil { + return nil, types.ErrUnbodningInvalidValidatorSig.Wrap(err.Error()) + } + + // all good, add signature to BTC delegation and set it back to KVStore + if err := ms.AddValidatorSigToUndelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.UnbondingTxSig); err != nil { + panic("failed to set BTC delegation that has passed verification") + } + + return nil, nil +} diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index b79e0484e..d86a7402a 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -116,12 +116,12 @@ func createValidator( btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) require.NoError(t, err) msgNewVal := types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, + Signer: datagen.GenRandomAccount().Address, Description: btcVal.Description, Commission: btcVal.Commission, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, } _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) require.NoError(t, err) @@ -271,6 +271,62 @@ func getDelegationAndCheckValues( return actualDel } +func createUndelegation( + t *testing.T, + r *rand.Rand, + goCtx context.Context, + ms types.MsgServer, + net *chaincfg.Params, + btclcKeeper *types.MockBTCLightClientKeeper, + actualDel *types.BTCDelegation, + stakingTxHash string, + delSK *btcec.PrivateKey, + validatorPK *btcec.PublicKey, + juryPK *btcec.PublicKey, + slashingAddr string, +) *types.MsgBTCUndelegate { + stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) + require.NoError(t, err) + stkOutputIdx := uint32(0) + defaultParams := btcctypes.DefaultParams() + + unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + r, + net, + delSK, + validatorPK, + juryPK, + wire.NewOutPoint(stkTxHash, stkOutputIdx), + uint16(defaultParams.CheckpointFinalizationTimeout)+1, + int64(actualDel.TotalSat)-1000, + slashingAddr, + ) + require.NoError(t, err) + // random signer + signer := datagen.GenRandomAccount().Address + unbondingTxMsg, err := unbondingTx.ToMsgTx() + require.NoError(t, err) + + sig, err := slashUnbondingTx.Sign( + unbondingTxMsg, + unbondingTx.Script, + delSK, + net, + ) + require.NoError(t, err) + + msg := &types.MsgBTCUndelegate{ + Signer: signer, + UnbondingTx: unbondingTx, + SlashingTx: slashUnbondingTx, + DelegatorSlashingSig: sig, + } + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) + _, err = ms.BTCUndelegate(goCtx, msg) + require.NoError(t, err) + return msg +} + func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) @@ -441,56 +497,154 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) - stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) + undelegateMsg := createUndelegation( + t, + r, + goCtx, + ms, + net, + btclcKeeper, + actualDel, + stakingTxHash, + delSK, + validatorPK, + juryPK, + slashingAddr.String(), + ) + + actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) require.NoError(t, err) - stkOutputIdx := uint32(0) - defaultParams := btcctypes.DefaultParams() - unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, undelegateMsg.UnbondingTx) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JurySlashingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JuryUnbondingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.ValidatorUnbondingSig) + }) +} + +func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + ms := keeper.NewMsgServerImpl(*bsKeeper) + goCtx := sdk.WrapSDKContext(ctx) + + jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + valSk, validatorPK, _ := createValidator(t, r, goCtx, ms) + stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( + t, + r, + goCtx, + ms, + btccKeeper, + btclcKeeper, + net, + validatorPK, + juryPK, + slashingAddr.String(), + 1000, + ) + actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) + + undelegateMsg := createUndelegation( + t, r, + goCtx, + ms, net, + btclcKeeper, + actualDel, + stakingTxHash, delSK, validatorPK, juryPK, - wire.NewOutPoint(stkTxHash, stkOutputIdx), - uint16(defaultParams.CheckpointFinalizationTimeout)+1, - int64(actualDel.TotalSat)-1000, slashingAddr.String(), ) + + del, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + require.NoError(t, err) + require.NotNil(t, del.BtcUndelegation) + + // Check sending validator signature + stakingTxMsg, err := del.StakingTx.ToMsgTx() + require.NoError(t, err) + ubondingTxSignatureValidator, err := undelegateMsg.UnbondingTx.Sign( + stakingTxMsg, + del.StakingTx.Script, + valSk, + net, + ) + require.NoError(t, err) + msg := types.MsgAddValidatorUnbondingSig{ + Signer: datagen.GenRandomAccount().Address, + ValPk: del.ValBtcPk, + DelPk: del.BtcPk, + StakingTxHash: stakingTxHash, + UnbondingTxSig: ubondingTxSignatureValidator, + } + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) + _, err = ms.AddValidatorUnbondingSig(goCtx, &msg) + require.NoError(t, err) + + delWithValSig, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + require.NoError(t, err) + require.NotNil(t, delWithValSig.BtcUndelegation) + require.NotNil(t, delWithValSig.BtcUndelegation.ValidatorUnbondingSig) + + // Check sending jury signatures + // unbonding tx spends staking tx + unbondingTxSignatureJury, err := undelegateMsg.UnbondingTx.Sign( + stakingTxMsg, + del.StakingTx.Script, + jurySK, + net, + ) require.NoError(t, err) - // random signer - signer := datagen.GenRandomAccount().Address - unbondingTxMsg, err := unbondingTx.ToMsgTx() + + unbondingTxMsg, err := undelegateMsg.UnbondingTx.ToMsgTx() require.NoError(t, err) - sig, err := slashUnbondingTx.Sign( + // slash unbodning tx spends unbonding tx + slashUnbondingTxSignatureJury, err := undelegateMsg.SlashingTx.Sign( unbondingTxMsg, - unbondingTx.Script, - delSK, + undelegateMsg.UnbondingTx.Script, + jurySK, net, ) require.NoError(t, err) - msg := &types.MsgBTCUndelegate{ - Signer: signer, - UnbondingTx: unbondingTx, - SlashingTx: slashUnbondingTx, - DelegatorSlashingSig: sig, + jurySigsMsg := types.MsgAddJuryUnbondingSigs{ + Signer: datagen.GenRandomAccount().Address, + ValPk: del.ValBtcPk, + DelPk: del.BtcPk, + StakingTxHash: stakingTxHash, + UnbondingTxSig: unbondingTxSignatureJury, + SlashingUnbondingTxSig: slashUnbondingTxSignatureJury, } + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.BTCUndelegate(goCtx, msg) + _, err = ms.AddJuryUnbondingSigs(goCtx, &jurySigsMsg) require.NoError(t, err) - actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) require.NoError(t, err) - - require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, unbondingTx) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, slashUnbondingTx) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, sig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JurySlashingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JuryUnbondingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.ValidatorUnbondingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.ValidatorUnbondingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.JurySlashingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.JuryUnbondingSig) }) } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index ab6199d22..e286762f9 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -7,6 +7,7 @@ import ( "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -86,6 +87,18 @@ func (d *BTCDelegation) HasJurySig() bool { return d.JurySig != nil } +func (ud *BTCUndelegation) HasJurySigs() bool { + return ud.JurySlashingSig != nil && ud.JuryUnbondingSig != nil +} + +func (ud *BTCUndelegation) HasValidatorSig() bool { + return ud.ValidatorUnbondingSig != nil +} + +func (ud *BTCUndelegation) HasAllSignatures() bool { + return ud.HasJurySigs() && ud.HasValidatorSig() +} + // GetStatus returns the status of the BTC Delegation based on a BTC height and a w value // TODO: Given that we only accept delegations that can be activated immediately, // we can only have expired delegations. If we accept optimistic submissions, @@ -96,14 +109,17 @@ func (d *BTCDelegation) HasJurySig() bool { // Expired: Delegation timelock func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { if d.BtcUndelegation != nil { - // If we received an undelegation, delegation becomes expired which means that staker - // voting power from this delegation is removed from the total voting power + if d.BtcUndelegation.HasAllSignatures() { + return BTCDelegationStatus_EXPIRED + } + // If we received an undelegation but is still does not have all required signature, + // delegation receives UNBONING status. + // Voting power from this delegation is removed from the total voting power and now we + // are waiting for signatures from validator and jury for delegation to become expired. // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain // we may consider adding unbonding time on Babylon chain later to avoid situation where // we can lose to much voting power in to short time. - // TODO: maybe it is worth having separte status for this case help jury/validators to - // filter for unbodning delegations - return BTCDelegationStatus_EXPIRED + return BTCDelegationStatus_UNBONDING } if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { @@ -159,32 +175,81 @@ func (dels *BTCDelegatorDelegations) Add(del *BTCDelegation) error { return nil } -// AddJurySig adds a jury signature to an existing BTC delegation in the BTC delegations -// TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegatorDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { +func (dels *BTCDelegatorDelegations) getAndModifyDelegation(stakingTxHash string, modifyFn func(del *BTCDelegation) error) error { del, err := dels.Get(stakingTxHash) if err != nil { return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) } - if del.JurySig != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) + + if err := modifyFn(del); err != nil { + return err } - del.JurySig = sig return nil } +// AddJurySig adds a jury signature to an existing BTC delegation in the BTC delegations +// TODO: this is an O(n) operation. Consider optimisation later +func (dels *BTCDelegatorDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { + addJurySig := func(del *BTCDelegation) error { + if del.JurySig != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) + } + del.JurySig = sig + return nil + } + + return dels.getAndModifyDelegation(stakingTxHash, addJurySig) +} + func (dels *BTCDelegatorDelegations) AddUndelegation(stakingTxHash string, ud *BTCUndelegation) error { - del, err := dels.Get(stakingTxHash) - if err != nil { - return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) + addUndelegation := func(del *BTCDelegation) error { + if del.BtcUndelegation != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has valid undelegation object", stakingTxHash) + } + del.BtcUndelegation = ud + return nil + } + + return dels.getAndModifyDelegation(stakingTxHash, addUndelegation) +} + +func (dels *BTCDelegatorDelegations) AddValidatorSigToUndelegation(stakingTxHash string, sig *bbn.BIP340Signature) error { + addValidatorSig := func(del *BTCDelegation) error { + if del.BtcUndelegation == nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) + } + + if del.BtcUndelegation.ValidatorUnbondingSig != nil { + return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid validator signature", stakingTxHash) + } + + del.BtcUndelegation.ValidatorUnbondingSig = sig + return nil } - if del.BtcUndelegation != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has valid undelegation object", stakingTxHash) + return dels.getAndModifyDelegation(stakingTxHash, addValidatorSig) +} + +func (dels *BTCDelegatorDelegations) AddJurySigsToUndelegation( + stakingTxHash string, + unbondingTxSig *bbn.BIP340Signature, + slashUnbondingTxSig *bbn.BIP340Signature, +) error { + addJurySigs := func(del *BTCDelegation) error { + if del.BtcUndelegation == nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) + } + + if del.BtcUndelegation.JuryUnbondingSig != nil || del.BtcUndelegation.JurySlashingSig != nil { + return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid jury signatures", stakingTxHash) + } + + del.BtcUndelegation.JuryUnbondingSig = unbondingTxSig + del.BtcUndelegation.JurySlashingSig = slashUnbondingTxSig + return nil } - del.BtcUndelegation = ud - return nil + return dels.getAndModifyDelegation(stakingTxHash, addJurySigs) } // TODO: this is an O(n) operation. Consider optimisation later @@ -342,3 +407,41 @@ func (tx *BabylonBTCTaprootTx) GetBabylonOutputInfo(net *chaincfg.Params) (*btcs StakingAmount: btcutil.Amount(outValue), }, nil } + +func (tx *BabylonBTCTaprootTx) Sign( + fundingTx *wire.MsgTx, + fundingTxScript []byte, + sk *btcec.PrivateKey, + net *chaincfg.Params) (*bbn.BIP340Signature, error) { + msgTx, err := tx.ToMsgTx() + if err != nil { + return nil, err + } + schnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + msgTx, + fundingTx, + sk, + fundingTxScript, + net, + ) + if err != nil { + return nil, err + } + sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) + return &sig, nil +} + +func (tx *BabylonBTCTaprootTx) VerifySignature(stakingPkScript []byte, stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { + msgTx, err := tx.ToMsgTx() + if err != nil { + return err + } + return btcstaking.VerifyTransactionSigWithOutputData( + msgTx, + stakingPkScript, + stakingAmount, + stakingScript, + pk, + *sig, + ) +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 9c6455a75..bcde0cec8 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -36,20 +36,25 @@ const ( BTCDelegationStatus_PENDING BTCDelegationStatus = 0 // ACTIVE defines a delegation that has voting power BTCDelegationStatus_ACTIVE BTCDelegationStatus = 1 + // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx + // from staker, but not yet received signatures from validator and jury + BTCDelegationStatus_UNBONDING BTCDelegationStatus = 2 // EXPIRED defines a delegation that has expired - BTCDelegationStatus_EXPIRED BTCDelegationStatus = 2 + BTCDelegationStatus_EXPIRED BTCDelegationStatus = 3 ) var BTCDelegationStatus_name = map[int32]string{ 0: "PENDING", 1: "ACTIVE", - 2: "EXPIRED", + 2: "UNBONDING", + 3: "EXPIRED", } var BTCDelegationStatus_value = map[string]int32{ - "PENDING": 0, - "ACTIVE": 1, - "EXPIRED": 2, + "PENDING": 0, + "ACTIVE": 1, + "UNBONDING": 2, + "EXPIRED": 3, } func (x BTCDelegationStatus) String() string { @@ -593,68 +598,69 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 976 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0xda, 0x8e, 0x13, 0xbf, 0x75, 0x89, 0x33, 0x4d, 0x53, 0x53, 0x84, 0x9d, 0x9a, 0x2a, - 0xb2, 0x2a, 0xba, 0x26, 0xee, 0x1f, 0x15, 0x10, 0x48, 0xf8, 0x8f, 0xc0, 0x82, 0x14, 0xb3, 0xde, - 0x96, 0x8a, 0x03, 0xd6, 0xfe, 0x99, 0xae, 0x17, 0x3b, 0x3b, 0xcb, 0xce, 0xd8, 0xd8, 0x77, 0x0e, - 0x1c, 0xb9, 0xf1, 0x45, 0xf8, 0x10, 0x3d, 0x56, 0x9c, 0x50, 0x0e, 0x16, 0x4a, 0xbe, 0x08, 0x9a, - 0xd9, 0x59, 0x7b, 0xd3, 0x34, 0x40, 0x70, 0x4e, 0xf6, 0xcc, 0x7b, 0xef, 0xf7, 0xde, 0xfb, 0xfd, - 0xde, 0xbe, 0x5d, 0xd8, 0xb7, 0x4c, 0x6b, 0x36, 0x22, 0x7e, 0xcd, 0x62, 0x36, 0x65, 0xe6, 0xd0, - 0xf3, 0xdd, 0xda, 0xe4, 0x20, 0x71, 0xd2, 0x82, 0x90, 0x30, 0x82, 0x6e, 0x48, 0x3f, 0x2d, 0x61, - 0x99, 0x1c, 0xdc, 0xda, 0x71, 0x89, 0x4b, 0x84, 0x47, 0x8d, 0xff, 0x8b, 0x9c, 0x6f, 0xbd, 0x6d, - 0x13, 0x7a, 0x44, 0x68, 0x3f, 0x32, 0x44, 0x07, 0x69, 0xaa, 0x44, 0xa7, 0x9a, 0x1d, 0xce, 0x02, - 0x46, 0x6a, 0x14, 0xdb, 0x41, 0xfd, 0xe1, 0xa3, 0xe1, 0x41, 0x6d, 0x88, 0x67, 0xb1, 0xcf, 0x1d, - 0xe9, 0xb3, 0xac, 0xc7, 0xc2, 0xcc, 0x3c, 0xa8, 0x9d, 0xa9, 0xa8, 0x32, 0x4f, 0x43, 0xbe, 0x61, - 0x34, 0x9f, 0x99, 0x23, 0xcf, 0x31, 0x19, 0x09, 0x51, 0x1b, 0x54, 0x07, 0x53, 0x3b, 0xf4, 0x02, - 0xe6, 0x11, 0xbf, 0xa8, 0xec, 0x29, 0x55, 0xb5, 0xfe, 0x9e, 0x26, 0xd3, 0x2f, 0x8b, 0x16, 0x60, - 0x5a, 0x6b, 0xe9, 0xaa, 0x27, 0xe3, 0xd0, 0x73, 0x00, 0x9b, 0x1c, 0x1d, 0x79, 0x94, 0x72, 0x94, - 0xd4, 0x9e, 0x52, 0xcd, 0x35, 0x1e, 0x1f, 0xcf, 0xcb, 0xfb, 0xae, 0xc7, 0x06, 0x63, 0x4b, 0xb3, - 0xc9, 0x51, 0x2d, 0x6e, 0x42, 0xfc, 0xdc, 0xa3, 0xce, 0xb0, 0xc6, 0x66, 0x01, 0xa6, 0x5a, 0x0b, - 0xdb, 0x7f, 0xfc, 0x7e, 0x0f, 0x64, 0xca, 0x16, 0xb6, 0xf5, 0x04, 0x16, 0xfa, 0x14, 0x40, 0xb2, - 0xd8, 0x0f, 0x86, 0xc5, 0xb4, 0xa8, 0xaf, 0x1c, 0xd7, 0x17, 0x11, 0xa2, 0x2d, 0x08, 0xd1, 0xba, - 0x63, 0xeb, 0x4b, 0x3c, 0xd3, 0x73, 0x32, 0xa4, 0x3b, 0x44, 0x87, 0x90, 0xb5, 0x98, 0xcd, 0x63, - 0x33, 0x7b, 0x4a, 0x35, 0xdf, 0x78, 0x74, 0x3c, 0x2f, 0xd7, 0x13, 0x55, 0x49, 0x4f, 0x7b, 0x60, - 0x7a, 0x7e, 0x7c, 0x90, 0x85, 0x35, 0x3a, 0xdd, 0xfb, 0x0f, 0x3e, 0x90, 0x90, 0xeb, 0x16, 0xb3, - 0xbb, 0x43, 0xf4, 0x11, 0xa4, 0x03, 0x12, 0x14, 0xd7, 0x45, 0x1d, 0x55, 0xed, 0x8d, 0x02, 0x6b, - 0xdd, 0x90, 0x90, 0x17, 0x5f, 0xbf, 0xe8, 0x12, 0x4a, 0xb1, 0xe8, 0x42, 0xe7, 0x41, 0xe8, 0x01, - 0xec, 0xd2, 0x91, 0x49, 0x07, 0xd8, 0xe9, 0xc7, 0x2d, 0x0d, 0xb0, 0xe7, 0x0e, 0x58, 0x31, 0xbb, - 0xa7, 0x54, 0x33, 0xfa, 0x8e, 0xb4, 0x36, 0x22, 0xe3, 0x17, 0xc2, 0x86, 0xde, 0x07, 0xb4, 0x88, - 0x62, 0x76, 0x1c, 0xb1, 0x21, 0x22, 0x0a, 0x71, 0x04, 0xb3, 0x23, 0xef, 0xca, 0xcf, 0x29, 0xd8, - 0x49, 0x0a, 0xfc, 0xad, 0xc7, 0x06, 0x87, 0x98, 0x99, 0x09, 0x1e, 0x94, 0xab, 0xe0, 0x61, 0x17, - 0xb2, 0xb2, 0x92, 0x94, 0xa8, 0x44, 0x9e, 0xd0, 0x6d, 0xc8, 0x4f, 0x08, 0xf3, 0x7c, 0xb7, 0x1f, - 0x90, 0x9f, 0x70, 0x28, 0x04, 0xcb, 0xe8, 0x6a, 0x74, 0xd7, 0xe5, 0x57, 0xff, 0x40, 0x43, 0xe6, - 0xd2, 0x34, 0xac, 0x5f, 0x40, 0xc3, 0x6f, 0x59, 0xb8, 0xd6, 0x30, 0x9a, 0x2d, 0x3c, 0xc2, 0xae, - 0xc9, 0xce, 0xcf, 0x91, 0xb2, 0xc2, 0x1c, 0xa5, 0xae, 0x70, 0x8e, 0xd2, 0xff, 0x67, 0x8e, 0x0c, - 0x80, 0x89, 0x39, 0xea, 0x5f, 0xc9, 0x58, 0x6f, 0x4e, 0xcc, 0x51, 0x43, 0x54, 0x74, 0x1b, 0xf2, - 0x94, 0x99, 0x21, 0x3b, 0x4b, 0xad, 0x2a, 0xee, 0xa4, 0x06, 0xef, 0x02, 0x60, 0xdf, 0x39, 0x3b, - 0xb4, 0x39, 0xec, 0x3b, 0xd2, 0xfc, 0x0e, 0xe4, 0x18, 0x61, 0xe6, 0xa8, 0x4f, 0xcd, 0x78, 0x40, - 0x37, 0xc5, 0x45, 0xcf, 0x64, 0xa8, 0x03, 0x20, 0x3b, 0xeb, 0xb3, 0x69, 0x71, 0x53, 0xf4, 0x7d, - 0xf7, 0x82, 0xbe, 0xa5, 0xf2, 0x0d, 0xa3, 0x69, 0x98, 0x41, 0x48, 0x08, 0x33, 0xa6, 0x7a, 0x4e, - 0xda, 0x8d, 0x29, 0xaa, 0x83, 0x2a, 0x04, 0x97, 0x58, 0x39, 0x41, 0xc0, 0xf6, 0xf1, 0xbc, 0xcc, - 0x25, 0xef, 0x49, 0x8b, 0x31, 0xd5, 0x81, 0x2e, 0xfe, 0xa3, 0xef, 0xe1, 0x9a, 0x13, 0x0d, 0x03, - 0x09, 0xfb, 0xd4, 0x73, 0x8b, 0x20, 0xa2, 0x3e, 0x3c, 0x9e, 0x97, 0x1f, 0x5e, 0x86, 0xb6, 0x9e, - 0xe7, 0xfa, 0x26, 0x1b, 0x87, 0x58, 0xcf, 0x2f, 0xf0, 0x7a, 0x9e, 0x8b, 0x0c, 0xd8, 0xfc, 0x61, - 0x1c, 0xce, 0x04, 0xb4, 0xba, 0x2a, 0xf4, 0x06, 0x87, 0xe2, 0xa8, 0xdf, 0x40, 0x81, 0xab, 0x3c, - 0xf6, 0x9d, 0xc5, 0x20, 0x17, 0xf3, 0x82, 0xba, 0xfd, 0x8b, 0xa8, 0x33, 0x9a, 0x4f, 0x13, 0xde, - 0xfa, 0x96, 0xc5, 0xec, 0xe4, 0x45, 0xe5, 0x65, 0x06, 0xb6, 0x5e, 0x73, 0x42, 0x87, 0x90, 0x1f, - 0xfb, 0x16, 0xf1, 0x1d, 0xc9, 0xa8, 0x72, 0x69, 0x75, 0xd4, 0x45, 0xfc, 0x79, 0x7d, 0x52, 0xff, - 0x45, 0x1f, 0x02, 0xbb, 0x09, 0x7d, 0xe2, 0x68, 0xce, 0x66, 0x7a, 0x55, 0x36, 0x77, 0x96, 0x42, - 0x49, 0x5c, 0x4e, 0x2d, 0x86, 0xed, 0x48, 0xb0, 0x64, 0xae, 0xcc, 0xaa, 0xb9, 0xb6, 0x84, 0x72, - 0x89, 0x34, 0x2e, 0x20, 0x91, 0x66, 0xc9, 0x2f, 0xcf, 0xb3, 0xbe, 0x6a, 0x9e, 0x02, 0x07, 0x7d, - 0x1a, 0x63, 0xf2, 0x44, 0x3f, 0xc2, 0xcd, 0x49, 0xbc, 0xf4, 0x5f, 0xcb, 0x96, 0x5d, 0x35, 0xdb, - 0x8d, 0x05, 0x72, 0x32, 0x65, 0xa5, 0x07, 0x37, 0x97, 0x3b, 0x96, 0x84, 0xcb, 0x65, 0x4b, 0xd1, - 0x63, 0xc8, 0x38, 0x78, 0x44, 0x8b, 0xca, 0x5e, 0xba, 0xaa, 0xd6, 0xef, 0x5c, 0x3c, 0xac, 0xcb, - 0x20, 0x5d, 0x44, 0x54, 0x7e, 0x51, 0x60, 0xfb, 0xdc, 0xde, 0x43, 0x65, 0x50, 0xe3, 0xed, 0xcd, - 0x3b, 0x12, 0xaf, 0x30, 0x3d, 0x5e, 0xe8, 0xbc, 0x7d, 0x1d, 0x36, 0xf8, 0x93, 0xc2, 0x8d, 0xa9, - 0x55, 0xdb, 0xe5, 0x8b, 0x9e, 0xf7, 0xf7, 0x09, 0x5c, 0x7f, 0xc3, 0xac, 0xa3, 0xb7, 0x20, 0x25, - 0x9f, 0x91, 0xbc, 0x9e, 0x62, 0x53, 0xfe, 0x2a, 0x8c, 0x3e, 0x84, 0xa2, 0xcc, 0xba, 0x3c, 0xdd, - 0xfd, 0x18, 0xae, 0x9f, 0x69, 0xb0, 0xc7, 0x4c, 0x36, 0xa6, 0x48, 0x85, 0x8d, 0x6e, 0xfb, 0x49, - 0xab, 0xf3, 0xe4, 0xf3, 0xc2, 0x1a, 0x02, 0xc8, 0x7e, 0xd6, 0x34, 0x3a, 0xcf, 0xda, 0x05, 0x85, - 0x1b, 0xda, 0xcf, 0xbb, 0x1d, 0xbd, 0xdd, 0x2a, 0xa4, 0x1a, 0x5f, 0xbd, 0x3c, 0x29, 0x29, 0xaf, - 0x4e, 0x4a, 0xca, 0x5f, 0x27, 0x25, 0xe5, 0xd7, 0xd3, 0xd2, 0xda, 0xab, 0xd3, 0xd2, 0xda, 0x9f, - 0xa7, 0xa5, 0xb5, 0xef, 0xfe, 0x75, 0xcb, 0x4f, 0x93, 0x9f, 0xa5, 0xa2, 0x43, 0x2b, 0x2b, 0xbe, - 0xfe, 0xee, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x59, 0x41, 0x29, 0xb9, 0x0a, 0x00, 0x00, + // 987 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x73, 0xdb, 0x44, + 0x14, 0x8f, 0x6c, 0xc7, 0x89, 0x9f, 0x1c, 0xe2, 0x6c, 0xd3, 0xd4, 0x94, 0xc1, 0x4e, 0x4d, 0x27, + 0xe3, 0xe9, 0x50, 0x99, 0xa4, 0x7f, 0xa6, 0x30, 0x03, 0x33, 0x28, 0xce, 0x40, 0x80, 0xa4, 0x46, + 0x56, 0x4a, 0x87, 0x03, 0x1e, 0xfd, 0xd9, 0xca, 0xc2, 0x8e, 0x56, 0x68, 0xd7, 0xc6, 0xbe, 0x73, + 0xe0, 0xc8, 0x8d, 0x2f, 0xc2, 0x87, 0xe8, 0xb1, 0xc3, 0x89, 0xc9, 0xc1, 0xc3, 0x24, 0x5f, 0x84, + 0xd9, 0xd5, 0xca, 0x56, 0x9a, 0x06, 0x48, 0x9d, 0x93, 0xbd, 0xfb, 0xde, 0xfb, 0xbd, 0xf7, 0x7e, + 0xbf, 0xa7, 0x27, 0xc1, 0x96, 0x6d, 0xd9, 0xe3, 0x3e, 0x09, 0x1a, 0x36, 0x73, 0x28, 0xb3, 0x7a, + 0x7e, 0xe0, 0x35, 0x86, 0xdb, 0xa9, 0x93, 0x16, 0x46, 0x84, 0x11, 0x74, 0x53, 0xfa, 0x69, 0x29, + 0xcb, 0x70, 0xfb, 0xf6, 0xba, 0x47, 0x3c, 0x22, 0x3c, 0x1a, 0xfc, 0x5f, 0xec, 0x7c, 0xfb, 0x5d, + 0x87, 0xd0, 0x63, 0x42, 0x3b, 0xb1, 0x21, 0x3e, 0x48, 0x53, 0x2d, 0x3e, 0x35, 0x9c, 0x68, 0x1c, + 0x32, 0xd2, 0xa0, 0xd8, 0x09, 0x77, 0x1e, 0x3d, 0xee, 0x6d, 0x37, 0x7a, 0x78, 0x9c, 0xf8, 0xdc, + 0x95, 0x3e, 0xb3, 0x7a, 0x6c, 0xcc, 0xac, 0xed, 0xc6, 0xb9, 0x8a, 0x6a, 0x93, 0x2c, 0x14, 0x75, + 0x73, 0xf7, 0x99, 0xd5, 0xf7, 0x5d, 0x8b, 0x91, 0x08, 0xed, 0x81, 0xea, 0x62, 0xea, 0x44, 0x7e, + 0xc8, 0x7c, 0x12, 0x94, 0x95, 0x4d, 0xa5, 0xae, 0xee, 0x7c, 0xa0, 0xc9, 0xf4, 0xb3, 0xa2, 0x05, + 0x98, 0xd6, 0x9c, 0xb9, 0x1a, 0xe9, 0x38, 0xf4, 0x1c, 0xc0, 0x21, 0xc7, 0xc7, 0x3e, 0xa5, 0x1c, + 0x25, 0xb3, 0xa9, 0xd4, 0x0b, 0xfa, 0x93, 0x93, 0x49, 0x75, 0xcb, 0xf3, 0x59, 0x77, 0x60, 0x6b, + 0x0e, 0x39, 0x6e, 0x24, 0x4d, 0x88, 0x9f, 0xfb, 0xd4, 0xed, 0x35, 0xd8, 0x38, 0xc4, 0x54, 0x6b, + 0x62, 0xe7, 0xcf, 0x3f, 0xee, 0x83, 0x4c, 0xd9, 0xc4, 0x8e, 0x91, 0xc2, 0x42, 0x9f, 0x01, 0x48, + 0x16, 0x3b, 0x61, 0xaf, 0x9c, 0x15, 0xf5, 0x55, 0x93, 0xfa, 0x62, 0x42, 0xb4, 0x29, 0x21, 0x5a, + 0x6b, 0x60, 0x7f, 0x8d, 0xc7, 0x46, 0x41, 0x86, 0xb4, 0x7a, 0xe8, 0x00, 0xf2, 0x36, 0x73, 0x78, + 0x6c, 0x6e, 0x53, 0xa9, 0x17, 0xf5, 0xc7, 0x27, 0x93, 0xea, 0x4e, 0xaa, 0x2a, 0xe9, 0xe9, 0x74, + 0x2d, 0x3f, 0x48, 0x0e, 0xb2, 0x30, 0x7d, 0xbf, 0xf5, 0xe0, 0xe1, 0x47, 0x12, 0x72, 0xd1, 0x66, + 0x4e, 0xab, 0x87, 0x3e, 0x81, 0x6c, 0x48, 0xc2, 0xf2, 0xa2, 0xa8, 0xa3, 0xae, 0xbd, 0x51, 0x60, + 0xad, 0x15, 0x11, 0xf2, 0xe2, 0xe9, 0x8b, 0x16, 0xa1, 0x14, 0x8b, 0x2e, 0x0c, 0x1e, 0x84, 0x1e, + 0xc2, 0x06, 0xed, 0x5b, 0xb4, 0x8b, 0xdd, 0x4e, 0xd2, 0x52, 0x17, 0xfb, 0x5e, 0x97, 0x95, 0xf3, + 0x9b, 0x4a, 0x3d, 0x67, 0xac, 0x4b, 0xab, 0x1e, 0x1b, 0xbf, 0x14, 0x36, 0xf4, 0x21, 0xa0, 0x69, + 0x14, 0x73, 0x92, 0x88, 0x25, 0x11, 0x51, 0x4a, 0x22, 0x98, 0x13, 0x7b, 0xd7, 0x7e, 0xc9, 0xc0, + 0x7a, 0x5a, 0xe0, 0xef, 0x7c, 0xd6, 0x3d, 0xc0, 0xcc, 0x4a, 0xf1, 0xa0, 0x5c, 0x07, 0x0f, 0x1b, + 0x90, 0x97, 0x95, 0x64, 0x44, 0x25, 0xf2, 0x84, 0xee, 0x40, 0x71, 0x48, 0x98, 0x1f, 0x78, 0x9d, + 0x90, 0xfc, 0x8c, 0x23, 0x21, 0x58, 0xce, 0x50, 0xe3, 0xbb, 0x16, 0xbf, 0xfa, 0x17, 0x1a, 0x72, + 0x57, 0xa6, 0x61, 0xf1, 0x12, 0x1a, 0x7e, 0xcf, 0xc3, 0x8a, 0x6e, 0xee, 0x36, 0x71, 0x1f, 0x7b, + 0x16, 0xbb, 0x38, 0x47, 0xca, 0x1c, 0x73, 0x94, 0xb9, 0xc6, 0x39, 0xca, 0xbe, 0xcd, 0x1c, 0x99, + 0x00, 0x43, 0xab, 0xdf, 0xb9, 0x96, 0xb1, 0x5e, 0x1e, 0x5a, 0x7d, 0x5d, 0x54, 0x74, 0x07, 0x8a, + 0x94, 0x59, 0x11, 0x3b, 0x4f, 0xad, 0x2a, 0xee, 0xa4, 0x06, 0xef, 0x03, 0xe0, 0xc0, 0x3d, 0x3f, + 0xb4, 0x05, 0x1c, 0xb8, 0xd2, 0xfc, 0x1e, 0x14, 0x18, 0x61, 0x56, 0xbf, 0x43, 0xad, 0x64, 0x40, + 0x97, 0xc5, 0x45, 0xdb, 0x62, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x61, 0xa3, 0xf2, 0xb2, 0xe8, 0xfb, + 0xde, 0x25, 0x7d, 0x4b, 0xe5, 0x75, 0x73, 0xd7, 0xb4, 0xc2, 0x88, 0x10, 0x66, 0x8e, 0x8c, 0x82, + 0xb4, 0x9b, 0x23, 0xb4, 0x03, 0xaa, 0x10, 0x5c, 0x62, 0x15, 0x04, 0x01, 0x6b, 0x27, 0x93, 0x2a, + 0x97, 0xbc, 0x2d, 0x2d, 0xe6, 0xc8, 0x00, 0x3a, 0xfd, 0x8f, 0x7e, 0x80, 0x15, 0x37, 0x1e, 0x06, + 0x12, 0x75, 0xa8, 0xef, 0x95, 0x41, 0x44, 0x7d, 0x7c, 0x32, 0xa9, 0x3e, 0xba, 0x0a, 0x6d, 0x6d, + 0xdf, 0x0b, 0x2c, 0x36, 0x88, 0xb0, 0x51, 0x9c, 0xe2, 0xb5, 0x7d, 0x0f, 0x99, 0xb0, 0xfc, 0xe3, + 0x20, 0x1a, 0x0b, 0x68, 0x75, 0x5e, 0xe8, 0x25, 0x0e, 0xc5, 0x51, 0xbf, 0x85, 0x12, 0x57, 0x79, + 0x10, 0xb8, 0xd3, 0x41, 0x2e, 0x17, 0x05, 0x75, 0x5b, 0x97, 0x51, 0x67, 0xee, 0x1e, 0xa5, 0xbc, + 0x8d, 0x55, 0x9b, 0x39, 0xe9, 0x8b, 0xda, 0xcb, 0x1c, 0xac, 0xbe, 0xe6, 0x84, 0x0e, 0xa0, 0x38, + 0x08, 0x6c, 0x12, 0xb8, 0x92, 0x51, 0xe5, 0xca, 0xea, 0xa8, 0xd3, 0xf8, 0x8b, 0xfa, 0x64, 0xfe, + 0x8f, 0x3e, 0x04, 0x36, 0x52, 0xfa, 0x24, 0xd1, 0x9c, 0xcd, 0xec, 0xbc, 0x6c, 0xae, 0xcf, 0x84, + 0x92, 0xb8, 0x9c, 0x5a, 0x0c, 0x6b, 0xb1, 0x60, 0xe9, 0x5c, 0xb9, 0x79, 0x73, 0xad, 0x0a, 0xe5, + 0x52, 0x69, 0x3c, 0x40, 0x22, 0xcd, 0x8c, 0x5f, 0x9e, 0x67, 0x71, 0xde, 0x3c, 0x25, 0x0e, 0x7a, + 0x94, 0x60, 0xf2, 0x44, 0x3f, 0xc1, 0xad, 0x61, 0xb2, 0xf4, 0x5f, 0xcb, 0x96, 0x9f, 0x37, 0xdb, + 0xcd, 0x29, 0x72, 0x3a, 0x65, 0xad, 0x0d, 0xb7, 0x66, 0x3b, 0x96, 0x44, 0xb3, 0x65, 0x4b, 0xd1, + 0x13, 0xc8, 0xb9, 0xb8, 0x4f, 0xcb, 0xca, 0x66, 0xb6, 0xae, 0xee, 0xdc, 0xbd, 0x7c, 0x58, 0x67, + 0x41, 0x86, 0x88, 0xa8, 0xfd, 0xaa, 0xc0, 0xda, 0x85, 0xbd, 0x87, 0xaa, 0xa0, 0x26, 0xdb, 0x9b, + 0x77, 0x24, 0x5e, 0x61, 0x46, 0xb2, 0xd0, 0x79, 0xfb, 0x06, 0x2c, 0xf1, 0x27, 0x85, 0x1b, 0x33, + 0xf3, 0xb6, 0xcb, 0x17, 0x3d, 0xef, 0xef, 0x53, 0xb8, 0xf1, 0x86, 0x59, 0x47, 0xef, 0x40, 0x46, + 0x3e, 0x23, 0x45, 0x23, 0xc3, 0x46, 0xfc, 0x55, 0x18, 0x7f, 0x08, 0xc5, 0x99, 0x0d, 0x79, 0xba, + 0xf7, 0x15, 0xdc, 0x38, 0xd7, 0x60, 0x9b, 0x59, 0x6c, 0x40, 0x91, 0x0a, 0x4b, 0xad, 0xbd, 0xc3, + 0xe6, 0xfe, 0xe1, 0x17, 0xa5, 0x05, 0x04, 0x90, 0xff, 0x7c, 0xd7, 0xdc, 0x7f, 0xb6, 0x57, 0x52, + 0xd0, 0x0a, 0x14, 0x8e, 0x0e, 0xf5, 0xa7, 0xb1, 0x29, 0xc3, 0xfd, 0xf6, 0x9e, 0xb7, 0xf6, 0x8d, + 0xbd, 0x66, 0x29, 0xab, 0x7f, 0xf3, 0xf2, 0xb4, 0xa2, 0xbc, 0x3a, 0xad, 0x28, 0x7f, 0x9f, 0x56, + 0x94, 0xdf, 0xce, 0x2a, 0x0b, 0xaf, 0xce, 0x2a, 0x0b, 0x7f, 0x9d, 0x55, 0x16, 0xbe, 0xff, 0xcf, + 0xa5, 0x3f, 0x4a, 0x7f, 0xa5, 0x8a, 0x86, 0xed, 0xbc, 0xf8, 0x18, 0x7c, 0xf0, 0x4f, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x6c, 0xe0, 0x15, 0xed, 0xc8, 0x0a, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index 2ed77f6b8..9c77cb321 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -13,6 +13,8 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgAddJurySig{}, "btcstaking/MsgAddJurySig", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) cdc.RegisterConcrete(&MsgBTCUndelegate{}, "btcstaking/MsgBtcUndelegate", nil) + cdc.RegisterConcrete(&MsgAddJuryUnbondingSigs{}, "btcstaking/MsgAddJuryUnbondingSigs", nil) + cdc.RegisterConcrete(&MsgAddValidatorUnbondingSig{}, "btcstaking/MsgAddValidatorUnbondingSig", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -24,6 +26,8 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgAddJurySig{}, &MsgUpdateParams{}, &MsgBTCUndelegate{}, + &MsgAddJuryUnbondingSigs{}, + &MsgAddValidatorUnbondingSig{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 689613006..badf3c3c2 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,19 +6,22 @@ import ( // x/btcstaking module sentinel errors var ( - ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") - ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") - ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") - ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been slashed") - ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1104, "the BTC staking protocol is not activated yet") - ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1105, "the BTC height is not found") - ErrReusedStakingTx = errorsmod.Register(ModuleName, 1106, "the BTC staking tx is already used") - ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1107, "the BTC staking tx specifies a wrong jury PK") - ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1108, "the BTC staking tx is not valid") - ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1109, "the BTC slashing tx is not valid") - ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") - ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") - ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1112, "commission cannot be less than min rate") - ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1113, "Unexpected delegation state") - ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1114, "the BTC unbonding tx is not valid") + ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") + ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") + ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been slashed") + ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1104, "the BTC staking protocol is not activated yet") + ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1105, "the BTC height is not found") + ErrReusedStakingTx = errorsmod.Register(ModuleName, 1106, "the BTC staking tx is already used") + ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1107, "the BTC staking tx specifies a wrong jury PK") + ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1108, "the BTC staking tx is not valid") + ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1109, "the BTC slashing tx is not valid") + ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") + ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") + ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1112, "commission cannot be less than min rate") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1113, "Unexpected delegation state") + ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1114, "the BTC unbonding tx is not valid") + ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1115, "the BTC undelegation has already received validator signature") + ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1116, "the validator signature is not valid") + ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1117, "the BTC undelegation did not receive validator signature yet") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 636f99b2e..d1f6f968d 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -17,6 +17,8 @@ var ( _ sdk.Msg = &MsgCreateBTCDelegation{} _ sdk.Msg = &MsgAddJurySig{} _ sdk.Msg = &MsgBTCUndelegate{} + _ sdk.Msg = &MsgAddJuryUnbondingSigs{} + _ sdk.Msg = &MsgAddValidatorUnbondingSig{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -190,3 +192,55 @@ func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{signer} } + +func (m *MsgAddJuryUnbondingSigs) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgAddJuryUnbondingSigs) ValidateBasic() error { + if m.ValPk == nil { + return fmt.Errorf("empty BTC validator public key") + } + if m.DelPk == nil { + return fmt.Errorf("empty BTC delegation public key") + } + if m.UnbondingTxSig == nil { + return fmt.Errorf("empty jury signature") + } + if m.SlashingUnbondingTxSig == nil { + return fmt.Errorf("empty jury signature") + } + if len(m.StakingTxHash) != chainhash.MaxHashStringSize { + return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) + } + return nil +} + +func (m *MsgAddValidatorUnbondingSig) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgAddValidatorUnbondingSig) ValidateBasic() error { + if m.ValPk == nil { + return fmt.Errorf("empty BTC validator public key") + } + if m.DelPk == nil { + return fmt.Errorf("empty BTC delegation public key") + } + if m.UnbondingTxSig == nil { + return fmt.Errorf("empty jury signature") + } + if len(m.StakingTxHash) != chainhash.MaxHashStringSize { + return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) + } + + return nil +} diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index fde9ffcc9..f9f6821c6 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -570,6 +570,211 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo +// MsgAddJuryUnbondingSigs is the message for handling a signature from jury +type MsgAddJuryUnbondingSigs struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // unbonding_tx_sig is the signature of the jury on the unbodning tx submitted to babylon + // the signature follows encoding in BIP-340 spec + UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` + // slashing_unbonding_tx_sig is the signature of the jury on slashing tx corresponding to unbodning tx submitted to babylon + // the signature follows encoding in BIP-340 spec + SlashingUnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=slashing_unbonding_tx_sig,json=slashingUnbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_unbonding_tx_sig,omitempty"` +} + +func (m *MsgAddJuryUnbondingSigs) Reset() { *m = MsgAddJuryUnbondingSigs{} } +func (m *MsgAddJuryUnbondingSigs) String() string { return proto.CompactTextString(m) } +func (*MsgAddJuryUnbondingSigs) ProtoMessage() {} +func (*MsgAddJuryUnbondingSigs) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{10} +} +func (m *MsgAddJuryUnbondingSigs) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddJuryUnbondingSigs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddJuryUnbondingSigs.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddJuryUnbondingSigs) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddJuryUnbondingSigs.Merge(m, src) +} +func (m *MsgAddJuryUnbondingSigs) XXX_Size() int { + return m.Size() +} +func (m *MsgAddJuryUnbondingSigs) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddJuryUnbondingSigs.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddJuryUnbondingSigs proto.InternalMessageInfo + +func (m *MsgAddJuryUnbondingSigs) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgAddJuryUnbondingSigs) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +// MsgAddJurySigResponse is the response for MsgAddJurySig +type MsgAddJuryUnbondingSigsResponse struct { +} + +func (m *MsgAddJuryUnbondingSigsResponse) Reset() { *m = MsgAddJuryUnbondingSigsResponse{} } +func (m *MsgAddJuryUnbondingSigsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddJuryUnbondingSigsResponse) ProtoMessage() {} +func (*MsgAddJuryUnbondingSigsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{11} +} +func (m *MsgAddJuryUnbondingSigsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddJuryUnbondingSigsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddJuryUnbondingSigsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.Merge(m, src) +} +func (m *MsgAddJuryUnbondingSigsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAddJuryUnbondingSigsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddJuryUnbondingSigsResponse proto.InternalMessageInfo + +// MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker +type MsgAddValidatorUnbondingSig struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // val_pk is the Bitcoin secp256k1 PK of the BTC validator + // the PK follows encoding in BIP-340 spec + ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` + // del_pk is the Bitcoin secp256k1 PK of the BTC delegation + // the PK follows encoding in BIP-340 spec + DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // unbonding_tx_sig is the signature of validator for unbodning tx submitted to babylon by staker + // the signature follows encoding in BIP-340 spec + UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` +} + +func (m *MsgAddValidatorUnbondingSig) Reset() { *m = MsgAddValidatorUnbondingSig{} } +func (m *MsgAddValidatorUnbondingSig) String() string { return proto.CompactTextString(m) } +func (*MsgAddValidatorUnbondingSig) ProtoMessage() {} +func (*MsgAddValidatorUnbondingSig) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{12} +} +func (m *MsgAddValidatorUnbondingSig) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddValidatorUnbondingSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddValidatorUnbondingSig.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddValidatorUnbondingSig) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddValidatorUnbondingSig.Merge(m, src) +} +func (m *MsgAddValidatorUnbondingSig) XXX_Size() int { + return m.Size() +} +func (m *MsgAddValidatorUnbondingSig) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddValidatorUnbondingSig.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddValidatorUnbondingSig proto.InternalMessageInfo + +func (m *MsgAddValidatorUnbondingSig) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgAddValidatorUnbondingSig) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +// MsgAddJurySigResponse is the response for MsgAddJurySig +type MsgAddValidatorUnbondingSigResponse struct { +} + +func (m *MsgAddValidatorUnbondingSigResponse) Reset() { *m = MsgAddValidatorUnbondingSigResponse{} } +func (m *MsgAddValidatorUnbondingSigResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddValidatorUnbondingSigResponse) ProtoMessage() {} +func (*MsgAddValidatorUnbondingSigResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{13} +} +func (m *MsgAddValidatorUnbondingSigResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddValidatorUnbondingSigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAddValidatorUnbondingSigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.Merge(m, src) +} +func (m *MsgAddValidatorUnbondingSigResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAddValidatorUnbondingSigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddValidatorUnbondingSigResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgCreateBTCValidator)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidator") proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") @@ -581,74 +786,85 @@ func init() { proto.RegisterType((*MsgAddJurySigResponse)(nil), "babylon.btcstaking.v1.MsgAddJurySigResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") + proto.RegisterType((*MsgAddJuryUnbondingSigs)(nil), "babylon.btcstaking.v1.MsgAddJuryUnbondingSigs") + proto.RegisterType((*MsgAddJuryUnbondingSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddJuryUnbondingSigsResponse") + proto.RegisterType((*MsgAddValidatorUnbondingSig)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSig") + proto.RegisterType((*MsgAddValidatorUnbondingSigResponse)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSigResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 982 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0x6f, 0xea, 0x36, 0x28, 0x2f, 0x0d, 0x0b, 0xa6, 0xdb, 0x66, 0x23, 0x6d, 0x52, 0x85, 0x55, - 0x29, 0xab, 0xd6, 0x26, 0xd9, 0x6d, 0x05, 0x8b, 0x84, 0xb4, 0x4e, 0x91, 0x28, 0x4b, 0x44, 0x70, - 0xbc, 0x08, 0x71, 0x20, 0x8c, 0xed, 0x89, 0x63, 0x25, 0xf1, 0x58, 0x9e, 0x49, 0x94, 0x88, 0x1b, - 0x9f, 0x80, 0x13, 0x17, 0x24, 0x3e, 0x03, 0x48, 0x7b, 0xe2, 0x13, 0xec, 0x71, 0xb5, 0x27, 0xd4, - 0x43, 0x84, 0xda, 0x03, 0x5f, 0x03, 0xd9, 0x1e, 0xff, 0x49, 0x95, 0x40, 0x42, 0x4f, 0xf6, 0xf8, - 0xfd, 0xde, 0xef, 0xbd, 0xf9, 0xbd, 0x79, 0x6f, 0x0c, 0x65, 0x1d, 0xe9, 0xd3, 0x01, 0x71, 0x64, - 0x9d, 0x19, 0x94, 0xa1, 0xbe, 0xed, 0x58, 0xf2, 0xb8, 0x26, 0xb3, 0x89, 0xe4, 0x7a, 0x84, 0x11, - 0xf1, 0x2e, 0xb7, 0x4b, 0x89, 0x5d, 0x1a, 0xd7, 0x4a, 0xbb, 0x16, 0xb1, 0x48, 0x80, 0x90, 0xfd, - 0xb7, 0x10, 0x5c, 0xba, 0x67, 0x10, 0x3a, 0x24, 0xb4, 0x13, 0x1a, 0xc2, 0x05, 0x37, 0xed, 0x87, - 0x2b, 0x79, 0x48, 0x03, 0xfe, 0x21, 0xb5, 0xb8, 0xa1, 0xca, 0x0d, 0x86, 0x37, 0x75, 0x19, 0x91, - 0x29, 0x36, 0xdc, 0xfa, 0xe9, 0x59, 0xbf, 0x26, 0xf7, 0xf1, 0x34, 0x72, 0xae, 0x2e, 0x4e, 0xd2, - 0x45, 0x1e, 0x1a, 0x46, 0x98, 0xc3, 0xc5, 0x98, 0x54, 0xda, 0x21, 0xee, 0x38, 0x85, 0x33, 0x7a, - 0xd8, 0xe8, 0xbb, 0xc4, 0x76, 0x18, 0x87, 0x26, 0x1f, 0x38, 0xfa, 0x01, 0xcf, 0x2e, 0x61, 0xd4, - 0x31, 0x43, 0x35, 0x79, 0x8e, 0xb3, 0xfa, 0x8b, 0x00, 0x77, 0x9b, 0xd4, 0x6a, 0x78, 0x18, 0x31, - 0xac, 0x68, 0x8d, 0xaf, 0xd1, 0xc0, 0x36, 0x11, 0x23, 0x9e, 0xb8, 0x07, 0x59, 0x6a, 0x5b, 0x0e, - 0xf6, 0x8a, 0x99, 0x83, 0xcc, 0x51, 0x4e, 0xe5, 0x2b, 0xf1, 0x53, 0xc8, 0x9b, 0x98, 0x1a, 0x9e, - 0xed, 0x32, 0x9b, 0x38, 0xc5, 0xcd, 0x83, 0xcc, 0x51, 0xbe, 0xfe, 0xae, 0xc4, 0x25, 0x4b, 0x84, - 0x0e, 0xa2, 0x49, 0xe7, 0x09, 0x54, 0x4d, 0xfb, 0x89, 0xdf, 0x00, 0x18, 0x64, 0x38, 0xb4, 0x29, - 0xf5, 0x59, 0x04, 0x3f, 0x84, 0xf2, 0xe1, 0xe5, 0xac, 0x72, 0x68, 0xd9, 0xac, 0x37, 0xd2, 0x25, - 0x83, 0x0c, 0xe5, 0x48, 0xdf, 0xe0, 0x71, 0x42, 0xcd, 0xbe, 0xcc, 0xa6, 0x2e, 0xa6, 0xd2, 0x39, - 0x36, 0x5e, 0xbf, 0x38, 0x01, 0x1e, 0xf2, 0x1c, 0x1b, 0x6a, 0x8a, 0x4b, 0xfc, 0x04, 0x80, 0x0b, - 0xd5, 0x71, 0xfb, 0xc5, 0xad, 0x20, 0xbf, 0x4a, 0x94, 0x5f, 0x58, 0x2b, 0x29, 0xae, 0x95, 0xd4, - 0x1a, 0xe9, 0xcf, 0xf0, 0x54, 0xcd, 0x71, 0x97, 0x56, 0x5f, 0x6c, 0x42, 0x56, 0x67, 0x86, 0xef, - 0xbb, 0x7d, 0x90, 0x39, 0xda, 0x51, 0xce, 0x2e, 0x67, 0x95, 0x7a, 0x2a, 0x2b, 0x8e, 0x34, 0x7a, - 0xc8, 0x76, 0xa2, 0x05, 0x4f, 0x4c, 0xb9, 0x68, 0x3d, 0x7a, 0xfc, 0x01, 0xa7, 0xdc, 0xd6, 0x99, - 0xd1, 0xea, 0x8b, 0x4f, 0x40, 0x70, 0x89, 0x5b, 0xcc, 0x06, 0x79, 0x1c, 0x49, 0x0b, 0x0f, 0xa5, - 0xd4, 0xf2, 0x08, 0xe9, 0x7e, 0xd9, 0x6d, 0x11, 0x4a, 0x71, 0xb0, 0x0b, 0xd5, 0x77, 0xaa, 0x56, - 0xe0, 0xfe, 0xc2, 0xe2, 0xa8, 0x98, 0xba, 0xc4, 0xa1, 0xb8, 0x3a, 0x13, 0x60, 0x2f, 0x8d, 0x38, - 0xc7, 0x03, 0x6c, 0xa1, 0x40, 0xe0, 0x65, 0xf5, 0x9b, 0x97, 0x67, 0x73, 0x6d, 0x79, 0xf8, 0x7e, - 0x84, 0xff, 0xb1, 0x1f, 0xf1, 0x02, 0x80, 0x83, 0x3a, 0x6c, 0xc2, 0x4b, 0xf3, 0x70, 0x09, 0x85, - 0x12, 0x7e, 0x55, 0xb4, 0x86, 0x86, 0x5c, 0x8f, 0x10, 0xa6, 0x4d, 0xd4, 0x1c, 0xb7, 0x6b, 0x13, - 0xf1, 0x2b, 0xb8, 0x93, 0x50, 0x75, 0x6c, 0xa7, 0x4b, 0x82, 0x72, 0xe5, 0xeb, 0xef, 0xa7, 0xf9, - 0x52, 0x5d, 0x31, 0xae, 0x49, 0x9a, 0x87, 0x1c, 0x8a, 0x0c, 0x5f, 0x9e, 0x0b, 0xa7, 0x4b, 0xd4, - 0x42, 0x4c, 0xe7, 0x2f, 0xc5, 0x3a, 0xe4, 0xe9, 0x00, 0xd1, 0x1e, 0x4f, 0x2f, 0x1b, 0x54, 0xff, - 0xed, 0xcb, 0x59, 0xa5, 0xa0, 0x68, 0x8d, 0x36, 0xb7, 0x68, 0x13, 0x15, 0x68, 0xfc, 0x2e, 0x7e, - 0x07, 0x05, 0x33, 0xd4, 0x9c, 0x78, 0x1d, 0x6a, 0x5b, 0xc5, 0x37, 0x02, 0xaf, 0x8f, 0x2e, 0x67, - 0x95, 0xd3, 0x75, 0xce, 0x4c, 0xdb, 0xb6, 0x1c, 0xc4, 0x46, 0x1e, 0x56, 0x77, 0x62, 0xbe, 0xb6, - 0x6d, 0x55, 0x0f, 0xa0, 0xbc, 0xb8, 0xbe, 0xf1, 0x11, 0xf8, 0x75, 0x13, 0xde, 0x6a, 0x52, 0x4b, - 0xd1, 0x1a, 0xcf, 0x1d, 0xee, 0x8a, 0x97, 0x16, 0xbf, 0x09, 0x3b, 0x23, 0x47, 0x27, 0x8e, 0xc9, - 0xf7, 0xb8, 0xb9, 0x76, 0x09, 0xf2, 0xb1, 0xbf, 0x36, 0xb9, 0xa9, 0x98, 0xb0, 0x8a, 0x62, 0x04, - 0xf6, 0x52, 0x8a, 0x45, 0xde, 0xbe, 0x74, 0x5b, 0xb7, 0x95, 0x6e, 0x37, 0x91, 0x8e, 0xf3, 0xfa, - 0x12, 0x96, 0xa0, 0x78, 0x53, 0x9f, 0x58, 0xbc, 0x3f, 0x36, 0xa1, 0xd0, 0xa4, 0xd6, 0x53, 0xd3, - 0xfc, 0x7c, 0xe4, 0x4d, 0xdb, 0xb6, 0xf5, 0x2f, 0xca, 0x65, 0xc7, 0x68, 0x10, 0xb5, 0xcc, 0x2d, - 0xa6, 0xc2, 0x18, 0x0d, 0xc2, 0x21, 0x63, 0xe2, 0x80, 0x4e, 0xb8, 0x1d, 0x9d, 0x89, 0x7d, 0xba, - 0xc3, 0xb9, 0x6e, 0xe8, 0x21, 0xda, 0x0b, 0xd4, 0xcc, 0xa5, 0x8e, 0xf8, 0x67, 0x88, 0xf6, 0xc4, - 0x67, 0x20, 0xf8, 0x4a, 0x6f, 0xdf, 0x56, 0x69, 0x9f, 0xa5, 0xba, 0x1f, 0x5c, 0x1d, 0x89, 0x76, - 0xb1, 0xaa, 0x3f, 0x67, 0xe0, 0x4e, 0x93, 0x5a, 0xcf, 0x5d, 0x13, 0x31, 0xdc, 0x0a, 0xae, 0x3a, - 0xf1, 0x0c, 0x72, 0x68, 0xc4, 0x7a, 0xc4, 0xb3, 0xd9, 0x34, 0x94, 0x56, 0x29, 0xbe, 0x7e, 0x71, - 0xb2, 0xcb, 0x07, 0xcf, 0x53, 0xd3, 0xf4, 0x30, 0xa5, 0x6d, 0xe6, 0xd9, 0x8e, 0xa5, 0x26, 0x50, - 0xf1, 0x63, 0xc8, 0x86, 0x97, 0x25, 0x3f, 0xab, 0xf7, 0x97, 0x4d, 0x9c, 0x00, 0xa4, 0x6c, 0xbd, - 0x9c, 0x55, 0x36, 0x54, 0xee, 0xf2, 0xe4, 0xcd, 0x1f, 0xff, 0xfe, 0xed, 0x61, 0x42, 0x56, 0xbd, - 0x07, 0xfb, 0x37, 0xf2, 0x8a, 0x72, 0xae, 0xff, 0xbe, 0x05, 0x42, 0x93, 0x5a, 0xe2, 0x04, 0xc4, - 0x05, 0x97, 0xe1, 0xf1, 0x92, 0xa8, 0x0b, 0xa7, 0x73, 0xe9, 0xf1, 0x3a, 0xe8, 0x28, 0x03, 0xf1, - 0x07, 0x78, 0x67, 0xd1, 0x1c, 0x3f, 0x59, 0x81, 0x2c, 0x81, 0x97, 0x4e, 0xd7, 0x82, 0xc7, 0xc1, - 0x6d, 0x28, 0xcc, 0x4f, 0x90, 0xf7, 0x96, 0xf3, 0xcc, 0x01, 0x4b, 0xf2, 0x8a, 0xc0, 0x38, 0xd4, - 0xf7, 0x00, 0xa9, 0x7e, 0x7b, 0xb0, 0xdc, 0x3d, 0x41, 0x95, 0x8e, 0x57, 0x41, 0xc5, 0x11, 0xba, - 0xb0, 0x33, 0x77, 0xf6, 0x0e, 0x97, 0x7b, 0xa7, 0x71, 0x25, 0x69, 0x35, 0x5c, 0x14, 0x47, 0xf9, - 0xe2, 0xe5, 0x55, 0x39, 0xf3, 0xea, 0xaa, 0x9c, 0xf9, 0xeb, 0xaa, 0x9c, 0xf9, 0xe9, 0xba, 0xbc, - 0xf1, 0xea, 0xba, 0xbc, 0xf1, 0xe7, 0x75, 0x79, 0xe3, 0xdb, 0xff, 0x6c, 0xe5, 0x49, 0xfa, 0x67, - 0x2f, 0xe8, 0x31, 0x3d, 0x1b, 0xfc, 0x91, 0x3d, 0xfa, 0x27, 0x00, 0x00, 0xff, 0xff, 0x3a, 0x1b, - 0x89, 0x0a, 0xd8, 0x0a, 0x00, 0x00, + // 1104 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0xcf, 0x6f, 0xe3, 0xc4, + 0x17, 0x6f, 0x92, 0x36, 0x5f, 0xf5, 0xb5, 0xdd, 0xdd, 0xaf, 0xe9, 0xb6, 0x69, 0xd0, 0x26, 0x25, + 0xbb, 0x94, 0xb2, 0x6a, 0x6d, 0x9a, 0xdd, 0x56, 0x50, 0x24, 0xa4, 0x75, 0x8b, 0x44, 0x59, 0x22, + 0x82, 0x93, 0x22, 0xc4, 0x81, 0x30, 0xb6, 0xa7, 0x8e, 0x95, 0xc4, 0x63, 0x79, 0x26, 0x55, 0x22, + 0x24, 0x0e, 0x1c, 0x39, 0x71, 0xe2, 0x82, 0xc4, 0xdf, 0xc0, 0x61, 0x4f, 0x1c, 0x38, 0xef, 0x71, + 0xd5, 0x13, 0xea, 0x21, 0x5a, 0xb5, 0x07, 0xfe, 0x0d, 0x64, 0x7b, 0xfc, 0x23, 0xc5, 0xee, 0x36, + 0x84, 0x13, 0xe2, 0x94, 0x8c, 0xdf, 0xe7, 0x7d, 0xde, 0x9b, 0xcf, 0x9b, 0xf7, 0xc6, 0x86, 0x92, + 0x8a, 0xd4, 0x61, 0x97, 0x58, 0x92, 0xca, 0x34, 0xca, 0x50, 0xc7, 0xb4, 0x0c, 0xe9, 0x74, 0x47, + 0x62, 0x03, 0xd1, 0x76, 0x08, 0x23, 0xc2, 0x5d, 0x6e, 0x17, 0x23, 0xbb, 0x78, 0xba, 0x53, 0x5c, + 0x36, 0x88, 0x41, 0x3c, 0x84, 0xe4, 0xfe, 0xf3, 0xc1, 0xc5, 0x35, 0x8d, 0xd0, 0x1e, 0xa1, 0x2d, + 0xdf, 0xe0, 0x2f, 0xb8, 0x69, 0xd5, 0x5f, 0x49, 0x3d, 0xea, 0xf1, 0xf7, 0xa8, 0xc1, 0x0d, 0x15, + 0x6e, 0xd0, 0x9c, 0xa1, 0xcd, 0x88, 0x44, 0xb1, 0x66, 0x57, 0x77, 0xf7, 0x3a, 0x3b, 0x52, 0x07, + 0x0f, 0x03, 0xe7, 0x4a, 0x72, 0x92, 0x36, 0x72, 0x50, 0x2f, 0xc0, 0x6c, 0x24, 0x63, 0x62, 0x69, + 0xfb, 0xb8, 0xad, 0x18, 0x4e, 0x6b, 0x63, 0xad, 0x63, 0x13, 0xd3, 0x62, 0x1c, 0x1a, 0x3d, 0xe0, + 0xe8, 0x07, 0x3c, 0xbb, 0x88, 0x51, 0xc5, 0x0c, 0xed, 0x48, 0x63, 0x9c, 0x95, 0x9f, 0x72, 0x70, + 0xb7, 0x46, 0x8d, 0x03, 0x07, 0x23, 0x86, 0xe5, 0xe6, 0xc1, 0xe7, 0xa8, 0x6b, 0xea, 0x88, 0x11, + 0x47, 0x58, 0x81, 0x3c, 0x35, 0x0d, 0x0b, 0x3b, 0x85, 0xcc, 0x7a, 0x66, 0x73, 0x5e, 0xe1, 0x2b, + 0xe1, 0x43, 0x58, 0xd0, 0x31, 0xd5, 0x1c, 0xd3, 0x66, 0x26, 0xb1, 0x0a, 0xd9, 0xf5, 0xcc, 0xe6, + 0x42, 0xf5, 0xbe, 0xc8, 0x25, 0x8b, 0x84, 0xf6, 0xa2, 0x89, 0x87, 0x11, 0x54, 0x89, 0xfb, 0x09, + 0x5f, 0x00, 0x68, 0xa4, 0xd7, 0x33, 0x29, 0x75, 0x59, 0x72, 0x6e, 0x08, 0xf9, 0xdd, 0xf3, 0x51, + 0x79, 0xc3, 0x30, 0x59, 0xbb, 0xaf, 0x8a, 0x1a, 0xe9, 0x49, 0x81, 0xbe, 0xde, 0xcf, 0x36, 0xd5, + 0x3b, 0x12, 0x1b, 0xda, 0x98, 0x8a, 0x87, 0x58, 0x3b, 0x7b, 0xb6, 0x0d, 0x3c, 0xe4, 0x21, 0xd6, + 0x94, 0x18, 0x97, 0xf0, 0x01, 0x00, 0x17, 0xaa, 0x65, 0x77, 0x0a, 0xb3, 0x5e, 0x7e, 0xe5, 0x20, + 0x3f, 0xbf, 0x56, 0x62, 0x58, 0x2b, 0xb1, 0xde, 0x57, 0x9f, 0xe2, 0xa1, 0x32, 0xcf, 0x5d, 0xea, + 0x1d, 0xa1, 0x06, 0x79, 0x95, 0x69, 0xae, 0xef, 0xdc, 0x7a, 0x66, 0x73, 0x51, 0xde, 0x3b, 0x1f, + 0x95, 0xab, 0xb1, 0xac, 0x38, 0x52, 0x6b, 0x23, 0xd3, 0x0a, 0x16, 0x3c, 0x31, 0xf9, 0xa8, 0xfe, + 0xe8, 0xf1, 0x3b, 0x9c, 0x72, 0x4e, 0x65, 0x5a, 0xbd, 0x23, 0xec, 0x43, 0xce, 0x26, 0x76, 0x21, + 0xef, 0xe5, 0xb1, 0x29, 0x26, 0x1e, 0x4a, 0xb1, 0xee, 0x10, 0x72, 0xf2, 0xe9, 0x49, 0x9d, 0x50, + 0x8a, 0xbd, 0x5d, 0x28, 0xae, 0x53, 0xa5, 0x0c, 0xf7, 0x12, 0x8b, 0xa3, 0x60, 0x6a, 0x13, 0x8b, + 0xe2, 0xca, 0x28, 0x07, 0x2b, 0x71, 0xc4, 0x21, 0xee, 0x62, 0x03, 0x79, 0x02, 0xa7, 0xd5, 0x6f, + 0x5c, 0x9e, 0xec, 0xc4, 0xf2, 0xf0, 0xfd, 0xe4, 0xfe, 0xc6, 0x7e, 0x84, 0x23, 0x00, 0x0e, 0x6a, + 0xb1, 0x01, 0x2f, 0xcd, 0xc3, 0x14, 0x0a, 0xd9, 0x7f, 0x2a, 0x37, 0x0f, 0x9a, 0xc8, 0x76, 0x08, + 0x61, 0xcd, 0x81, 0x32, 0xcf, 0xed, 0xcd, 0x81, 0xf0, 0x19, 0xdc, 0x8e, 0xa8, 0x5a, 0xa6, 0x75, + 0x42, 0xbc, 0x72, 0x2d, 0x54, 0xdf, 0x8e, 0xf3, 0xc5, 0xba, 0xe2, 0x74, 0x47, 0x6c, 0x3a, 0xc8, + 0xa2, 0x48, 0x73, 0xe5, 0x39, 0xb2, 0x4e, 0x88, 0xb2, 0x14, 0xd2, 0xb9, 0x4b, 0xa1, 0x0a, 0x0b, + 0xb4, 0x8b, 0x68, 0x9b, 0xa7, 0x97, 0xf7, 0xaa, 0xff, 0xff, 0xf3, 0x51, 0x79, 0x49, 0x6e, 0x1e, + 0x34, 0xb8, 0xa5, 0x39, 0x50, 0x80, 0x86, 0xff, 0x85, 0xaf, 0x60, 0x49, 0xf7, 0x35, 0x27, 0x4e, + 0x8b, 0x9a, 0x46, 0xe1, 0x7f, 0x9e, 0xd7, 0x7b, 0xe7, 0xa3, 0xf2, 0xee, 0x24, 0x67, 0xa6, 0x61, + 0x1a, 0x16, 0x62, 0x7d, 0x07, 0x2b, 0x8b, 0x21, 0x5f, 0xc3, 0x34, 0x2a, 0xeb, 0x50, 0x4a, 0xae, + 0x6f, 0x78, 0x04, 0x7e, 0xce, 0xc2, 0x9d, 0x1a, 0x35, 0xe4, 0xe6, 0xc1, 0xb1, 0xc5, 0x5d, 0x71, + 0x6a, 0xf1, 0x6b, 0xb0, 0xd8, 0xb7, 0x54, 0x62, 0xe9, 0x7c, 0x8f, 0xd9, 0x89, 0x4b, 0xb0, 0x10, + 0xfa, 0x37, 0x07, 0x57, 0x15, 0xcb, 0xdd, 0x44, 0x31, 0x02, 0x2b, 0x31, 0xc5, 0x02, 0x6f, 0x57, + 0xba, 0xd9, 0x69, 0xa5, 0x5b, 0x8e, 0xa4, 0xe3, 0xbc, 0xae, 0x84, 0x45, 0x28, 0x5c, 0xd5, 0x27, + 0x14, 0xef, 0xd7, 0x2c, 0x2c, 0xd5, 0xa8, 0xf1, 0x44, 0xd7, 0x3f, 0xee, 0x3b, 0xc3, 0x86, 0x69, + 0x5c, 0xa3, 0x5c, 0xfe, 0x14, 0x75, 0x83, 0x96, 0x99, 0x62, 0x2a, 0x9c, 0xa2, 0xae, 0x3f, 0x64, + 0x74, 0xec, 0xd1, 0xe5, 0xa6, 0xa3, 0xd3, 0xb1, 0x4b, 0xb7, 0x31, 0xd6, 0x0d, 0x6d, 0x44, 0xdb, + 0x9e, 0x9a, 0xf3, 0xb1, 0x23, 0xfe, 0x11, 0xa2, 0x6d, 0xe1, 0x29, 0xe4, 0x5c, 0xa5, 0xe7, 0xa6, + 0x55, 0xda, 0x65, 0xa9, 0xac, 0x7a, 0x57, 0x47, 0xa4, 0x5d, 0xa8, 0xea, 0x8f, 0x19, 0xb8, 0x5d, + 0xa3, 0xc6, 0xb1, 0xad, 0x23, 0x86, 0xeb, 0xde, 0x55, 0x27, 0xec, 0xc1, 0x3c, 0xea, 0xb3, 0x36, + 0x71, 0x4c, 0x36, 0xf4, 0xa5, 0x95, 0x0b, 0x67, 0xcf, 0xb6, 0x97, 0xf9, 0xe0, 0x79, 0xa2, 0xeb, + 0x0e, 0xa6, 0xb4, 0xc1, 0x1c, 0xd3, 0x32, 0x94, 0x08, 0x2a, 0xbc, 0x0f, 0x79, 0xff, 0xb2, 0xe4, + 0x67, 0xf5, 0x5e, 0xda, 0xc4, 0xf1, 0x40, 0xf2, 0xec, 0xf3, 0x51, 0x79, 0x46, 0xe1, 0x2e, 0xfb, + 0xb7, 0xbe, 0xfb, 0xe3, 0x97, 0x87, 0x11, 0x59, 0x65, 0x0d, 0x56, 0xaf, 0xe4, 0x15, 0xe6, 0x7c, + 0x96, 0xf3, 0x6c, 0x7c, 0x37, 0xc7, 0xc1, 0x21, 0x6f, 0x98, 0x06, 0xfd, 0x97, 0x9f, 0x09, 0x0d, + 0xee, 0xc4, 0x67, 0x42, 0xeb, 0x1f, 0x39, 0x20, 0xb7, 0x62, 0x63, 0xc2, 0x6d, 0x2b, 0x06, 0x6b, + 0x61, 0xaf, 0xff, 0x25, 0x5a, 0x7e, 0xda, 0x68, 0x2b, 0x01, 0xf7, 0xf1, 0x58, 0xd4, 0xca, 0x1b, + 0x50, 0x4e, 0xa9, 0x69, 0x58, 0xf7, 0x97, 0x59, 0x78, 0xdd, 0xc7, 0x84, 0xb7, 0x6b, 0x1c, 0xf8, + 0x5f, 0xed, 0xa7, 0xae, 0x7d, 0xe5, 0x4d, 0xb8, 0x7f, 0x8d, 0xc2, 0x41, 0x25, 0xaa, 0xbf, 0xe5, + 0x21, 0x57, 0xa3, 0x86, 0x30, 0x00, 0x21, 0xe1, 0x75, 0x74, 0x2b, 0xa5, 0xef, 0x13, 0xdf, 0x8f, + 0x8a, 0x8f, 0x27, 0x41, 0x07, 0x19, 0x08, 0xdf, 0xc0, 0x6b, 0x49, 0x6f, 0x52, 0xdb, 0x37, 0x20, + 0x8b, 0xe0, 0xc5, 0xdd, 0x89, 0xe0, 0x61, 0x70, 0x13, 0x96, 0xc6, 0xef, 0xf0, 0xb7, 0xd2, 0x79, + 0xc6, 0x80, 0x45, 0xe9, 0x86, 0xc0, 0x30, 0xd4, 0xd7, 0x00, 0xb1, 0x1b, 0xef, 0x41, 0xba, 0x7b, + 0x84, 0x2a, 0x6e, 0xdd, 0x04, 0x15, 0x46, 0xf8, 0x16, 0x96, 0x13, 0x27, 0xa9, 0xf8, 0x4a, 0x96, + 0x31, 0x7c, 0x71, 0x6f, 0x32, 0x7c, 0x18, 0xff, 0xfb, 0x0c, 0x14, 0x52, 0x5b, 0xba, 0x7a, 0x2d, + 0x69, 0xa2, 0x4f, 0x71, 0x7f, 0x72, 0x9f, 0x30, 0x99, 0x13, 0x58, 0x1c, 0xbb, 0x0a, 0x37, 0xd2, + 0xb9, 0xe2, 0xb8, 0xa2, 0x78, 0x33, 0x5c, 0x10, 0x47, 0xfe, 0xe4, 0xf9, 0x45, 0x29, 0xf3, 0xe2, + 0xa2, 0x94, 0x79, 0x79, 0x51, 0xca, 0xfc, 0x70, 0x59, 0x9a, 0x79, 0x71, 0x59, 0x9a, 0xf9, 0xfd, + 0xb2, 0x34, 0xf3, 0xe5, 0x2b, 0x27, 0xc9, 0x20, 0xfe, 0xed, 0xe9, 0x75, 0xb5, 0x9a, 0xf7, 0x3e, + 0x10, 0x1f, 0xfd, 0x19, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x77, 0x1d, 0xb1, 0x67, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -669,8 +885,14 @@ type MsgClient interface { CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury + // AddJurySig handles a signature from jury for slashing tx of staking tx AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) + // AddJuryUnbondingSigs handles a signature from jury for: + // - unbonding tx submitted to babylon by staker + // - slashing tx corresponding to unbodning tx submitted to babylon by staker + AddJuryUnbondingSigs(ctx context.Context, in *MsgAddJuryUnbondingSigs, opts ...grpc.CallOption) (*MsgAddJuryUnbondingSigsResponse, error) + // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker + AddValidatorUnbondingSig(ctx context.Context, in *MsgAddValidatorUnbondingSig, opts ...grpc.CallOption) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -719,6 +941,24 @@ func (c *msgClient) AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...g return out, nil } +func (c *msgClient) AddJuryUnbondingSigs(ctx context.Context, in *MsgAddJuryUnbondingSigs, opts ...grpc.CallOption) (*MsgAddJuryUnbondingSigsResponse, error) { + out := new(MsgAddJuryUnbondingSigsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddJuryUnbondingSigs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) AddValidatorUnbondingSig(ctx context.Context, in *MsgAddValidatorUnbondingSig, opts ...grpc.CallOption) (*MsgAddValidatorUnbondingSigResponse, error) { + out := new(MsgAddValidatorUnbondingSigResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddValidatorUnbondingSig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) @@ -736,8 +976,14 @@ type MsgServer interface { CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury + // AddJurySig handles a signature from jury for slashing tx of staking tx AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) + // AddJuryUnbondingSigs handles a signature from jury for: + // - unbonding tx submitted to babylon by staker + // - slashing tx corresponding to unbodning tx submitted to babylon by staker + AddJuryUnbondingSigs(context.Context, *MsgAddJuryUnbondingSigs) (*MsgAddJuryUnbondingSigsResponse, error) + // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker + AddValidatorUnbondingSig(context.Context, *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } @@ -758,6 +1004,12 @@ func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUnd func (*UnimplementedMsgServer) AddJurySig(ctx context.Context, req *MsgAddJurySig) (*MsgAddJurySigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddJurySig not implemented") } +func (*UnimplementedMsgServer) AddJuryUnbondingSigs(ctx context.Context, req *MsgAddJuryUnbondingSigs) (*MsgAddJuryUnbondingSigsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddJuryUnbondingSigs not implemented") +} +func (*UnimplementedMsgServer) AddValidatorUnbondingSig(ctx context.Context, req *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddValidatorUnbondingSig not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -838,6 +1090,42 @@ func _Msg_AddJurySig_Handler(srv interface{}, ctx context.Context, dec func(inte return interceptor(ctx, in, info, handler) } +func _Msg_AddJuryUnbondingSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddJuryUnbondingSigs) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AddJuryUnbondingSigs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/AddJuryUnbondingSigs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AddJuryUnbondingSigs(ctx, req.(*MsgAddJuryUnbondingSigs)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_AddValidatorUnbondingSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddValidatorUnbondingSig) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AddValidatorUnbondingSig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/AddValidatorUnbondingSig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AddValidatorUnbondingSig(ctx, req.(*MsgAddValidatorUnbondingSig)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -876,6 +1164,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AddJurySig", Handler: _Msg_AddJurySig_Handler, }, + { + MethodName: "AddJuryUnbondingSigs", + Handler: _Msg_AddJuryUnbondingSigs_Handler, + }, + { + MethodName: "AddValidatorUnbondingSig", + Handler: _Msg_AddValidatorUnbondingSig_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -1371,75 +1667,279 @@ func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *MsgCreateBTCValidator) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Description != nil { - l = m.Description.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.Commission != nil { - l = m.Commission.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.BabylonPk != nil { - l = m.BabylonPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.BtcPk != nil { - l = m.BtcPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.Pop != nil { - l = m.Pop.Size() - n += 1 + l + sovTx(uint64(l)) +func (m *MsgAddJuryUnbondingSigs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - return n + return dAtA[:n], nil } -func (m *MsgCreateBTCValidatorResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n +func (m *MsgAddJuryUnbondingSigs) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgCreateBTCDelegation) Size() (n int) { - if m == nil { - return 0 - } +func (m *MsgAddJuryUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.BabylonPk != nil { - l = m.BabylonPk.Size() - n += 1 + l + sovTx(uint64(l)) + if m.SlashingUnbondingTxSig != nil { + { + size := m.SlashingUnbondingTxSig.Size() + i -= size + if _, err := m.SlashingUnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 } - if m.Pop != nil { - l = m.Pop.Size() + if m.UnbondingTxSig != nil { + { + size := m.UnbondingTxSig.Size() + i -= size + if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x22 + } + if m.DelPk != nil { + { + size := m.DelPk.Size() + i -= size + if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ValPk != nil { + { + size := m.ValPk.Size() + i -= size + if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAddJuryUnbondingSigsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAddJuryUnbondingSigsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddJuryUnbondingSigsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgAddValidatorUnbondingSig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAddValidatorUnbondingSig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddValidatorUnbondingSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UnbondingTxSig != nil { + { + size := m.UnbondingTxSig.Size() + i -= size + if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x22 + } + if m.DelPk != nil { + { + size := m.DelPk.Size() + i -= size + if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ValPk != nil { + { + size := m.ValPk.Size() + i -= size + if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAddValidatorUnbondingSigResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAddValidatorUnbondingSigResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddValidatorUnbondingSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgCreateBTCValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Description != nil { + l = m.Description.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Commission != nil { + l = m.Commission.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCreateBTCValidatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgCreateBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Pop != nil { + l = m.Pop.Size() n += 1 + l + sovTx(uint64(l)) } if m.StakingTx != nil { @@ -1566,11 +2066,91 @@ func (m *MsgUpdateParamsResponse) Size() (n int) { return n } -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 +func (m *MsgAddJuryUnbondingSigs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ValPk != nil { + l = m.ValPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.DelPk != nil { + l = m.DelPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.UnbondingTxSig != nil { + l = m.UnbondingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.SlashingUnbondingTxSig != nil { + l = m.SlashingUnbondingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n } -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) + +func (m *MsgAddJuryUnbondingSigsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgAddValidatorUnbondingSig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ValPk != nil { + l = m.ValPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.DelPk != nil { + l = m.DelPk.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.UnbondingTxSig != nil { + l = m.UnbondingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAddValidatorUnbondingSigResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { l := len(dAtA) @@ -1598,7 +2178,664 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: MsgCreateBTCValidator: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCreateBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Description == nil { + m.Description = &types.Description{} + } + if err := m.Description.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commission", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_cosmos_cosmos_sdk_types.Dec + m.Commission = &v + if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pop == nil { + m.Pop = &ProofOfPossession{} + } + if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StakingTx == nil { + m.StakingTx = &BabylonBTCTaprootTx{} + } + if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StakingTxInfo == nil { + m.StakingTxInfo = &types1.TransactionInfo{} + } + if err := m.StakingTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorSig = &v + if err := m.DelegatorSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgBTCUndelegate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgBTCUndelegate: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1635,7 +2872,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1662,18 +2899,18 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Description == nil { - m.Description = &types.Description{} + if m.UnbondingTx == nil { + m.UnbondingTx = &BabylonBTCTaprootTx{} } - if err := m.Description.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Commission", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -1683,67 +2920,30 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_cosmos_cosmos_sdk_types.Dec - m.Commission = &v - if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.BabylonPk == nil { - m.BabylonPk = &secp256k1.PubKey{} - } - if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1770,45 +2970,9 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.BtcPk = &v - if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pop == nil { - m.Pop = &ProofOfPossession{} - } - if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorSlashingSig = &v + if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1833,7 +2997,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { +func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1856,10 +3020,10 @@ func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgBTCUndelegateResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgBTCUndelegateResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1883,7 +3047,7 @@ func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { +func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1906,10 +3070,10 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCDelegation: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddJurySig: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddJurySig: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1946,9 +3110,9 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -1958,33 +3122,32 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if m.BabylonPk == nil { - m.BabylonPk = &secp256k1.PubKey{} - } - if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValPk = &v + if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -1994,33 +3157,32 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Pop == nil { - m.Pop = &ProofOfPossession{} - } - if err := m.Pop.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.DelPk = &v + if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -2030,102 +3192,27 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if m.StakingTx == nil { - m.StakingTx = &BabylonBTCTaprootTx{} - } - if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxInfo", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.StakingTxInfo == nil { - m.StakingTxInfo = &types1.TransactionInfo{} - } - if err := m.StakingTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v BTCSlashingTx - m.SlashingTx = &v - if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2153,8 +3240,8 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.DelegatorSig = &v - if err := m.DelegatorSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Sig = &v + if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2179,7 +3266,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2202,10 +3289,10 @@ func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddJurySigResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddJurySigResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2229,7 +3316,7 @@ func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2252,15 +3339,15 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgBTCUndelegate: wiretype end group for non-group") + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBTCUndelegate: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2288,11 +3375,11 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signer = string(dAtA[iNdEx:postIndex]) + m.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2319,80 +3406,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.UnbondingTx == nil { - m.UnbondingTx = &BabylonBTCTaprootTx{} - } - if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v BTCSlashingTx - m.SlashingTx = &v - if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.DelegatorSlashingSig = &v - if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2417,7 +3431,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2440,10 +3454,10 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgBTCUndelegateResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBTCUndelegateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2467,7 +3481,7 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { +func (m *MsgAddJuryUnbondingSigs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2490,10 +3504,10 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJurySig: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddJuryUnbondingSigs: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJurySig: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddJuryUnbondingSigs: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2632,7 +3646,7 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2660,8 +3674,43 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.Sig = &v - if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.UnbondingTxSig = &v + if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.SlashingUnbondingTxSig = &v + if err := m.SlashingUnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2686,7 +3735,7 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddJuryUnbondingSigsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2709,10 +3758,10 @@ func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJurySigResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddJuryUnbondingSigsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJurySigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddJuryUnbondingSigsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2736,7 +3785,7 @@ func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { +func (m *MsgAddValidatorUnbondingSig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2759,15 +3808,15 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddValidatorUnbondingSig: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddValidatorUnbondingSig: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2795,13 +3844,13 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Authority = string(dAtA[iNdEx:postIndex]) + m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -2811,22 +3860,126 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValPk = &v + if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.DelPk = &v + if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.UnbondingTxSig = &v + if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2851,7 +4004,7 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddValidatorUnbondingSigResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2874,10 +4027,10 @@ func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddValidatorUnbondingSigResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddValidatorUnbondingSigResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: From c23587edbcab07b879162fdaee08b799973f0a22 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 6 Sep 2023 08:17:37 +0200 Subject: [PATCH 074/202] Rename expired to unbonded (#73) * Rename expired to unbonded --- proto/babylon/btcstaking/v1/btcstaking.proto | 14 +- test/e2e/btc_staking_e2e_test.go | 2 +- x/btcstaking/types/btcstaking.go | 4 +- x/btcstaking/types/btcstaking.pb.go | 145 ++++++++++--------- x/btcstaking/types/tx.pb.go | 10 +- x/btcstaking/types/utils.go | 18 --- 6 files changed, 96 insertions(+), 97 deletions(-) delete mode 100644 x/btcstaking/types/utils.go diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 6be49c4c6..d96fa3b7d 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -151,7 +151,12 @@ message BabylonBTCTaprootTx { bytes script = 2; } -// BTCDelegationStatus is the status of a delegation. +// BTCDelegationStatus is the status of a delegation. There are two possible state +// transition paths: +// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path when timelock of staking +// transaction expires. +// 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through +// MsgBTCUndelegate message. enum BTCDelegationStatus { // PENDING defines a delegation that is waiting for a jury signature to become active. PENDING = 0; @@ -161,7 +166,10 @@ enum BTCDelegationStatus { // from staker, but not yet received signatures from validator and jury. // Delegation in this state already lost its voting power. UNBONDING = 2; - // EXPIRED defines a delegation that has expired - EXPIRED = 3; + // UNBONDED defines a delegation no longer has voting power: + // - either reaching the end of staking transaction timelock + // - or receiving unbonding tx and then receiving signatures from validator and jury for this + // unbonding tx. + UNBONDED = 3; } diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 90efa2c1c..5a6afc028 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -426,7 +426,7 @@ func (s *BTCStakingTestSuite) Test5SubmitUnbondingSignatures() { btcTip, err = nonValidatorNode.QueryTip() s.NoError(err) s.Equal( - bstypes.BTCDelegationStatus_EXPIRED, + bstypes.BTCDelegationStatus_UNBONDED, delegationWithSigs.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), ) } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index e286762f9..899aef7f5 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -110,7 +110,7 @@ func (ud *BTCUndelegation) HasAllSignatures() bool { func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { if d.BtcUndelegation != nil { if d.BtcUndelegation.HasAllSignatures() { - return BTCDelegationStatus_EXPIRED + return BTCDelegationStatus_UNBONDED } // If we received an undelegation but is still does not have all required signature, // delegation receives UNBONING status. @@ -129,7 +129,7 @@ func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatu return BTCDelegationStatus_PENDING } } - return BTCDelegationStatus_EXPIRED + return BTCDelegationStatus_UNBONDED } // VotingPower returns the voting power of the BTC delegation at a given BTC height diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index bcde0cec8..15b311590 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -28,7 +28,12 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// BTCDelegationStatus is the status of a delegation. +// BTCDelegationStatus is the status of a delegation. There are two possible state +// transition paths: +// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path whent timelock of staking +// transaction expires. +// 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through +// MsgBTCUndelegate message. type BTCDelegationStatus int32 const ( @@ -37,24 +42,28 @@ const ( // ACTIVE defines a delegation that has voting power BTCDelegationStatus_ACTIVE BTCDelegationStatus = 1 // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx - // from staker, but not yet received signatures from validator and jury + // from staker, but not yet received signatures from validator and jury. + // Delegation in this state already lost its voting power. BTCDelegationStatus_UNBONDING BTCDelegationStatus = 2 - // EXPIRED defines a delegation that has expired - BTCDelegationStatus_EXPIRED BTCDelegationStatus = 3 + // UNBONDED defines a delegation no longer has voting power: + // - either reaching the end of staking transaction timelock + // - or receiving unbonding tx and then receiving signatures from validator and jury for this + // unbonding tx. + BTCDelegationStatus_UNBONDED BTCDelegationStatus = 3 ) var BTCDelegationStatus_name = map[int32]string{ 0: "PENDING", 1: "ACTIVE", 2: "UNBONDING", - 3: "EXPIRED", + 3: "UNBONDED", } var BTCDelegationStatus_value = map[string]int32{ "PENDING": 0, "ACTIVE": 1, "UNBONDING": 2, - "EXPIRED": 3, + "UNBONDED": 3, } func (x BTCDelegationStatus) String() string { @@ -598,69 +607,69 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 987 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x73, 0xdb, 0x44, - 0x14, 0x8f, 0x6c, 0xc7, 0x89, 0x9f, 0x1c, 0xe2, 0x6c, 0xd3, 0xd4, 0x94, 0xc1, 0x4e, 0x4d, 0x27, - 0xe3, 0xe9, 0x50, 0x99, 0xa4, 0x7f, 0xa6, 0x30, 0x03, 0x33, 0x28, 0xce, 0x40, 0x80, 0xa4, 0x46, - 0x56, 0x4a, 0x87, 0x03, 0x1e, 0xfd, 0xd9, 0xca, 0xc2, 0x8e, 0x56, 0x68, 0xd7, 0xc6, 0xbe, 0x73, - 0xe0, 0xc8, 0x8d, 0x2f, 0xc2, 0x87, 0xe8, 0xb1, 0xc3, 0x89, 0xc9, 0xc1, 0xc3, 0x24, 0x5f, 0x84, - 0xd9, 0xd5, 0xca, 0x56, 0x9a, 0x06, 0x48, 0x9d, 0x93, 0xbd, 0xfb, 0xde, 0xfb, 0xbd, 0xf7, 0x7e, - 0xbf, 0xa7, 0x27, 0xc1, 0x96, 0x6d, 0xd9, 0xe3, 0x3e, 0x09, 0x1a, 0x36, 0x73, 0x28, 0xb3, 0x7a, - 0x7e, 0xe0, 0x35, 0x86, 0xdb, 0xa9, 0x93, 0x16, 0x46, 0x84, 0x11, 0x74, 0x53, 0xfa, 0x69, 0x29, - 0xcb, 0x70, 0xfb, 0xf6, 0xba, 0x47, 0x3c, 0x22, 0x3c, 0x1a, 0xfc, 0x5f, 0xec, 0x7c, 0xfb, 0x5d, - 0x87, 0xd0, 0x63, 0x42, 0x3b, 0xb1, 0x21, 0x3e, 0x48, 0x53, 0x2d, 0x3e, 0x35, 0x9c, 0x68, 0x1c, - 0x32, 0xd2, 0xa0, 0xd8, 0x09, 0x77, 0x1e, 0x3d, 0xee, 0x6d, 0x37, 0x7a, 0x78, 0x9c, 0xf8, 0xdc, - 0x95, 0x3e, 0xb3, 0x7a, 0x6c, 0xcc, 0xac, 0xed, 0xc6, 0xb9, 0x8a, 0x6a, 0x93, 0x2c, 0x14, 0x75, - 0x73, 0xf7, 0x99, 0xd5, 0xf7, 0x5d, 0x8b, 0x91, 0x08, 0xed, 0x81, 0xea, 0x62, 0xea, 0x44, 0x7e, - 0xc8, 0x7c, 0x12, 0x94, 0x95, 0x4d, 0xa5, 0xae, 0xee, 0x7c, 0xa0, 0xc9, 0xf4, 0xb3, 0xa2, 0x05, - 0x98, 0xd6, 0x9c, 0xb9, 0x1a, 0xe9, 0x38, 0xf4, 0x1c, 0xc0, 0x21, 0xc7, 0xc7, 0x3e, 0xa5, 0x1c, - 0x25, 0xb3, 0xa9, 0xd4, 0x0b, 0xfa, 0x93, 0x93, 0x49, 0x75, 0xcb, 0xf3, 0x59, 0x77, 0x60, 0x6b, - 0x0e, 0x39, 0x6e, 0x24, 0x4d, 0x88, 0x9f, 0xfb, 0xd4, 0xed, 0x35, 0xd8, 0x38, 0xc4, 0x54, 0x6b, - 0x62, 0xe7, 0xcf, 0x3f, 0xee, 0x83, 0x4c, 0xd9, 0xc4, 0x8e, 0x91, 0xc2, 0x42, 0x9f, 0x01, 0x48, - 0x16, 0x3b, 0x61, 0xaf, 0x9c, 0x15, 0xf5, 0x55, 0x93, 0xfa, 0x62, 0x42, 0xb4, 0x29, 0x21, 0x5a, - 0x6b, 0x60, 0x7f, 0x8d, 0xc7, 0x46, 0x41, 0x86, 0xb4, 0x7a, 0xe8, 0x00, 0xf2, 0x36, 0x73, 0x78, - 0x6c, 0x6e, 0x53, 0xa9, 0x17, 0xf5, 0xc7, 0x27, 0x93, 0xea, 0x4e, 0xaa, 0x2a, 0xe9, 0xe9, 0x74, - 0x2d, 0x3f, 0x48, 0x0e, 0xb2, 0x30, 0x7d, 0xbf, 0xf5, 0xe0, 0xe1, 0x47, 0x12, 0x72, 0xd1, 0x66, - 0x4e, 0xab, 0x87, 0x3e, 0x81, 0x6c, 0x48, 0xc2, 0xf2, 0xa2, 0xa8, 0xa3, 0xae, 0xbd, 0x51, 0x60, - 0xad, 0x15, 0x11, 0xf2, 0xe2, 0xe9, 0x8b, 0x16, 0xa1, 0x14, 0x8b, 0x2e, 0x0c, 0x1e, 0x84, 0x1e, - 0xc2, 0x06, 0xed, 0x5b, 0xb4, 0x8b, 0xdd, 0x4e, 0xd2, 0x52, 0x17, 0xfb, 0x5e, 0x97, 0x95, 0xf3, - 0x9b, 0x4a, 0x3d, 0x67, 0xac, 0x4b, 0xab, 0x1e, 0x1b, 0xbf, 0x14, 0x36, 0xf4, 0x21, 0xa0, 0x69, - 0x14, 0x73, 0x92, 0x88, 0x25, 0x11, 0x51, 0x4a, 0x22, 0x98, 0x13, 0x7b, 0xd7, 0x7e, 0xc9, 0xc0, - 0x7a, 0x5a, 0xe0, 0xef, 0x7c, 0xd6, 0x3d, 0xc0, 0xcc, 0x4a, 0xf1, 0xa0, 0x5c, 0x07, 0x0f, 0x1b, - 0x90, 0x97, 0x95, 0x64, 0x44, 0x25, 0xf2, 0x84, 0xee, 0x40, 0x71, 0x48, 0x98, 0x1f, 0x78, 0x9d, - 0x90, 0xfc, 0x8c, 0x23, 0x21, 0x58, 0xce, 0x50, 0xe3, 0xbb, 0x16, 0xbf, 0xfa, 0x17, 0x1a, 0x72, - 0x57, 0xa6, 0x61, 0xf1, 0x12, 0x1a, 0x7e, 0xcf, 0xc3, 0x8a, 0x6e, 0xee, 0x36, 0x71, 0x1f, 0x7b, - 0x16, 0xbb, 0x38, 0x47, 0xca, 0x1c, 0x73, 0x94, 0xb9, 0xc6, 0x39, 0xca, 0xbe, 0xcd, 0x1c, 0x99, - 0x00, 0x43, 0xab, 0xdf, 0xb9, 0x96, 0xb1, 0x5e, 0x1e, 0x5a, 0x7d, 0x5d, 0x54, 0x74, 0x07, 0x8a, + // 985 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0x8e, 0x13, 0xbf, 0x75, 0x89, 0x33, 0x4d, 0x53, 0x53, 0x84, 0x9d, 0x9a, 0x2a, + 0xb2, 0x2a, 0xba, 0x26, 0xe9, 0x1f, 0x15, 0x24, 0x90, 0x58, 0x3b, 0x82, 0xa8, 0x24, 0x35, 0xeb, + 0x4d, 0x41, 0x1c, 0xb0, 0xf6, 0xcf, 0x74, 0xbd, 0xd8, 0xd9, 0x59, 0x76, 0xc6, 0xc6, 0xbe, 0x73, + 0xe0, 0xc8, 0x8d, 0x2f, 0xc2, 0x87, 0xe8, 0xb1, 0xe2, 0x84, 0x72, 0xb0, 0x50, 0xf2, 0x45, 0xd0, + 0xcc, 0xce, 0xda, 0x9b, 0xa6, 0x01, 0x82, 0x73, 0xb2, 0x67, 0xde, 0x7b, 0xbf, 0xf7, 0xde, 0xef, + 0xf7, 0xf6, 0xed, 0xc2, 0xb6, 0x6d, 0xd9, 0x93, 0x01, 0x09, 0x1a, 0x36, 0x73, 0x28, 0xb3, 0xfa, + 0x7e, 0xe0, 0x35, 0x46, 0x3b, 0xa9, 0x93, 0x16, 0x46, 0x84, 0x11, 0x74, 0x4b, 0xfa, 0x69, 0x29, + 0xcb, 0x68, 0xe7, 0xce, 0x86, 0x47, 0x3c, 0x22, 0x3c, 0x1a, 0xfc, 0x5f, 0xec, 0x7c, 0xe7, 0x5d, + 0x87, 0xd0, 0x63, 0x42, 0xbb, 0xb1, 0x21, 0x3e, 0x48, 0x53, 0x2d, 0x3e, 0x35, 0x9c, 0x68, 0x12, + 0x32, 0xd2, 0xa0, 0xd8, 0x09, 0x77, 0x1f, 0x3f, 0xe9, 0xef, 0x34, 0xfa, 0x78, 0x92, 0xf8, 0xdc, + 0x93, 0x3e, 0xf3, 0x7a, 0x6c, 0xcc, 0xac, 0x9d, 0xc6, 0xb9, 0x8a, 0x6a, 0xd3, 0x2c, 0x14, 0x75, + 0xb3, 0xf9, 0xc2, 0x1a, 0xf8, 0xae, 0xc5, 0x48, 0x84, 0xf6, 0x40, 0x75, 0x31, 0x75, 0x22, 0x3f, + 0x64, 0x3e, 0x09, 0xca, 0xca, 0x96, 0x52, 0x57, 0x77, 0x3f, 0xd0, 0x64, 0xfa, 0x79, 0xd1, 0x02, + 0x4c, 0x6b, 0xcd, 0x5d, 0x8d, 0x74, 0x1c, 0xfa, 0x16, 0xc0, 0x21, 0xc7, 0xc7, 0x3e, 0xa5, 0x1c, + 0x25, 0xb3, 0xa5, 0xd4, 0x0b, 0xfa, 0xd3, 0x93, 0x69, 0x75, 0xdb, 0xf3, 0x59, 0x6f, 0x68, 0x6b, + 0x0e, 0x39, 0x6e, 0x24, 0x4d, 0x88, 0x9f, 0x07, 0xd4, 0xed, 0x37, 0xd8, 0x24, 0xc4, 0x54, 0x6b, + 0x61, 0xe7, 0x8f, 0xdf, 0x1f, 0x80, 0x4c, 0xd9, 0xc2, 0x8e, 0x91, 0xc2, 0x42, 0x9f, 0x01, 0x48, + 0x16, 0xbb, 0x61, 0xbf, 0x9c, 0x15, 0xf5, 0x55, 0x93, 0xfa, 0x62, 0x42, 0xb4, 0x19, 0x21, 0x5a, + 0x7b, 0x68, 0x3f, 0xc3, 0x13, 0xa3, 0x20, 0x43, 0xda, 0x7d, 0x74, 0x00, 0x79, 0x9b, 0x39, 0x3c, + 0x36, 0xb7, 0xa5, 0xd4, 0x8b, 0xfa, 0x93, 0x93, 0x69, 0x75, 0x37, 0x55, 0x95, 0xf4, 0x74, 0x7a, + 0x96, 0x1f, 0x24, 0x07, 0x59, 0x98, 0xbe, 0xdf, 0x7e, 0xf8, 0xe8, 0x23, 0x09, 0xb9, 0x6c, 0x33, + 0xa7, 0xdd, 0x47, 0x9f, 0x40, 0x36, 0x24, 0x61, 0x79, 0x59, 0xd4, 0x51, 0xd7, 0xde, 0x2a, 0xb0, + 0xd6, 0x8e, 0x08, 0x79, 0xf9, 0xfc, 0x65, 0x9b, 0x50, 0x8a, 0x45, 0x17, 0x06, 0x0f, 0x42, 0x8f, + 0x60, 0x93, 0x0e, 0x2c, 0xda, 0xc3, 0x6e, 0x37, 0x69, 0xa9, 0x87, 0x7d, 0xaf, 0xc7, 0xca, 0xf9, + 0x2d, 0xa5, 0x9e, 0x33, 0x36, 0xa4, 0x55, 0x8f, 0x8d, 0x5f, 0x0a, 0x1b, 0xfa, 0x10, 0xd0, 0x2c, + 0x8a, 0x39, 0x49, 0xc4, 0x8a, 0x88, 0x28, 0x25, 0x11, 0xcc, 0x89, 0xbd, 0x6b, 0x3f, 0x67, 0x60, + 0x23, 0x2d, 0xf0, 0x37, 0x3e, 0xeb, 0x1d, 0x60, 0x66, 0xa5, 0x78, 0x50, 0xae, 0x83, 0x87, 0x4d, + 0xc8, 0xcb, 0x4a, 0x32, 0xa2, 0x12, 0x79, 0x42, 0x77, 0xa1, 0x38, 0x22, 0xcc, 0x0f, 0xbc, 0x6e, + 0x48, 0x7e, 0xc2, 0x91, 0x10, 0x2c, 0x67, 0xa8, 0xf1, 0x5d, 0x9b, 0x5f, 0xfd, 0x03, 0x0d, 0xb9, + 0x2b, 0xd3, 0xb0, 0x7c, 0x09, 0x0d, 0xbf, 0xe5, 0xe1, 0x86, 0x6e, 0x36, 0x5b, 0x78, 0x80, 0x3d, + 0x8b, 0x5d, 0x9c, 0x23, 0x65, 0x81, 0x39, 0xca, 0x5c, 0xe3, 0x1c, 0x65, 0xff, 0xcf, 0x1c, 0x99, + 0x00, 0x23, 0x6b, 0xd0, 0xbd, 0x96, 0xb1, 0x5e, 0x1d, 0x59, 0x03, 0x5d, 0x54, 0x74, 0x17, 0x8a, 0x94, 0x59, 0x11, 0x3b, 0x4f, 0xad, 0x2a, 0xee, 0xa4, 0x06, 0xef, 0x03, 0xe0, 0xc0, 0x3d, 0x3f, - 0xb4, 0x05, 0x1c, 0xb8, 0xd2, 0xfc, 0x1e, 0x14, 0x18, 0x61, 0x56, 0xbf, 0x43, 0xad, 0x64, 0x40, - 0x97, 0xc5, 0x45, 0xdb, 0x62, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x61, 0xa3, 0xf2, 0xb2, 0xe8, 0xfb, - 0xde, 0x25, 0x7d, 0x4b, 0xe5, 0x75, 0x73, 0xd7, 0xb4, 0xc2, 0x88, 0x10, 0x66, 0x8e, 0x8c, 0x82, - 0xb4, 0x9b, 0x23, 0xb4, 0x03, 0xaa, 0x10, 0x5c, 0x62, 0x15, 0x04, 0x01, 0x6b, 0x27, 0x93, 0x2a, - 0x97, 0xbc, 0x2d, 0x2d, 0xe6, 0xc8, 0x00, 0x3a, 0xfd, 0x8f, 0x7e, 0x80, 0x15, 0x37, 0x1e, 0x06, - 0x12, 0x75, 0xa8, 0xef, 0x95, 0x41, 0x44, 0x7d, 0x7c, 0x32, 0xa9, 0x3e, 0xba, 0x0a, 0x6d, 0x6d, - 0xdf, 0x0b, 0x2c, 0x36, 0x88, 0xb0, 0x51, 0x9c, 0xe2, 0xb5, 0x7d, 0x0f, 0x99, 0xb0, 0xfc, 0xe3, - 0x20, 0x1a, 0x0b, 0x68, 0x75, 0x5e, 0xe8, 0x25, 0x0e, 0xc5, 0x51, 0xbf, 0x85, 0x12, 0x57, 0x79, - 0x10, 0xb8, 0xd3, 0x41, 0x2e, 0x17, 0x05, 0x75, 0x5b, 0x97, 0x51, 0x67, 0xee, 0x1e, 0xa5, 0xbc, - 0x8d, 0x55, 0x9b, 0x39, 0xe9, 0x8b, 0xda, 0xcb, 0x1c, 0xac, 0xbe, 0xe6, 0x84, 0x0e, 0xa0, 0x38, - 0x08, 0x6c, 0x12, 0xb8, 0x92, 0x51, 0xe5, 0xca, 0xea, 0xa8, 0xd3, 0xf8, 0x8b, 0xfa, 0x64, 0xfe, - 0x8f, 0x3e, 0x04, 0x36, 0x52, 0xfa, 0x24, 0xd1, 0x9c, 0xcd, 0xec, 0xbc, 0x6c, 0xae, 0xcf, 0x84, - 0x92, 0xb8, 0x9c, 0x5a, 0x0c, 0x6b, 0xb1, 0x60, 0xe9, 0x5c, 0xb9, 0x79, 0x73, 0xad, 0x0a, 0xe5, - 0x52, 0x69, 0x3c, 0x40, 0x22, 0xcd, 0x8c, 0x5f, 0x9e, 0x67, 0x71, 0xde, 0x3c, 0x25, 0x0e, 0x7a, - 0x94, 0x60, 0xf2, 0x44, 0x3f, 0xc1, 0xad, 0x61, 0xb2, 0xf4, 0x5f, 0xcb, 0x96, 0x9f, 0x37, 0xdb, - 0xcd, 0x29, 0x72, 0x3a, 0x65, 0xad, 0x0d, 0xb7, 0x66, 0x3b, 0x96, 0x44, 0xb3, 0x65, 0x4b, 0xd1, - 0x13, 0xc8, 0xb9, 0xb8, 0x4f, 0xcb, 0xca, 0x66, 0xb6, 0xae, 0xee, 0xdc, 0xbd, 0x7c, 0x58, 0x67, - 0x41, 0x86, 0x88, 0xa8, 0xfd, 0xaa, 0xc0, 0xda, 0x85, 0xbd, 0x87, 0xaa, 0xa0, 0x26, 0xdb, 0x9b, - 0x77, 0x24, 0x5e, 0x61, 0x46, 0xb2, 0xd0, 0x79, 0xfb, 0x06, 0x2c, 0xf1, 0x27, 0x85, 0x1b, 0x33, - 0xf3, 0xb6, 0xcb, 0x17, 0x3d, 0xef, 0xef, 0x53, 0xb8, 0xf1, 0x86, 0x59, 0x47, 0xef, 0x40, 0x46, - 0x3e, 0x23, 0x45, 0x23, 0xc3, 0x46, 0xfc, 0x55, 0x18, 0x7f, 0x08, 0xc5, 0x99, 0x0d, 0x79, 0xba, - 0xf7, 0x15, 0xdc, 0x38, 0xd7, 0x60, 0x9b, 0x59, 0x6c, 0x40, 0x91, 0x0a, 0x4b, 0xad, 0xbd, 0xc3, - 0xe6, 0xfe, 0xe1, 0x17, 0xa5, 0x05, 0x04, 0x90, 0xff, 0x7c, 0xd7, 0xdc, 0x7f, 0xb6, 0x57, 0x52, - 0xd0, 0x0a, 0x14, 0x8e, 0x0e, 0xf5, 0xa7, 0xb1, 0x29, 0xc3, 0xfd, 0xf6, 0x9e, 0xb7, 0xf6, 0x8d, - 0xbd, 0x66, 0x29, 0xab, 0x7f, 0xf3, 0xf2, 0xb4, 0xa2, 0xbc, 0x3a, 0xad, 0x28, 0x7f, 0x9f, 0x56, - 0x94, 0xdf, 0xce, 0x2a, 0x0b, 0xaf, 0xce, 0x2a, 0x0b, 0x7f, 0x9d, 0x55, 0x16, 0xbe, 0xff, 0xcf, - 0xa5, 0x3f, 0x4a, 0x7f, 0xa5, 0x8a, 0x86, 0xed, 0xbc, 0xf8, 0x18, 0x7c, 0xf0, 0x4f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x6c, 0xe0, 0x15, 0xed, 0xc8, 0x0a, 0x00, 0x00, + 0xb4, 0x05, 0x1c, 0xb8, 0xd2, 0xfc, 0x1e, 0x14, 0x18, 0x61, 0xd6, 0xa0, 0x4b, 0xad, 0x64, 0x40, + 0x57, 0xc5, 0x45, 0xc7, 0x62, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x65, 0xe3, 0xf2, 0xaa, 0xe8, 0xfb, + 0xfe, 0x25, 0x7d, 0x4b, 0xe5, 0x75, 0xb3, 0x69, 0x5a, 0x61, 0x44, 0x08, 0x33, 0xc7, 0x46, 0x41, + 0xda, 0xcd, 0x31, 0xda, 0x05, 0x55, 0x08, 0x2e, 0xb1, 0x0a, 0x82, 0x80, 0xf5, 0x93, 0x69, 0x95, + 0x4b, 0xde, 0x91, 0x16, 0x73, 0x6c, 0x00, 0x9d, 0xfd, 0x47, 0xdf, 0xc3, 0x0d, 0x37, 0x1e, 0x06, + 0x12, 0x75, 0xa9, 0xef, 0x95, 0x41, 0x44, 0x7d, 0x7c, 0x32, 0xad, 0x3e, 0xbe, 0x0a, 0x6d, 0x1d, + 0xdf, 0x0b, 0x2c, 0x36, 0x8c, 0xb0, 0x51, 0x9c, 0xe1, 0x75, 0x7c, 0x0f, 0x99, 0xb0, 0xfa, 0xc3, + 0x30, 0x9a, 0x08, 0x68, 0x75, 0x51, 0xe8, 0x15, 0x0e, 0xc5, 0x51, 0xbf, 0x86, 0x12, 0x57, 0x79, + 0x18, 0xb8, 0xb3, 0x41, 0x2e, 0x17, 0x05, 0x75, 0xdb, 0x97, 0x51, 0x67, 0x36, 0x8f, 0x52, 0xde, + 0xc6, 0x9a, 0xcd, 0x9c, 0xf4, 0x45, 0xed, 0x55, 0x0e, 0xd6, 0xde, 0x70, 0x42, 0x07, 0x50, 0x1c, + 0x06, 0x36, 0x09, 0x5c, 0xc9, 0xa8, 0x72, 0x65, 0x75, 0xd4, 0x59, 0xfc, 0x45, 0x7d, 0x32, 0xff, + 0x45, 0x1f, 0x02, 0x9b, 0x29, 0x7d, 0x92, 0x68, 0xce, 0x66, 0x76, 0x51, 0x36, 0x37, 0xe6, 0x42, + 0x49, 0x5c, 0x4e, 0x2d, 0x86, 0xf5, 0x58, 0xb0, 0x74, 0xae, 0xdc, 0xa2, 0xb9, 0xd6, 0x84, 0x72, + 0xa9, 0x34, 0x1e, 0x20, 0x91, 0x66, 0xce, 0x2f, 0xcf, 0xb3, 0xbc, 0x68, 0x9e, 0x12, 0x07, 0x3d, + 0x4a, 0x30, 0x79, 0xa2, 0x1f, 0xe1, 0xf6, 0x28, 0x59, 0xfa, 0x6f, 0x64, 0xcb, 0x2f, 0x9a, 0xed, + 0xd6, 0x0c, 0x39, 0x9d, 0xb2, 0xd6, 0x81, 0xdb, 0xf3, 0x1d, 0x4b, 0xa2, 0xf9, 0xb2, 0xa5, 0xe8, + 0x29, 0xe4, 0x5c, 0x3c, 0xa0, 0x65, 0x65, 0x2b, 0x5b, 0x57, 0x77, 0xef, 0x5d, 0x3e, 0xac, 0xf3, + 0x20, 0x43, 0x44, 0xd4, 0x7e, 0x51, 0x60, 0xfd, 0xc2, 0xde, 0x43, 0x55, 0x50, 0x93, 0xed, 0xcd, + 0x3b, 0x12, 0xaf, 0x30, 0x23, 0x59, 0xe8, 0xbc, 0x7d, 0x03, 0x56, 0xf8, 0x93, 0xc2, 0x8d, 0x99, + 0x45, 0xdb, 0xe5, 0x8b, 0x9e, 0xf7, 0xf7, 0x29, 0xdc, 0x7c, 0xcb, 0xac, 0xa3, 0x77, 0x20, 0x23, + 0x9f, 0x91, 0xa2, 0x91, 0x61, 0x63, 0xfe, 0x2a, 0x8c, 0x3f, 0x84, 0xe2, 0xcc, 0x86, 0x3c, 0xdd, + 0x7f, 0x06, 0x37, 0xcf, 0x35, 0xd8, 0x61, 0x16, 0x1b, 0x52, 0xa4, 0xc2, 0x4a, 0x7b, 0xef, 0xb0, + 0xb5, 0x7f, 0xf8, 0x45, 0x69, 0x09, 0x01, 0xe4, 0x3f, 0x6f, 0x9a, 0xfb, 0x2f, 0xf6, 0x4a, 0x0a, + 0xba, 0x01, 0x85, 0xa3, 0x43, 0xfd, 0x79, 0x6c, 0xca, 0xa0, 0x22, 0xac, 0xc6, 0xc7, 0xbd, 0x56, + 0x29, 0xab, 0x7f, 0xf5, 0xea, 0xb4, 0xa2, 0xbc, 0x3e, 0xad, 0x28, 0x7f, 0x9d, 0x56, 0x94, 0x5f, + 0xcf, 0x2a, 0x4b, 0xaf, 0xcf, 0x2a, 0x4b, 0x7f, 0x9e, 0x55, 0x96, 0xbe, 0xfb, 0xd7, 0xad, 0x3f, + 0x4e, 0x7f, 0xa6, 0x8a, 0x8e, 0xed, 0xbc, 0xf8, 0x1a, 0x7c, 0xf8, 0x77, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xb2, 0x88, 0xf9, 0x2e, 0xc9, 0x0a, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index f9f6821c6..136a0c165 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -582,7 +582,7 @@ type MsgAddJuryUnbondingSigs struct { // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // unbonding_tx_sig is the signature of the jury on the unbodning tx submitted to babylon + // unbonding_tx_sig is the signature of the jury on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` // slashing_unbonding_tx_sig is the signature of the jury on slashing tx corresponding to unbodning tx submitted to babylon @@ -885,9 +885,9 @@ type MsgClient interface { CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury for slashing tx of staking tx + // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) - // AddJuryUnbondingSigs handles a signature from jury for: + // AddJuryUnbondingSigs handles two signatures from jury for: // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker AddJuryUnbondingSigs(ctx context.Context, in *MsgAddJuryUnbondingSigs, opts ...grpc.CallOption) (*MsgAddJuryUnbondingSigsResponse, error) @@ -976,9 +976,9 @@ type MsgServer interface { CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury for slashing tx of staking tx + // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) - // AddJuryUnbondingSigs handles a signature from jury for: + // AddJuryUnbondingSigs handles two signatures from jury for: // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker AddJuryUnbondingSigs(context.Context, *MsgAddJuryUnbondingSigs) (*MsgAddJuryUnbondingSigsResponse, error) diff --git a/x/btcstaking/types/utils.go b/x/btcstaking/types/utils.go deleted file mode 100644 index c1ae6a521..000000000 --- a/x/btcstaking/types/utils.go +++ /dev/null @@ -1,18 +0,0 @@ -package types - -import "fmt" - -// NewBTCDelegationStatus takes a human-readable delegation status format and returns our custom enum. -// Options: Active | Pending | Expired -func NewBTCDelegationStatus(status string) (BTCDelegationStatus, error) { - if status == "Active" { - return BTCDelegationStatus_ACTIVE, nil - } - if status == "Pending" { - return BTCDelegationStatus_PENDING, nil - } - if status == "Expired" { - return BTCDelegationStatus_EXPIRED, nil - } - return BTCDelegationStatus_ACTIVE, fmt.Errorf("invalid delegation status %s", status) -} From 66908792bb08fe5d689a1ab4859859465c56e0a5 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Thu, 7 Sep 2023 07:40:38 +0200 Subject: [PATCH 075/202] Add endpoint returning delegations requiring jury signatures (#74) * Add endpoint returning delegations requireing jury signatures * Add cli command --- proto/babylon/btcstaking/v1/query.proto | 16 + test/e2e/btc_staking_e2e_test.go | 3 + .../configurer/chain/queries_btcstaking.go | 11 + x/btcstaking/client/cli/query.go | 22 + x/btcstaking/keeper/grpc_query.go | 99 +++- x/btcstaking/keeper/grpc_query_test.go | 67 +++ x/btcstaking/types/btcstaking.pb.go | 2 +- x/btcstaking/types/query.pb.go | 522 +++++++++++++++--- x/btcstaking/types/query.pb.gw.go | 65 +++ 9 files changed, 690 insertions(+), 117 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 1d82197bd..eef9cbae0 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -31,6 +31,11 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/pending_btc_delegations"; } + // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature + rpc UnbondingBTCDelegations(QueryUnbondingBTCDelegationsRequest) returns (QueryUnbondingBTCDelegationsResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/unbonding_btc_delegations"; + } + // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. rpc ActiveBTCValidatorsAtHeight(QueryActiveBTCValidatorsAtHeightRequest) returns (QueryActiveBTCValidatorsAtHeightResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{height}"; @@ -113,6 +118,17 @@ message QueryPendingBTCDelegationsResponse { repeated BTCDelegation btc_delegations = 1; } +// QueryUnbondingBTCDelegationsRequest is the request type for the +// QueryUnbondingBTCDelegations RPC method. +message QueryUnbondingBTCDelegationsRequest {} + +// QueryUnbondingBTCDelegationsResponse is the response type for the +// QueryUnbondingBTCDelegations RPC method. +message QueryUnbondingBTCDelegationsResponse { + // btc_delegations contains all the queried BTC delegations. + repeated BTCDelegation btc_delegations = 1; +} + // QueryBTCValidatorPowerAtHeightRequest is the request type for the // Query/BTCValidatorPowerAtHeight RPC method. message QueryBTCValidatorPowerAtHeightRequest { diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 5a6afc028..feb2c08bb 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -387,6 +387,9 @@ func (s *BTCStakingTestSuite) Test5SubmitUnbondingSignatures() { s.NotNil(delegationWithValSig.BtcUndelegation) s.NotNil(delegationWithValSig.BtcUndelegation.ValidatorUnbondingSig) + unbodnindDelegations := nonValidatorNode.QueryUnbondingDelegations() + s.Len(unbodnindDelegations, 1) + btcTip, err := nonValidatorNode.QueryTip() s.NoError(err) s.Equal( diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 2a63d2b42..13b934dc8 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -57,6 +57,17 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BT return resp.BtcDelegatorDelegations } +func (n *NodeConfig) QueryUnbondingDelegations() []*bstypes.BTCDelegation { + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/unbonding_btc_delegations", url.Values{}) + require.NoError(n.t, err) + + var resp bstypes.QueryUnbondingBTCDelegationsResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp.BtcDelegations +} + func (n *NodeConfig) QueryBtcDelegation(stakingTxHash string) *bstypes.QueryBTCDelegationResponse { path := fmt.Sprintf("/babylon/btcstaking/v1/btc_delegations/%s", stakingTxHash) bz, err := n.QueryGRPCGateway(path, url.Values{}) diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index eb46247e9..dff329ed0 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -87,6 +87,28 @@ func CmdPendingBTCDelegations() *cobra.Command { return cmd } +func CmdUnbondingBTCDelegations() *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-btc-delegations", + Short: "retrieve all unbonding BTC delegations which require jury signature", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.UnbondingBTCDelegations(cmd.Context(), &types.QueryUnbondingBTCDelegationsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + func CmdBTCValidatorPowerAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "btc-validator-power-at-height [val_btc_pk_hex] [height]", diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 238742448..be18f4ec5 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -17,6 +17,46 @@ import ( var _ types.QueryServer = Keeper{} +func (k Keeper) getDelegationsMatchingCriteria( + sdkCtx sdk.Context, + match func(*types.BTCDelegation) bool, +) []*types.BTCDelegation { + btcDels := []*types.BTCDelegation{} + + // iterate over each BTC validator + valStore := k.btcValidatorStore(sdkCtx) + valIter := valStore.Iterator(nil, nil) + defer valIter.Close() + + for ; valIter.Valid(); valIter.Next() { + valBTCPKBytes := valIter.Key() + valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + if err != nil { + // this can only be programming error + panic("failed to unmarshal validator BTC PK in KVstore") + } + delStore := k.btcDelegationStore(sdkCtx, valBTCPK) + delIter := delStore.Iterator(nil, nil) + + // iterate over each BTC delegation under this BTC validator + for ; delIter.Valid(); delIter.Next() { + var curBTCDels types.BTCDelegatorDelegations + btcDelsBytes := delIter.Value() + k.cdc.MustUnmarshal(btcDelsBytes, &curBTCDels) + for i, btcDel := range curBTCDels.Dels { + del := btcDel + if match(del) { + btcDels = append(btcDels, curBTCDels.Dels[i]) + } + } + } + + delIter.Close() + } + + return btcDels +} + // BTCValidators returns a paginated list of all Babylon maintained validators func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidatorsRequest) (*types.QueryBTCValidatorsResponse, error) { if req == nil { @@ -75,7 +115,6 @@ func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendi } sdkCtx := sdk.UnwrapSDKContext(ctx) - btcDels := []*types.BTCDelegation{} // get current BTC height btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) @@ -85,38 +124,42 @@ func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendi // get value of w wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout - // iterate over each BTC validator - valStore := k.btcValidatorStore(sdkCtx) - valIter := valStore.Iterator(nil, nil) - defer valIter.Close() + btcDels := k.getDelegationsMatchingCriteria( + sdkCtx, + func(del *types.BTCDelegation) bool { + return del.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING + }, + ) - for ; valIter.Valid(); valIter.Next() { - valBTCPKBytes := valIter.Key() - valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) - if err != nil { - // this can only be programming error - panic("failed to unmarshal validator BTC PK in KVstore") - } - delStore := k.btcDelegationStore(sdkCtx, valBTCPK) - delIter := delStore.Iterator(nil, nil) + return &types.QueryPendingBTCDelegationsResponse{BtcDelegations: btcDels}, nil +} - // iterate over each BTC delegation under this BTC validator - for ; delIter.Valid(); delIter.Next() { - var curBTCDels types.BTCDelegatorDelegations - btcDelsBytes := delIter.Value() - k.cdc.MustUnmarshal(btcDelsBytes, &curBTCDels) - for i, btcDel := range curBTCDels.Dels { - if btcDel.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING { - // avoid using btcDel which changes over the iterations - btcDels = append(btcDels, curBTCDels.Dels[i]) - } +// UnbondingBTCDelegations returns all unbonding BTC delegations which require jury signature +// TODO: find a good way to support pagination of this query +func (k Keeper) UnbondingBTCDelegations(ctx context.Context, req *types.QueryUnbondingBTCDelegationsRequest) (*types.QueryUnbondingBTCDelegationsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + btcDels := k.getDelegationsMatchingCriteria( + sdkCtx, + func(del *types.BTCDelegation) bool { + // grab all delegations which are unbonding and already have validator signature + if del.BtcUndelegation == nil { + return false } - } - delIter.Close() - } + if del.BtcUndelegation.ValidatorUnbondingSig == nil { + return false + } - return &types.QueryPendingBTCDelegationsResponse{BtcDelegations: btcDels}, nil + return true + }, + ) + + return &types.QueryUnbondingBTCDelegationsResponse{BtcDelegations: btcDels}, nil } // BTCValidatorPowerAtHeight returns the voting power of the specified validator diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 7daa78c05..e442cb251 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -233,6 +233,73 @@ func FuzzBTCDelegations(f *testing.F) { }) } +func FuzzUnbondingBTCDelegations(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Setup keeper and context + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + + // Generate a random number of BTC validators + numBTCVals := datagen.RandomInt(r, 5) + 1 + btcVals := []*types.BTCValidator{} + for i := uint64(0); i < numBTCVals; i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + btcVals = append(btcVals, btcVal) + } + + // Generate a random number of BTC delegations under each validator + startHeight := datagen.RandomInt(r, 100) + 1 + endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 + numBTCDels := datagen.RandomInt(r, 10) + 1 + unbondingBtcDelsMap := make(map[string]*types.BTCDelegation) + for _, btcVal := range btcVals { + for j := uint64(0); j < numBTCDels; j++ { + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) + require.NoError(t, err) + + if datagen.RandomInt(r, 2) == 1 { + // add unbonding object in random BTC delegations to make them ready to receive jury sig + btcDel.BtcUndelegation = &types.BTCUndelegation{ + // doesn't matter what we put here + ValidatorUnbondingSig: btcDel.JurySig, + } + + unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel + } + + err = keeper.SetBTCDelegation(ctx, btcDel) + require.NoError(t, err) + } + } + + // assert all pending BTC delegations + resp, err := keeper.UnbondingBTCDelegations(ctx, &types.QueryUnbondingBTCDelegationsRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, len(unbondingBtcDelsMap), len(resp.BtcDelegations)) + for _, btcDel := range resp.BtcDelegations { + _, ok := unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] + require.True(t, ok) + } + }) +} + func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 15b311590..0b09181ae 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -30,7 +30,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // BTCDelegationStatus is the status of a delegation. There are two possible state // transition paths: -// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path whent timelock of staking +// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path when timelock of staking // transaction expires. // 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through // MsgBTCUndelegate message. diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 603025961..003577d1e 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -394,6 +394,91 @@ func (m *QueryPendingBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegatio return nil } +// QueryUnbondingBTCDelegationsRequest is the request type for the +// QueryUnbondingBTCDelegations RPC method. +type QueryUnbondingBTCDelegationsRequest struct { +} + +func (m *QueryUnbondingBTCDelegationsRequest) Reset() { *m = QueryUnbondingBTCDelegationsRequest{} } +func (m *QueryUnbondingBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUnbondingBTCDelegationsRequest) ProtoMessage() {} +func (*QueryUnbondingBTCDelegationsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{8} +} +func (m *QueryUnbondingBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnbondingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnbondingBTCDelegationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.Merge(m, src) +} +func (m *QueryUnbondingBTCDelegationsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUnbondingBTCDelegationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnbondingBTCDelegationsRequest proto.InternalMessageInfo + +// QueryUnbondingBTCDelegationsResponse is the response type for the +// QueryUnbondingBTCDelegations RPC method. +type QueryUnbondingBTCDelegationsResponse struct { + // btc_delegations contains all the queried BTC delegations. + BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` +} + +func (m *QueryUnbondingBTCDelegationsResponse) Reset() { *m = QueryUnbondingBTCDelegationsResponse{} } +func (m *QueryUnbondingBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUnbondingBTCDelegationsResponse) ProtoMessage() {} +func (*QueryUnbondingBTCDelegationsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{9} +} +func (m *QueryUnbondingBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnbondingBTCDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnbondingBTCDelegationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.Merge(m, src) +} +func (m *QueryUnbondingBTCDelegationsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUnbondingBTCDelegationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnbondingBTCDelegationsResponse proto.InternalMessageInfo + +func (m *QueryUnbondingBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegation { + if m != nil { + return m.BtcDelegations + } + return nil +} + // QueryBTCValidatorPowerAtHeightRequest is the request type for the // Query/BTCValidatorPowerAtHeight RPC method. type QueryBTCValidatorPowerAtHeightRequest struct { @@ -409,7 +494,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{8} + return fileDescriptor_74d49d26f7429697, []int{10} } func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -465,7 +550,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{9} + return fileDescriptor_74d49d26f7429697, []int{11} } func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -514,7 +599,7 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorCurrentPowerRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorCurrentPowerRequest) ProtoMessage() {} func (*QueryBTCValidatorCurrentPowerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{10} + return fileDescriptor_74d49d26f7429697, []int{12} } func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -563,7 +648,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorCurrentPowerResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorCurrentPowerResponse) ProtoMessage() {} func (*QueryBTCValidatorCurrentPowerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{11} + return fileDescriptor_74d49d26f7429697, []int{13} } func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -621,7 +706,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{12} + return fileDescriptor_74d49d26f7429697, []int{14} } func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -679,7 +764,7 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{13} + return fileDescriptor_74d49d26f7429697, []int{15} } func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -730,7 +815,7 @@ func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeigh func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightRequest) ProtoMessage() {} func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{14} + return fileDescriptor_74d49d26f7429697, []int{16} } func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -768,7 +853,7 @@ func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeig func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightResponse) ProtoMessage() {} func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{15} + return fileDescriptor_74d49d26f7429697, []int{17} } func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -819,7 +904,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCVal func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{16} + return fileDescriptor_74d49d26f7429697, []int{18} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -875,7 +960,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{17} + return fileDescriptor_74d49d26f7429697, []int{19} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -929,7 +1014,7 @@ func (m *QueryBTCDelegationRequest) Reset() { *m = QueryBTCDelegationReq func (m *QueryBTCDelegationRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCDelegationRequest) ProtoMessage() {} func (*QueryBTCDelegationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{18} + return fileDescriptor_74d49d26f7429697, []int{20} } func (m *QueryBTCDelegationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -994,7 +1079,7 @@ func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationRe func (m *QueryBTCDelegationResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCDelegationResponse) ProtoMessage() {} func (*QueryBTCDelegationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{19} + return fileDescriptor_74d49d26f7429697, []int{21} } func (m *QueryBTCDelegationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1067,6 +1152,8 @@ func init() { proto.RegisterType((*QueryBTCValidatorResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorResponse") proto.RegisterType((*QueryPendingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsRequest") proto.RegisterType((*QueryPendingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsResponse") + proto.RegisterType((*QueryUnbondingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryUnbondingBTCDelegationsRequest") + proto.RegisterType((*QueryUnbondingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryUnbondingBTCDelegationsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") proto.RegisterType((*QueryBTCValidatorCurrentPowerRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerRequest") @@ -1084,82 +1171,85 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1194 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x6f, 0xdc, 0x44, - 0x14, 0xcf, 0xa4, 0xc9, 0xd2, 0xbc, 0x7c, 0xa1, 0x69, 0x4b, 0x37, 0x9b, 0x66, 0xd3, 0x38, 0xcd, - 0x47, 0x8b, 0x6a, 0xe7, 0x03, 0x45, 0x40, 0x42, 0x4b, 0x37, 0xa1, 0x0d, 0x09, 0x91, 0x16, 0x37, - 0x02, 0x89, 0xcb, 0x6a, 0xbc, 0x3b, 0xf2, 0x9a, 0x6c, 0xec, 0xed, 0x7a, 0xb2, 0x24, 0xaa, 0x72, - 0xe1, 0x80, 0xc4, 0x0d, 0xc1, 0x9f, 0xc0, 0x05, 0x89, 0x13, 0x57, 0xb8, 0x22, 0xd1, 0x63, 0x25, - 0x24, 0x84, 0xa8, 0x14, 0xa1, 0x04, 0xc1, 0xbf, 0x81, 0x3c, 0x1e, 0xaf, 0xed, 0xd8, 0xde, 0x75, - 0x96, 0x72, 0xcb, 0x7a, 0xde, 0xc7, 0xef, 0xf7, 0x7b, 0x6f, 0xde, 0x3c, 0x05, 0xa6, 0x34, 0xa2, - 0x1d, 0xd5, 0x2c, 0x53, 0xd1, 0x58, 0xd9, 0x66, 0x64, 0xcf, 0x30, 0x75, 0xa5, 0xb9, 0xa8, 0x3c, - 0x39, 0xa0, 0x8d, 0x23, 0xb9, 0xde, 0xb0, 0x98, 0x85, 0xaf, 0x09, 0x13, 0xd9, 0x37, 0x91, 0x9b, - 0x8b, 0xb9, 0xab, 0xba, 0xa5, 0x5b, 0xdc, 0x42, 0x71, 0xfe, 0x72, 0x8d, 0x73, 0x37, 0x74, 0xcb, - 0xd2, 0x6b, 0x54, 0x21, 0x75, 0x43, 0x21, 0xa6, 0x69, 0x31, 0xc2, 0x0c, 0xcb, 0xb4, 0xc5, 0xe9, - 0x9d, 0xb2, 0x65, 0xef, 0x5b, 0xb6, 0xa2, 0x11, 0x9b, 0xba, 0x39, 0x94, 0xe6, 0xa2, 0x46, 0x19, - 0x59, 0x54, 0xea, 0x44, 0x37, 0x4c, 0x6e, 0x2c, 0x6c, 0xa5, 0x78, 0x64, 0x75, 0xd2, 0x20, 0xfb, - 0x5e, 0xbc, 0xd9, 0x78, 0x9b, 0x00, 0x50, 0x6e, 0x27, 0x5d, 0x05, 0xfc, 0xa1, 0x93, 0xad, 0xc8, - 0x9d, 0x55, 0xfa, 0xe4, 0x80, 0xda, 0x4c, 0x52, 0xe1, 0x4a, 0xe8, 0xab, 0x5d, 0xb7, 0x4c, 0x9b, - 0xe2, 0x55, 0xc8, 0xb8, 0x49, 0xb2, 0xe8, 0x26, 0x9a, 0x1f, 0x5c, 0x9a, 0x90, 0x63, 0x05, 0x90, - 0x5d, 0xb7, 0x42, 0xdf, 0xb3, 0x93, 0xc9, 0x1e, 0x55, 0xb8, 0x48, 0x65, 0x18, 0xe3, 0x31, 0x0b, - 0xbb, 0xeb, 0x1f, 0x91, 0x9a, 0x51, 0x21, 0xcc, 0x6a, 0x78, 0x09, 0xf1, 0x43, 0x00, 0x9f, 0xa6, - 0x88, 0x3e, 0x2b, 0xbb, 0x9a, 0xc8, 0x8e, 0x26, 0xb2, 0xab, 0xbb, 0xd0, 0x44, 0x2e, 0x12, 0x9d, - 0x0a, 0x5f, 0x35, 0xe0, 0x29, 0xfd, 0x80, 0x20, 0x17, 0x97, 0x45, 0x10, 0xd8, 0x82, 0x11, 0x8d, - 0x95, 0x4b, 0xcd, 0xd6, 0x49, 0x16, 0xdd, 0xbc, 0x34, 0x3f, 0xb8, 0x34, 0x9d, 0x40, 0x24, 0x18, - 0x45, 0x1d, 0xd6, 0x58, 0xd9, 0x8f, 0x89, 0x1f, 0x85, 0x20, 0xf7, 0x72, 0xc8, 0x73, 0x1d, 0x21, - 0xbb, 0x40, 0x42, 0x98, 0xef, 0x43, 0x36, 0x02, 0xd9, 0xd3, 0x65, 0x1a, 0x46, 0x9a, 0xa4, 0x56, - 0x72, 0x40, 0xd7, 0xf7, 0x4a, 0x55, 0x7a, 0xc8, 0xb5, 0x19, 0x50, 0x07, 0x9b, 0xa4, 0x56, 0x60, - 0xe5, 0xe2, 0xde, 0x26, 0x3d, 0x94, 0x68, 0x8c, 0xb2, 0x2d, 0xca, 0x9b, 0x30, 0x1c, 0xa2, 0x2c, - 0xc4, 0x4d, 0xc5, 0x78, 0x28, 0xc8, 0x58, 0x9a, 0x86, 0x29, 0xb7, 0x29, 0xa8, 0x59, 0x31, 0x4c, - 0xbd, 0xb0, 0xbb, 0xbe, 0x41, 0x6b, 0x54, 0x77, 0xdb, 0xd8, 0xeb, 0x1c, 0x1b, 0xa4, 0x76, 0x46, - 0x02, 0xd4, 0x0e, 0x8c, 0x3a, 0xa0, 0x2a, 0xfe, 0x91, 0x28, 0xc4, 0xad, 0x64, 0x58, 0x7e, 0x1c, - 0xd5, 0x29, 0x62, 0x20, 0xac, 0x54, 0x81, 0x99, 0x88, 0x00, 0x45, 0xeb, 0x33, 0xda, 0x78, 0xc0, - 0x36, 0xa9, 0xa1, 0x57, 0xd9, 0x45, 0xe4, 0xc4, 0xaf, 0x41, 0xa6, 0xca, 0xbd, 0x78, 0x51, 0xfb, - 0x54, 0xf1, 0x4b, 0xda, 0x86, 0xd9, 0x4e, 0x59, 0x04, 0xbd, 0x29, 0x18, 0x6a, 0x5a, 0xcc, 0x30, - 0xf5, 0x52, 0xdd, 0x39, 0xe7, 0x49, 0xfa, 0xd4, 0x41, 0xf7, 0x1b, 0x77, 0x91, 0xb6, 0xe1, 0x56, - 0x24, 0xd8, 0xfa, 0x41, 0xa3, 0x41, 0x4d, 0xc6, 0x0d, 0x2e, 0xd4, 0x00, 0x5a, 0x0c, 0xff, 0x70, - 0x30, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x22, 0x80, 0x7b, 0xa3, 0x80, 0xbf, 0x44, 0x30, 0xc7, - 0x93, 0x3c, 0x28, 0x33, 0xa3, 0x49, 0x43, 0xf7, 0xeb, 0xbc, 0xcc, 0x49, 0x69, 0x1e, 0xc6, 0x5c, - 0x99, 0x6e, 0x6e, 0xf9, 0x2f, 0x08, 0xe6, 0x3b, 0x63, 0x11, 0x9c, 0xd5, 0x84, 0x3b, 0xff, 0x7a, - 0x8a, 0x1b, 0xf0, 0xb1, 0xc1, 0xaa, 0x3b, 0x94, 0x91, 0xff, 0xed, 0xee, 0x4f, 0xc0, 0xb8, 0x4f, - 0x84, 0x30, 0x5a, 0x09, 0x09, 0x29, 0xad, 0xc0, 0x8d, 0xf8, 0xe3, 0xf6, 0xf5, 0x94, 0xbe, 0x46, - 0x30, 0x1d, 0xe9, 0x88, 0xe8, 0x6d, 0x4d, 0x77, 0x1f, 0x5e, 0x56, 0xd5, 0x5e, 0xa0, 0x98, 0x9e, - 0x8f, 0x9b, 0x0e, 0x9f, 0xc2, 0x58, 0x60, 0x3a, 0x58, 0x8d, 0x98, 0x39, 0x21, 0x77, 0x9c, 0x13, - 0xe1, 0xd0, 0xd7, 0xfd, 0x89, 0x11, 0x3a, 0x78, 0x79, 0x95, 0xdc, 0xf2, 0x87, 0x70, 0x60, 0x52, - 0x09, 0x9d, 0xef, 0xc2, 0x15, 0x01, 0xb2, 0xc4, 0x0e, 0x4b, 0x55, 0x62, 0x57, 0x03, 0x62, 0xbf, - 0x2a, 0x8e, 0x76, 0x0f, 0x37, 0x89, 0x5d, 0x75, 0xee, 0xf3, 0xdf, 0xbd, 0xfe, 0x2b, 0x16, 0x0c, - 0xd6, 0x9a, 0x9e, 0x19, 0xb7, 0x62, 0x3c, 0xc0, 0x50, 0x61, 0xe5, 0x8f, 0x93, 0xc9, 0x25, 0xdd, - 0x60, 0xd5, 0x03, 0x4d, 0x2e, 0x5b, 0xfb, 0x8a, 0x90, 0xa6, 0x5c, 0x25, 0x86, 0xe9, 0xfd, 0x50, - 0xd8, 0x51, 0x9d, 0xda, 0x72, 0xe1, 0xfd, 0xe2, 0xf2, 0x1b, 0x0b, 0xc5, 0x03, 0x6d, 0x9b, 0x1e, - 0xa9, 0xfd, 0x9a, 0x53, 0x62, 0xbc, 0x0b, 0xe0, 0x37, 0x01, 0x97, 0xa0, 0xfb, 0x90, 0x97, 0xbd, - 0xc6, 0x71, 0x46, 0x8a, 0xcd, 0x48, 0x83, 0x95, 0x44, 0x83, 0x5e, 0x72, 0x47, 0x0a, 0xff, 0xe6, - 0x76, 0x31, 0x9e, 0x00, 0xa0, 0x66, 0xc5, 0x33, 0xe8, 0xe3, 0x06, 0x03, 0xd4, 0x14, 0x4d, 0x8e, - 0xc7, 0x61, 0x80, 0x59, 0x8c, 0xd4, 0x4a, 0x36, 0x61, 0xd9, 0x7e, 0x7e, 0x7a, 0x99, 0x7f, 0x78, - 0x4c, 0xb8, 0xaf, 0xaf, 0x68, 0x36, 0xc3, 0x85, 0x1c, 0x68, 0x09, 0x89, 0x67, 0x60, 0xc4, 0x3b, - 0xb6, 0xcb, 0x0d, 0xa3, 0xce, 0xb2, 0xaf, 0x70, 0x93, 0x61, 0xf1, 0xf5, 0x31, 0xff, 0xb8, 0xf4, - 0xdd, 0x28, 0xf4, 0x73, 0xa1, 0xf1, 0x17, 0x08, 0x32, 0xee, 0xda, 0x82, 0x6f, 0x27, 0xf4, 0x56, - 0x74, 0x4f, 0xca, 0xdd, 0x49, 0x63, 0xea, 0x56, 0x4d, 0x9a, 0xf9, 0xfc, 0xd7, 0xbf, 0xbe, 0xe9, - 0x9d, 0xc4, 0x13, 0x4a, 0xbb, 0xf5, 0x0d, 0x7f, 0x8b, 0x60, 0x38, 0x34, 0xd0, 0xf0, 0x42, 0xbb, - 0x24, 0x71, 0xdb, 0x54, 0x6e, 0xf1, 0x02, 0x1e, 0x02, 0xdd, 0x5d, 0x8e, 0x6e, 0x0e, 0xcf, 0x28, - 0x89, 0x8b, 0x63, 0x60, 0x84, 0xe2, 0x9f, 0x10, 0x0c, 0x05, 0x03, 0x61, 0x25, 0x6d, 0x4a, 0x0f, - 0xe3, 0x42, 0x7a, 0x07, 0x01, 0x71, 0x93, 0x43, 0x2c, 0xe0, 0x77, 0x53, 0x41, 0x54, 0x9e, 0x86, - 0x27, 0xdb, 0xb1, 0xd2, 0x3a, 0xc3, 0x3f, 0x23, 0xb8, 0x16, 0xbb, 0xa0, 0xe0, 0x37, 0xdb, 0x16, - 0xb4, 0xcd, 0xe2, 0x93, 0x7b, 0xab, 0x0b, 0x4f, 0x41, 0x6c, 0x85, 0x13, 0x5b, 0xc0, 0x72, 0x52, - 0x67, 0xb8, 0xde, 0xa5, 0x73, 0x2b, 0x13, 0xfe, 0x0d, 0xc1, 0x78, 0x9b, 0x17, 0x10, 0xdf, 0x6b, - 0x07, 0xa9, 0xf3, 0x33, 0x9e, 0xbb, 0xdf, 0xb5, 0x7f, 0x4a, 0x62, 0xe7, 0x2b, 0xe6, 0x4e, 0x82, - 0x63, 0xfc, 0x0f, 0x82, 0xb1, 0xc4, 0x2d, 0x0b, 0xaf, 0xa5, 0xed, 0x9c, 0xb8, 0x15, 0x30, 0xf7, - 0x4e, 0x97, 0xde, 0x82, 0xd2, 0x0e, 0xa7, 0xf4, 0x08, 0xbf, 0xd7, 0x65, 0x13, 0xf2, 0xfd, 0xca, - 0x67, 0xfa, 0x02, 0x41, 0x36, 0x69, 0x6b, 0xc3, 0xab, 0x69, 0xa1, 0xc6, 0x2c, 0x8e, 0xb9, 0xb5, - 0xee, 0x9c, 0x05, 0xcd, 0x0d, 0x4e, 0xf3, 0x1e, 0x5e, 0xfb, 0x2f, 0x34, 0xf1, 0xf7, 0x08, 0x46, - 0xcf, 0xad, 0x2e, 0x78, 0xa9, 0x63, 0x53, 0x45, 0xd6, 0xa0, 0xdc, 0xf2, 0x85, 0x7c, 0x04, 0x05, - 0x85, 0x53, 0xb8, 0x8d, 0xe7, 0x12, 0x28, 0x10, 0xcf, 0x4f, 0x3c, 0x40, 0xf8, 0x04, 0xc1, 0xf5, - 0x84, 0xd5, 0x04, 0xbf, 0x9d, 0x56, 0xcd, 0x98, 0xc9, 0xb0, 0xda, 0x95, 0xaf, 0x60, 0xb1, 0xc5, - 0x59, 0x6c, 0xe0, 0x42, 0x97, 0x85, 0x08, 0xce, 0x8b, 0x1f, 0xdd, 0xa7, 0xc5, 0x4f, 0xd3, 0xf1, - 0x69, 0x89, 0x6c, 0x32, 0x1d, 0x9f, 0x96, 0xe8, 0xba, 0x92, 0xaa, 0x97, 0x02, 0x30, 0x95, 0xa7, - 0x31, 0xab, 0xd2, 0x71, 0xe1, 0x83, 0x67, 0xa7, 0x79, 0xf4, 0xfc, 0x34, 0x8f, 0xfe, 0x3c, 0xcd, - 0xa3, 0xaf, 0xce, 0xf2, 0x3d, 0xcf, 0xcf, 0xf2, 0x3d, 0xbf, 0x9f, 0xe5, 0x7b, 0x3e, 0xe9, 0xb8, - 0xa7, 0x1c, 0x06, 0x13, 0xf2, 0xa5, 0x45, 0xcb, 0xf0, 0xff, 0x7e, 0x2c, 0xff, 0x1b, 0x00, 0x00, - 0xff, 0xff, 0xc3, 0x09, 0x06, 0xb0, 0xe5, 0x11, 0x00, 0x00, + // 1247 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0xc5, + 0x17, 0xcf, 0xa4, 0x89, 0xbf, 0xcd, 0xcb, 0x8f, 0x56, 0xd3, 0xf6, 0x1b, 0x67, 0xd3, 0x38, 0xcd, + 0xa6, 0xf9, 0xd1, 0xa2, 0xee, 0x26, 0x0e, 0x8a, 0x0a, 0x09, 0x2d, 0x75, 0x42, 0x1b, 0x12, 0x22, + 0x19, 0x37, 0x80, 0xc4, 0xc5, 0x9a, 0xb5, 0x47, 0xeb, 0x25, 0xce, 0xae, 0xeb, 0x1d, 0x9b, 0x44, + 0x55, 0x2e, 0x1c, 0x90, 0xb8, 0x21, 0xf8, 0x13, 0x38, 0x72, 0xe2, 0x0a, 0x57, 0x24, 0x7a, 0xe0, + 0x50, 0x09, 0x09, 0x21, 0x2a, 0x45, 0x28, 0x41, 0xf0, 0x27, 0x70, 0x45, 0x9e, 0x9d, 0xcd, 0xee, + 0xc6, 0xbb, 0xeb, 0x8d, 0x69, 0x6f, 0xf1, 0xce, 0xfb, 0xf1, 0xf9, 0xbc, 0xf7, 0xe6, 0xcd, 0x47, + 0x81, 0x29, 0x8d, 0x68, 0x07, 0x55, 0xcb, 0x54, 0x35, 0x56, 0xb2, 0x19, 0xd9, 0x35, 0x4c, 0x5d, + 0x6d, 0x2e, 0xaa, 0x4f, 0x1a, 0xb4, 0x7e, 0xa0, 0xd4, 0xea, 0x16, 0xb3, 0xf0, 0x35, 0x61, 0xa2, + 0x78, 0x26, 0x4a, 0x73, 0x51, 0xba, 0xaa, 0x5b, 0xba, 0xc5, 0x2d, 0xd4, 0xd6, 0x5f, 0x8e, 0xb1, + 0x74, 0x5d, 0xb7, 0x2c, 0xbd, 0x4a, 0x55, 0x52, 0x33, 0x54, 0x62, 0x9a, 0x16, 0x23, 0xcc, 0xb0, + 0x4c, 0x5b, 0x9c, 0xde, 0x2e, 0x59, 0xf6, 0x9e, 0x65, 0xab, 0x1a, 0xb1, 0xa9, 0x93, 0x43, 0x6d, + 0x2e, 0x6a, 0x94, 0x91, 0x45, 0xb5, 0x46, 0x74, 0xc3, 0xe4, 0xc6, 0xc2, 0x56, 0x0e, 0x47, 0x56, + 0x23, 0x75, 0xb2, 0xe7, 0xc6, 0x9b, 0x0d, 0xb7, 0xf1, 0x01, 0xe5, 0x76, 0xf2, 0x55, 0xc0, 0xef, + 0xb7, 0xb2, 0xe5, 0xb9, 0x73, 0x81, 0x3e, 0x69, 0x50, 0x9b, 0xc9, 0x05, 0xb8, 0x12, 0xf8, 0x6a, + 0xd7, 0x2c, 0xd3, 0xa6, 0x78, 0x05, 0x52, 0x4e, 0x92, 0x34, 0xba, 0x81, 0xe6, 0x07, 0xb3, 0x13, + 0x4a, 0x68, 0x01, 0x14, 0xc7, 0x2d, 0xd7, 0xf7, 0xec, 0x68, 0xb2, 0xa7, 0x20, 0x5c, 0xe4, 0x12, + 0x8c, 0xf1, 0x98, 0xb9, 0x9d, 0xb5, 0x0f, 0x49, 0xd5, 0x28, 0x13, 0x66, 0xd5, 0xdd, 0x84, 0xf8, + 0x21, 0x80, 0x47, 0x53, 0x44, 0x9f, 0x55, 0x9c, 0x9a, 0x28, 0xad, 0x9a, 0x28, 0x4e, 0xdd, 0x45, + 0x4d, 0x94, 0x3c, 0xd1, 0xa9, 0xf0, 0x2d, 0xf8, 0x3c, 0xe5, 0xef, 0x10, 0x48, 0x61, 0x59, 0x04, + 0x81, 0x4d, 0x18, 0xd1, 0x58, 0xa9, 0xd8, 0x3c, 0x3d, 0x49, 0xa3, 0x1b, 0x17, 0xe6, 0x07, 0xb3, + 0xd3, 0x11, 0x44, 0xfc, 0x51, 0x0a, 0xc3, 0x1a, 0x2b, 0x79, 0x31, 0xf1, 0xa3, 0x00, 0xe4, 0x5e, + 0x0e, 0x79, 0xae, 0x23, 0x64, 0x07, 0x48, 0x00, 0xf3, 0x7d, 0x48, 0xb7, 0x41, 0x76, 0xeb, 0x32, + 0x0d, 0x23, 0x4d, 0x52, 0x2d, 0xb6, 0x40, 0xd7, 0x76, 0x8b, 0x15, 0xba, 0xcf, 0x6b, 0x33, 0x50, + 0x18, 0x6c, 0x92, 0x6a, 0x8e, 0x95, 0xf2, 0xbb, 0x1b, 0x74, 0x5f, 0xa6, 0x21, 0x95, 0x3d, 0xa5, + 0xbc, 0x01, 0xc3, 0x01, 0xca, 0xa2, 0xb8, 0x89, 0x18, 0x0f, 0xf9, 0x19, 0xcb, 0xd3, 0x30, 0xe5, + 0x0c, 0x05, 0x35, 0xcb, 0x86, 0xa9, 0xe7, 0x76, 0xd6, 0xd6, 0x69, 0x95, 0xea, 0xce, 0x18, 0xbb, + 0x93, 0x63, 0x83, 0x1c, 0x67, 0x24, 0x40, 0x6d, 0xc3, 0xa5, 0x16, 0xa8, 0xb2, 0x77, 0x24, 0x1a, + 0x71, 0x33, 0x1a, 0x96, 0x17, 0xa7, 0xd0, 0x6a, 0xa2, 0x2f, 0xac, 0x3c, 0x03, 0xd3, 0x3c, 0xe9, + 0x07, 0xa6, 0x66, 0xc5, 0x60, 0x6b, 0xc0, 0xcd, 0x78, 0xb3, 0x57, 0x83, 0xae, 0x0c, 0x33, 0x6d, + 0xed, 0xc9, 0x5b, 0x9f, 0xd2, 0xfa, 0x03, 0xb6, 0x41, 0x0d, 0xbd, 0xc2, 0xce, 0xd3, 0x6c, 0xfc, + 0x7f, 0x48, 0x55, 0xb8, 0x17, 0x1f, 0xb9, 0xbe, 0x82, 0xf8, 0x25, 0x6f, 0xc1, 0x6c, 0xa7, 0x2c, + 0x82, 0xde, 0x14, 0x0c, 0x35, 0x2d, 0x66, 0x98, 0x7a, 0xb1, 0xd6, 0x3a, 0xe7, 0x49, 0xfa, 0x0a, + 0x83, 0xce, 0x37, 0xee, 0x22, 0x6f, 0x89, 0x4a, 0xf9, 0x83, 0xad, 0x35, 0xea, 0x75, 0x6a, 0x32, + 0x6e, 0x70, 0xae, 0xf1, 0xd4, 0x42, 0xf8, 0x07, 0x83, 0x09, 0x60, 0x1e, 0x35, 0xe4, 0xa7, 0xd6, + 0x06, 0xb8, 0xb7, 0x1d, 0xf0, 0x17, 0x08, 0xe6, 0x78, 0x92, 0x07, 0x25, 0x66, 0x34, 0x69, 0xe0, + 0xf6, 0x9f, 0x2d, 0x73, 0x54, 0x9a, 0x87, 0x21, 0x17, 0xba, 0x9b, 0x1d, 0xf4, 0x13, 0x82, 0xf9, + 0xce, 0x58, 0x04, 0xe7, 0x42, 0xc4, 0x46, 0x7a, 0x2d, 0xc1, 0xfd, 0xfc, 0xc8, 0x60, 0x95, 0x6d, + 0xca, 0xc8, 0x2b, 0xdb, 0x4c, 0x13, 0x30, 0xee, 0x11, 0x21, 0x8c, 0x96, 0x03, 0x85, 0x94, 0x97, + 0xe1, 0x7a, 0xf8, 0x71, 0x7c, 0x3f, 0xe5, 0xaf, 0x90, 0xb8, 0xaf, 0x7e, 0x32, 0xed, 0xf7, 0x35, + 0xd9, 0x7d, 0x78, 0x59, 0x5d, 0x7b, 0x81, 0x42, 0x66, 0x3e, 0x6c, 0x3b, 0x7c, 0x02, 0x63, 0xbe, + 0xed, 0x60, 0xd5, 0x43, 0xf6, 0x84, 0xd2, 0x71, 0x4f, 0x04, 0x43, 0x8f, 0x7a, 0x1b, 0x23, 0x70, + 0xf0, 0xf2, 0x3a, 0xb9, 0xe9, 0x3d, 0x11, 0xbe, 0x4d, 0x25, 0xea, 0x7c, 0x07, 0xae, 0x08, 0x90, + 0x45, 0xb6, 0x5f, 0xac, 0x10, 0xbb, 0xe2, 0x2b, 0xf6, 0x65, 0x71, 0xb4, 0xb3, 0xbf, 0x41, 0xec, + 0x4a, 0xeb, 0x3e, 0xff, 0xd5, 0xeb, 0xbd, 0xb1, 0xfe, 0x60, 0xa7, 0xdb, 0x33, 0xe5, 0x74, 0x8c, + 0x07, 0x18, 0xca, 0x2d, 0xff, 0x7e, 0x34, 0x99, 0xd5, 0x0d, 0x56, 0x69, 0x68, 0x4a, 0xc9, 0xda, + 0x53, 0x45, 0x69, 0x4a, 0x15, 0x62, 0x98, 0xee, 0x0f, 0x95, 0x1d, 0xd4, 0xa8, 0xad, 0xe4, 0xde, + 0xcd, 0x2f, 0xbd, 0xbe, 0x90, 0x6f, 0x68, 0x5b, 0xf4, 0xa0, 0xd0, 0xaf, 0xb5, 0x5a, 0x8c, 0x77, + 0x00, 0xbc, 0x21, 0xe0, 0x25, 0xe8, 0x3e, 0xe4, 0x45, 0x77, 0x70, 0x5a, 0x2b, 0xc5, 0x66, 0xa4, + 0xce, 0x8a, 0x62, 0x40, 0x2f, 0x38, 0x2b, 0x85, 0x7f, 0x73, 0xa6, 0x18, 0x4f, 0x00, 0x50, 0xb3, + 0xec, 0x1a, 0xf4, 0x71, 0x83, 0x01, 0x6a, 0x8a, 0x21, 0xc7, 0xe3, 0x30, 0xc0, 0x2c, 0x46, 0xaa, + 0x45, 0x9b, 0xb0, 0x74, 0x3f, 0x3f, 0xbd, 0xc8, 0x3f, 0x3c, 0x26, 0xdc, 0xd7, 0xab, 0x68, 0x3a, + 0xc5, 0x0b, 0x39, 0x70, 0x5a, 0x48, 0x3c, 0x03, 0x23, 0xee, 0xb1, 0x5d, 0xaa, 0x1b, 0x35, 0x96, + 0xfe, 0x1f, 0x37, 0x19, 0x16, 0x5f, 0x1f, 0xf3, 0x8f, 0xd9, 0x7f, 0x2e, 0x43, 0x3f, 0x2f, 0x34, + 0xfe, 0x1c, 0x41, 0xca, 0x11, 0x55, 0xf8, 0x56, 0xc4, 0x6c, 0xb5, 0xab, 0x38, 0xe9, 0x76, 0x12, + 0x53, 0xa7, 0x6b, 0xf2, 0xcc, 0x67, 0xbf, 0xfc, 0xf9, 0x75, 0xef, 0x24, 0x9e, 0x50, 0xe3, 0xc4, + 0x25, 0xfe, 0x06, 0xc1, 0x70, 0x60, 0xa1, 0xe1, 0x85, 0xb8, 0x24, 0x61, 0x5a, 0x4f, 0x5a, 0x3c, + 0x87, 0x87, 0x40, 0x77, 0x87, 0xa3, 0x9b, 0xc3, 0x33, 0x6a, 0xa4, 0xac, 0xf5, 0xad, 0x50, 0xfc, + 0x03, 0x82, 0x21, 0x7f, 0x20, 0xac, 0x26, 0x4d, 0xe9, 0x62, 0x5c, 0x48, 0xee, 0x20, 0x20, 0x6e, + 0x70, 0x88, 0x39, 0xfc, 0x76, 0x22, 0x88, 0xea, 0xd3, 0xe0, 0x66, 0x3b, 0x54, 0x4f, 0xcf, 0xf0, + 0x8f, 0x08, 0xae, 0x85, 0xca, 0x27, 0x7c, 0x37, 0xb6, 0xa1, 0x31, 0xb2, 0x4c, 0x7a, 0xa3, 0x0b, + 0x4f, 0x41, 0x6c, 0x99, 0x13, 0x5b, 0xc0, 0x4a, 0xd4, 0x64, 0x38, 0xde, 0xc5, 0x33, 0x92, 0x09, + 0xff, 0x8c, 0x60, 0x34, 0x42, 0x69, 0xe1, 0x37, 0xe3, 0xe0, 0xc4, 0xab, 0x38, 0x69, 0xa5, 0x2b, + 0x5f, 0x41, 0xe6, 0x2e, 0x27, 0x93, 0xc5, 0x0b, 0x11, 0x64, 0x1a, 0xae, 0x7f, 0x1b, 0x9d, 0x5f, + 0x11, 0x8c, 0xc7, 0x3c, 0xe8, 0xf8, 0x5e, 0x1c, 0xac, 0xce, 0xaa, 0x44, 0xba, 0xdf, 0xb5, 0x7f, + 0xc2, 0x3e, 0x9d, 0x1d, 0x40, 0x67, 0xb1, 0x1d, 0xe2, 0xbf, 0x11, 0x8c, 0x45, 0x8a, 0x46, 0xbc, + 0x9a, 0xf4, 0x22, 0x84, 0x29, 0x5a, 0xe9, 0xad, 0x2e, 0xbd, 0x05, 0xa5, 0x6d, 0x4e, 0xe9, 0x11, + 0x7e, 0xa7, 0xcb, 0x3b, 0xc5, 0xe5, 0xa2, 0xc7, 0xf4, 0x05, 0x82, 0x74, 0x94, 0x08, 0xc5, 0x2b, + 0x49, 0xa1, 0x86, 0xe8, 0x60, 0x69, 0xb5, 0x3b, 0x67, 0x41, 0x73, 0x9d, 0xd3, 0xbc, 0x87, 0x57, + 0xff, 0x0b, 0x4d, 0xfc, 0x2d, 0x82, 0x4b, 0x67, 0x94, 0x18, 0xce, 0x76, 0x1c, 0xaa, 0x36, 0x55, + 0x27, 0x2d, 0x9d, 0xcb, 0x47, 0x50, 0x50, 0x39, 0x85, 0x5b, 0x78, 0x2e, 0x82, 0x02, 0x71, 0xfd, + 0xc4, 0x7b, 0x8a, 0x8f, 0x10, 0x8c, 0x46, 0x28, 0xad, 0xf8, 0xed, 0x10, 0xaf, 0x19, 0xa5, 0x95, + 0xae, 0x7c, 0x05, 0x8b, 0x4d, 0xce, 0x62, 0x1d, 0xe7, 0xba, 0x6c, 0x84, 0x7f, 0x5f, 0x7c, 0xef, + 0xbc, 0x94, 0x5e, 0x9a, 0x8e, 0x2f, 0x65, 0x9b, 0x30, 0xeb, 0xf8, 0x52, 0xb6, 0xab, 0xaf, 0x44, + 0xb3, 0xe4, 0x83, 0xa9, 0x3e, 0x0d, 0x51, 0x7e, 0x87, 0xb9, 0xf7, 0x9e, 0x1d, 0x67, 0xd0, 0xf3, + 0xe3, 0x0c, 0xfa, 0xe3, 0x38, 0x83, 0xbe, 0x3c, 0xc9, 0xf4, 0x3c, 0x3f, 0xc9, 0xf4, 0xfc, 0x76, + 0x92, 0xe9, 0xf9, 0xb8, 0xa3, 0xec, 0xda, 0xf7, 0x27, 0xe4, 0x1a, 0x4c, 0x4b, 0xf1, 0x7f, 0x35, + 0x2d, 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x8f, 0x91, 0x6d, 0x44, 0x52, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1182,6 +1272,8 @@ type QueryClient interface { BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) // PendingBTCDelegations queries all pending BTC delegations PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) + // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature + UnbondingBTCDelegations(ctx context.Context, in *QueryUnbondingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryUnbondingBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height @@ -1241,6 +1333,15 @@ func (c *queryClient) PendingBTCDelegations(ctx context.Context, in *QueryPendin return out, nil } +func (c *queryClient) UnbondingBTCDelegations(ctx context.Context, in *QueryUnbondingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryUnbondingBTCDelegationsResponse, error) { + out := new(QueryUnbondingBTCDelegationsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/UnbondingBTCDelegations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) { out := new(QueryActiveBTCValidatorsAtHeightResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", in, out, opts...) @@ -1305,6 +1406,8 @@ type QueryServer interface { BTCValidator(context.Context, *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) // PendingBTCDelegations queries all pending BTC delegations PendingBTCDelegations(context.Context, *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) + // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature + UnbondingBTCDelegations(context.Context, *QueryUnbondingBTCDelegationsRequest) (*QueryUnbondingBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height @@ -1336,6 +1439,9 @@ func (*UnimplementedQueryServer) BTCValidator(ctx context.Context, req *QueryBTC func (*UnimplementedQueryServer) PendingBTCDelegations(ctx context.Context, req *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PendingBTCDelegations not implemented") } +func (*UnimplementedQueryServer) UnbondingBTCDelegations(ctx context.Context, req *QueryUnbondingBTCDelegationsRequest) (*QueryUnbondingBTCDelegationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnbondingBTCDelegations not implemented") +} func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context, req *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ActiveBTCValidatorsAtHeight not implemented") } @@ -1431,6 +1537,24 @@ func _Query_PendingBTCDelegations_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Query_UnbondingBTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUnbondingBTCDelegationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UnbondingBTCDelegations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Query/UnbondingBTCDelegations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UnbondingBTCDelegations(ctx, req.(*QueryUnbondingBTCDelegationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_ActiveBTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryActiveBTCValidatorsAtHeightRequest) if err := dec(in); err != nil { @@ -1559,6 +1683,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "PendingBTCDelegations", Handler: _Query_PendingBTCDelegations_Handler, }, + { + MethodName: "UnbondingBTCDelegations", + Handler: _Query_UnbondingBTCDelegations_Handler, + }, { MethodName: "ActiveBTCValidatorsAtHeight", Handler: _Query_ActiveBTCValidatorsAtHeight_Handler, @@ -1853,6 +1981,66 @@ func (m *QueryPendingBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } +func (m *QueryUnbondingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnbondingBTCDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnbondingBTCDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryUnbondingBTCDelegationsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnbondingBTCDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnbondingBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcDelegations) > 0 { + for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func (m *QueryBTCValidatorPowerAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2429,6 +2617,30 @@ func (m *QueryPendingBTCDelegationsResponse) Size() (n int) { return n } +func (m *QueryUnbondingBTCDelegationsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryUnbondingBTCDelegationsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcDelegations) > 0 { + for _, e := range m.BtcDelegations { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + func (m *QueryBTCValidatorPowerAtHeightRequest) Size() (n int) { if m == nil { return 0 @@ -3271,6 +3483,140 @@ func (m *QueryPendingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryUnbondingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnbondingBTCDelegationsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnbondingBTCDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnbondingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnbondingBTCDelegationsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnbondingBTCDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) + if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index afb8be0bd..522bc859d 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -159,6 +159,24 @@ func local_request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler } +func request_Query_UnbondingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnbondingBTCDelegationsRequest + var metadata runtime.ServerMetadata + + msg, err := client.UnbondingBTCDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UnbondingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnbondingBTCDelegationsRequest + var metadata runtime.ServerMetadata + + msg, err := server.UnbondingBTCDelegations(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_ActiveBTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -603,6 +621,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_UnbondingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UnbondingBTCDelegations_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnbondingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -862,6 +903,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_UnbondingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UnbondingBTCDelegations_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnbondingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -994,6 +1055,8 @@ var ( pattern_Query_PendingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "pending_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_UnbondingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "unbonding_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -1016,6 +1079,8 @@ var ( forward_Query_PendingBTCDelegations_0 = runtime.ForwardResponseMessage + forward_Query_UnbondingBTCDelegations_0 = runtime.ForwardResponseMessage + forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage From fc7452bb060002ca51109188977fe655b0ab6571 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 7 Sep 2023 16:15:38 +1000 Subject: [PATCH 076/202] incentive: distribute rewards to BTC validators/delegations (#72) --- app/app.go | 44 +- proto/babylon/btcstaking/v1/incentive.proto | 41 + proto/babylon/incentive/incentive.proto | 5 - test/e2e/btc_staking_e2e_test.go | 21 +- testutil/datagen/btcstaking.go | 6 +- testutil/datagen/incentive.go | 41 + testutil/keeper/btccheckpoint.go | 2 + testutil/keeper/finality.go | 3 +- .../keeper/grpc_query_params_test.go | 2 +- x/btccheckpoint/keeper/keeper.go | 6 +- x/btccheckpoint/keeper/msg_server_test.go | 2 +- x/btccheckpoint/keeper/params_test.go | 2 +- x/btccheckpoint/types/expected_keepers.go | 3 + x/btcstaking/abci.go | 8 + x/btcstaking/keeper/incentive.go | 93 ++ x/btcstaking/keeper/incentive_test.go | 88 ++ x/btcstaking/keeper/voting_power_table.go | 9 + x/btcstaking/types/errors.go | 1 + x/btcstaking/types/incentive.go | 78 ++ x/btcstaking/types/incentive.pb.go | 994 ++++++++++++++++++ x/btcstaking/types/keys.go | 11 +- x/finality/genesis_test.go | 2 +- x/finality/keeper/grpc_query_test.go | 12 +- x/finality/keeper/keeper.go | 7 +- x/finality/keeper/msg_server_test.go | 6 +- x/finality/keeper/params_test.go | 2 +- x/finality/keeper/query_params_test.go | 2 +- x/finality/keeper/tallying.go | 32 +- x/finality/keeper/tallying_test.go | 7 +- x/finality/keeper/votes.go | 24 + x/finality/types/expected_keepers.go | 8 + x/finality/types/mocked_keepers.go | 74 ++ x/incentive/keeper/btc_staking_gauge.go | 40 + x/incentive/keeper/btc_staking_gauge_test.go | 66 ++ x/incentive/types/incentive.go | 7 +- x/incentive/types/incentive.pb.go | 91 +- 36 files changed, 1697 insertions(+), 143 deletions(-) create mode 100644 proto/babylon/btcstaking/v1/incentive.proto create mode 100644 x/btcstaking/keeper/incentive.go create mode 100644 x/btcstaking/keeper/incentive_test.go create mode 100644 x/btcstaking/types/incentive.go create mode 100644 x/btcstaking/types/incentive.pb.go create mode 100644 x/incentive/keeper/btc_staking_gauge_test.go diff --git a/app/app.go b/app/app.go index 1cd9bb332..33ac735d5 100644 --- a/app/app.go +++ b/app/app.go @@ -470,14 +470,20 @@ func NewBabylonApp( appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, keys[minttypes.StoreKey], app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, keys[distrtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + // set up epoching keeper // NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module epochingKeeper := epochingkeeper.NewKeeper( appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.BankKeeper, app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - app.MintKeeper = mintkeeper.NewKeeper(appCodec, keys[minttypes.StoreKey], app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) - - app.DistrKeeper = distrkeeper.NewKeeper(appCodec, keys[distrtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + // set up incentive keeper + app.IncentiveKeeper = incentivekeeper.NewKeeper( + appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, &epochingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, + ) app.SlashingKeeper = slashingkeeper.NewKeeper( appCodec, legacyAmino, keys[slashingtypes.StoreKey], app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), @@ -558,6 +564,7 @@ func NewBabylonApp( keys[btccheckpointtypes.MemStoreKey], &btclightclientKeeper, &checkpointingKeeper, + &app.IncentiveKeeper, &powLimit, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) @@ -623,19 +630,6 @@ func NewBabylonApp( &btclightclientKeeper, ) - // set up BTC staking keeper - app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( - appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], - &btclightclientKeeper, &btcCheckpointKeeper, - btcNetParams, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - // set up finality keeper - app.FinalityKeeper = finalitykeeper.NewKeeper( - appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, - app.BTCStakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - // add msgServiceRouter so that the epoching module can forward unwrapped messages to the staking module epochingKeeper.SetMsgServiceRouter(app.BaseApp.MsgServiceRouter()) // make ZoneConcierge and Monitor to subscribe to the epoching's hooks @@ -643,11 +637,6 @@ func NewBabylonApp( epochingtypes.NewMultiEpochingHooks(app.ZoneConciergeKeeper.Hooks(), app.MonitorKeeper.Hooks()), ) - // set up incentive keeper - app.IncentiveKeeper = incentivekeeper.NewKeeper( - appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, app.EpochingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, - ) - // set up Checkpointing, BTCCheckpoint, and BTCLightclient keepers app.CheckpointingKeeper = *checkpointingKeeper.SetHooks( checkpointingtypes.NewMultiCheckpointingHooks(app.EpochingKeeper.Hooks(), app.ZoneConciergeKeeper.Hooks(), app.MonitorKeeper.Hooks()), @@ -657,6 +646,19 @@ func NewBabylonApp( btclightclienttypes.NewMultiBTCLightClientHooks(app.BtcCheckpointKeeper.Hooks()), ) + // set up BTC staking keeper + app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( + appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], + &btclightclientKeeper, &btcCheckpointKeeper, + btcNetParams, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + // set up finality keeper + app.FinalityKeeper = finalitykeeper.NewKeeper( + appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, + app.BTCStakingKeeper, app.IncentiveKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + // create evidence keeper with router evidenceKeeper := evidencekeeper.NewKeeper( appCodec, keys[evidencetypes.StoreKey], app.StakingKeeper, app.SlashingKeeper, diff --git a/proto/babylon/btcstaking/v1/incentive.proto b/proto/babylon/btcstaking/v1/incentive.proto new file mode 100644 index 000000000..cc9053b85 --- /dev/null +++ b/proto/babylon/btcstaking/v1/incentive.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/crypto/secp256k1/keys.proto"; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// RewardDistCache is the cache for reward distribution of BTC validators at a height +message RewardDistCache { + uint64 total_voting_power = 1; + // btc_vals is a list of BTC validators' voting power information + repeated BTCValDistInfo btc_vals = 2; +} + +// BTCValDistInfo is the reward distribution of a BTC validator and its BTC delegations +message BTCValDistInfo { + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // babylon_pk is the Babylon public key of the BTC validator + cosmos.crypto.secp256k1.PubKey babylon_pk = 2; + // commission defines the commission rate of BTC validator + string commission = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + ]; + // total_voting_power is the total voting power of the BTC validator + uint64 total_voting_power = 4; + // btc_dels is a list of BTC delegations' voting power information under this BTC validator + repeated BTCDelDistInfo btc_dels = 5; +} + +// BTCDelDistInfo contains the information related to reward distribution for a BTC delegations +message BTCDelDistInfo { + // babylon_pk is the Babylon public key of the BTC delegations + cosmos.crypto.secp256k1.PubKey babylon_pk = 1; + // voting_power is the voting power of the BTC delegation + uint64 voting_power = 2; +} \ No newline at end of file diff --git a/proto/babylon/incentive/incentive.proto b/proto/babylon/incentive/incentive.proto index 03c9980f1..ccc4c3a6c 100644 --- a/proto/babylon/incentive/incentive.proto +++ b/proto/babylon/incentive/incentive.proto @@ -15,11 +15,6 @@ message Gauge { (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; - // distributed_coins are coins that have been distributed already - repeated cosmos.base.v1beta1.Coin distributed_coins = 2 [ - (gogoproto.nullable) = false, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; } // RewardGauge is an object that stores rewards distributed to a BTC staking/timestamping stakeholder diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index feb2c08bb..a0d3b5937 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -30,10 +30,12 @@ var ( r = rand.New(rand.NewSource(time.Now().Unix())) net = &chaincfg.SimNetParams // BTC validator - valSK, _, _ = datagen.GenRandomBTCKeyPair(r) - btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) + valSK, _, _ = datagen.GenRandomBTCKeyPair(r) + btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) + btcValBabylonAddr = sdk.AccAddress(btcVal.BabylonPk.Address()).String() // BTC delegation delBabylonSK, delBabylonPK, _ = datagen.GenRandomSecp256k1KeyPair(r) + delBabylonAddr = sdk.AccAddress(delBabylonPK.Address()).String() delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) // jury jurySK, _ = btcec.PrivKeyFromBytes( @@ -245,6 +247,13 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat }, time.Minute, time.Second*5) s.Equal(pubRandMap[activatedHeight].MustMarshal(), msgCommitPubRandList.PubRandList[0].MustMarshal()) + // get the balance of BTC validator and delegation before submitting finality signature + // later we will check if rewards are distributed after they finalise a block + btcValBalance, err := nonValidatorNode.QueryBalances(btcValBabylonAddr) + s.NoError(err) + btcDelBalance, err := nonValidatorNode.QueryBalances(delBabylonAddr) + s.NoError(err) + /* submit finality signature */ @@ -274,6 +283,14 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat finalizedBlocks := nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) s.NotEmpty(finalizedBlocks) s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) + + // ensure voters have received rewards after the block is finalised + btcValBalance2, err := nonValidatorNode.QueryBalances(btcValBabylonAddr) + s.NoError(err) + s.True(btcValBalance2.IsAllGT(btcValBalance)) + btcDelBalance2, err := nonValidatorNode.QueryBalances(delBabylonAddr) + s.NoError(err) + s.True(btcDelBalance2.IsAllGT(btcDelBalance)) } // Test4SubmitStakerUnbonding is an end-to-end test for user unbodning diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 19ca1a131..efe859c41 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" - "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -15,6 +14,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -29,7 +29,7 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { // commission - zeroCommission := math.LegacyZeroDec() + commission := sdk.NewDecWithPrec(int64(RandomInt(r, 99)+1), 2) // [1/100, 100/100] // description description := stakingtypes.Description{} // key pairs @@ -50,7 +50,7 @@ func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst } return &bstypes.BTCValidator{ Description: &description, - Commission: &zeroCommission, + Commission: &commission, BabylonPk: secp256k1PK, BtcPk: bip340PK, Pop: pop, diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index e26babb72..baf93d101 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -3,7 +3,9 @@ package datagen import ( "math/rand" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" itypes "github.com/babylonchain/babylon/x/incentive/types" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -70,3 +72,42 @@ func GenRandomGauge(r *rand.Rand) *itypes.Gauge { coins := GenRandomCoins(r) return itypes.NewGauge(coins) } + +func GenRandomBTCDelDistInfo(r *rand.Rand) *bstypes.BTCDelDistInfo { + return &bstypes.BTCDelDistInfo{ + BabylonPk: GenRandomAccount().GetPubKey().(*secp256k1.PubKey), + VotingPower: RandomInt(r, 1000) + 1, + } +} + +func GenRandomBTCValDistInfo(r *rand.Rand) (*bstypes.BTCValDistInfo, error) { + // create BTC validator with random commission + btcVal, err := GenRandomBTCValidator(r) + if err != nil { + return nil, err + } + // create BTC validator distribution info + btcValDistInfo := bstypes.NewBTCValDistInfo(btcVal) + // add a random number of BTC delegation distribution info + numBTCDels := RandomInt(r, 100) + 1 + for i := uint64(0); i < numBTCDels; i++ { + btcDelDistInfo := GenRandomBTCDelDistInfo(r) + btcValDistInfo.BtcDels = append(btcValDistInfo.BtcDels, btcDelDistInfo) + btcValDistInfo.TotalVotingPower += btcDelDistInfo.VotingPower + } + return btcValDistInfo, nil +} + +func GenRandomRewardDistCache(r *rand.Rand) (*bstypes.RewardDistCache, error) { + rdc := bstypes.NewRewardDistCache() + // a random number of BTC validators + numBTCVals := RandomInt(r, 10) + 1 + for i := uint64(0); i < numBTCVals; i++ { + v, err := GenRandomBTCValDistInfo(r) + if err != nil { + return nil, err + } + rdc.AddBTCValDistInfo(v) + } + return rdc, nil +} diff --git a/testutil/keeper/btccheckpoint.go b/testutil/keeper/btccheckpoint.go index ecd1f7c4f..22a5860aa 100644 --- a/testutil/keeper/btccheckpoint.go +++ b/testutil/keeper/btccheckpoint.go @@ -24,6 +24,7 @@ func NewBTCCheckpointKeeper( t testing.TB, lk btcctypes.BTCLightClientKeeper, ek btcctypes.CheckpointingKeeper, + ik btcctypes.IncentiveKeeper, powLimit *big.Int) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(btcctypes.StoreKey) tstoreKey := sdk.NewTransientStoreKey(btcctypes.TStoreKey) @@ -45,6 +46,7 @@ func NewBTCCheckpointKeeper( memStoreKey, lk, ek, + ik, powLimit, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index a058d941d..7a8d056f6 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -18,7 +18,7 @@ import ( "github.com/stretchr/testify/require" ) -func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper) (*keeper.Keeper, sdk.Context) { +func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types.IncentiveKeeper) (*keeper.Keeper, sdk.Context) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -38,6 +38,7 @@ func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper) (*keeper.Keep nil, nil, bsKeeper, + iKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/x/btccheckpoint/keeper/grpc_query_params_test.go b/x/btccheckpoint/keeper/grpc_query_params_test.go index ac8e19de5..4863c0de8 100644 --- a/x/btccheckpoint/keeper/grpc_query_params_test.go +++ b/x/btccheckpoint/keeper/grpc_query_params_test.go @@ -52,7 +52,7 @@ func FuzzParamsQuery(f *testing.F) { params.BtcConfirmationDepth = checkpointFinalizationTimeout } - keeper, ctx := testkeeper.NewBTCCheckpointKeeper(t, nil, nil, nil) + keeper, ctx := testkeeper.NewBTCCheckpointKeeper(t, nil, nil, nil, nil) wctx := sdk.WrapSDKContext(ctx) // if setParamsFlag == 0, set params diff --git a/x/btccheckpoint/keeper/keeper.go b/x/btccheckpoint/keeper/keeper.go index 10753584a..e99030d5f 100644 --- a/x/btccheckpoint/keeper/keeper.go +++ b/x/btccheckpoint/keeper/keeper.go @@ -24,6 +24,7 @@ type ( memKey storetypes.StoreKey btcLightClientKeeper types.BTCLightClientKeeper checkpointingKeeper types.CheckpointingKeeper + incentiveKeeper types.IncentiveKeeper powLimit *big.Int authority string } @@ -64,6 +65,7 @@ func NewKeeper( memKey storetypes.StoreKey, bk types.BTCLightClientKeeper, ck types.CheckpointingKeeper, + ik types.IncentiveKeeper, powLimit *big.Int, authority string, ) Keeper { @@ -75,6 +77,7 @@ func NewKeeper( memKey: memKey, btcLightClientKeeper: bk, checkpointingKeeper: ck, + incentiveKeeper: ik, powLimit: powLimit, authority: authority, } @@ -631,7 +634,6 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { // epoch just got confirmed by best submission currentEpoch.Status = types.Confirmed k.checkpointingKeeper.SetCheckpointConfirmed(ctx, epoch) - // TODO This is the place to check other submissions and pay up rewards. } if bestSubmissionStatus > currentEpoch.Status && currentEpoch.Status == types.Confirmed { @@ -642,6 +644,8 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { } if currentEpoch.Status == types.Finalized { + // TODO(incentive): trigger incentive module to distribute rewards to submitters/reporters + for i, sk := range currentEpoch.Key { // delete all submissions except best one if i != epochChanges.BestSubmissionIdx { diff --git a/x/btccheckpoint/keeper/msg_server_test.go b/x/btccheckpoint/keeper/msg_server_test.go index b621d444b..212496b2e 100644 --- a/x/btccheckpoint/keeper/msg_server_test.go +++ b/x/btccheckpoint/keeper/msg_server_test.go @@ -53,7 +53,7 @@ func InitTestKeepers( cc := btcctypes.NewMockCheckpointingKeeper() - k, ctx := keepertest.NewBTCCheckpointKeeper(t, lc, cc, chaincfg.SimNetParams.PowLimit) + k, ctx := keepertest.NewBTCCheckpointKeeper(t, lc, cc, nil, chaincfg.SimNetParams.PowLimit) srv := bkeeper.NewMsgServerImpl(*k) diff --git a/x/btccheckpoint/keeper/params_test.go b/x/btccheckpoint/keeper/params_test.go index d690964a2..444e46eb9 100644 --- a/x/btccheckpoint/keeper/params_test.go +++ b/x/btccheckpoint/keeper/params_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.NewBTCCheckpointKeeper(t, nil, nil, nil) + k, ctx := testkeeper.NewBTCCheckpointKeeper(t, nil, nil, nil, nil) params := types.DefaultParams() diff --git a/x/btccheckpoint/types/expected_keepers.go b/x/btccheckpoint/types/expected_keepers.go index d351b6a5b..9dc5fa676 100644 --- a/x/btccheckpoint/types/expected_keepers.go +++ b/x/btccheckpoint/types/expected_keepers.go @@ -52,3 +52,6 @@ type CheckpointingKeeper interface { // all submissions on btc chain SetCheckpointForgotten(ctx sdk.Context, epoch uint64) } + +type IncentiveKeeper interface { +} diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index 0527abbbb..eaf61365f 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -17,8 +17,16 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + // index BTC height at the current height k.IndexBTCHeight(ctx) + // record voting power table at the current height k.RecordVotingPowerTable(ctx) + // if BTC staking is activated, record reward distribution cache at the current height + // TODO: consider merging RecordVotingPowerTable and RecordRewardDistCache so that we + // only need to perform one full scan over BTC validators/delegations + if k.IsBTCStakingActivated(ctx) { + k.RecordRewardDistCache(ctx) + } return []abci.ValidatorUpdate{} } diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go new file mode 100644 index 000000000..aef073baf --- /dev/null +++ b/x/btcstaking/keeper/incentive.go @@ -0,0 +1,93 @@ +package keeper + +import ( + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) RecordRewardDistCache(ctx sdk.Context) { + // get BTC tip height and w, which are necessary for determining a BTC + // delegation's voting power + btcTipHeight, err := k.GetCurrentBTCHeight(ctx) + if err != nil { + return + } + wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + rdc := types.NewRewardDistCache() + + // iterate all BTC validators to add each BTC validator's distribution info + // to reward distribution cache + btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) + defer btcValIter.Close() + for ; btcValIter.Valid(); btcValIter.Next() { + valBTCPKBytes := btcValIter.Key() + valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + if err != nil { + // failing to unmarshal BTC validator PK in KVStore is a programming error + panic(err) + } + btcVal, err := k.GetBTCValidator(ctx, valBTCPKBytes) + if err != nil { + // failing to get a BTC validator with voting power is a programming error + panic(err) + } + if btcVal.IsSlashed() { + // slashed BTC validator will not get any reward + continue + } + + // iterate over all BTC delegations under this validator to compute + // the BTC validator's distribution info + btcValDistInfo := types.NewBTCValDistInfo(btcVal) + btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) + for ; btcDelIter.Valid(); btcDelIter.Next() { + // unmarshal + var btcDels types.BTCDelegatorDelegations + k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) + // process each of the BTC delegation + for _, btcDel := range btcDels.Dels { + btcValDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue) + } + } + btcDelIter.Close() + + // try to add this BTC validator distribution info to reward distribution cache + rdc.AddBTCValDistInfo(btcValDistInfo) + } + + // all good, set the reward distribution cache of the current height + k.setRewardDistCache(ctx, uint64(ctx.BlockHeight()), rdc) +} + +func (k Keeper) setRewardDistCache(ctx sdk.Context, height uint64, rdc *types.RewardDistCache) { + store := k.rewardDistCacheStore(ctx) + store.Set(sdk.Uint64ToBigEndian(height), k.cdc.MustMarshal(rdc)) +} + +func (k Keeper) GetRewardDistCache(ctx sdk.Context, height uint64) (*types.RewardDistCache, error) { + store := k.rewardDistCacheStore(ctx) + rdcBytes := store.Get(sdk.Uint64ToBigEndian(height)) + if len(rdcBytes) == 0 { + return nil, types.ErrRewardDistCacheNotFound + } + var rdc types.RewardDistCache + k.cdc.MustUnmarshal(rdcBytes, &rdc) + return &rdc, nil +} + +func (k Keeper) RemoveRewardDistCache(ctx sdk.Context, height uint64) { + store := k.rewardDistCacheStore(ctx) + store.Delete(sdk.Uint64ToBigEndian(height)) +} + +// rewardDistCacheStore returns the KVStore of the reward distribution cache +// prefix: RewardDistCacheKey +// key: Babylon block height +// value: RewardDistCache +func (k Keeper) rewardDistCacheStore(ctx sdk.Context) prefix.Store { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.RewardDistCacheKey) +} diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go new file mode 100644 index 000000000..772ab691f --- /dev/null +++ b/x/btcstaking/keeper/incentive_test.go @@ -0,0 +1,88 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + keepertest "github.com/babylonchain/babylon/testutil/keeper" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzRecordRewardDistCache(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // jury and slashing addr + jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + + // generate a random batch of validators + numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 + numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) + btcValsWithVotingPowerMap := map[string]*types.BTCValidator{} + for i := uint64(0); i < numBTCVals; i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + if i < numBTCValsWithVotingPower { + // these BTC validators will receive BTC delegations and have voting power + btcValsWithVotingPowerMap[btcVal.BabylonPk.String()] = btcVal + } + } + + // for the first numBTCValsWithVotingPower validators, generate a random number of BTC delegations + numBTCDels := datagen.RandomInt(r, 10) + 1 + stakingValue := datagen.RandomInt(r, 100000) + 100000 + for _, btcVal := range btcValsWithVotingPowerMap { + valBTCPK := btcVal.BtcPk + for j := uint64(0); j < numBTCDels; j++ { + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.SetBTCDelegation(ctx, btcDel) + require.NoError(t, err) + } + } + + // record reward distribution cache + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.IndexBTCHeight(ctx) + keeper.RecordRewardDistCache(ctx) + + // assert reward distribution cache is correct + rdc, err := keeper.GetRewardDistCache(ctx, babylonHeight) + require.NoError(t, err) + require.Equal(t, rdc.TotalVotingPower, numBTCValsWithVotingPower*numBTCDels*stakingValue) + for _, valDistInfo := range rdc.BtcVals { + require.Equal(t, valDistInfo.TotalVotingPower, numBTCDels*stakingValue) + btcVal, ok := btcValsWithVotingPowerMap[valDistInfo.BabylonPk.String()] + require.True(t, ok) + require.Equal(t, valDistInfo.Commission, btcVal.Commission) + require.Len(t, valDistInfo.BtcDels, int(numBTCDels)) + for _, delDistInfo := range valDistInfo.BtcDels { + require.Equal(t, delDistInfo.VotingPower, stakingValue) + } + } + }) +} diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 631cdf187..f95c25308 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -128,6 +128,15 @@ func (k Keeper) GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) { } } +func (k Keeper) IsBTCStakingActivated(ctx sdk.Context) bool { + store := ctx.KVStore(k.storeKey) + votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) + iter := votingPowerStore.Iterator(nil, nil) + defer iter.Close() + // if the iterator is valid, then BTC staking is already activated + return iter.Valid() +} + // votingPowerStore returns the KVStore of the BTC validators' voting power // prefix: (VotingPowerKey || Babylon block height) // key: Bitcoin secp256k1 PK diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index badf3c3c2..7a48bf6c9 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -24,4 +24,5 @@ var ( ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1115, "the BTC undelegation has already received validator signature") ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1116, "the validator signature is not valid") ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1117, "the BTC undelegation did not receive validator signature yet") + ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1118, "the reward distribution cache is not found") ) diff --git a/x/btcstaking/types/incentive.go b/x/btcstaking/types/incentive.go new file mode 100644 index 000000000..9764a6282 --- /dev/null +++ b/x/btcstaking/types/incentive.go @@ -0,0 +1,78 @@ +package types + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func NewRewardDistCache() *RewardDistCache { + return &RewardDistCache{ + TotalVotingPower: 0, + BtcVals: []*BTCValDistInfo{}, + } +} + +func (rdc *RewardDistCache) AddBTCValDistInfo(v *BTCValDistInfo) { + if v.TotalVotingPower > 0 { + // append BTC validator dist info and accumulate voting power + rdc.BtcVals = append(rdc.BtcVals, v) + rdc.TotalVotingPower += v.TotalVotingPower + } +} + +// FilterVotedBTCVals filters out BTC validators that have voted according to a map of given voters +// and update total voted power accordingly +func (rdc *RewardDistCache) FilterVotedBTCVals(voterBTCPKs map[string]struct{}) { + filteredBTCVals := []*BTCValDistInfo{} + totalVotingPower := uint64(0) + for _, v := range rdc.BtcVals { + if _, ok := voterBTCPKs[v.BtcPk.MarshalHex()]; ok { + filteredBTCVals = append(filteredBTCVals, v) + totalVotingPower += v.TotalVotingPower + } + } + rdc.BtcVals = filteredBTCVals + rdc.TotalVotingPower = totalVotingPower +} + +// GetBTCValPortion returns the portion of a BTC validator's voting power out of the total voting power +func (rdc *RewardDistCache) GetBTCValPortion(v *BTCValDistInfo) sdk.Dec { + return math.LegacyNewDec(int64(v.TotalVotingPower)).QuoTruncate(math.LegacyNewDec(int64(rdc.TotalVotingPower))) +} + +func NewBTCValDistInfo(btcVal *BTCValidator) *BTCValDistInfo { + return &BTCValDistInfo{ + BtcPk: btcVal.BtcPk, + BabylonPk: btcVal.BabylonPk, + Commission: btcVal.Commission, + TotalVotingPower: 0, + BtcDels: []*BTCDelDistInfo{}, + } +} + +func (v *BTCValDistInfo) GetAddress() sdk.AccAddress { + return sdk.AccAddress(v.BabylonPk.Address()) +} + +func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wValue uint64) { + btcDelDistInfo := &BTCDelDistInfo{ + BabylonPk: btcDel.BabylonPk, + VotingPower: btcDel.VotingPower(btcHeight, wValue), + } + + if btcDelDistInfo.VotingPower > 0 { + // if this BTC delegation has voting power, append it and accumulate voting power + v.BtcDels = append(v.BtcDels, btcDelDistInfo) + v.TotalVotingPower += btcDelDistInfo.VotingPower + } +} + +// GetBTCValPortion returns the portion of a BTC delegation's voting power out of +// the BTC validator's total voting power +func (v *BTCValDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdk.Dec { + return math.LegacyNewDec(int64(d.VotingPower)).QuoTruncate(math.LegacyNewDec(int64(v.TotalVotingPower))) +} + +func (d *BTCDelDistInfo) GetAddress() sdk.AccAddress { + return sdk.AccAddress(d.BabylonPk.Address()) +} diff --git a/x/btcstaking/types/incentive.pb.go b/x/btcstaking/types/incentive.pb.go new file mode 100644 index 000000000..4b1e578c2 --- /dev/null +++ b/x/btcstaking/types/incentive.pb.go @@ -0,0 +1,994 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/incentive.proto + +package types + +import ( + fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/cosmos-proto" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// RewardDistCache is the cache for reward distribution of BTC validators at a height +type RewardDistCache struct { + TotalVotingPower uint64 `protobuf:"varint,1,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` + // btc_vals is a list of BTC validators' voting power information + BtcVals []*BTCValDistInfo `protobuf:"bytes,2,rep,name=btc_vals,json=btcVals,proto3" json:"btc_vals,omitempty"` +} + +func (m *RewardDistCache) Reset() { *m = RewardDistCache{} } +func (m *RewardDistCache) String() string { return proto.CompactTextString(m) } +func (*RewardDistCache) ProtoMessage() {} +func (*RewardDistCache) Descriptor() ([]byte, []int) { + return fileDescriptor_ac354c3bd6d7a66b, []int{0} +} +func (m *RewardDistCache) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RewardDistCache) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RewardDistCache.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RewardDistCache) XXX_Merge(src proto.Message) { + xxx_messageInfo_RewardDistCache.Merge(m, src) +} +func (m *RewardDistCache) XXX_Size() int { + return m.Size() +} +func (m *RewardDistCache) XXX_DiscardUnknown() { + xxx_messageInfo_RewardDistCache.DiscardUnknown(m) +} + +var xxx_messageInfo_RewardDistCache proto.InternalMessageInfo + +func (m *RewardDistCache) GetTotalVotingPower() uint64 { + if m != nil { + return m.TotalVotingPower + } + return 0 +} + +func (m *RewardDistCache) GetBtcVals() []*BTCValDistInfo { + if m != nil { + return m.BtcVals + } + return nil +} + +// BTCValDistInfo is the reward distribution of a BTC validator and its BTC delegations +type BTCValDistInfo struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // babylon_pk is the Babylon public key of the BTC validator + BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // commission defines the commission rate of BTC validator + Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` + // total_voting_power is the total voting power of the BTC validator + TotalVotingPower uint64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` + // btc_dels is a list of BTC delegations' voting power information under this BTC validator + BtcDels []*BTCDelDistInfo `protobuf:"bytes,5,rep,name=btc_dels,json=btcDels,proto3" json:"btc_dels,omitempty"` +} + +func (m *BTCValDistInfo) Reset() { *m = BTCValDistInfo{} } +func (m *BTCValDistInfo) String() string { return proto.CompactTextString(m) } +func (*BTCValDistInfo) ProtoMessage() {} +func (*BTCValDistInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_ac354c3bd6d7a66b, []int{1} +} +func (m *BTCValDistInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCValDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCValDistInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCValDistInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCValDistInfo.Merge(m, src) +} +func (m *BTCValDistInfo) XXX_Size() int { + return m.Size() +} +func (m *BTCValDistInfo) XXX_DiscardUnknown() { + xxx_messageInfo_BTCValDistInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCValDistInfo proto.InternalMessageInfo + +func (m *BTCValDistInfo) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *BTCValDistInfo) GetTotalVotingPower() uint64 { + if m != nil { + return m.TotalVotingPower + } + return 0 +} + +func (m *BTCValDistInfo) GetBtcDels() []*BTCDelDistInfo { + if m != nil { + return m.BtcDels + } + return nil +} + +// BTCDelDistInfo contains the information related to reward distribution for a BTC delegations +type BTCDelDistInfo struct { + // babylon_pk is the Babylon public key of the BTC delegations + BabylonPk *secp256k1.PubKey `protobuf:"bytes,1,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` + // voting_power is the voting power of the BTC delegation + VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` +} + +func (m *BTCDelDistInfo) Reset() { *m = BTCDelDistInfo{} } +func (m *BTCDelDistInfo) String() string { return proto.CompactTextString(m) } +func (*BTCDelDistInfo) ProtoMessage() {} +func (*BTCDelDistInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_ac354c3bd6d7a66b, []int{2} +} +func (m *BTCDelDistInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCDelDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCDelDistInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCDelDistInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelDistInfo.Merge(m, src) +} +func (m *BTCDelDistInfo) XXX_Size() int { + return m.Size() +} +func (m *BTCDelDistInfo) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelDistInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCDelDistInfo proto.InternalMessageInfo + +func (m *BTCDelDistInfo) GetBabylonPk() *secp256k1.PubKey { + if m != nil { + return m.BabylonPk + } + return nil +} + +func (m *BTCDelDistInfo) GetVotingPower() uint64 { + if m != nil { + return m.VotingPower + } + return 0 +} + +func init() { + proto.RegisterType((*RewardDistCache)(nil), "babylon.btcstaking.v1.RewardDistCache") + proto.RegisterType((*BTCValDistInfo)(nil), "babylon.btcstaking.v1.BTCValDistInfo") + proto.RegisterType((*BTCDelDistInfo)(nil), "babylon.btcstaking.v1.BTCDelDistInfo") +} + +func init() { + proto.RegisterFile("babylon/btcstaking/v1/incentive.proto", fileDescriptor_ac354c3bd6d7a66b) +} + +var fileDescriptor_ac354c3bd6d7a66b = []byte{ + // 467 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x4f, 0x6f, 0xd3, 0x30, + 0x18, 0xc6, 0xeb, 0xee, 0x0f, 0xcc, 0x9d, 0x00, 0x45, 0x20, 0x95, 0x1d, 0xd2, 0x52, 0x69, 0xa8, + 0x07, 0x66, 0xd3, 0x0e, 0x26, 0x4e, 0x08, 0x65, 0xb9, 0x4c, 0x80, 0x14, 0x45, 0xa8, 0x42, 0x5c, + 0x2a, 0xdb, 0x35, 0xa9, 0x95, 0x34, 0x8e, 0x6a, 0x2f, 0x23, 0x47, 0xbe, 0x01, 0x1f, 0x86, 0x0f, + 0xc1, 0x71, 0xe2, 0x84, 0x76, 0x98, 0x50, 0x7b, 0xe2, 0x5b, 0xa0, 0x38, 0x1e, 0x4b, 0x11, 0x50, + 0x71, 0x4a, 0xde, 0x3c, 0xcf, 0xfb, 0xbe, 0x4f, 0x7e, 0x89, 0xe1, 0x3e, 0x25, 0xb4, 0x48, 0x64, + 0x8a, 0xa9, 0x66, 0x4a, 0x93, 0x58, 0xa4, 0x11, 0xce, 0x07, 0x58, 0xa4, 0x8c, 0xa7, 0x5a, 0xe4, + 0x1c, 0x65, 0x73, 0xa9, 0xa5, 0x73, 0xcf, 0xda, 0xd0, 0xb5, 0x0d, 0xe5, 0x83, 0xbd, 0xbb, 0x91, + 0x8c, 0xa4, 0x71, 0xe0, 0xf2, 0xae, 0x32, 0xef, 0xdd, 0x67, 0x52, 0xcd, 0xa4, 0x1a, 0x57, 0x42, + 0x55, 0x58, 0xa9, 0x57, 0x55, 0x98, 0xcd, 0x8b, 0x4c, 0x4b, 0xac, 0x38, 0xcb, 0x86, 0x4f, 0x8f, + 0xe2, 0x01, 0x8e, 0x79, 0x61, 0x3d, 0xbd, 0x8f, 0x00, 0xde, 0x0e, 0xf9, 0x19, 0x99, 0x4f, 0x7c, + 0xa1, 0xf4, 0x31, 0x61, 0x53, 0xee, 0x3c, 0x82, 0x8e, 0x96, 0x9a, 0x24, 0xe3, 0x5c, 0x6a, 0x91, + 0x46, 0xe3, 0x4c, 0x9e, 0xf1, 0x79, 0x1b, 0x74, 0x41, 0x7f, 0x33, 0xbc, 0x63, 0x94, 0x91, 0x11, + 0x82, 0xf2, 0xb9, 0xf3, 0x02, 0xde, 0xa4, 0x9a, 0x8d, 0x73, 0x92, 0xa8, 0x76, 0xb3, 0xbb, 0xd1, + 0x6f, 0x0d, 0xf7, 0xd1, 0x1f, 0x5f, 0x00, 0x79, 0x6f, 0x8e, 0x47, 0x24, 0x29, 0xf7, 0x9c, 0xa4, + 0xef, 0x65, 0x78, 0x83, 0x6a, 0x36, 0x22, 0x89, 0xea, 0xfd, 0x68, 0xc2, 0x5b, 0xab, 0x9a, 0xf3, + 0x1a, 0x6e, 0x97, 0x43, 0xb3, 0xd8, 0xac, 0xdd, 0xf5, 0x8e, 0x2e, 0x2e, 0x3b, 0xc3, 0x48, 0xe8, + 0xe9, 0x29, 0x45, 0x4c, 0xce, 0xb0, 0x5d, 0xc0, 0xa6, 0x44, 0xa4, 0x57, 0x05, 0xd6, 0x45, 0xc6, + 0x15, 0xf2, 0x4e, 0x82, 0xc3, 0x27, 0x8f, 0x83, 0x53, 0xfa, 0x92, 0x17, 0xe1, 0x16, 0xd5, 0x2c, + 0x88, 0x9d, 0xe7, 0x10, 0x5a, 0x53, 0x39, 0xb2, 0xd9, 0x05, 0xfd, 0xd6, 0xb0, 0x83, 0x2c, 0xac, + 0x0a, 0x0f, 0xfa, 0x85, 0x07, 0xd9, 0xde, 0x1d, 0xdb, 0x12, 0xc4, 0xce, 0x5b, 0x08, 0x99, 0x9c, + 0xcd, 0x84, 0x52, 0x42, 0xa6, 0xed, 0x8d, 0x2e, 0xe8, 0xef, 0x78, 0xcf, 0x2e, 0x2e, 0x3b, 0x0f, + 0x6b, 0x91, 0xae, 0x60, 0x9b, 0xcb, 0x81, 0x9a, 0xc4, 0x36, 0x8f, 0xcf, 0xd9, 0xd7, 0xcf, 0x07, + 0xd0, 0x2e, 0xf3, 0x39, 0x0b, 0x6b, 0xb3, 0xfe, 0xc2, 0x7a, 0xf3, 0xdf, 0xac, 0x27, 0x3c, 0x51, + 0xed, 0xad, 0x75, 0xac, 0x7d, 0xbe, 0xca, 0xda, 0xe7, 0x89, 0xea, 0x29, 0x83, 0xba, 0x26, 0xfd, + 0xc6, 0x06, 0xfc, 0x37, 0x9b, 0x07, 0x70, 0x77, 0x25, 0x7b, 0xd3, 0x64, 0x6f, 0xe5, 0xd7, 0xb1, + 0xbd, 0x57, 0x5f, 0x16, 0x2e, 0x38, 0x5f, 0xb8, 0xe0, 0xfb, 0xc2, 0x05, 0x9f, 0x96, 0x6e, 0xe3, + 0x7c, 0xe9, 0x36, 0xbe, 0x2d, 0xdd, 0xc6, 0xbb, 0xb5, 0xdf, 0xf4, 0x43, 0xfd, 0xac, 0x18, 0xa0, + 0x74, 0xdb, 0xfc, 0xb9, 0x87, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb4, 0x48, 0xf9, 0xd9, 0x4e, + 0x03, 0x00, 0x00, +} + +func (m *RewardDistCache) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RewardDistCache) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RewardDistCache) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcVals) > 0 { + for iNdEx := len(m.BtcVals) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcVals[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.TotalVotingPower != 0 { + i = encodeVarintIncentive(dAtA, i, uint64(m.TotalVotingPower)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *BTCValDistInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCValDistInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCValDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcDels) > 0 { + for iNdEx := len(m.BtcDels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcDels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.TotalVotingPower != 0 { + i = encodeVarintIncentive(dAtA, i, uint64(m.TotalVotingPower)) + i-- + dAtA[i] = 0x20 + } + if m.Commission != nil { + { + size := m.Commission.Size() + i -= size + if _, err := m.Commission.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *BTCDelDistInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCDelDistInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCDelDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.VotingPower != 0 { + i = encodeVarintIncentive(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x10 + } + if m.BabylonPk != nil { + { + size, err := m.BabylonPk.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIncentive(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintIncentive(dAtA []byte, offset int, v uint64) int { + offset -= sovIncentive(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RewardDistCache) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TotalVotingPower != 0 { + n += 1 + sovIncentive(uint64(m.TotalVotingPower)) + } + if len(m.BtcVals) > 0 { + for _, e := range m.BtcVals { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + return n +} + +func (m *BTCValDistInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + if m.Commission != nil { + l = m.Commission.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + if m.TotalVotingPower != 0 { + n += 1 + sovIncentive(uint64(m.TotalVotingPower)) + } + if len(m.BtcDels) > 0 { + for _, e := range m.BtcDels { + l = e.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + } + return n +} + +func (m *BTCDelDistInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BabylonPk != nil { + l = m.BabylonPk.Size() + n += 1 + l + sovIncentive(uint64(l)) + } + if m.VotingPower != 0 { + n += 1 + sovIncentive(uint64(m.VotingPower)) + } + return n +} + +func sovIncentive(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozIncentive(x uint64) (n int) { + return sovIncentive(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RewardDistCache) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RewardDistCache: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RewardDistCache: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalVotingPower", wireType) + } + m.TotalVotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalVotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcVals", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcVals = append(m.BtcVals, &BTCValDistInfo{}) + if err := m.BtcVals[len(m.BtcVals)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipIncentive(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIncentive + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BTCValDistInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCValDistInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCValDistInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commission", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_cosmos_cosmos_sdk_types.Dec + m.Commission = &v + if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalVotingPower", wireType) + } + m.TotalVotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalVotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcDels = append(m.BtcDels, &BTCDelDistInfo{}) + if err := m.BtcDels[len(m.BtcDels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipIncentive(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIncentive + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BTCDelDistInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCDelDistInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCDelDistInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonPk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIncentive + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIncentive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BabylonPk == nil { + m.BabylonPk = &secp256k1.PubKey{} + } + if err := m.BabylonPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIncentive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipIncentive(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIncentive + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipIncentive(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowIncentive + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthIncentive + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupIncentive + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthIncentive + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthIncentive = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowIncentive = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupIncentive = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 90c9dc217..0793a56cf 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -15,11 +15,12 @@ const ( ) var ( - ParamsKey = []byte{0x01} // key prefix for the parameters - BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators - BTCDelegationKey = []byte{0x03} // key prefix for the BTC delegations - VotingPowerKey = []byte{0x04} // key prefix for the voting power - BTCHeightKey = []byte{0x05} // key prefix for the BTC heights + ParamsKey = []byte{0x01} // key prefix for the parameters + BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators + BTCDelegationKey = []byte{0x03} // key prefix for the BTC delegations + VotingPowerKey = []byte{0x04} // key prefix for the voting power + BTCHeightKey = []byte{0x05} // key prefix for the BTC heights + RewardDistCacheKey = []byte{0x06} // key prefix for reward distribution cache ) func KeyPrefix(p string) []byte { diff --git a/x/finality/genesis_test.go b/x/finality/genesis_test.go index f3ebab922..e99999e4e 100644 --- a/x/finality/genesis_test.go +++ b/x/finality/genesis_test.go @@ -15,7 +15,7 @@ func TestGenesis(t *testing.T) { Params: types.DefaultParams(), } - k, ctx := keepertest.FinalityKeeper(t, nil) + k, ctx := keepertest.FinalityKeeper(t, nil, nil) finality.InitGenesis(ctx, *k, genesisState) got := finality.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index b3da6f783..9aaee9ca6 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -20,7 +20,7 @@ func FuzzListPublicRandomness(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // add a random list of EOTS public randomness @@ -59,7 +59,7 @@ func FuzzBlock(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) height := datagen.RandomInt(r, 100) @@ -90,7 +90,7 @@ func FuzzListBlocks(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // index a random list of finalised blocks @@ -181,7 +181,7 @@ func FuzzVotesAtHeight(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // Add random number of voted validators to the store @@ -223,7 +223,7 @@ func FuzzQueryEvidence(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // set random BTC SK PK @@ -270,7 +270,7 @@ func FuzzListEvidences(f *testing.F) { r := rand.New(rand.NewSource(seed)) // Setup keeper and context - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) // generate a random list of evidences since startHeight diff --git a/x/finality/keeper/keeper.go b/x/finality/keeper/keeper.go index 4c4a9f6dd..d87b2dd1f 100644 --- a/x/finality/keeper/keeper.go +++ b/x/finality/keeper/keeper.go @@ -20,6 +20,7 @@ type ( accountKeeper types.AccountKeeper bankKeeper types.BankKeeper BTCStakingKeeper types.BTCStakingKeeper + IncentiveKeeper types.IncentiveKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -33,7 +34,8 @@ func NewKeeper( accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, - BTCStakingKeeper types.BTCStakingKeeper, + btctakingKeeper types.BTCStakingKeeper, + incentiveKeeper types.IncentiveKeeper, authority string, ) Keeper { return Keeper{ @@ -43,7 +45,8 @@ func NewKeeper( accountKeeper: accountKeeper, bankKeeper: bankKeeper, - BTCStakingKeeper: BTCStakingKeeper, + BTCStakingKeeper: btctakingKeeper, + IncentiveKeeper: incentiveKeeper, authority: authority, } } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index b90fc85a2..5e24e3447 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -18,7 +18,7 @@ import ( ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { - fKeeper, ctx := keepertest.FinalityKeeper(t, nil) + fKeeper, ctx := keepertest.FinalityKeeper(t, nil, nil) return fKeeper, keeper.NewMsgServerImpl(*fKeeper), sdk.WrapSDKContext(ctx) } @@ -37,7 +37,7 @@ func FuzzCommitPubRandList(f *testing.F) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) ms := keeper.NewMsgServerImpl(*fKeeper) // create a random BTC validator @@ -108,7 +108,7 @@ func FuzzAddFinalitySig(f *testing.F) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) ms := keeper.NewMsgServerImpl(*fKeeper) // create and register a random BTC validator diff --git a/x/finality/keeper/params_test.go b/x/finality/keeper/params_test.go index 418acda55..b34d260d5 100644 --- a/x/finality/keeper/params_test.go +++ b/x/finality/keeper/params_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.FinalityKeeper(t, nil) + k, ctx := testkeeper.FinalityKeeper(t, nil, nil) params := types.DefaultParams() err := k.SetParams(ctx, params) diff --git a/x/finality/keeper/query_params_test.go b/x/finality/keeper/query_params_test.go index 31d552cbf..01ad9a661 100644 --- a/x/finality/keeper/query_params_test.go +++ b/x/finality/keeper/query_params_test.go @@ -10,7 +10,7 @@ import ( ) func TestParamsQuery(t *testing.T) { - keeper, ctx := testkeeper.FinalityKeeper(t, nil) + keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index 6c7938542..ca169152d 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -3,7 +3,6 @@ package keeper import ( "fmt" - bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -66,12 +65,11 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { // for each of these blocks from earliest to latest, tally the block w.r.t. existing votes for i := len(blocksToFinalize) - 1; i >= 0; i-- { blockToFinalize := blocksToFinalize[i] - sigSet := k.GetSigSet(ctx, blockToFinalize.Height) + voterBTCPKs := k.GetVoters(ctx, blockToFinalize.Height) valSet := valSets[blockToFinalize.Height] - if tally(valSet, sigSet) { + if tally(valSet, voterBTCPKs) { // if this block gets >2/3 votes, finalise it - blockToFinalize.Finalized = true - k.SetBlock(ctx, blockToFinalize) + k.finalizeBlock(ctx, blockToFinalize, voterBTCPKs) } else { // if not, then this block and all subsequent blocks should not be finalised // thus, we need to break here @@ -80,13 +78,33 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { } } +// finalizeBlock sets a block to be finalised in KVStore and distributes rewards to +// BTC validators and delegations +func (k Keeper) finalizeBlock(ctx sdk.Context, block *types.IndexedBlock, voterBTCPKs map[string]struct{}) { + // set block to be finalised in KVStore + block.Finalized = true + k.SetBlock(ctx, block) + // distribute rewards to BTC staking stakeholders w.r.t. the reward distribution cache + rdc, err := k.BTCStakingKeeper.GetRewardDistCache(ctx, block.Height) + if err != nil { + // failing to get a reward distribution cache before distributing reward is a programming error + panic(err) + } + // filter out voted BTC validators + rdc.FilterVotedBTCVals(voterBTCPKs) + // reward voted BTC validators + k.IncentiveKeeper.RewardBTCStaking(ctx, block.Height, rdc) + // remove reward distribution cache afterwards + k.BTCStakingKeeper.RemoveRewardDistCache(ctx, block.Height) +} + // tally checks whether a block with the given validator set and votes reaches a quorum or not -func tally(valSet map[string]uint64, sigSet map[string]*bbn.SchnorrEOTSSig) bool { +func tally(valSet map[string]uint64, voterBTCPKs map[string]struct{}) bool { totalPower := uint64(0) votedPower := uint64(0) for pkStr, power := range valSet { totalPower += power - if _, ok := sigSet[pkStr]; ok { + if _, ok := voterBTCPKs[pkStr]; ok { votedPower += power } } diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index cab2015e4..12ba86bc9 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -25,7 +25,8 @@ func FuzzTallying(f *testing.F) { defer ctrl.Finish() bsKeeper := types.NewMockBTCStakingKeeper(ctrl) - fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper) + iKeeper := types.NewMockIncentiveKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) // Case 1: expect to panic if tallying upon BTC staking protocol is not activated bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(0), bstypes.ErrBTCStakingNotActivated).Times(1) @@ -99,6 +100,10 @@ func FuzzTallying(f *testing.F) { require.NoError(t, err) } } + // we don't test incentive in this function + bsKeeper.EXPECT().GetRewardDistCache(gomock.Any(), gomock.Any()).Return(bstypes.NewRewardDistCache(), nil).Times(int(numWithQCs)) + iKeeper.EXPECT().RewardBTCStaking(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) + bsKeeper.EXPECT().RemoveRewardDistCache(gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) // add mock queries to GetBTCStakingActivatedHeight bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) // tally blocks and none of them should be finalised diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index f5447cbdd..167cf805e 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -63,6 +63,30 @@ func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.Schnor return sigs } +// GetVoters gets returns a map of voters' BTC PKs to the given height +func (k Keeper) GetVoters(ctx sdk.Context, height uint64) map[string]struct{} { + store := k.voteStore(ctx, height) + iter := store.Iterator(nil, nil) + defer iter.Close() + + // if there is no vote on this height, return nil + if !iter.Valid() { + return nil + } + + voterBTCPKs := map[string]struct{}{} + for ; iter.Valid(); iter.Next() { + // accumulate voterBTCPKs + valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + if err != nil { + // failing to unmarshal validator BTC PK in KVStore is a programming error + panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) + } + voterBTCPKs[valBTCPK.MarshalHex()] = struct{}{} + } + return voterBTCPKs +} + // voteStore returns the KVStore of the votes // prefix: VoteKey // key: (block height || BTC validator PK) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index 3b4ac9869..f57656676 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -13,6 +13,9 @@ type BTCStakingKeeper interface { GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) + RecordRewardDistCache(ctx sdk.Context) + GetRewardDistCache(ctx sdk.Context, height uint64) (*bstypes.RewardDistCache, error) + RemoveRewardDistCache(ctx sdk.Context, height uint64) } // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -26,3 +29,8 @@ type BankKeeper interface { SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins // Methods imported from bank should be defined here } + +// IncentiveKeeper defines the expected interface needed to distribute rewards. +type IncentiveKeeper interface { + RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.RewardDistCache) +} diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index 3948629f7..dddc22a30 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -66,6 +66,21 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetBTCValidator(ctx, valBTCPK interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCValidator), ctx, valBTCPK) } +// GetRewardDistCache mocks base method. +func (m *MockBTCStakingKeeper) GetRewardDistCache(ctx types0.Context, height uint64) (*types.RewardDistCache, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRewardDistCache", ctx, height) + ret0, _ := ret[0].(*types.RewardDistCache) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRewardDistCache indicates an expected call of GetRewardDistCache. +func (mr *MockBTCStakingKeeperMockRecorder) GetRewardDistCache(ctx, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardDistCache", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetRewardDistCache), ctx, height) +} + // GetVotingPower mocks base method. func (m *MockBTCStakingKeeper) GetVotingPower(ctx types0.Context, valBTCPK []byte, height uint64) uint64 { m.ctrl.T.Helper() @@ -108,6 +123,30 @@ func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).HasBTCValidator), ctx, valBTCPK) } +// RecordRewardDistCache mocks base method. +func (m *MockBTCStakingKeeper) RecordRewardDistCache(ctx types0.Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RecordRewardDistCache", ctx) +} + +// RecordRewardDistCache indicates an expected call of RecordRewardDistCache. +func (mr *MockBTCStakingKeeperMockRecorder) RecordRewardDistCache(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRewardDistCache", reflect.TypeOf((*MockBTCStakingKeeper)(nil).RecordRewardDistCache), ctx) +} + +// RemoveRewardDistCache mocks base method. +func (m *MockBTCStakingKeeper) RemoveRewardDistCache(ctx types0.Context, height uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RemoveRewardDistCache", ctx, height) +} + +// RemoveRewardDistCache indicates an expected call of RemoveRewardDistCache. +func (mr *MockBTCStakingKeeperMockRecorder) RemoveRewardDistCache(ctx, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRewardDistCache", reflect.TypeOf((*MockBTCStakingKeeper)(nil).RemoveRewardDistCache), ctx, height) +} + // SlashBTCValidator mocks base method. func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx types0.Context, valBTCPK []byte) error { m.ctrl.T.Helper() @@ -195,3 +234,38 @@ func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gom mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) } + +// MockIncentiveKeeper is a mock of IncentiveKeeper interface. +type MockIncentiveKeeper struct { + ctrl *gomock.Controller + recorder *MockIncentiveKeeperMockRecorder +} + +// MockIncentiveKeeperMockRecorder is the mock recorder for MockIncentiveKeeper. +type MockIncentiveKeeperMockRecorder struct { + mock *MockIncentiveKeeper +} + +// NewMockIncentiveKeeper creates a new mock instance. +func NewMockIncentiveKeeper(ctrl *gomock.Controller) *MockIncentiveKeeper { + mock := &MockIncentiveKeeper{ctrl: ctrl} + mock.recorder = &MockIncentiveKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIncentiveKeeper) EXPECT() *MockIncentiveKeeperMockRecorder { + return m.recorder +} + +// RewardBTCStaking mocks base method. +func (m *MockIncentiveKeeper) RewardBTCStaking(ctx types0.Context, height uint64, rdc *types.RewardDistCache) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RewardBTCStaking", ctx, height, rdc) +} + +// RewardBTCStaking indicates an expected call of RewardBTCStaking. +func (mr *MockIncentiveKeeperMockRecorder) RewardBTCStaking(ctx, height, rdc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewardBTCStaking", reflect.TypeOf((*MockIncentiveKeeper)(nil).RewardBTCStaking), ctx, height, rdc) +} diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index ba5c09b18..8467bac66 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -1,11 +1,51 @@ package keeper import ( + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/incentive/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) +// RewardBTCStaking distributes rewards to BTC validators/delegations at a given height according +// to the reward distribution cache +// (adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L12-L64) +func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.RewardDistCache) { + gauge, err := k.GetBTCStakingGauge(ctx, height) + if err != nil { + // failing to get a reward gauge at previous height is a programming error + panic(err) + } + // reward each of the BTC validator and its BTC delegations in proportion + for _, btcVal := range rdc.BtcVals { + // get coins that will be allocated to the BTC validator and its BTC delegations + btcValPortion := rdc.GetBTCValPortion(btcVal) + coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) + // reward the BTC validator with commission + coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) + if coinsForCommission.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, btcVal.GetAddress(), coinsForCommission); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + // reward the rest of coins to each BTC delegation proportional to its voting power portion + coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) + for _, btcDel := range btcVal.BtcDels { + btcDelPortion := btcVal.GetBTCDelPortion(btcDel) + coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) + if coinsForDel.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, btcDel.GetAddress(), coinsForDel); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + } + } + + // TODO: handle the change in the gauge due to the truncating operations +} + func (k Keeper) accumulateBTCStakingReward(ctx sdk.Context, btcStakingReward sdk.Coins) { // update BTC staking gauge height := uint64(ctx.BlockHeight()) diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go new file mode 100644 index 000000000..ea2b32ff2 --- /dev/null +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -0,0 +1,66 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzRewardBTCStaking(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock bank keeper + bankKeeper := types.NewMockBankKeeper(ctrl) + + // create incentive keeper + keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, nil, nil) + height := datagen.RandomInt(r, 1000) + ctx = ctx.WithBlockHeight(int64(height)) + + // set a random gauge + gauge := datagen.GenRandomGauge(r) + keeper.SetBTCStakingGauge(ctx, height, gauge) + + // generate a random reward distribution cache + rdc, err := datagen.GenRandomRewardDistCache(r) + require.NoError(t, err) + + // mock transfer to ensure reward is distributed correctly + distributedCoins := sdk.NewCoins() + for _, btcVal := range rdc.BtcVals { + btcValPortion := rdc.GetBTCValPortion(btcVal) + coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) + coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) + if coinsForCommission.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(btcVal.GetAddress()), gomock.Eq(coinsForCommission)).Return(nil).Times(1) + distributedCoins = distributedCoins.Add(coinsForCommission...) + } + coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) + for _, btcDel := range btcVal.BtcDels { + btcDelPortion := btcVal.GetBTCDelPortion(btcDel) + coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) + if coinsForDel.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(btcDel.GetAddress()), gomock.Eq(coinsForDel)).Return(nil).Times(1) + distributedCoins = distributedCoins.Add(coinsForDel...) + } + } + } + + // distribute rewards in the gauge to BTC validators/delegations + keeper.RewardBTCStaking(ctx, height, rdc) + + // assert distributedCoins is a subset of coins in gauge + require.True(t, gauge.Coins.IsAllGTE(distributedCoins)) + }) +} diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index f8535ed73..dd51687ac 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -9,11 +9,14 @@ import ( func NewGauge(coins sdk.Coins) *Gauge { return &Gauge{ - Coins: coins, - DistributedCoins: sdk.NewCoins(), + Coins: coins, } } +func (g *Gauge) GetCoinsPortion(portion math.LegacyDec) sdk.Coins { + return GetCoinsPortion(g.Coins, portion) +} + func NewRewardGauge(coins sdk.Coins) *RewardGauge { return &RewardGauge{ Coins: coins, diff --git a/x/incentive/types/incentive.pb.go b/x/incentive/types/incentive.pb.go index 98ac831b5..304f85dba 100644 --- a/x/incentive/types/incentive.pb.go +++ b/x/incentive/types/incentive.pb.go @@ -31,8 +31,6 @@ type Gauge struct { // coins are coins that have been in the gauge // Can have multiple coin denoms Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` - // distributed_coins are coins that have been distributed already - DistributedCoins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=distributed_coins,json=distributedCoins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"distributed_coins"` } func (m *Gauge) Reset() { *m = Gauge{} } @@ -75,13 +73,6 @@ func (m *Gauge) GetCoins() github_com_cosmos_cosmos_sdk_types.Coins { return nil } -func (m *Gauge) GetDistributedCoins() github_com_cosmos_cosmos_sdk_types.Coins { - if m != nil { - return m.DistributedCoins - } - return nil -} - // RewardGauge is an object that stores rewards distributed to a BTC staking/timestamping stakeholder // code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto type RewardGauge struct { @@ -147,26 +138,24 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/incentive.proto", fileDescriptor_3954bc4942045a7a) } var fileDescriptor_3954bc4942045a7a = []byte{ - // 292 bytes of a gzipped FileDescriptorProto + // 269 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0x45, 0xb0, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x04, 0xa1, 0x4a, 0xf4, 0xe0, 0x12, 0x52, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0x59, 0x7d, 0x10, 0x0b, 0xa2, 0x50, 0x4a, 0x2e, 0x39, 0xbf, 0x38, 0x37, 0xbf, 0x58, 0x3f, 0x29, 0xb1, 0x38, 0x55, 0xbf, 0xcc, 0x30, 0x29, 0xb5, 0x24, 0xd1, 0x50, 0x3f, 0x39, - 0x3f, 0x33, 0x0f, 0x22, 0xaf, 0xf4, 0x84, 0x91, 0x8b, 0xd5, 0x3d, 0xb1, 0x34, 0x3d, 0x55, 0x28, - 0x91, 0x8b, 0x15, 0x24, 0x5e, 0x2c, 0xc1, 0xa8, 0xc0, 0xac, 0xc1, 0x6d, 0x24, 0xa9, 0x07, 0xd1, - 0xa9, 0x07, 0xd2, 0xa9, 0x07, 0xd5, 0xa9, 0xe7, 0x9c, 0x9f, 0x99, 0xe7, 0x64, 0x70, 0xe2, 0x9e, - 0x3c, 0xc3, 0xaa, 0xfb, 0xf2, 0x1a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, - 0xfa, 0x50, 0x6b, 0x20, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0x31, 0x58, - 0x43, 0x71, 0x10, 0xc4, 0x64, 0xa1, 0x0a, 0x2e, 0xc1, 0x94, 0xcc, 0xe2, 0x92, 0xa2, 0xcc, 0xa4, - 0xd2, 0x92, 0xd4, 0x94, 0x78, 0x88, 0x75, 0x4c, 0xd4, 0xb7, 0x4e, 0x00, 0xc9, 0x16, 0xb0, 0x88, - 0xd2, 0x33, 0x46, 0x2e, 0xee, 0xa0, 0xd4, 0xf2, 0xc4, 0xa2, 0x14, 0xba, 0x79, 0xb6, 0x84, 0x8b, - 0xbf, 0x3c, 0xb3, 0x24, 0x23, 0xa5, 0x28, 0xb1, 0x3c, 0x8f, 0x76, 0x5e, 0xe5, 0x83, 0xdb, 0x01, - 0xe6, 0x3b, 0x79, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, - 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x21, 0x92, - 0x99, 0xd0, 0xd4, 0x93, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x57, 0x20, 0xa5, 0x37, 0xb0, - 0x15, 0x49, 0x6c, 0xe0, 0x34, 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x84, 0x8c, 0xb9, - 0x91, 0x02, 0x00, 0x00, + 0x3f, 0x33, 0x0f, 0x22, 0xaf, 0x94, 0xc5, 0xc5, 0xea, 0x9e, 0x58, 0x9a, 0x9e, 0x2a, 0x94, 0xc8, + 0xc5, 0x0a, 0x12, 0x2e, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x92, 0xd4, 0x83, 0x68, 0xd4, + 0x03, 0x69, 0xd4, 0x83, 0x6a, 0xd4, 0x73, 0xce, 0xcf, 0xcc, 0x73, 0x32, 0x38, 0x71, 0x4f, 0x9e, + 0x61, 0xd5, 0x7d, 0x79, 0x8d, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, + 0xa8, 0x2d, 0x10, 0x4a, 0xb7, 0x38, 0x25, 0x5b, 0xbf, 0xa4, 0xb2, 0x20, 0xb5, 0x18, 0xac, 0xa1, + 0x38, 0x08, 0x62, 0xb2, 0xd2, 0x33, 0x46, 0x2e, 0xee, 0xa0, 0xd4, 0xf2, 0xc4, 0xa2, 0x14, 0x7a, + 0x59, 0x29, 0x54, 0xc2, 0xc5, 0x5f, 0x9e, 0x59, 0x92, 0x91, 0x52, 0x94, 0x58, 0x9e, 0x17, 0x0f, + 0xb1, 0x8c, 0x89, 0xfa, 0x96, 0xf1, 0xc1, 0xed, 0x00, 0xf3, 0x9d, 0xbc, 0x4f, 0x3c, 0x92, 0x63, + 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, + 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x10, 0xc9, 0x4c, 0x68, 0x14, 0x26, 0x67, 0x24, 0x66, + 0xe6, 0xc1, 0x38, 0xfa, 0x15, 0x48, 0x91, 0x0e, 0xb6, 0x22, 0x89, 0x0d, 0x1c, 0x51, 0xc6, 0x80, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x83, 0xc5, 0xfb, 0x16, 0x02, 0x00, 0x00, } func (m *Gauge) Marshal() (dAtA []byte, err error) { @@ -189,20 +178,6 @@ func (m *Gauge) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.DistributedCoins) > 0 { - for iNdEx := len(m.DistributedCoins) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.DistributedCoins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintIncentive(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } if len(m.Coins) > 0 { for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { { @@ -294,12 +269,6 @@ func (m *Gauge) Size() (n int) { n += 1 + l + sovIncentive(uint64(l)) } } - if len(m.DistributedCoins) > 0 { - for _, e := range m.DistributedCoins { - l = e.Size() - n += 1 + l + sovIncentive(uint64(l)) - } - } return n } @@ -393,40 +362,6 @@ func (m *Gauge) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DistributedCoins", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowIncentive - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthIncentive - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthIncentive - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DistributedCoins = append(m.DistributedCoins, types.Coin{}) - if err := m.DistributedCoins[len(m.DistributedCoins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipIncentive(dAtA[iNdEx:]) From 09eda7391108fa91026e2907c2a229d79a21aa77 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 8 Sep 2023 11:51:29 +1000 Subject: [PATCH 077/202] incentive: distribute rewards to vigilante submitters/reporters (#75) --- .../btccheckpoint/v1/btccheckpoint.proto | 8 +- test/e2e/btc_timestamping_e2e_test.go | 10 + testutil/datagen/incentive.go | 20 +- x/btccheckpoint/keeper/grpc_query.go | 6 +- x/btccheckpoint/keeper/incentive.go | 44 +++ x/btccheckpoint/keeper/keeper.go | 260 +----------------- x/btccheckpoint/keeper/msg_server.go | 2 +- x/btccheckpoint/keeper/msg_server_test.go | 42 +-- x/btccheckpoint/keeper/submissions.go | 242 ++++++++++++++++ x/btccheckpoint/types/btccheckpoint.pb.go | 132 ++++----- x/btccheckpoint/types/expected_keepers.go | 1 + x/btccheckpoint/types/incentive.go | 47 ++++ x/btccheckpoint/types/mock_keepers.go | 10 + x/btccheckpoint/types/types.go | 4 +- x/incentive/abci.go | 4 +- x/incentive/keeper/btc_staking_gauge_test.go | 2 +- x/incentive/keeper/btc_timestamping_gauge.go | 93 ++++++- .../keeper/btc_timestamping_gauge_test.go | 103 +++++++ x/zoneconcierge/keeper/grpc_query_test.go | 2 +- 19 files changed, 681 insertions(+), 351 deletions(-) create mode 100644 x/btccheckpoint/keeper/incentive.go create mode 100644 x/btccheckpoint/keeper/submissions.go create mode 100644 x/btccheckpoint/types/incentive.go create mode 100644 x/incentive/keeper/btc_timestamping_gauge_test.go diff --git a/proto/babylon/btccheckpoint/v1/btccheckpoint.proto b/proto/babylon/btccheckpoint/v1/btccheckpoint.proto index c1fe3e6ec..9dae49c47 100644 --- a/proto/babylon/btccheckpoint/v1/btccheckpoint.proto +++ b/proto/babylon/btccheckpoint/v1/btccheckpoint.proto @@ -112,11 +112,11 @@ message SubmissionData { // TODO: Add btc blockheight at epooch end, when adding hadnling of epoching // callbacks message EpochData { - // List of all received checkpoints during this epoch, sorted by order of - // submission. - repeated SubmissionKey key = 1; + // keys is the list of all received checkpoints during this epoch, sorted by + // order of submission. + repeated SubmissionKey keys = 1; - // Current btc status of the epoch + // status is the current btc status of the epoch BtcStatus status = 2; } diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go index bf423cda0..a26653b8d 100644 --- a/test/e2e/btc_timestamping_e2e_test.go +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -122,8 +122,18 @@ func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { endEpochNum uint64 = 3 ) + // get balance of submitter/reporter address + // will compare with the balance after finalising some epochs + submitterReporterBalance, err := nonValidatorNode.QueryBalances(nonValidatorNode.PublicAddress) + s.NoError(err) + nonValidatorNode.FinalizeSealedEpochs(startEpochNum, endEpochNum) + // ensure balance has incresed after finalising some epochs + submitterReporterBalance2, err := nonValidatorNode.QueryBalances(nonValidatorNode.PublicAddress) + s.NoError(err) + s.True(submitterReporterBalance2.IsAllGT(submitterReporterBalance)) + endEpoch, err := nonValidatorNode.QueryRawCheckpoint(endEpochNum) s.NoError(err) s.Equal(endEpoch.Status, ct.Finalized) diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index baf93d101..a907b66c7 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -3,6 +3,7 @@ package datagen import ( "math/rand" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" itypes "github.com/babylonchain/babylon/x/incentive/types" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -98,7 +99,7 @@ func GenRandomBTCValDistInfo(r *rand.Rand) (*bstypes.BTCValDistInfo, error) { return btcValDistInfo, nil } -func GenRandomRewardDistCache(r *rand.Rand) (*bstypes.RewardDistCache, error) { +func GenRandomBTCStakingRewardDistCache(r *rand.Rand) (*bstypes.RewardDistCache, error) { rdc := bstypes.NewRewardDistCache() // a random number of BTC validators numBTCVals := RandomInt(r, 10) + 1 @@ -111,3 +112,20 @@ func GenRandomRewardDistCache(r *rand.Rand) (*bstypes.RewardDistCache, error) { } return rdc, nil } + +func GenRandomCheckpointAddressPair(r *rand.Rand) *btcctypes.CheckpointAddressPair { + return &btcctypes.CheckpointAddressPair{ + Submitter: GenRandomAccount().GetAddress(), + Reporter: GenRandomAccount().GetAddress(), + } +} + +func GenRandomBTCTimestampingRewardDistInfo(r *rand.Rand) *btcctypes.RewardDistInfo { + best := GenRandomCheckpointAddressPair(r) + numOthers := RandomInt(r, 10) + others := []*btcctypes.CheckpointAddressPair{} + for i := uint64(0); i < numOthers; i++ { + others = append(others, GenRandomCheckpointAddressPair(r)) + } + return btcctypes.NewRewardDistInfo(best, others...) +} diff --git a/x/btccheckpoint/keeper/grpc_query.go b/x/btccheckpoint/keeper/grpc_query.go index 19c038344..a5c600aeb 100644 --- a/x/btccheckpoint/keeper/grpc_query.go +++ b/x/btccheckpoint/keeper/grpc_query.go @@ -139,7 +139,7 @@ func (k Keeper) EpochSubmissions(c context.Context, req *types.QueryEpochSubmiss epochData := k.GetEpochData(ctx, checkpointEpoch) - if epochData == nil || len(epochData.Key) == 0 { + if epochData == nil || len(epochData.Keys) == 0 { return &types.QueryEpochSubmissionsResponse{ Keys: []*types.SubmissionKey{}, @@ -147,7 +147,7 @@ func (k Keeper) EpochSubmissions(c context.Context, req *types.QueryEpochSubmiss }, nil } - numberOfKeys := uint64(len((epochData.Key))) + numberOfKeys := uint64(len((epochData.Keys))) if offset >= numberOfKeys { // offset larger than number of keys return empty response @@ -164,7 +164,7 @@ func (k Keeper) EpochSubmissions(c context.Context, req *types.QueryEpochSubmiss break } - responseKeys = append(responseKeys, epochData.Key[i]) + responseKeys = append(responseKeys, epochData.Keys[i]) } return &types.QueryEpochSubmissionsResponse{ diff --git a/x/btccheckpoint/keeper/incentive.go b/x/btccheckpoint/keeper/incentive.go new file mode 100644 index 000000000..c1574b788 --- /dev/null +++ b/x/btccheckpoint/keeper/incentive.go @@ -0,0 +1,44 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/btccheckpoint/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// rewardBTCTimestamping finds the (submitter, reporter) pairs of all submissions at the +// given finalised epoch according to the given epoch data, then distribute rewards to them +// by invoking the incentive module +func (k Keeper) rewardBTCTimestamping(ctx sdk.Context, epoch uint64, ed *types.EpochData, bestIdx int) { + var ( + bestSubmissionAddrs *types.CheckpointAddressPair + otherSubmissionAddrs []*types.CheckpointAddressPair + ) + + // iterate over all submission keys to find all submission addresses, including the best one + for i, sk := range ed.Keys { + // retrieve submission data, including vigilante addresses + submissionData := k.GetSubmissionData(ctx, *sk) + if submissionData == nil { + // ignore nil submission data for whatever reason + continue + } + + // get vigilante addresses of this submission + submissionAddrs, err := types.NewCheckpointAddressPair(submissionData.VigilanteAddresses) + if err != nil { + // failing to unmarshal checkpoint address pair in KVStore is a programming error + panic(err) + } + + // assign to best submission or append to other submission according to best submission index + if i == bestIdx { + bestSubmissionAddrs = submissionAddrs + } else { + otherSubmissionAddrs = append(otherSubmissionAddrs, submissionAddrs) + } + } + + // construct reward distribution information and invoke incentive module to distribute rewards + rewardDistInfo := types.NewRewardDistInfo(bestSubmissionAddrs, otherSubmissionAddrs...) + k.incentiveKeeper.RewardBTCTimestamping(ctx, epoch, rewardDistInfo) +} diff --git a/x/btccheckpoint/keeper/keeper.go b/x/btccheckpoint/keeper/keeper.go index e99030d5f..87a3a5b33 100644 --- a/x/btccheckpoint/keeper/keeper.go +++ b/x/btccheckpoint/keeper/keeper.go @@ -3,7 +3,6 @@ package keeper import ( "encoding/hex" "fmt" - "math" "math/big" txformat "github.com/babylonchain/babylon/btctxformatter" @@ -127,143 +126,6 @@ func (k Keeper) headerDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) return uint64(blockDepth), nil } -func (k Keeper) checkSubmissionStatus(ctx sdk.Context, info *types.SubmissionBtcInfo) types.BtcStatus { - subDepth := info.SubmissionDepth() - if subDepth >= k.GetParams(ctx).CheckpointFinalizationTimeout { - return types.Finalized - } else if subDepth >= k.GetParams(ctx).BtcConfirmationDepth { - return types.Confirmed - } else { - return types.Submitted - } -} - -func (k Keeper) GetSubmissionBtcInfo(ctx sdk.Context, sk types.SubmissionKey) (*types.SubmissionBtcInfo, error) { - - var youngestBlockDepth uint64 = math.MaxUint64 - var youngestBlockHash *bbn.BTCHeaderHashBytes - - var lowestIndexInMostFreshBlock uint32 = math.MaxUint32 - - var oldestBlockDepth uint64 = uint64(0) - - for _, tk := range sk.Key { - currentBlockDepth, err := k.headerDepth(ctx, tk.Hash) - - if err != nil { - return nil, err - } - - if currentBlockDepth < youngestBlockDepth { - youngestBlockDepth = currentBlockDepth - lowestIndexInMostFreshBlock = tk.Index - youngestBlockHash = tk.Hash - } - - // This case happens when we have two submissions in the same block. - if currentBlockDepth == youngestBlockDepth && tk.Index < lowestIndexInMostFreshBlock { - // This is something which needs a bit more careful thinking as it is used - // to determine which submission is better. - // Currently if two submissions of one checkpoint are in the same block, - // we pick tx with lower index as the point at which checkpoint happened. - // This is in line with the logic that if two submission are in the same block, - // they are esentially happening at the same time, so it does not really matter - // which index pick, and for possibble tie breaks it is better to pick lower one. - // This means in case when we have: - // Checkpoint submission `x` for epoch 5, both tx in same block at height 100, with indexes 1 and 10 - // and - // Checkpoint submission `y` for epoch 5, both tx in same block at height 100, with indexes 3 and 9 - // we will chose submission `x` as the better one. - // This good enough solution, but it is not perfect and leads to some edge cases like: - // Checkpoint submission `x` for epoch 5, one tx in block 99 with index 1, and second tx in block 100 with index 4 - // and - // Checkpoint submission `y` for epoch 5, both tx in same block at height 100, with indexes 3 and 9 - // In this case submission `y` will be better as it `earliest` tx in most fresh block is first. But at first glance - // submission `x` seems better. - lowestIndexInMostFreshBlock = tk.Index - } - - if currentBlockDepth > oldestBlockDepth { - oldestBlockDepth = currentBlockDepth - } - } - - return &types.SubmissionBtcInfo{ - SubmissionKey: sk, - OldestBlockDepth: oldestBlockDepth, - YoungestBlockDepth: youngestBlockDepth, - YoungestBlockHash: *youngestBlockHash, - YoungestBlockLowestTxIdx: lowestIndexInMostFreshBlock, - }, nil -} - -func (k Keeper) SubmissionExists(ctx sdk.Context, sk types.SubmissionKey) bool { - return k.GetSubmissionData(ctx, sk) != nil -} - -// GetEpochData returns epoch data for given epoch, if there is not epoch data yet returns nil -func (k Keeper) GetEpochData(ctx sdk.Context, e uint64) *types.EpochData { - store := ctx.KVStore(k.storeKey) - bytes := store.Get(types.GetEpochIndexKey(e)) - - // note: Cannot check len(bytes) == 0, as empty bytes encoding of types.EpochData - // is epoch data with Status == Submitted and no valid submissions - if bytes == nil { - return nil - } - - ed := &types.EpochData{} - k.cdc.MustUnmarshal(bytes, ed) - return ed -} - -// GetBestSubmission gets the status and the best submission of a given finalized epoch -func (k Keeper) GetBestSubmission(ctx sdk.Context, epochNumber uint64) (types.BtcStatus, *types.SubmissionKey, error) { - // find the btc checkpoint tx index of this epoch - ed := k.GetEpochData(ctx, epochNumber) - if ed == nil { - return 0, nil, types.ErrNoCheckpointsForPreviousEpoch - } - if ed.Status != types.Finalized { - return 0, nil, fmt.Errorf("epoch %d has not been finalized yet", epochNumber) - } - if len(ed.Key) == 0 { - return 0, nil, types.ErrNoCheckpointsForPreviousEpoch - } - bestSubmissionKey := ed.Key[0] // index of checkpoint tx on BTC - - return ed.Status, bestSubmissionKey, nil -} - -func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *types.EpochData) *types.SubmissionBtcInfo { - // there is no submissions for this epoch, so transitivly there is no best submission - if ed == nil || len(ed.Key) == 0 { - return nil - } - - // There is only one submission for this epoch: - // - either epoch is already finalized and we already chosen the best submission - // - or we only received one submission for this epoch - // Either way, we do not need to decide which submission is the best one. - if len(ed.Key) == 1 { - sk := *ed.Key[0] - btcInfo, err := k.GetSubmissionBtcInfo(ctx, sk) - - if err != nil { - k.Logger(ctx).Debug("Previously stored submission is not valid anymore. Submission key: %+v", sk) - } - - // we only log error, as the only error which we can receive here is that submission - // is not longer on btc canoncial chain, which essentially means that there is no valid submission - return btcInfo - } - - // We have more that one valid submission. We need to chose the best one. - epochSummary := k.getEpochChanges(ctx, nil, ed) - - return epochSummary.EpochBestSubmission -} - // checkAncestors checks if there is at least one ancestor in previous epoch submissions // previous epoch submission is considered ancestor when: // - it is on main chain @@ -288,13 +150,13 @@ func (k Keeper) checkAncestors( return types.ErrNoCheckpointsForPreviousEpoch } - if len(previousEpochData.Key) == 0 { + if len(previousEpochData.Keys) == 0 { return types.ErrNoCheckpointsForPreviousEpoch } var haveDescendant = false - for _, sk := range previousEpochData.Key { + for _, sk := range previousEpochData.Keys { if len(sk.Key) < 2 { panic("Submission key composed of less than 2 transactions keys in database") } @@ -323,91 +185,6 @@ func (k Keeper) checkAncestors( return nil } -func (k Keeper) saveEpochData(ctx sdk.Context, e uint64, ed *types.EpochData) { - store := ctx.KVStore(k.storeKey) - ek := types.GetEpochIndexKey(e) - eb := k.cdc.MustMarshal(ed) - store.Set(ek, eb) -} - -// addEpochSubmission save given submission key and data to database and takes -// car of updating any necessary indexes. -// Provided submmission should be known to btclightclient and all of its blocks -// should be on btc main chaing as viewed by btclightclient -func (k Keeper) addEpochSubmission( - ctx sdk.Context, - epochNum uint64, - sk types.SubmissionKey, - sd types.SubmissionData, -) error { - - ed := k.GetEpochData(ctx, epochNum) - - // TODO: SaveEpochData and SaveSubmission should be done in one transaction. - // Not sure cosmos-sdk has facialities to do it. - // Otherwise it is possible to end up with node which updated submission list - // but did not save submission itself. - - // if ed is nil, it means it is our first submission for this epoch - if ed == nil { - // we do not have any data saved yet - newEd := types.NewEmptyEpochData() - ed = &newEd - } - - if ed.Status == types.Finalized { - // we already finlized given epoch so we do not need any more submissions - // TODO We should probably compare new submmission with the exisiting submission - // which finalized the epoch. As it means we finalized epoch with not the best - // submission possible - return types.ErrEpochAlreadyFinalized - } - - if len(ed.Key) == 0 { - // it is first epoch submission inform checkpointing module about this fact - k.checkpointingKeeper.SetCheckpointSubmitted(ctx, epochNum) - } - - ed.AppendKey(sk) - k.saveEpochData(ctx, epochNum, ed) - k.saveSubmission(ctx, sk, sd) - return nil -} - -func (k Keeper) saveSubmission(ctx sdk.Context, sk types.SubmissionKey, sd types.SubmissionData) { - store := ctx.KVStore(k.storeKey) - kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - sBytes := k.cdc.MustMarshal(&sd) - store.Set(kBytes, sBytes) -} - -func (k Keeper) deleteSubmission(ctx sdk.Context, sk types.SubmissionKey) { - store := ctx.KVStore(k.storeKey) - kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - store.Delete(kBytes) -} - -// GetSubmissionData returns submission data for a given key or nil if there is no data -// under the given key -func (k Keeper) GetSubmissionData(ctx sdk.Context, sk types.SubmissionKey) *types.SubmissionData { - store := ctx.KVStore(k.storeKey) - kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - sdBytes := store.Get(kBytes) - - if len(sdBytes) == 0 { - return nil - } - - var sd types.SubmissionData - k.cdc.MustUnmarshal(sdBytes, &sd) - return &sd -} - -// Callback to be called when btc light client tip changes -func (k Keeper) OnTipChange(ctx sdk.Context) { - k.checkCheckpoints(ctx) -} - func (k Keeper) setBtcLightClientUpdated(ctx sdk.Context) { store := ctx.TransientStore(k.tstoreKey) store.Set(types.GetBtcLightClientUpdatedKey(), []byte{1}) @@ -449,7 +226,7 @@ func (k Keeper) getEpochChanges( var currentEpochBestSubmission *types.SubmissionBtcInfo var bestSubmissionIdx int - for i, sk := range ed.Key { + for i, sk := range ed.Keys { sk := sk if len(sk.Key) < 2 { panic("Submission key composed of less than 2 transactions keys in database") @@ -497,16 +274,9 @@ func (k Keeper) getEpochChanges( } } -func (k Keeper) clearEpochData( - ctx sdk.Context, - epoch []byte, - epochDataStore prefix.Store, - currentEpoch *types.EpochData) { - for _, sk := range currentEpoch.Key { - k.deleteSubmission(ctx, *sk) - } - currentEpoch.Key = []*types.SubmissionKey{} - epochDataStore.Set(epoch, k.cdc.MustMarshal(currentEpoch)) +// OnTipChange is the callback function to be called when btc light client tip changes +func (k Keeper) OnTipChange(ctx sdk.Context) { + k.checkCheckpoints(ctx) } // checkCheckpoints is the main function checking status of all submissions @@ -565,7 +335,7 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { k.cdc.MustUnmarshal(it.Value(), ¤tEpoch) epoch := sdk.BigEndianToUint64(it.Key()) - if len(currentEpoch.Key) == 0 { + if len(currentEpoch.Keys) == 0 { // current epoch does not have any submissions, so following one should also // not have any submissions, stop the processing. break @@ -574,11 +344,11 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { if currentEpoch.Status == types.Finalized { // current epoch is already finalized. This is our first epoch in iteration // just set parent info - if len(currentEpoch.Key) != 1 { + if len(currentEpoch.Keys) != 1 { panic("Finalized epoch must have only one valid submission") } - subInfo, err := k.GetSubmissionBtcInfo(ctx, *currentEpoch.Key[0]) + subInfo, err := k.GetSubmissionBtcInfo(ctx, *currentEpoch.Keys[0]) if err != nil { panic("Finalized epoch submission must be on main chain") @@ -644,22 +414,22 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { } if currentEpoch.Status == types.Finalized { - // TODO(incentive): trigger incentive module to distribute rewards to submitters/reporters - - for i, sk := range currentEpoch.Key { - // delete all submissions except best one + // trigger incentive module to distribute rewards to submitters/reporters + k.rewardBTCTimestamping(ctx, epoch, ¤tEpoch, epochChanges.BestSubmissionIdx) + // delete all submissions except best one + for i, sk := range currentEpoch.Keys { if i != epochChanges.BestSubmissionIdx { k.deleteSubmission(ctx, *sk) } } // leave only best submission key - currentEpoch.Key = []*types.SubmissionKey{&epochChanges.EpochBestSubmission.SubmissionKey} + currentEpoch.Keys = []*types.SubmissionKey{&epochChanges.EpochBestSubmission.SubmissionKey} } else { // apply changes to epoch according to changes for _, sk := range epochChanges.SubmissionsToDelete { k.deleteSubmission(ctx, *sk) } - currentEpoch.Key = epochChanges.SubmissionsToKeep + currentEpoch.Keys = epochChanges.SubmissionsToKeep } parentEpochInfo = &epochInfo{bestSubmission: epochChanges.EpochBestSubmission} diff --git a/x/btccheckpoint/keeper/msg_server.go b/x/btccheckpoint/keeper/msg_server.go index 08906227e..d6d1605d3 100644 --- a/x/btccheckpoint/keeper/msg_server.go +++ b/x/btccheckpoint/keeper/msg_server.go @@ -37,7 +37,7 @@ func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBT submissionKey := rawSubmission.GetSubmissionKey() - if m.k.SubmissionExists(sdkCtx, submissionKey) { + if m.k.HasSubmission(sdkCtx, submissionKey) { return nil, types.ErrDuplicatedSubmission } diff --git a/x/btccheckpoint/keeper/msg_server_test.go b/x/btccheckpoint/keeper/msg_server_test.go index 212496b2e..c05ef5698 100644 --- a/x/btccheckpoint/keeper/msg_server_test.go +++ b/x/btccheckpoint/keeper/msg_server_test.go @@ -8,8 +8,6 @@ import ( "testing" "time" - "github.com/babylonchain/babylon/testutil/datagen" - dg "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -25,6 +23,7 @@ type TestKeepers struct { Ctx context.Context BTCLightClient *btcctypes.MockBTCLightClientKeeper Checkpointing *btcctypes.MockCheckpointingKeeper + Incentive *btcctypes.MockIncentiveKeeper BTCCheckpoint *bkeeper.Keeper MsgSrv btcctypes.MsgServer } @@ -50,10 +49,10 @@ func InitTestKeepers( t *testing.T, ) *TestKeepers { lc := btcctypes.NewMockBTCLightClientKeeper() - cc := btcctypes.NewMockCheckpointingKeeper() + ic := btcctypes.NewMockIncentiveKeeper() - k, ctx := keepertest.NewBTCCheckpointKeeper(t, lc, cc, nil, chaincfg.SimNetParams.PowLimit) + k, ctx := keepertest.NewBTCCheckpointKeeper(t, lc, cc, ic, chaincfg.SimNetParams.PowLimit) srv := bkeeper.NewMsgServerImpl(*k) @@ -62,6 +61,7 @@ func InitTestKeepers( Ctx: sdk.WrapSDKContext(ctx), BTCLightClient: lc, Checkpointing: cc, + Incentive: ic, BTCCheckpoint: k, MsgSrv: srv, } @@ -201,7 +201,7 @@ func TestSubmitValidNewCheckpoint(t *testing.T) { ed := tk.GetEpochData(epoch) - if len(ed.Key) == 0 { + if len(ed.Keys) == 0 { t.Errorf("There should be at least one key in epoch %d", epoch) } @@ -209,7 +209,7 @@ func TestSubmitValidNewCheckpoint(t *testing.T) { t.Errorf("Epoch should be in submitted state after processing message") } - submissionKey := ed.Key[0] + submissionKey := ed.Keys[0] submissionData := tk.getSubmissionData(*submissionKey) @@ -240,7 +240,7 @@ func TestSubmitValidNewCheckpoint(t *testing.T) { // TODO Add custom equal fo submission key and transaction key to check // it is expected key - if len(ed1.Key) == 0 { + if len(ed1.Keys) == 0 { t.Errorf("Unexpected missing unconfirmed submissions") } } @@ -389,7 +389,7 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { // all 3 epoch must have two submissions ed := tk.GetEpochData(uint64(i)) require.NotNil(t, ed) - require.Len(t, ed.Key, 2) + require.Len(t, ed.Keys, 2) require.EqualValues(t, ed.Status, btcctypes.Submitted) } @@ -406,10 +406,10 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { if i == 1 { // forked submission got pruned - require.Len(t, ed.Key, 1) + require.Len(t, ed.Keys, 1) } else { // other submissions still have parent so they are left intact - require.Len(t, ed.Key, 2) + require.Len(t, ed.Keys, 2) } require.EqualValues(t, ed.Status, btcctypes.Submitted) } @@ -424,7 +424,7 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { // all 3 epoch must have two submissions ed := tk.GetEpochData(uint64(i)) require.NotNil(t, ed) - require.Len(t, ed.Key, 0) + require.Len(t, ed.Keys, 0) require.EqualValues(t, ed.Status, btcctypes.Submitted) } } @@ -455,7 +455,7 @@ func TestLeaveOnlyBestSubmissionWhenEpochFinalized(t *testing.T) { ed := tk.GetEpochData(uint64(1)) require.NotNil(t, ed) - require.Len(t, ed.Key, 3) + require.Len(t, ed.Keys, 3) // deepest submission is submission in msg3 tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(wDeep)) @@ -469,10 +469,10 @@ func TestLeaveOnlyBestSubmissionWhenEpochFinalized(t *testing.T) { ed = tk.GetEpochData(uint64(1)) require.NotNil(t, ed) - require.Len(t, ed.Key, 1) + require.Len(t, ed.Keys, 1) require.Equal(t, ed.Status, btcctypes.Finalized) - finalSubKey := ed.Key[0] + finalSubKey := ed.Keys[0] require.Equal(t, finalSubKey.Key[0].Hash, b1Hash(msg3)) require.Equal(t, finalSubKey.Key[1].Hash, b2Hash(msg3)) @@ -498,7 +498,7 @@ func TestTxIdxShouldBreakTies(t *testing.T) { ed := tk.GetEpochData(uint64(1)) require.NotNil(t, ed) - require.Len(t, ed.Key, 2) + require.Len(t, ed.Keys, 2) // Both submissions have the same depth the most fresh block i.e // it is the same block @@ -512,9 +512,9 @@ func TestTxIdxShouldBreakTies(t *testing.T) { ed = tk.GetEpochData(uint64(1)) require.NotNil(t, ed) - require.Len(t, ed.Key, 1) + require.Len(t, ed.Keys, 1) require.Equal(t, ed.Status, btcctypes.Finalized) - finalSubKey := ed.Key[0] + finalSubKey := ed.Keys[0] // There is small chance that we can draw the same transactions indexes, which // cannot happend in real life i.e in real life if block has the same depth @@ -555,7 +555,7 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { // TODO customs Equality for submission keys ed := tk.GetEpochData(epoch) - if len(ed.Key) != 1 { + if len(ed.Keys) != 1 { t.Errorf("Unexpected missing submissions") } @@ -573,7 +573,7 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { // we are looking for ed = tk.GetEpochData(epoch) - if len(ed.Key) != 1 { + if len(ed.Keys) != 1 { t.Errorf("Unexpected missing submission") } @@ -594,7 +594,7 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { } func FuzzConfirmAndDinalizeManyEpochs(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 20) + dg.AddRandomSeedsToFuzzer(f, 20) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -682,7 +682,7 @@ func FuzzConfirmAndDinalizeManyEpochs(f *testing.F) { if epoch <= uint64(numFinalizedEpochs) { require.Equal(t, ed.Status, btcctypes.Finalized) // finalized epochs should have only best submission - require.Equal(t, len(ed.Key), 1) + require.Equal(t, len(ed.Keys), 1) } else if epoch <= uint64(numFinalizedEpochs+numConfirmedEpochs) { require.Equal(t, ed.Status, btcctypes.Confirmed) } else { diff --git a/x/btccheckpoint/keeper/submissions.go b/x/btccheckpoint/keeper/submissions.go new file mode 100644 index 000000000..2a5bf29fa --- /dev/null +++ b/x/btccheckpoint/keeper/submissions.go @@ -0,0 +1,242 @@ +package keeper + +import ( + "fmt" + "math" + + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btccheckpoint/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) HasSubmission(ctx sdk.Context, sk types.SubmissionKey) bool { + store := ctx.KVStore(k.storeKey) + kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) + return store.Has(kBytes) +} + +// GetBestSubmission gets the status and the best submission of a given finalized epoch +func (k Keeper) GetBestSubmission(ctx sdk.Context, epochNumber uint64) (types.BtcStatus, *types.SubmissionKey, error) { + // find the btc checkpoint tx index of this epoch + ed := k.GetEpochData(ctx, epochNumber) + if ed == nil { + return 0, nil, types.ErrNoCheckpointsForPreviousEpoch + } + if ed.Status != types.Finalized { + return 0, nil, fmt.Errorf("epoch %d has not been finalized yet", epochNumber) + } + if len(ed.Keys) == 0 { + return 0, nil, types.ErrNoCheckpointsForPreviousEpoch + } + bestSubmissionKey := ed.Keys[0] // index of checkpoint tx on BTC + + return ed.Status, bestSubmissionKey, nil +} + +// addEpochSubmission save given submission key and data to database and takes +// car of updating any necessary indexes. +// Provided submmission should be known to btclightclient and all of its blocks +// should be on btc main chaing as viewed by btclightclient +func (k Keeper) addEpochSubmission( + ctx sdk.Context, + epochNum uint64, + sk types.SubmissionKey, + sd types.SubmissionData, +) error { + + ed := k.GetEpochData(ctx, epochNum) + + // TODO: SaveEpochData and SaveSubmission should be done in one transaction. + // Not sure cosmos-sdk has facialities to do it. + // Otherwise it is possible to end up with node which updated submission list + // but did not save submission itself. + + // if ed is nil, it means it is our first submission for this epoch + if ed == nil { + // we do not have any data saved yet + newEd := types.NewEmptyEpochData() + ed = &newEd + } + + if ed.Status == types.Finalized { + // we already finlized given epoch so we do not need any more submissions + // TODO We should probably compare new submmission with the exisiting submission + // which finalized the epoch. As it means we finalized epoch with not the best + // submission possible + return types.ErrEpochAlreadyFinalized + } + + if len(ed.Keys) == 0 { + // it is first epoch submission inform checkpointing module about this fact + k.checkpointingKeeper.SetCheckpointSubmitted(ctx, epochNum) + } + + ed.AppendKey(sk) + k.saveEpochData(ctx, epochNum, ed) + k.saveSubmission(ctx, sk, sd) + return nil +} + +func (k Keeper) saveSubmission(ctx sdk.Context, sk types.SubmissionKey, sd types.SubmissionData) { + store := ctx.KVStore(k.storeKey) + kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) + sBytes := k.cdc.MustMarshal(&sd) + store.Set(kBytes, sBytes) +} + +func (k Keeper) deleteSubmission(ctx sdk.Context, sk types.SubmissionKey) { + store := ctx.KVStore(k.storeKey) + kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) + store.Delete(kBytes) +} + +// GetSubmissionData returns submission data for a given key or nil if there is no data +// under the given key +func (k Keeper) GetSubmissionData(ctx sdk.Context, sk types.SubmissionKey) *types.SubmissionData { + store := ctx.KVStore(k.storeKey) + kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) + sdBytes := store.Get(kBytes) + + if len(sdBytes) == 0 { + return nil + } + + var sd types.SubmissionData + k.cdc.MustUnmarshal(sdBytes, &sd) + return &sd +} + +func (k Keeper) checkSubmissionStatus(ctx sdk.Context, info *types.SubmissionBtcInfo) types.BtcStatus { + subDepth := info.SubmissionDepth() + if subDepth >= k.GetParams(ctx).CheckpointFinalizationTimeout { + return types.Finalized + } else if subDepth >= k.GetParams(ctx).BtcConfirmationDepth { + return types.Confirmed + } else { + return types.Submitted + } +} + +func (k Keeper) GetSubmissionBtcInfo(ctx sdk.Context, sk types.SubmissionKey) (*types.SubmissionBtcInfo, error) { + + var youngestBlockDepth uint64 = math.MaxUint64 + var youngestBlockHash *bbn.BTCHeaderHashBytes + + var lowestIndexInMostFreshBlock uint32 = math.MaxUint32 + + var oldestBlockDepth uint64 = uint64(0) + + for _, tk := range sk.Key { + currentBlockDepth, err := k.headerDepth(ctx, tk.Hash) + + if err != nil { + return nil, err + } + + if currentBlockDepth < youngestBlockDepth { + youngestBlockDepth = currentBlockDepth + lowestIndexInMostFreshBlock = tk.Index + youngestBlockHash = tk.Hash + } + + // This case happens when we have two submissions in the same block. + if currentBlockDepth == youngestBlockDepth && tk.Index < lowestIndexInMostFreshBlock { + // This is something which needs a bit more careful thinking as it is used + // to determine which submission is better. + // Currently if two submissions of one checkpoint are in the same block, + // we pick tx with lower index as the point at which checkpoint happened. + // This is in line with the logic that if two submission are in the same block, + // they are esentially happening at the same time, so it does not really matter + // which index pick, and for possibble tie breaks it is better to pick lower one. + // This means in case when we have: + // Checkpoint submission `x` for epoch 5, both tx in same block at height 100, with indexes 1 and 10 + // and + // Checkpoint submission `y` for epoch 5, both tx in same block at height 100, with indexes 3 and 9 + // we will chose submission `x` as the better one. + // This good enough solution, but it is not perfect and leads to some edge cases like: + // Checkpoint submission `x` for epoch 5, one tx in block 99 with index 1, and second tx in block 100 with index 4 + // and + // Checkpoint submission `y` for epoch 5, both tx in same block at height 100, with indexes 3 and 9 + // In this case submission `y` will be better as it `earliest` tx in most fresh block is first. But at first glance + // submission `x` seems better. + lowestIndexInMostFreshBlock = tk.Index + } + + if currentBlockDepth > oldestBlockDepth { + oldestBlockDepth = currentBlockDepth + } + } + + return &types.SubmissionBtcInfo{ + SubmissionKey: sk, + OldestBlockDepth: oldestBlockDepth, + YoungestBlockDepth: youngestBlockDepth, + YoungestBlockHash: *youngestBlockHash, + YoungestBlockLowestTxIdx: lowestIndexInMostFreshBlock, + }, nil +} + +func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *types.EpochData) *types.SubmissionBtcInfo { + // there is no submissions for this epoch, so transitivly there is no best submission + if ed == nil || len(ed.Keys) == 0 { + return nil + } + + // There is only one submission for this epoch: + // - either epoch is already finalized and we already chosen the best submission + // - or we only received one submission for this epoch + // Either way, we do not need to decide which submission is the best one. + if len(ed.Keys) == 1 { + sk := *ed.Keys[0] + btcInfo, err := k.GetSubmissionBtcInfo(ctx, sk) + + if err != nil { + k.Logger(ctx).Debug("Previously stored submission is not valid anymore. Submission key: %+v", sk) + } + + // we only log error, as the only error which we can receive here is that submission + // is not longer on btc canoncial chain, which essentially means that there is no valid submission + return btcInfo + } + + // We have more that one valid submission. We need to chose the best one. + epochSummary := k.getEpochChanges(ctx, nil, ed) + + return epochSummary.EpochBestSubmission +} + +// GetEpochData returns epoch data for given epoch, if there is not epoch data yet returns nil +func (k Keeper) GetEpochData(ctx sdk.Context, e uint64) *types.EpochData { + store := ctx.KVStore(k.storeKey) + bytes := store.Get(types.GetEpochIndexKey(e)) + + // note: Cannot check len(bytes) == 0, as empty bytes encoding of types.EpochData + // is epoch data with Status == Submitted and no valid submissions + if bytes == nil { + return nil + } + + ed := &types.EpochData{} + k.cdc.MustUnmarshal(bytes, ed) + return ed +} + +func (k Keeper) saveEpochData(ctx sdk.Context, e uint64, ed *types.EpochData) { + store := ctx.KVStore(k.storeKey) + ek := types.GetEpochIndexKey(e) + eb := k.cdc.MustMarshal(ed) + store.Set(ek, eb) +} + +func (k Keeper) clearEpochData( + ctx sdk.Context, + epoch []byte, + epochDataStore prefix.Store, + currentEpoch *types.EpochData) { + for _, sk := range currentEpoch.Keys { + k.deleteSubmission(ctx, *sk) + } + currentEpoch.Keys = []*types.SubmissionKey{} + epochDataStore.Set(epoch, k.cdc.MustMarshal(currentEpoch)) +} diff --git a/x/btccheckpoint/types/btccheckpoint.pb.go b/x/btccheckpoint/types/btccheckpoint.pb.go index e18ce0ac3..23f50d154 100644 --- a/x/btccheckpoint/types/btccheckpoint.pb.go +++ b/x/btccheckpoint/types/btccheckpoint.pb.go @@ -394,10 +394,10 @@ func (m *SubmissionData) GetEpoch() uint64 { // TODO: Add btc blockheight at epooch end, when adding hadnling of epoching // callbacks type EpochData struct { - // List of all received checkpoints during this epoch, sorted by order of - // submission. - Key []*SubmissionKey `protobuf:"bytes,1,rep,name=key,proto3" json:"key,omitempty"` - // Current btc status of the epoch + // keys is the list of all received checkpoints during this epoch, sorted by + // order of submission. + Keys []*SubmissionKey `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"` + // status is the current btc status of the epoch Status BtcStatus `protobuf:"varint,2,opt,name=status,proto3,enum=babylon.btccheckpoint.v1.BtcStatus" json:"status,omitempty"` } @@ -434,9 +434,9 @@ func (m *EpochData) XXX_DiscardUnknown() { var xxx_messageInfo_EpochData proto.InternalMessageInfo -func (m *EpochData) GetKey() []*SubmissionKey { +func (m *EpochData) GetKeys() []*SubmissionKey { if m != nil { - return m.Key + return m.Keys } return nil } @@ -601,58 +601,58 @@ func init() { } var fileDescriptor_e096cac78d49b0a6 = []byte{ - // 809 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4b, 0x6f, 0xea, 0x46, - 0x14, 0x66, 0x80, 0xdc, 0x5e, 0x06, 0xc2, 0x4d, 0x87, 0xdc, 0xca, 0x42, 0x91, 0x2f, 0xd7, 0x95, - 0x1a, 0x52, 0xb5, 0xa0, 0xa4, 0xad, 0x94, 0x3e, 0x36, 0x31, 0x0f, 0x81, 0x92, 0x40, 0x64, 0x9c, - 0x2e, 0xb2, 0xa8, 0x65, 0x9b, 0x01, 0x8f, 0x00, 0x0f, 0xf2, 0x0c, 0x08, 0xba, 0x6a, 0x55, 0x55, - 0xaa, 0xba, 0xaa, 0xba, 0xef, 0xaa, 0x7f, 0xa6, 0x8b, 0x2e, 0xb2, 0xac, 0xb2, 0x88, 0xaa, 0xe4, - 0x1f, 0x74, 0xdb, 0x4d, 0xe5, 0xb1, 0xc3, 0x2b, 0xa1, 0x2d, 0xd2, 0xdd, 0x71, 0xce, 0x7c, 0xe7, - 0xf1, 0x7d, 0xe7, 0x1c, 0x0c, 0x3f, 0xb0, 0x4c, 0x6b, 0xda, 0xa7, 0x6e, 0xd1, 0xe2, 0xb6, 0xed, - 0x60, 0xbb, 0x37, 0xa4, 0xc4, 0xe5, 0xc5, 0xf1, 0xe1, 0xb2, 0xa3, 0x30, 0xf4, 0x28, 0xa7, 0x48, - 0x0a, 0xd1, 0x85, 0xe5, 0xc7, 0xf1, 0x61, 0x76, 0xb7, 0x4b, 0xbb, 0x54, 0x80, 0x8a, 0xfe, 0xaf, - 0x00, 0xaf, 0xfc, 0x0d, 0x60, 0x52, 0xd5, 0x4b, 0xad, 0xe1, 0xf8, 0xc2, 0xa3, 0xb4, 0x83, 0xf6, - 0xe1, 0x0b, 0x8b, 0xdb, 0x06, 0xf7, 0x4c, 0x97, 0x99, 0x36, 0x27, 0xd4, 0x95, 0x40, 0x0e, 0xe4, - 0x53, 0x5a, 0xda, 0xe2, 0xb6, 0x3e, 0xf7, 0xa2, 0x23, 0xf8, 0x72, 0x05, 0x68, 0x10, 0xb7, 0x8d, - 0x27, 0x52, 0x34, 0x07, 0xf2, 0xdb, 0x5a, 0x66, 0x19, 0x5e, 0xf7, 0x9f, 0xd0, 0x6b, 0x98, 0x1a, - 0x60, 0xaf, 0xd7, 0xc7, 0x86, 0x4b, 0xdb, 0x98, 0x49, 0x31, 0x91, 0x39, 0x19, 0xf8, 0x1a, 0xbe, - 0x0b, 0xf5, 0xe1, 0x4b, 0x9b, 0xba, 0x1d, 0xe2, 0x0d, 0x88, 0xdb, 0x35, 0xfc, 0x0a, 0x0e, 0x36, - 0xdb, 0xd8, 0x93, 0xe2, 0x3e, 0x56, 0x3d, 0xbe, 0xb9, 0x7d, 0xf5, 0x71, 0x97, 0x70, 0x67, 0x64, - 0x15, 0x6c, 0x3a, 0x28, 0x86, 0x6c, 0x6d, 0xc7, 0x24, 0xee, 0x83, 0x51, 0xe4, 0xd3, 0x21, 0x66, - 0x05, 0x55, 0x2f, 0xd5, 0x44, 0xa8, 0x3a, 0xe5, 0x98, 0x69, 0x99, 0x79, 0x5a, 0x95, 0xdb, 0xc1, - 0x8b, 0x32, 0x81, 0xe9, 0x85, 0x26, 0x4f, 0xf1, 0x14, 0xed, 0xc2, 0xad, 0x80, 0x06, 0x10, 0x34, - 0x02, 0x03, 0x5d, 0xc0, 0xb8, 0x63, 0x32, 0x47, 0x70, 0x4b, 0xa9, 0x5f, 0xdc, 0xdc, 0xbe, 0x3a, - 0xde, 0xb0, 0x89, 0x9a, 0xc9, 0x9c, 0xa0, 0x11, 0x91, 0x49, 0x39, 0x85, 0xdb, 0xad, 0x91, 0x35, - 0x20, 0x8c, 0x85, 0x85, 0x3f, 0x83, 0xb1, 0x1e, 0x9e, 0x4a, 0x20, 0x17, 0xcb, 0x27, 0x8f, 0xf2, - 0x85, 0x75, 0x63, 0x2c, 0x2c, 0xf7, 0xab, 0xf9, 0x41, 0xca, 0xf7, 0x00, 0xbe, 0x58, 0x12, 0xbb, - 0x43, 0xe7, 0xf9, 0xc0, 0xc6, 0xf9, 0x50, 0x0e, 0x26, 0x17, 0x17, 0x20, 0x1a, 0x8c, 0x69, 0xc1, - 0xe5, 0xcb, 0x34, 0xf4, 0xf7, 0x25, 0x1c, 0x61, 0x60, 0x28, 0xbf, 0x03, 0x98, 0x9e, 0xb3, 0x2a, - 0x9b, 0xdc, 0x44, 0x5f, 0xc1, 0xcc, 0x98, 0x74, 0x49, 0xdf, 0x74, 0x39, 0x36, 0xcc, 0x76, 0xdb, - 0xc3, 0x8c, 0x61, 0x16, 0xb6, 0xf5, 0xe1, 0xfa, 0xb6, 0x4a, 0x33, 0xeb, 0xe4, 0x21, 0x48, 0x43, - 0xb3, 0x4c, 0x33, 0x1f, 0x2a, 0xc3, 0xe7, 0x7c, 0xc2, 0x0c, 0xe2, 0x76, 0xa8, 0x14, 0x15, 0xda, - 0x1d, 0xfc, 0x2f, 0xae, 0xbe, 0x46, 0xda, 0x5b, 0x7c, 0xc2, 0x84, 0x58, 0xbb, 0x70, 0x0b, 0x0f, - 0xa9, 0xed, 0x08, 0x3a, 0x71, 0x2d, 0x30, 0x94, 0xef, 0x00, 0x4c, 0x54, 0xfc, 0x5f, 0x82, 0xc9, - 0xa7, 0x8b, 0x03, 0xda, 0x5f, 0x5f, 0x64, 0x69, 0xac, 0x81, 0x9e, 0x9f, 0xc3, 0x67, 0x8c, 0x9b, - 0x7c, 0xc4, 0x84, 0x94, 0xe9, 0xa3, 0x77, 0xd7, 0x47, 0xab, 0xdc, 0x6e, 0x09, 0xa8, 0x16, 0x86, - 0x28, 0x4d, 0x98, 0x79, 0x42, 0x0c, 0xb4, 0x07, 0x13, 0xcc, 0xaf, 0xc4, 0x39, 0xf6, 0xc2, 0x13, - 0x9d, 0x3b, 0x50, 0x16, 0x3e, 0xf7, 0xf0, 0x90, 0x7a, 0xfe, 0x63, 0x30, 0xbe, 0x99, 0xad, 0xfc, - 0x15, 0x83, 0x6f, 0xab, 0x7a, 0x69, 0x9e, 0x54, 0x48, 0xf0, 0x1a, 0xa6, 0x04, 0x6b, 0xc3, 0x1d, - 0x0d, 0xac, 0x30, 0x65, 0x5c, 0x4b, 0x0a, 0x5f, 0x43, 0xb8, 0x50, 0x15, 0xe6, 0x2c, 0xcc, 0xb8, - 0xc1, 0x66, 0x0c, 0xc5, 0x81, 0x5a, 0x7d, 0x6a, 0xf7, 0x0c, 0x07, 0x93, 0xae, 0xc3, 0x45, 0xb1, - 0xb8, 0xb6, 0xe7, 0xe3, 0xe6, 0x42, 0xa8, 0xdc, 0x56, 0x7d, 0x50, 0x4d, 0x60, 0xd0, 0x37, 0x00, - 0xca, 0xff, 0x92, 0xc8, 0x3f, 0xb4, 0xd8, 0x1b, 0x38, 0xb4, 0xec, 0x9a, 0x26, 0x4c, 0xe6, 0xa0, - 0x1e, 0xdc, 0x5b, 0xed, 0x60, 0x61, 0xbd, 0x99, 0x14, 0xdf, 0x74, 0x95, 0x56, 0x8a, 0x2d, 0x3c, - 0x33, 0xf4, 0x2d, 0x80, 0xef, 0xad, 0x56, 0x7b, 0x74, 0x14, 0x46, 0x9f, 0x30, 0x2e, 0x6d, 0x89, - 0xba, 0x1b, 0xde, 0x85, 0xb2, 0x5c, 0xfb, 0xcb, 0x95, 0x2b, 0x39, 0x23, 0x8c, 0xbf, 0xff, 0x33, - 0x80, 0x89, 0xd9, 0x6e, 0xa1, 0x03, 0xf8, 0x4e, 0xe5, 0xa2, 0x59, 0xaa, 0x19, 0x2d, 0xfd, 0x44, - 0xbf, 0x6c, 0x19, 0xad, 0x4b, 0xf5, 0xbc, 0xae, 0xeb, 0x95, 0xf2, 0x4e, 0x24, 0xbb, 0xfd, 0xe3, - 0x2f, 0xb9, 0x44, 0x2b, 0xdc, 0xa4, 0xf6, 0x23, 0x68, 0xa9, 0xd9, 0xa8, 0xd6, 0xb5, 0xf3, 0x4a, - 0x79, 0x07, 0x04, 0xd0, 0x52, 0xf0, 0xbf, 0xfa, 0x04, 0xb4, 0x5a, 0x6f, 0x9c, 0x9c, 0xd5, 0xaf, - 0x2a, 0xe5, 0x9d, 0x68, 0x00, 0xad, 0x12, 0xd7, 0xec, 0x93, 0xaf, 0x71, 0x3b, 0x1b, 0xff, 0xe1, - 0x57, 0x39, 0xa2, 0x36, 0x7f, 0xbb, 0x93, 0xc1, 0xf5, 0x9d, 0x0c, 0xfe, 0xbc, 0x93, 0xc1, 0x4f, - 0xf7, 0x72, 0xe4, 0xfa, 0x5e, 0x8e, 0xfc, 0x71, 0x2f, 0x47, 0xae, 0x3e, 0xf9, 0xaf, 0xa9, 0x4f, - 0x56, 0x3e, 0x87, 0x62, 0x0b, 0xac, 0x67, 0xe2, 0xa3, 0xf6, 0xd1, 0x3f, 0x01, 0x00, 0x00, 0xff, - 0xff, 0xa5, 0xcd, 0xc5, 0xce, 0x34, 0x07, 0x00, 0x00, + // 814 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcd, 0x8f, 0x22, 0x45, + 0x14, 0xa7, 0xa0, 0x67, 0x5d, 0x0a, 0x86, 0x1d, 0x8b, 0x59, 0xd3, 0x21, 0x93, 0x5e, 0xb6, 0x4d, + 0x5c, 0xd6, 0x28, 0x64, 0x47, 0x4d, 0x36, 0xae, 0x97, 0x69, 0x3e, 0x02, 0xd9, 0x5d, 0x98, 0x34, + 0xbd, 0x1e, 0xf6, 0x60, 0xa7, 0xbb, 0x29, 0xe8, 0x0a, 0xd0, 0x45, 0xba, 0x0a, 0x02, 0x9e, 0xf4, + 0x60, 0x62, 0x3c, 0x19, 0xef, 0x9e, 0xfc, 0x67, 0x3c, 0x78, 0xd8, 0xa3, 0xd9, 0xc3, 0xc4, 0xcc, + 0xfc, 0x07, 0x5e, 0xbd, 0x98, 0xaa, 0xee, 0xe1, 0x6b, 0x06, 0x95, 0xc4, 0x1b, 0xef, 0xd5, 0xef, + 0x7d, 0xfc, 0x7e, 0xef, 0x3d, 0x1a, 0x7e, 0xe4, 0x3a, 0xee, 0x62, 0x44, 0x83, 0x8a, 0xcb, 0x3d, + 0xcf, 0xc7, 0xde, 0x70, 0x42, 0x49, 0xc0, 0x2b, 0xb3, 0x27, 0x9b, 0x8e, 0xf2, 0x24, 0xa4, 0x9c, + 0x22, 0x35, 0x46, 0x97, 0x37, 0x1f, 0x67, 0x4f, 0x0a, 0xc7, 0x03, 0x3a, 0xa0, 0x12, 0x54, 0x11, + 0xbf, 0x22, 0xbc, 0xfe, 0x17, 0x80, 0x19, 0xc3, 0xaa, 0x76, 0x27, 0xb3, 0xf3, 0x90, 0xd2, 0x3e, + 0x7a, 0x04, 0xef, 0xb9, 0xdc, 0xb3, 0x79, 0xe8, 0x04, 0xcc, 0xf1, 0x38, 0xa1, 0x81, 0x0a, 0x8a, + 0xa0, 0x94, 0x35, 0x73, 0x2e, 0xf7, 0xac, 0x95, 0x17, 0x9d, 0xc2, 0xfb, 0x5b, 0x40, 0x9b, 0x04, + 0x3d, 0x3c, 0x57, 0x93, 0x45, 0x50, 0x3a, 0x34, 0xf3, 0x9b, 0xf0, 0x96, 0x78, 0x42, 0x0f, 0x61, + 0x76, 0x8c, 0xc3, 0xe1, 0x08, 0xdb, 0x01, 0xed, 0x61, 0xa6, 0xa6, 0x64, 0xe6, 0x4c, 0xe4, 0x6b, + 0x0b, 0x17, 0x1a, 0xc1, 0xfb, 0x1e, 0x0d, 0xfa, 0x24, 0x1c, 0x93, 0x60, 0x60, 0x8b, 0x0a, 0x3e, + 0x76, 0x7a, 0x38, 0x54, 0x15, 0x81, 0x35, 0x9e, 0xbe, 0xbd, 0x78, 0xf0, 0xe9, 0x80, 0x70, 0x7f, + 0xea, 0x96, 0x3d, 0x3a, 0xae, 0xc4, 0x6c, 0x3d, 0xdf, 0x21, 0xc1, 0xb5, 0x51, 0xe1, 0x8b, 0x09, + 0x66, 0x65, 0xc3, 0xaa, 0x36, 0x65, 0xa8, 0xb1, 0xe0, 0x98, 0x99, 0xf9, 0x55, 0x5a, 0x83, 0x7b, + 0xd1, 0x8b, 0x3e, 0x87, 0xb9, 0xb5, 0x26, 0x9f, 0xe3, 0x05, 0x3a, 0x86, 0x07, 0x11, 0x0d, 0x20, + 0x69, 0x44, 0x06, 0x3a, 0x87, 0x8a, 0xef, 0x30, 0x5f, 0x72, 0xcb, 0x1a, 0x5f, 0xbc, 0xbd, 0x78, + 0xf0, 0x74, 0xcf, 0x26, 0x9a, 0x0e, 0xf3, 0xa3, 0x46, 0x64, 0x26, 0xfd, 0x39, 0x3c, 0xec, 0x4e, + 0xdd, 0x31, 0x61, 0x2c, 0x2e, 0xfc, 0x39, 0x4c, 0x0d, 0xf1, 0x42, 0x05, 0xc5, 0x54, 0x29, 0x73, + 0x5a, 0x2a, 0xef, 0x1a, 0x63, 0x79, 0xb3, 0x5f, 0x53, 0x04, 0xe9, 0xdf, 0x01, 0x78, 0x6f, 0x43, + 0xec, 0x3e, 0x5d, 0xe5, 0x03, 0x7b, 0xe7, 0x43, 0x45, 0x98, 0x59, 0x5f, 0x80, 0x64, 0x34, 0xa6, + 0x35, 0x97, 0x90, 0x69, 0x22, 0xf6, 0x25, 0x1e, 0x61, 0x64, 0xe8, 0xbf, 0x01, 0x98, 0x5b, 0xb1, + 0xaa, 0x39, 0xdc, 0x41, 0x5f, 0xc1, 0xfc, 0x8c, 0x0c, 0xc8, 0xc8, 0x09, 0x38, 0xb6, 0x9d, 0x5e, + 0x2f, 0xc4, 0x8c, 0x61, 0x16, 0xb7, 0xf5, 0xf1, 0xee, 0xb6, 0xaa, 0x4b, 0xeb, 0xec, 0x3a, 0xc8, + 0x44, 0xcb, 0x4c, 0x4b, 0x1f, 0xaa, 0xc1, 0xbb, 0x7c, 0xce, 0x6c, 0x12, 0xf4, 0xa9, 0x9a, 0x94, + 0xda, 0x3d, 0xfe, 0x4f, 0x5c, 0x85, 0x46, 0xe6, 0x3b, 0x7c, 0xce, 0xa4, 0x58, 0xc7, 0xf0, 0x00, + 0x4f, 0xa8, 0xe7, 0x4b, 0x3a, 0x8a, 0x19, 0x19, 0x42, 0xd6, 0x74, 0x5d, 0xfc, 0x92, 0x4c, 0x9e, + 0x41, 0x65, 0x88, 0x17, 0x2c, 0x9e, 0xd0, 0xa3, 0xdd, 0x55, 0x36, 0xe6, 0x6a, 0xca, 0x20, 0xf4, + 0x0c, 0xde, 0x61, 0xdc, 0xe1, 0x53, 0x26, 0xc5, 0xcc, 0x9d, 0xbe, 0xbf, 0x3b, 0xdc, 0xe0, 0x5e, + 0x57, 0x42, 0xcd, 0x38, 0x44, 0xef, 0xc0, 0xfc, 0x2d, 0x72, 0xa0, 0x13, 0x98, 0x66, 0xa2, 0x14, + 0xe7, 0x38, 0x8c, 0x8f, 0x74, 0xe5, 0x40, 0x05, 0x78, 0x37, 0xc4, 0x13, 0x1a, 0x8a, 0xc7, 0x68, + 0x80, 0x4b, 0x5b, 0xff, 0x33, 0x05, 0xdf, 0x35, 0xac, 0xea, 0x2a, 0xa9, 0x14, 0xe1, 0x21, 0xcc, + 0x4a, 0xde, 0x76, 0x30, 0x1d, 0xbb, 0x71, 0x4a, 0xc5, 0xcc, 0x48, 0x5f, 0x5b, 0xba, 0x50, 0x03, + 0x16, 0x5d, 0xcc, 0xb8, 0xcd, 0x96, 0x14, 0xe5, 0x89, 0xba, 0x23, 0xea, 0x0d, 0x6d, 0x1f, 0x93, + 0x81, 0xcf, 0x65, 0x31, 0xc5, 0x3c, 0x11, 0xb8, 0x95, 0x12, 0x06, 0xf7, 0x0c, 0x01, 0x6a, 0x4a, + 0x0c, 0xfa, 0x06, 0x40, 0xed, 0x1f, 0x12, 0x89, 0x53, 0x4b, 0xfd, 0x0f, 0xa7, 0x56, 0xd8, 0xd1, + 0x84, 0xc3, 0x7c, 0x34, 0x84, 0x27, 0xdb, 0x1d, 0xac, 0x2d, 0x38, 0x53, 0x95, 0x7d, 0x97, 0x69, + 0xab, 0xd8, 0xda, 0x33, 0x43, 0xdf, 0x02, 0xf8, 0xc1, 0x76, 0xb5, 0x1b, 0x67, 0x61, 0x8f, 0x08, + 0xe3, 0xea, 0x81, 0xac, 0xbb, 0xe7, 0x65, 0xe8, 0x9b, 0xb5, 0xbf, 0xdc, 0xba, 0x93, 0x17, 0x84, + 0xf1, 0x0f, 0x7f, 0x02, 0x30, 0xbd, 0xdc, 0x2d, 0xf4, 0x18, 0xbe, 0x57, 0x3f, 0xef, 0x54, 0x9b, + 0x76, 0xd7, 0x3a, 0xb3, 0x5e, 0x75, 0xed, 0xee, 0x2b, 0xe3, 0x65, 0xcb, 0xb2, 0xea, 0xb5, 0xa3, + 0x44, 0xe1, 0xf0, 0x87, 0x9f, 0x8b, 0xe9, 0x6e, 0xbc, 0x49, 0xbd, 0x1b, 0xd0, 0x6a, 0xa7, 0xdd, + 0x68, 0x99, 0x2f, 0xeb, 0xb5, 0x23, 0x10, 0x41, 0xab, 0xd1, 0x3f, 0xeb, 0x2d, 0xd0, 0x46, 0xab, + 0x7d, 0xf6, 0xa2, 0xf5, 0xba, 0x5e, 0x3b, 0x4a, 0x46, 0xd0, 0x06, 0x09, 0x9c, 0x11, 0xf9, 0x1a, + 0xf7, 0x0a, 0xca, 0xf7, 0xbf, 0x68, 0x09, 0xa3, 0xf3, 0xeb, 0xa5, 0x06, 0xde, 0x5c, 0x6a, 0xe0, + 0x8f, 0x4b, 0x0d, 0xfc, 0x78, 0xa5, 0x25, 0xde, 0x5c, 0x69, 0x89, 0xdf, 0xaf, 0xb4, 0xc4, 0xeb, + 0xcf, 0xfe, 0x6d, 0xea, 0xf3, 0xad, 0x0f, 0xa2, 0xdc, 0x02, 0xf7, 0x8e, 0xfc, 0xac, 0x7d, 0xf2, + 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x55, 0x92, 0x5f, 0x36, 0x07, 0x00, 0x00, } func (m *BTCSpvProof) Marshal() (dAtA []byte, err error) { @@ -914,10 +914,10 @@ func (m *EpochData) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x10 } - if len(m.Key) > 0 { - for iNdEx := len(m.Key) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Keys) > 0 { + for iNdEx := len(m.Keys) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.Key[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Keys[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1156,8 +1156,8 @@ func (m *EpochData) Size() (n int) { } var l int _ = l - if len(m.Key) > 0 { - for _, e := range m.Key { + if len(m.Keys) > 0 { + for _, e := range m.Keys { l = e.Size() n += 1 + l + sovBtccheckpoint(uint64(l)) } @@ -1906,7 +1906,7 @@ func (m *EpochData) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Keys", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1933,8 +1933,8 @@ func (m *EpochData) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Key = append(m.Key, &SubmissionKey{}) - if err := m.Key[len(m.Key)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Keys = append(m.Keys, &SubmissionKey{}) + if err := m.Keys[len(m.Keys)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btccheckpoint/types/expected_keepers.go b/x/btccheckpoint/types/expected_keepers.go index 9dc5fa676..79f50f014 100644 --- a/x/btccheckpoint/types/expected_keepers.go +++ b/x/btccheckpoint/types/expected_keepers.go @@ -54,4 +54,5 @@ type CheckpointingKeeper interface { } type IncentiveKeeper interface { + RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rewardDistInfo *RewardDistInfo) } diff --git a/x/btccheckpoint/types/incentive.go b/x/btccheckpoint/types/incentive.go new file mode 100644 index 000000000..970eaf4ea --- /dev/null +++ b/x/btccheckpoint/types/incentive.go @@ -0,0 +1,47 @@ +package types + +import ( + fmt "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// CheckpointAddressPair is a pair of (submitter, reporter) addresses of a checkpoint +// submission +type CheckpointAddressPair struct { + Submitter sdk.AccAddress + Reporter sdk.AccAddress +} + +func NewCheckpointAddressPair(addrs *CheckpointAddresses) (*CheckpointAddressPair, error) { + var ( + submitter sdk.AccAddress + reporter sdk.AccAddress + ) + if err := submitter.Unmarshal(addrs.Submitter); err != nil { + return nil, fmt.Errorf("failed to unmarshal submitter address in bytes: %w", err) + } + if err := reporter.Unmarshal(addrs.Reporter); err != nil { + return nil, fmt.Errorf("failed to unmarshal reporter address in bytes: %w", err) + } + return &CheckpointAddressPair{ + Submitter: submitter, + Reporter: reporter, + }, nil +} + +// RewardDistInfo includes information necessary for incentive module to distribute rewards to +// a given finalised epoch +type RewardDistInfo struct { + // Best is the address pair of the best checkpoint submission + Best *CheckpointAddressPair + // Others is a list of other address pairs + Others []*CheckpointAddressPair +} + +func NewRewardDistInfo(best *CheckpointAddressPair, others ...*CheckpointAddressPair) *RewardDistInfo { + return &RewardDistInfo{ + Best: best, + Others: others, + } +} diff --git a/x/btccheckpoint/types/mock_keepers.go b/x/btccheckpoint/types/mock_keepers.go index c541246b7..1e734b040 100644 --- a/x/btccheckpoint/types/mock_keepers.go +++ b/x/btccheckpoint/types/mock_keepers.go @@ -16,6 +16,9 @@ type MockCheckpointingKeeper struct { returnError bool } +type MockIncentiveKeeper struct { +} + func NewMockBTCLightClientKeeper() *MockBTCLightClientKeeper { lc := MockBTCLightClientKeeper{ headers: make(map[string]int64), @@ -30,6 +33,10 @@ func NewMockCheckpointingKeeper() *MockCheckpointingKeeper { return &mc } +func NewMockIncentiveKeeper() *MockIncentiveKeeper { + return &MockIncentiveKeeper{} +} + func (mc *MockCheckpointingKeeper) ReturnError() { mc.returnError = true } @@ -83,3 +90,6 @@ func (ck MockCheckpointingKeeper) SetCheckpointFinalized(ctx sdk.Context, epoch // lost all its checkpoints and is checkpoint empty func (ck MockCheckpointingKeeper) SetCheckpointForgotten(ctx sdk.Context, epoch uint64) { } + +func (ik *MockIncentiveKeeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rewardDistInfo *RewardDistInfo) { +} diff --git a/x/btccheckpoint/types/types.go b/x/btccheckpoint/types/types.go index d70fa2894..75a862bbe 100644 --- a/x/btccheckpoint/types/types.go +++ b/x/btccheckpoint/types/types.go @@ -117,14 +117,14 @@ func (sk *SubmissionKey) GetKeyBlockHashes() []*types.BTCHeaderHashBytes { func NewEmptyEpochData() EpochData { return EpochData{ - Key: []*SubmissionKey{}, + Keys: []*SubmissionKey{}, Status: Submitted, } } func (s *EpochData) AppendKey(k SubmissionKey) { key := &k - s.Key = append(s.Key, key) + s.Keys = append(s.Keys, key) } // HappenedAfter returns true if `this` submission happened after `that` submission diff --git a/x/incentive/abci.go b/x/incentive/abci.go index 0b092f185..cd70e2b32 100644 --- a/x/incentive/abci.go +++ b/x/incentive/abci.go @@ -17,7 +17,9 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) // - send a portion of coins in the fee collector account to the incentive module account // - accumulate BTC staking gauge at the current height // - accumulate BTC timestamping gauge at the current epoch - k.HandleCoinsInFeeCollector(ctx) + if ctx.BlockHeight() > 0 { + k.HandleCoinsInFeeCollector(ctx) + } } func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go index ea2b32ff2..1f3450087 100644 --- a/x/incentive/keeper/btc_staking_gauge_test.go +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -33,7 +33,7 @@ func FuzzRewardBTCStaking(f *testing.F) { keeper.SetBTCStakingGauge(ctx, height, gauge) // generate a random reward distribution cache - rdc, err := datagen.GenRandomRewardDistCache(r) + rdc, err := datagen.GenRandomBTCStakingRewardDistCache(r) require.NoError(t, err) // mock transfer to ensure reward is distributed correctly diff --git a/x/incentive/keeper/btc_timestamping_gauge.go b/x/incentive/keeper/btc_timestamping_gauge.go index e52d2578b..4b722ed02 100644 --- a/x/incentive/keeper/btc_timestamping_gauge.go +++ b/x/incentive/keeper/btc_timestamping_gauge.go @@ -1,11 +1,99 @@ package keeper import ( + "cosmossdk.io/math" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/babylonchain/babylon/x/incentive/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) +// RewardBTCTimestamping distributes rewards to submitters/reporters of a checkpoint at a given epoch +// according to the reward distribution cache +func (k Keeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rdi *btcctypes.RewardDistInfo) { + gauge, err := k.GetBTCTimestampingGauge(ctx, epoch) + if err != nil { + // failing to get a reward gauge at a finalised epoch is a programming error + panic(err) + } + + params := k.GetParams(ctx) + btcTimestampingPortion := params.BTCTimestampingPortion() + // TODO: parameterise bestPortion + bestPortion := math.LegacyNewDecWithPrec(80, 2) // 80 * 10^{-2} = 0.8 + + // distribute coins to best submitter + submitterPortion := params.SubmitterPortion.QuoTruncate(btcTimestampingPortion) + coinsToSubmitters := gauge.GetCoinsPortion(submitterPortion) + coinsToBestSubmitter := types.GetCoinsPortion(coinsToSubmitters, bestPortion) + if coinsToBestSubmitter.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Submitter, coinsToBestSubmitter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + // distribute coins to best reporter + reporterPortion := params.ReporterPortion.QuoTruncate(btcTimestampingPortion) + coinsToReporters := gauge.GetCoinsPortion(reporterPortion) + coinsToBestReporter := types.GetCoinsPortion(coinsToReporters, bestPortion) + if coinsToBestReporter.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Reporter, coinsToBestReporter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + + // if there is only 1 submission, distribute the rest to submitter and reporter, then skip the rest logic + if len(rdi.Others) == 0 { + // give rest coins to the best submitter + restCoinsToSubmitter := coinsToSubmitters.Sub(coinsToBestSubmitter...) + if restCoinsToSubmitter.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Submitter, restCoinsToSubmitter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + // give rest coins to the best reporter + restCoinsToReporter := coinsToReporters.Sub(coinsToBestReporter...) + if restCoinsToReporter.IsAllPositive() { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Reporter, restCoinsToReporter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + // skip the rest logic + return + } + + // distribute the rest to each of the other submitters + // TODO: our tokenomics might specify weights for the rest submitters in the future + coinsToOtherSubmitters := coinsToSubmitters.Sub(coinsToBestSubmitter...) + eachOtherSubmitterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) + coinsToEachOtherSubmitter := types.GetCoinsPortion(coinsToOtherSubmitters, eachOtherSubmitterPortion) + if coinsToEachOtherSubmitter.IsAllPositive() { + for _, submission := range rdi.Others { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, submission.Submitter, coinsToEachOtherSubmitter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + } + + // distribute the rest to each of the other reporters + // TODO: our tokenomics might specify weights for the rest reporters in the future + coinsToOtherReporters := coinsToReporters.Sub(coinsToBestReporter...) + eachOtherReporterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) + coinsToEachOtherReporter := types.GetCoinsPortion(coinsToOtherReporters, eachOtherReporterPortion) + if coinsToEachOtherReporter.IsAllPositive() { + for _, submission := range rdi.Others { + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, submission.Reporter, coinsToEachOtherReporter); err != nil { + // incentive module account is supposed to have enough balance + panic(err) + } + } + } +} + func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestampingReward sdk.Coins) { var ( epoch = k.epochingKeeper.GetEpoch(ctx) @@ -13,11 +101,6 @@ func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestamping err error ) - // do nothing at epoch 0 - if epoch.EpochNumber == 0 { - return - } - // update BTC timestamping reward gauge if k.HasBTCTimestampingGauge(ctx, epoch.EpochNumber) { // if this epoch already has an non-empty gauge, accumulate diff --git a/x/incentive/keeper/btc_timestamping_gauge_test.go b/x/incentive/keeper/btc_timestamping_gauge_test.go new file mode 100644 index 000000000..73f6ca61f --- /dev/null +++ b/x/incentive/keeper/btc_timestamping_gauge_test.go @@ -0,0 +1,103 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func FuzzRewardBTCTimestamping(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock bank keeper + bankKeeper := types.NewMockBankKeeper(ctrl) + + // create incentive keeper + keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, nil, nil) + epoch := datagen.RandomInt(r, 1000) + 1 + + // set a random gauge + gauge := datagen.GenRandomGauge(r) + keeper.SetBTCTimestampingGauge(ctx, epoch, gauge) + + // generate a random BTC timestamping reward distribution info + rdi := datagen.GenRandomBTCTimestampingRewardDistInfo(r) + + // parameters + params := types.DefaultParams() + btcTimestampingPortion := params.BTCTimestampingPortion() + bestPortion := math.LegacyNewDecWithPrec(80, 2) // 80 * 10^{-2} = 0.8 + + // mock bank transfer here + // best submitter + distributedCoins := sdk.NewCoins() + submitterPortion := params.SubmitterPortion.QuoTruncate(btcTimestampingPortion) + coinsToSubmitters := gauge.GetCoinsPortion(submitterPortion) + coinsToBestSubmitter := types.GetCoinsPortion(coinsToSubmitters, bestPortion) + if coinsToBestSubmitter.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Submitter), gomock.Eq(coinsToBestSubmitter)).Times(1) + distributedCoins.Add(coinsToBestSubmitter...) + } + // best reporter + reporterPortion := params.ReporterPortion.QuoTruncate(btcTimestampingPortion) + coinsToReporters := gauge.GetCoinsPortion(reporterPortion) + coinsToBestReporter := types.GetCoinsPortion(coinsToReporters, bestPortion) + if coinsToBestReporter.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Reporter), gomock.Eq(coinsToBestReporter)).Times(1) + distributedCoins.Add(coinsToBestReporter...) + } + // other submitters and reporters + if len(rdi.Others) > 0 { + // other submitters + coinsToOtherSubmitters := coinsToSubmitters.Sub(coinsToBestSubmitter...) + eachOtherSubmitterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) + coinsToEachOtherSubmitter := types.GetCoinsPortion(coinsToOtherSubmitters, eachOtherSubmitterPortion) + if coinsToEachOtherSubmitter.IsAllPositive() { + for _, submission := range rdi.Others { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(submission.Submitter), gomock.Eq(coinsToEachOtherSubmitter)).Times(1) + } + distributedCoins.Add(coinsToEachOtherSubmitter...) + } + // other reporters + coinsToOtherReporters := coinsToReporters.Sub(coinsToBestReporter...) + eachOtherReporterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) + coinsToEachOtherReporter := types.GetCoinsPortion(coinsToOtherReporters, eachOtherReporterPortion) + if coinsToEachOtherReporter.IsAllPositive() { + for _, submission := range rdi.Others { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(submission.Reporter), gomock.Eq(coinsToEachOtherReporter)).Times(1) + } + distributedCoins.Add(coinsToEachOtherReporter...) + } + } else { + // no other submission. give rest coins to best submitter/reporter + // give rest coins to the best submitter + restCoinsToSubmitter := coinsToSubmitters.Sub(coinsToBestSubmitter...) + if restCoinsToSubmitter.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Submitter), gomock.Eq(restCoinsToSubmitter)).Times(1) + } + // give rest coins to the best reporter + restCoinsToReporter := coinsToReporters.Sub(coinsToBestReporter...) + if restCoinsToReporter.IsAllPositive() { + bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Reporter), gomock.Eq(restCoinsToReporter)).Times(1) + } + } + + // distribute rewards in the gauge to BTC validators/delegations + keeper.RewardBTCTimestamping(ctx, epoch, rdi) + + // assert distributedCoins is a subset of coins in gauge + require.True(t, gauge.Coins.IsAllGTE(distributedCoins)) + }) +} diff --git a/x/zoneconcierge/keeper/grpc_query_test.go b/x/zoneconcierge/keeper/grpc_query_test.go index 4a5139e0f..a02807007 100644 --- a/x/zoneconcierge/keeper/grpc_query_test.go +++ b/x/zoneconcierge/keeper/grpc_query_test.go @@ -417,7 +417,7 @@ func FuzzFinalizedChainInfo(f *testing.F) { ) numChains := datagen.RandomInt(r, 100) + 1 for i := uint64(0); i < numChains; i++ { - czChainIDLen := datagen.RandomInt(r, 50) + 1 + czChainIDLen := datagen.RandomInt(r, 40) + 10 czChainID := string(datagen.GenRandomByteArray(r, czChainIDLen)) // invoke the hook a random number of times to simulate a random number of blocks From 9f409815294e842068c9323f82a04ca82a9bd0cb Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Sat, 9 Sep 2023 10:45:31 +1000 Subject: [PATCH 078/202] incentive: accumulating reward gauge and e2e tests for incentive module (#76) --- test/e2e/btc_staking_e2e_test.go | 108 ++++++++++++++---- test/e2e/btc_timestamping_e2e_test.go | 98 +++++++++++++--- test/e2e/configurer/chain/commands.go | 9 ++ .../e2e/configurer/chain/queries_incentive.go | 13 ++- test/e2e/initialization/config.go | 10 ++ testutil/datagen/btcstaking.go | 2 +- testutil/datagen/incentive.go | 4 +- x/incentive/keeper/btc_staking_gauge.go | 30 ++--- x/incentive/keeper/btc_staking_gauge_test.go | 29 ++++- x/incentive/keeper/btc_timestamping_gauge.go | 92 +++++---------- .../keeper/btc_timestamping_gauge_test.go | 40 +++++-- x/incentive/keeper/grpc_query.go | 25 ++-- x/incentive/keeper/grpc_query_test.go | 2 +- .../keeper/intercept_fee_collector_test.go | 12 +- x/incentive/keeper/msg_server_test.go | 6 +- x/incentive/keeper/reward_gauge.go | 44 ++++--- x/incentive/keeper/reward_gauge_test.go | 20 ++++ x/incentive/types/incentive.go | 47 +++----- 18 files changed, 377 insertions(+), 214 deletions(-) create mode 100644 x/incentive/keeper/reward_gauge_test.go diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index a0d3b5937..19c932529 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -6,14 +6,6 @@ import ( "math/rand" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" - "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/crypto/eots" "github.com/babylonchain/babylon/test/e2e/configurer" @@ -24,6 +16,14 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ftypes "github.com/babylonchain/babylon/x/finality/types" + itypes "github.com/babylonchain/babylon/x/incentive/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" ) var ( @@ -32,10 +32,10 @@ var ( // BTC validator valSK, _, _ = datagen.GenRandomBTCKeyPair(r) btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) - btcValBabylonAddr = sdk.AccAddress(btcVal.BabylonPk.Address()).String() + btcValBabylonAddr = sdk.AccAddress(btcVal.BabylonPk.Address()) // BTC delegation delBabylonSK, delBabylonPK, _ = datagen.GenRandomSecp256k1KeyPair(r) - delBabylonAddr = sdk.AccAddress(delBabylonPK.Address()).String() + delBabylonAddr = sdk.AccAddress(delBabylonPK.Address()) delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) // jury jurySK, _ = btcec.PrivKeyFromBytes( @@ -247,12 +247,11 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat }, time.Minute, time.Second*5) s.Equal(pubRandMap[activatedHeight].MustMarshal(), msgCommitPubRandList.PubRandList[0].MustMarshal()) - // get the balance of BTC validator and delegation before submitting finality signature - // later we will check if rewards are distributed after they finalise a block - btcValBalance, err := nonValidatorNode.QueryBalances(btcValBabylonAddr) - s.NoError(err) - btcDelBalance, err := nonValidatorNode.QueryBalances(delBabylonAddr) - s.NoError(err) + // no reward gauge for BTC validator and delegation yet + _, err = nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + s.Error(err) + _, err = nonValidatorNode.QueryRewardGauge(delBabylonAddr) + s.Error(err) /* submit finality signature @@ -284,17 +283,80 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NotEmpty(finalizedBlocks) s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) - // ensure voters have received rewards after the block is finalised - btcValBalance2, err := nonValidatorNode.QueryBalances(btcValBabylonAddr) + // ensure BTC validator has received rewards after the block is finalised + btcValRewardGauges, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + s.NoError(err) + btcValRewardGauge, ok := btcValRewardGauges[itypes.BTCValidatorType.String()] + s.True(ok) + s.True(btcValRewardGauge.Coins.IsAllPositive()) + // ensure BTC delegation has received rewards after the block is finalised + btcDelRewardGauges, err := nonValidatorNode.QueryRewardGauge(delBabylonAddr) + s.NoError(err) + btcDelRewardGauge, ok := btcDelRewardGauges[itypes.BTCDelegationType.String()] + s.True(ok) + s.True(btcDelRewardGauge.Coins.IsAllPositive()) +} + +func (s *BTCStakingTestSuite) Test4WithdrawReward() { + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + // BTC validator balance before withdraw + btcValBalance, err := nonValidatorNode.QueryBalances(btcValBabylonAddr.String()) + s.NoError(err) + // BTC validator reward gauge should not be fully withdrawn + btcValRgs, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) s.NoError(err) + btcValRg := btcValRgs[itypes.BTCValidatorType.String()] + s.T().Logf("BTC validator's withdrawable reward before withdrawing: %s", btcValRg.GetWithdrawableCoins().String()) + s.False(btcValRg.IsFullyWithdrawn()) + + // withdraw BTC validator reward + nonValidatorNode.WithdrawReward(itypes.BTCValidatorType.String(), btcValBabylonAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + + // balance after withdrawing BTC validator reward + btcValBalance2, err := nonValidatorNode.QueryBalances(btcValBabylonAddr.String()) + s.NoError(err) + s.T().Logf("btcValBalance2: %s; btcValBalance: %s", btcValBalance2.String(), btcValBalance.String()) s.True(btcValBalance2.IsAllGT(btcValBalance)) - btcDelBalance2, err := nonValidatorNode.QueryBalances(delBabylonAddr) + // BTC validator reward gauge should be fully withdrawn now + btcValRgs2, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + s.NoError(err) + btcValRg2 := btcValRgs2[itypes.BTCValidatorType.String()] + s.T().Logf("BTC validator's withdrawable reward after withdrawing: %s", btcValRg2.GetWithdrawableCoins().String()) + s.True(btcValRg2.IsFullyWithdrawn()) + + // BTC delegation balance before withdraw + btcDelBalance, err := nonValidatorNode.QueryBalances(delBabylonAddr.String()) s.NoError(err) + // BTC delegation reward gauge should not be fully withdrawn + btcDelRgs, err := nonValidatorNode.QueryRewardGauge(delBabylonAddr) + s.NoError(err) + btcDelRg := btcDelRgs[itypes.BTCDelegationType.String()] + s.T().Logf("BTC delegation's withdrawable reward before withdrawing: %s", btcDelRg.GetWithdrawableCoins().String()) + s.False(btcDelRg.IsFullyWithdrawn()) + + // withdraw BTC delegation reward + nonValidatorNode.WithdrawReward(itypes.BTCDelegationType.String(), delBabylonAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + + // balance after withdrawing BTC delegation reward + btcDelBalance2, err := nonValidatorNode.QueryBalances(delBabylonAddr.String()) + s.NoError(err) + s.T().Logf("btcDelBalance2: %s; btcDelBalance: %s", btcDelBalance2.String(), btcDelBalance.String()) s.True(btcDelBalance2.IsAllGT(btcDelBalance)) + // BTC delegation reward gauge should be fully withdrawn now + btcDelRgs2, err := nonValidatorNode.QueryRewardGauge(delBabylonAddr) + s.NoError(err) + btcDelRg2 := btcDelRgs2[itypes.BTCDelegationType.String()] + s.T().Logf("BTC delegation's withdrawable reward after withdrawing: %s", btcDelRg2.GetWithdrawableCoins().String()) + s.True(btcDelRg2.IsFullyWithdrawn()) } -// Test4SubmitStakerUnbonding is an end-to-end test for user unbodning -func (s *BTCStakingTestSuite) Test4SubmitStakerUnbonding() { +// Test5SubmitStakerUnbonding is an end-to-end test for user unbonding +func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) @@ -361,9 +423,9 @@ func (s *BTCStakingTestSuite) Test4SubmitStakerUnbonding() { s.NotNil(delegation.BtcUndelegation) } -// Test5SubmitStakerUnbonding is an end-to-end test for jury and validator submitting signatures +// Test6SubmitStakerUnbonding is an end-to-end test for jury and validator submitting signatures // for unbonding transaction -func (s *BTCStakingTestSuite) Test5SubmitUnbondingSignatures() { +func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go index a26653b8d..f47fa5a4f 100644 --- a/test/e2e/btc_timestamping_e2e_test.go +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -12,7 +12,8 @@ import ( "github.com/babylonchain/babylon/test/e2e/initialization" bbn "github.com/babylonchain/babylon/types" ct "github.com/babylonchain/babylon/x/checkpointing/types" - incentivetypes "github.com/babylonchain/babylon/x/incentive/types" + itypes "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -55,7 +56,7 @@ func (s *BTCTimestampingTestSuite) TearDownSuite() { // Most simple test, just checking that two chains are up and connected through // ibc -func (s *BTCTimestampingTestSuite) TestConnectIbc() { +func (s *BTCTimestampingTestSuite) Test1ConnectIbc() { chainA := s.configurer.GetChainConfig(0) chainB := s.configurer.GetChainConfig(1) _, err := chainA.GetDefaultNode() @@ -64,7 +65,7 @@ func (s *BTCTimestampingTestSuite) TestConnectIbc() { s.NoError(err) } -func (s *BTCTimestampingTestSuite) TestBTCBaseHeader() { +func (s *BTCTimestampingTestSuite) Test2BTCBaseHeader() { hardcodedHeader, _ := bbn.NewBTCHeaderBytesFromHex("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000") hardcodedHeaderHeight := uint64(0) @@ -77,7 +78,7 @@ func (s *BTCTimestampingTestSuite) TestBTCBaseHeader() { s.Equal(hardcodedHeaderHeight, baseHeader.Height) } -func (s *BTCTimestampingTestSuite) TestSendTx() { +func (s *BTCTimestampingTestSuite) Test3SendTx() { r := rand.New(rand.NewSource(time.Now().Unix())) chainA := s.configurer.GetChainConfig(0) nonValidatorNode, err := chainA.GetNodeAtIndex(2) @@ -104,7 +105,7 @@ func (s *BTCTimestampingTestSuite) TestSendTx() { s.Equal(tip2Depth, uint64(0)) } -func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { +func (s *BTCTimestampingTestSuite) Test4IbcCheckpointing() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(35) @@ -122,18 +123,13 @@ func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { endEpochNum uint64 = 3 ) - // get balance of submitter/reporter address - // will compare with the balance after finalising some epochs - submitterReporterBalance, err := nonValidatorNode.QueryBalances(nonValidatorNode.PublicAddress) - s.NoError(err) + // submitter/reporter address should not have any reward yet + submitterReporterAddr := sdk.MustAccAddressFromBech32(nonValidatorNode.PublicAddress) + _, err = nonValidatorNode.QueryRewardGauge(submitterReporterAddr) + s.Error(err) nonValidatorNode.FinalizeSealedEpochs(startEpochNum, endEpochNum) - // ensure balance has incresed after finalising some epochs - submitterReporterBalance2, err := nonValidatorNode.QueryBalances(nonValidatorNode.PublicAddress) - s.NoError(err) - s.True(submitterReporterBalance2.IsAllGT(submitterReporterBalance)) - endEpoch, err := nonValidatorNode.QueryRawCheckpoint(endEpochNum) s.NoError(err) s.Equal(endEpoch.Status, ct.Finalized) @@ -163,12 +159,75 @@ func (s *BTCTimestampingTestSuite) TestIbcCheckpointing() { s.FailNow(fmt.Sprintf("Light client height should be > 0 on epoch %d", currEpoch-1)) } + // ensure balance has increased after finalising some epochs + rewardGauges, err := nonValidatorNode.QueryRewardGauge(submitterReporterAddr) + s.NoError(err) + submitterRewardGauge, ok := rewardGauges[itypes.SubmitterType.String()] + s.True(ok) + s.True(submitterRewardGauge.Coins.IsAllPositive()) + reporterRewardGauge, ok := rewardGauges[itypes.ReporterType.String()] + s.True(ok) + s.True(reporterRewardGauge.Coins.IsAllPositive()) + chainB := s.configurer.GetChainConfig(1) _, err = chainB.GetDefaultNode() s.NoError(err) } -func (s *BTCTimestampingTestSuite) TestWasm() { +func (s *BTCTimestampingTestSuite) Test5WithdrawReward() { + chainA := s.configurer.GetChainConfig(0) + nonValidatorNode, err := chainA.GetNodeAtIndex(2) + s.NoError(err) + + submitterReporterAddr := sdk.MustAccAddressFromBech32(nonValidatorNode.PublicAddress) + + // balance before withdraw + balance, err := nonValidatorNode.QueryBalances(submitterReporterAddr.String()) + s.NoError(err) + // submitter/reporter reward gauges before withdraw should not be fully withdrawn + rgs, err := nonValidatorNode.QueryRewardGauge(submitterReporterAddr) + s.NoError(err) + submitterRg, reporterRg := rgs[itypes.SubmitterType.String()], rgs[itypes.ReporterType.String()] + s.T().Logf("submitter witdhrawable reward: %s, reporter witdhrawable reward: %s before withdrawing", submitterRg.GetWithdrawableCoins().String(), reporterRg.GetWithdrawableCoins().String()) + s.False(submitterRg.IsFullyWithdrawn()) + s.False(reporterRg.IsFullyWithdrawn()) + + // withdraw submitter reward + nonValidatorNode.WithdrawReward(itypes.SubmitterType.String(), submitterReporterAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + + // balance after withdrawing submitter reward + balance2, err := nonValidatorNode.QueryBalances(submitterReporterAddr.String()) + s.NoError(err) + s.T().Logf("balance2: %s; balance: %s", balance2.String(), balance.String()) + s.True(balance2.IsAllGT(balance)) + + // submitter reward gauge should be fully withdrawn + rgs2, err := nonValidatorNode.QueryRewardGauge(submitterReporterAddr) + s.NoError(err) + submitterRg2 := rgs2[itypes.SubmitterType.String()] + s.T().Logf("submitter withdrawable reward: %s after withdrawing", submitterRg2.GetWithdrawableCoins().String()) + s.True(rgs2[itypes.SubmitterType.String()].IsFullyWithdrawn()) + + // withdraw reporter reward + nonValidatorNode.WithdrawReward(itypes.ReporterType.String(), submitterReporterAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WaitForNextBlock() + + // balance after withdrawing reporter reward + balance3, err := nonValidatorNode.QueryBalances(submitterReporterAddr.String()) + s.NoError(err) + s.T().Logf("balance3: %s; balance2: %s", balance3.String(), balance2.String()) + s.True(balance3.IsAllGT(balance2)) + + // reporter reward gauge should be fully withdrawn + rgs3, err := nonValidatorNode.QueryRewardGauge(submitterReporterAddr) + s.NoError(err) + reporterRg3 := rgs3[itypes.SubmitterType.String()] + s.T().Logf("reporter withdrawable reward: %s after withdrawing", reporterRg3.GetWithdrawableCoins().String()) + s.True(rgs3[itypes.ReporterType.String()].IsFullyWithdrawn()) +} + +func (s *BTCTimestampingTestSuite) Test6Wasm() { contractPath := "/bytecode/storage_contract.wasm" chainA := s.configurer.GetChainConfig(0) nonValidatorNode, err := chainA.GetNodeAtIndex(2) @@ -209,17 +268,18 @@ func (s *BTCTimestampingTestSuite) TestWasm() { require.Greater(s.T(), saveEpoch, latestFinalizedEpoch) } -func (s *BTCTimestampingTestSuite) TestInterceptFeeCollector() { +func (s *BTCTimestampingTestSuite) Test7InterceptFeeCollector() { chainA := s.configurer.GetChainConfig(0) nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) // ensure incentive module account has positive balance - incentiveModuleAddr, err := nonValidatorNode.QueryModuleAddress(incentivetypes.ModuleName) + incentiveModuleAddr, err := nonValidatorNode.QueryModuleAddress(itypes.ModuleName) s.NoError(err) incentiveBalance, err := nonValidatorNode.QueryBalances(incentiveModuleAddr.String()) s.NoError(err) s.NotEmpty(incentiveBalance) + s.T().Logf("incentive module account's balance: %s", incentiveBalance.String()) s.True(incentiveBalance.IsAllPositive()) // ensure BTC staking gauge at the current height is non-empty @@ -227,6 +287,7 @@ func (s *BTCTimestampingTestSuite) TestInterceptFeeCollector() { s.NoError(err) btcStakingGauge, err := nonValidatorNode.QueryBTCStakingGauge(uint64(curHeight)) s.NoError(err) + s.T().Logf("BTC staking gauge at current height %d: %s", curHeight, btcStakingGauge.String()) s.True(len(btcStakingGauge.Coins) >= 1) s.True(btcStakingGauge.Coins[0].Amount.IsPositive()) @@ -239,17 +300,20 @@ func (s *BTCTimestampingTestSuite) TestInterceptFeeCollector() { nonValidatorNode.WaitForNextBlock() btcTimestampingGauge, err := nonValidatorNode.QueryBTCTimestampingGauge(curEpoch) s.NoError(err) + s.T().Logf("BTC timestamping gauge at current epoch %d: %s", curEpoch, btcTimestampingGauge.String()) s.NotEmpty(btcTimestampingGauge.Coins) // wait for 1 block to see if BTC timestamp gauge has accumulated nonValidatorNode.WaitForNextBlock() btcTimestampingGauge2, err := nonValidatorNode.QueryBTCTimestampingGauge(curEpoch) s.NoError(err) + s.T().Logf("BTC timestamping gauge after a block at current epoch %d: %s", curEpoch, btcTimestampingGauge2.String()) s.NotEmpty(btcTimestampingGauge2.Coins) s.True(btcTimestampingGauge2.Coins.IsAllGTE(btcTimestampingGauge.Coins)) // after 1 block, incentive's balance has to be accumulated incentiveBalance2, err := nonValidatorNode.QueryBalances(incentiveModuleAddr.String()) s.NoError(err) + s.T().Logf("incentive module account's balance after a block: %s", incentiveBalance2.String()) s.True(incentiveBalance2.IsAllGTE(incentiveBalance)) } diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index 66a6dd1d5..f38c500d5 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -190,3 +190,12 @@ func (n *NodeConfig) WasmExecute(contract, execMsg, from string) { require.NoError(n.t, err) n.LogActionF("successfully executed") } + +func (n *NodeConfig) WithdrawReward(sType, sAddr, from string) { + n.LogActionF("withdraw reward of type %s address %s", sType, sAddr) + cmd := []string{"babylond", "tx", "incentive", "withdraw-reward", sType, sAddr, fmt.Sprintf("--from=%s", from)} + n.LogActionF(strings.Join(cmd, " ")) + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully withdrawn") +} diff --git a/test/e2e/configurer/chain/queries_incentive.go b/test/e2e/configurer/chain/queries_incentive.go index d9c91eff6..5f8dce124 100644 --- a/test/e2e/configurer/chain/queries_incentive.go +++ b/test/e2e/configurer/chain/queries_incentive.go @@ -13,7 +13,9 @@ import ( func (n *NodeConfig) QueryBTCStakingGauge(height uint64) (*incentivetypes.Gauge, error) { path := fmt.Sprintf("/babylonchain/babylon/incentive/btc_staking_gauge/%d", height) bz, err := n.QueryGRPCGateway(path, url.Values{}) - require.NoError(n.t, err) + if err != nil { + return nil, err + } var resp incentivetypes.QueryBTCStakingGaugeResponse if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { @@ -39,8 +41,9 @@ func (n *NodeConfig) QueryIncentiveParams() (*incentivetypes.Params, error) { func (n *NodeConfig) QueryRewardGauge(sAddr sdk.AccAddress) (map[string]*incentivetypes.RewardGauge, error) { path := fmt.Sprintf("/babylonchain/babylon/incentive/address/%s/reward_gauge", sAddr.String()) bz, err := n.QueryGRPCGateway(path, url.Values{}) - require.NoError(n.t, err) - + if err != nil { + return nil, err + } var resp incentivetypes.QueryRewardGaugesResponse if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { return nil, err @@ -52,7 +55,9 @@ func (n *NodeConfig) QueryRewardGauge(sAddr sdk.AccAddress) (map[string]*incenti func (n *NodeConfig) QueryBTCTimestampingGauge(epoch uint64) (*incentivetypes.Gauge, error) { path := fmt.Sprintf("/babylonchain/babylon/incentive/btc_timestamping_gauge/%d", epoch) bz, err := n.QueryGRPCGateway(path, url.Values{}) - require.NoError(n.t, err) + if err != nil { + return nil, err + } var resp incentivetypes.QueryBTCTimestampingGaugeResponse if err := util.Cdc.UnmarshalJSON(bz, &resp); err != nil { diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index 3903f49f3..ac8e5f923 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -21,6 +21,7 @@ import ( crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/gogoproto/proto" @@ -213,6 +214,11 @@ func initGenesis(chain *internalChain, votingPeriod, expeditedVotingPeriod time. return err } + err = updateModuleGenesis(appGenState, minttypes.ModuleName, &minttypes.GenesisState{}, updateMintGenesis) + if err != nil { + return err + } + err = updateModuleGenesis(appGenState, staketypes.ModuleName, &staketypes.GenesisState{}, updateStakeGenesis) if err != nil { return err @@ -280,6 +286,10 @@ func updateBankGenesis(bankGenState *banktypes.GenesisState) { }) } +func updateMintGenesis(mintGenState *minttypes.GenesisState) { + mintGenState.Params.MintDenom = BabylonDenom +} + func updateStakeGenesis(stakeGenState *staketypes.GenesisState) { stakeGenState.Params = staketypes.Params{ BondDenom: BabylonDenom, diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index efe859c41..a4c8be8c3 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -29,7 +29,7 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { // commission - commission := sdk.NewDecWithPrec(int64(RandomInt(r, 99)+1), 2) // [1/100, 100/100] + commission := sdk.NewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] // description description := stakingtypes.Description{} // key pairs diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index a907b66c7..b06bd5e18 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -50,7 +50,7 @@ func GenRandomCoins(r *rand.Rand) sdk.Coins { func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge { coins := GenRandomCoins(r) - return itypes.NewRewardGauge(coins) + return itypes.NewRewardGauge(coins...) } func GenRandomWithdrawnCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins { @@ -71,7 +71,7 @@ func GenRandomWithdrawnCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins { func GenRandomGauge(r *rand.Rand) *itypes.Gauge { coins := GenRandomCoins(r) - return itypes.NewGauge(coins) + return itypes.NewGauge(coins...) } func GenRandomBTCDelDistInfo(r *rand.Rand) *bstypes.BTCDelDistInfo { diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index 8467bac66..1ca1c4934 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -11,10 +11,10 @@ import ( // to the reward distribution cache // (adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L12-L64) func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.RewardDistCache) { - gauge, err := k.GetBTCStakingGauge(ctx, height) - if err != nil { + gauge := k.GetBTCStakingGauge(ctx, height) + if gauge == nil { // failing to get a reward gauge at previous height is a programming error - panic(err) + panic("failed to get a reward gauge at previous height") } // reward each of the BTC validator and its BTC delegations in proportion for _, btcVal := range rdc.BtcVals { @@ -23,23 +23,13 @@ func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.Re coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) // reward the BTC validator with commission coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) - if coinsForCommission.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, btcVal.GetAddress(), coinsForCommission); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.BTCValidatorType, btcVal.GetAddress(), coinsForCommission) // reward the rest of coins to each BTC delegation proportional to its voting power portion coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) for _, btcDel := range btcVal.BtcDels { btcDelPortion := btcVal.GetBTCDelPortion(btcDel) coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) - if coinsForDel.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, btcDel.GetAddress(), coinsForDel); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.BTCDelegationType, btcDel.GetAddress(), coinsForDel) } } @@ -49,7 +39,7 @@ func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.Re func (k Keeper) accumulateBTCStakingReward(ctx sdk.Context, btcStakingReward sdk.Coins) { // update BTC staking gauge height := uint64(ctx.BlockHeight()) - gauge := types.NewGauge(btcStakingReward) + gauge := types.NewGauge(btcStakingReward...) k.SetBTCStakingGauge(ctx, height, gauge) // transfer the BTC staking reward from fee collector account to incentive module account @@ -66,16 +56,16 @@ func (k Keeper) SetBTCStakingGauge(ctx sdk.Context, height uint64, gauge *types. store.Set(sdk.Uint64ToBigEndian(height), gaugeBytes) } -func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) (*types.Gauge, error) { +func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) *types.Gauge { store := k.btcStakingGaugeStore(ctx) gaugeBytes := store.Get(sdk.Uint64ToBigEndian(height)) - if len(gaugeBytes) == 0 { - return nil, types.ErrBTCStakingGaugeNotFound + if gaugeBytes == nil { + return nil } var gauge types.Gauge k.cdc.MustUnmarshal(gaugeBytes, &gauge) - return &gauge, nil + return &gauge } // btcStakingGaugeStore returns the KVStore of the gauge of total reward for diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go index 1f3450087..067275726 100644 --- a/x/incentive/keeper/btc_staking_gauge_test.go +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -36,23 +36,26 @@ func FuzzRewardBTCStaking(f *testing.F) { rdc, err := datagen.GenRandomBTCStakingRewardDistCache(r) require.NoError(t, err) - // mock transfer to ensure reward is distributed correctly + // expected values distributedCoins := sdk.NewCoins() + btcValRewardMap := map[string]sdk.Coins{} // key: address, value: reward + btcDelRewardMap := map[string]sdk.Coins{} // key: address, value: reward + for _, btcVal := range rdc.BtcVals { btcValPortion := rdc.GetBTCValPortion(btcVal) coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) if coinsForCommission.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(btcVal.GetAddress()), gomock.Eq(coinsForCommission)).Return(nil).Times(1) - distributedCoins = distributedCoins.Add(coinsForCommission...) + btcValRewardMap[btcVal.GetAddress().String()] = coinsForCommission + distributedCoins.Add(coinsForCommission...) } coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) for _, btcDel := range btcVal.BtcDels { btcDelPortion := btcVal.GetBTCDelPortion(btcDel) coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) if coinsForDel.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(btcDel.GetAddress()), gomock.Eq(coinsForDel)).Return(nil).Times(1) - distributedCoins = distributedCoins.Add(coinsForDel...) + btcDelRewardMap[btcDel.GetAddress().String()] = coinsForDel + distributedCoins.Add(coinsForDel...) } } } @@ -60,6 +63,22 @@ func FuzzRewardBTCStaking(f *testing.F) { // distribute rewards in the gauge to BTC validators/delegations keeper.RewardBTCStaking(ctx, height, rdc) + // assert consistency between reward map and reward gauge + for addrStr, reward := range btcValRewardMap { + addr, err := sdk.AccAddressFromBech32(addrStr) + require.NoError(t, err) + rg := keeper.GetRewardGauge(ctx, types.BTCValidatorType, addr) + require.NotNil(t, rg) + require.Equal(t, reward, rg.Coins) + } + for addrStr, reward := range btcDelRewardMap { + addr, err := sdk.AccAddressFromBech32(addrStr) + require.NoError(t, err) + rg := keeper.GetRewardGauge(ctx, types.BTCDelegationType, addr) + require.NotNil(t, rg) + require.Equal(t, reward, rg.Coins) + } + // assert distributedCoins is a subset of coins in gauge require.True(t, gauge.Coins.IsAllGTE(distributedCoins)) }) diff --git a/x/incentive/keeper/btc_timestamping_gauge.go b/x/incentive/keeper/btc_timestamping_gauge.go index 4b722ed02..232c8c78b 100644 --- a/x/incentive/keeper/btc_timestamping_gauge.go +++ b/x/incentive/keeper/btc_timestamping_gauge.go @@ -11,10 +11,10 @@ import ( // RewardBTCTimestamping distributes rewards to submitters/reporters of a checkpoint at a given epoch // according to the reward distribution cache func (k Keeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rdi *btcctypes.RewardDistInfo) { - gauge, err := k.GetBTCTimestampingGauge(ctx, epoch) - if err != nil { + gauge := k.GetBTCTimestampingGauge(ctx, epoch) + if gauge == nil { // failing to get a reward gauge at a finalised epoch is a programming error - panic(err) + panic("failed to get a reward gauge at a finalized epoch") } params := k.GetParams(ctx) @@ -26,97 +26,64 @@ func (k Keeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rdi *btccty submitterPortion := params.SubmitterPortion.QuoTruncate(btcTimestampingPortion) coinsToSubmitters := gauge.GetCoinsPortion(submitterPortion) coinsToBestSubmitter := types.GetCoinsPortion(coinsToSubmitters, bestPortion) - if coinsToBestSubmitter.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Submitter, coinsToBestSubmitter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.SubmitterType, rdi.Best.Submitter, coinsToBestSubmitter) + restCoinsToSubmitters := coinsToSubmitters.Sub(coinsToBestSubmitter...) + // distribute coins to best reporter reporterPortion := params.ReporterPortion.QuoTruncate(btcTimestampingPortion) coinsToReporters := gauge.GetCoinsPortion(reporterPortion) coinsToBestReporter := types.GetCoinsPortion(coinsToReporters, bestPortion) - if coinsToBestReporter.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Reporter, coinsToBestReporter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.ReporterType, rdi.Best.Reporter, coinsToBestReporter) + restCoinsToReporters := coinsToReporters.Sub(coinsToBestReporter...) // if there is only 1 submission, distribute the rest to submitter and reporter, then skip the rest logic if len(rdi.Others) == 0 { // give rest coins to the best submitter - restCoinsToSubmitter := coinsToSubmitters.Sub(coinsToBestSubmitter...) - if restCoinsToSubmitter.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Submitter, restCoinsToSubmitter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.SubmitterType, rdi.Best.Submitter, restCoinsToSubmitters) // give rest coins to the best reporter - restCoinsToReporter := coinsToReporters.Sub(coinsToBestReporter...) - if restCoinsToReporter.IsAllPositive() { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, rdi.Best.Reporter, restCoinsToReporter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } - } + k.accumulateRewardGauge(ctx, types.ReporterType, rdi.Best.Reporter, restCoinsToReporters) // skip the rest logic return } // distribute the rest to each of the other submitters // TODO: our tokenomics might specify weights for the rest submitters in the future - coinsToOtherSubmitters := coinsToSubmitters.Sub(coinsToBestSubmitter...) eachOtherSubmitterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) - coinsToEachOtherSubmitter := types.GetCoinsPortion(coinsToOtherSubmitters, eachOtherSubmitterPortion) + coinsToEachOtherSubmitter := types.GetCoinsPortion(restCoinsToSubmitters, eachOtherSubmitterPortion) if coinsToEachOtherSubmitter.IsAllPositive() { for _, submission := range rdi.Others { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, submission.Submitter, coinsToEachOtherSubmitter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } + k.accumulateRewardGauge(ctx, types.SubmitterType, submission.Submitter, coinsToEachOtherSubmitter) } } // distribute the rest to each of the other reporters // TODO: our tokenomics might specify weights for the rest reporters in the future - coinsToOtherReporters := coinsToReporters.Sub(coinsToBestReporter...) eachOtherReporterPortion := math.LegacyOneDec().QuoTruncate(math.LegacyOneDec().MulInt64(int64(len(rdi.Others)))) - coinsToEachOtherReporter := types.GetCoinsPortion(coinsToOtherReporters, eachOtherReporterPortion) + coinsToEachOtherReporter := types.GetCoinsPortion(restCoinsToReporters, eachOtherReporterPortion) if coinsToEachOtherReporter.IsAllPositive() { for _, submission := range rdi.Others { - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, submission.Reporter, coinsToEachOtherReporter); err != nil { - // incentive module account is supposed to have enough balance - panic(err) - } + k.accumulateRewardGauge(ctx, types.ReporterType, submission.Reporter, coinsToEachOtherReporter) } } } func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestampingReward sdk.Coins) { - var ( - epoch = k.epochingKeeper.GetEpoch(ctx) - gauge *types.Gauge - err error - ) + epoch := k.epochingKeeper.GetEpoch(ctx) // update BTC timestamping reward gauge - if k.HasBTCTimestampingGauge(ctx, epoch.EpochNumber) { - // if this epoch already has an non-empty gauge, accumulate - gauge, err = k.GetBTCTimestampingGauge(ctx, epoch.EpochNumber) - if err != nil { - panic(err) // only programming error is possible - } - gauge.Coins = gauge.Coins.Add(btcTimestampingReward...) // accumulate coins in the gauge - } else { + gauge := k.GetBTCTimestampingGauge(ctx, epoch.EpochNumber) + if gauge == nil { // if this epoch does not have a gauge yet, create a new one - gauge = types.NewGauge(btcTimestampingReward) + gauge = types.NewGauge(btcTimestampingReward...) + } else { + // if this epoch already has a gauge, accumulate coins in the gauge + gauge.Coins = gauge.Coins.Add(btcTimestampingReward...) } + k.SetBTCTimestampingGauge(ctx, epoch.EpochNumber, gauge) // transfer the BTC timestamping reward from fee collector account to incentive module account - err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcTimestampingReward) + err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcTimestampingReward) if err != nil { // this can only be programming error and is unrecoverable panic(err) @@ -129,21 +96,16 @@ func (k Keeper) SetBTCTimestampingGauge(ctx sdk.Context, epoch uint64, gauge *ty store.Set(sdk.Uint64ToBigEndian(epoch), gaugeBytes) } -func (k Keeper) HasBTCTimestampingGauge(ctx sdk.Context, epoch uint64) bool { - store := k.btcTimestampingGaugeStore(ctx) - return store.Has(sdk.Uint64ToBigEndian(epoch)) -} - -func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) (*types.Gauge, error) { +func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) *types.Gauge { store := k.btcTimestampingGaugeStore(ctx) gaugeBytes := store.Get(sdk.Uint64ToBigEndian(epoch)) - if len(gaugeBytes) == 0 { - return nil, types.ErrBTCTimestampingGaugeNotFound + if gaugeBytes == nil { + return nil } var gauge types.Gauge k.cdc.MustUnmarshal(gaugeBytes, &gauge) - return &gauge, nil + return &gauge } // btcTimestampingGaugeStore returns the KVStore of the gauge of total reward for diff --git a/x/incentive/keeper/btc_timestamping_gauge_test.go b/x/incentive/keeper/btc_timestamping_gauge_test.go index 73f6ca61f..d7173a3ac 100644 --- a/x/incentive/keeper/btc_timestamping_gauge_test.go +++ b/x/incentive/keeper/btc_timestamping_gauge_test.go @@ -40,14 +40,16 @@ func FuzzRewardBTCTimestamping(f *testing.F) { btcTimestampingPortion := params.BTCTimestampingPortion() bestPortion := math.LegacyNewDecWithPrec(80, 2) // 80 * 10^{-2} = 0.8 - // mock bank transfer here - // best submitter + // expected values distributedCoins := sdk.NewCoins() + submitterRewardMap := map[string]sdk.Coins{} // key: address, value: reward + reporterRewardMap := map[string]sdk.Coins{} // key: address, value: reward + submitterPortion := params.SubmitterPortion.QuoTruncate(btcTimestampingPortion) coinsToSubmitters := gauge.GetCoinsPortion(submitterPortion) coinsToBestSubmitter := types.GetCoinsPortion(coinsToSubmitters, bestPortion) if coinsToBestSubmitter.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Submitter), gomock.Eq(coinsToBestSubmitter)).Times(1) + submitterRewardMap[rdi.Best.Submitter.String()] = coinsToBestSubmitter distributedCoins.Add(coinsToBestSubmitter...) } // best reporter @@ -55,7 +57,7 @@ func FuzzRewardBTCTimestamping(f *testing.F) { coinsToReporters := gauge.GetCoinsPortion(reporterPortion) coinsToBestReporter := types.GetCoinsPortion(coinsToReporters, bestPortion) if coinsToBestReporter.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Reporter), gomock.Eq(coinsToBestReporter)).Times(1) + reporterRewardMap[rdi.Best.Reporter.String()] = coinsToBestReporter distributedCoins.Add(coinsToBestReporter...) } // other submitters and reporters @@ -66,9 +68,9 @@ func FuzzRewardBTCTimestamping(f *testing.F) { coinsToEachOtherSubmitter := types.GetCoinsPortion(coinsToOtherSubmitters, eachOtherSubmitterPortion) if coinsToEachOtherSubmitter.IsAllPositive() { for _, submission := range rdi.Others { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(submission.Submitter), gomock.Eq(coinsToEachOtherSubmitter)).Times(1) + submitterRewardMap[submission.Submitter.String()] = coinsToEachOtherSubmitter + distributedCoins.Add(coinsToEachOtherSubmitter...) } - distributedCoins.Add(coinsToEachOtherSubmitter...) } // other reporters coinsToOtherReporters := coinsToReporters.Sub(coinsToBestReporter...) @@ -76,27 +78,45 @@ func FuzzRewardBTCTimestamping(f *testing.F) { coinsToEachOtherReporter := types.GetCoinsPortion(coinsToOtherReporters, eachOtherReporterPortion) if coinsToEachOtherReporter.IsAllPositive() { for _, submission := range rdi.Others { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(submission.Reporter), gomock.Eq(coinsToEachOtherReporter)).Times(1) + reporterRewardMap[submission.Reporter.String()] = coinsToEachOtherReporter + distributedCoins.Add(coinsToEachOtherReporter...) } - distributedCoins.Add(coinsToEachOtherReporter...) } } else { // no other submission. give rest coins to best submitter/reporter // give rest coins to the best submitter restCoinsToSubmitter := coinsToSubmitters.Sub(coinsToBestSubmitter...) if restCoinsToSubmitter.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Submitter), gomock.Eq(restCoinsToSubmitter)).Times(1) + submitterRewardMap[rdi.Best.Submitter.String()] = submitterRewardMap[rdi.Best.Submitter.String()].Add(restCoinsToSubmitter...) + distributedCoins.Add(restCoinsToSubmitter...) } // give rest coins to the best reporter restCoinsToReporter := coinsToReporters.Sub(coinsToBestReporter...) if restCoinsToReporter.IsAllPositive() { - bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gomock.Any(), gomock.Eq(types.ModuleName), gomock.Eq(rdi.Best.Reporter), gomock.Eq(restCoinsToReporter)).Times(1) + reporterRewardMap[rdi.Best.Reporter.String()] = reporterRewardMap[rdi.Best.Reporter.String()].Add(restCoinsToSubmitter...) + distributedCoins.Add(restCoinsToReporter...) } } // distribute rewards in the gauge to BTC validators/delegations keeper.RewardBTCTimestamping(ctx, epoch, rdi) + // assert consistency between reward map and reward gauge + for addrStr, reward := range submitterRewardMap { + addr, err := sdk.AccAddressFromBech32(addrStr) + require.NoError(t, err) + rg := keeper.GetRewardGauge(ctx, types.SubmitterType, addr) + require.NotNil(t, rg) + require.Equal(t, reward, rg.Coins) + } + for addrStr, reward := range reporterRewardMap { + addr, err := sdk.AccAddressFromBech32(addrStr) + require.NoError(t, err) + rg := keeper.GetRewardGauge(ctx, types.ReporterType, addr) + require.NotNil(t, rg) + require.Equal(t, reward, rg.Coins) + } + // assert distributedCoins is a subset of coins in gauge require.True(t, gauge.Coins.IsAllGTE(distributedCoins)) }) diff --git a/x/incentive/keeper/grpc_query.go b/x/incentive/keeper/grpc_query.go index a0d4c6311..341359b7d 100644 --- a/x/incentive/keeper/grpc_query.go +++ b/x/incentive/keeper/grpc_query.go @@ -27,17 +27,18 @@ func (k Keeper) RewardGauges(goCtx context.Context, req *types.QueryRewardGauges // find reward gauge for _, sType := range types.GetAllStakeholderTypes() { - if !k.HasRewardGauge(ctx, sType, address) { + rg := k.GetRewardGauge(ctx, sType, address) + if rg == nil { continue } - rg, err := k.GetRewardGauge(ctx, sType, address) - if err != nil { - // only programming error is possible - panic("failed to get an existing reward gauge") - } rgMap[sType.String()] = rg } + // return error if no reward gauge is found + if len(rgMap) == 0 { + return nil, types.ErrRewardGaugeNotFound + } + return &types.QueryRewardGaugesResponse{RewardGauges: rgMap}, nil } @@ -48,9 +49,9 @@ func (k Keeper) BTCStakingGauge(goCtx context.Context, req *types.QueryBTCStakin ctx := sdk.UnwrapSDKContext(goCtx) // find gauge - gauge, err := k.GetBTCStakingGauge(ctx, req.Height) - if err != nil { - return nil, err + gauge := k.GetBTCStakingGauge(ctx, req.Height) + if gauge == nil { + return nil, types.ErrBTCStakingGaugeNotFound } return &types.QueryBTCStakingGaugeResponse{Gauge: gauge}, nil @@ -63,9 +64,9 @@ func (k Keeper) BTCTimestampingGauge(goCtx context.Context, req *types.QueryBTCT ctx := sdk.UnwrapSDKContext(goCtx) // find gauge - gauge, err := k.GetBTCTimestampingGauge(ctx, req.EpochNum) - if err != nil { - return nil, err + gauge := k.GetBTCTimestampingGauge(ctx, req.EpochNum) + if gauge == nil { + return nil, types.ErrBTCTimestampingGaugeNotFound } return &types.QueryBTCTimestampingGaugeResponse{Gauge: gauge}, nil diff --git a/x/incentive/keeper/grpc_query_test.go b/x/incentive/keeper/grpc_query_test.go index e9a604b92..d0ad4ae8d 100644 --- a/x/incentive/keeper/grpc_query_test.go +++ b/x/incentive/keeper/grpc_query_test.go @@ -28,7 +28,7 @@ func FuzzRewardGaugesQuery(f *testing.F) { rgMap := map[string]*types.RewardGauge{} sAddr := datagen.GenRandomAccount().GetAddress() sAddrList = append(sAddrList, sAddr) - for i := uint64(0); i < datagen.RandomInt(r, 4); i++ { + for i := uint64(0); i <= datagen.RandomInt(r, 4); i++ { sType := datagen.GenRandomStakeholderType(r) rg := datagen.GenRandomRewardGauge(r) rgMap[sType.String()] = rg diff --git a/x/incentive/keeper/intercept_fee_collector_test.go b/x/incentive/keeper/intercept_fee_collector_test.go index c64973213..c3ddb6d1d 100644 --- a/x/incentive/keeper/intercept_fee_collector_test.go +++ b/x/incentive/keeper/intercept_fee_collector_test.go @@ -57,14 +57,14 @@ func FuzzInterceptFeeCollector(f *testing.F) { // assert correctness of BTC staking gauge at height btcStakingFee := types.GetCoinsPortion(fees, params.BTCStakingPortion()) - btcStakingGauge, err := keeper.GetBTCStakingGauge(ctx, height) - require.NoError(t, err) + btcStakingGauge := keeper.GetBTCStakingGauge(ctx, height) + require.NotNil(t, btcStakingGauge) require.Equal(t, btcStakingFee, btcStakingGauge.Coins) // assert correctness of BTC timestamping gauge at epoch btcTimestampingFee := types.GetCoinsPortion(fees, params.BTCTimestampingPortion()) - btcTimestampingGauge, err := keeper.GetBTCTimestampingGauge(ctx, epochNum) - require.NoError(t, err) + btcTimestampingGauge := keeper.GetBTCTimestampingGauge(ctx, epochNum) + require.NotNil(t, btcTimestampingGauge) require.Equal(t, btcTimestampingFee, btcTimestampingGauge.Coins) // accumulate for this epoch again and see if the epoch's BTC timestamping gauge has accumulated or not @@ -78,8 +78,8 @@ func FuzzInterceptFeeCollector(f *testing.F) { // handle coins in fee collector keeper.HandleCoinsInFeeCollector(ctx) // assert BTC timestamping gauge has doubled - btcTimestampingGauge2, err := keeper.GetBTCTimestampingGauge(ctx, epochNum) - require.NoError(t, err) + btcTimestampingGauge2 := keeper.GetBTCTimestampingGauge(ctx, epochNum) + require.NotNil(t, btcTimestampingGauge2) for i := range btcTimestampingGauge.Coins { amount := btcTimestampingGauge.Coins[i].Amount.Uint64() amount2 := btcTimestampingGauge2.Coins[i].Amount.Uint64() diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go index bd724b2a3..b545deb5e 100644 --- a/x/incentive/keeper/msg_server_test.go +++ b/x/incentive/keeper/msg_server_test.go @@ -60,8 +60,8 @@ func FuzzWithdrawReward(f *testing.F) { require.Equal(t, withdrawableCoins, resp.Coins) // ensure reward gauge is now empty - newRg, err := ik.GetRewardGauge(ctx, sType, sAddr) - require.NoError(t, err) - require.True(t, newRg.IsEmpty()) + newRg := ik.GetRewardGauge(ctx, sType, sAddr) + require.NotNil(t, newRg) + require.True(t, newRg.IsFullyWithdrawn()) }) } diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go index 6124760b0..5389672d9 100644 --- a/x/incentive/keeper/reward_gauge.go +++ b/x/incentive/keeper/reward_gauge.go @@ -8,13 +8,13 @@ import ( func (k Keeper) withdrawReward(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (sdk.Coins, error) { // retrieve reward gauge of the given stakeholder - rg, err := k.GetRewardGauge(ctx, sType, addr) - if err != nil { - return nil, err + rg := k.GetRewardGauge(ctx, sType, addr) + if rg == nil { + return nil, types.ErrRewardGaugeNotFound } // get withdrawable coins withdrawableCoins := rg.GetWithdrawableCoins() - if len(withdrawableCoins) == 0 { + if !withdrawableCoins.IsAllPositive() { return nil, types.ErrNoWithdrawableCoins } // transfer withdrawable coins from incentive module account to the stakeholder's address @@ -22,33 +22,45 @@ func (k Keeper) withdrawReward(ctx sdk.Context, sType types.StakeholderType, add return nil, err } // empty reward gauge - rg.Clear() + rg.SetFullyWithdrawn() k.SetRewardGauge(ctx, sType, addr, rg) // all good, return return withdrawableCoins, nil } -func (k Keeper) SetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { - store := k.rewardGaugeStore(ctx, sType) - rgBytes := k.cdc.MustMarshal(rg) - store.Set(addr, rgBytes) +// accumulateRewardGauge accumulates the given reward of of a given stakeholder in a given type +func (k Keeper) accumulateRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, reward sdk.Coins) { + // if reward contains nothing, do nothing + if !reward.IsAllPositive() { + return + } + // get reward gauge, or create a new one if it does not exist + rg := k.GetRewardGauge(ctx, sType, addr) + if rg == nil { + rg = types.NewRewardGauge() + } + // add the given reward to reward gauge + rg.Add(reward) + // set back + k.SetRewardGauge(ctx, sType, addr, rg) } -func (k Keeper) HasRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) bool { +func (k Keeper) SetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { store := k.rewardGaugeStore(ctx, sType) - return store.Has(addr) + rgBytes := k.cdc.MustMarshal(rg) + store.Set(addr.Bytes(), rgBytes) } -func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (*types.RewardGauge, error) { +func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) *types.RewardGauge { store := k.rewardGaugeStore(ctx, sType) - rgBytes := store.Get(addr) - if len(rgBytes) == 0 { - return nil, types.ErrRewardGaugeNotFound + rgBytes := store.Get(addr.Bytes()) + if rgBytes == nil { + return nil } var rg types.RewardGauge k.cdc.MustUnmarshal(rgBytes, &rg) - return &rg, nil + return &rg } // rewardGaugeStore returns the KVStore of the reward gauge of a stakeholder diff --git a/x/incentive/keeper/reward_gauge_test.go b/x/incentive/keeper/reward_gauge_test.go new file mode 100644 index 000000000..74a840c13 --- /dev/null +++ b/x/incentive/keeper/reward_gauge_test.go @@ -0,0 +1,20 @@ +package keeper_test + +import ( + "testing" + + "github.com/babylonchain/babylon/x/incentive/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestEmptyRewardGauge(t *testing.T) { + emptyRewardGauge := &types.RewardGauge{ + Coins: sdk.NewCoins(), + WithdrawnCoins: sdk.NewCoins(), + } + rgBytes, err := emptyRewardGauge.Marshal() + require.NoError(t, err) + require.NotNil(t, rgBytes) // the marshaled empty reward gauge is not nil + require.True(t, len(rgBytes) == 0) // the marshalled empty reward gauge has 0 bytes +} diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index dd51687ac..59ce080f0 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewGauge(coins sdk.Coins) *Gauge { +func NewGauge(coins ...sdk.Coin) *Gauge { return &Gauge{ Coins: coins, } @@ -17,7 +17,7 @@ func (g *Gauge) GetCoinsPortion(portion math.LegacyDec) sdk.Coins { return GetCoinsPortion(g.Coins, portion) } -func NewRewardGauge(coins sdk.Coins) *RewardGauge { +func NewRewardGauge(coins ...sdk.Coin) *RewardGauge { return &RewardGauge{ Coins: coins, WithdrawnCoins: sdk.NewCoins(), @@ -26,35 +26,24 @@ func NewRewardGauge(coins sdk.Coins) *RewardGauge { // GetWithdrawableCoins returns withdrawable coins in this reward gauge func (rg *RewardGauge) GetWithdrawableCoins() sdk.Coins { - withdrawableCoins := sdk.NewCoins() - for _, coin := range rg.Coins { - found, withdrawnCoin := rg.WithdrawnCoins.Find(coin.Denom) - // if the coin is not found in withdrawn coins, all of this coin is withdrawable - if !found { - withdrawableCoins = withdrawableCoins.Add(coin) - continue - } - // if the withdrawable amount is positive, then the coin with this amount is withdrawable - withdrawableCoinAmount := coin.Amount.Sub(withdrawnCoin.Amount) - if withdrawableCoinAmount.IsPositive() { - withdrawableCoin := sdk.NewCoin(coin.Denom, withdrawableCoinAmount) - withdrawableCoins = withdrawableCoins.Add(withdrawableCoin) - } - } - return withdrawableCoins + return rg.Coins.Sub(rg.WithdrawnCoins...) } -// Clear makes the reward gauge to have no withdrawable coins +// SetFullyWithdrawn makes the reward gauge to have no withdrawable coins // typically called after the stakeholder withdraws its reward -func (rg *RewardGauge) Clear() { +func (rg *RewardGauge) SetFullyWithdrawn() { rg.WithdrawnCoins = sdk.NewCoins(rg.Coins...) } -// IsEmpty returns whether the reward gauge has nothing to withdraw -func (rg *RewardGauge) IsEmpty() bool { +// IsFullyWithdrawn returns whether the reward gauge has nothing to withdraw +func (rg *RewardGauge) IsFullyWithdrawn() bool { return rg.Coins.IsEqual(rg.WithdrawnCoins) } +func (rg *RewardGauge) Add(coins sdk.Coins) { + rg.Coins = rg.Coins.Add(coins...) +} + func GetCoinsPortion(coinsInt sdk.Coins, portion math.LegacyDec) sdk.Coins { // coins with decimal value coins := sdk.NewDecCoinsFromCoins(coinsInt...) @@ -111,18 +100,18 @@ func NewStakeHolderTypeFromString(stStr string) (StakeholderType, error) { } } -func (st *StakeholderType) Bytes() []byte { - return []byte{byte(*st)} +func (st StakeholderType) Bytes() []byte { + return []byte{byte(st)} } -func (st *StakeholderType) String() string { - if *st == SubmitterType { +func (st StakeholderType) String() string { + if st == SubmitterType { return "submitter" - } else if *st == ReporterType { + } else if st == ReporterType { return "reporter" - } else if *st == BTCValidatorType { + } else if st == BTCValidatorType { return "btc_validator" - } else if *st == BTCDelegationType { + } else if st == BTCDelegationType { return "btc_delegation" } panic("invalid stakeholder type") From adca8416eb708c69f11a60a7dbc96756396ecfc6 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 11 Sep 2023 10:57:28 +1000 Subject: [PATCH 079/202] incentive: access control of withdrawing rewards (#77) --- proto/babylon/incentive/tx.proto | 7 +- test/e2e/btc_staking_e2e_test.go | 30 ++++--- test/e2e/btc_timestamping_e2e_test.go | 6 +- test/e2e/configurer/chain/commands.go | 11 +-- test/e2e/initialization/export.go | 7 +- test/e2e/initialization/node.go | 4 +- testutil/datagen/btcstaking.go | 14 ++- x/incentive/client/cli/tx.go | 9 +- x/incentive/keeper/msg_server_test.go | 1 - x/incentive/types/msg.go | 7 +- x/incentive/types/tx.pb.go | 121 ++++++++------------------ 11 files changed, 89 insertions(+), 128 deletions(-) diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto index cd478e3e6..a491d5437 100644 --- a/proto/babylon/incentive/tx.proto +++ b/proto/babylon/incentive/tx.proto @@ -20,12 +20,11 @@ service Msg { // MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. message MsgWithdrawReward { - string signer = 1; - // {submitter, reporter, btc_validator, btc_delegation} - string type = 2; + string type = 1; // address is the address of the stakeholder in bech32 string - string address = 3; + // signer of this msg has to be this address + string address = 2; } // MsgWithdrawRewardResponse is the response to the MsgWithdrawReward message diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 19c932529..d4df464d1 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -30,13 +30,10 @@ var ( r = rand.New(rand.NewSource(time.Now().Unix())) net = &chaincfg.SimNetParams // BTC validator - valSK, _, _ = datagen.GenRandomBTCKeyPair(r) - btcVal, _ = datagen.GenRandomBTCValidatorWithBTCSK(r, valSK) - btcValBabylonAddr = sdk.AccAddress(btcVal.BabylonPk.Address()) + valBTCSK, _, _ = datagen.GenRandomBTCKeyPair(r) + btcVal *bstypes.BTCValidator // BTC delegation - delBabylonSK, delBabylonPK, _ = datagen.GenRandomSecp256k1KeyPair(r) - delBabylonAddr = sdk.AccAddress(delBabylonPK.Address()) - delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) + delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) // jury jurySK, _ = btcec.PrivKeyFromBytes( []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, @@ -83,6 +80,9 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { /* create a random BTC validator on Babylon */ + // NOTE: we use the node's secret key as Babylon secret key for the BTC validator + btcVal, err = datagen.GenRandomBTCValidatorWithBTCBabylonSKs(r, valBTCSK, nonValidatorNode.SecretKey) + s.NoError(err) nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop, btcVal.Description.Moniker, btcVal.Description.Identity, btcVal.Description.Website, btcVal.Description.SecurityContact, btcVal.Description.Details, btcVal.Commission) // wait for a block so that above txs take effect @@ -98,6 +98,8 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { */ // BTC staking params, BTC delegation key pairs and PoP params := nonValidatorNode.QueryBTCStakingParams() + // NOTE: we use the node's secret key as Babylon secret key for the BTC delegation + delBabylonSK := nonValidatorNode.SecretKey pop, err := bstypes.NewPoP(delBabylonSK, delBTCSK) s.NoError(err) // generate staking tx and slashing tx @@ -136,7 +138,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { stakingTxInfo := btcctypes.NewTransactionInfoFromSpvProof(blockWithStakingTx.SpvProof) // submit the message for creating BTC delegation - nonValidatorNode.CreateBTCDelegation(delBabylonPK.(*secp256k1.PubKey), pop, stakingTx, stakingTxInfo, slashingTx, delegatorSig) + nonValidatorNode.CreateBTCDelegation(delBabylonSK.PubKey().(*secp256k1.PubKey), pop, stakingTx, stakingTxInfo, slashingTx, delegatorSig) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() @@ -229,7 +231,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat commit a number of public randomness since activatedHeight */ // commit public randomness list - srList, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, valSK, activatedHeight, 100) + srList, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, valBTCSK, activatedHeight, 100) s.NoError(err) nonValidatorNode.CommitPubRandList( msgCommitPubRandList.ValBtcPk, @@ -248,8 +250,10 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.Equal(pubRandMap[activatedHeight].MustMarshal(), msgCommitPubRandList.PubRandList[0].MustMarshal()) // no reward gauge for BTC validator and delegation yet + btcValBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) _, err = nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) s.Error(err) + delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) _, err = nonValidatorNode.QueryRewardGauge(delBabylonAddr) s.Error(err) @@ -261,7 +265,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NoError(err) msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), blockToVote.LastCommitHash...) // generate EOTS signature - sig, err := eots.Sign(valSK, srList[0], msgToSign) + sig, err := eots.Sign(valBTCSK, srList[0], msgToSign) s.NoError(err) eotsSig := bbn.NewSchnorrEOTSSigFromModNScalar(sig) // submit finality signature @@ -303,6 +307,8 @@ func (s *BTCStakingTestSuite) Test4WithdrawReward() { s.NoError(err) // BTC validator balance before withdraw + btcValBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) + delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) btcValBalance, err := nonValidatorNode.QueryBalances(btcValBabylonAddr.String()) s.NoError(err) // BTC validator reward gauge should not be fully withdrawn @@ -313,7 +319,7 @@ func (s *BTCStakingTestSuite) Test4WithdrawReward() { s.False(btcValRg.IsFullyWithdrawn()) // withdraw BTC validator reward - nonValidatorNode.WithdrawReward(itypes.BTCValidatorType.String(), btcValBabylonAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WithdrawReward(itypes.BTCValidatorType.String(), initialization.ValidatorWalletName) nonValidatorNode.WaitForNextBlock() // balance after withdrawing BTC validator reward @@ -339,7 +345,7 @@ func (s *BTCStakingTestSuite) Test4WithdrawReward() { s.False(btcDelRg.IsFullyWithdrawn()) // withdraw BTC delegation reward - nonValidatorNode.WithdrawReward(itypes.BTCDelegationType.String(), delBabylonAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WithdrawReward(itypes.BTCDelegationType.String(), initialization.ValidatorWalletName) nonValidatorNode.WaitForNextBlock() // balance after withdrawing BTC delegation reward @@ -452,7 +458,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { validatorUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( stakingTxMsg, delegation.StakingTx.Script, - valSK, + valBTCSK, net, ) s.NoError(err) diff --git a/test/e2e/btc_timestamping_e2e_test.go b/test/e2e/btc_timestamping_e2e_test.go index f47fa5a4f..33aa3c8e8 100644 --- a/test/e2e/btc_timestamping_e2e_test.go +++ b/test/e2e/btc_timestamping_e2e_test.go @@ -179,6 +179,8 @@ func (s *BTCTimestampingTestSuite) Test5WithdrawReward() { nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) + // NOTE: nonValidatorNode.PublicAddress is the address associated with key name `val` + // and is both the submitter and reporter submitterReporterAddr := sdk.MustAccAddressFromBech32(nonValidatorNode.PublicAddress) // balance before withdraw @@ -193,7 +195,7 @@ func (s *BTCTimestampingTestSuite) Test5WithdrawReward() { s.False(reporterRg.IsFullyWithdrawn()) // withdraw submitter reward - nonValidatorNode.WithdrawReward(itypes.SubmitterType.String(), submitterReporterAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WithdrawReward(itypes.SubmitterType.String(), initialization.ValidatorWalletName) nonValidatorNode.WaitForNextBlock() // balance after withdrawing submitter reward @@ -210,7 +212,7 @@ func (s *BTCTimestampingTestSuite) Test5WithdrawReward() { s.True(rgs2[itypes.SubmitterType.String()].IsFullyWithdrawn()) // withdraw reporter reward - nonValidatorNode.WithdrawReward(itypes.ReporterType.String(), submitterReporterAddr.String(), initialization.ValidatorWalletName) + nonValidatorNode.WithdrawReward(itypes.ReporterType.String(), initialization.ValidatorWalletName) nonValidatorNode.WaitForNextBlock() // balance after withdrawing reporter reward diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index f38c500d5..a085f8e53 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -119,10 +119,10 @@ func (n *NodeConfig) FinalizeSealedEpochs(startEpoch uint64, lastEpoch uint64) { currentBtcTip, err := n.QueryTip() require.NoError(n.t, err) - _, c, err := bech32.DecodeAndConvert(n.PublicAddress) + _, submitterAddr, err := bech32.DecodeAndConvert(n.PublicAddress) require.NoError(n.t, err) - btcCheckpoint, err := cttypes.FromRawCkptToBTCCkpt(checkpoint.Ckpt, c) + btcCheckpoint, err := cttypes.FromRawCkptToBTCCkpt(checkpoint.Ckpt, submitterAddr) require.NoError(n.t, err) babylonTagBytes, err := hex.DecodeString(initialization.BabylonOpReturnTag) @@ -191,9 +191,10 @@ func (n *NodeConfig) WasmExecute(contract, execMsg, from string) { n.LogActionF("successfully executed") } -func (n *NodeConfig) WithdrawReward(sType, sAddr, from string) { - n.LogActionF("withdraw reward of type %s address %s", sType, sAddr) - cmd := []string{"babylond", "tx", "incentive", "withdraw-reward", sType, sAddr, fmt.Sprintf("--from=%s", from)} +// NOTE: this command will withdraw the reward of the address associated with the tx signer `from` +func (n *NodeConfig) WithdrawReward(sType, from string) { + n.LogActionF("withdraw reward of type %s for tx signer %s", sType, from) + cmd := []string{"babylond", "tx", "incentive", "withdraw-reward", sType, fmt.Sprintf("--from=%s", from)} n.LogActionF(strings.Join(cmd, " ")) _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) diff --git a/test/e2e/initialization/export.go b/test/e2e/initialization/export.go index 8e4224164..570a0cd47 100644 --- a/test/e2e/initialization/export.go +++ b/test/e2e/initialization/export.go @@ -1,6 +1,10 @@ package initialization -import "fmt" +import ( + "fmt" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) type ChainMeta struct { DataDir string `json:"dataDir"` @@ -12,6 +16,7 @@ type Node struct { ConfigDir string `json:"configDir"` Mnemonic string `json:"mnemonic"` PublicAddress string `json:"publicAddress"` + SecretKey cryptotypes.PrivKey PublicKey string `json:"publicKey"` PeerId string `json:"peerId"` IsValidator bool `json:"isValidator"` diff --git a/test/e2e/initialization/node.go b/test/e2e/initialization/node.go index f3ede8b3b..1f0872391 100644 --- a/test/e2e/initialization/node.go +++ b/test/e2e/initialization/node.go @@ -239,7 +239,6 @@ func (n *internalNode) export() *Node { } pub, err := n.keyInfo.GetPubKey() - if err != nil { panic("pub key should be correct") } @@ -249,7 +248,8 @@ func (n *internalNode) export() *Node { ConfigDir: n.configDir(), Mnemonic: n.mnemonic, PublicAddress: addr.String(), - PublicKey: pub.Address().String(), + SecretKey: n.privateKey, + PublicKey: pub.String(), PeerId: n.peerId, IsValidator: n.isValidator, } diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index a4c8be8c3..ed3d97382 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -28,6 +29,14 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { } func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { + bbnSK, _, err := GenRandomSecp256k1KeyPair(r) + if err != nil { + return nil, err + } + return GenRandomBTCValidatorWithBTCBabylonSKs(r, btcSK, bbnSK) +} + +func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKey, bbnSK cryptotypes.PrivKey) (*bstypes.BTCValidator, error) { // commission commission := sdk.NewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] // description @@ -35,10 +44,7 @@ func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst // key pairs btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) - bbnSK, bbnPK, err := GenRandomSecp256k1KeyPair(r) - if err != nil { - return nil, err - } + bbnPK := bbnSK.PubKey() secp256k1PK, ok := bbnPK.(*secp256k1.PubKey) if !ok { return nil, fmt.Errorf("failed to assert bbnPK to *secp256k1.PubKey") diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go index 59afa1a9e..294110267 100644 --- a/x/incentive/client/cli/tx.go +++ b/x/incentive/client/cli/tx.go @@ -35,9 +35,9 @@ func GetTxCmd() *cobra.Command { func NewWithdrawRewardCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "withdraw-reward [type] [address]", - Short: "withdraw reward of a given stakeholder in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", - Args: cobra.ExactArgs(2), + Use: "withdraw-reward [type]", + Short: "withdraw reward of the stakeholder behind the transaction submitter in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { @@ -45,9 +45,8 @@ func NewWithdrawRewardCmd() *cobra.Command { } msg := types.MsgWithdrawReward{ - Signer: clientCtx.FromAddress.String(), Type: args[0], - Address: args[1], + Address: clientCtx.FromAddress.String(), } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go index b545deb5e..b320c7a58 100644 --- a/x/incentive/keeper/msg_server_test.go +++ b/x/incentive/keeper/msg_server_test.go @@ -52,7 +52,6 @@ func FuzzWithdrawReward(f *testing.F) { // invoke withdraw and assert consistency resp, err := ms.WithdrawReward(ctx, &types.MsgWithdrawReward{ - Signer: datagen.GenRandomAccount().Address, Type: sType.String(), Address: sAddr.String(), }) diff --git a/x/incentive/types/msg.go b/x/incentive/types/msg.go index 9614281f6..d6b485c32 100644 --- a/x/incentive/types/msg.go +++ b/x/incentive/types/msg.go @@ -1,8 +1,6 @@ package types import ( - "fmt" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,15 +13,12 @@ var ( // GetSigners returns the expected signers for a MsgUpdateParams message. func (m *MsgWithdrawReward) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Signer) + addr, _ := sdk.AccAddressFromBech32(m.Address) return []sdk.AccAddress{addr} } // ValidateBasic does a sanity check on the provided data. func (m *MsgWithdrawReward) ValidateBasic() error { - if len(m.Signer) == 0 { - return fmt.Errorf("empty signer") - } if _, err := NewStakeHolderTypeFromString(m.Type); err != nil { return err } diff --git a/x/incentive/types/tx.pb.go b/x/incentive/types/tx.pb.go index 63bbbc6fb..a7fa3568d 100644 --- a/x/incentive/types/tx.pb.go +++ b/x/incentive/types/tx.pb.go @@ -34,11 +34,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. type MsgWithdrawReward struct { - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // {submitter, reporter, btc_validator, btc_delegation} - Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // address is the address of the stakeholder in bech32 string - Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + // signer of this msg has to be this address + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` } func (m *MsgWithdrawReward) Reset() { *m = MsgWithdrawReward{} } @@ -74,13 +74,6 @@ func (m *MsgWithdrawReward) XXX_DiscardUnknown() { var xxx_messageInfo_MsgWithdrawReward proto.InternalMessageInfo -func (m *MsgWithdrawReward) GetSigner() string { - if m != nil { - return m.Signer - } - return "" -} - func (m *MsgWithdrawReward) GetType() string { if m != nil { return m.Type @@ -248,37 +241,36 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/tx.proto", fileDescriptor_b4de6776d39a3a22) } var fileDescriptor_b4de6776d39a3a22 = []byte{ - // 468 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x6b, 0x13, 0x41, - 0x1c, 0xcd, 0x9a, 0x1a, 0xe9, 0x28, 0x95, 0x0e, 0xc5, 0x6e, 0xf6, 0xb0, 0x2d, 0x8b, 0x87, 0x50, - 0xec, 0x8e, 0xa9, 0xa0, 0xe0, 0xcd, 0x78, 0x94, 0x80, 0xac, 0x88, 0xe8, 0x41, 0x99, 0xdd, 0x1d, - 0x26, 0x83, 0x66, 0x66, 0xd9, 0xdf, 0x34, 0x6d, 0x2e, 0x1e, 0xfc, 0x04, 0xe2, 0xc7, 0xf0, 0xe4, - 0xc1, 0x0f, 0x51, 0xf0, 0x52, 0x3c, 0x79, 0x52, 0x49, 0x0e, 0x7e, 0x0d, 0x99, 0x3f, 0xdb, 0xc6, - 0x26, 0x60, 0x4f, 0x33, 0xbf, 0x79, 0x6f, 0xde, 0xbc, 0xf7, 0xfb, 0xed, 0xa2, 0x28, 0xa7, 0xf9, - 0xf4, 0x9d, 0x92, 0x44, 0xc8, 0x82, 0x49, 0x2d, 0x26, 0x8c, 0xe8, 0xe3, 0xb4, 0xaa, 0x95, 0x56, - 0x78, 0xd3, 0x63, 0xe9, 0x19, 0x16, 0x6d, 0x71, 0xc5, 0x95, 0x45, 0x89, 0xd9, 0x39, 0x62, 0xd4, - 0x2d, 0x14, 0x8c, 0x15, 0xbc, 0x71, 0x80, 0x2b, 0x3c, 0xb4, 0xed, 0x2a, 0x32, 0x06, 0x4e, 0x26, - 0x7d, 0xb3, 0x78, 0x20, 0xf6, 0x40, 0x4e, 0x81, 0x91, 0x49, 0x3f, 0x67, 0x9a, 0xf6, 0x49, 0xa1, - 0x84, 0x6c, 0xf0, 0x65, 0x63, 0x15, 0xad, 0xe9, 0xd8, 0x0b, 0x27, 0x2f, 0xd1, 0xe6, 0x10, 0xf8, - 0x0b, 0xa1, 0x47, 0x65, 0x4d, 0x8f, 0x32, 0x76, 0x44, 0xeb, 0x12, 0xdf, 0x42, 0x1d, 0x10, 0x5c, - 0xb2, 0x3a, 0x0c, 0x76, 0x83, 0xde, 0x7a, 0xe6, 0x2b, 0x8c, 0xd1, 0x9a, 0x9e, 0x56, 0x2c, 0xbc, - 0x62, 0x4f, 0xed, 0x1e, 0x87, 0xe8, 0x1a, 0x2d, 0xcb, 0x9a, 0x01, 0x84, 0x6d, 0x7b, 0xdc, 0x94, - 0xc9, 0x7b, 0xd4, 0x5d, 0x92, 0xce, 0x18, 0x54, 0x4a, 0x02, 0xc3, 0x14, 0x5d, 0x35, 0x2e, 0x21, - 0x0c, 0x76, 0xdb, 0xbd, 0xeb, 0x07, 0xdd, 0xd4, 0xc7, 0x35, 0x39, 0x52, 0x9f, 0x23, 0x7d, 0xac, - 0x84, 0x1c, 0xdc, 0x3d, 0xf9, 0xb9, 0xd3, 0xfa, 0xfc, 0x6b, 0xa7, 0xc7, 0x85, 0x1e, 0x1d, 0xe6, - 0x69, 0xa1, 0xc6, 0xbe, 0x37, 0x7e, 0xd9, 0x87, 0xf2, 0x2d, 0x31, 0x5e, 0xc0, 0x5e, 0x80, 0xcc, - 0x29, 0x27, 0x9f, 0x02, 0x74, 0x73, 0x08, 0xfc, 0x79, 0x55, 0x52, 0xcd, 0x9e, 0xda, 0xd0, 0xf8, - 0x3e, 0x5a, 0xa7, 0x87, 0x7a, 0xa4, 0x6a, 0xa1, 0xa7, 0x2e, 0xdc, 0x20, 0xfc, 0xfe, 0x75, 0x7f, - 0xcb, 0xbf, 0xfe, 0xc8, 0x59, 0x7f, 0xa6, 0x6b, 0x21, 0x79, 0x76, 0x4e, 0xc5, 0x0f, 0x50, 0xc7, - 0xb5, 0xcd, 0x66, 0x37, 0x7e, 0x97, 0x86, 0x9a, 0xba, 0x27, 0x06, 0x6b, 0xc6, 0x6f, 0xe6, 0xe9, - 0x0f, 0x37, 0x3e, 0xfc, 0xf9, 0xb2, 0x77, 0x2e, 0x94, 0x74, 0xd1, 0xf6, 0x05, 0x4f, 0x4d, 0x4b, - 0x0e, 0xbe, 0x05, 0xa8, 0x3d, 0x04, 0x8e, 0x4b, 0xb4, 0x71, 0x61, 0x1e, 0xb7, 0x57, 0xbc, 0xb6, - 0xd4, 0xda, 0xe8, 0xce, 0x65, 0x58, 0x67, 0x03, 0x78, 0x8d, 0x6e, 0xfc, 0xd3, 0x99, 0x64, 0xf5, - 0xed, 0x45, 0x4e, 0xb4, 0xf7, 0x7f, 0x4e, 0xa3, 0x3f, 0x78, 0x72, 0x32, 0x8b, 0x83, 0xd3, 0x59, - 0x1c, 0xfc, 0x9e, 0xc5, 0xc1, 0xc7, 0x79, 0xdc, 0x3a, 0x9d, 0xc7, 0xad, 0x1f, 0xf3, 0xb8, 0xf5, - 0xaa, 0xbf, 0x30, 0x48, 0xaf, 0x57, 0x8c, 0xa8, 0x90, 0x4d, 0x41, 0x8e, 0x17, 0xff, 0x22, 0x33, - 0xd7, 0xbc, 0x63, 0x3f, 0xd6, 0x7b, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xa2, 0x06, 0x75, - 0x67, 0x03, 0x00, 0x00, + // 455 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x41, 0x6b, 0xd4, 0x40, + 0x18, 0xdd, 0xb1, 0xb5, 0xd2, 0x51, 0x2a, 0x1d, 0x0a, 0x4d, 0x72, 0x48, 0x4b, 0xf0, 0xb0, 0x14, + 0x9b, 0x31, 0x15, 0x14, 0xbc, 0x35, 0x1e, 0x65, 0x41, 0x22, 0x22, 0x78, 0x50, 0x26, 0xc9, 0x30, + 0x19, 0x34, 0x33, 0x21, 0x33, 0xdd, 0x76, 0x2f, 0x1e, 0xfc, 0x05, 0xe2, 0xcf, 0xf0, 0xe4, 0xc1, + 0x1f, 0x51, 0xf0, 0x52, 0x3c, 0x79, 0x52, 0xd9, 0x3d, 0xf8, 0x37, 0x64, 0x32, 0x93, 0x76, 0x6d, + 0x0a, 0x7a, 0x9a, 0xf9, 0xf6, 0xbd, 0x79, 0xdf, 0xfb, 0xde, 0xb7, 0x81, 0x41, 0x4e, 0xf2, 0xd9, + 0x5b, 0x29, 0x30, 0x17, 0x05, 0x15, 0x9a, 0x4f, 0x29, 0xd6, 0x27, 0x71, 0xd3, 0x4a, 0x2d, 0xd1, + 0xa6, 0xc3, 0xe2, 0x73, 0x2c, 0xd8, 0x62, 0x92, 0xc9, 0x0e, 0xc5, 0xe6, 0x66, 0x89, 0x81, 0x5f, + 0x48, 0x55, 0x4b, 0xf5, 0xda, 0x02, 0xb6, 0x70, 0xd0, 0xb6, 0xad, 0x70, 0xad, 0x18, 0x9e, 0x26, + 0xe6, 0x70, 0x40, 0xe8, 0x80, 0x9c, 0x28, 0x8a, 0xa7, 0x49, 0x4e, 0x35, 0x49, 0x70, 0x21, 0xb9, + 0xe8, 0xf1, 0xa1, 0xb1, 0x86, 0xb4, 0xa4, 0x76, 0xc2, 0xd1, 0x21, 0xdc, 0x9c, 0x28, 0xf6, 0x82, + 0xeb, 0xaa, 0x6c, 0xc9, 0x71, 0x46, 0x8f, 0x49, 0x5b, 0x22, 0x04, 0x57, 0xf5, 0xac, 0xa1, 0x1e, + 0xd8, 0x05, 0xe3, 0xf5, 0xac, 0xbb, 0x23, 0x0f, 0xde, 0x20, 0x65, 0xd9, 0x52, 0xa5, 0xbc, 0x6b, + 0xdd, 0xcf, 0x7d, 0x19, 0xbd, 0x83, 0xfe, 0x40, 0x22, 0xa3, 0xaa, 0x91, 0x42, 0x51, 0x44, 0xe0, + 0x75, 0xe3, 0x46, 0x79, 0x60, 0x77, 0x65, 0x7c, 0xf3, 0xc0, 0x8f, 0xdd, 0x58, 0xc6, 0x6f, 0xec, + 0xfc, 0xc6, 0x8f, 0x25, 0x17, 0xe9, 0xbd, 0xd3, 0x1f, 0x3b, 0xa3, 0x4f, 0x3f, 0x77, 0xc6, 0x8c, + 0xeb, 0xea, 0x28, 0x8f, 0x0b, 0x59, 0xbb, 0x0c, 0xdc, 0xb1, 0xaf, 0xca, 0x37, 0xd8, 0x78, 0x51, + 0xdd, 0x03, 0x95, 0x59, 0xe5, 0xe8, 0x23, 0x80, 0xb7, 0x27, 0x8a, 0x3d, 0x6f, 0x4a, 0xa2, 0xe9, + 0xd3, 0x6e, 0x38, 0xf4, 0x00, 0xae, 0x93, 0x23, 0x5d, 0xc9, 0x96, 0xeb, 0x99, 0x1d, 0x23, 0xf5, + 0xbe, 0x7d, 0xd9, 0xdf, 0x72, 0xdd, 0x0f, 0xad, 0xf5, 0x67, 0xba, 0xe5, 0x82, 0x65, 0x17, 0x54, + 0xf4, 0x10, 0xae, 0xd9, 0x78, 0xba, 0x21, 0x8d, 0xdf, 0xc1, 0xf2, 0x62, 0xdb, 0x22, 0x5d, 0x35, + 0x7e, 0x33, 0x47, 0x7f, 0xb4, 0xf1, 0xfe, 0xf7, 0xe7, 0xbd, 0x0b, 0xa1, 0xc8, 0x87, 0xdb, 0x97, + 0x3c, 0xf5, 0x91, 0x1c, 0x7c, 0x05, 0x70, 0x65, 0xa2, 0x18, 0x2a, 0xe1, 0xc6, 0xa5, 0xdc, 0xef, + 0x5c, 0xd1, 0x6d, 0x10, 0x6d, 0x70, 0xf7, 0x7f, 0x58, 0xe7, 0x0b, 0x78, 0x05, 0x6f, 0xfd, 0x95, + 0x4c, 0x74, 0xf5, 0xeb, 0x65, 0x4e, 0xb0, 0xf7, 0x6f, 0x4e, 0xaf, 0x9f, 0x3e, 0x39, 0x9d, 0x87, + 0xe0, 0x6c, 0x1e, 0x82, 0x5f, 0xf3, 0x10, 0x7c, 0x58, 0x84, 0xa3, 0xb3, 0x45, 0x38, 0xfa, 0xbe, + 0x08, 0x47, 0x2f, 0x93, 0xa5, 0x45, 0x3a, 0xbd, 0xa2, 0x22, 0x5c, 0xf4, 0x05, 0x3e, 0x59, 0xfe, + 0x5a, 0xcc, 0x5e, 0xf3, 0xb5, 0xee, 0x4f, 0x79, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, + 0x67, 0xfa, 0x97, 0x4f, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -426,20 +418,13 @@ func (m *MsgWithdrawReward) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.Address) i = encodeVarintTx(dAtA, i, uint64(len(m.Address))) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x12 } if len(m.Type) > 0 { i -= len(m.Type) copy(dAtA[i:], m.Type) i = encodeVarintTx(dAtA, i, uint64(len(m.Type))) i-- - dAtA[i] = 0x12 - } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- dAtA[i] = 0xa } return len(dAtA) - i, nil @@ -562,10 +547,6 @@ func (m *MsgWithdrawReward) Size() (n int) { } var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } l = len(m.Type) if l > 0 { n += 1 + l + sovTx(uint64(l)) @@ -652,38 +633,6 @@ func (m *MsgWithdrawReward) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signer = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) } @@ -715,7 +664,7 @@ func (m *MsgWithdrawReward) Unmarshal(dAtA []byte) error { } m.Type = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) } From 0a09a55601cc8ef96294d3e34d73d5ca2342f196 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 11 Sep 2023 15:38:57 +0200 Subject: [PATCH 080/202] Additional data in endpoint (#79) * Add additional data to endpoint --- proto/babylon/btcstaking/v1/btcstaking.proto | 20 + proto/babylon/btcstaking/v1/query.proto | 5 + x/btcstaking/keeper/grpc_query.go | 33 +- x/btcstaking/keeper/grpc_query_test.go | 2 +- x/btcstaking/types/btcstaking.pb.go | 428 ++++++++++++++++--- x/btcstaking/types/query.pb.go | 265 ++++++++---- 6 files changed, 600 insertions(+), 153 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index d96fa3b7d..17dee3654 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -126,6 +126,26 @@ message BTCUndelegation { bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } + +// BTCUndelegationInfo provides all necessary info about the undeleagation +message BTCUndelegationInfo { + // unbonding_tx is the transaction which will transfer the funds from staking + // output to unbonding output. Unbonding output will usually have lower timelock + // than staking output. + BabylonBTCTaprootTx unbonding_tx = 1; + + // validator_unbonding_sig is the signature on the unbonding tx + // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) + // It must be provided after processing undelagate message by Babylon + // It will be nil if validator didn't sign it yet + bytes validator_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + + // jury_unbonding_sig is the signature on the unbonding tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // it will be nil if jury didn't sign it yet + bytes jury_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} + // BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. message BTCDelegatorDelegations { repeated BTCDelegation dels = 1; diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index eef9cbae0..aa8371f4c 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -246,4 +246,9 @@ message QueryBTCDelegationResponse { string staking_tx = 6; // staking_script is the script commited to in staking output string staking_script = 7; + // whether this delegation is active + bool active = 8; + // undelegation_info is the undelegation info of this delegation. It is nil + // if it is not undelegating + BTCUndelegationInfo undelegation_info = 9; } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index be18f4ec5..d452fc44a 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -309,15 +309,34 @@ func (k Keeper) delegationView( if err != nil { continue } + currentTip := k.btclcKeeper.GetTipInfo(ctx) + currentWValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + + isActive := delegation.GetStatus( + currentTip.Height, + currentWValue, + ) == types.BTCDelegationStatus_ACTIVE + + var undelegationInfo *types.BTCUndelegationInfo = nil + + if delegation.BtcUndelegation != nil { + undelegationInfo = &types.BTCUndelegationInfo{ + UnbondingTx: delegation.BtcUndelegation.UnbondingTx, + ValidatorUnbondingSig: delegation.BtcUndelegation.ValidatorUnbondingSig, + JuryUnbondingSig: delegation.BtcUndelegation.JuryUnbondingSig, + } + } return &types.QueryBTCDelegationResponse{ - BtcPk: delegation.BtcPk, - ValBtcPk: delegation.ValBtcPk, - StartHeight: delegation.StartHeight, - EndHeight: delegation.EndHeight, - TotalSat: delegation.TotalSat, - StakingTx: hex.EncodeToString(delegation.StakingTx.Tx), - StakingScript: hex.EncodeToString(delegation.StakingTx.Script), + BtcPk: delegation.BtcPk, + ValBtcPk: delegation.ValBtcPk, + StartHeight: delegation.StartHeight, + EndHeight: delegation.EndHeight, + TotalSat: delegation.TotalSat, + StakingTx: hex.EncodeToString(delegation.StakingTx.Tx), + StakingScript: hex.EncodeToString(delegation.StakingTx.Script), + Active: isActive, + UndelegationInfo: undelegationInfo, } } return nil diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index e442cb251..4b5abe996 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -207,7 +207,7 @@ func FuzzBTCDelegations(f *testing.F) { require.NoError(t, err) txHash := btcDel.StakingTx.MustGetTxHash() - + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) delView, err := keeper.BTCDelegation(ctx, &types.QueryBTCDelegationRequest{ StakingTxHashHex: txHash, }) diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 0b09181ae..cf923dbea 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -439,6 +439,63 @@ func (m *BTCUndelegation) GetUnbondingTx() *BabylonBTCTaprootTx { return nil } +// BTCUndelegationInfo provides all necessary info about the undeleagation +type BTCUndelegationInfo struct { + // unbonding_tx is the transaction which will transfer the funds from staking + // output to unbonding output. Unbonding output will usually have lower timelock + // than staking output. + UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // validator_unbonding_sig is the signature on the unbonding tx + // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) + // It must be provided after processing undelagate message by Babylon + // It will be nil if validator didn't sign it yet + ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` + // jury_unbonding_sig is the signature on the unbonding tx + // by the jury (i.e., SK corresponding to jury_pk in params) + // it will be nil if jury didn't sign it yet + JuryUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=jury_unbonding_sig,json=juryUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_unbonding_sig,omitempty"` +} + +func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } +func (m *BTCUndelegationInfo) String() string { return proto.CompactTextString(m) } +func (*BTCUndelegationInfo) ProtoMessage() {} +func (*BTCUndelegationInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{4} +} +func (m *BTCUndelegationInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCUndelegationInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCUndelegationInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCUndelegationInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCUndelegationInfo.Merge(m, src) +} +func (m *BTCUndelegationInfo) XXX_Size() int { + return m.Size() +} +func (m *BTCUndelegationInfo) XXX_DiscardUnknown() { + xxx_messageInfo_BTCUndelegationInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCUndelegationInfo proto.InternalMessageInfo + +func (m *BTCUndelegationInfo) GetUnbondingTx() *BabylonBTCTaprootTx { + if m != nil { + return m.UnbondingTx + } + return nil +} + // BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` @@ -448,7 +505,7 @@ func (m *BTCDelegatorDelegations) Reset() { *m = BTCDelegatorDelegations func (m *BTCDelegatorDelegations) String() string { return proto.CompactTextString(m) } func (*BTCDelegatorDelegations) ProtoMessage() {} func (*BTCDelegatorDelegations) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{4} + return fileDescriptor_3851ae95ccfaf7db, []int{5} } func (m *BTCDelegatorDelegations) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -499,7 +556,7 @@ func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } func (*ProofOfPossession) ProtoMessage() {} func (*ProofOfPossession) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{5} + return fileDescriptor_3851ae95ccfaf7db, []int{6} } func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -548,7 +605,7 @@ func (m *BabylonBTCTaprootTx) Reset() { *m = BabylonBTCTaprootTx{} } func (m *BabylonBTCTaprootTx) String() string { return proto.CompactTextString(m) } func (*BabylonBTCTaprootTx) ProtoMessage() {} func (*BabylonBTCTaprootTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{6} + return fileDescriptor_3851ae95ccfaf7db, []int{7} } func (m *BabylonBTCTaprootTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -597,6 +654,7 @@ func init() { proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") proto.RegisterType((*BTCUndelegation)(nil), "babylon.btcstaking.v1.BTCUndelegation") + proto.RegisterType((*BTCUndelegationInfo)(nil), "babylon.btcstaking.v1.BTCUndelegationInfo") proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") @@ -607,69 +665,71 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 985 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0xda, 0x8e, 0x13, 0xbf, 0x75, 0x89, 0x33, 0x4d, 0x53, 0x53, 0x84, 0x9d, 0x9a, 0x2a, - 0xb2, 0x2a, 0xba, 0x26, 0xe9, 0x1f, 0x15, 0x24, 0x90, 0x58, 0x3b, 0x82, 0xa8, 0x24, 0x35, 0xeb, - 0x4d, 0x41, 0x1c, 0xb0, 0xf6, 0xcf, 0x74, 0xbd, 0xd8, 0xd9, 0x59, 0x76, 0xc6, 0xc6, 0xbe, 0x73, - 0xe0, 0xc8, 0x8d, 0x2f, 0xc2, 0x87, 0xe8, 0xb1, 0xe2, 0x84, 0x72, 0xb0, 0x50, 0xf2, 0x45, 0xd0, - 0xcc, 0xce, 0xda, 0x9b, 0xa6, 0x01, 0x82, 0x73, 0xb2, 0x67, 0xde, 0x7b, 0xbf, 0xf7, 0xde, 0xef, - 0xf7, 0xf6, 0xed, 0xc2, 0xb6, 0x6d, 0xd9, 0x93, 0x01, 0x09, 0x1a, 0x36, 0x73, 0x28, 0xb3, 0xfa, - 0x7e, 0xe0, 0x35, 0x46, 0x3b, 0xa9, 0x93, 0x16, 0x46, 0x84, 0x11, 0x74, 0x4b, 0xfa, 0x69, 0x29, - 0xcb, 0x68, 0xe7, 0xce, 0x86, 0x47, 0x3c, 0x22, 0x3c, 0x1a, 0xfc, 0x5f, 0xec, 0x7c, 0xe7, 0x5d, - 0x87, 0xd0, 0x63, 0x42, 0xbb, 0xb1, 0x21, 0x3e, 0x48, 0x53, 0x2d, 0x3e, 0x35, 0x9c, 0x68, 0x12, - 0x32, 0xd2, 0xa0, 0xd8, 0x09, 0x77, 0x1f, 0x3f, 0xe9, 0xef, 0x34, 0xfa, 0x78, 0x92, 0xf8, 0xdc, - 0x93, 0x3e, 0xf3, 0x7a, 0x6c, 0xcc, 0xac, 0x9d, 0xc6, 0xb9, 0x8a, 0x6a, 0xd3, 0x2c, 0x14, 0x75, - 0xb3, 0xf9, 0xc2, 0x1a, 0xf8, 0xae, 0xc5, 0x48, 0x84, 0xf6, 0x40, 0x75, 0x31, 0x75, 0x22, 0x3f, - 0x64, 0x3e, 0x09, 0xca, 0xca, 0x96, 0x52, 0x57, 0x77, 0x3f, 0xd0, 0x64, 0xfa, 0x79, 0xd1, 0x02, - 0x4c, 0x6b, 0xcd, 0x5d, 0x8d, 0x74, 0x1c, 0xfa, 0x16, 0xc0, 0x21, 0xc7, 0xc7, 0x3e, 0xa5, 0x1c, - 0x25, 0xb3, 0xa5, 0xd4, 0x0b, 0xfa, 0xd3, 0x93, 0x69, 0x75, 0xdb, 0xf3, 0x59, 0x6f, 0x68, 0x6b, - 0x0e, 0x39, 0x6e, 0x24, 0x4d, 0x88, 0x9f, 0x07, 0xd4, 0xed, 0x37, 0xd8, 0x24, 0xc4, 0x54, 0x6b, - 0x61, 0xe7, 0x8f, 0xdf, 0x1f, 0x80, 0x4c, 0xd9, 0xc2, 0x8e, 0x91, 0xc2, 0x42, 0x9f, 0x01, 0x48, - 0x16, 0xbb, 0x61, 0xbf, 0x9c, 0x15, 0xf5, 0x55, 0x93, 0xfa, 0x62, 0x42, 0xb4, 0x19, 0x21, 0x5a, - 0x7b, 0x68, 0x3f, 0xc3, 0x13, 0xa3, 0x20, 0x43, 0xda, 0x7d, 0x74, 0x00, 0x79, 0x9b, 0x39, 0x3c, - 0x36, 0xb7, 0xa5, 0xd4, 0x8b, 0xfa, 0x93, 0x93, 0x69, 0x75, 0x37, 0x55, 0x95, 0xf4, 0x74, 0x7a, - 0x96, 0x1f, 0x24, 0x07, 0x59, 0x98, 0xbe, 0xdf, 0x7e, 0xf8, 0xe8, 0x23, 0x09, 0xb9, 0x6c, 0x33, - 0xa7, 0xdd, 0x47, 0x9f, 0x40, 0x36, 0x24, 0x61, 0x79, 0x59, 0xd4, 0x51, 0xd7, 0xde, 0x2a, 0xb0, - 0xd6, 0x8e, 0x08, 0x79, 0xf9, 0xfc, 0x65, 0x9b, 0x50, 0x8a, 0x45, 0x17, 0x06, 0x0f, 0x42, 0x8f, - 0x60, 0x93, 0x0e, 0x2c, 0xda, 0xc3, 0x6e, 0x37, 0x69, 0xa9, 0x87, 0x7d, 0xaf, 0xc7, 0xca, 0xf9, - 0x2d, 0xa5, 0x9e, 0x33, 0x36, 0xa4, 0x55, 0x8f, 0x8d, 0x5f, 0x0a, 0x1b, 0xfa, 0x10, 0xd0, 0x2c, - 0x8a, 0x39, 0x49, 0xc4, 0x8a, 0x88, 0x28, 0x25, 0x11, 0xcc, 0x89, 0xbd, 0x6b, 0x3f, 0x67, 0x60, - 0x23, 0x2d, 0xf0, 0x37, 0x3e, 0xeb, 0x1d, 0x60, 0x66, 0xa5, 0x78, 0x50, 0xae, 0x83, 0x87, 0x4d, - 0xc8, 0xcb, 0x4a, 0x32, 0xa2, 0x12, 0x79, 0x42, 0x77, 0xa1, 0x38, 0x22, 0xcc, 0x0f, 0xbc, 0x6e, - 0x48, 0x7e, 0xc2, 0x91, 0x10, 0x2c, 0x67, 0xa8, 0xf1, 0x5d, 0x9b, 0x5f, 0xfd, 0x03, 0x0d, 0xb9, - 0x2b, 0xd3, 0xb0, 0x7c, 0x09, 0x0d, 0xbf, 0xe5, 0xe1, 0x86, 0x6e, 0x36, 0x5b, 0x78, 0x80, 0x3d, - 0x8b, 0x5d, 0x9c, 0x23, 0x65, 0x81, 0x39, 0xca, 0x5c, 0xe3, 0x1c, 0x65, 0xff, 0xcf, 0x1c, 0x99, - 0x00, 0x23, 0x6b, 0xd0, 0xbd, 0x96, 0xb1, 0x5e, 0x1d, 0x59, 0x03, 0x5d, 0x54, 0x74, 0x17, 0x8a, - 0x94, 0x59, 0x11, 0x3b, 0x4f, 0xad, 0x2a, 0xee, 0xa4, 0x06, 0xef, 0x03, 0xe0, 0xc0, 0x3d, 0x3f, - 0xb4, 0x05, 0x1c, 0xb8, 0xd2, 0xfc, 0x1e, 0x14, 0x18, 0x61, 0xd6, 0xa0, 0x4b, 0xad, 0x64, 0x40, - 0x57, 0xc5, 0x45, 0xc7, 0x62, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x65, 0xe3, 0xf2, 0xaa, 0xe8, 0xfb, - 0xfe, 0x25, 0x7d, 0x4b, 0xe5, 0x75, 0xb3, 0x69, 0x5a, 0x61, 0x44, 0x08, 0x33, 0xc7, 0x46, 0x41, - 0xda, 0xcd, 0x31, 0xda, 0x05, 0x55, 0x08, 0x2e, 0xb1, 0x0a, 0x82, 0x80, 0xf5, 0x93, 0x69, 0x95, - 0x4b, 0xde, 0x91, 0x16, 0x73, 0x6c, 0x00, 0x9d, 0xfd, 0x47, 0xdf, 0xc3, 0x0d, 0x37, 0x1e, 0x06, - 0x12, 0x75, 0xa9, 0xef, 0x95, 0x41, 0x44, 0x7d, 0x7c, 0x32, 0xad, 0x3e, 0xbe, 0x0a, 0x6d, 0x1d, - 0xdf, 0x0b, 0x2c, 0x36, 0x8c, 0xb0, 0x51, 0x9c, 0xe1, 0x75, 0x7c, 0x0f, 0x99, 0xb0, 0xfa, 0xc3, - 0x30, 0x9a, 0x08, 0x68, 0x75, 0x51, 0xe8, 0x15, 0x0e, 0xc5, 0x51, 0xbf, 0x86, 0x12, 0x57, 0x79, - 0x18, 0xb8, 0xb3, 0x41, 0x2e, 0x17, 0x05, 0x75, 0xdb, 0x97, 0x51, 0x67, 0x36, 0x8f, 0x52, 0xde, - 0xc6, 0x9a, 0xcd, 0x9c, 0xf4, 0x45, 0xed, 0x55, 0x0e, 0xd6, 0xde, 0x70, 0x42, 0x07, 0x50, 0x1c, - 0x06, 0x36, 0x09, 0x5c, 0xc9, 0xa8, 0x72, 0x65, 0x75, 0xd4, 0x59, 0xfc, 0x45, 0x7d, 0x32, 0xff, - 0x45, 0x1f, 0x02, 0x9b, 0x29, 0x7d, 0x92, 0x68, 0xce, 0x66, 0x76, 0x51, 0x36, 0x37, 0xe6, 0x42, - 0x49, 0x5c, 0x4e, 0x2d, 0x86, 0xf5, 0x58, 0xb0, 0x74, 0xae, 0xdc, 0xa2, 0xb9, 0xd6, 0x84, 0x72, - 0xa9, 0x34, 0x1e, 0x20, 0x91, 0x66, 0xce, 0x2f, 0xcf, 0xb3, 0xbc, 0x68, 0x9e, 0x12, 0x07, 0x3d, - 0x4a, 0x30, 0x79, 0xa2, 0x1f, 0xe1, 0xf6, 0x28, 0x59, 0xfa, 0x6f, 0x64, 0xcb, 0x2f, 0x9a, 0xed, - 0xd6, 0x0c, 0x39, 0x9d, 0xb2, 0xd6, 0x81, 0xdb, 0xf3, 0x1d, 0x4b, 0xa2, 0xf9, 0xb2, 0xa5, 0xe8, - 0x29, 0xe4, 0x5c, 0x3c, 0xa0, 0x65, 0x65, 0x2b, 0x5b, 0x57, 0x77, 0xef, 0x5d, 0x3e, 0xac, 0xf3, - 0x20, 0x43, 0x44, 0xd4, 0x7e, 0x51, 0x60, 0xfd, 0xc2, 0xde, 0x43, 0x55, 0x50, 0x93, 0xed, 0xcd, - 0x3b, 0x12, 0xaf, 0x30, 0x23, 0x59, 0xe8, 0xbc, 0x7d, 0x03, 0x56, 0xf8, 0x93, 0xc2, 0x8d, 0x99, - 0x45, 0xdb, 0xe5, 0x8b, 0x9e, 0xf7, 0xf7, 0x29, 0xdc, 0x7c, 0xcb, 0xac, 0xa3, 0x77, 0x20, 0x23, - 0x9f, 0x91, 0xa2, 0x91, 0x61, 0x63, 0xfe, 0x2a, 0x8c, 0x3f, 0x84, 0xe2, 0xcc, 0x86, 0x3c, 0xdd, - 0x7f, 0x06, 0x37, 0xcf, 0x35, 0xd8, 0x61, 0x16, 0x1b, 0x52, 0xa4, 0xc2, 0x4a, 0x7b, 0xef, 0xb0, - 0xb5, 0x7f, 0xf8, 0x45, 0x69, 0x09, 0x01, 0xe4, 0x3f, 0x6f, 0x9a, 0xfb, 0x2f, 0xf6, 0x4a, 0x0a, - 0xba, 0x01, 0x85, 0xa3, 0x43, 0xfd, 0x79, 0x6c, 0xca, 0xa0, 0x22, 0xac, 0xc6, 0xc7, 0xbd, 0x56, - 0x29, 0xab, 0x7f, 0xf5, 0xea, 0xb4, 0xa2, 0xbc, 0x3e, 0xad, 0x28, 0x7f, 0x9d, 0x56, 0x94, 0x5f, - 0xcf, 0x2a, 0x4b, 0xaf, 0xcf, 0x2a, 0x4b, 0x7f, 0x9e, 0x55, 0x96, 0xbe, 0xfb, 0xd7, 0xad, 0x3f, - 0x4e, 0x7f, 0xa6, 0x8a, 0x8e, 0xed, 0xbc, 0xf8, 0x1a, 0x7c, 0xf8, 0x77, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xb2, 0x88, 0xf9, 0x2e, 0xc9, 0x0a, 0x00, 0x00, + // 1012 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4d, 0x6f, 0x23, 0x45, + 0x13, 0xce, 0xd8, 0x8e, 0x13, 0x97, 0x9d, 0x37, 0x4e, 0x6f, 0x36, 0xeb, 0x77, 0x11, 0x76, 0xd6, + 0xac, 0x22, 0x6b, 0xc5, 0x8e, 0x49, 0xf6, 0x43, 0x0b, 0x12, 0x48, 0x4c, 0x1c, 0x41, 0xb4, 0x24, + 0x6b, 0xc6, 0x93, 0x05, 0x71, 0xc0, 0x9a, 0x8f, 0xce, 0x78, 0xb0, 0x33, 0x3d, 0x4c, 0xb7, 0x8d, + 0x7d, 0xe7, 0xc0, 0x91, 0x1b, 0x7f, 0x84, 0x1f, 0xc0, 0x71, 0x8f, 0x2b, 0x4e, 0x28, 0x87, 0x08, + 0x25, 0x7f, 0x04, 0x75, 0x4f, 0x8f, 0x3d, 0x49, 0xd6, 0x0b, 0xc1, 0xe6, 0x14, 0x77, 0x57, 0xd5, + 0x53, 0x55, 0xcf, 0x53, 0x99, 0x6a, 0xd8, 0xb2, 0x4c, 0x6b, 0xd4, 0x23, 0x7e, 0xdd, 0x62, 0x36, + 0x65, 0x66, 0xd7, 0xf3, 0xdd, 0xfa, 0x60, 0x3b, 0x71, 0x52, 0x83, 0x90, 0x30, 0x82, 0x6e, 0x4b, + 0x3f, 0x35, 0x61, 0x19, 0x6c, 0xdf, 0x5d, 0x77, 0x89, 0x4b, 0x84, 0x47, 0x9d, 0xff, 0x8a, 0x9c, + 0xef, 0xfe, 0xdf, 0x26, 0xf4, 0x84, 0xd0, 0x76, 0x64, 0x88, 0x0e, 0xd2, 0x54, 0x8d, 0x4e, 0x75, + 0x3b, 0x1c, 0x05, 0x8c, 0xd4, 0x29, 0xb6, 0x83, 0x9d, 0x27, 0x4f, 0xbb, 0xdb, 0xf5, 0x2e, 0x1e, + 0xc5, 0x3e, 0xf7, 0xa5, 0xcf, 0xa4, 0x1e, 0x0b, 0x33, 0x73, 0xbb, 0x7e, 0xa9, 0xa2, 0xea, 0x59, + 0x1a, 0x0a, 0x9a, 0xb1, 0xfb, 0xd2, 0xec, 0x79, 0x8e, 0xc9, 0x48, 0x88, 0xf6, 0x20, 0xef, 0x60, + 0x6a, 0x87, 0x5e, 0xc0, 0x3c, 0xe2, 0x97, 0x94, 0x4d, 0xa5, 0x96, 0xdf, 0x79, 0x4f, 0x95, 0xe9, + 0x27, 0x45, 0x0b, 0x30, 0xb5, 0x31, 0x71, 0xd5, 0x93, 0x71, 0xe8, 0x6b, 0x00, 0x9b, 0x9c, 0x9c, + 0x78, 0x94, 0x72, 0x94, 0xd4, 0xa6, 0x52, 0xcb, 0x69, 0xcf, 0x4e, 0xcf, 0x2a, 0x5b, 0xae, 0xc7, + 0x3a, 0x7d, 0x4b, 0xb5, 0xc9, 0x49, 0x3d, 0x6e, 0x42, 0xfc, 0x79, 0x48, 0x9d, 0x6e, 0x9d, 0x8d, + 0x02, 0x4c, 0xd5, 0x06, 0xb6, 0x7f, 0xff, 0xf5, 0x21, 0xc8, 0x94, 0x0d, 0x6c, 0xeb, 0x09, 0x2c, + 0xf4, 0x09, 0x80, 0x64, 0xb1, 0x1d, 0x74, 0x4b, 0x69, 0x51, 0x5f, 0x25, 0xae, 0x2f, 0x22, 0x44, + 0x1d, 0x13, 0xa2, 0x36, 0xfb, 0xd6, 0x73, 0x3c, 0xd2, 0x73, 0x32, 0xa4, 0xd9, 0x45, 0x07, 0x90, + 0xb5, 0x98, 0xcd, 0x63, 0x33, 0x9b, 0x4a, 0xad, 0xa0, 0x3d, 0x3d, 0x3d, 0xab, 0xec, 0x24, 0xaa, + 0x92, 0x9e, 0x76, 0xc7, 0xf4, 0xfc, 0xf8, 0x20, 0x0b, 0xd3, 0xf6, 0x9b, 0x8f, 0x1e, 0x7f, 0x20, + 0x21, 0x17, 0x2d, 0x66, 0x37, 0xbb, 0xe8, 0x23, 0x48, 0x07, 0x24, 0x28, 0x2d, 0x8a, 0x3a, 0x6a, + 0xea, 0x1b, 0x05, 0x56, 0x9b, 0x21, 0x21, 0xc7, 0x2f, 0x8e, 0x9b, 0x84, 0x52, 0x2c, 0xba, 0xd0, + 0x79, 0x10, 0x7a, 0x0c, 0x1b, 0xb4, 0x67, 0xd2, 0x0e, 0x76, 0xda, 0x71, 0x4b, 0x1d, 0xec, 0xb9, + 0x1d, 0x56, 0xca, 0x6e, 0x2a, 0xb5, 0x8c, 0xbe, 0x2e, 0xad, 0x5a, 0x64, 0xfc, 0x5c, 0xd8, 0xd0, + 0xfb, 0x80, 0xc6, 0x51, 0xcc, 0x8e, 0x23, 0x96, 0x44, 0x44, 0x31, 0x8e, 0x60, 0x76, 0xe4, 0x5d, + 0xfd, 0x31, 0x05, 0xeb, 0x49, 0x81, 0xbf, 0xf2, 0x58, 0xe7, 0x00, 0x33, 0x33, 0xc1, 0x83, 0x32, + 0x0f, 0x1e, 0x36, 0x20, 0x2b, 0x2b, 0x49, 0x89, 0x4a, 0xe4, 0x09, 0xdd, 0x83, 0xc2, 0x80, 0x30, + 0xcf, 0x77, 0xdb, 0x01, 0xf9, 0x01, 0x87, 0x42, 0xb0, 0x8c, 0x9e, 0x8f, 0xee, 0x9a, 0xfc, 0xea, + 0x2d, 0x34, 0x64, 0x6e, 0x4c, 0xc3, 0xe2, 0x14, 0x1a, 0x7e, 0xc9, 0xc2, 0x8a, 0x66, 0xec, 0x36, + 0x70, 0x0f, 0xbb, 0x26, 0xbb, 0x3e, 0x47, 0xca, 0x0c, 0x73, 0x94, 0x9a, 0xe3, 0x1c, 0xa5, 0xff, + 0xcd, 0x1c, 0x19, 0x00, 0x03, 0xb3, 0xd7, 0x9e, 0xcb, 0x58, 0x2f, 0x0f, 0xcc, 0x9e, 0x26, 0x2a, + 0xba, 0x07, 0x05, 0xca, 0xcc, 0x90, 0x5d, 0xa6, 0x36, 0x2f, 0xee, 0xa4, 0x06, 0xef, 0x02, 0x60, + 0xdf, 0xb9, 0x3c, 0xb4, 0x39, 0xec, 0x3b, 0xd2, 0xfc, 0x0e, 0xe4, 0x18, 0x61, 0x66, 0xaf, 0x4d, + 0xcd, 0x78, 0x40, 0x97, 0xc5, 0x45, 0xcb, 0x64, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x66, 0xc3, 0xd2, + 0xb2, 0xe8, 0xfb, 0xc1, 0x94, 0xbe, 0xa5, 0xf2, 0x9a, 0xb1, 0x6b, 0x98, 0x41, 0x48, 0x08, 0x33, + 0x86, 0x7a, 0x4e, 0xda, 0x8d, 0x21, 0xda, 0x81, 0xbc, 0x10, 0x5c, 0x62, 0xe5, 0x04, 0x01, 0x6b, + 0xa7, 0x67, 0x15, 0x2e, 0x79, 0x4b, 0x5a, 0x8c, 0xa1, 0x0e, 0x74, 0xfc, 0x1b, 0x7d, 0x0b, 0x2b, + 0x4e, 0x34, 0x0c, 0x24, 0x6c, 0x53, 0xcf, 0x2d, 0x81, 0x88, 0xfa, 0xf0, 0xf4, 0xac, 0xf2, 0xe4, + 0x26, 0xb4, 0xb5, 0x3c, 0xd7, 0x37, 0x59, 0x3f, 0xc4, 0x7a, 0x61, 0x8c, 0xd7, 0xf2, 0x5c, 0x64, + 0xc0, 0xf2, 0x77, 0xfd, 0x70, 0x24, 0xa0, 0xf3, 0xb3, 0x42, 0x2f, 0x71, 0x28, 0x8e, 0xfa, 0x25, + 0x14, 0xb9, 0xca, 0x7d, 0xdf, 0x19, 0x0f, 0x72, 0xa9, 0x20, 0xa8, 0xdb, 0x9a, 0x46, 0x9d, 0xb1, + 0x7b, 0x94, 0xf0, 0xd6, 0x57, 0x2d, 0x66, 0x27, 0x2f, 0xaa, 0xaf, 0x32, 0xb0, 0x7a, 0xc5, 0x09, + 0x1d, 0x40, 0xa1, 0xef, 0x5b, 0xc4, 0x77, 0x24, 0xa3, 0xca, 0x8d, 0xd5, 0xc9, 0x8f, 0xe3, 0xaf, + 0xeb, 0x93, 0xfa, 0x27, 0xfa, 0x10, 0xd8, 0x48, 0xe8, 0x13, 0x47, 0x73, 0x36, 0xd3, 0xb3, 0xb2, + 0xb9, 0x3e, 0x11, 0x4a, 0xe2, 0x72, 0x6a, 0x31, 0xac, 0x45, 0x82, 0x25, 0x73, 0x65, 0x66, 0xcd, + 0xb5, 0x2a, 0x94, 0x4b, 0xa4, 0x71, 0x01, 0x89, 0x34, 0x13, 0x7e, 0x79, 0x9e, 0xc5, 0x59, 0xf3, + 0x14, 0x39, 0xe8, 0x51, 0x8c, 0xc9, 0x13, 0x7d, 0x0f, 0x77, 0x06, 0xf1, 0x47, 0xff, 0x4a, 0xb6, + 0xec, 0xac, 0xd9, 0x6e, 0x8f, 0x91, 0x93, 0x29, 0xab, 0xbf, 0xa5, 0xe0, 0xd6, 0x95, 0x51, 0xda, + 0xf7, 0x8f, 0xc9, 0xbc, 0xc7, 0xe9, 0x2d, 0x9d, 0xa5, 0xfe, 0x9b, 0xce, 0xa6, 0xa8, 0x96, 0x9e, + 0xbb, 0x6a, 0xd5, 0x16, 0xdc, 0x99, 0xac, 0x29, 0x12, 0x4e, 0xf6, 0x15, 0x45, 0xcf, 0x20, 0xe3, + 0xe0, 0x1e, 0x2d, 0x29, 0x9b, 0xe9, 0x5a, 0x7e, 0xe7, 0xfe, 0xf4, 0xff, 0xf7, 0x49, 0x90, 0x2e, + 0x22, 0xaa, 0x3f, 0x29, 0xb0, 0x76, 0x6d, 0x75, 0xa0, 0x0a, 0xe4, 0xe3, 0x05, 0xc8, 0x9b, 0x11, + 0xaf, 0x00, 0x3d, 0xde, 0x89, 0xbc, 0x69, 0x1d, 0x96, 0xf8, 0xc7, 0x66, 0x2e, 0xbc, 0xf2, 0x5d, + 0xc9, 0xfb, 0xfb, 0x18, 0x6e, 0xbd, 0x41, 0x5f, 0xf4, 0x3f, 0x48, 0xc9, 0xb9, 0x28, 0xe8, 0x29, + 0x36, 0xe4, 0xaf, 0x89, 0xe8, 0x2d, 0x19, 0x65, 0xd6, 0xe5, 0xe9, 0xc1, 0x73, 0x31, 0x60, 0x93, + 0x06, 0x5b, 0xcc, 0x64, 0x7d, 0x8a, 0xf2, 0xb0, 0xd4, 0xdc, 0x3b, 0x6c, 0xec, 0x1f, 0x7e, 0x56, + 0x5c, 0x40, 0x00, 0xd9, 0x4f, 0x77, 0x8d, 0xfd, 0x97, 0x7b, 0x45, 0x05, 0xad, 0x40, 0xee, 0xe8, + 0x50, 0x7b, 0x11, 0x99, 0x52, 0xa8, 0x00, 0xcb, 0xd1, 0x71, 0xaf, 0x51, 0x4c, 0x6b, 0x5f, 0xbc, + 0x3a, 0x2f, 0x2b, 0xaf, 0xcf, 0xcb, 0xca, 0x9f, 0xe7, 0x65, 0xe5, 0xe7, 0x8b, 0xf2, 0xc2, 0xeb, + 0x8b, 0xf2, 0xc2, 0x1f, 0x17, 0xe5, 0x85, 0x6f, 0xfe, 0x76, 0x71, 0x0e, 0x93, 0x2f, 0x7d, 0xd1, + 0xb1, 0x95, 0x15, 0x0f, 0xea, 0x47, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x25, 0xa9, 0x4e, 0x2a, + 0x0c, 0x0c, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1061,6 +1121,65 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BTCUndelegationInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCUndelegationInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.JuryUnbondingSig != nil { + { + size := m.JuryUnbondingSig.Size() + i -= size + if _, err := m.JuryUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ValidatorUnbondingSig != nil { + { + size := m.ValidatorUnbondingSig.Size() + i -= size + if _, err := m.ValidatorUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.UnbondingTx != nil { + { + size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *BTCDelegatorDelegations) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1335,6 +1454,27 @@ func (m *BTCUndelegation) Size() (n int) { return n } +func (m *BTCUndelegationInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UnbondingTx != nil { + l = m.UnbondingTx.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.ValidatorUnbondingSig != nil { + l = m.ValidatorUnbondingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.JuryUnbondingSig != nil { + l = m.JuryUnbondingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + func (m *BTCDelegatorDelegations) Size() (n int) { if m == nil { return 0 @@ -2505,6 +2645,162 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } return nil } +func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCUndelegationInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCUndelegationInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UnbondingTx == nil { + m.UnbondingTx = &BabylonBTCTaprootTx{} + } + if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorUnbondingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.ValidatorUnbondingSig = &v + if err := m.ValidatorUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JuryUnbondingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.JuryUnbondingSig = &v + if err := m.JuryUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *BTCDelegatorDelegations) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 003577d1e..1fce7194e 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -1073,6 +1073,11 @@ type QueryBTCDelegationResponse struct { StakingTx string `protobuf:"bytes,6,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // staking_script is the script commited to in staking output StakingScript string `protobuf:"bytes,7,opt,name=staking_script,json=stakingScript,proto3" json:"staking_script,omitempty"` + // whether this delegation is active + Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` + // undelegation_info is the undelegation info of this delegation. It is nil + // if it is not undelegating + UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,9,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` } func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationResponse{} } @@ -1143,6 +1148,20 @@ func (m *QueryBTCDelegationResponse) GetStakingScript() string { return "" } +func (m *QueryBTCDelegationResponse) GetActive() bool { + if m != nil { + return m.Active + } + return false +} + +func (m *QueryBTCDelegationResponse) GetUndelegationInfo() *BTCUndelegationInfo { + if m != nil { + return m.UndelegationInfo + } + return nil +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btcstaking.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") @@ -1171,85 +1190,88 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1247 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0xc5, - 0x17, 0xcf, 0xa4, 0x89, 0xbf, 0xcd, 0xcb, 0x8f, 0x56, 0xd3, 0xf6, 0x1b, 0x67, 0xd3, 0x38, 0xcd, - 0xa6, 0xf9, 0xd1, 0xa2, 0xee, 0x26, 0x0e, 0x8a, 0x0a, 0x09, 0x2d, 0x75, 0x42, 0x1b, 0x12, 0x22, - 0x19, 0x37, 0x80, 0xc4, 0xc5, 0x9a, 0xb5, 0x47, 0xeb, 0x25, 0xce, 0xae, 0xeb, 0x1d, 0x9b, 0x44, - 0x55, 0x2e, 0x1c, 0x90, 0xb8, 0x21, 0xf8, 0x13, 0x38, 0x72, 0xe2, 0x0a, 0x57, 0x24, 0x7a, 0xe0, - 0x50, 0x09, 0x09, 0x21, 0x2a, 0x45, 0x28, 0x41, 0xf0, 0x27, 0x70, 0x45, 0x9e, 0x9d, 0xcd, 0xee, - 0xc6, 0xbb, 0xeb, 0x8d, 0x69, 0x6f, 0xf1, 0xce, 0xfb, 0xf1, 0xf9, 0xbc, 0xf7, 0xe6, 0xcd, 0x47, - 0x81, 0x29, 0x8d, 0x68, 0x07, 0x55, 0xcb, 0x54, 0x35, 0x56, 0xb2, 0x19, 0xd9, 0x35, 0x4c, 0x5d, - 0x6d, 0x2e, 0xaa, 0x4f, 0x1a, 0xb4, 0x7e, 0xa0, 0xd4, 0xea, 0x16, 0xb3, 0xf0, 0x35, 0x61, 0xa2, - 0x78, 0x26, 0x4a, 0x73, 0x51, 0xba, 0xaa, 0x5b, 0xba, 0xc5, 0x2d, 0xd4, 0xd6, 0x5f, 0x8e, 0xb1, - 0x74, 0x5d, 0xb7, 0x2c, 0xbd, 0x4a, 0x55, 0x52, 0x33, 0x54, 0x62, 0x9a, 0x16, 0x23, 0xcc, 0xb0, - 0x4c, 0x5b, 0x9c, 0xde, 0x2e, 0x59, 0xf6, 0x9e, 0x65, 0xab, 0x1a, 0xb1, 0xa9, 0x93, 0x43, 0x6d, - 0x2e, 0x6a, 0x94, 0x91, 0x45, 0xb5, 0x46, 0x74, 0xc3, 0xe4, 0xc6, 0xc2, 0x56, 0x0e, 0x47, 0x56, - 0x23, 0x75, 0xb2, 0xe7, 0xc6, 0x9b, 0x0d, 0xb7, 0xf1, 0x01, 0xe5, 0x76, 0xf2, 0x55, 0xc0, 0xef, - 0xb7, 0xb2, 0xe5, 0xb9, 0x73, 0x81, 0x3e, 0x69, 0x50, 0x9b, 0xc9, 0x05, 0xb8, 0x12, 0xf8, 0x6a, - 0xd7, 0x2c, 0xd3, 0xa6, 0x78, 0x05, 0x52, 0x4e, 0x92, 0x34, 0xba, 0x81, 0xe6, 0x07, 0xb3, 0x13, - 0x4a, 0x68, 0x01, 0x14, 0xc7, 0x2d, 0xd7, 0xf7, 0xec, 0x68, 0xb2, 0xa7, 0x20, 0x5c, 0xe4, 0x12, - 0x8c, 0xf1, 0x98, 0xb9, 0x9d, 0xb5, 0x0f, 0x49, 0xd5, 0x28, 0x13, 0x66, 0xd5, 0xdd, 0x84, 0xf8, - 0x21, 0x80, 0x47, 0x53, 0x44, 0x9f, 0x55, 0x9c, 0x9a, 0x28, 0xad, 0x9a, 0x28, 0x4e, 0xdd, 0x45, - 0x4d, 0x94, 0x3c, 0xd1, 0xa9, 0xf0, 0x2d, 0xf8, 0x3c, 0xe5, 0xef, 0x10, 0x48, 0x61, 0x59, 0x04, - 0x81, 0x4d, 0x18, 0xd1, 0x58, 0xa9, 0xd8, 0x3c, 0x3d, 0x49, 0xa3, 0x1b, 0x17, 0xe6, 0x07, 0xb3, - 0xd3, 0x11, 0x44, 0xfc, 0x51, 0x0a, 0xc3, 0x1a, 0x2b, 0x79, 0x31, 0xf1, 0xa3, 0x00, 0xe4, 0x5e, - 0x0e, 0x79, 0xae, 0x23, 0x64, 0x07, 0x48, 0x00, 0xf3, 0x7d, 0x48, 0xb7, 0x41, 0x76, 0xeb, 0x32, - 0x0d, 0x23, 0x4d, 0x52, 0x2d, 0xb6, 0x40, 0xd7, 0x76, 0x8b, 0x15, 0xba, 0xcf, 0x6b, 0x33, 0x50, - 0x18, 0x6c, 0x92, 0x6a, 0x8e, 0x95, 0xf2, 0xbb, 0x1b, 0x74, 0x5f, 0xa6, 0x21, 0x95, 0x3d, 0xa5, - 0xbc, 0x01, 0xc3, 0x01, 0xca, 0xa2, 0xb8, 0x89, 0x18, 0x0f, 0xf9, 0x19, 0xcb, 0xd3, 0x30, 0xe5, - 0x0c, 0x05, 0x35, 0xcb, 0x86, 0xa9, 0xe7, 0x76, 0xd6, 0xd6, 0x69, 0x95, 0xea, 0xce, 0x18, 0xbb, - 0x93, 0x63, 0x83, 0x1c, 0x67, 0x24, 0x40, 0x6d, 0xc3, 0xa5, 0x16, 0xa8, 0xb2, 0x77, 0x24, 0x1a, - 0x71, 0x33, 0x1a, 0x96, 0x17, 0xa7, 0xd0, 0x6a, 0xa2, 0x2f, 0xac, 0x3c, 0x03, 0xd3, 0x3c, 0xe9, - 0x07, 0xa6, 0x66, 0xc5, 0x60, 0x6b, 0xc0, 0xcd, 0x78, 0xb3, 0x57, 0x83, 0xae, 0x0c, 0x33, 0x6d, - 0xed, 0xc9, 0x5b, 0x9f, 0xd2, 0xfa, 0x03, 0xb6, 0x41, 0x0d, 0xbd, 0xc2, 0xce, 0xd3, 0x6c, 0xfc, - 0x7f, 0x48, 0x55, 0xb8, 0x17, 0x1f, 0xb9, 0xbe, 0x82, 0xf8, 0x25, 0x6f, 0xc1, 0x6c, 0xa7, 0x2c, - 0x82, 0xde, 0x14, 0x0c, 0x35, 0x2d, 0x66, 0x98, 0x7a, 0xb1, 0xd6, 0x3a, 0xe7, 0x49, 0xfa, 0x0a, - 0x83, 0xce, 0x37, 0xee, 0x22, 0x6f, 0x89, 0x4a, 0xf9, 0x83, 0xad, 0x35, 0xea, 0x75, 0x6a, 0x32, - 0x6e, 0x70, 0xae, 0xf1, 0xd4, 0x42, 0xf8, 0x07, 0x83, 0x09, 0x60, 0x1e, 0x35, 0xe4, 0xa7, 0xd6, - 0x06, 0xb8, 0xb7, 0x1d, 0xf0, 0x17, 0x08, 0xe6, 0x78, 0x92, 0x07, 0x25, 0x66, 0x34, 0x69, 0xe0, - 0xf6, 0x9f, 0x2d, 0x73, 0x54, 0x9a, 0x87, 0x21, 0x17, 0xba, 0x9b, 0x1d, 0xf4, 0x13, 0x82, 0xf9, - 0xce, 0x58, 0x04, 0xe7, 0x42, 0xc4, 0x46, 0x7a, 0x2d, 0xc1, 0xfd, 0xfc, 0xc8, 0x60, 0x95, 0x6d, - 0xca, 0xc8, 0x2b, 0xdb, 0x4c, 0x13, 0x30, 0xee, 0x11, 0x21, 0x8c, 0x96, 0x03, 0x85, 0x94, 0x97, - 0xe1, 0x7a, 0xf8, 0x71, 0x7c, 0x3f, 0xe5, 0xaf, 0x90, 0xb8, 0xaf, 0x7e, 0x32, 0xed, 0xf7, 0x35, - 0xd9, 0x7d, 0x78, 0x59, 0x5d, 0x7b, 0x81, 0x42, 0x66, 0x3e, 0x6c, 0x3b, 0x7c, 0x02, 0x63, 0xbe, - 0xed, 0x60, 0xd5, 0x43, 0xf6, 0x84, 0xd2, 0x71, 0x4f, 0x04, 0x43, 0x8f, 0x7a, 0x1b, 0x23, 0x70, - 0xf0, 0xf2, 0x3a, 0xb9, 0xe9, 0x3d, 0x11, 0xbe, 0x4d, 0x25, 0xea, 0x7c, 0x07, 0xae, 0x08, 0x90, - 0x45, 0xb6, 0x5f, 0xac, 0x10, 0xbb, 0xe2, 0x2b, 0xf6, 0x65, 0x71, 0xb4, 0xb3, 0xbf, 0x41, 0xec, - 0x4a, 0xeb, 0x3e, 0xff, 0xd5, 0xeb, 0xbd, 0xb1, 0xfe, 0x60, 0xa7, 0xdb, 0x33, 0xe5, 0x74, 0x8c, - 0x07, 0x18, 0xca, 0x2d, 0xff, 0x7e, 0x34, 0x99, 0xd5, 0x0d, 0x56, 0x69, 0x68, 0x4a, 0xc9, 0xda, - 0x53, 0x45, 0x69, 0x4a, 0x15, 0x62, 0x98, 0xee, 0x0f, 0x95, 0x1d, 0xd4, 0xa8, 0xad, 0xe4, 0xde, - 0xcd, 0x2f, 0xbd, 0xbe, 0x90, 0x6f, 0x68, 0x5b, 0xf4, 0xa0, 0xd0, 0xaf, 0xb5, 0x5a, 0x8c, 0x77, - 0x00, 0xbc, 0x21, 0xe0, 0x25, 0xe8, 0x3e, 0xe4, 0x45, 0x77, 0x70, 0x5a, 0x2b, 0xc5, 0x66, 0xa4, - 0xce, 0x8a, 0x62, 0x40, 0x2f, 0x38, 0x2b, 0x85, 0x7f, 0x73, 0xa6, 0x18, 0x4f, 0x00, 0x50, 0xb3, - 0xec, 0x1a, 0xf4, 0x71, 0x83, 0x01, 0x6a, 0x8a, 0x21, 0xc7, 0xe3, 0x30, 0xc0, 0x2c, 0x46, 0xaa, - 0x45, 0x9b, 0xb0, 0x74, 0x3f, 0x3f, 0xbd, 0xc8, 0x3f, 0x3c, 0x26, 0xdc, 0xd7, 0xab, 0x68, 0x3a, - 0xc5, 0x0b, 0x39, 0x70, 0x5a, 0x48, 0x3c, 0x03, 0x23, 0xee, 0xb1, 0x5d, 0xaa, 0x1b, 0x35, 0x96, - 0xfe, 0x1f, 0x37, 0x19, 0x16, 0x5f, 0x1f, 0xf3, 0x8f, 0xd9, 0x7f, 0x2e, 0x43, 0x3f, 0x2f, 0x34, - 0xfe, 0x1c, 0x41, 0xca, 0x11, 0x55, 0xf8, 0x56, 0xc4, 0x6c, 0xb5, 0xab, 0x38, 0xe9, 0x76, 0x12, - 0x53, 0xa7, 0x6b, 0xf2, 0xcc, 0x67, 0xbf, 0xfc, 0xf9, 0x75, 0xef, 0x24, 0x9e, 0x50, 0xe3, 0xc4, - 0x25, 0xfe, 0x06, 0xc1, 0x70, 0x60, 0xa1, 0xe1, 0x85, 0xb8, 0x24, 0x61, 0x5a, 0x4f, 0x5a, 0x3c, - 0x87, 0x87, 0x40, 0x77, 0x87, 0xa3, 0x9b, 0xc3, 0x33, 0x6a, 0xa4, 0xac, 0xf5, 0xad, 0x50, 0xfc, - 0x03, 0x82, 0x21, 0x7f, 0x20, 0xac, 0x26, 0x4d, 0xe9, 0x62, 0x5c, 0x48, 0xee, 0x20, 0x20, 0x6e, - 0x70, 0x88, 0x39, 0xfc, 0x76, 0x22, 0x88, 0xea, 0xd3, 0xe0, 0x66, 0x3b, 0x54, 0x4f, 0xcf, 0xf0, - 0x8f, 0x08, 0xae, 0x85, 0xca, 0x27, 0x7c, 0x37, 0xb6, 0xa1, 0x31, 0xb2, 0x4c, 0x7a, 0xa3, 0x0b, - 0x4f, 0x41, 0x6c, 0x99, 0x13, 0x5b, 0xc0, 0x4a, 0xd4, 0x64, 0x38, 0xde, 0xc5, 0x33, 0x92, 0x09, - 0xff, 0x8c, 0x60, 0x34, 0x42, 0x69, 0xe1, 0x37, 0xe3, 0xe0, 0xc4, 0xab, 0x38, 0x69, 0xa5, 0x2b, - 0x5f, 0x41, 0xe6, 0x2e, 0x27, 0x93, 0xc5, 0x0b, 0x11, 0x64, 0x1a, 0xae, 0x7f, 0x1b, 0x9d, 0x5f, - 0x11, 0x8c, 0xc7, 0x3c, 0xe8, 0xf8, 0x5e, 0x1c, 0xac, 0xce, 0xaa, 0x44, 0xba, 0xdf, 0xb5, 0x7f, - 0xc2, 0x3e, 0x9d, 0x1d, 0x40, 0x67, 0xb1, 0x1d, 0xe2, 0xbf, 0x11, 0x8c, 0x45, 0x8a, 0x46, 0xbc, - 0x9a, 0xf4, 0x22, 0x84, 0x29, 0x5a, 0xe9, 0xad, 0x2e, 0xbd, 0x05, 0xa5, 0x6d, 0x4e, 0xe9, 0x11, - 0x7e, 0xa7, 0xcb, 0x3b, 0xc5, 0xe5, 0xa2, 0xc7, 0xf4, 0x05, 0x82, 0x74, 0x94, 0x08, 0xc5, 0x2b, - 0x49, 0xa1, 0x86, 0xe8, 0x60, 0x69, 0xb5, 0x3b, 0x67, 0x41, 0x73, 0x9d, 0xd3, 0xbc, 0x87, 0x57, - 0xff, 0x0b, 0x4d, 0xfc, 0x2d, 0x82, 0x4b, 0x67, 0x94, 0x18, 0xce, 0x76, 0x1c, 0xaa, 0x36, 0x55, - 0x27, 0x2d, 0x9d, 0xcb, 0x47, 0x50, 0x50, 0x39, 0x85, 0x5b, 0x78, 0x2e, 0x82, 0x02, 0x71, 0xfd, - 0xc4, 0x7b, 0x8a, 0x8f, 0x10, 0x8c, 0x46, 0x28, 0xad, 0xf8, 0xed, 0x10, 0xaf, 0x19, 0xa5, 0x95, - 0xae, 0x7c, 0x05, 0x8b, 0x4d, 0xce, 0x62, 0x1d, 0xe7, 0xba, 0x6c, 0x84, 0x7f, 0x5f, 0x7c, 0xef, - 0xbc, 0x94, 0x5e, 0x9a, 0x8e, 0x2f, 0x65, 0x9b, 0x30, 0xeb, 0xf8, 0x52, 0xb6, 0xab, 0xaf, 0x44, - 0xb3, 0xe4, 0x83, 0xa9, 0x3e, 0x0d, 0x51, 0x7e, 0x87, 0xb9, 0xf7, 0x9e, 0x1d, 0x67, 0xd0, 0xf3, - 0xe3, 0x0c, 0xfa, 0xe3, 0x38, 0x83, 0xbe, 0x3c, 0xc9, 0xf4, 0x3c, 0x3f, 0xc9, 0xf4, 0xfc, 0x76, - 0x92, 0xe9, 0xf9, 0xb8, 0xa3, 0xec, 0xda, 0xf7, 0x27, 0xe4, 0x1a, 0x4c, 0x4b, 0xf1, 0x7f, 0x35, - 0x2d, 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x8f, 0x91, 0x6d, 0x44, 0x52, 0x13, 0x00, 0x00, + // 1292 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x4f, 0x1b, 0xc7, + 0x1b, 0x66, 0xf8, 0xf0, 0x0f, 0x5e, 0x3e, 0xc2, 0x6f, 0x92, 0x14, 0xb3, 0x04, 0x03, 0x4b, 0xf8, + 0x08, 0x55, 0x76, 0xc1, 0x54, 0x28, 0x2d, 0x34, 0x69, 0x0c, 0x4d, 0x08, 0x14, 0xc9, 0xdd, 0x90, + 0x46, 0xea, 0xc5, 0x9a, 0xb5, 0x27, 0xeb, 0x2d, 0x66, 0xd7, 0xf1, 0x8e, 0x5d, 0x50, 0xc4, 0xa5, + 0x87, 0x4a, 0xbd, 0x55, 0xed, 0x9f, 0xd0, 0x63, 0x4f, 0xbd, 0xb6, 0xd7, 0x4a, 0xcd, 0xa1, 0x87, + 0x48, 0x95, 0xda, 0xaa, 0x91, 0x50, 0x05, 0x95, 0xfa, 0x27, 0xf4, 0x5a, 0x79, 0x76, 0xcc, 0xae, + 0xf1, 0xee, 0xda, 0xb8, 0xc9, 0x0d, 0xcf, 0xbc, 0x1f, 0xcf, 0xf3, 0xbe, 0xef, 0xbc, 0xfb, 0x08, + 0x98, 0xd2, 0x89, 0x7e, 0x58, 0xb0, 0x2d, 0x55, 0x67, 0x59, 0x87, 0x91, 0x3d, 0xd3, 0x32, 0xd4, + 0xca, 0x92, 0xfa, 0xb4, 0x4c, 0x4b, 0x87, 0x4a, 0xb1, 0x64, 0x33, 0x1b, 0x5f, 0x15, 0x26, 0x8a, + 0x67, 0xa2, 0x54, 0x96, 0xa4, 0x2b, 0x86, 0x6d, 0xd8, 0xdc, 0x42, 0xad, 0xfe, 0xe5, 0x1a, 0x4b, + 0xd7, 0x0c, 0xdb, 0x36, 0x0a, 0x54, 0x25, 0x45, 0x53, 0x25, 0x96, 0x65, 0x33, 0xc2, 0x4c, 0xdb, + 0x72, 0xc4, 0xed, 0x42, 0xd6, 0x76, 0xf6, 0x6d, 0x47, 0xd5, 0x89, 0x43, 0xdd, 0x1c, 0x6a, 0x65, + 0x49, 0xa7, 0x8c, 0x2c, 0xa9, 0x45, 0x62, 0x98, 0x16, 0x37, 0x16, 0xb6, 0x72, 0x30, 0xb2, 0x22, + 0x29, 0x91, 0xfd, 0x5a, 0xbc, 0xd9, 0x60, 0x1b, 0x1f, 0x50, 0x6e, 0x27, 0x5f, 0x01, 0xfc, 0x61, + 0x35, 0x5b, 0x9a, 0x3b, 0x6b, 0xf4, 0x69, 0x99, 0x3a, 0x4c, 0xd6, 0xe0, 0x72, 0xdd, 0xa9, 0x53, + 0xb4, 0x2d, 0x87, 0xe2, 0x55, 0x88, 0xb9, 0x49, 0xe2, 0x68, 0x12, 0xcd, 0xf7, 0x27, 0xc7, 0x95, + 0xc0, 0x02, 0x28, 0xae, 0x5b, 0xaa, 0xfb, 0xf9, 0xf1, 0x44, 0x87, 0x26, 0x5c, 0xe4, 0x2c, 0x8c, + 0xf2, 0x98, 0xa9, 0xdd, 0xf5, 0x8f, 0x48, 0xc1, 0xcc, 0x11, 0x66, 0x97, 0x6a, 0x09, 0xf1, 0x3d, + 0x00, 0x8f, 0xa6, 0x88, 0x3e, 0xab, 0xb8, 0x35, 0x51, 0xaa, 0x35, 0x51, 0xdc, 0xba, 0x8b, 0x9a, + 0x28, 0x69, 0x62, 0x50, 0xe1, 0xab, 0xf9, 0x3c, 0xe5, 0xef, 0x10, 0x48, 0x41, 0x59, 0x04, 0x81, + 0x2d, 0x18, 0xd2, 0x59, 0x36, 0x53, 0x39, 0xbb, 0x89, 0xa3, 0xc9, 0xae, 0xf9, 0xfe, 0xe4, 0x74, + 0x08, 0x11, 0x7f, 0x14, 0x6d, 0x50, 0x67, 0x59, 0x2f, 0x26, 0xbe, 0x5f, 0x07, 0xb9, 0x93, 0x43, + 0x9e, 0x6b, 0x0a, 0xd9, 0x05, 0x52, 0x87, 0xf9, 0x0e, 0xc4, 0x1b, 0x20, 0xd7, 0xea, 0x32, 0x0d, + 0x43, 0x15, 0x52, 0xc8, 0x54, 0x41, 0x17, 0xf7, 0x32, 0x79, 0x7a, 0xc0, 0x6b, 0xd3, 0xa7, 0xf5, + 0x57, 0x48, 0x21, 0xc5, 0xb2, 0xe9, 0xbd, 0x4d, 0x7a, 0x20, 0xd3, 0x80, 0xca, 0x9e, 0x51, 0xde, + 0x84, 0xc1, 0x3a, 0xca, 0xa2, 0xb8, 0x2d, 0x31, 0x1e, 0xf0, 0x33, 0x96, 0xa7, 0x61, 0xca, 0x1d, + 0x0a, 0x6a, 0xe5, 0x4c, 0xcb, 0x48, 0xed, 0xae, 0x6f, 0xd0, 0x02, 0x35, 0xdc, 0x31, 0xae, 0x4d, + 0x8e, 0x03, 0x72, 0x94, 0x91, 0x00, 0xb5, 0x03, 0x97, 0xaa, 0xa0, 0x72, 0xde, 0x95, 0x68, 0xc4, + 0xf5, 0x70, 0x58, 0x5e, 0x1c, 0xad, 0xda, 0x44, 0x5f, 0x58, 0x79, 0x06, 0xa6, 0x79, 0xd2, 0x47, + 0x96, 0x6e, 0x47, 0x60, 0x2b, 0xc3, 0xf5, 0x68, 0xb3, 0xd7, 0x83, 0x2e, 0x07, 0x33, 0x0d, 0xed, + 0x49, 0xdb, 0x9f, 0xd2, 0xd2, 0x5d, 0xb6, 0x49, 0x4d, 0x23, 0xcf, 0x2e, 0xd2, 0x6c, 0xfc, 0x06, + 0xc4, 0xf2, 0xdc, 0x8b, 0x8f, 0x5c, 0xb7, 0x26, 0x7e, 0xc9, 0xdb, 0x30, 0xdb, 0x2c, 0x8b, 0xa0, + 0x37, 0x05, 0x03, 0x15, 0x9b, 0x99, 0x96, 0x91, 0x29, 0x56, 0xef, 0x79, 0x92, 0x6e, 0xad, 0xdf, + 0x3d, 0xe3, 0x2e, 0xf2, 0xb6, 0xa8, 0x94, 0x3f, 0xd8, 0x7a, 0xb9, 0x54, 0xa2, 0x16, 0xe3, 0x06, + 0x17, 0x1a, 0x4f, 0x3d, 0x80, 0x7f, 0x7d, 0x30, 0x01, 0xcc, 0xa3, 0x86, 0xfc, 0xd4, 0x1a, 0x00, + 0x77, 0x36, 0x02, 0xfe, 0x02, 0xc1, 0x1c, 0x4f, 0x72, 0x37, 0xcb, 0xcc, 0x0a, 0xad, 0x7b, 0xfd, + 0xe7, 0xcb, 0x1c, 0x96, 0xe6, 0x5e, 0xc0, 0x83, 0x6e, 0x67, 0x07, 0xfd, 0x84, 0x60, 0xbe, 0x39, + 0x16, 0xc1, 0x59, 0x0b, 0xd9, 0x48, 0x6f, 0xb6, 0xf0, 0x3e, 0x1f, 0x9b, 0x2c, 0xbf, 0x43, 0x19, + 0x79, 0x6d, 0x9b, 0x69, 0x1c, 0xc6, 0x3c, 0x22, 0x84, 0xd1, 0x5c, 0x5d, 0x21, 0xe5, 0x15, 0xb8, + 0x16, 0x7c, 0x1d, 0xdd, 0x4f, 0xf9, 0x2b, 0x24, 0xde, 0xab, 0x9f, 0x4c, 0xe3, 0x7b, 0x6d, 0xed, + 0x3d, 0xbc, 0xaa, 0xae, 0xbd, 0x44, 0x01, 0x33, 0x1f, 0xb4, 0x1d, 0x3e, 0x81, 0x51, 0xdf, 0x76, + 0xb0, 0x4b, 0x01, 0x7b, 0x42, 0x69, 0xba, 0x27, 0xea, 0x43, 0x8f, 0x78, 0x1b, 0xa3, 0xee, 0xe2, + 0xd5, 0x75, 0x72, 0xcb, 0xfb, 0x44, 0xf8, 0x36, 0x95, 0xa8, 0xf3, 0x4d, 0xb8, 0x2c, 0x40, 0x66, + 0xd8, 0x41, 0x26, 0x4f, 0x9c, 0xbc, 0xaf, 0xd8, 0xc3, 0xe2, 0x6a, 0xf7, 0x60, 0x93, 0x38, 0xf9, + 0xea, 0x7b, 0xfe, 0xad, 0xcb, 0xfb, 0xc6, 0xfa, 0x83, 0x9d, 0x6d, 0xcf, 0x98, 0xdb, 0x31, 0x1e, + 0x60, 0x20, 0xb5, 0xf2, 0xc7, 0xf1, 0x44, 0xd2, 0x30, 0x59, 0xbe, 0xac, 0x2b, 0x59, 0x7b, 0x5f, + 0x15, 0xa5, 0xc9, 0xe6, 0x89, 0x69, 0xd5, 0x7e, 0xa8, 0xec, 0xb0, 0x48, 0x1d, 0x25, 0xf5, 0x20, + 0xbd, 0xfc, 0xd6, 0x62, 0xba, 0xac, 0x6f, 0xd3, 0x43, 0xad, 0x47, 0xaf, 0xb6, 0x18, 0xef, 0x02, + 0x78, 0x43, 0xc0, 0x4b, 0xd0, 0x7e, 0xc8, 0xde, 0xda, 0xe0, 0x54, 0x57, 0x8a, 0xc3, 0x48, 0x89, + 0x65, 0xc4, 0x80, 0x76, 0xb9, 0x2b, 0x85, 0x9f, 0xb9, 0x53, 0x8c, 0xc7, 0x01, 0xa8, 0x95, 0xab, + 0x19, 0x74, 0x73, 0x83, 0x3e, 0x6a, 0x89, 0x21, 0xc7, 0x63, 0xd0, 0xc7, 0x6c, 0x46, 0x0a, 0x19, + 0x87, 0xb0, 0x78, 0x0f, 0xbf, 0xed, 0xe5, 0x07, 0x0f, 0x09, 0xf7, 0xf5, 0x2a, 0x1a, 0x8f, 0xf1, + 0x42, 0xf6, 0x9d, 0x15, 0x12, 0xcf, 0xc0, 0x50, 0xed, 0xda, 0xc9, 0x96, 0xcc, 0x22, 0x8b, 0xff, + 0x8f, 0x9b, 0x0c, 0x8a, 0xd3, 0x87, 0xfc, 0xb0, 0xfa, 0x7e, 0x08, 0x5f, 0x21, 0xf1, 0xde, 0x49, + 0x34, 0xdf, 0xab, 0x89, 0x5f, 0xf8, 0x31, 0xfc, 0xbf, 0x6c, 0x79, 0x53, 0x97, 0x31, 0xad, 0x27, + 0x76, 0xbc, 0x8f, 0x0f, 0xc7, 0x42, 0xf8, 0xe4, 0x3d, 0xf2, 0xb9, 0x3c, 0xb0, 0x9e, 0xd8, 0xda, + 0x70, 0xf9, 0xdc, 0x49, 0xf2, 0x9f, 0x61, 0xe8, 0xe1, 0x9d, 0xc5, 0x9f, 0x23, 0x88, 0xb9, 0x2a, + 0x0e, 0xdf, 0x08, 0x09, 0xd9, 0x28, 0x1b, 0xa5, 0x85, 0x56, 0x4c, 0xdd, 0x31, 0x91, 0x67, 0x3e, + 0xfb, 0xe5, 0xaf, 0xaf, 0x3b, 0x27, 0xf0, 0xb8, 0x1a, 0xa5, 0x66, 0xf1, 0x37, 0x08, 0x06, 0xeb, + 0x36, 0x28, 0x5e, 0x8c, 0x4a, 0x12, 0x24, 0x2e, 0xa5, 0xa5, 0x0b, 0x78, 0x08, 0x74, 0x37, 0x39, + 0xba, 0x39, 0x3c, 0xa3, 0x86, 0xea, 0x68, 0xdf, 0xce, 0xc6, 0x3f, 0x20, 0x18, 0xf0, 0x07, 0xc2, + 0x6a, 0xab, 0x29, 0x6b, 0x18, 0x17, 0x5b, 0x77, 0x10, 0x10, 0x37, 0x39, 0xc4, 0x14, 0x7e, 0xaf, + 0x25, 0x88, 0xea, 0xb3, 0xfa, 0x55, 0x7a, 0xa4, 0x9e, 0xdd, 0xe1, 0x1f, 0x11, 0x5c, 0x0d, 0xd4, + 0x6b, 0xf8, 0x56, 0x64, 0x43, 0x23, 0x74, 0xa0, 0xf4, 0x76, 0x1b, 0x9e, 0x82, 0xd8, 0x0a, 0x27, + 0xb6, 0x88, 0x95, 0xb0, 0xc9, 0x70, 0xbd, 0x33, 0xe7, 0x34, 0x1a, 0xfe, 0x19, 0xc1, 0x48, 0x88, + 0xb4, 0xc3, 0xef, 0x44, 0xc1, 0x89, 0x96, 0x8d, 0xd2, 0x6a, 0x5b, 0xbe, 0x82, 0xcc, 0x2d, 0x4e, + 0x26, 0x89, 0x17, 0x43, 0xc8, 0x94, 0x6b, 0xfe, 0x0d, 0x74, 0x7e, 0x45, 0x30, 0x16, 0xa1, 0x20, + 0xf0, 0xed, 0x28, 0x58, 0xcd, 0x65, 0x90, 0x74, 0xa7, 0x6d, 0xff, 0x16, 0xfb, 0x74, 0x7e, 0x00, + 0xdd, 0x4d, 0x7a, 0x84, 0xff, 0x46, 0x30, 0x1a, 0xaa, 0x52, 0xf1, 0x5a, 0xab, 0x0f, 0x21, 0x48, + 0x42, 0x4b, 0xef, 0xb6, 0xe9, 0x2d, 0x28, 0xed, 0x70, 0x4a, 0xf7, 0xf1, 0xfb, 0x6d, 0xbe, 0x29, + 0xae, 0x4f, 0x3d, 0xa6, 0x2f, 0x11, 0xc4, 0xc3, 0x54, 0x2f, 0x5e, 0x6d, 0x15, 0x6a, 0x80, 0xf0, + 0x96, 0xd6, 0xda, 0x73, 0x16, 0x34, 0x37, 0x38, 0xcd, 0xdb, 0x78, 0xed, 0xbf, 0xd0, 0xc4, 0xdf, + 0x22, 0xb8, 0x74, 0x4e, 0xfa, 0xe1, 0x64, 0xd3, 0xa1, 0x6a, 0x90, 0x91, 0xd2, 0xf2, 0x85, 0x7c, + 0x04, 0x05, 0x95, 0x53, 0xb8, 0x81, 0xe7, 0x42, 0x28, 0x90, 0x9a, 0x9f, 0xf8, 0x80, 0xe3, 0x63, + 0x04, 0x23, 0x21, 0xd2, 0x2e, 0x7a, 0x3b, 0x44, 0x8b, 0x54, 0x69, 0xb5, 0x2d, 0x5f, 0xc1, 0x62, + 0x8b, 0xb3, 0xd8, 0xc0, 0xa9, 0x36, 0x1b, 0xe1, 0xdf, 0x17, 0xdf, 0xbb, 0x5f, 0x4a, 0x2f, 0x4d, + 0xd3, 0x2f, 0x65, 0x83, 0x12, 0x6c, 0xfa, 0xa5, 0x6c, 0x94, 0x7b, 0x2d, 0xcd, 0x92, 0x0f, 0xa6, + 0xfa, 0x2c, 0x40, 0x6a, 0x1e, 0xa5, 0x3e, 0x78, 0x7e, 0x92, 0x40, 0x2f, 0x4e, 0x12, 0xe8, 0xcf, + 0x93, 0x04, 0xfa, 0xf2, 0x34, 0xd1, 0xf1, 0xe2, 0x34, 0xd1, 0xf1, 0xfb, 0x69, 0xa2, 0xe3, 0xe3, + 0xa6, 0x3a, 0xef, 0xc0, 0x9f, 0x90, 0x8b, 0x3e, 0x3d, 0xc6, 0xff, 0xb7, 0xb5, 0xfc, 0x6f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x0e, 0x90, 0xa4, 0x93, 0xc3, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2448,6 +2470,28 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err _ = i var l int _ = l + if m.UndelegationInfo != nil { + { + size, err := m.UndelegationInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + if m.Active { + i-- + if m.Active { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x40 + } if len(m.StakingScript) > 0 { i -= len(m.StakingScript) copy(dAtA[i:], m.StakingScript) @@ -2833,6 +2877,13 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } + if m.Active { + n += 2 + } + if m.UndelegationInfo != nil { + l = m.UndelegationInfo.Size() + n += 1 + l + sovQuery(uint64(l)) + } return n } @@ -4841,6 +4892,62 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { } m.StakingScript = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Active", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Active = bool(v != 0) + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UndelegationInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UndelegationInfo == nil { + m.UndelegationInfo = &BTCUndelegationInfo{} + } + if err := m.UndelegationInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) From aece8a12b91cb72b100a235c5096f96838faa5e5 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 14 Sep 2023 16:24:34 +0800 Subject: [PATCH 081/202] pop: proof of possession with bip-322 Bitcoin signature (#78) --- crypto/bip322/bip322.go | 181 +++++ crypto/bip322/bip322_test.go | 67 ++ crypto/bip322/witness.go | 105 +++ proto/babylon/btcstaking/v1/btcstaking.proto | 12 +- proto/babylon/btcstaking/v1/pop.proto | 34 + proto/babylon/btcstaking/v1/tx.proto | 1 + x/btcstaking/keeper/msg_server.go | 22 + x/btcstaking/types/btcstaking.go | 13 - x/btcstaking/types/btcstaking.pb.go | 360 ++-------- x/btcstaking/types/msg.go | 14 +- x/btcstaking/types/pop.go | 57 +- x/btcstaking/types/pop.pb.go | 675 +++++++++++++++++++ x/btcstaking/types/pop_test.go | 4 +- x/btcstaking/types/tx.pb.go | 141 ++-- 14 files changed, 1279 insertions(+), 407 deletions(-) create mode 100644 crypto/bip322/bip322.go create mode 100644 crypto/bip322/bip322_test.go create mode 100644 crypto/bip322/witness.go create mode 100644 proto/babylon/btcstaking/v1/pop.proto create mode 100644 x/btcstaking/types/pop.pb.go diff --git a/crypto/bip322/bip322.go b/crypto/bip322/bip322.go new file mode 100644 index 000000000..2bd63db52 --- /dev/null +++ b/crypto/bip322/bip322.go @@ -0,0 +1,181 @@ +package bip322 + +import ( + "crypto/sha256" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +const ( + bip322Tag = "BIP0322-signed-message" + + // toSpend tx constants + toSpendVersion = 0 + toSpendLockTime = 0 + toSpendInputHash = "0000000000000000000000000000000000000000000000000000000000000000" + toSpendInputIndex = 0xFFFFFFFF + toSpendInputSeq = 0 + toSpendOutputValue = 0 + + // toSign tx constants + toSignVersion = 0 + toSignLockTime = 0 + toSignInputSeq = 0 + toSignOutputValue = 0 +) + +// GetBIP340TaggedHash builds a BIP-340 tagged hash +// More specifically, the hash is of the form +// sha256(sha256(tag) || sha256(tag) || msg) +// See https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0340.mediawiki#design +// for more details +func GetBIP340TaggedHash(msg []byte) [32]byte { + tagHash := sha256.Sum256([]byte(bip322Tag)) + sum := make([]byte, 0) + sum = append(sum, tagHash[:]...) + sum = append(sum, tagHash[:]...) + sum = append(sum, msg...) + return sha256.Sum256(sum) +} + +// toSpendSignatureScript creates the signature script for the input +// of the toSpend transaction, i.e. +// `OP_0 PUSH32 [ BIP340_TAGGED_MSG ]` +// https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0322.mediawiki#full +func toSpendSignatureScript(msg []byte) ([]byte, error) { + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_0) + data := GetBIP340TaggedHash(msg) + builder.AddData(data[:]) + script, err := builder.Script() + if err != nil { + // msg depends on the input, so play it safe here and don't panic + return nil, err + } + return script, nil +} + +// toSignPkScript creates the public key script for the output +// of the toSign transaction, i.e. +// `OP_RETURN` +// https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0322.mediawiki#full +func toSignPkScript() []byte { + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_RETURN) + script, err := builder.Script() + if err != nil { + // Panic as we're building the script entirely ourselves + panic(err) + } + return script +} + +// addressToPkScript takes an address and creates a payment script for it +func addressToPkScript(addr string, net *chaincfg.Params) ([]byte, error) { + decoded, err := btcutil.DecodeAddress(addr, net) + if err != nil { + return nil, err + } + pkScript, err := txscript.PayToAddrScript(decoded) + if err != nil { + return nil, err + } + return pkScript, nil +} + +// GetToSpendTx builds a toSpend transaction based on the BIP-322 spec +// https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0322.mediawiki#full +// It requires as input the message that is signed and the address that produced the signature +func GetToSpendTx(msg []byte, address string, net *chaincfg.Params) (*wire.MsgTx, error) { + toSpend := wire.NewMsgTx(toSpendVersion) + toSpend.LockTime = toSpendLockTime + + // Create a single input with dummy data based on the spec constants + inputHash, err := chainhash.NewHashFromStr(toSpendInputHash) + if err != nil { + // This is a constant we have defined, so an issue here is a programming error + panic(err) + } + outPoint := wire.NewOutPoint(inputHash, toSpendInputIndex) + + // The signature script containing the BIP-322 Tagged message + script, err := toSpendSignatureScript(msg) + if err != nil { + return nil, err + } + input := wire.NewTxIn(outPoint, script, nil) + input.Sequence = toSpendInputSeq + + // Create the output + // The PK Script should be a pay to addr script on the provided address + pkScript, err := addressToPkScript(address, net) + if err != nil { + return nil, err + } + output := wire.NewTxOut(toSpendOutputValue, pkScript) + + toSpend.AddTxIn(input) + toSpend.AddTxOut(output) + return toSpend, nil +} + +// GetToSignTx builds a toSign transaction based on the BIP-322 spec +// https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0322.mediawiki#full +// It requires as input the toSpend transaction that it spends and the message signature +func GetToSignTx(toSpend *wire.MsgTx, sig []byte) (*wire.MsgTx, error) { + toSign := wire.NewMsgTx(toSignVersion) + toSign.LockTime = toSignLockTime + + // Specify the input outpoint + // Given that the input is the toSpend tx we have built, the input index is 0 + inputHash := toSpend.TxHash() + outPoint := wire.NewOutPoint(&inputHash, 0) + // Convert the signature into a witness stack + witness, err := simpleSigToWitness(sig) + if err != nil { + return nil, err + } + input := wire.NewTxIn(outPoint, nil, witness) + input.Sequence = toSignInputSeq + + // Create the output + output := wire.NewTxOut(toSignOutputValue, toSignPkScript()) + + toSign.AddTxIn(input) + toSign.AddTxOut(output) + return toSign, nil +} + +func Verify(msg []byte, sig []byte, address string, net *chaincfg.Params) error { + toSpend, err := GetToSpendTx(msg, address, net) + if err != nil { + return err + } + + toSign, err := GetToSignTx(toSpend, sig) + if err != nil { + return err + } + + // From the rules here: + // https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#verification-process + // We only need to perform verification of whether toSign spends toSpend properly + // given that the signature is a simple one and we construct both toSpend and toSign + inputFetcher := txscript.NewCannedPrevOutputFetcher([]byte{}, 0) + sigHashes := txscript.NewTxSigHashes(toSign, inputFetcher) + vm, err := txscript.NewEngine( + toSpend.TxOut[0].PkScript, toSign, 0, + txscript.StandardVerifyFlags, txscript.NewSigCache(0), sigHashes, + toSpend.TxOut[0].Value, inputFetcher, + ) + + if err != nil { + return err + } + + return vm.Execute() +} diff --git a/crypto/bip322/bip322_test.go b/crypto/bip322/bip322_test.go new file mode 100644 index 000000000..736441272 --- /dev/null +++ b/crypto/bip322/bip322_test.go @@ -0,0 +1,67 @@ +package bip322_test + +import ( + "encoding/base64" + "encoding/hex" + "testing" + + "github.com/babylonchain/babylon/crypto/bip322" + "github.com/btcsuite/btcd/chaincfg" + "github.com/stretchr/testify/require" +) + +var ( + net = &chaincfg.TestNet3Params + emptyBytes = []byte("") + helloWorldBytes = []byte("Hello World") + emptyBytesSig, _ = base64.StdEncoding.DecodeString("AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=") + helloWorldBytesSig, _ = base64.StdEncoding.DecodeString("AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=") + testAddr = "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l" +) + +// test vectors at https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#message-hashing +func TestBIP322_MsgHash(t *testing.T) { + msgHash := bip322.GetBIP340TaggedHash(emptyBytes) + msgHashHex := hex.EncodeToString(msgHash[:]) + require.Equal(t, msgHashHex, "c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1") + + msgHash = bip322.GetBIP340TaggedHash(helloWorldBytes) + msgHashHex = hex.EncodeToString(msgHash[:]) + require.Equal(t, msgHashHex, "f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a") +} + +// test vectors at https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#transaction-hashes +func TestBIP322_TxHashToSpend(t *testing.T) { + // empty str + toSpendTx, err := bip322.GetToSpendTx(emptyBytes, testAddr, net) + require.NoError(t, err) + require.Equal(t, "c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7", toSpendTx.TxHash().String()) + toSignTx, err := bip322.GetToSignTx(toSpendTx, emptyBytesSig) + require.NoError(t, err) + require.Equal(t, "1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6", toSignTx.TxHash().String()) + + // hello world str + toSpendTx, err = bip322.GetToSpendTx(helloWorldBytes, testAddr, net) + require.NoError(t, err) + require.Equal(t, "b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b", toSpendTx.TxHash().String()) + toSignTx, err = bip322.GetToSignTx(toSpendTx, helloWorldBytesSig) + require.NoError(t, err) + require.Equal(t, "88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf", toSignTx.TxHash().String()) +} + +func TestBIP322_Verify(t *testing.T) { + sigBase64 := "AkcwRAIgbAFRpM0rhdBlXr7qe5eEf3XgSeausCm2XTmZVxSYpcsCIDcbR87wF9DTrvdw1czYEEzOjso52dOSaw8VrC4GgzFRASECO5NGNFlPClJnTHNDW94h7pPL5D7xbl6FBNTrGaYpYcA=" + msgBase64 := "HRQD77+9dmnvv71N77+9O2/Wuzbvv73vv71a77+977+977+977+9Du+/ve+/vTgrNH/vv71lQX0=" + // TODO: make it work with the public key?? + address := "tb1qfwtfzdagj7efph6zfcv68ce3v48c8e9fatunur" + emptyBytesSig, err := base64.StdEncoding.DecodeString(sigBase64) + require.NoError(t, err) + + msg, err := base64.StdEncoding.DecodeString(msgBase64) + require.NoError(t, err) + + err = bip322.Verify(msg, emptyBytesSig, address, net) + require.NoError(t, err) +} + +// TODO: test signature generation diff --git a/crypto/bip322/witness.go b/crypto/bip322/witness.go new file mode 100644 index 000000000..541ba2a96 --- /dev/null +++ b/crypto/bip322/witness.go @@ -0,0 +1,105 @@ +package bip322 + +import ( + "bytes" + "fmt" + "io" + + "github.com/btcsuite/btcd/wire" +) + +// this file provides functionality for converting a simple signature to +// a witness stack. +// the entire file is adapted from https://github.com/btcsuite/btcd/blob/v0.23.4/wire/msgtx.go#L568-L599 + +const ( + // maxWitnessItemsPerInput is the maximum number of witness items to + // be read for the witness data for a single TxIn. This number is + // derived using a possible lower bound for the encoding of a witness + // item: 1 byte for length + 1 byte for the witness item itself, or two + // bytes. This value is then divided by the currently allowed maximum + // "cost" for a transaction. We use this for an upper bound for the + // buffer and consensus makes sure that the weight of a transaction + // cannot be more than 4000000. + maxWitnessItemsPerInput = 4_000_000 + + // maxWitnessItemSize is the maximum allowed size for an item within + // an input's witness data. This value is bounded by the largest + // possible block size, post segwit v1 (taproot). + maxWitnessItemSize = 4_000_000 +) + +// readScript reads a variable length byte array that represents a transaction +// script. It is encoded as a varInt containing the length of the array +// followed by the bytes themselves. An error is returned if the length is +// greater than the passed maxAllowed parameter which helps protect against +// memory exhaustion attacks and forced panics through malformed messages. The +// fieldName parameter is only used for the error message so it provides more +// context in the error. +func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) { + count, err := wire.ReadVarInt(r, pver) + if err != nil { + return nil, err + } + + // Prevent byte array larger than the max message size. It would + // be possible to cause memory exhaustion and panics without a sane + // upper bound on this count. + if count > uint64(maxAllowed) { + return nil, fmt.Errorf("%s is larger than the max allowed size "+ + "[count %d, max %d]", fieldName, count, maxAllowed) + } + + b := make([]byte, count) + _, err = io.ReadFull(r, b) + if err != nil { + return nil, err + } + return b, nil +} + +// simpleSigToWitness converts a simple signature into a witness stack +// As per the BIP-322 spec: +// "A simple signature consists of a witness stack, consensus encoded as a vector of vectors of bytes" +// https://github.com/bitcoin/bips/blob/e643d247c8bc086745f3031cdee0899803edea2f/bip-0322.mediawiki#simple +// However, the above does not provide much information about its encoding. +// We work on the encoding based on the Leather wallet implementation: +// https://github.com/leather-wallet/extension/blob/068d3cd465e1a642a763fecfa0e3ce5e94b07286/src/shared/crypto/bitcoin/bip322/bip322-utils.ts#L58 +// More specifically, the signature is encoded as follows: +// - 1st byte: Elements of the witness stack that are serialized +// - For each element of the stack +// - The first byte specifies how many bytes it contains +// - The rest are the bytes of the element +func simpleSigToWitness(sig []byte) ([][]byte, error) { + // For each input, the witness is encoded as a stack + // with one or more items. Therefore, we first read a + // varint which encodes the number of stack items. + buf := bytes.NewBuffer(sig) + witCount, err := wire.ReadVarInt(buf, 0) + if err != nil { + return nil, err + } + + // Prevent a possible memory exhaustion attack by + // limiting the witCount value to a sane upper bound. + if witCount > maxWitnessItemsPerInput { + return nil, fmt.Errorf("too many witness items to fit "+ + "into max message size [count %d, max %d]", + witCount, maxWitnessItemsPerInput) + } + + // Then for witCount number of stack items, each item + // has a varint length prefix, followed by the witness + // item itself. + witnessStack := make([][]byte, witCount) + for j := uint64(0); j < witCount; j++ { + witnessStack[j], err = readScript( + buf, 0, maxWitnessItemSize, "script witness item", + ) + if err != nil { + return nil, err + } + } + + return witnessStack, nil +} diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 17dee3654..0f7d765cc 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -5,6 +5,7 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "cosmos/crypto/secp256k1/keys.proto"; import "cosmos/staking/v1beta1/staking.proto"; +import "babylon/btcstaking/v1/pop.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; @@ -151,17 +152,6 @@ message BTCDelegatorDelegations { repeated BTCDelegation dels = 1; } -// ProofOfPossession is the proof of possession that a Babylon secp256k1 -// secret key and a Bitcoin secp256k1 secret key are held by the same -// person -message ProofOfPossession { - // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) - bytes babylon_sig = 1; - // btc_sig is the signature generated via sign(sk_btc, babylon_sig) - // the signature follows encoding in BIP-340 spec - bytes btc_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; -} - // BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and // transaction which commits to the provided script. message BabylonBTCTaprootTx { diff --git a/proto/babylon/btcstaking/v1/pop.proto b/proto/babylon/btcstaking/v1/pop.proto new file mode 100644 index 000000000..00a99bcaa --- /dev/null +++ b/proto/babylon/btcstaking/v1/pop.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package babylon.btcstaking.v1; + +option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; + +// BTCSigType indicates the type of btc_sig in a pop +enum BTCSigType { + // BIP340 means the btc_sig will follow the BIP-340 encoding + BIP340 = 0; + // BIP322 means the btc_sig will follow the BIP-322 encoding + BIP322 = 1; +} + +// ProofOfPossession is the proof of possession that a Babylon secp256k1 +// secret key and a Bitcoin secp256k1 secret key are held by the same +// person +message ProofOfPossession { + // btc_sig_type indicates the type of btc_sig in the pop + BTCSigType btc_sig_type = 1; + // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) + bytes babylon_sig = 2; + // btc_sig is the signature generated via sign(sk_btc, babylon_sig) + // the signature follows encoding in either BIP-340 spec or BIP-322 spec + bytes btc_sig = 3; +} + +// BIP322Sig is a BIP-322 signature together with the address corresponding to +// the signer +message BIP322Sig { + // address is the signer's address + string address = 1; + // sig is the actual signature in BIP-322 format + bytes sig = 2; +} \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 1632af979..727194485 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -9,6 +9,7 @@ import "babylon/btcstaking/v1/params.proto"; import "babylon/btcstaking/v1/btcstaking.proto"; import "babylon/btccheckpoint/v1/btccheckpoint.proto"; import "cosmos/staking/v1beta1/staking.proto"; +import "babylon/btcstaking/v1/pop.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index e9c1c4782..e4cb2c600 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -14,6 +14,8 @@ import ( "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type msgServer struct { @@ -75,6 +77,16 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // ensure the validator address does not exist before ctx := sdk.UnwrapSDKContext(goCtx) + if req.Pop.BtcSigType == types.BTCSigType_BIP322 { + if err := req.Pop.VerifyBIP322(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-322 encoding: %v", err) + } + } else if req.Pop.BtcSigType == types.BTCSigType_BIP340 { + if err := req.Pop.Verify(req.BabylonPk, req.BtcPk); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-340 encoding: %v", err) + } + } + // ensure commission rate is at least the minimum commission rate in parameters if req.Commission.LT(ms.MinCommissionRate(ctx)) { return nil, types.ErrCommissionLTMinRate.Wrapf("cannot set validator commission to less than minimum rate of %s", ms.MinCommissionRate(ctx)) @@ -120,6 +132,16 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) juryPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.JuryKey) + if req.Pop.BtcSigType == types.BTCSigType_BIP322 { + if err := req.Pop.VerifyBIP322(req.BabylonPk, delBTCPK, ms.btcNet); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-322 encoding: %v", err) + } + } else if req.Pop.BtcSigType == types.BTCSigType_BIP340 { + if err := req.Pop.Verify(req.BabylonPk, delBTCPK); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-340 encoding: %v", err) + } + } + // extract staking tx and its hash stakingMsgTx, err := req.StakingTx.ToMsgTx() if err != nil { diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 899aef7f5..1bc1c5f06 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -31,14 +31,9 @@ func (v *BTCValidator) ValidateBasic() error { if v.Pop == nil { return fmt.Errorf("empty proof of possession") } - - // verify PoP if err := v.Pop.ValidateBasic(); err != nil { return err } - if err := v.Pop.Verify(v.BabylonPk, v.BtcPk); err != nil { - return err - } return nil } @@ -70,14 +65,9 @@ func (d *BTCDelegation) ValidateBasic() error { if err := d.StakingTx.ValidateBasic(); err != nil { return err } - - // verify PoP if err := d.Pop.ValidateBasic(); err != nil { return err } - if err := d.Pop.Verify(d.BabylonPk, d.BtcPk); err != nil { - return err - } return nil } @@ -290,9 +280,6 @@ func (p *ProofOfPossession) ValidateBasic() error { if p.BtcSig == nil { return fmt.Errorf("empty BTC signature") } - if _, err := p.BtcSig.ToBTCSig(); err != nil { - return fmt.Errorf("BtcSig is incorrectly formatted: %w", err) - } return nil } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index cf923dbea..70cb7f759 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -541,57 +541,6 @@ func (m *BTCDelegatorDelegations) GetDels() []*BTCDelegation { return nil } -// ProofOfPossession is the proof of possession that a Babylon secp256k1 -// secret key and a Bitcoin secp256k1 secret key are held by the same -// person -type ProofOfPossession struct { - // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) - BabylonSig []byte `protobuf:"bytes,1,opt,name=babylon_sig,json=babylonSig,proto3" json:"babylon_sig,omitempty"` - // btc_sig is the signature generated via sign(sk_btc, babylon_sig) - // the signature follows encoding in BIP-340 spec - BtcSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=btc_sig,json=btcSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"btc_sig,omitempty"` -} - -func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } -func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } -func (*ProofOfPossession) ProtoMessage() {} -func (*ProofOfPossession) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{6} -} -func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ProofOfPossession) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ProofOfPossession.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ProofOfPossession) XXX_Merge(src proto.Message) { - xxx_messageInfo_ProofOfPossession.Merge(m, src) -} -func (m *ProofOfPossession) XXX_Size() int { - return m.Size() -} -func (m *ProofOfPossession) XXX_DiscardUnknown() { - xxx_messageInfo_ProofOfPossession.DiscardUnknown(m) -} - -var xxx_messageInfo_ProofOfPossession proto.InternalMessageInfo - -func (m *ProofOfPossession) GetBabylonSig() []byte { - if m != nil { - return m.BabylonSig - } - return nil -} - // BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and // transaction which commits to the provided script. type BabylonBTCTaprootTx struct { @@ -605,7 +554,7 @@ func (m *BabylonBTCTaprootTx) Reset() { *m = BabylonBTCTaprootTx{} } func (m *BabylonBTCTaprootTx) String() string { return proto.CompactTextString(m) } func (*BabylonBTCTaprootTx) ProtoMessage() {} func (*BabylonBTCTaprootTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{7} + return fileDescriptor_3851ae95ccfaf7db, []int{6} } func (m *BabylonBTCTaprootTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -656,7 +605,6 @@ func init() { proto.RegisterType((*BTCUndelegation)(nil), "babylon.btcstaking.v1.BTCUndelegation") proto.RegisterType((*BTCUndelegationInfo)(nil), "babylon.btcstaking.v1.BTCUndelegationInfo") proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") - proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") } @@ -665,71 +613,69 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1012 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4d, 0x6f, 0x23, 0x45, - 0x13, 0xce, 0xd8, 0x8e, 0x13, 0x97, 0x9d, 0x37, 0x4e, 0x6f, 0x36, 0xeb, 0x77, 0x11, 0x76, 0xd6, - 0xac, 0x22, 0x6b, 0xc5, 0x8e, 0x49, 0xf6, 0x43, 0x0b, 0x12, 0x48, 0x4c, 0x1c, 0x41, 0xb4, 0x24, - 0x6b, 0xc6, 0x93, 0x05, 0x71, 0xc0, 0x9a, 0x8f, 0xce, 0x78, 0xb0, 0x33, 0x3d, 0x4c, 0xb7, 0x8d, - 0x7d, 0xe7, 0xc0, 0x91, 0x1b, 0x7f, 0x84, 0x1f, 0xc0, 0x71, 0x8f, 0x2b, 0x4e, 0x28, 0x87, 0x08, - 0x25, 0x7f, 0x04, 0x75, 0x4f, 0x8f, 0x3d, 0x49, 0xd6, 0x0b, 0xc1, 0xe6, 0x14, 0x77, 0x57, 0xd5, - 0x53, 0x55, 0xcf, 0x53, 0x99, 0x6a, 0xd8, 0xb2, 0x4c, 0x6b, 0xd4, 0x23, 0x7e, 0xdd, 0x62, 0x36, - 0x65, 0x66, 0xd7, 0xf3, 0xdd, 0xfa, 0x60, 0x3b, 0x71, 0x52, 0x83, 0x90, 0x30, 0x82, 0x6e, 0x4b, - 0x3f, 0x35, 0x61, 0x19, 0x6c, 0xdf, 0x5d, 0x77, 0x89, 0x4b, 0x84, 0x47, 0x9d, 0xff, 0x8a, 0x9c, - 0xef, 0xfe, 0xdf, 0x26, 0xf4, 0x84, 0xd0, 0x76, 0x64, 0x88, 0x0e, 0xd2, 0x54, 0x8d, 0x4e, 0x75, - 0x3b, 0x1c, 0x05, 0x8c, 0xd4, 0x29, 0xb6, 0x83, 0x9d, 0x27, 0x4f, 0xbb, 0xdb, 0xf5, 0x2e, 0x1e, - 0xc5, 0x3e, 0xf7, 0xa5, 0xcf, 0xa4, 0x1e, 0x0b, 0x33, 0x73, 0xbb, 0x7e, 0xa9, 0xa2, 0xea, 0x59, - 0x1a, 0x0a, 0x9a, 0xb1, 0xfb, 0xd2, 0xec, 0x79, 0x8e, 0xc9, 0x48, 0x88, 0xf6, 0x20, 0xef, 0x60, - 0x6a, 0x87, 0x5e, 0xc0, 0x3c, 0xe2, 0x97, 0x94, 0x4d, 0xa5, 0x96, 0xdf, 0x79, 0x4f, 0x95, 0xe9, - 0x27, 0x45, 0x0b, 0x30, 0xb5, 0x31, 0x71, 0xd5, 0x93, 0x71, 0xe8, 0x6b, 0x00, 0x9b, 0x9c, 0x9c, - 0x78, 0x94, 0x72, 0x94, 0xd4, 0xa6, 0x52, 0xcb, 0x69, 0xcf, 0x4e, 0xcf, 0x2a, 0x5b, 0xae, 0xc7, - 0x3a, 0x7d, 0x4b, 0xb5, 0xc9, 0x49, 0x3d, 0x6e, 0x42, 0xfc, 0x79, 0x48, 0x9d, 0x6e, 0x9d, 0x8d, - 0x02, 0x4c, 0xd5, 0x06, 0xb6, 0x7f, 0xff, 0xf5, 0x21, 0xc8, 0x94, 0x0d, 0x6c, 0xeb, 0x09, 0x2c, - 0xf4, 0x09, 0x80, 0x64, 0xb1, 0x1d, 0x74, 0x4b, 0x69, 0x51, 0x5f, 0x25, 0xae, 0x2f, 0x22, 0x44, - 0x1d, 0x13, 0xa2, 0x36, 0xfb, 0xd6, 0x73, 0x3c, 0xd2, 0x73, 0x32, 0xa4, 0xd9, 0x45, 0x07, 0x90, - 0xb5, 0x98, 0xcd, 0x63, 0x33, 0x9b, 0x4a, 0xad, 0xa0, 0x3d, 0x3d, 0x3d, 0xab, 0xec, 0x24, 0xaa, - 0x92, 0x9e, 0x76, 0xc7, 0xf4, 0xfc, 0xf8, 0x20, 0x0b, 0xd3, 0xf6, 0x9b, 0x8f, 0x1e, 0x7f, 0x20, - 0x21, 0x17, 0x2d, 0x66, 0x37, 0xbb, 0xe8, 0x23, 0x48, 0x07, 0x24, 0x28, 0x2d, 0x8a, 0x3a, 0x6a, - 0xea, 0x1b, 0x05, 0x56, 0x9b, 0x21, 0x21, 0xc7, 0x2f, 0x8e, 0x9b, 0x84, 0x52, 0x2c, 0xba, 0xd0, - 0x79, 0x10, 0x7a, 0x0c, 0x1b, 0xb4, 0x67, 0xd2, 0x0e, 0x76, 0xda, 0x71, 0x4b, 0x1d, 0xec, 0xb9, - 0x1d, 0x56, 0xca, 0x6e, 0x2a, 0xb5, 0x8c, 0xbe, 0x2e, 0xad, 0x5a, 0x64, 0xfc, 0x5c, 0xd8, 0xd0, - 0xfb, 0x80, 0xc6, 0x51, 0xcc, 0x8e, 0x23, 0x96, 0x44, 0x44, 0x31, 0x8e, 0x60, 0x76, 0xe4, 0x5d, - 0xfd, 0x31, 0x05, 0xeb, 0x49, 0x81, 0xbf, 0xf2, 0x58, 0xe7, 0x00, 0x33, 0x33, 0xc1, 0x83, 0x32, - 0x0f, 0x1e, 0x36, 0x20, 0x2b, 0x2b, 0x49, 0x89, 0x4a, 0xe4, 0x09, 0xdd, 0x83, 0xc2, 0x80, 0x30, - 0xcf, 0x77, 0xdb, 0x01, 0xf9, 0x01, 0x87, 0x42, 0xb0, 0x8c, 0x9e, 0x8f, 0xee, 0x9a, 0xfc, 0xea, - 0x2d, 0x34, 0x64, 0x6e, 0x4c, 0xc3, 0xe2, 0x14, 0x1a, 0x7e, 0xc9, 0xc2, 0x8a, 0x66, 0xec, 0x36, - 0x70, 0x0f, 0xbb, 0x26, 0xbb, 0x3e, 0x47, 0xca, 0x0c, 0x73, 0x94, 0x9a, 0xe3, 0x1c, 0xa5, 0xff, - 0xcd, 0x1c, 0x19, 0x00, 0x03, 0xb3, 0xd7, 0x9e, 0xcb, 0x58, 0x2f, 0x0f, 0xcc, 0x9e, 0x26, 0x2a, - 0xba, 0x07, 0x05, 0xca, 0xcc, 0x90, 0x5d, 0xa6, 0x36, 0x2f, 0xee, 0xa4, 0x06, 0xef, 0x02, 0x60, - 0xdf, 0xb9, 0x3c, 0xb4, 0x39, 0xec, 0x3b, 0xd2, 0xfc, 0x0e, 0xe4, 0x18, 0x61, 0x66, 0xaf, 0x4d, - 0xcd, 0x78, 0x40, 0x97, 0xc5, 0x45, 0xcb, 0x64, 0x68, 0x1f, 0x40, 0x76, 0xd6, 0x66, 0xc3, 0xd2, - 0xb2, 0xe8, 0xfb, 0xc1, 0x94, 0xbe, 0xa5, 0xf2, 0x9a, 0xb1, 0x6b, 0x98, 0x41, 0x48, 0x08, 0x33, - 0x86, 0x7a, 0x4e, 0xda, 0x8d, 0x21, 0xda, 0x81, 0xbc, 0x10, 0x5c, 0x62, 0xe5, 0x04, 0x01, 0x6b, - 0xa7, 0x67, 0x15, 0x2e, 0x79, 0x4b, 0x5a, 0x8c, 0xa1, 0x0e, 0x74, 0xfc, 0x1b, 0x7d, 0x0b, 0x2b, - 0x4e, 0x34, 0x0c, 0x24, 0x6c, 0x53, 0xcf, 0x2d, 0x81, 0x88, 0xfa, 0xf0, 0xf4, 0xac, 0xf2, 0xe4, - 0x26, 0xb4, 0xb5, 0x3c, 0xd7, 0x37, 0x59, 0x3f, 0xc4, 0x7a, 0x61, 0x8c, 0xd7, 0xf2, 0x5c, 0x64, - 0xc0, 0xf2, 0x77, 0xfd, 0x70, 0x24, 0xa0, 0xf3, 0xb3, 0x42, 0x2f, 0x71, 0x28, 0x8e, 0xfa, 0x25, - 0x14, 0xb9, 0xca, 0x7d, 0xdf, 0x19, 0x0f, 0x72, 0xa9, 0x20, 0xa8, 0xdb, 0x9a, 0x46, 0x9d, 0xb1, - 0x7b, 0x94, 0xf0, 0xd6, 0x57, 0x2d, 0x66, 0x27, 0x2f, 0xaa, 0xaf, 0x32, 0xb0, 0x7a, 0xc5, 0x09, - 0x1d, 0x40, 0xa1, 0xef, 0x5b, 0xc4, 0x77, 0x24, 0xa3, 0xca, 0x8d, 0xd5, 0xc9, 0x8f, 0xe3, 0xaf, - 0xeb, 0x93, 0xfa, 0x27, 0xfa, 0x10, 0xd8, 0x48, 0xe8, 0x13, 0x47, 0x73, 0x36, 0xd3, 0xb3, 0xb2, - 0xb9, 0x3e, 0x11, 0x4a, 0xe2, 0x72, 0x6a, 0x31, 0xac, 0x45, 0x82, 0x25, 0x73, 0x65, 0x66, 0xcd, - 0xb5, 0x2a, 0x94, 0x4b, 0xa4, 0x71, 0x01, 0x89, 0x34, 0x13, 0x7e, 0x79, 0x9e, 0xc5, 0x59, 0xf3, - 0x14, 0x39, 0xe8, 0x51, 0x8c, 0xc9, 0x13, 0x7d, 0x0f, 0x77, 0x06, 0xf1, 0x47, 0xff, 0x4a, 0xb6, - 0xec, 0xac, 0xd9, 0x6e, 0x8f, 0x91, 0x93, 0x29, 0xab, 0xbf, 0xa5, 0xe0, 0xd6, 0x95, 0x51, 0xda, - 0xf7, 0x8f, 0xc9, 0xbc, 0xc7, 0xe9, 0x2d, 0x9d, 0xa5, 0xfe, 0x9b, 0xce, 0xa6, 0xa8, 0x96, 0x9e, - 0xbb, 0x6a, 0xd5, 0x16, 0xdc, 0x99, 0xac, 0x29, 0x12, 0x4e, 0xf6, 0x15, 0x45, 0xcf, 0x20, 0xe3, - 0xe0, 0x1e, 0x2d, 0x29, 0x9b, 0xe9, 0x5a, 0x7e, 0xe7, 0xfe, 0xf4, 0xff, 0xf7, 0x49, 0x90, 0x2e, - 0x22, 0xaa, 0x3f, 0x29, 0xb0, 0x76, 0x6d, 0x75, 0xa0, 0x0a, 0xe4, 0xe3, 0x05, 0xc8, 0x9b, 0x11, - 0xaf, 0x00, 0x3d, 0xde, 0x89, 0xbc, 0x69, 0x1d, 0x96, 0xf8, 0xc7, 0x66, 0x2e, 0xbc, 0xf2, 0x5d, - 0xc9, 0xfb, 0xfb, 0x18, 0x6e, 0xbd, 0x41, 0x5f, 0xf4, 0x3f, 0x48, 0xc9, 0xb9, 0x28, 0xe8, 0x29, - 0x36, 0xe4, 0xaf, 0x89, 0xe8, 0x2d, 0x19, 0x65, 0xd6, 0xe5, 0xe9, 0xc1, 0x73, 0x31, 0x60, 0x93, - 0x06, 0x5b, 0xcc, 0x64, 0x7d, 0x8a, 0xf2, 0xb0, 0xd4, 0xdc, 0x3b, 0x6c, 0xec, 0x1f, 0x7e, 0x56, - 0x5c, 0x40, 0x00, 0xd9, 0x4f, 0x77, 0x8d, 0xfd, 0x97, 0x7b, 0x45, 0x05, 0xad, 0x40, 0xee, 0xe8, - 0x50, 0x7b, 0x11, 0x99, 0x52, 0xa8, 0x00, 0xcb, 0xd1, 0x71, 0xaf, 0x51, 0x4c, 0x6b, 0x5f, 0xbc, - 0x3a, 0x2f, 0x2b, 0xaf, 0xcf, 0xcb, 0xca, 0x9f, 0xe7, 0x65, 0xe5, 0xe7, 0x8b, 0xf2, 0xc2, 0xeb, - 0x8b, 0xf2, 0xc2, 0x1f, 0x17, 0xe5, 0x85, 0x6f, 0xfe, 0x76, 0x71, 0x0e, 0x93, 0x2f, 0x7d, 0xd1, - 0xb1, 0x95, 0x15, 0x0f, 0xea, 0x47, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x25, 0xa9, 0x4e, 0x2a, - 0x0c, 0x0c, 0x00, 0x00, + // 984 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5f, 0x6f, 0x1b, 0x45, + 0x10, 0xcf, 0x9d, 0x1d, 0x27, 0x1e, 0x3b, 0xc4, 0xdd, 0xa6, 0xa9, 0x09, 0xc2, 0x4e, 0x4d, 0x15, + 0x59, 0x15, 0x3d, 0x93, 0xf4, 0x8f, 0x0a, 0x12, 0x48, 0x5c, 0x1c, 0x81, 0x55, 0x92, 0x9a, 0xf3, + 0xa5, 0x20, 0x1e, 0xb0, 0xee, 0xcf, 0xe6, 0x7c, 0xd8, 0xb9, 0x3d, 0x6e, 0xd7, 0xc6, 0x7e, 0xe7, + 0x03, 0xf0, 0xc6, 0x17, 0xe1, 0x03, 0xf0, 0xd8, 0xc7, 0x8a, 0x27, 0x94, 0x07, 0x0b, 0x25, 0x5f, + 0x04, 0xdd, 0xde, 0x9e, 0x7d, 0x49, 0xe3, 0x42, 0xb0, 0x79, 0xca, 0xcd, 0xce, 0xcc, 0x6f, 0x66, + 0x7e, 0xbf, 0x89, 0x77, 0x61, 0xc7, 0x34, 0xcc, 0x51, 0x8f, 0x78, 0x35, 0x93, 0x59, 0x94, 0x19, + 0x5d, 0xd7, 0x73, 0x6a, 0x83, 0xdd, 0x84, 0xa5, 0xf8, 0x01, 0x61, 0x04, 0xdd, 0x11, 0x71, 0x4a, + 0xc2, 0x33, 0xd8, 0xdd, 0xda, 0x70, 0x88, 0x43, 0x78, 0x44, 0x2d, 0xfc, 0x8a, 0x82, 0xb7, 0xde, + 0xb5, 0x08, 0x3d, 0x25, 0xb4, 0x1d, 0x39, 0x22, 0x43, 0xb8, 0x2a, 0x91, 0x55, 0xb3, 0x82, 0x91, + 0xcf, 0x48, 0x8d, 0x62, 0xcb, 0xdf, 0x7b, 0xf2, 0xb4, 0xbb, 0x5b, 0xeb, 0xe2, 0x51, 0x1c, 0x73, + 0x5f, 0xc4, 0x4c, 0xfb, 0x31, 0x31, 0x33, 0x76, 0x6b, 0x97, 0x3a, 0xda, 0x2a, 0x5f, 0xdf, 0xb9, + 0x4f, 0xfc, 0x28, 0xa0, 0x32, 0x4e, 0x41, 0x5e, 0xd5, 0xf7, 0x5f, 0x1a, 0x3d, 0xd7, 0x36, 0x18, + 0x09, 0xd0, 0x01, 0xe4, 0x6c, 0x4c, 0xad, 0xc0, 0xf5, 0x99, 0x4b, 0xbc, 0xa2, 0xb4, 0x2d, 0x55, + 0x73, 0x7b, 0x1f, 0x28, 0xa2, 0xbf, 0xe9, 0x54, 0xbc, 0x9a, 0x52, 0x9f, 0x86, 0x6a, 0xc9, 0x3c, + 0xf4, 0x2d, 0x80, 0x45, 0x4e, 0x4f, 0x5d, 0x4a, 0x43, 0x14, 0x79, 0x5b, 0xaa, 0x66, 0xd5, 0x67, + 0x67, 0xe3, 0xf2, 0x8e, 0xe3, 0xb2, 0x4e, 0xdf, 0x54, 0x2c, 0x72, 0x5a, 0x8b, 0xa7, 0xe4, 0x7f, + 0x1e, 0x52, 0xbb, 0x5b, 0x63, 0x23, 0x1f, 0x53, 0xa5, 0x8e, 0xad, 0x3f, 0x7e, 0x7b, 0x08, 0xa2, + 0x64, 0x1d, 0x5b, 0x5a, 0x02, 0x0b, 0x7d, 0x06, 0x20, 0x86, 0x6a, 0xfb, 0xdd, 0x62, 0x8a, 0xf7, + 0x57, 0x8e, 0xfb, 0x8b, 0x18, 0x53, 0x26, 0x8c, 0x29, 0xcd, 0xbe, 0xf9, 0x1c, 0x8f, 0xb4, 0xac, + 0x48, 0x69, 0x76, 0xd1, 0x21, 0x64, 0x4c, 0x66, 0x85, 0xb9, 0xe9, 0x6d, 0xa9, 0x9a, 0x57, 0x9f, + 0x9e, 0x8d, 0xcb, 0x7b, 0x89, 0xae, 0x44, 0xa4, 0xd5, 0x31, 0x5c, 0x2f, 0x36, 0x44, 0x63, 0x6a, + 0xa3, 0xf9, 0xe8, 0xf1, 0x47, 0x02, 0x72, 0xd9, 0x64, 0x56, 0xb3, 0x8b, 0x3e, 0x81, 0x94, 0x4f, + 0xfc, 0xe2, 0x32, 0xef, 0xa3, 0xaa, 0x5c, 0xbb, 0x01, 0x4a, 0x33, 0x20, 0xe4, 0xe4, 0xc5, 0x49, + 0x93, 0x50, 0x8a, 0xf9, 0x14, 0x5a, 0x98, 0x84, 0x1e, 0xc3, 0x26, 0xed, 0x19, 0xb4, 0x83, 0xed, + 0x76, 0x3c, 0x52, 0x07, 0xbb, 0x4e, 0x87, 0x15, 0x33, 0xdb, 0x52, 0x35, 0xad, 0x6d, 0x08, 0xaf, + 0x1a, 0x39, 0xbf, 0xe4, 0x3e, 0xf4, 0x21, 0xa0, 0x49, 0x16, 0xb3, 0xe2, 0x8c, 0x15, 0x9e, 0x51, + 0x88, 0x33, 0x98, 0x15, 0x45, 0x57, 0x7e, 0x96, 0x61, 0x23, 0x29, 0xf0, 0x37, 0x2e, 0xeb, 0x1c, + 0x62, 0x66, 0x24, 0x78, 0x90, 0x16, 0xc1, 0xc3, 0x26, 0x64, 0x44, 0x27, 0x32, 0xef, 0x44, 0x58, + 0xe8, 0x1e, 0xe4, 0x07, 0x84, 0xb9, 0x9e, 0xd3, 0xf6, 0xc9, 0x4f, 0x38, 0xe0, 0x82, 0xa5, 0xb5, + 0x5c, 0x74, 0xd6, 0x0c, 0x8f, 0xde, 0x42, 0x43, 0xfa, 0xc6, 0x34, 0x2c, 0xcf, 0xa0, 0xe1, 0xd7, + 0x0c, 0xac, 0xa9, 0xfa, 0x7e, 0x1d, 0xf7, 0xb0, 0x63, 0xb0, 0x37, 0xf7, 0x48, 0x9a, 0x63, 0x8f, + 0xe4, 0x05, 0xee, 0x51, 0xea, 0xbf, 0xec, 0x91, 0x0e, 0x30, 0x30, 0x7a, 0xed, 0x85, 0xac, 0xf5, + 0xea, 0xc0, 0xe8, 0xa9, 0xbc, 0xa3, 0x7b, 0x90, 0xa7, 0xcc, 0x08, 0xd8, 0x65, 0x6a, 0x73, 0xfc, + 0x4c, 0x68, 0xf0, 0x3e, 0x00, 0xf6, 0xec, 0xcb, 0x4b, 0x9b, 0xc5, 0x9e, 0x2d, 0xdc, 0xef, 0x41, + 0x96, 0x11, 0x66, 0xf4, 0xda, 0xd4, 0x88, 0x17, 0x74, 0x95, 0x1f, 0xb4, 0x0c, 0x86, 0x1a, 0x00, + 0x62, 0xb2, 0x36, 0x1b, 0x16, 0x57, 0xf9, 0xdc, 0x0f, 0x66, 0xcc, 0x2d, 0x94, 0x57, 0xf5, 0x7d, + 0xdd, 0xf0, 0x03, 0x42, 0x98, 0x3e, 0xd4, 0xb2, 0xc2, 0xaf, 0x0f, 0xd1, 0x1e, 0xe4, 0xb8, 0xe0, + 0x02, 0x2b, 0xcb, 0x09, 0xb8, 0x75, 0x36, 0x2e, 0x87, 0x92, 0xb7, 0x84, 0x47, 0x1f, 0x6a, 0x40, + 0x27, 0xdf, 0xe8, 0x7b, 0x58, 0xb3, 0xa3, 0x65, 0x20, 0x41, 0x9b, 0xba, 0x4e, 0x11, 0x78, 0xd6, + 0xc7, 0x67, 0xe3, 0xf2, 0x93, 0x9b, 0xd0, 0xd6, 0x72, 0x1d, 0xcf, 0x60, 0xfd, 0x00, 0x6b, 0xf9, + 0x09, 0x5e, 0xcb, 0x75, 0x90, 0x0e, 0xab, 0x3f, 0xf4, 0x83, 0x11, 0x87, 0xce, 0xcd, 0x0b, 0xbd, + 0x12, 0x42, 0x85, 0xa8, 0x5f, 0x43, 0x21, 0x54, 0xb9, 0xef, 0xd9, 0x93, 0x45, 0x2e, 0xe6, 0x39, + 0x75, 0x3b, 0xb3, 0xa8, 0xd3, 0xf7, 0x8f, 0x13, 0xd1, 0xda, 0xba, 0xc9, 0xac, 0xe4, 0x41, 0xe5, + 0x55, 0x1a, 0xd6, 0xaf, 0x04, 0xa1, 0x43, 0xc8, 0xf7, 0x3d, 0x93, 0x78, 0xb6, 0x60, 0x54, 0xba, + 0xb1, 0x3a, 0xb9, 0x49, 0xfe, 0x9b, 0xfa, 0xc8, 0xff, 0x46, 0x1f, 0x02, 0x9b, 0x09, 0x7d, 0xe2, + 0xec, 0x90, 0xcd, 0xd4, 0xbc, 0x6c, 0x6e, 0x4c, 0x85, 0x12, 0xb8, 0x21, 0xb5, 0x18, 0x6e, 0x45, + 0x82, 0x25, 0x6b, 0xa5, 0xe7, 0xad, 0xb5, 0xce, 0x95, 0x4b, 0x94, 0x71, 0x00, 0xf1, 0x32, 0x53, + 0x7e, 0xc3, 0x3a, 0xcb, 0xf3, 0xd6, 0x29, 0x84, 0xa0, 0xc7, 0x31, 0x66, 0x58, 0xe8, 0x47, 0xb8, + 0x3b, 0x88, 0x7f, 0xf4, 0xaf, 0x54, 0xcb, 0xcc, 0x5b, 0xed, 0xce, 0x04, 0x39, 0x59, 0xb2, 0xf2, + 0xbb, 0x0c, 0xb7, 0xaf, 0xac, 0x52, 0xc3, 0x3b, 0x21, 0x8b, 0x5e, 0xa7, 0xb7, 0x4c, 0x26, 0xff, + 0x3f, 0x93, 0xcd, 0x50, 0x2d, 0xb5, 0x70, 0xd5, 0x2a, 0x2d, 0xb8, 0x3b, 0xbd, 0xa6, 0x48, 0x30, + 0xbd, 0xaf, 0x28, 0x7a, 0x06, 0x69, 0x1b, 0xf7, 0x68, 0x51, 0xda, 0x4e, 0x55, 0x73, 0x7b, 0xf7, + 0x67, 0xff, 0xbf, 0x4f, 0x93, 0x34, 0x9e, 0x51, 0xf9, 0x14, 0x6e, 0x5f, 0x43, 0x2a, 0x7a, 0x07, + 0x64, 0x21, 0x46, 0x5e, 0x93, 0xd9, 0x30, 0xbc, 0xc2, 0xa3, 0x07, 0x5c, 0x44, 0xa3, 0x26, 0xac, + 0x07, 0xcf, 0xb9, 0xaa, 0x53, 0xd4, 0x16, 0x33, 0x58, 0x9f, 0xa2, 0x1c, 0xac, 0x34, 0x0f, 0x8e, + 0xea, 0x8d, 0xa3, 0x2f, 0x0a, 0x4b, 0x08, 0x20, 0xf3, 0xf9, 0xbe, 0xde, 0x78, 0x79, 0x50, 0x90, + 0xd0, 0x1a, 0x64, 0x8f, 0x8f, 0xd4, 0x17, 0x91, 0x4b, 0x46, 0x79, 0x58, 0x8d, 0xcc, 0x83, 0x7a, + 0x21, 0xa5, 0x7e, 0xf5, 0xea, 0xbc, 0x24, 0xbd, 0x3e, 0x2f, 0x49, 0x7f, 0x9d, 0x97, 0xa4, 0x5f, + 0x2e, 0x4a, 0x4b, 0xaf, 0x2f, 0x4a, 0x4b, 0x7f, 0x5e, 0x94, 0x96, 0xbe, 0xfb, 0xc7, 0xdb, 0x6a, + 0x98, 0x7c, 0xc5, 0x72, 0x42, 0xcd, 0x0c, 0x7f, 0xc5, 0x3e, 0xfa, 0x3b, 0x00, 0x00, 0xff, 0xff, + 0x20, 0xb1, 0xff, 0x55, 0xa2, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1217,48 +1163,6 @@ func (m *BTCDelegatorDelegations) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *ProofOfPossession) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ProofOfPossession) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.BtcSig != nil { - { - size := m.BtcSig.Size() - i -= size - if _, err := m.BtcSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.BabylonSig) > 0 { - i -= len(m.BabylonSig) - copy(dAtA[i:], m.BabylonSig) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.BabylonSig))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *BabylonBTCTaprootTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1490,23 +1394,6 @@ func (m *BTCDelegatorDelegations) Size() (n int) { return n } -func (m *ProofOfPossession) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.BabylonSig) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - if m.BtcSig != nil { - l = m.BtcSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) - } - return n -} - func (m *BabylonBTCTaprootTx) Size() (n int) { if m == nil { return 0 @@ -2885,125 +2772,6 @@ func (m *BTCDelegatorDelegations) Unmarshal(dAtA []byte) error { } return nil } -func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ProofOfPossession: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ProofOfPossession: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BabylonSig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.BabylonSig = append(m.BabylonSig[:0], dAtA[iNdEx:postIndex]...) - if m.BabylonSig == nil { - m.BabylonSig = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcSig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.BtcSig = &v - if err := m.BtcSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBtcstaking(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBtcstaking - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *BabylonBTCTaprootTx) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index d1f6f968d..eb3625384 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -5,7 +5,6 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -73,7 +72,8 @@ func (m *MsgCreateBTCValidator) ValidateBasic() error { if err := m.Pop.ValidateBasic(); err != nil { return err } - return m.Pop.Verify(m.BabylonPk, m.BtcPk) + + return nil } func (m *MsgCreateBTCDelegation) GetSigners() []sdk.AccAddress { @@ -111,19 +111,9 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if err := m.StakingTx.ValidateBasic(); err != nil { return err } - - // verify PoP if err := m.Pop.ValidateBasic(); err != nil { return err } - stakingScriptData, err := m.StakingTx.GetScriptData() - if err != nil { - return err - } - btcPK := bbn.NewBIP340PubKeyFromBTCPK(stakingScriptData.StakerKey) - if err := m.Pop.Verify(m.BabylonPk, btcPK); err != nil { - return err - } return nil } diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index 1bb841ab3..5c61251d6 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -4,9 +4,11 @@ import ( "encoding/hex" "fmt" + "github.com/babylonchain/babylon/crypto/bip322" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg" "github.com/cometbft/cometbft/crypto/tmhash" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) @@ -16,7 +18,9 @@ import ( // - pop.BabylonSig = sign(sk_Babylon, pk_BTC) // - pop.BtcSig = sign(sk_BTC, pop.BabylonSig) func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPossession, error) { - pop := ProofOfPossession{} + pop := ProofOfPossession{ + BtcSigType: BTCSigType_BIP340, // by default, we use BIP-340 encoding for BTC signature + } // generate pop.BabylonSig = sign(sk_Babylon, pk_BTC) btcPK := btcSK.PubKey() @@ -36,7 +40,7 @@ func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPos return nil, err } bip340Sig := bbn.NewBIP340SignatureFromBTCSig(btcSig) - pop.BtcSig = &bip340Sig + pop.BtcSig = bip340Sig.MustMarshal() return &pop, nil } @@ -61,12 +65,20 @@ func (pop *ProofOfPossession) ToHexStr() (string, error) { return hex.EncodeToString(popBytes), nil } -// Verify verifies the validity of PoP +// Verify verifies the validity of PoP where Bitcoin signature is in BIP-340 // 1. verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? // 2. verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey) error { + if pop.BtcSigType != BTCSigType_BIP340 { + return fmt.Errorf("the Bitcoin signature in this proof of possession is not using BIP-340 encoding") + } + // rule 1: verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? - btcSig, err := pop.BtcSig.ToBTCSig() + bip340Sig, err := bbn.NewBIP340Signature(pop.BtcSig) + if err != nil { + return err + } + btcSig, err := bip340Sig.ToBTCSig() if err != nil { return err } @@ -88,3 +100,40 @@ func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn return nil } + +// VerifyBIP322 verifies the validity of PoP where Bitcoin signature is in BIP-322 +// after decoding pop.BtcSig to bip322Sig which contains sig and address, +// 1. verify(sig=bip322Sig.Sig, address=bip322Sig.Address, msg=pop.BabylonSig)? +// 2. verify(sig=pop.BabylonSig, pubkey=babylonPK, msg=bip340PK)? +// 3. verify pop.Address corresponds to bip340PK in the given network +func (pop *ProofOfPossession) VerifyBIP322(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey, net *chaincfg.Params) error { + if pop.BtcSigType != BTCSigType_BIP322 { + return fmt.Errorf("the Bitcoin signature in this proof of possession is not using BIP-322 encoding") + } + + // unmarshal pop.BtcSig to bip322Sig + var bip322Sig BIP322Sig + if err := bip322Sig.Unmarshal(pop.BtcSig); err != nil { + return nil + } + + // rule 1: verify(sig=bip322Sig.Sig, address=bip322Sig.Address, msg=pop.BabylonSig)? + // TODO: temporary solution for MVP purposes. + // Eventually we need to use tmhash.Sum(pop.BabylonSig) rather than bbnSigHashHexBytes + // ref: https://github.com/babylonchain/babylon-private/issues/80 + bbnSigHash := tmhash.Sum(pop.BabylonSig) + bbnSigHashHex := hex.EncodeToString(bbnSigHash) + bbnSigHashHexBytes := []byte(bbnSigHashHex) + if err := bip322.Verify(bbnSigHashHexBytes, bip322Sig.Sig, bip322Sig.Address, net); err != nil { + return err + } + + // rule 2: verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? + if !babylonPK.VerifySignature(*bip340PK, pop.BabylonSig) { + return fmt.Errorf("failed to verify pop.BabylonSig") + } + + // TODO: rule 3: verify bip322Sig.Address corresponds to bip340PK + + return nil +} diff --git a/x/btcstaking/types/pop.pb.go b/x/btcstaking/types/pop.pb.go new file mode 100644 index 000000000..dace92982 --- /dev/null +++ b/x/btcstaking/types/pop.pb.go @@ -0,0 +1,675 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btcstaking/v1/pop.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// BTCSigType indicates the type of btc_sig in a pop +type BTCSigType int32 + +const ( + // BIP340 means the btc_sig will follow the BIP-340 encoding + BTCSigType_BIP340 BTCSigType = 0 + // BIP322 means the btc_sig will follow the BIP-322 encoding + BTCSigType_BIP322 BTCSigType = 1 +) + +var BTCSigType_name = map[int32]string{ + 0: "BIP340", + 1: "BIP322", +} + +var BTCSigType_value = map[string]int32{ + "BIP340": 0, + "BIP322": 1, +} + +func (x BTCSigType) String() string { + return proto.EnumName(BTCSigType_name, int32(x)) +} + +func (BTCSigType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_9d6ceb088d9e9f3a, []int{0} +} + +// ProofOfPossession is the proof of possession that a Babylon secp256k1 +// secret key and a Bitcoin secp256k1 secret key are held by the same +// person +type ProofOfPossession struct { + // btc_sig_type indicates the type of btc_sig in the pop + BtcSigType BTCSigType `protobuf:"varint,1,opt,name=btc_sig_type,json=btcSigType,proto3,enum=babylon.btcstaking.v1.BTCSigType" json:"btc_sig_type,omitempty"` + // babylon_sig is the signature generated via sign(sk_babylon, pk_btc) + BabylonSig []byte `protobuf:"bytes,2,opt,name=babylon_sig,json=babylonSig,proto3" json:"babylon_sig,omitempty"` + // btc_sig is the signature generated via sign(sk_btc, babylon_sig) + // the signature follows encoding in either BIP-340 spec or BIP-322 spec + BtcSig []byte `protobuf:"bytes,3,opt,name=btc_sig,json=btcSig,proto3" json:"btc_sig,omitempty"` +} + +func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } +func (m *ProofOfPossession) String() string { return proto.CompactTextString(m) } +func (*ProofOfPossession) ProtoMessage() {} +func (*ProofOfPossession) Descriptor() ([]byte, []int) { + return fileDescriptor_9d6ceb088d9e9f3a, []int{0} +} +func (m *ProofOfPossession) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ProofOfPossession) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ProofOfPossession.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ProofOfPossession) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProofOfPossession.Merge(m, src) +} +func (m *ProofOfPossession) XXX_Size() int { + return m.Size() +} +func (m *ProofOfPossession) XXX_DiscardUnknown() { + xxx_messageInfo_ProofOfPossession.DiscardUnknown(m) +} + +var xxx_messageInfo_ProofOfPossession proto.InternalMessageInfo + +func (m *ProofOfPossession) GetBtcSigType() BTCSigType { + if m != nil { + return m.BtcSigType + } + return BTCSigType_BIP340 +} + +func (m *ProofOfPossession) GetBabylonSig() []byte { + if m != nil { + return m.BabylonSig + } + return nil +} + +func (m *ProofOfPossession) GetBtcSig() []byte { + if m != nil { + return m.BtcSig + } + return nil +} + +// BIP322Sig is a BIP-322 signature together with the address corresponding to +// the signer +type BIP322Sig struct { + // address is the signer's address + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // sig is the actual signature in BIP-322 format + Sig []byte `protobuf:"bytes,2,opt,name=sig,proto3" json:"sig,omitempty"` +} + +func (m *BIP322Sig) Reset() { *m = BIP322Sig{} } +func (m *BIP322Sig) String() string { return proto.CompactTextString(m) } +func (*BIP322Sig) ProtoMessage() {} +func (*BIP322Sig) Descriptor() ([]byte, []int) { + return fileDescriptor_9d6ceb088d9e9f3a, []int{1} +} +func (m *BIP322Sig) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BIP322Sig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BIP322Sig.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BIP322Sig) XXX_Merge(src proto.Message) { + xxx_messageInfo_BIP322Sig.Merge(m, src) +} +func (m *BIP322Sig) XXX_Size() int { + return m.Size() +} +func (m *BIP322Sig) XXX_DiscardUnknown() { + xxx_messageInfo_BIP322Sig.DiscardUnknown(m) +} + +var xxx_messageInfo_BIP322Sig proto.InternalMessageInfo + +func (m *BIP322Sig) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *BIP322Sig) GetSig() []byte { + if m != nil { + return m.Sig + } + return nil +} + +func init() { + proto.RegisterEnum("babylon.btcstaking.v1.BTCSigType", BTCSigType_name, BTCSigType_value) + proto.RegisterType((*ProofOfPossession)(nil), "babylon.btcstaking.v1.ProofOfPossession") + proto.RegisterType((*BIP322Sig)(nil), "babylon.btcstaking.v1.BIP322Sig") +} + +func init() { proto.RegisterFile("babylon/btcstaking/v1/pop.proto", fileDescriptor_9d6ceb088d9e9f3a) } + +var fileDescriptor_9d6ceb088d9e9f3a = []byte{ + // 288 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4b, 0xc3, 0x30, + 0x1c, 0xc5, 0x1b, 0x07, 0x1b, 0xfb, 0x3b, 0x64, 0x06, 0xc4, 0x9e, 0xb2, 0x39, 0x3c, 0x0c, 0x0f, + 0xa9, 0xcb, 0x04, 0xef, 0xdd, 0x49, 0x10, 0x2c, 0xdd, 0x4e, 0x5e, 0x46, 0xd3, 0x75, 0x5d, 0x50, + 0x9b, 0xd2, 0xc4, 0x61, 0xbf, 0x85, 0xf8, 0xa9, 0x3c, 0xee, 0xe8, 0x51, 0xda, 0x2f, 0x22, 0xad, + 0x29, 0xf5, 0xe0, 0xed, 0xbd, 0xe4, 0xe5, 0x97, 0xc7, 0x83, 0x11, 0x0f, 0x78, 0xfe, 0x2c, 0x13, + 0x87, 0xeb, 0x50, 0xe9, 0xe0, 0x49, 0x24, 0xb1, 0xb3, 0x9f, 0x39, 0xa9, 0x4c, 0x69, 0x9a, 0x49, + 0x2d, 0xf1, 0x99, 0x09, 0xd0, 0x36, 0x40, 0xf7, 0xb3, 0xc9, 0x07, 0x82, 0x53, 0x2f, 0x93, 0x72, + 0xfb, 0xb0, 0xf5, 0xa4, 0x52, 0x91, 0x52, 0x42, 0x26, 0x78, 0x01, 0x03, 0xae, 0xc3, 0xb5, 0x12, + 0xf1, 0x5a, 0xe7, 0x69, 0x64, 0xa3, 0x31, 0x9a, 0x9e, 0xb0, 0x0b, 0xfa, 0x2f, 0x83, 0xba, 0xab, + 0xc5, 0x52, 0xc4, 0xab, 0x3c, 0x8d, 0x7c, 0xe0, 0x3a, 0x34, 0x1a, 0x8f, 0xe0, 0xd8, 0xe4, 0x2b, + 0x90, 0x7d, 0x34, 0x46, 0xd3, 0x81, 0x0f, 0xe6, 0x68, 0x29, 0x62, 0x7c, 0x0e, 0x3d, 0xf3, 0x8b, + 0xdd, 0xa9, 0x2f, 0xbb, 0xbf, 0xaf, 0x27, 0xb7, 0xd0, 0x77, 0xef, 0xbc, 0x39, 0x63, 0x55, 0xca, + 0x86, 0x5e, 0xb0, 0xd9, 0x64, 0x91, 0x52, 0x75, 0x8d, 0xbe, 0xdf, 0x58, 0x3c, 0x84, 0x4e, 0x0b, + 0xae, 0xe4, 0xd5, 0x25, 0x40, 0x5b, 0x06, 0x03, 0x74, 0x2b, 0xcc, 0xcd, 0xf5, 0xd0, 0x6a, 0x34, + 0x63, 0x43, 0xe4, 0xde, 0x7f, 0x16, 0x04, 0x1d, 0x0a, 0x82, 0xbe, 0x0b, 0x82, 0xde, 0x4b, 0x62, + 0x1d, 0x4a, 0x62, 0x7d, 0x95, 0xc4, 0x7a, 0x64, 0xb1, 0xd0, 0xbb, 0x57, 0x4e, 0x43, 0xf9, 0xe2, + 0x98, 0xa2, 0xe1, 0x2e, 0x10, 0x49, 0x63, 0x9c, 0xb7, 0xbf, 0xfb, 0x56, 0xd3, 0x28, 0xde, 0xad, + 0xf7, 0x9d, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xc5, 0x76, 0x11, 0x82, 0x01, 0x00, 0x00, +} + +func (m *ProofOfPossession) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProofOfPossession) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcSig) > 0 { + i -= len(m.BtcSig) + copy(dAtA[i:], m.BtcSig) + i = encodeVarintPop(dAtA, i, uint64(len(m.BtcSig))) + i-- + dAtA[i] = 0x1a + } + if len(m.BabylonSig) > 0 { + i -= len(m.BabylonSig) + copy(dAtA[i:], m.BabylonSig) + i = encodeVarintPop(dAtA, i, uint64(len(m.BabylonSig))) + i-- + dAtA[i] = 0x12 + } + if m.BtcSigType != 0 { + i = encodeVarintPop(dAtA, i, uint64(m.BtcSigType)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *BIP322Sig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BIP322Sig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BIP322Sig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Sig) > 0 { + i -= len(m.Sig) + copy(dAtA[i:], m.Sig) + i = encodeVarintPop(dAtA, i, uint64(len(m.Sig))) + i-- + dAtA[i] = 0x12 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintPop(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintPop(dAtA []byte, offset int, v uint64) int { + offset -= sovPop(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ProofOfPossession) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcSigType != 0 { + n += 1 + sovPop(uint64(m.BtcSigType)) + } + l = len(m.BabylonSig) + if l > 0 { + n += 1 + l + sovPop(uint64(l)) + } + l = len(m.BtcSig) + if l > 0 { + n += 1 + l + sovPop(uint64(l)) + } + return n +} + +func (m *BIP322Sig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovPop(uint64(l)) + } + l = len(m.Sig) + if l > 0 { + n += 1 + l + sovPop(uint64(l)) + } + return n +} + +func sovPop(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozPop(x uint64) (n int) { + return sovPop(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProofOfPossession: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProofOfPossession: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcSigType", wireType) + } + m.BtcSigType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BtcSigType |= BTCSigType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPop + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPop + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BabylonSig = append(m.BabylonSig[:0], dAtA[iNdEx:postIndex]...) + if m.BabylonSig == nil { + m.BabylonSig = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPop + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPop + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcSig = append(m.BtcSig[:0], dAtA[iNdEx:postIndex]...) + if m.BtcSig == nil { + m.BtcSig = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPop(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPop + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BIP322Sig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BIP322Sig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BIP322Sig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPop + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPop + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPop + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPop + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPop + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sig = append(m.Sig[:0], dAtA[iNdEx:postIndex]...) + if m.Sig == nil { + m.Sig = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPop(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPop + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipPop(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPop + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPop + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPop + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthPop + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupPop + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthPop + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthPop = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowPop = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupPop = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btcstaking/types/pop_test.go b/x/btcstaking/types/pop_test.go index d566cea0f..97c2b7467 100644 --- a/x/btcstaking/types/pop_test.go +++ b/x/btcstaking/types/pop_test.go @@ -40,7 +40,7 @@ func newInvalidPoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.Pri panic(err) } bip340Sig := bbn.NewBIP340SignatureFromBTCSig(btcSig) - pop.BtcSig = &bip340Sig + pop.BtcSig = bip340Sig.MustMarshal() return &pop } @@ -72,3 +72,5 @@ func FuzzPoP(f *testing.F) { require.Error(t, err) }) } + +// TODO: fuzz test for BIP322 PoP diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 136a0c165..7a2e0b177 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -795,76 +795,77 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1104 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0xcf, 0x6f, 0xe3, 0xc4, - 0x17, 0x6f, 0x92, 0x36, 0x5f, 0xf5, 0xb5, 0xdd, 0xdd, 0xaf, 0xe9, 0xb6, 0x69, 0xd0, 0x26, 0x25, - 0xbb, 0x94, 0xb2, 0x6a, 0x6d, 0x9a, 0xdd, 0x56, 0x50, 0x24, 0xa4, 0x75, 0x8b, 0x44, 0x59, 0x22, - 0x82, 0x93, 0x22, 0xc4, 0x81, 0x30, 0xb6, 0xa7, 0x8e, 0x95, 0xc4, 0x63, 0x79, 0x26, 0x55, 0x22, - 0x24, 0x0e, 0x1c, 0x39, 0x71, 0xe2, 0x82, 0xc4, 0xdf, 0xc0, 0x61, 0x4f, 0x1c, 0x38, 0xef, 0x71, - 0xd5, 0x13, 0xea, 0x21, 0x5a, 0xb5, 0x07, 0xfe, 0x0d, 0x64, 0x7b, 0xfc, 0x23, 0xc5, 0xee, 0x36, - 0x84, 0x13, 0xe2, 0x94, 0x8c, 0xdf, 0xe7, 0x7d, 0xde, 0x9b, 0xcf, 0x9b, 0xf7, 0xc6, 0x86, 0x92, - 0x8a, 0xd4, 0x61, 0x97, 0x58, 0x92, 0xca, 0x34, 0xca, 0x50, 0xc7, 0xb4, 0x0c, 0xe9, 0x74, 0x47, - 0x62, 0x03, 0xd1, 0x76, 0x08, 0x23, 0xc2, 0x5d, 0x6e, 0x17, 0x23, 0xbb, 0x78, 0xba, 0x53, 0x5c, - 0x36, 0x88, 0x41, 0x3c, 0x84, 0xe4, 0xfe, 0xf3, 0xc1, 0xc5, 0x35, 0x8d, 0xd0, 0x1e, 0xa1, 0x2d, - 0xdf, 0xe0, 0x2f, 0xb8, 0x69, 0xd5, 0x5f, 0x49, 0x3d, 0xea, 0xf1, 0xf7, 0xa8, 0xc1, 0x0d, 0x15, - 0x6e, 0xd0, 0x9c, 0xa1, 0xcd, 0x88, 0x44, 0xb1, 0x66, 0x57, 0x77, 0xf7, 0x3a, 0x3b, 0x52, 0x07, - 0x0f, 0x03, 0xe7, 0x4a, 0x72, 0x92, 0x36, 0x72, 0x50, 0x2f, 0xc0, 0x6c, 0x24, 0x63, 0x62, 0x69, - 0xfb, 0xb8, 0xad, 0x18, 0x4e, 0x6b, 0x63, 0xad, 0x63, 0x13, 0xd3, 0x62, 0x1c, 0x1a, 0x3d, 0xe0, - 0xe8, 0x07, 0x3c, 0xbb, 0x88, 0x51, 0xc5, 0x0c, 0xed, 0x48, 0x63, 0x9c, 0x95, 0x9f, 0x72, 0x70, - 0xb7, 0x46, 0x8d, 0x03, 0x07, 0x23, 0x86, 0xe5, 0xe6, 0xc1, 0xe7, 0xa8, 0x6b, 0xea, 0x88, 0x11, - 0x47, 0x58, 0x81, 0x3c, 0x35, 0x0d, 0x0b, 0x3b, 0x85, 0xcc, 0x7a, 0x66, 0x73, 0x5e, 0xe1, 0x2b, - 0xe1, 0x43, 0x58, 0xd0, 0x31, 0xd5, 0x1c, 0xd3, 0x66, 0x26, 0xb1, 0x0a, 0xd9, 0xf5, 0xcc, 0xe6, - 0x42, 0xf5, 0xbe, 0xc8, 0x25, 0x8b, 0x84, 0xf6, 0xa2, 0x89, 0x87, 0x11, 0x54, 0x89, 0xfb, 0x09, - 0x5f, 0x00, 0x68, 0xa4, 0xd7, 0x33, 0x29, 0x75, 0x59, 0x72, 0x6e, 0x08, 0xf9, 0xdd, 0xf3, 0x51, - 0x79, 0xc3, 0x30, 0x59, 0xbb, 0xaf, 0x8a, 0x1a, 0xe9, 0x49, 0x81, 0xbe, 0xde, 0xcf, 0x36, 0xd5, - 0x3b, 0x12, 0x1b, 0xda, 0x98, 0x8a, 0x87, 0x58, 0x3b, 0x7b, 0xb6, 0x0d, 0x3c, 0xe4, 0x21, 0xd6, - 0x94, 0x18, 0x97, 0xf0, 0x01, 0x00, 0x17, 0xaa, 0x65, 0x77, 0x0a, 0xb3, 0x5e, 0x7e, 0xe5, 0x20, - 0x3f, 0xbf, 0x56, 0x62, 0x58, 0x2b, 0xb1, 0xde, 0x57, 0x9f, 0xe2, 0xa1, 0x32, 0xcf, 0x5d, 0xea, - 0x1d, 0xa1, 0x06, 0x79, 0x95, 0x69, 0xae, 0xef, 0xdc, 0x7a, 0x66, 0x73, 0x51, 0xde, 0x3b, 0x1f, - 0x95, 0xab, 0xb1, 0xac, 0x38, 0x52, 0x6b, 0x23, 0xd3, 0x0a, 0x16, 0x3c, 0x31, 0xf9, 0xa8, 0xfe, - 0xe8, 0xf1, 0x3b, 0x9c, 0x72, 0x4e, 0x65, 0x5a, 0xbd, 0x23, 0xec, 0x43, 0xce, 0x26, 0x76, 0x21, - 0xef, 0xe5, 0xb1, 0x29, 0x26, 0x1e, 0x4a, 0xb1, 0xee, 0x10, 0x72, 0xf2, 0xe9, 0x49, 0x9d, 0x50, - 0x8a, 0xbd, 0x5d, 0x28, 0xae, 0x53, 0xa5, 0x0c, 0xf7, 0x12, 0x8b, 0xa3, 0x60, 0x6a, 0x13, 0x8b, - 0xe2, 0xca, 0x28, 0x07, 0x2b, 0x71, 0xc4, 0x21, 0xee, 0x62, 0x03, 0x79, 0x02, 0xa7, 0xd5, 0x6f, - 0x5c, 0x9e, 0xec, 0xc4, 0xf2, 0xf0, 0xfd, 0xe4, 0xfe, 0xc6, 0x7e, 0x84, 0x23, 0x00, 0x0e, 0x6a, - 0xb1, 0x01, 0x2f, 0xcd, 0xc3, 0x14, 0x0a, 0xd9, 0x7f, 0x2a, 0x37, 0x0f, 0x9a, 0xc8, 0x76, 0x08, - 0x61, 0xcd, 0x81, 0x32, 0xcf, 0xed, 0xcd, 0x81, 0xf0, 0x19, 0xdc, 0x8e, 0xa8, 0x5a, 0xa6, 0x75, - 0x42, 0xbc, 0x72, 0x2d, 0x54, 0xdf, 0x8e, 0xf3, 0xc5, 0xba, 0xe2, 0x74, 0x47, 0x6c, 0x3a, 0xc8, - 0xa2, 0x48, 0x73, 0xe5, 0x39, 0xb2, 0x4e, 0x88, 0xb2, 0x14, 0xd2, 0xb9, 0x4b, 0xa1, 0x0a, 0x0b, - 0xb4, 0x8b, 0x68, 0x9b, 0xa7, 0x97, 0xf7, 0xaa, 0xff, 0xff, 0xf3, 0x51, 0x79, 0x49, 0x6e, 0x1e, - 0x34, 0xb8, 0xa5, 0x39, 0x50, 0x80, 0x86, 0xff, 0x85, 0xaf, 0x60, 0x49, 0xf7, 0x35, 0x27, 0x4e, - 0x8b, 0x9a, 0x46, 0xe1, 0x7f, 0x9e, 0xd7, 0x7b, 0xe7, 0xa3, 0xf2, 0xee, 0x24, 0x67, 0xa6, 0x61, - 0x1a, 0x16, 0x62, 0x7d, 0x07, 0x2b, 0x8b, 0x21, 0x5f, 0xc3, 0x34, 0x2a, 0xeb, 0x50, 0x4a, 0xae, - 0x6f, 0x78, 0x04, 0x7e, 0xce, 0xc2, 0x9d, 0x1a, 0x35, 0xe4, 0xe6, 0xc1, 0xb1, 0xc5, 0x5d, 0x71, - 0x6a, 0xf1, 0x6b, 0xb0, 0xd8, 0xb7, 0x54, 0x62, 0xe9, 0x7c, 0x8f, 0xd9, 0x89, 0x4b, 0xb0, 0x10, - 0xfa, 0x37, 0x07, 0x57, 0x15, 0xcb, 0xdd, 0x44, 0x31, 0x02, 0x2b, 0x31, 0xc5, 0x02, 0x6f, 0x57, - 0xba, 0xd9, 0x69, 0xa5, 0x5b, 0x8e, 0xa4, 0xe3, 0xbc, 0xae, 0x84, 0x45, 0x28, 0x5c, 0xd5, 0x27, - 0x14, 0xef, 0xd7, 0x2c, 0x2c, 0xd5, 0xa8, 0xf1, 0x44, 0xd7, 0x3f, 0xee, 0x3b, 0xc3, 0x86, 0x69, - 0x5c, 0xa3, 0x5c, 0xfe, 0x14, 0x75, 0x83, 0x96, 0x99, 0x62, 0x2a, 0x9c, 0xa2, 0xae, 0x3f, 0x64, - 0x74, 0xec, 0xd1, 0xe5, 0xa6, 0xa3, 0xd3, 0xb1, 0x4b, 0xb7, 0x31, 0xd6, 0x0d, 0x6d, 0x44, 0xdb, - 0x9e, 0x9a, 0xf3, 0xb1, 0x23, 0xfe, 0x11, 0xa2, 0x6d, 0xe1, 0x29, 0xe4, 0x5c, 0xa5, 0xe7, 0xa6, - 0x55, 0xda, 0x65, 0xa9, 0xac, 0x7a, 0x57, 0x47, 0xa4, 0x5d, 0xa8, 0xea, 0x8f, 0x19, 0xb8, 0x5d, - 0xa3, 0xc6, 0xb1, 0xad, 0x23, 0x86, 0xeb, 0xde, 0x55, 0x27, 0xec, 0xc1, 0x3c, 0xea, 0xb3, 0x36, - 0x71, 0x4c, 0x36, 0xf4, 0xa5, 0x95, 0x0b, 0x67, 0xcf, 0xb6, 0x97, 0xf9, 0xe0, 0x79, 0xa2, 0xeb, - 0x0e, 0xa6, 0xb4, 0xc1, 0x1c, 0xd3, 0x32, 0x94, 0x08, 0x2a, 0xbc, 0x0f, 0x79, 0xff, 0xb2, 0xe4, - 0x67, 0xf5, 0x5e, 0xda, 0xc4, 0xf1, 0x40, 0xf2, 0xec, 0xf3, 0x51, 0x79, 0x46, 0xe1, 0x2e, 0xfb, - 0xb7, 0xbe, 0xfb, 0xe3, 0x97, 0x87, 0x11, 0x59, 0x65, 0x0d, 0x56, 0xaf, 0xe4, 0x15, 0xe6, 0x7c, - 0x96, 0xf3, 0x6c, 0x7c, 0x37, 0xc7, 0xc1, 0x21, 0x6f, 0x98, 0x06, 0xfd, 0x97, 0x9f, 0x09, 0x0d, - 0xee, 0xc4, 0x67, 0x42, 0xeb, 0x1f, 0x39, 0x20, 0xb7, 0x62, 0x63, 0xc2, 0x6d, 0x2b, 0x06, 0x6b, - 0x61, 0xaf, 0xff, 0x25, 0x5a, 0x7e, 0xda, 0x68, 0x2b, 0x01, 0xf7, 0xf1, 0x58, 0xd4, 0xca, 0x1b, - 0x50, 0x4e, 0xa9, 0x69, 0x58, 0xf7, 0x97, 0x59, 0x78, 0xdd, 0xc7, 0x84, 0xb7, 0x6b, 0x1c, 0xf8, - 0x5f, 0xed, 0xa7, 0xae, 0x7d, 0xe5, 0x4d, 0xb8, 0x7f, 0x8d, 0xc2, 0x41, 0x25, 0xaa, 0xbf, 0xe5, - 0x21, 0x57, 0xa3, 0x86, 0x30, 0x00, 0x21, 0xe1, 0x75, 0x74, 0x2b, 0xa5, 0xef, 0x13, 0xdf, 0x8f, - 0x8a, 0x8f, 0x27, 0x41, 0x07, 0x19, 0x08, 0xdf, 0xc0, 0x6b, 0x49, 0x6f, 0x52, 0xdb, 0x37, 0x20, - 0x8b, 0xe0, 0xc5, 0xdd, 0x89, 0xe0, 0x61, 0x70, 0x13, 0x96, 0xc6, 0xef, 0xf0, 0xb7, 0xd2, 0x79, - 0xc6, 0x80, 0x45, 0xe9, 0x86, 0xc0, 0x30, 0xd4, 0xd7, 0x00, 0xb1, 0x1b, 0xef, 0x41, 0xba, 0x7b, - 0x84, 0x2a, 0x6e, 0xdd, 0x04, 0x15, 0x46, 0xf8, 0x16, 0x96, 0x13, 0x27, 0xa9, 0xf8, 0x4a, 0x96, - 0x31, 0x7c, 0x71, 0x6f, 0x32, 0x7c, 0x18, 0xff, 0xfb, 0x0c, 0x14, 0x52, 0x5b, 0xba, 0x7a, 0x2d, - 0x69, 0xa2, 0x4f, 0x71, 0x7f, 0x72, 0x9f, 0x30, 0x99, 0x13, 0x58, 0x1c, 0xbb, 0x0a, 0x37, 0xd2, - 0xb9, 0xe2, 0xb8, 0xa2, 0x78, 0x33, 0x5c, 0x10, 0x47, 0xfe, 0xe4, 0xf9, 0x45, 0x29, 0xf3, 0xe2, - 0xa2, 0x94, 0x79, 0x79, 0x51, 0xca, 0xfc, 0x70, 0x59, 0x9a, 0x79, 0x71, 0x59, 0x9a, 0xf9, 0xfd, - 0xb2, 0x34, 0xf3, 0xe5, 0x2b, 0x27, 0xc9, 0x20, 0xfe, 0xed, 0xe9, 0x75, 0xb5, 0x9a, 0xf7, 0x3e, - 0x10, 0x1f, 0xfd, 0x19, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x77, 0x1d, 0xb1, 0x67, 0x0f, 0x00, 0x00, + // 1110 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0xe3, 0xc4, + 0x1b, 0x6e, 0x92, 0x36, 0x3f, 0xf5, 0x6d, 0xbb, 0xbb, 0x3f, 0xd3, 0x6d, 0xd3, 0xa0, 0x4d, 0x4a, + 0x76, 0x29, 0x65, 0xd5, 0xda, 0x34, 0xbb, 0xad, 0xa0, 0x48, 0x48, 0xeb, 0x16, 0x89, 0xb2, 0x44, + 0x04, 0x27, 0x45, 0x88, 0x03, 0x61, 0x6c, 0x4f, 0x1d, 0x2b, 0x89, 0xc7, 0xf2, 0x4c, 0xaa, 0x44, + 0x48, 0x1c, 0x38, 0x72, 0xe2, 0xc4, 0x05, 0x89, 0xcf, 0xc0, 0x61, 0x4f, 0x1c, 0x38, 0xef, 0x71, + 0xd5, 0x13, 0xea, 0x21, 0x5a, 0xb5, 0x07, 0xbe, 0x06, 0xb2, 0x3d, 0xfe, 0x93, 0x62, 0x77, 0x1b, + 0xc2, 0x09, 0x71, 0x4a, 0xc6, 0xef, 0xf3, 0x3e, 0xef, 0x3b, 0xcf, 0x33, 0x7f, 0x6c, 0x28, 0xa9, + 0x48, 0x1d, 0x76, 0x89, 0x25, 0xa9, 0x4c, 0xa3, 0x0c, 0x75, 0x4c, 0xcb, 0x90, 0x4e, 0x77, 0x24, + 0x36, 0x10, 0x6d, 0x87, 0x30, 0x22, 0xdc, 0xe5, 0x71, 0x31, 0x8a, 0x8b, 0xa7, 0x3b, 0xc5, 0x65, + 0x83, 0x18, 0xc4, 0x43, 0x48, 0xee, 0x3f, 0x1f, 0x5c, 0x5c, 0xd3, 0x08, 0xed, 0x11, 0xda, 0xf2, + 0x03, 0xfe, 0x80, 0x87, 0x56, 0xfd, 0x91, 0xd4, 0xa3, 0x1e, 0x7f, 0x8f, 0x1a, 0x3c, 0x50, 0xe1, + 0x01, 0xcd, 0x19, 0xda, 0x8c, 0x48, 0x14, 0x6b, 0x76, 0x75, 0x77, 0xaf, 0xb3, 0x23, 0x75, 0xf0, + 0x30, 0x48, 0xae, 0x24, 0x37, 0x69, 0x23, 0x07, 0xf5, 0x02, 0xcc, 0x46, 0x32, 0x26, 0xd6, 0xb6, + 0x8f, 0xdb, 0x8a, 0xe1, 0xb4, 0x36, 0xd6, 0x3a, 0x36, 0x31, 0x2d, 0xc6, 0xa1, 0xd1, 0x03, 0x8e, + 0x7e, 0xc0, 0xbb, 0x8b, 0x18, 0x55, 0xcc, 0xd0, 0x8e, 0x34, 0xce, 0x59, 0x4e, 0xe9, 0x8f, 0xd8, + 0x3e, 0xa0, 0xf2, 0x53, 0x0e, 0xee, 0xd6, 0xa8, 0x71, 0xe0, 0x60, 0xc4, 0xb0, 0xdc, 0x3c, 0xf8, + 0x1c, 0x75, 0x4d, 0x1d, 0x31, 0xe2, 0x08, 0x2b, 0x90, 0xa7, 0xa6, 0x61, 0x61, 0xa7, 0x90, 0x59, + 0xcf, 0x6c, 0xce, 0x2b, 0x7c, 0x24, 0x7c, 0x08, 0x0b, 0x3a, 0xa6, 0x9a, 0x63, 0xda, 0xcc, 0x24, + 0x56, 0x21, 0xbb, 0x9e, 0xd9, 0x5c, 0xa8, 0xde, 0x17, 0xb9, 0xa6, 0x91, 0x13, 0x5e, 0x3b, 0xe2, + 0x61, 0x04, 0x55, 0xe2, 0x79, 0xc2, 0x17, 0x00, 0x1a, 0xe9, 0xf5, 0x4c, 0x4a, 0x5d, 0x96, 0x9c, + 0x5b, 0x42, 0x7e, 0xf7, 0x7c, 0x54, 0xde, 0x30, 0x4c, 0xd6, 0xee, 0xab, 0xa2, 0x46, 0x7a, 0x52, + 0x60, 0x80, 0xf7, 0xb3, 0x4d, 0xf5, 0x8e, 0xc4, 0x86, 0x36, 0xa6, 0xe2, 0x21, 0xd6, 0xce, 0x9e, + 0x6d, 0x03, 0x2f, 0x79, 0x88, 0x35, 0x25, 0xc6, 0x25, 0x7c, 0x00, 0xc0, 0x67, 0xdd, 0xb2, 0x3b, + 0x85, 0x59, 0xaf, 0xbf, 0x72, 0xd0, 0x9f, 0x6f, 0xa6, 0x18, 0x9a, 0x29, 0xd6, 0xfb, 0xea, 0x53, + 0x3c, 0x54, 0xe6, 0x79, 0x4a, 0xbd, 0x23, 0xd4, 0x20, 0xaf, 0x32, 0xcd, 0xcd, 0x9d, 0x5b, 0xcf, + 0x6c, 0x2e, 0xca, 0x7b, 0xe7, 0xa3, 0x72, 0x35, 0xd6, 0x15, 0x47, 0x6a, 0x6d, 0x64, 0x5a, 0xc1, + 0x80, 0x37, 0x26, 0x1f, 0xd5, 0x1f, 0x3d, 0x7e, 0x87, 0x53, 0xce, 0xa9, 0x4c, 0xab, 0x77, 0x84, + 0x7d, 0xc8, 0xd9, 0xc4, 0x2e, 0xe4, 0xbd, 0x3e, 0x36, 0xc5, 0xc4, 0x55, 0x2b, 0xd6, 0x1d, 0x42, + 0x4e, 0x3e, 0x3d, 0xa9, 0x13, 0x4a, 0xb1, 0x37, 0x0b, 0xc5, 0x4d, 0xaa, 0x94, 0xe1, 0x5e, 0xa2, + 0x39, 0x0a, 0xa6, 0x36, 0xb1, 0x28, 0xae, 0x8c, 0x72, 0xb0, 0x12, 0x47, 0x1c, 0xe2, 0x2e, 0x36, + 0x90, 0x27, 0x70, 0x9a, 0x7f, 0xe3, 0xf2, 0x64, 0x27, 0x96, 0x87, 0xcf, 0x27, 0xf7, 0x37, 0xe6, + 0x23, 0x1c, 0x01, 0x70, 0x50, 0x8b, 0x0d, 0xb8, 0x35, 0x0f, 0x53, 0x28, 0x64, 0xff, 0xa9, 0xdc, + 0x3c, 0x68, 0x22, 0xdb, 0x21, 0x84, 0x35, 0x07, 0xca, 0x3c, 0x8f, 0x37, 0x07, 0xc2, 0x67, 0x70, + 0x3b, 0xa2, 0x6a, 0x99, 0xd6, 0x09, 0xf1, 0xec, 0x5a, 0xa8, 0xbe, 0x1d, 0xe7, 0x8b, 0x6d, 0x9b, + 0xd3, 0x1d, 0xb1, 0xe9, 0x20, 0x8b, 0x22, 0xcd, 0x95, 0xe7, 0xc8, 0x3a, 0x21, 0xca, 0x52, 0x48, + 0xe7, 0x0e, 0x85, 0x2a, 0x2c, 0xd0, 0x2e, 0xa2, 0x6d, 0xde, 0x5e, 0xde, 0x73, 0xff, 0xff, 0xe7, + 0xa3, 0xf2, 0x92, 0xdc, 0x3c, 0x68, 0xf0, 0x48, 0x73, 0xa0, 0x00, 0x0d, 0xff, 0x0b, 0x5f, 0xc1, + 0x92, 0xee, 0x6b, 0x4e, 0x9c, 0x16, 0x35, 0x8d, 0xc2, 0xff, 0xbc, 0xac, 0xf7, 0xce, 0x47, 0xe5, + 0xdd, 0x49, 0xd6, 0x4c, 0xc3, 0x34, 0x2c, 0xc4, 0xfa, 0x0e, 0x56, 0x16, 0x43, 0xbe, 0x86, 0x69, + 0x54, 0xd6, 0xa1, 0x94, 0xec, 0x6f, 0xb8, 0x04, 0x7e, 0xce, 0xc2, 0x9d, 0x1a, 0x35, 0xe4, 0xe6, + 0xc1, 0xb1, 0xc5, 0x53, 0x71, 0xaa, 0xf9, 0x35, 0x58, 0xec, 0x5b, 0x2a, 0xb1, 0x74, 0x3e, 0xc7, + 0xec, 0xc4, 0x16, 0x2c, 0x84, 0xf9, 0xcd, 0xc1, 0x55, 0xc5, 0x72, 0x37, 0x51, 0x8c, 0xc0, 0x4a, + 0x4c, 0xb1, 0x20, 0xdb, 0x95, 0x6e, 0x76, 0x5a, 0xe9, 0x96, 0x23, 0xe9, 0x38, 0xaf, 0x2b, 0x61, + 0x11, 0x0a, 0x57, 0xf5, 0x09, 0xc5, 0xfb, 0x35, 0x0b, 0x4b, 0x35, 0x6a, 0x3c, 0xd1, 0xf5, 0x8f, + 0xfb, 0xce, 0xb0, 0x61, 0x1a, 0xd7, 0x28, 0x97, 0x3f, 0x45, 0xdd, 0x60, 0xcb, 0x4c, 0x71, 0x2a, + 0x9c, 0xa2, 0xae, 0x7f, 0xc8, 0xe8, 0xd8, 0xa3, 0xcb, 0x4d, 0x47, 0xa7, 0x63, 0x97, 0x6e, 0x63, + 0x6c, 0x37, 0xb4, 0x11, 0x6d, 0x7b, 0x6a, 0xce, 0xc7, 0x96, 0xf8, 0x47, 0x88, 0xb6, 0x85, 0xa7, + 0x90, 0x73, 0x95, 0x9e, 0x9b, 0x56, 0x69, 0x97, 0xa5, 0xb2, 0xea, 0x5d, 0x1d, 0x91, 0x76, 0xa1, + 0xaa, 0x3f, 0x66, 0xe0, 0x76, 0x8d, 0x1a, 0xc7, 0xb6, 0x8e, 0x18, 0xae, 0x7b, 0x77, 0xa1, 0xb0, + 0x07, 0xf3, 0xa8, 0xcf, 0xda, 0xc4, 0x31, 0xd9, 0xd0, 0x97, 0x56, 0x2e, 0x9c, 0x3d, 0xdb, 0x5e, + 0xe6, 0x07, 0xcf, 0x13, 0x5d, 0x77, 0x30, 0xa5, 0x0d, 0xe6, 0x98, 0x96, 0xa1, 0x44, 0x50, 0xe1, + 0x7d, 0xc8, 0xfb, 0xb7, 0x29, 0x5f, 0xab, 0xf7, 0xd2, 0x4e, 0x1c, 0x0f, 0x24, 0xcf, 0x3e, 0x1f, + 0x95, 0x67, 0x14, 0x9e, 0xb2, 0x7f, 0xeb, 0xbb, 0x3f, 0x7e, 0x79, 0x18, 0x91, 0x55, 0xd6, 0x60, + 0xf5, 0x4a, 0x5f, 0x61, 0xcf, 0x67, 0x39, 0x2f, 0xc6, 0x67, 0x73, 0x1c, 0x2c, 0xf2, 0x86, 0x69, + 0xd0, 0x7f, 0xf9, 0x9a, 0xd0, 0xe0, 0x4e, 0xfc, 0x4c, 0x68, 0xfd, 0x23, 0x0b, 0xe4, 0x56, 0xec, + 0x98, 0x70, 0xb7, 0x15, 0x83, 0xb5, 0x70, 0xaf, 0xff, 0xa5, 0x5a, 0x7e, 0xda, 0x6a, 0x2b, 0x01, + 0xf7, 0xf1, 0x58, 0xd5, 0xca, 0x1b, 0x50, 0x4e, 0xf1, 0x34, 0xf4, 0xfd, 0x65, 0x16, 0x5e, 0xf7, + 0x31, 0xe1, 0xed, 0x1a, 0x07, 0xfe, 0xe7, 0xfd, 0xd4, 0xde, 0x57, 0xde, 0x84, 0xfb, 0xd7, 0x28, + 0x1c, 0x38, 0x51, 0xfd, 0x2d, 0x0f, 0xb9, 0x1a, 0x35, 0x84, 0x01, 0x08, 0x09, 0xaf, 0xa3, 0x5b, + 0x29, 0xfb, 0x3e, 0xf1, 0xfd, 0xa8, 0xf8, 0x78, 0x12, 0x74, 0xd0, 0x81, 0xf0, 0x0d, 0xbc, 0x96, + 0xf4, 0x26, 0xb5, 0x7d, 0x03, 0xb2, 0x08, 0x5e, 0xdc, 0x9d, 0x08, 0x1e, 0x16, 0x37, 0x61, 0x69, + 0xfc, 0x0e, 0x7f, 0x2b, 0x9d, 0x67, 0x0c, 0x58, 0x94, 0x6e, 0x08, 0x0c, 0x4b, 0x7d, 0x0d, 0x10, + 0xbb, 0xf1, 0x1e, 0xa4, 0xa7, 0x47, 0xa8, 0xe2, 0xd6, 0x4d, 0x50, 0x61, 0x85, 0x6f, 0x61, 0x39, + 0xf1, 0x24, 0x15, 0x5f, 0xc9, 0x32, 0x86, 0x2f, 0xee, 0x4d, 0x86, 0x0f, 0xeb, 0x7f, 0x9f, 0x81, + 0x42, 0xea, 0x96, 0xae, 0x5e, 0x4b, 0x9a, 0x98, 0x53, 0xdc, 0x9f, 0x3c, 0x27, 0x6c, 0xe6, 0x04, + 0x16, 0xc7, 0xae, 0xc2, 0x8d, 0x74, 0xae, 0x38, 0xae, 0x28, 0xde, 0x0c, 0x17, 0xd4, 0x91, 0x3f, + 0x79, 0x7e, 0x51, 0xca, 0xbc, 0xb8, 0x28, 0x65, 0x5e, 0x5e, 0x94, 0x32, 0x3f, 0x5c, 0x96, 0x66, + 0x5e, 0x5c, 0x96, 0x66, 0x7e, 0xbf, 0x2c, 0xcd, 0x7c, 0xf9, 0xca, 0x93, 0x64, 0x10, 0xff, 0x40, + 0xf4, 0x76, 0xb5, 0x9a, 0xf7, 0x3e, 0x10, 0x1f, 0xfd, 0x19, 0x00, 0x00, 0xff, 0xff, 0x92, 0x66, + 0xef, 0x13, 0x88, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. From f4230e064c4c9c5ff4cc9496e7ebab25e6dfefbe Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Tue, 19 Sep 2023 16:57:08 +0200 Subject: [PATCH 082/202] Return only undelegation which require jury sig (#82) * Return only undelegation which require jury sig --- x/btcstaking/keeper/grpc_query.go | 8 +++++++- x/btcstaking/keeper/grpc_query_test.go | 9 +++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index d452fc44a..8010fbfd9 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -146,7 +146,8 @@ func (k Keeper) UnbondingBTCDelegations(ctx context.Context, req *types.QueryUnb btcDels := k.getDelegationsMatchingCriteria( sdkCtx, func(del *types.BTCDelegation) bool { - // grab all delegations which are unbonding and already have validator signature + // grab all delegations which are unbonding and already have validator signature, but did not receive + // jury signature yet if del.BtcUndelegation == nil { return false } @@ -155,6 +156,11 @@ func (k Keeper) UnbondingBTCDelegations(ctx context.Context, req *types.QueryUnb return false } + // undelegation already received jury signature, no need to retrieve it + if del.BtcUndelegation.JuryUnbondingSig != nil { + return false + } + return true }, ) diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 4b5abe996..b2c63255e 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -264,7 +264,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { // Generate a random number of BTC delegations under each validator startHeight := datagen.RandomInt(r, 100) + 1 endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 - numBTCDels := datagen.RandomInt(r, 10) + 1 + numBTCDels := datagen.RandomInt(r, 100) + 1 unbondingBtcDelsMap := make(map[string]*types.BTCDelegation) for _, btcVal := range btcVals { for j := uint64(0); j < numBTCDels; j++ { @@ -280,7 +280,12 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { ValidatorUnbondingSig: btcDel.JurySig, } - unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel + if datagen.RandomInt(r, 2) == 1 { + // some undelegations already have jury sig so they should not be returned + btcDel.BtcUndelegation.JuryUnbondingSig = btcDel.JurySig + } else { + unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel + } } err = keeper.SetBTCDelegation(ctx, btcDel) From 06b529fb5ff4858f57ca9fa3637c31fe2dbf071d Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 20 Sep 2023 13:05:10 +0300 Subject: [PATCH 083/202] feat: Genesis CLI param for minimum commission rate and param validations (#83) --- cmd/babylond/cmd/flags.go | 6 ++++++ cmd/babylond/cmd/genesis.go | 11 ++++++++-- cmd/babylond/cmd/testnet.go | 2 +- x/btcstaking/keeper/msg_server_test.go | 2 ++ x/btcstaking/types/genesis_test.go | 14 +++++++++--- x/btcstaking/types/params.go | 30 ++++++++++++++++++++++++++ x/finality/types/genesis_test.go | 10 ++++++--- x/finality/types/params.go | 11 ++++++++++ 8 files changed, 77 insertions(+), 9 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 7c0781b11..1c522280c 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -1,6 +1,7 @@ package cmd import ( + sdk "github.com/cosmos/cosmos-sdk/types" "time" babylonApp "github.com/babylonchain/babylon/app" @@ -30,6 +31,7 @@ const ( flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" flagMinPubRand = "min-pub-rand" + flagMinCommissionRate = "min-commission-rate" ) type GenesisCLIArgs struct { @@ -52,6 +54,7 @@ type GenesisCLIArgs struct { SlashingAddress string MinSlashingTransactionFeeSat int64 MinPubRand uint64 + MinCommissionRate sdk.Dec } func addGenesisFlags(cmd *cobra.Command) { @@ -72,6 +75,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagJuryPk, btcstypes.DefaultParams().JuryPk.MarshalHex(), "Bitcoin staking jury public key") cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") + cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") // finality args cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") // inflation args @@ -98,6 +102,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { juryPk, _ := cmd.Flags().GetString(flagJuryPk) slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) + minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) @@ -125,6 +130,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { JuryPK: juryPk, SlashingAddress: slashingAddress, MinSlashingTransactionFeeSat: minSlashingFee, + MinCommissionRate: sdk.MustNewDecFromStr(minCommissionRate), MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index f15516c7e..dccf43742 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -69,7 +69,7 @@ Example: genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, + genesisCliArgs.MinCommissionRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) } else if network == "mainnet" { @@ -229,7 +229,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, juryPk string, slashingAddress string, minSlashingFee int64, - minPubRand uint64, inflationRateChange float64, + minCommissionRate sdk.Dec, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -309,10 +309,17 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 } genParams.BtcstakingParams.SlashingAddress = slashingAddress genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee + genParams.BtcstakingParams.MinCommissionRate = minCommissionRate if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } + genParams.FinalityParams = finalitytypes.DefaultParams() + genParams.FinalityParams.MinPubRand = minPubRand + if err := genParams.FinalityParams.Validate(); err != nil { + panic(err) + } + if epochInterval == 0 { panic(fmt.Sprintf("Invalid epoch interval %d", epochInterval)) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 0cd8502ef..28a0ed512 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -99,7 +99,7 @@ Example: genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, + genesisCliArgs.MinCommissionRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index d86a7402a..5c0b01c83 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -99,6 +99,7 @@ func getJuryInfo(t *testing.T, JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, + MinCommissionRate: sdk.MustNewDecFromStr("0.01"), }) require.NoError(t, err) return jurySK, juryPK, slashingAddr @@ -404,6 +405,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, + MinCommissionRate: sdk.MustNewDecFromStr("0.01"), }) require.NoError(t, err) diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index 966ba2b6f..9869f6db7 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -1,6 +1,7 @@ package types_test import ( + sdkmath "cosmossdk.io/math" "testing" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -19,9 +20,16 @@ func TestGenesisState_Validate(t *testing.T) { valid: true, }, { - desc: "valid genesis state", - genState: &types.GenesisState{}, - valid: true, + desc: "valid genesis state", + genState: &types.GenesisState{ + Params: types.Params{ + JuryPk: types.DefaultParams().JuryPk, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + }, + }, + valid: true, }, } for _, tc := range tests { diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index fbc50a6ce..84035ecb7 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -2,10 +2,12 @@ package types import ( "cosmossdk.io/math" + "fmt" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -49,8 +51,36 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{} } +func validateMinSlashingTxFeeSat(fee int64) error { + if fee <= 0 { + return fmt.Errorf("minimum slashing tx fee has to be positive") + } + return nil +} + +func validateMinCommissionRate(rate sdk.Dec) error { + if rate.IsNil() { + return fmt.Errorf("minimum commission rate cannot be nil") + } + + if rate.IsNegative() { + return fmt.Errorf("minimum commission rate cannot be negative") + } + + if rate.GT(math.LegacyOneDec()) { + return fmt.Errorf("minimum commission rate cannot be greater than 100%%") + } + return nil +} + // Validate validates the set of params func (p Params) Validate() error { + if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { + return err + } + if err := validateMinCommissionRate(p.MinCommissionRate); err != nil { + return err + } return nil } diff --git a/x/finality/types/genesis_test.go b/x/finality/types/genesis_test.go index 3036502e9..97dbf25c2 100644 --- a/x/finality/types/genesis_test.go +++ b/x/finality/types/genesis_test.go @@ -19,9 +19,13 @@ func TestGenesisState_Validate(t *testing.T) { valid: true, }, { - desc: "valid genesis state", - genState: &types.GenesisState{}, - valid: true, + desc: "valid genesis state", + genState: &types.GenesisState{ + Params: types.Params{ + MinPubRand: 200, + }, + }, + valid: true, }, } for _, tc := range tests { diff --git a/x/finality/types/params.go b/x/finality/types/params.go index 00413293d..5ea8d9515 100644 --- a/x/finality/types/params.go +++ b/x/finality/types/params.go @@ -1,6 +1,7 @@ package types import ( + "fmt" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -24,8 +25,18 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{} } +func validateMinPubRand(minPubRand uint64) error { + if minPubRand == 0 { + return fmt.Errorf("Min Pub Rand cannot be 0") + } + return nil +} + // Validate validates the set of params func (p Params) Validate() error { + if err := validateMinPubRand(p.MinPubRand); err != nil { + return err + } return nil } From bbcf33a3890c9ceb968c72b5846f4756f357959b Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 21 Sep 2023 17:28:08 +1000 Subject: [PATCH 084/202] chore: emit event upon unbonding/unbonded BTC delegations (#84) --- proto/babylon/btcstaking/v1/events.proto | 37 ++ x/btcstaking/keeper/msg_server.go | 41 ++ x/btcstaking/types/events.pb.go | 753 ++++++++++++++++++++++- 3 files changed, 814 insertions(+), 17 deletions(-) diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto index 80dc48df8..eb636aacc 100644 --- a/proto/babylon/btcstaking/v1/events.proto +++ b/proto/babylon/btcstaking/v1/events.proto @@ -1,6 +1,7 @@ syntax = "proto3"; package babylon.btcstaking.v1; +import "gogoproto/gogo.proto"; import "babylon/btcstaking/v1/btcstaking.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; @@ -16,3 +17,39 @@ message EventNewBTCDelegation { BTCDelegation btc_del = 1; } // EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation // such that the BTC delegation starts to have voting power in its timelock period message EventActivateBTCDelegation { BTCDelegation btc_del = 1; } + +// EventUnbondingBTCDelegation is the event emitted when receiving an unbonding request +// for an existing BTC delegation +message EventUnbondingBTCDelegation { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + string staking_tx_hash = 3; + // unbonding_tx_hash is the hash of the unbonding tx. + string unbonding_tx_hash = 4; +} + +// EventUnbondingBTCDelegation is the event emitted when an unbonding BTC delegation +// receives all signatures needed for becoming unbonded +message EventUnbondedBTCDelegation { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + string staking_tx_hash = 3; + // unbonding_tx_hash is the hash of the unbonding tx. + string unbonding_tx_hash = 4; + // from_state is the last state the BTC delegation was at + BTCDelegationStatus from_state = 5; +} \ No newline at end of file diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index e4cb2c600..d13c19a5c 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -384,6 +384,17 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele panic(fmt.Errorf("failed to set BTC delegation that has passed verification: %w", err)) } + // notify subscriber + event := &types.EventUnbondingBTCDelegation{ + BtcPk: del.BtcPk, + ValBtcPk: del.ValBtcPk, + StakingTxHash: stakingTxHash, + UnbondingTxHash: unbondingMsgTx.TxHash().String(), + } + if err := ctx.EventManager().EmitTypedEvent(event); err != nil { + panic(fmt.Errorf("failed to emit EventUnbondingBTCDelegation: %w", err)) + } + return &types.MsgBTCUndelegateResponse{}, nil } @@ -528,6 +539,21 @@ func (ms msgServer) AddJuryUnbondingSigs( panic("failed to set BTC delegation that has passed verification") } + // if the BTC undelegation has validator sig, then after above operations the + // BTC delegation will become unbonded + if btcDel.BtcUndelegation.HasValidatorSig() { + event := &types.EventUnbondedBTCDelegation{ + BtcPk: btcDel.BtcPk, + ValBtcPk: btcDel.ValBtcPk, + StakingTxHash: req.StakingTxHash, + UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHash(), + FromState: types.BTCDelegationStatus_UNBONDING, + } + if err := ctx.EventManager().EmitTypedEvent(event); err != nil { + panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) + } + } + return nil, nil } @@ -587,5 +613,20 @@ func (ms msgServer) AddValidatorUnbondingSig( panic("failed to set BTC delegation that has passed verification") } + // if the BTC undelegation has jury sigs, then after above operations the + // BTC delegation will become unbonded + if btcDel.BtcUndelegation.HasJurySigs() { + event := &types.EventUnbondedBTCDelegation{ + BtcPk: btcDel.BtcPk, + ValBtcPk: btcDel.ValBtcPk, + StakingTxHash: req.StakingTxHash, + UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHash(), + FromState: types.BTCDelegationStatus_UNBONDING, + } + if err := ctx.EventManager().EmitTypedEvent(event); err != nil { + panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) + } + } + return nil, nil } diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index 71a57cb8c..e36063cae 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -5,6 +5,8 @@ package types import ( fmt "fmt" + github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -160,10 +162,149 @@ func (m *EventActivateBTCDelegation) GetBtcDel() *BTCDelegation { return nil } +// EventUnbondingBTCDelegation is the event emitted when receiving an unbonding request +// for an existing BTC delegation +type EventUnbondingBTCDelegation struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // unbonding_tx_hash is the hash of the unbonding tx. + UnbondingTxHash string `protobuf:"bytes,4,opt,name=unbonding_tx_hash,json=unbondingTxHash,proto3" json:"unbonding_tx_hash,omitempty"` +} + +func (m *EventUnbondingBTCDelegation) Reset() { *m = EventUnbondingBTCDelegation{} } +func (m *EventUnbondingBTCDelegation) String() string { return proto.CompactTextString(m) } +func (*EventUnbondingBTCDelegation) ProtoMessage() {} +func (*EventUnbondingBTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{3} +} +func (m *EventUnbondingBTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventUnbondingBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventUnbondingBTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventUnbondingBTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventUnbondingBTCDelegation.Merge(m, src) +} +func (m *EventUnbondingBTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *EventUnbondingBTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_EventUnbondingBTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_EventUnbondingBTCDelegation proto.InternalMessageInfo + +func (m *EventUnbondingBTCDelegation) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +func (m *EventUnbondingBTCDelegation) GetUnbondingTxHash() string { + if m != nil { + return m.UnbondingTxHash + } + return "" +} + +// EventUnbondingBTCDelegation is the event emitted when an unbonding BTC delegation +// receives all signatures needed for becoming unbonded +type EventUnbondedBTCDelegation struct { + // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation + // the PK follows encoding in BIP-340 spec + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` + // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // this BTC delegation delegates to + // the PK follows encoding in BIP-340 spec + ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // unbonding_tx_hash is the hash of the unbonding tx. + UnbondingTxHash string `protobuf:"bytes,4,opt,name=unbonding_tx_hash,json=unbondingTxHash,proto3" json:"unbonding_tx_hash,omitempty"` + // from_state is the last state the BTC delegation was at + FromState BTCDelegationStatus `protobuf:"varint,5,opt,name=from_state,json=fromState,proto3,enum=babylon.btcstaking.v1.BTCDelegationStatus" json:"from_state,omitempty"` +} + +func (m *EventUnbondedBTCDelegation) Reset() { *m = EventUnbondedBTCDelegation{} } +func (m *EventUnbondedBTCDelegation) String() string { return proto.CompactTextString(m) } +func (*EventUnbondedBTCDelegation) ProtoMessage() {} +func (*EventUnbondedBTCDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{4} +} +func (m *EventUnbondedBTCDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventUnbondedBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventUnbondedBTCDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventUnbondedBTCDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventUnbondedBTCDelegation.Merge(m, src) +} +func (m *EventUnbondedBTCDelegation) XXX_Size() int { + return m.Size() +} +func (m *EventUnbondedBTCDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_EventUnbondedBTCDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_EventUnbondedBTCDelegation proto.InternalMessageInfo + +func (m *EventUnbondedBTCDelegation) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +func (m *EventUnbondedBTCDelegation) GetUnbondingTxHash() string { + if m != nil { + return m.UnbondingTxHash + } + return "" +} + +func (m *EventUnbondedBTCDelegation) GetFromState() BTCDelegationStatus { + if m != nil { + return m.FromState + } + return BTCDelegationStatus_PENDING +} + func init() { proto.RegisterType((*EventNewBTCValidator)(nil), "babylon.btcstaking.v1.EventNewBTCValidator") proto.RegisterType((*EventNewBTCDelegation)(nil), "babylon.btcstaking.v1.EventNewBTCDelegation") proto.RegisterType((*EventActivateBTCDelegation)(nil), "babylon.btcstaking.v1.EventActivateBTCDelegation") + proto.RegisterType((*EventUnbondingBTCDelegation)(nil), "babylon.btcstaking.v1.EventUnbondingBTCDelegation") + proto.RegisterType((*EventUnbondedBTCDelegation)(nil), "babylon.btcstaking.v1.EventUnbondedBTCDelegation") } func init() { @@ -171,23 +312,34 @@ func init() { } var fileDescriptor_74118427820fff75 = []byte{ - // 250 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, - 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, - 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x52, 0xc3, 0xae, 0x15, 0x49, 0x11, 0x58, - 0xbb, 0x52, 0x08, 0x97, 0x88, 0x2b, 0xc8, 0x38, 0xbf, 0xd4, 0x72, 0xa7, 0x10, 0xe7, 0xb0, 0xc4, - 0x9c, 0xcc, 0x94, 0xc4, 0x92, 0xfc, 0x22, 0x21, 0x1b, 0x2e, 0xf6, 0xa4, 0x92, 0xe4, 0xf8, 0xb2, - 0xc4, 0x1c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x65, 0x3d, 0xac, 0x16, 0xe9, 0x21, 0xeb, - 0x0a, 0x62, 0x4b, 0x2a, 0x49, 0x0e, 0x4b, 0xcc, 0x51, 0x0a, 0xe3, 0x12, 0x45, 0x32, 0xd5, 0x25, - 0x35, 0x27, 0x35, 0x3d, 0xb1, 0x24, 0x33, 0x3f, 0x4f, 0xc8, 0x16, 0x62, 0x6c, 0x4a, 0x2a, 0xcc, - 0x58, 0x15, 0xdc, 0xc6, 0x22, 0xb4, 0x81, 0xcd, 0x75, 0x49, 0xcd, 0x51, 0x8a, 0xe6, 0x92, 0x02, - 0x9b, 0xeb, 0x98, 0x5c, 0x92, 0x59, 0x96, 0x58, 0x92, 0x4a, 0x4d, 0xc3, 0x9d, 0x7c, 0x4e, 0x3c, - 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, - 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x28, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, - 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x62, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, - 0x1c, 0xcc, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0xf0, 0x35, 0x06, 0x04, 0x00, 0x00, - 0xff, 0xff, 0x88, 0x0d, 0x1d, 0x71, 0xc4, 0x01, 0x00, 0x00, + // 431 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x93, 0xcd, 0xaa, 0xd3, 0x40, + 0x18, 0x86, 0x9b, 0xea, 0x39, 0x7a, 0xc6, 0x9f, 0x83, 0xe1, 0x14, 0x4a, 0x85, 0x58, 0xa2, 0x94, + 0xd2, 0x45, 0x62, 0x5b, 0x71, 0xa5, 0x0b, 0x63, 0x05, 0x8b, 0x3f, 0x94, 0x18, 0xbb, 0xd0, 0x45, + 0x98, 0x49, 0xc6, 0x24, 0x74, 0x3a, 0x53, 0x9a, 0x2f, 0xb1, 0xbd, 0x0b, 0x57, 0x5e, 0x93, 0xcb, + 0x2e, 0xc5, 0x85, 0x94, 0xf6, 0x46, 0x24, 0x93, 0xb4, 0x8d, 0x50, 0x51, 0xd0, 0xa5, 0xbb, 0x49, + 0xf2, 0xbc, 0xcf, 0xc7, 0xf7, 0x92, 0x41, 0x3a, 0xc1, 0x64, 0xc9, 0x04, 0x37, 0x09, 0x78, 0x31, + 0xe0, 0x49, 0xc4, 0x03, 0x33, 0xed, 0x9a, 0x34, 0xa5, 0x1c, 0x62, 0x63, 0x36, 0x17, 0x20, 0xd4, + 0x5a, 0xc1, 0x18, 0x07, 0xc6, 0x48, 0xbb, 0x8d, 0x8b, 0x40, 0x04, 0x42, 0x12, 0x66, 0x76, 0xca, + 0xe1, 0x46, 0xeb, 0xb8, 0xb0, 0x14, 0x95, 0x9c, 0xee, 0xa0, 0x8b, 0x67, 0xd9, 0x90, 0xd7, 0xf4, + 0xa3, 0xe5, 0x3c, 0x1d, 0x63, 0x16, 0xf9, 0x18, 0xc4, 0x5c, 0x7d, 0x84, 0xae, 0x10, 0xf0, 0xdc, + 0x14, 0xb3, 0xba, 0xd2, 0x54, 0xda, 0xd7, 0x7a, 0x77, 0x8d, 0xa3, 0xe3, 0x8d, 0x72, 0xca, 0x3e, + 0x25, 0xe0, 0x8d, 0x31, 0xd3, 0xc7, 0xa8, 0x56, 0xb2, 0x0e, 0x28, 0xa3, 0x01, 0x86, 0x48, 0x70, + 0xf5, 0x71, 0xae, 0xf5, 0xe9, 0x4e, 0x7b, 0xef, 0xd7, 0xda, 0x43, 0x4c, 0x7a, 0x07, 0x94, 0xe9, + 0xef, 0x51, 0x43, 0x7a, 0x9f, 0x78, 0x10, 0xa5, 0x18, 0xe8, 0x3f, 0x95, 0x7f, 0xae, 0xa2, 0xdb, + 0xd2, 0xfe, 0x96, 0x13, 0xc1, 0xfd, 0x88, 0x07, 0x3f, 0xeb, 0x5f, 0xa1, 0x8c, 0x74, 0x67, 0x13, + 0x69, 0xbf, 0x6e, 0x3d, 0xfc, 0xf6, 0xfd, 0x4e, 0x2f, 0x88, 0x20, 0x4c, 0x88, 0xe1, 0x89, 0xa9, + 0x59, 0xcc, 0xf2, 0x42, 0x1c, 0xf1, 0xdd, 0x83, 0x09, 0xcb, 0x19, 0x8d, 0x0d, 0x6b, 0x38, 0xea, + 0x3f, 0xb8, 0x3f, 0x4a, 0xc8, 0x0b, 0xba, 0xb4, 0x4f, 0x08, 0x78, 0xa3, 0x89, 0xea, 0x20, 0x94, + 0x62, 0xe6, 0x16, 0xca, 0xea, 0x5f, 0x29, 0xaf, 0xa6, 0x98, 0x59, 0xd2, 0xda, 0x42, 0xe7, 0xc5, + 0xa2, 0x2e, 0x2c, 0xdc, 0x10, 0xc7, 0x61, 0xfd, 0x52, 0x53, 0x69, 0x9f, 0xd9, 0x37, 0x8a, 0xd7, + 0xce, 0xe2, 0x39, 0x8e, 0x43, 0xb5, 0x83, 0x6e, 0x25, 0xbb, 0x35, 0xf7, 0xe4, 0x65, 0x49, 0x9e, + 0xef, 0x3f, 0xe4, 0xac, 0xbe, 0xae, 0x16, 0xb5, 0xe7, 0xc5, 0x50, 0xff, 0x7f, 0x2f, 0x92, 0x1d, + 0x22, 0xf4, 0x61, 0x2e, 0xa6, 0x6e, 0x0c, 0x18, 0x68, 0xfd, 0xa4, 0xa9, 0xb4, 0x6f, 0xf6, 0x3a, + 0x7f, 0xf2, 0xcb, 0xbd, 0x01, 0x0c, 0x49, 0x6c, 0x9f, 0x65, 0xe9, 0xec, 0x4c, 0xad, 0x97, 0x5f, + 0x36, 0x9a, 0xb2, 0xda, 0x68, 0xca, 0x7a, 0xa3, 0x29, 0x9f, 0xb6, 0x5a, 0x65, 0xb5, 0xd5, 0x2a, + 0x5f, 0xb7, 0x5a, 0xe5, 0xdd, 0x6f, 0xd7, 0x5e, 0x94, 0xaf, 0xb8, 0xec, 0x80, 0x9c, 0xca, 0xbb, + 0xdd, 0xff, 0x11, 0x00, 0x00, 0xff, 0xff, 0x66, 0x62, 0xb6, 0xdc, 0x56, 0x04, 0x00, 0x00, } func (m *EventNewBTCValidator) Marshal() (dAtA []byte, err error) { @@ -295,6 +447,133 @@ func (m *EventActivateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *EventUnbondingBTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventUnbondingBTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventUnbondingBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.UnbondingTxHash) > 0 { + i -= len(m.UnbondingTxHash) + copy(dAtA[i:], m.UnbondingTxHash) + i = encodeVarintEvents(dAtA, i, uint64(len(m.UnbondingTxHash))) + i-- + dAtA[i] = 0x22 + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintEvents(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x1a + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventUnbondedBTCDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventUnbondedBTCDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventUnbondedBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.FromState != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.FromState)) + i-- + dAtA[i] = 0x28 + } + if len(m.UnbondingTxHash) > 0 { + i -= len(m.UnbondingTxHash) + copy(dAtA[i:], m.UnbondingTxHash) + i = encodeVarintEvents(dAtA, i, uint64(len(m.UnbondingTxHash))) + i-- + dAtA[i] = 0x22 + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintEvents(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x1a + } + if m.ValBtcPk != nil { + { + size := m.ValBtcPk.Size() + i -= size + if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.BtcPk != nil { + { + size := m.BtcPk.Size() + i -= size + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -345,6 +624,59 @@ func (m *EventActivateBTCDelegation) Size() (n int) { return n } +func (m *EventUnbondingBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovEvents(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.UnbondingTxHash) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventUnbondedBTCDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BtcPk != nil { + l = m.BtcPk.Size() + n += 1 + l + sovEvents(uint64(l)) + } + if m.ValBtcPk != nil { + l = m.ValBtcPk.Size() + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.UnbondingTxHash) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if m.FromState != 0 { + n += 1 + sovEvents(uint64(m.FromState)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -609,6 +941,393 @@ func (m *EventActivateBTCDelegation) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventUnbondingBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventUnbondingBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UnbondingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventUnbondedBTCDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventUnbondedBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPk = &v + if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UnbondingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FromState", wireType) + } + m.FromState = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FromState |= BTCDelegationStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 806a605f7b7b456863b8757d4cfd867620ebd281 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 11 Oct 2023 21:30:15 +1100 Subject: [PATCH 085/202] finality: closing iterator before finalising blocks (#88) --- x/finality/keeper/tallying.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index ca169152d..33f6eb6dd 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -36,7 +36,6 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { // - does not have validators, finalised: impossible to happen, panic // After this for loop, the blocks since earliest activated height are either finalised or non-finalisable blockRevIter := k.blockStore(ctx).ReverseIterator(sdk.Uint64ToBigEndian(uint64(activatedHeight)), nil) - defer blockRevIter.Close() for ; blockRevIter.Valid(); blockRevIter.Next() { // get the indexed block ibBytes := blockRevIter.Value() @@ -61,6 +60,9 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { panic(fmt.Errorf("block %d is finalized, but does not have a validator set", ib.Height)) } } + // closing the iterator right now before finalising the finalisable blocks + // this is to follow the contract at https://github.com/cosmos/cosmos-sdk/blob/v0.47.4/store/types/store.go#L239-L240 + blockRevIter.Close() // for each of these blocks from earliest to latest, tally the block w.r.t. existing votes for i := len(blocksToFinalize) - 1; i >= 0; i-- { From 5307ffac5068c1ed7df7faa3768dd892f4221048 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 12 Oct 2023 10:52:02 +1100 Subject: [PATCH 086/202] btcstaking: index BTC delegations for simplifying/paginating APIs (#86) --- proto/babylon/btcstaking/v1/btcstaking.proto | 9 +- proto/babylon/btcstaking/v1/query.proto | 41 +- test/e2e/btc_staking_e2e_test.go | 8 +- .../configurer/chain/queries_btcstaking.go | 6 +- x/btcstaking/client/cli/query.go | 39 +- x/btcstaking/keeper/btc_delegations.go | 187 +---- x/btcstaking/keeper/btc_delegators.go | 236 ++++++ x/btcstaking/keeper/grpc_query.go | 217 ++---- x/btcstaking/keeper/grpc_query_test.go | 82 +- x/btcstaking/keeper/incentive.go | 16 +- x/btcstaking/keeper/incentive_test.go | 2 +- x/btcstaking/keeper/msg_server.go | 14 +- x/btcstaking/keeper/msg_server_test.go | 4 +- x/btcstaking/keeper/voting_power_table.go | 12 +- .../keeper/voting_power_table_test.go | 2 +- x/btcstaking/types/btc_delegations.go | 45 ++ x/btcstaking/types/btcstaking.go | 194 ++--- x/btcstaking/types/btcstaking.pb.go | 312 ++++++-- x/btcstaking/types/errors.go | 37 +- x/btcstaking/types/keys.go | 9 +- x/btcstaking/types/pop.go | 11 + x/btcstaking/types/query.pb.go | 721 +++++++----------- x/btcstaking/types/query.pb.gw.go | 107 +-- 23 files changed, 1130 insertions(+), 1181 deletions(-) create mode 100644 x/btcstaking/keeper/btc_delegators.go create mode 100644 x/btcstaking/types/btc_delegations.go diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 0f7d765cc..8836cc5ef 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -147,11 +147,16 @@ message BTCUndelegationInfo { bytes jury_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. +// BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. message BTCDelegatorDelegations { repeated BTCDelegation dels = 1; } +// BTCDelegatorDelegationIndex is a list of staking tx hashes of BTC delegations from the same delegator. +message BTCDelegatorDelegationIndex { + repeated bytes staking_tx_hash_list = 1; +} + // BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and // transaction which commits to the provided script. message BabylonBTCTaprootTx { @@ -181,5 +186,7 @@ enum BTCDelegationStatus { // - or receiving unbonding tx and then receiving signatures from validator and jury for this // unbonding tx. UNBONDED = 3; + // ANY is any of the above status + ANY = 4; } diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index aa8371f4c..ae84a4e71 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -26,14 +26,9 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/validator"; } - // PendingBTCDelegations queries all pending BTC delegations - rpc PendingBTCDelegations(QueryPendingBTCDelegationsRequest) returns (QueryPendingBTCDelegationsResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/pending_btc_delegations"; - } - - // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature - rpc UnbondingBTCDelegations(QueryUnbondingBTCDelegationsRequest) returns (QueryUnbondingBTCDelegationsResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/unbonding_btc_delegations"; + // BTCDelegations queries all BTC delegations under a given status + rpc BTCDelegations(QueryBTCDelegationsRequest) returns (QueryBTCDelegationsResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/btc_delegations"; } // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. @@ -107,26 +102,24 @@ message QueryBTCValidatorResponse { BTCValidator btc_validator = 1; } -// QueryPendingBTCDelegationsRequest is the request type for the -// Query/PendingBTCDelegations RPC method. -message QueryPendingBTCDelegationsRequest {} +// QueryBTCDelegationsRequest is the request type for the +// Query/BTCDelegations RPC method. +message QueryBTCDelegationsRequest { + // status is the queried status for BTC delegations + BTCDelegationStatus status = 1; -// QueryPendingBTCDelegationsResponse is the response type for the -// Query/PendingBTCDelegations RPC method. -message QueryPendingBTCDelegationsResponse { - // btc_delegations contains all the queried BTC delegations. - repeated BTCDelegation btc_delegations = 1; + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; } -// QueryUnbondingBTCDelegationsRequest is the request type for the -// QueryUnbondingBTCDelegations RPC method. -message QueryUnbondingBTCDelegationsRequest {} - -// QueryUnbondingBTCDelegationsResponse is the response type for the -// QueryUnbondingBTCDelegations RPC method. -message QueryUnbondingBTCDelegationsResponse { - // btc_delegations contains all the queried BTC delegations. +// QueryBTCDelegationsResponse is the response type for the +// Query/BTCDelegations RPC method. +message QueryBTCDelegationsResponse { + // btc_delegations contains all the queried BTC delegations under the given status repeated BTCDelegation btc_delegations = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; } // QueryBTCValidatorPowerAtHeightRequest is the request type for the diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index d4df464d1..4bddaf411 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -151,7 +151,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.Nil(pendingDels.Dels[0].JurySig) // check delegation - delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHash()) + delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHashStr()) s.NotNil(delegation) expectedScript := hex.EncodeToString(stakingTx.Script) s.Equal(expectedScript, delegation.StakingScript) @@ -177,7 +177,7 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { stakingTx := pendingDel.StakingTx stakingMsgTx, err := stakingTx.ToMsgTx() s.NoError(err) - stakingTxHash := stakingTx.MustGetTxHash() + stakingTxHash := stakingTx.MustGetTxHashStr() /* generate and insert new jury signature, in order to activate the BTC delegation @@ -383,7 +383,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { stakingTx := activeDel.StakingTx stakingMsgTx, err := stakingTx.ToMsgTx() s.NoError(err) - stakingTxHash := stakingTx.MustGetTxHash() + stakingTxHash := stakingTx.MustGetTxHashStr() stakingTxChainHash, err := chainhash.NewHashFromStr(stakingTxHash) s.NoError(err) @@ -453,7 +453,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { // First sent validator signature stakingTxMsg, err := delegation.StakingTx.ToMsgTx() s.NoError(err) - stakingTxHash := delegation.StakingTx.MustGetTxHash() + stakingTxHash := delegation.StakingTx.MustGetTxHashStr() validatorUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( stakingTxMsg, diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 13b934dc8..a9bf61a0b 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -58,10 +58,12 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BT } func (n *NodeConfig) QueryUnbondingDelegations() []*bstypes.BTCDelegation { - bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/unbonding_btc_delegations", url.Values{}) + queryParams := url.Values{} + queryParams.Add("status", fmt.Sprintf("%d", bstypes.BTCDelegationStatus_UNBONDING)) + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/btc_delegations", queryParams) require.NoError(n.t, err) - var resp bstypes.QueryUnbondingBTCDelegationsResponse + var resp bstypes.QueryBTCDelegationsResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index dff329ed0..856c1fce9 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -24,7 +24,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdBTCValidators()) - cmd.AddCommand(CmdPendingBTCDelegations()) + cmd.AddCommand(CmdBTCDelegations()) cmd.AddCommand(CmdBTCValidatorsAtHeight()) cmd.AddCommand(CmdBTCValidatorPowerAtHeight()) cmd.AddCommand(CmdActivatedHeight()) @@ -65,37 +65,29 @@ func CmdBTCValidators() *cobra.Command { return cmd } -func CmdPendingBTCDelegations() *cobra.Command { +func CmdBTCDelegations() *cobra.Command { cmd := &cobra.Command{ - Use: "pending-btc-delegations", - Short: "retrieve all pending BTC delegations", - Args: cobra.NoArgs, + Use: "btc-delegations [status]", + Short: "retrieve all BTC delegations under the given status (pending, active, unbonding, unbonded, any)", + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.PendingBTCDelegations(cmd.Context(), &types.QueryPendingBTCDelegationsRequest{}) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) if err != nil { return err } - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} + status, err := types.NewBTCDelegationStatusFromString(args[0]) + if err != nil { + return err + } -func CmdUnbondingBTCDelegations() *cobra.Command { - cmd := &cobra.Command{ - Use: "unbonding-btc-delegations", - Short: "retrieve all unbonding BTC delegations which require jury signature", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx := client.GetClientContextFromCmd(cmd) - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.UnbondingBTCDelegations(cmd.Context(), &types.QueryUnbondingBTCDelegationsRequest{}) + res, err := queryClient.BTCDelegations(cmd.Context(), &types.QueryBTCDelegationsRequest{ + Status: status, + Pagination: pageReq, + }) if err != nil { return err } @@ -105,6 +97,7 @@ func CmdUnbondingBTCDelegations() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "btc-delegations") return cmd } diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index c8f011971..a0b006548 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -1,186 +1,35 @@ package keeper import ( - "fmt" - - bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) SetBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { - var ( - btcDels = types.NewBTCDelegatorDelegations() - err error - ) - if k.hasBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) { - btcDels, err = k.getBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) - if err != nil { - // this can only be a programming error - panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegations returns true")) - } - } - if err := btcDels.Add(btcDel); err != nil { - return types.ErrInvalidStakingTx.Wrapf(err.Error()) - } - - k.setBTCDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk, btcDels) - return nil -} - -func (k Keeper) getAndSetDelegations( - ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, - modifyFn func(*types.BTCDelegatorDelegations) error, -) error { - btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) - if err != nil { - return err - } - - if err := modifyFn(btcDels); err != nil { - return err - } - - k.setBTCDelegations(ctx, valBTCPK, delBTCPK, btcDels) - return nil +func (k Keeper) setBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { + store := k.btcDelegationStore(ctx) + stakingTxHash := btcDel.MustGetStakingTxHash() + btcDelBytes := k.cdc.MustMarshal(btcDel) + store.Set(stakingTxHash[:], btcDelBytes) } -// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation -// with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { - addJurySig := func(btcDels *types.BTCDelegatorDelegations) error { - if err := btcDels.AddJurySig(stakingTxHash, jurySig); err != nil { - return types.ErrInvalidJurySig.Wrapf(err.Error()) - } +func (k Keeper) getBTCDelegation(ctx sdk.Context, stakingTxHash chainhash.Hash) *types.BTCDelegation { + store := k.btcDelegationStore(ctx) + btcDelBytes := store.Get(stakingTxHash[:]) + if len(btcDelBytes) == 0 { return nil } - - return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addJurySig) -} - -func (k Keeper) AddUndelegationToBTCDelegation( - ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, - stakingTxHash string, - ud *types.BTCUndelegation, -) error { - addUndelegation := func(btcDels *types.BTCDelegatorDelegations) error { - if err := btcDels.AddUndelegation(stakingTxHash, ud); err != nil { - return types.ErrInvalidDelegationState.Wrapf(err.Error()) - } - return nil - } - - return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addUndelegation) -} - -func (k Keeper) AddValidatorSigToUndelegation( - ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, - stakingTxHash string, - sig *bbn.BIP340Signature, -) error { - addValidatorSig := func(btcDels *types.BTCDelegatorDelegations) error { - if err := btcDels.AddValidatorSigToUndelegation(stakingTxHash, sig); err != nil { - return types.ErrInvalidDelegationState.Wrapf(err.Error()) - } - return nil - } - - return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addValidatorSig) -} - -func (k Keeper) AddJurySigsToUndelegation( - ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, - stakingTxHash string, - unbondingTxSig *bbn.BIP340Signature, - slashUnbondingTxSig *bbn.BIP340Signature, -) error { - addJurySigs := func(btcDels *types.BTCDelegatorDelegations) error { - if err := btcDels.AddJurySigsToUndelegation(stakingTxHash, unbondingTxSig, slashUnbondingTxSig); err != nil { - return types.ErrInvalidDelegationState.Wrapf(err.Error()) - } - return nil - } - - return k.getAndSetDelegations(ctx, valBTCPK, delBTCPK, addJurySigs) -} - -// setBTCDelegations sets the given BTC delegation to KVStore -func (k Keeper) setBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, btcDels *types.BTCDelegatorDelegations) { - delBTCPKBytes := delBTCPK.MustMarshal() - - store := k.btcDelegationStore(ctx, valBTCPK) - btcDelBytes := k.cdc.MustMarshal(btcDels) - store.Set(delBTCPKBytes, btcDelBytes) -} - -// hasBTCDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator -func (k Keeper) hasBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { - valBTCPKBytes := valBTCPK.MustMarshal() - delBTCPKBytes := delBTCPK.MustMarshal() - - if !k.HasBTCValidator(ctx, valBTCPKBytes) { - return false - } - store := k.btcDelegationStore(ctx, valBTCPK) - return store.Has(delBTCPKBytes) -} - -// validatorDelegations gets the BTC delegations with a given BTC PK under a given BTC validator -// NOTE: Internal function which assumes that the validator exists -func (k Keeper) validatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPKBytes []byte) (*types.BTCDelegatorDelegations, error) { - store := k.btcDelegationStore(ctx, valBTCPK) - // ensure BTC delegation exists - if !store.Has(delBTCPKBytes) { - return nil, types.ErrBTCDelNotFound - } - // get and unmarshal - var btcDels types.BTCDelegatorDelegations - btcDelsBytes := store.Get(delBTCPKBytes) - k.cdc.MustUnmarshal(btcDelsBytes, &btcDels) - return &btcDels, nil -} - -// getBTCDelegations gets the BTC delegations with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { - valBTCPKBytes := valBTCPK.MustMarshal() - delBTCPKBytes := delBTCPK.MustMarshal() - - // ensure the BTC validator exists - if !k.HasBTCValidator(ctx, valBTCPKBytes) { - return nil, types.ErrBTCValNotFound - } - - return k.validatorDelegations(ctx, valBTCPK, delBTCPKBytes) -} - -// GetBTCDelegation gets the BTC delegation with a given BTC PK and staking tx hash under a given BTC validator -func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string) (*types.BTCDelegation, error) { - btcDels, err := k.getBTCDelegations(ctx, valBTCPK, delBTCPK) - if err != nil { - return nil, err - } - btcDel, err := btcDels.Get(stakingTxHash) - if err != nil { - return nil, types.ErrBTCDelNotFound.Wrapf(err.Error()) - } - return btcDel, nil + var btcDel types.BTCDelegation + k.cdc.MustUnmarshal(btcDelBytes, &btcDel) + return &btcDel } // btcDelegationStore returns the KVStore of the BTC delegations -// prefix: BTCDelegationKey || validator's Bitcoin secp256k1 PK -// key: delegation's Bitcoin secp256k1 PK -// value: BTCDelegations (a list of BTCDelegation) -func (k Keeper) btcDelegationStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { +// prefix: BTCDelegationKey +// key: BTC delegation's staking tx hash +// value: BTCDelegation +func (k Keeper) btcDelegationStore(ctx sdk.Context) prefix.Store { store := ctx.KVStore(k.storeKey) - delegationStore := prefix.NewStore(store, types.BTCDelegationKey) - return prefix.NewStore(delegationStore, valBTCPK.MustMarshal()) + return prefix.NewStore(store, types.BTCDelegationKey) } diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go new file mode 100644 index 000000000..7065b54b3 --- /dev/null +++ b/x/btcstaking/keeper/btc_delegators.go @@ -0,0 +1,236 @@ +package keeper + +import ( + "fmt" + + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AddBTCDelegation indexes the given BTC delegation in the BTC delegator store, and saves +// it under BTC delegation store +func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { + var ( + btcDelIndex = types.NewBTCDelegatorDelegationIndex() + err error + ) + if k.hasBTCDelegatorDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) { + btcDelIndex, err = k.getBTCDelegatorDelegationIndex(ctx, btcDel.ValBtcPk, btcDel.BtcPk) + if err != nil { + // this can only be a programming error + panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegatorDelegations returns true")) + } + } + // index staking tx hash of this BTC delegation + stakingTxHash, err := btcDel.GetStakingTxHash() + if err != nil { + return err + } + if err := btcDelIndex.Add(stakingTxHash); err != nil { + return types.ErrInvalidStakingTx.Wrapf(err.Error()) + } + // save the index + store := k.btcDelegatorStore(ctx, btcDel.ValBtcPk) + delBTCPKBytes := btcDel.BtcPk.MustMarshal() + btcDelIndexBytes := k.cdc.MustMarshal(btcDelIndex) + store.Set(delBTCPKBytes, btcDelIndexBytes) + + // save this BTC delegation + k.setBTCDelegation(ctx, btcDel) + + return nil +} + +// updateBTCDelegation updates an existing BTC delegation w.r.t. validator BTC PK, delegator BTC PK, +// and staking tx hash by using a given function +func (k Keeper) updateBTCDelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHashStr string, + modifyFn func(*types.BTCDelegation) error, +) error { + // get the BTC delegation + btcDel, err := k.GetBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHashStr) + if err != nil { + return err + } + + // apply modification + if err := modifyFn(btcDel); err != nil { + return err + } + + // we only need to update the actual BTC delegation object here, without touching + // the BTC delegation index + k.setBTCDelegation(ctx, btcDel) + return nil +} + +// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation +// with the given (val PK, del PK, staking tx hash) tuple +func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { + addJurySig := func(btcDel *types.BTCDelegation) error { + if btcDel.JurySig != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) + } + btcDel.JurySig = jurySig + return nil + } + + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addJurySig) +} + +func (k Keeper) AddUndelegationToBTCDelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + ud *types.BTCUndelegation, +) error { + addUndelegation := func(btcDel *types.BTCDelegation) error { + if btcDel.BtcUndelegation != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has valid undelegation object", stakingTxHash) + } + btcDel.BtcUndelegation = ud + return nil + } + + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addUndelegation) +} + +func (k Keeper) AddValidatorSigToUndelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + sig *bbn.BIP340Signature, +) error { + addValidatorSig := func(btcDel *types.BTCDelegation) error { + if btcDel.BtcUndelegation == nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) + } + + if btcDel.BtcUndelegation.ValidatorUnbondingSig != nil { + return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid validator signature", stakingTxHash) + } + + btcDel.BtcUndelegation.ValidatorUnbondingSig = sig + return nil + } + + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addValidatorSig) +} + +func (k Keeper) AddJurySigsToUndelegation( + ctx sdk.Context, + valBTCPK *bbn.BIP340PubKey, + delBTCPK *bbn.BIP340PubKey, + stakingTxHash string, + unbondingTxSig *bbn.BIP340Signature, + slashUnbondingTxSig *bbn.BIP340Signature, +) error { + addJurySigs := func(btcDel *types.BTCDelegation) error { + if btcDel.BtcUndelegation == nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) + } + + if btcDel.BtcUndelegation.JuryUnbondingSig != nil || btcDel.BtcUndelegation.JurySlashingSig != nil { + return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid jury signatures", stakingTxHash) + } + + btcDel.BtcUndelegation.JuryUnbondingSig = unbondingTxSig + btcDel.BtcUndelegation.JurySlashingSig = slashUnbondingTxSig + return nil + } + + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addJurySigs) +} + +// hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator +func (k Keeper) hasBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { + valBTCPKBytes := valBTCPK.MustMarshal() + delBTCPKBytes := delBTCPK.MustMarshal() + + if !k.HasBTCValidator(ctx, valBTCPKBytes) { + return false + } + store := k.btcDelegatorStore(ctx, valBTCPK) + return store.Has(delBTCPKBytes) +} + +// getBTCDelegatorDelegationIndex gets the BTC delegation index with a given BTC PK under a given BTC validator +func (k Keeper) getBTCDelegatorDelegationIndex(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegationIndex, error) { + valBTCPKBytes := valBTCPK.MustMarshal() + delBTCPKBytes := delBTCPK.MustMarshal() + store := k.btcDelegatorStore(ctx, valBTCPK) + + // ensure the BTC validator exists + if !k.HasBTCValidator(ctx, valBTCPKBytes) { + return nil, types.ErrBTCValNotFound + } + + // ensure BTC delegator exists + if !store.Has(delBTCPKBytes) { + return nil, types.ErrBTCDelegatorNotFound + } + // get and unmarshal + var btcDelIndex types.BTCDelegatorDelegationIndex + btcDelIndexBytes := store.Get(delBTCPKBytes) + k.cdc.MustUnmarshal(btcDelIndexBytes, &btcDelIndex) + return &btcDelIndex, nil +} + +// getBTCDelegatorDelegations gets the BTC delegations with a given BTC PK under a given BTC validator +func (k Keeper) getBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { + btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) + if err != nil { + return nil, err + } + // get BTC delegation from each staking tx hash + btcDels := []*types.BTCDelegation{} + for _, stakingTxHashBytes := range btcDelIndex.StakingTxHashList { + stakingTxHash, err := chainhash.NewHash(stakingTxHashBytes) + if err != nil { + // failing to unmarshal hash bytes in DB's BTC delegation index is a programming error + panic(err) + } + btcDel := k.getBTCDelegation(ctx, *stakingTxHash) + btcDels = append(btcDels, btcDel) + } + return &types.BTCDelegatorDelegations{Dels: btcDels}, nil +} + +// GetBTCDelegation gets the BTC delegation with a given BTC PK and staking tx hash under a given BTC validator +// TODO: only take stakingTxHash as input could be enough? +func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHashStr string) (*types.BTCDelegation, error) { + // find the BTC delegation index + btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) + if err != nil { + return nil, err + } + // decode staking tx hash string + stakingTxHash, err := chainhash.NewHashFromStr(stakingTxHashStr) + if err != nil { + return nil, err + } + // ensure the BTC delegation index has this staking tx hash + if !btcDelIndex.Has(*stakingTxHash) { + return nil, types.ErrBTCDelegatorNotFound.Wrapf(err.Error()) + } + + return k.getBTCDelegation(ctx, *stakingTxHash), nil +} + +// btcDelegatorStore returns the KVStore of the BTC delegators +// prefix: BTCDelegatorKey || validator's Bitcoin secp256k1 PK +// key: delegator's Bitcoin secp256k1 PK +// value: BTCDelegatorDelegationIndex (a list of BTCDelegations' staking tx hashes) +func (k Keeper) btcDelegatorStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { + store := ctx.KVStore(k.storeKey) + delegationStore := prefix.NewStore(store, types.BTCDelegatorKey) + return prefix.NewStore(delegationStore, valBTCPK.MustMarshal()) +} diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 8010fbfd9..d19324e23 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -17,46 +17,6 @@ import ( var _ types.QueryServer = Keeper{} -func (k Keeper) getDelegationsMatchingCriteria( - sdkCtx sdk.Context, - match func(*types.BTCDelegation) bool, -) []*types.BTCDelegation { - btcDels := []*types.BTCDelegation{} - - // iterate over each BTC validator - valStore := k.btcValidatorStore(sdkCtx) - valIter := valStore.Iterator(nil, nil) - defer valIter.Close() - - for ; valIter.Valid(); valIter.Next() { - valBTCPKBytes := valIter.Key() - valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) - if err != nil { - // this can only be programming error - panic("failed to unmarshal validator BTC PK in KVstore") - } - delStore := k.btcDelegationStore(sdkCtx, valBTCPK) - delIter := delStore.Iterator(nil, nil) - - // iterate over each BTC delegation under this BTC validator - for ; delIter.Valid(); delIter.Next() { - var curBTCDels types.BTCDelegatorDelegations - btcDelsBytes := delIter.Value() - k.cdc.MustUnmarshal(btcDelsBytes, &curBTCDels) - for i, btcDel := range curBTCDels.Dels { - del := btcDel - if match(del) { - btcDels = append(btcDels, curBTCDels.Dels[i]) - } - } - } - - delIter.Close() - } - - return btcDels -} - // BTCValidators returns a paginated list of all Babylon maintained validators func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidatorsRequest) (*types.QueryBTCValidatorsResponse, error) { if req == nil { @@ -107,9 +67,8 @@ func (k Keeper) BTCValidator(ctx context.Context, req *types.QueryBTCValidatorRe return &types.QueryBTCValidatorResponse{BtcValidator: val}, nil } -// PendingBTCDelegations returns all pending BTC delegations -// TODO: find a good way to support pagination of this query -func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendingBTCDelegationsRequest) (*types.QueryPendingBTCDelegationsResponse, error) { +// BTCDelegations returns all BTC delegations under a given status +func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegationsRequest) (*types.QueryBTCDelegationsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } @@ -124,48 +83,30 @@ func (k Keeper) PendingBTCDelegations(ctx context.Context, req *types.QueryPendi // get value of w wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout - btcDels := k.getDelegationsMatchingCriteria( - sdkCtx, - func(del *types.BTCDelegation) bool { - return del.GetStatus(btcTipHeight, wValue) == types.BTCDelegationStatus_PENDING - }, - ) - - return &types.QueryPendingBTCDelegationsResponse{BtcDelegations: btcDels}, nil -} + store := k.btcDelegationStore(sdkCtx) + var btcDels []*types.BTCDelegation + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) { + var btcDel types.BTCDelegation + k.cdc.MustUnmarshal(value, &btcDel) -// UnbondingBTCDelegations returns all unbonding BTC delegations which require jury signature -// TODO: find a good way to support pagination of this query -func (k Keeper) UnbondingBTCDelegations(ctx context.Context, req *types.QueryUnbondingBTCDelegationsRequest) (*types.QueryUnbondingBTCDelegationsResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - - btcDels := k.getDelegationsMatchingCriteria( - sdkCtx, - func(del *types.BTCDelegation) bool { - // grab all delegations which are unbonding and already have validator signature, but did not receive - // jury signature yet - if del.BtcUndelegation == nil { - return false - } - - if del.BtcUndelegation.ValidatorUnbondingSig == nil { - return false - } - - // undelegation already received jury signature, no need to retrieve it - if del.BtcUndelegation.JuryUnbondingSig != nil { - return false + // hit if the queried status is ANY or matches the BTC delegation status + if req.Status == types.BTCDelegationStatus_ANY || btcDel.GetStatus(btcTipHeight, wValue) == req.Status { + if accumulate { + btcDels = append(btcDels, &btcDel) } + return true, nil + } - return true - }, - ) + return false, nil + }) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } - return &types.QueryUnbondingBTCDelegationsResponse{BtcDelegations: btcDels}, nil + return &types.QueryBTCDelegationsResponse{ + BtcDelegations: btcDels, + Pagination: pageRes, + }, nil } // BTCValidatorPowerAtHeight returns the voting power of the specified validator @@ -285,13 +226,21 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC } sdkCtx := sdk.UnwrapSDKContext(ctx) - btcDelStore := k.btcDelegationStore(sdkCtx, valPK) + btcDelStore := k.btcDelegatorStore(sdkCtx, valPK) btcDels := []*types.BTCDelegatorDelegations{} pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { - var curBTCDels types.BTCDelegatorDelegations - k.cdc.MustUnmarshal(value, &curBTCDels) - btcDels = append(btcDels, &curBTCDels) + delBTCPK, err := bbn.NewBIP340PubKey(key) + if err != nil { + return err + } + + curBTCDels, err := k.getBTCDelegatorDelegations(sdkCtx, valPK, delBTCPK) + if err != nil { + return err + } + + btcDels = append(btcDels, curBTCDels) return nil }) if err != nil { @@ -301,53 +250,6 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC return &types.QueryBTCValidatorDelegationsResponse{BtcDelegatorDelegations: btcDels, Pagination: pageRes}, nil } -func (k Keeper) delegationView( - ctx sdk.Context, - validatorBtcPubKey *bbn.BIP340PubKey, - stakingTxHash *chainhash.Hash) *types.QueryBTCDelegationResponse { - - btcDelIter := k.btcDelegationStore(ctx, validatorBtcPubKey).Iterator(nil, nil) - defer btcDelIter.Close() - for ; btcDelIter.Valid(); btcDelIter.Next() { - var btcDels types.BTCDelegatorDelegations - k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) - delegation, err := btcDels.Get(stakingTxHash.String()) - if err != nil { - continue - } - currentTip := k.btclcKeeper.GetTipInfo(ctx) - currentWValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - - isActive := delegation.GetStatus( - currentTip.Height, - currentWValue, - ) == types.BTCDelegationStatus_ACTIVE - - var undelegationInfo *types.BTCUndelegationInfo = nil - - if delegation.BtcUndelegation != nil { - undelegationInfo = &types.BTCUndelegationInfo{ - UnbondingTx: delegation.BtcUndelegation.UnbondingTx, - ValidatorUnbondingSig: delegation.BtcUndelegation.ValidatorUnbondingSig, - JuryUnbondingSig: delegation.BtcUndelegation.JuryUnbondingSig, - } - } - - return &types.QueryBTCDelegationResponse{ - BtcPk: delegation.BtcPk, - ValBtcPk: delegation.ValBtcPk, - StartHeight: delegation.StartHeight, - EndHeight: delegation.EndHeight, - TotalSat: delegation.TotalSat, - StakingTx: hex.EncodeToString(delegation.StakingTx.Tx), - StakingScript: hex.EncodeToString(delegation.StakingTx.Script), - Active: isActive, - UndelegationInfo: undelegationInfo, - } - } - return nil -} - // BTCDelegation returns existing btc delegation by staking tx hash func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegationRequest) (*types.QueryBTCDelegationResponse, error) { if req == nil { @@ -355,31 +257,46 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation } sdkCtx := sdk.UnwrapSDKContext(ctx) - stakingTxHash, err := chainhash.NewHashFromStr(req.StakingTxHashHex) + // decode staking tx hash + stakingTxHash, err := chainhash.NewHashFromStr(req.StakingTxHashHex) if err != nil { return nil, err } - btcValIter := k.btcValidatorStore(sdkCtx).Iterator(nil, nil) - defer btcValIter.Close() - for ; btcValIter.Valid(); btcValIter.Next() { - valBTCPKBytes := btcValIter.Key() - valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) - - if err != nil { - // failed to unmarshal BTC validator PK in KVStore is a programming error - panic(err) - } - - response := k.delegationView(sdkCtx, valBTCPK, stakingTxHash) + // find BTC delegation + btcDel := k.getBTCDelegation(sdkCtx, *stakingTxHash) + if btcDel == nil { + return nil, types.ErrBTCDelegationNotFound + } - if response == nil { - continue + // check whether it's active + currentTip := k.btclcKeeper.GetTipInfo(sdkCtx) + currentWValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + isActive := btcDel.GetStatus( + currentTip.Height, + currentWValue, + ) == types.BTCDelegationStatus_ACTIVE + + // get its undelegation info + var undelegationInfo *types.BTCUndelegationInfo + if btcDel.BtcUndelegation != nil { + undelegationInfo = &types.BTCUndelegationInfo{ + UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, + ValidatorUnbondingSig: btcDel.BtcUndelegation.ValidatorUnbondingSig, + JuryUnbondingSig: btcDel.BtcUndelegation.JuryUnbondingSig, } - - return response, nil } - return nil, types.ErrBTCDelNotFound + return &types.QueryBTCDelegationResponse{ + BtcPk: btcDel.BtcPk, + ValBtcPk: btcDel.ValBtcPk, + StartHeight: btcDel.StartHeight, + EndHeight: btcDel.EndHeight, + TotalSat: btcDel.TotalSat, + StakingTx: hex.EncodeToString(btcDel.StakingTx.Tx), + StakingScript: hex.EncodeToString(btcDel.StakingTx.Script), + Active: isActive, + UndelegationInfo: undelegationInfo, + }, nil } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index b2c63255e..26e5843df 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -158,7 +158,7 @@ func FuzzBTCValidator(f *testing.F) { }) } -func FuzzBTCDelegations(f *testing.F) { +func FuzzPendingBTCDelegations(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -203,10 +203,10 @@ func FuzzBTCDelegations(f *testing.F) { btcDel.JurySig = nil pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) - txHash := btcDel.StakingTx.MustGetTxHash() + txHash := btcDel.StakingTx.MustGetTxHashStr() btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) delView, err := keeper.BTCDelegation(ctx, &types.QueryBTCDelegationRequest{ StakingTxHashHex: txHash, @@ -221,14 +221,27 @@ func FuzzBTCDelegations(f *testing.F) { btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) keeper.IndexBTCHeight(ctx) - // assert all pending BTC delegations - resp, err := keeper.PendingBTCDelegations(ctx, &types.QueryPendingBTCDelegationsRequest{}) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, len(pendingBtcDelsMap), len(resp.BtcDelegations)) - for _, btcDel := range resp.BtcDelegations { - _, ok := pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] - require.True(t, ok) + // querying paginated BTC delegations and assert + // Generate a page request with a limit and a nil key + if len(pendingBtcDelsMap) == 0 { + return + } + limit := datagen.RandomInt(r, len(pendingBtcDelsMap)) + 1 + pagination := constructRequestWithLimit(r, limit) + req := &types.QueryBTCDelegationsRequest{ + Status: types.BTCDelegationStatus_PENDING, + Pagination: pagination, + } + for i := uint64(0); i < numBTCDels; i += limit { + resp, err := keeper.BTCDelegations(ctx, req) + require.NoError(t, err) + require.NotNil(t, resp) + for _, btcDel := range resp.BtcDelegations { + _, ok := pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] + require.True(t, ok) + } + // Construct the next page request + pagination.Key = resp.Pagination.NextKey } }) } @@ -243,6 +256,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { // Setup keeper and context btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // jury and slashing addr @@ -264,7 +278,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { // Generate a random number of BTC delegations under each validator startHeight := datagen.RandomInt(r, 100) + 1 endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 - numBTCDels := datagen.RandomInt(r, 100) + 1 + numBTCDels := datagen.RandomInt(r, 10) + 1 unbondingBtcDelsMap := make(map[string]*types.BTCDelegation) for _, btcVal := range btcVals { for j := uint64(0); j < numBTCDels; j++ { @@ -281,26 +295,46 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { } if datagen.RandomInt(r, 2) == 1 { - // some undelegations already have jury sig so they should not be returned + // these BTC delegations are unbonded btcDel.BtcUndelegation.JuryUnbondingSig = btcDel.JurySig + btcDel.BtcUndelegation.JurySlashingSig = btcDel.JurySig } else { + // these BTC delegations are unbonding unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } } - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) } } - // assert all pending BTC delegations - resp, err := keeper.UnbondingBTCDelegations(ctx, &types.QueryUnbondingBTCDelegationsRequest{}) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, len(unbondingBtcDelsMap), len(resp.BtcDelegations)) - for _, btcDel := range resp.BtcDelegations { - _, ok := unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] - require.True(t, ok) + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) + keeper.IndexBTCHeight(ctx) + + // querying paginated BTC delegations and assert + // Generate a page request with a limit and a nil key + if len(unbondingBtcDelsMap) == 0 { + return + } + limit := datagen.RandomInt(r, len(unbondingBtcDelsMap)) + 1 + pagination := constructRequestWithLimit(r, limit) + req := &types.QueryBTCDelegationsRequest{ + Status: types.BTCDelegationStatus_UNBONDING, + Pagination: pagination, + } + for i := uint64(0); i < numBTCDels; i += limit { + resp, err := keeper.BTCDelegations(ctx, req) + require.NoError(t, err) + require.NotNil(t, resp) + for _, btcDel := range resp.BtcDelegations { + _, ok := unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] + require.True(t, ok) + } + // Construct the next page request + pagination.Key = resp.Pagination.NextKey } }) } @@ -417,7 +451,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, 10000) // timelock period: 1-1000 require.NoError(t, err) - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) totalVotingPower += btcDel.TotalSat } @@ -506,7 +540,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) } diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go index aef073baf..7864e7d44 100644 --- a/x/btcstaking/keeper/incentive.go +++ b/x/btcstaking/keeper/incentive.go @@ -3,6 +3,7 @@ package keeper import ( bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -42,13 +43,18 @@ func (k Keeper) RecordRewardDistCache(ctx sdk.Context) { // iterate over all BTC delegations under this validator to compute // the BTC validator's distribution info btcValDistInfo := types.NewBTCValDistInfo(btcVal) - btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) + btcDelIter := k.btcDelegatorStore(ctx, valBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { // unmarshal - var btcDels types.BTCDelegatorDelegations - k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) - // process each of the BTC delegation - for _, btcDel := range btcDels.Dels { + var btcDelIndex types.BTCDelegatorDelegationIndex + k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDelIndex) + // retrieve and process each of the BTC delegation + for _, stakingTxHashBytes := range btcDelIndex.StakingTxHashList { + stakingTxHash, err := chainhash.NewHash(stakingTxHashBytes) + if err != nil { + panic(err) // only programming error is possible + } + btcDel := k.getBTCDelegation(ctx, *stakingTxHash) btcValDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue) } } diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 772ab691f..914bb1886 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -58,7 +58,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 require.NoError(t, err) - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) } } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index d13c19a5c..8ec661805 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -147,19 +147,19 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre if err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - stakingTxHash := stakingMsgTx.TxHash().String() + stakingTxHash := stakingMsgTx.TxHash() // ensure the validator exists if !ms.HasBTCValidator(ctx, *valBTCPK) { return nil, types.ErrBTCValNotFound } - delegations, err := ms.validatorDelegations(ctx, valBTCPK, delBTCPK.MustMarshal()) - + // ensure the staking tx is not duplicated + btcDelIndex, err := ms.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) if err == nil { // err is nil, meaning there exists a BTC delegation for this validator and delegator // ensure the staking tx is not duplicated - if delegations.Has(stakingTxHash) { + if btcDelIndex.Has(stakingTxHash) { return nil, types.ErrReusedStakingTx } } @@ -244,7 +244,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury BtcUndelegation: nil, } - if err := ms.SetBTCDelegation(ctx, newBTCDel); err != nil { + if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { panic("failed to set BTC delegation that has passed verification") } @@ -546,7 +546,7 @@ func (ms msgServer) AddJuryUnbondingSigs( BtcPk: btcDel.BtcPk, ValBtcPk: btcDel.ValBtcPk, StakingTxHash: req.StakingTxHash, - UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHash(), + UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), FromState: types.BTCDelegationStatus_UNBONDING, } if err := ctx.EventManager().EmitTypedEvent(event); err != nil { @@ -620,7 +620,7 @@ func (ms msgServer) AddValidatorUnbondingSig( BtcPk: btcDel.BtcPk, ValBtcPk: btcDel.ValBtcPk, StakingTxHash: req.StakingTxHash, - UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHash(), + UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), FromState: types.BTCDelegationStatus_UNBONDING, } if err := ctx.EventManager().EmitTypedEvent(event); err != nil { diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 5c0b01c83..66b690b4e 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -160,7 +160,7 @@ func createDelegation( // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() require.NoError(t, err) - stakingTxHash := stakingTx.MustGetTxHash() + stakingTxHash := stakingTx.MustGetTxHashStr() // random signer signer := datagen.GenRandomAccount().Address @@ -220,7 +220,7 @@ func createJurySig( ) { stakingMsgTx, err := msgCreateBTCDel.StakingTx.ToMsgTx() require.NoError(t, err) - stakingTxHash := msgCreateBTCDel.StakingTx.MustGetTxHash() + stakingTxHash := msgCreateBTCDel.StakingTx.MustGetTxHashStr() jurySig, err := msgCreateBTCDel.SlashingTx.Sign( stakingMsgTx, msgCreateBTCDel.StakingTx.Script, diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index f95c25308..f0ff56d3e 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -46,10 +46,16 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // iterate all BTC delegations under this validator // to calculate this validator's total voting power - btcDelIter := k.btcDelegationStore(ctx, valBTCPK).Iterator(nil, nil) + btcDelIter := k.btcDelegatorStore(ctx, valBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { - var btcDels types.BTCDelegatorDelegations - k.cdc.MustUnmarshal(btcDelIter.Value(), &btcDels) + delBTCPK, err := bbn.NewBIP340PubKey(btcDelIter.Key()) + if err != nil { + panic(err) // only programming error is possible + } + btcDels, err := k.getBTCDelegatorDelegations(ctx, valBTCPK, delBTCPK) + if err != nil { + panic(err) // only programming error is possible + } valPower += btcDels.VotingPower(btcTipHeight, wValue) } btcDelIter.Close() diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index b75b222c7..09ba168ca 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -55,7 +55,7 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 require.NoError(t, err) - err = keeper.SetBTCDelegation(ctx, btcDel) + err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) } } diff --git a/x/btcstaking/types/btc_delegations.go b/x/btcstaking/types/btc_delegations.go new file mode 100644 index 000000000..3f5e20136 --- /dev/null +++ b/x/btcstaking/types/btc_delegations.go @@ -0,0 +1,45 @@ +package types + +import ( + "bytes" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +func NewBTCDelegatorDelegationIndex() *BTCDelegatorDelegationIndex { + return &BTCDelegatorDelegationIndex{ + StakingTxHashList: [][]byte{}, + } +} + +func (i *BTCDelegatorDelegationIndex) Has(stakingTxHash chainhash.Hash) bool { + for _, hash := range i.StakingTxHashList { + if bytes.Equal(stakingTxHash[:], hash) { + return true + } + } + return false +} + +func (i *BTCDelegatorDelegationIndex) Add(stakingTxHash chainhash.Hash) error { + // ensure staking tx hash is not duplicated + for _, hash := range i.StakingTxHashList { + if bytes.Equal(stakingTxHash[:], hash) { + return fmt.Errorf("the given stakingTxHash %s is duplicated", stakingTxHash.String()) + } + } + // add + i.StakingTxHashList = append(i.StakingTxHashList, stakingTxHash[:]) + + return nil +} + +// VotingPower calculates the total voting power of all BTC delegations +func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { + power := uint64(0) + for _, del := range dels.Dels { + power += del.VotingPower(btcHeight, w) + } + return power +} diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 1bc1c5f06..7bfe774b6 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -10,9 +10,27 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) +func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, error) { + switch statusStr { + case "pending": + return BTCDelegationStatus_PENDING, nil + case "active": + return BTCDelegationStatus_ACTIVE, nil + case "unbonding": + return BTCDelegationStatus_UNBONDING, nil + case "unbonded": + return BTCDelegationStatus_UNBONDED, nil + case "any": + return BTCDelegationStatus_ANY, nil + default: + return -1, fmt.Errorf("invalid status string; should be one of {pending, active, unbonding, unbonded, any}") + } +} + func (v *BTCValidator) IsSlashed() bool { return v.SlashedBabylonHeight > 0 } @@ -132,156 +150,22 @@ func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { return d.GetTotalSat() } -// GetStakingTxHash returns the staking tx hash of the BTC delegation -// it can be used for uniquely identifying a BTC delegation -func (d *BTCDelegation) GetStakingTxHash() (string, error) { +func (d *BTCDelegation) GetStakingTxHash() (chainhash.Hash, error) { return d.StakingTx.GetTxHash() } -func (d *BTCDelegation) MustGetStakingTxHash() string { +func (d *BTCDelegation) MustGetStakingTxHash() chainhash.Hash { return d.StakingTx.MustGetTxHash() } -func NewBTCDelegatorDelegations() *BTCDelegatorDelegations { - return &BTCDelegatorDelegations{ - Dels: []*BTCDelegation{}, - } -} - -// Add appends a given BTC delegation to the BTC delegations -// It requires the given BTC delegation is not in the list yet -// TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegatorDelegations) Add(del *BTCDelegation) error { - stakingTxHash, err := del.GetStakingTxHash() - if err != nil { - return fmt.Errorf("failed to add BTC delegation to BTC delegations: %w", err) - } - // ensure the given del is not duplicated - if dels.Has(stakingTxHash) { - return fmt.Errorf("the given BTC delegation %s is duplicated", stakingTxHash) - } - // append - dels.Dels = append(dels.Dels, del) - return nil -} - -func (dels *BTCDelegatorDelegations) getAndModifyDelegation(stakingTxHash string, modifyFn func(del *BTCDelegation) error) error { - del, err := dels.Get(stakingTxHash) - if err != nil { - return fmt.Errorf("cannot find the BTC delegation with staking tx hash %s: %w", stakingTxHash, err) - } - - if err := modifyFn(del); err != nil { - return err - } - return nil -} - -// AddJurySig adds a jury signature to an existing BTC delegation in the BTC delegations -// TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegatorDelegations) AddJurySig(stakingTxHash string, sig *bbn.BIP340Signature) error { - addJurySig := func(del *BTCDelegation) error { - if del.JurySig != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) - } - del.JurySig = sig - return nil - } - - return dels.getAndModifyDelegation(stakingTxHash, addJurySig) -} - -func (dels *BTCDelegatorDelegations) AddUndelegation(stakingTxHash string, ud *BTCUndelegation) error { - addUndelegation := func(del *BTCDelegation) error { - if del.BtcUndelegation != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has valid undelegation object", stakingTxHash) - } - del.BtcUndelegation = ud - return nil - } - - return dels.getAndModifyDelegation(stakingTxHash, addUndelegation) -} - -func (dels *BTCDelegatorDelegations) AddValidatorSigToUndelegation(stakingTxHash string, sig *bbn.BIP340Signature) error { - addValidatorSig := func(del *BTCDelegation) error { - if del.BtcUndelegation == nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) - } - - if del.BtcUndelegation.ValidatorUnbondingSig != nil { - return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid validator signature", stakingTxHash) - } - - del.BtcUndelegation.ValidatorUnbondingSig = sig - return nil - } - - return dels.getAndModifyDelegation(stakingTxHash, addValidatorSig) -} - -func (dels *BTCDelegatorDelegations) AddJurySigsToUndelegation( - stakingTxHash string, - unbondingTxSig *bbn.BIP340Signature, - slashUnbondingTxSig *bbn.BIP340Signature, -) error { - addJurySigs := func(del *BTCDelegation) error { - if del.BtcUndelegation == nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) - } - - if del.BtcUndelegation.JuryUnbondingSig != nil || del.BtcUndelegation.JurySlashingSig != nil { - return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid jury signatures", stakingTxHash) - } - - del.BtcUndelegation.JuryUnbondingSig = unbondingTxSig - del.BtcUndelegation.JurySlashingSig = slashUnbondingTxSig - return nil - } - - return dels.getAndModifyDelegation(stakingTxHash, addJurySigs) -} - -// TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegatorDelegations) Has(stakingTxHash string) bool { - for _, d := range dels.Dels { - dStakingTxHash := d.MustGetStakingTxHash() - if dStakingTxHash == stakingTxHash { - return true - } - } - return false -} - -// TODO: this is an O(n) operation. Consider optimisation later -func (dels *BTCDelegatorDelegations) Get(stakingTxHash string) (*BTCDelegation, error) { - for _, d := range dels.Dels { - dStakingTxHash := d.MustGetStakingTxHash() - if dStakingTxHash == stakingTxHash { - return d, nil - } - } - return nil, fmt.Errorf("cannot find the BTC delegation with staking tx hash %s", stakingTxHash) -} - -// VotingPower calculates the total voting power of all BTC delegations -func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { - power := uint64(0) - for _, del := range dels.Dels { - power += del.VotingPower(btcHeight, w) - } - return power +// GetStakingTxHashStr returns the staking tx hash of the BTC delegation in hex string +// it can be used for uniquely identifying a BTC delegation +func (d *BTCDelegation) GetStakingTxHashStr() (string, error) { + return d.StakingTx.GetTxHashStr() } -func (p *ProofOfPossession) ValidateBasic() error { - if len(p.BabylonSig) == 0 { - return fmt.Errorf("empty Babylon signature") - } - if p.BtcSig == nil { - return fmt.Errorf("empty BTC signature") - } - - return nil +func (d *BTCDelegation) MustGetStakingTxHashStr() string { + return d.StakingTx.MustGetTxHashStr() } func NewBabylonTaprootTxFromHex(txHex string) (*BabylonBTCTaprootTx, error) { @@ -333,20 +217,36 @@ func (tx *BabylonBTCTaprootTx) ToMsgTx() (*wire.MsgTx, error) { return &msgTx, nil } -func (tx *BabylonBTCTaprootTx) GetTxHash() (string, error) { +func (tx *BabylonBTCTaprootTx) GetTxHash() (chainhash.Hash, error) { msgTx, err := tx.ToMsgTx() + if err != nil { + return chainhash.Hash{}, err + } + return msgTx.TxHash(), nil +} + +func (tx *BabylonBTCTaprootTx) MustGetTxHash() chainhash.Hash { + txHash, err := tx.GetTxHash() + if err != nil { + panic(err) + } + return txHash +} + +func (tx *BabylonBTCTaprootTx) GetTxHashStr() (string, error) { + txHash, err := tx.GetTxHash() if err != nil { return "", err } - return msgTx.TxHash().String(), nil + return txHash.String(), nil } -func (tx *BabylonBTCTaprootTx) MustGetTxHash() string { - msgTx, err := tx.ToMsgTx() +func (tx *BabylonBTCTaprootTx) MustGetTxHashStr() string { + txHashStr, err := tx.GetTxHashStr() if err != nil { panic(err) } - return msgTx.TxHash().String() + return txHashStr } func (tx *BabylonBTCTaprootTx) GetScriptData() (*btcstaking.StakingScriptData, error) { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 70cb7f759..75331b101 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -50,6 +50,8 @@ const ( // - or receiving unbonding tx and then receiving signatures from validator and jury for this // unbonding tx. BTCDelegationStatus_UNBONDED BTCDelegationStatus = 3 + // ANY is any of the above status + BTCDelegationStatus_ANY BTCDelegationStatus = 4 ) var BTCDelegationStatus_name = map[int32]string{ @@ -57,6 +59,7 @@ var BTCDelegationStatus_name = map[int32]string{ 1: "ACTIVE", 2: "UNBONDING", 3: "UNBONDED", + 4: "ANY", } var BTCDelegationStatus_value = map[string]int32{ @@ -64,6 +67,7 @@ var BTCDelegationStatus_value = map[string]int32{ "ACTIVE": 1, "UNBONDING": 2, "UNBONDED": 3, + "ANY": 4, } func (x BTCDelegationStatus) String() string { @@ -496,7 +500,7 @@ func (m *BTCUndelegationInfo) GetUnbondingTx() *BabylonBTCTaprootTx { return nil } -// BTCDelegatorDelegations is a collection of BTC delegations, typically from the same delegator. +// BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` } @@ -541,6 +545,51 @@ func (m *BTCDelegatorDelegations) GetDels() []*BTCDelegation { return nil } +// BTCDelegatorDelegationIndex is a list of staking tx hashes of BTC delegations from the same delegator. +type BTCDelegatorDelegationIndex struct { + StakingTxHashList [][]byte `protobuf:"bytes,1,rep,name=staking_tx_hash_list,json=stakingTxHashList,proto3" json:"staking_tx_hash_list,omitempty"` +} + +func (m *BTCDelegatorDelegationIndex) Reset() { *m = BTCDelegatorDelegationIndex{} } +func (m *BTCDelegatorDelegationIndex) String() string { return proto.CompactTextString(m) } +func (*BTCDelegatorDelegationIndex) ProtoMessage() {} +func (*BTCDelegatorDelegationIndex) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{6} +} +func (m *BTCDelegatorDelegationIndex) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCDelegatorDelegationIndex) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCDelegatorDelegationIndex.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCDelegatorDelegationIndex) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCDelegatorDelegationIndex.Merge(m, src) +} +func (m *BTCDelegatorDelegationIndex) XXX_Size() int { + return m.Size() +} +func (m *BTCDelegatorDelegationIndex) XXX_DiscardUnknown() { + xxx_messageInfo_BTCDelegatorDelegationIndex.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCDelegatorDelegationIndex proto.InternalMessageInfo + +func (m *BTCDelegatorDelegationIndex) GetStakingTxHashList() [][]byte { + if m != nil { + return m.StakingTxHashList + } + return nil +} + // BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and // transaction which commits to the provided script. type BabylonBTCTaprootTx struct { @@ -554,7 +603,7 @@ func (m *BabylonBTCTaprootTx) Reset() { *m = BabylonBTCTaprootTx{} } func (m *BabylonBTCTaprootTx) String() string { return proto.CompactTextString(m) } func (*BabylonBTCTaprootTx) ProtoMessage() {} func (*BabylonBTCTaprootTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{6} + return fileDescriptor_3851ae95ccfaf7db, []int{7} } func (m *BabylonBTCTaprootTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -605,6 +654,7 @@ func init() { proto.RegisterType((*BTCUndelegation)(nil), "babylon.btcstaking.v1.BTCUndelegation") proto.RegisterType((*BTCUndelegationInfo)(nil), "babylon.btcstaking.v1.BTCUndelegationInfo") proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") + proto.RegisterType((*BTCDelegatorDelegationIndex)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegationIndex") proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") } @@ -613,69 +663,72 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 984 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x5f, 0x6f, 0x1b, 0x45, - 0x10, 0xcf, 0x9d, 0x1d, 0x27, 0x1e, 0x3b, 0xc4, 0xdd, 0xa6, 0xa9, 0x09, 0xc2, 0x4e, 0x4d, 0x15, - 0x59, 0x15, 0x3d, 0x93, 0xf4, 0x8f, 0x0a, 0x12, 0x48, 0x5c, 0x1c, 0x81, 0x55, 0x92, 0x9a, 0xf3, - 0xa5, 0x20, 0x1e, 0xb0, 0xee, 0xcf, 0xe6, 0x7c, 0xd8, 0xb9, 0x3d, 0x6e, 0xd7, 0xc6, 0x7e, 0xe7, - 0x03, 0xf0, 0xc6, 0x17, 0xe1, 0x03, 0xf0, 0xd8, 0xc7, 0x8a, 0x27, 0x94, 0x07, 0x0b, 0x25, 0x5f, - 0x04, 0xdd, 0xde, 0x9e, 0x7d, 0x49, 0xe3, 0x42, 0xb0, 0x79, 0xca, 0xcd, 0xce, 0xcc, 0x6f, 0x66, - 0x7e, 0xbf, 0x89, 0x77, 0x61, 0xc7, 0x34, 0xcc, 0x51, 0x8f, 0x78, 0x35, 0x93, 0x59, 0x94, 0x19, - 0x5d, 0xd7, 0x73, 0x6a, 0x83, 0xdd, 0x84, 0xa5, 0xf8, 0x01, 0x61, 0x04, 0xdd, 0x11, 0x71, 0x4a, - 0xc2, 0x33, 0xd8, 0xdd, 0xda, 0x70, 0x88, 0x43, 0x78, 0x44, 0x2d, 0xfc, 0x8a, 0x82, 0xb7, 0xde, - 0xb5, 0x08, 0x3d, 0x25, 0xb4, 0x1d, 0x39, 0x22, 0x43, 0xb8, 0x2a, 0x91, 0x55, 0xb3, 0x82, 0x91, - 0xcf, 0x48, 0x8d, 0x62, 0xcb, 0xdf, 0x7b, 0xf2, 0xb4, 0xbb, 0x5b, 0xeb, 0xe2, 0x51, 0x1c, 0x73, - 0x5f, 0xc4, 0x4c, 0xfb, 0x31, 0x31, 0x33, 0x76, 0x6b, 0x97, 0x3a, 0xda, 0x2a, 0x5f, 0xdf, 0xb9, - 0x4f, 0xfc, 0x28, 0xa0, 0x32, 0x4e, 0x41, 0x5e, 0xd5, 0xf7, 0x5f, 0x1a, 0x3d, 0xd7, 0x36, 0x18, - 0x09, 0xd0, 0x01, 0xe4, 0x6c, 0x4c, 0xad, 0xc0, 0xf5, 0x99, 0x4b, 0xbc, 0xa2, 0xb4, 0x2d, 0x55, - 0x73, 0x7b, 0x1f, 0x28, 0xa2, 0xbf, 0xe9, 0x54, 0xbc, 0x9a, 0x52, 0x9f, 0x86, 0x6a, 0xc9, 0x3c, - 0xf4, 0x2d, 0x80, 0x45, 0x4e, 0x4f, 0x5d, 0x4a, 0x43, 0x14, 0x79, 0x5b, 0xaa, 0x66, 0xd5, 0x67, - 0x67, 0xe3, 0xf2, 0x8e, 0xe3, 0xb2, 0x4e, 0xdf, 0x54, 0x2c, 0x72, 0x5a, 0x8b, 0xa7, 0xe4, 0x7f, - 0x1e, 0x52, 0xbb, 0x5b, 0x63, 0x23, 0x1f, 0x53, 0xa5, 0x8e, 0xad, 0x3f, 0x7e, 0x7b, 0x08, 0xa2, - 0x64, 0x1d, 0x5b, 0x5a, 0x02, 0x0b, 0x7d, 0x06, 0x20, 0x86, 0x6a, 0xfb, 0xdd, 0x62, 0x8a, 0xf7, - 0x57, 0x8e, 0xfb, 0x8b, 0x18, 0x53, 0x26, 0x8c, 0x29, 0xcd, 0xbe, 0xf9, 0x1c, 0x8f, 0xb4, 0xac, - 0x48, 0x69, 0x76, 0xd1, 0x21, 0x64, 0x4c, 0x66, 0x85, 0xb9, 0xe9, 0x6d, 0xa9, 0x9a, 0x57, 0x9f, - 0x9e, 0x8d, 0xcb, 0x7b, 0x89, 0xae, 0x44, 0xa4, 0xd5, 0x31, 0x5c, 0x2f, 0x36, 0x44, 0x63, 0x6a, - 0xa3, 0xf9, 0xe8, 0xf1, 0x47, 0x02, 0x72, 0xd9, 0x64, 0x56, 0xb3, 0x8b, 0x3e, 0x81, 0x94, 0x4f, - 0xfc, 0xe2, 0x32, 0xef, 0xa3, 0xaa, 0x5c, 0xbb, 0x01, 0x4a, 0x33, 0x20, 0xe4, 0xe4, 0xc5, 0x49, - 0x93, 0x50, 0x8a, 0xf9, 0x14, 0x5a, 0x98, 0x84, 0x1e, 0xc3, 0x26, 0xed, 0x19, 0xb4, 0x83, 0xed, - 0x76, 0x3c, 0x52, 0x07, 0xbb, 0x4e, 0x87, 0x15, 0x33, 0xdb, 0x52, 0x35, 0xad, 0x6d, 0x08, 0xaf, - 0x1a, 0x39, 0xbf, 0xe4, 0x3e, 0xf4, 0x21, 0xa0, 0x49, 0x16, 0xb3, 0xe2, 0x8c, 0x15, 0x9e, 0x51, - 0x88, 0x33, 0x98, 0x15, 0x45, 0x57, 0x7e, 0x96, 0x61, 0x23, 0x29, 0xf0, 0x37, 0x2e, 0xeb, 0x1c, - 0x62, 0x66, 0x24, 0x78, 0x90, 0x16, 0xc1, 0xc3, 0x26, 0x64, 0x44, 0x27, 0x32, 0xef, 0x44, 0x58, - 0xe8, 0x1e, 0xe4, 0x07, 0x84, 0xb9, 0x9e, 0xd3, 0xf6, 0xc9, 0x4f, 0x38, 0xe0, 0x82, 0xa5, 0xb5, - 0x5c, 0x74, 0xd6, 0x0c, 0x8f, 0xde, 0x42, 0x43, 0xfa, 0xc6, 0x34, 0x2c, 0xcf, 0xa0, 0xe1, 0xd7, - 0x0c, 0xac, 0xa9, 0xfa, 0x7e, 0x1d, 0xf7, 0xb0, 0x63, 0xb0, 0x37, 0xf7, 0x48, 0x9a, 0x63, 0x8f, - 0xe4, 0x05, 0xee, 0x51, 0xea, 0xbf, 0xec, 0x91, 0x0e, 0x30, 0x30, 0x7a, 0xed, 0x85, 0xac, 0xf5, - 0xea, 0xc0, 0xe8, 0xa9, 0xbc, 0xa3, 0x7b, 0x90, 0xa7, 0xcc, 0x08, 0xd8, 0x65, 0x6a, 0x73, 0xfc, - 0x4c, 0x68, 0xf0, 0x3e, 0x00, 0xf6, 0xec, 0xcb, 0x4b, 0x9b, 0xc5, 0x9e, 0x2d, 0xdc, 0xef, 0x41, - 0x96, 0x11, 0x66, 0xf4, 0xda, 0xd4, 0x88, 0x17, 0x74, 0x95, 0x1f, 0xb4, 0x0c, 0x86, 0x1a, 0x00, - 0x62, 0xb2, 0x36, 0x1b, 0x16, 0x57, 0xf9, 0xdc, 0x0f, 0x66, 0xcc, 0x2d, 0x94, 0x57, 0xf5, 0x7d, - 0xdd, 0xf0, 0x03, 0x42, 0x98, 0x3e, 0xd4, 0xb2, 0xc2, 0xaf, 0x0f, 0xd1, 0x1e, 0xe4, 0xb8, 0xe0, - 0x02, 0x2b, 0xcb, 0x09, 0xb8, 0x75, 0x36, 0x2e, 0x87, 0x92, 0xb7, 0x84, 0x47, 0x1f, 0x6a, 0x40, - 0x27, 0xdf, 0xe8, 0x7b, 0x58, 0xb3, 0xa3, 0x65, 0x20, 0x41, 0x9b, 0xba, 0x4e, 0x11, 0x78, 0xd6, - 0xc7, 0x67, 0xe3, 0xf2, 0x93, 0x9b, 0xd0, 0xd6, 0x72, 0x1d, 0xcf, 0x60, 0xfd, 0x00, 0x6b, 0xf9, - 0x09, 0x5e, 0xcb, 0x75, 0x90, 0x0e, 0xab, 0x3f, 0xf4, 0x83, 0x11, 0x87, 0xce, 0xcd, 0x0b, 0xbd, - 0x12, 0x42, 0x85, 0xa8, 0x5f, 0x43, 0x21, 0x54, 0xb9, 0xef, 0xd9, 0x93, 0x45, 0x2e, 0xe6, 0x39, - 0x75, 0x3b, 0xb3, 0xa8, 0xd3, 0xf7, 0x8f, 0x13, 0xd1, 0xda, 0xba, 0xc9, 0xac, 0xe4, 0x41, 0xe5, - 0x55, 0x1a, 0xd6, 0xaf, 0x04, 0xa1, 0x43, 0xc8, 0xf7, 0x3d, 0x93, 0x78, 0xb6, 0x60, 0x54, 0xba, - 0xb1, 0x3a, 0xb9, 0x49, 0xfe, 0x9b, 0xfa, 0xc8, 0xff, 0x46, 0x1f, 0x02, 0x9b, 0x09, 0x7d, 0xe2, - 0xec, 0x90, 0xcd, 0xd4, 0xbc, 0x6c, 0x6e, 0x4c, 0x85, 0x12, 0xb8, 0x21, 0xb5, 0x18, 0x6e, 0x45, - 0x82, 0x25, 0x6b, 0xa5, 0xe7, 0xad, 0xb5, 0xce, 0x95, 0x4b, 0x94, 0x71, 0x00, 0xf1, 0x32, 0x53, - 0x7e, 0xc3, 0x3a, 0xcb, 0xf3, 0xd6, 0x29, 0x84, 0xa0, 0xc7, 0x31, 0x66, 0x58, 0xe8, 0x47, 0xb8, - 0x3b, 0x88, 0x7f, 0xf4, 0xaf, 0x54, 0xcb, 0xcc, 0x5b, 0xed, 0xce, 0x04, 0x39, 0x59, 0xb2, 0xf2, - 0xbb, 0x0c, 0xb7, 0xaf, 0xac, 0x52, 0xc3, 0x3b, 0x21, 0x8b, 0x5e, 0xa7, 0xb7, 0x4c, 0x26, 0xff, - 0x3f, 0x93, 0xcd, 0x50, 0x2d, 0xb5, 0x70, 0xd5, 0x2a, 0x2d, 0xb8, 0x3b, 0xbd, 0xa6, 0x48, 0x30, - 0xbd, 0xaf, 0x28, 0x7a, 0x06, 0x69, 0x1b, 0xf7, 0x68, 0x51, 0xda, 0x4e, 0x55, 0x73, 0x7b, 0xf7, - 0x67, 0xff, 0xbf, 0x4f, 0x93, 0x34, 0x9e, 0x51, 0xf9, 0x14, 0x6e, 0x5f, 0x43, 0x2a, 0x7a, 0x07, - 0x64, 0x21, 0x46, 0x5e, 0x93, 0xd9, 0x30, 0xbc, 0xc2, 0xa3, 0x07, 0x5c, 0x44, 0xa3, 0x26, 0xac, - 0x07, 0xcf, 0xb9, 0xaa, 0x53, 0xd4, 0x16, 0x33, 0x58, 0x9f, 0xa2, 0x1c, 0xac, 0x34, 0x0f, 0x8e, - 0xea, 0x8d, 0xa3, 0x2f, 0x0a, 0x4b, 0x08, 0x20, 0xf3, 0xf9, 0xbe, 0xde, 0x78, 0x79, 0x50, 0x90, - 0xd0, 0x1a, 0x64, 0x8f, 0x8f, 0xd4, 0x17, 0x91, 0x4b, 0x46, 0x79, 0x58, 0x8d, 0xcc, 0x83, 0x7a, - 0x21, 0xa5, 0x7e, 0xf5, 0xea, 0xbc, 0x24, 0xbd, 0x3e, 0x2f, 0x49, 0x7f, 0x9d, 0x97, 0xa4, 0x5f, - 0x2e, 0x4a, 0x4b, 0xaf, 0x2f, 0x4a, 0x4b, 0x7f, 0x5e, 0x94, 0x96, 0xbe, 0xfb, 0xc7, 0xdb, 0x6a, - 0x98, 0x7c, 0xc5, 0x72, 0x42, 0xcd, 0x0c, 0x7f, 0xc5, 0x3e, 0xfa, 0x3b, 0x00, 0x00, 0xff, 0xff, - 0x20, 0xb1, 0xff, 0x55, 0xa2, 0x0b, 0x00, 0x00, + // 1030 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0x36, 0x25, 0x59, 0xb6, 0x46, 0x4a, 0x2d, 0x6f, 0x1c, 0x47, 0x75, 0x50, 0xc9, 0x51, 0x03, + 0x43, 0x08, 0x1a, 0xa9, 0x76, 0x7e, 0x90, 0x16, 0x68, 0x81, 0xc8, 0x32, 0x1a, 0xa1, 0xb1, 0xa3, + 0x52, 0x74, 0xfa, 0x73, 0x28, 0xb1, 0x24, 0xd7, 0x14, 0x2b, 0x99, 0xcb, 0x72, 0x57, 0xaa, 0x74, + 0xef, 0x03, 0xf4, 0xd6, 0x17, 0xe9, 0x03, 0xf4, 0x98, 0x63, 0xd0, 0x53, 0xe1, 0x83, 0x51, 0xd8, + 0x2f, 0x52, 0x70, 0xb9, 0x14, 0x69, 0xc7, 0x4e, 0xeb, 0xca, 0x3d, 0x89, 0xb3, 0x33, 0xf3, 0xcd, + 0xcc, 0xf7, 0x8d, 0xc8, 0x85, 0x0d, 0x03, 0x1b, 0x93, 0x01, 0x75, 0x1b, 0x06, 0x37, 0x19, 0xc7, + 0x7d, 0xc7, 0xb5, 0x1b, 0xa3, 0xcd, 0x84, 0x55, 0xf7, 0x7c, 0xca, 0x29, 0xba, 0x25, 0xe3, 0xea, + 0x09, 0xcf, 0x68, 0x73, 0x6d, 0xc5, 0xa6, 0x36, 0x15, 0x11, 0x8d, 0xe0, 0x29, 0x0c, 0x5e, 0x7b, + 0xdf, 0xa4, 0xec, 0x90, 0x32, 0x3d, 0x74, 0x84, 0x86, 0x74, 0x55, 0x43, 0xab, 0x61, 0xfa, 0x13, + 0x8f, 0xd3, 0x06, 0x23, 0xa6, 0xb7, 0xf5, 0xf8, 0x49, 0x7f, 0xb3, 0xd1, 0x27, 0x93, 0x28, 0xe6, + 0x9e, 0x8c, 0x89, 0xfb, 0x31, 0x08, 0xc7, 0x9b, 0x8d, 0x33, 0x1d, 0xad, 0x55, 0x2e, 0xee, 0xdc, + 0xa3, 0x5e, 0x18, 0x50, 0x3d, 0x4e, 0x43, 0xa1, 0xa9, 0x6d, 0xbf, 0xc2, 0x03, 0xc7, 0xc2, 0x9c, + 0xfa, 0x68, 0x07, 0xf2, 0x16, 0x61, 0xa6, 0xef, 0x78, 0xdc, 0xa1, 0x6e, 0x49, 0x59, 0x57, 0x6a, + 0xf9, 0xad, 0x0f, 0xeb, 0xb2, 0xbf, 0x78, 0x2a, 0x51, 0xad, 0xde, 0x8a, 0x43, 0xd5, 0x64, 0x1e, + 0xfa, 0x06, 0xc0, 0xa4, 0x87, 0x87, 0x0e, 0x63, 0x01, 0x4a, 0x6a, 0x5d, 0xa9, 0xe5, 0x9a, 0x4f, + 0x8f, 0x8e, 0x2b, 0x1b, 0xb6, 0xc3, 0x7b, 0x43, 0xa3, 0x6e, 0xd2, 0xc3, 0x46, 0x34, 0xa5, 0xf8, + 0x79, 0xc0, 0xac, 0x7e, 0x83, 0x4f, 0x3c, 0xc2, 0xea, 0x2d, 0x62, 0xfe, 0xf1, 0xdb, 0x03, 0x90, + 0x25, 0x5b, 0xc4, 0x54, 0x13, 0x58, 0xe8, 0x73, 0x00, 0x39, 0x94, 0xee, 0xf5, 0x4b, 0x69, 0xd1, + 0x5f, 0x25, 0xea, 0x2f, 0x64, 0xac, 0x3e, 0x65, 0xac, 0xde, 0x19, 0x1a, 0x5f, 0x92, 0x89, 0x9a, + 0x93, 0x29, 0x9d, 0x3e, 0xda, 0x85, 0xac, 0xc1, 0xcd, 0x20, 0x37, 0xb3, 0xae, 0xd4, 0x0a, 0xcd, + 0x27, 0x47, 0xc7, 0x95, 0xad, 0x44, 0x57, 0x32, 0xd2, 0xec, 0x61, 0xc7, 0x8d, 0x0c, 0xd9, 0x58, + 0xb3, 0xdd, 0x79, 0xf8, 0xe8, 0x63, 0x09, 0x39, 0x6f, 0x70, 0xb3, 0xd3, 0x47, 0x9f, 0x42, 0xda, + 0xa3, 0x5e, 0x69, 0x5e, 0xf4, 0x51, 0xab, 0x5f, 0xb8, 0x01, 0xf5, 0x8e, 0x4f, 0xe9, 0xc1, 0xcb, + 0x83, 0x0e, 0x65, 0x8c, 0x88, 0x29, 0xd4, 0x20, 0x09, 0x3d, 0x82, 0x55, 0x36, 0xc0, 0xac, 0x47, + 0x2c, 0x3d, 0x1a, 0xa9, 0x47, 0x1c, 0xbb, 0xc7, 0x4b, 0xd9, 0x75, 0xa5, 0x96, 0x51, 0x57, 0xa4, + 0xb7, 0x19, 0x3a, 0x9f, 0x0b, 0x1f, 0xfa, 0x08, 0xd0, 0x34, 0x8b, 0x9b, 0x51, 0xc6, 0x82, 0xc8, + 0x28, 0x46, 0x19, 0xdc, 0x0c, 0xa3, 0xab, 0x3f, 0xa7, 0x60, 0x25, 0x29, 0xf0, 0xd7, 0x0e, 0xef, + 0xed, 0x12, 0x8e, 0x13, 0x3c, 0x28, 0xd7, 0xc1, 0xc3, 0x2a, 0x64, 0x65, 0x27, 0x29, 0xd1, 0x89, + 0xb4, 0xd0, 0x5d, 0x28, 0x8c, 0x28, 0x77, 0x5c, 0x5b, 0xf7, 0xe8, 0x4f, 0xc4, 0x17, 0x82, 0x65, + 0xd4, 0x7c, 0x78, 0xd6, 0x09, 0x8e, 0xde, 0x41, 0x43, 0xe6, 0xca, 0x34, 0xcc, 0x5f, 0x42, 0xc3, + 0xaf, 0x59, 0xb8, 0xd1, 0xd4, 0xb6, 0x5b, 0x64, 0x40, 0x6c, 0xcc, 0xdf, 0xde, 0x23, 0x65, 0x86, + 0x3d, 0x4a, 0x5d, 0xe3, 0x1e, 0xa5, 0xff, 0xcb, 0x1e, 0x69, 0x00, 0x23, 0x3c, 0xd0, 0xaf, 0x65, + 0xad, 0x17, 0x47, 0x78, 0xd0, 0x14, 0x1d, 0xdd, 0x85, 0x02, 0xe3, 0xd8, 0xe7, 0x67, 0xa9, 0xcd, + 0x8b, 0x33, 0xa9, 0xc1, 0x07, 0x00, 0xc4, 0xb5, 0xce, 0x2e, 0x6d, 0x8e, 0xb8, 0x96, 0x74, 0xdf, + 0x81, 0x1c, 0xa7, 0x1c, 0x0f, 0x74, 0x86, 0xa3, 0x05, 0x5d, 0x14, 0x07, 0x5d, 0xcc, 0x51, 0x1b, + 0x40, 0x4e, 0xa6, 0xf3, 0x71, 0x69, 0x51, 0xcc, 0x7d, 0xff, 0x92, 0xb9, 0xa5, 0xf2, 0x4d, 0x6d, + 0x5b, 0xc3, 0x9e, 0x4f, 0x29, 0xd7, 0xc6, 0x6a, 0x4e, 0xfa, 0xb5, 0x31, 0xda, 0x82, 0xbc, 0x10, + 0x5c, 0x62, 0xe5, 0x04, 0x01, 0xcb, 0x47, 0xc7, 0x95, 0x40, 0xf2, 0xae, 0xf4, 0x68, 0x63, 0x15, + 0xd8, 0xf4, 0x19, 0x7d, 0x0f, 0x37, 0xac, 0x70, 0x19, 0xa8, 0xaf, 0x33, 0xc7, 0x2e, 0x81, 0xc8, + 0xfa, 0xe4, 0xe8, 0xb8, 0xf2, 0xf8, 0x2a, 0xb4, 0x75, 0x1d, 0xdb, 0xc5, 0x7c, 0xe8, 0x13, 0xb5, + 0x30, 0xc5, 0xeb, 0x3a, 0x36, 0xd2, 0x60, 0xf1, 0x87, 0xa1, 0x3f, 0x11, 0xd0, 0xf9, 0x59, 0xa1, + 0x17, 0x02, 0xa8, 0x00, 0xf5, 0x2b, 0x28, 0x06, 0x2a, 0x0f, 0x5d, 0x6b, 0xba, 0xc8, 0xa5, 0x82, + 0xa0, 0x6e, 0xe3, 0x32, 0xea, 0xb4, 0xed, 0xfd, 0x44, 0xb4, 0xba, 0x64, 0x70, 0x33, 0x79, 0x50, + 0x7d, 0x9d, 0x81, 0xa5, 0x73, 0x41, 0x68, 0x17, 0x0a, 0x43, 0xd7, 0xa0, 0xae, 0x25, 0x19, 0x55, + 0xae, 0xac, 0x4e, 0x7e, 0x9a, 0xff, 0xb6, 0x3e, 0xa9, 0x7f, 0xa3, 0x0f, 0x85, 0xd5, 0x84, 0x3e, + 0x51, 0x76, 0xc0, 0x66, 0x7a, 0x56, 0x36, 0x57, 0x62, 0xa1, 0x24, 0x6e, 0x40, 0x2d, 0x81, 0xe5, + 0x50, 0xb0, 0x64, 0xad, 0xcc, 0xac, 0xb5, 0x96, 0x84, 0x72, 0x89, 0x32, 0x36, 0x20, 0x51, 0x26, + 0xe6, 0x37, 0xa8, 0x33, 0x3f, 0x6b, 0x9d, 0x62, 0x00, 0xba, 0x1f, 0x61, 0x06, 0x85, 0x7e, 0x84, + 0xdb, 0xa3, 0xe8, 0xa5, 0x7f, 0xae, 0x5a, 0x76, 0xd6, 0x6a, 0xb7, 0xa6, 0xc8, 0xc9, 0x92, 0xd5, + 0xdf, 0x53, 0x70, 0xf3, 0xdc, 0x2a, 0xb5, 0xdd, 0x03, 0x7a, 0xdd, 0xeb, 0xf4, 0x8e, 0xc9, 0x52, + 0xff, 0xcf, 0x64, 0x97, 0xa8, 0x96, 0xbe, 0x76, 0xd5, 0xaa, 0x5d, 0xb8, 0x1d, 0x7f, 0xa6, 0xa8, + 0x1f, 0x7f, 0xaf, 0x18, 0x7a, 0x0a, 0x19, 0x8b, 0x0c, 0x58, 0x49, 0x59, 0x4f, 0xd7, 0xf2, 0x5b, + 0xf7, 0x2e, 0xff, 0xbf, 0xc7, 0x49, 0xaa, 0xc8, 0xa8, 0xee, 0xc1, 0x9d, 0x8b, 0x41, 0xdb, 0xae, + 0x45, 0xc6, 0xa8, 0x01, 0x2b, 0xf1, 0x9b, 0x58, 0xef, 0x61, 0xd6, 0xd3, 0x07, 0x0e, 0xe3, 0xa2, + 0x50, 0x41, 0x5d, 0x9e, 0xbe, 0x67, 0x9f, 0x63, 0xd6, 0x7b, 0xe1, 0x30, 0x5e, 0xfd, 0x0c, 0x6e, + 0x5e, 0x20, 0x12, 0x7a, 0x0f, 0x52, 0x52, 0xdc, 0x82, 0x9a, 0xe2, 0xe3, 0xe0, 0x4a, 0x10, 0x5e, + 0x08, 0x43, 0x59, 0x54, 0x69, 0xdd, 0xd7, 0xc4, 0x96, 0xc4, 0x5d, 0x74, 0x39, 0xe6, 0x43, 0x86, + 0xf2, 0xb0, 0xd0, 0xd9, 0xd9, 0x6b, 0xb5, 0xf7, 0xbe, 0x28, 0xce, 0x21, 0x80, 0xec, 0xb3, 0x6d, + 0xad, 0xfd, 0x6a, 0xa7, 0xa8, 0xa0, 0x1b, 0x90, 0xdb, 0xdf, 0x6b, 0xbe, 0x0c, 0x5d, 0x29, 0x54, + 0x80, 0xc5, 0xd0, 0xdc, 0x69, 0x15, 0xd3, 0x68, 0x01, 0xd2, 0xcf, 0xf6, 0xbe, 0x2d, 0x66, 0x9a, + 0x2f, 0x5e, 0x9f, 0x94, 0x95, 0x37, 0x27, 0x65, 0xe5, 0xaf, 0x93, 0xb2, 0xf2, 0xcb, 0x69, 0x79, + 0xee, 0xcd, 0x69, 0x79, 0xee, 0xcf, 0xd3, 0xf2, 0xdc, 0x77, 0xff, 0xf8, 0x19, 0x1c, 0x27, 0xaf, + 0xc7, 0x42, 0x29, 0x23, 0x2b, 0xae, 0xc7, 0x0f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x4e, 0x0e, + 0x34, 0x40, 0xfb, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1163,6 +1216,38 @@ func (m *BTCDelegatorDelegations) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *BTCDelegatorDelegationIndex) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCDelegatorDelegationIndex) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCDelegatorDelegationIndex) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.StakingTxHashList) > 0 { + for iNdEx := len(m.StakingTxHashList) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.StakingTxHashList[iNdEx]) + copy(dAtA[i:], m.StakingTxHashList[iNdEx]) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.StakingTxHashList[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func (m *BabylonBTCTaprootTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1394,6 +1479,21 @@ func (m *BTCDelegatorDelegations) Size() (n int) { return n } +func (m *BTCDelegatorDelegationIndex) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.StakingTxHashList) > 0 { + for _, b := range m.StakingTxHashList { + l = len(b) + n += 1 + l + sovBtcstaking(uint64(l)) + } + } + return n +} + func (m *BabylonBTCTaprootTx) Size() (n int) { if m == nil { return 0 @@ -2772,6 +2872,88 @@ func (m *BTCDelegatorDelegations) Unmarshal(dAtA []byte) error { } return nil } +func (m *BTCDelegatorDelegationIndex) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCDelegatorDelegationIndex: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCDelegatorDelegationIndex: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHashList", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHashList = append(m.StakingTxHashList, make([]byte, postIndex-iNdEx)) + copy(m.StakingTxHashList[len(m.StakingTxHashList)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *BabylonBTCTaprootTx) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 7a48bf6c9..0a937c18f 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -7,22 +7,23 @@ import ( // x/btcstaking module sentinel errors var ( ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") - ErrBTCDelNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegation is not found") - ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1102, "the BTC validator has already been registered") - ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been slashed") - ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1104, "the BTC staking protocol is not activated yet") - ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1105, "the BTC height is not found") - ErrReusedStakingTx = errorsmod.Register(ModuleName, 1106, "the BTC staking tx is already used") - ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1107, "the BTC staking tx specifies a wrong jury PK") - ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1108, "the BTC staking tx is not valid") - ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1109, "the BTC slashing tx is not valid") - ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1110, "the BTC delegation has already received jury signature") - ErrInvalidJurySig = errorsmod.Register(ModuleName, 1111, "the jury signature is not valid") - ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1112, "commission cannot be less than min rate") - ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1113, "Unexpected delegation state") - ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1114, "the BTC unbonding tx is not valid") - ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1115, "the BTC undelegation has already received validator signature") - ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1116, "the validator signature is not valid") - ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1117, "the BTC undelegation did not receive validator signature yet") - ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1118, "the reward distribution cache is not found") + ErrBTCDelegatorNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegator is not found") + ErrBTCDelegationNotFound = errorsmod.Register(ModuleName, 1102, "the BTC delegation is not found") + ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been registered") + ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the BTC validator has already been slashed") + ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1105, "the BTC staking protocol is not activated yet") + ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1106, "the BTC height is not found") + ErrReusedStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is already used") + ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong jury PK") + ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1109, "the BTC staking tx is not valid") + ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1110, "the BTC slashing tx is not valid") + ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received jury signature") + ErrInvalidJurySig = errorsmod.Register(ModuleName, 1112, "the jury signature is not valid") + ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1113, "commission cannot be less than min rate") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1114, "Unexpected delegation state") + ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1115, "the BTC unbonding tx is not valid") + ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1116, "the BTC undelegation has already received validator signature") + ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1117, "the validator signature is not valid") + ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1118, "the BTC undelegation did not receive validator signature yet") + ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1119, "the reward distribution cache is not found") ) diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index 0793a56cf..c2ba33a68 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -17,10 +17,11 @@ const ( var ( ParamsKey = []byte{0x01} // key prefix for the parameters BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators - BTCDelegationKey = []byte{0x03} // key prefix for the BTC delegations - VotingPowerKey = []byte{0x04} // key prefix for the voting power - BTCHeightKey = []byte{0x05} // key prefix for the BTC heights - RewardDistCacheKey = []byte{0x06} // key prefix for reward distribution cache + BTCDelegatorKey = []byte{0x03} // key prefix for the BTC delegators + BTCDelegationKey = []byte{0x04} // key prefix for the BTC delegations + VotingPowerKey = []byte{0x05} // key prefix for the voting power + BTCHeightKey = []byte{0x06} // key prefix for the BTC heights + RewardDistCacheKey = []byte{0x07} // key prefix for reward distribution cache ) func KeyPrefix(p string) []byte { diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index 5c61251d6..a028814fc 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -137,3 +137,14 @@ func (pop *ProofOfPossession) VerifyBIP322(babylonPK cryptotypes.PubKey, bip340P return nil } + +func (p *ProofOfPossession) ValidateBasic() error { + if len(p.BabylonSig) == 0 { + return fmt.Errorf("empty Babylon signature") + } + if p.BtcSig == nil { + return fmt.Errorf("empty BTC signature") + } + + return nil +} diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 1fce7194e..c73571379 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -309,23 +309,27 @@ func (m *QueryBTCValidatorResponse) GetBtcValidator() *BTCValidator { return nil } -// QueryPendingBTCDelegationsRequest is the request type for the -// Query/PendingBTCDelegations RPC method. -type QueryPendingBTCDelegationsRequest struct { +// QueryBTCDelegationsRequest is the request type for the +// Query/BTCDelegations RPC method. +type QueryBTCDelegationsRequest struct { + // status is the queried status for BTC delegations + Status BTCDelegationStatus `protobuf:"varint,1,opt,name=status,proto3,enum=babylon.btcstaking.v1.BTCDelegationStatus" json:"status,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryPendingBTCDelegationsRequest) Reset() { *m = QueryPendingBTCDelegationsRequest{} } -func (m *QueryPendingBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryPendingBTCDelegationsRequest) ProtoMessage() {} -func (*QueryPendingBTCDelegationsRequest) Descriptor() ([]byte, []int) { +func (m *QueryBTCDelegationsRequest) Reset() { *m = QueryBTCDelegationsRequest{} } +func (m *QueryBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBTCDelegationsRequest) ProtoMessage() {} +func (*QueryBTCDelegationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{6} } -func (m *QueryPendingBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryPendingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryPendingBTCDelegationsRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryBTCDelegationsRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -335,82 +339,53 @@ func (m *QueryPendingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic return b[:n], nil } } -func (m *QueryPendingBTCDelegationsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryPendingBTCDelegationsRequest.Merge(m, src) +func (m *QueryBTCDelegationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCDelegationsRequest.Merge(m, src) } -func (m *QueryPendingBTCDelegationsRequest) XXX_Size() int { +func (m *QueryBTCDelegationsRequest) XXX_Size() int { return m.Size() } -func (m *QueryPendingBTCDelegationsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryPendingBTCDelegationsRequest.DiscardUnknown(m) +func (m *QueryBTCDelegationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCDelegationsRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryPendingBTCDelegationsRequest proto.InternalMessageInfo - -// QueryPendingBTCDelegationsResponse is the response type for the -// Query/PendingBTCDelegations RPC method. -type QueryPendingBTCDelegationsResponse struct { - // btc_delegations contains all the queried BTC delegations. - BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` -} +var xxx_messageInfo_QueryBTCDelegationsRequest proto.InternalMessageInfo -func (m *QueryPendingBTCDelegationsResponse) Reset() { *m = QueryPendingBTCDelegationsResponse{} } -func (m *QueryPendingBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryPendingBTCDelegationsResponse) ProtoMessage() {} -func (*QueryPendingBTCDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{7} -} -func (m *QueryPendingBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryPendingBTCDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryPendingBTCDelegationsResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil +func (m *QueryBTCDelegationsRequest) GetStatus() BTCDelegationStatus { + if m != nil { + return m.Status } -} -func (m *QueryPendingBTCDelegationsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryPendingBTCDelegationsResponse.Merge(m, src) -} -func (m *QueryPendingBTCDelegationsResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryPendingBTCDelegationsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryPendingBTCDelegationsResponse.DiscardUnknown(m) + return BTCDelegationStatus_PENDING } -var xxx_messageInfo_QueryPendingBTCDelegationsResponse proto.InternalMessageInfo - -func (m *QueryPendingBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegation { +func (m *QueryBTCDelegationsRequest) GetPagination() *query.PageRequest { if m != nil { - return m.BtcDelegations + return m.Pagination } return nil } -// QueryUnbondingBTCDelegationsRequest is the request type for the -// QueryUnbondingBTCDelegations RPC method. -type QueryUnbondingBTCDelegationsRequest struct { +// QueryBTCDelegationsResponse is the response type for the +// Query/BTCDelegations RPC method. +type QueryBTCDelegationsResponse struct { + // btc_delegations contains all the queried BTC delegations under the given status + BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryUnbondingBTCDelegationsRequest) Reset() { *m = QueryUnbondingBTCDelegationsRequest{} } -func (m *QueryUnbondingBTCDelegationsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryUnbondingBTCDelegationsRequest) ProtoMessage() {} -func (*QueryUnbondingBTCDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{8} +func (m *QueryBTCDelegationsResponse) Reset() { *m = QueryBTCDelegationsResponse{} } +func (m *QueryBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBTCDelegationsResponse) ProtoMessage() {} +func (*QueryBTCDelegationsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_74d49d26f7429697, []int{7} } -func (m *QueryUnbondingBTCDelegationsRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryUnbondingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryBTCDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryBTCDelegationsResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -420,61 +395,28 @@ func (m *QueryUnbondingBTCDelegationsRequest) XXX_Marshal(b []byte, deterministi return b[:n], nil } } -func (m *QueryUnbondingBTCDelegationsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.Merge(m, src) +func (m *QueryBTCDelegationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBTCDelegationsResponse.Merge(m, src) } -func (m *QueryUnbondingBTCDelegationsRequest) XXX_Size() int { +func (m *QueryBTCDelegationsResponse) XXX_Size() int { return m.Size() } -func (m *QueryUnbondingBTCDelegationsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryUnbondingBTCDelegationsRequest.DiscardUnknown(m) +func (m *QueryBTCDelegationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBTCDelegationsResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryUnbondingBTCDelegationsRequest proto.InternalMessageInfo - -// QueryUnbondingBTCDelegationsResponse is the response type for the -// QueryUnbondingBTCDelegations RPC method. -type QueryUnbondingBTCDelegationsResponse struct { - // btc_delegations contains all the queried BTC delegations. - BtcDelegations []*BTCDelegation `protobuf:"bytes,1,rep,name=btc_delegations,json=btcDelegations,proto3" json:"btc_delegations,omitempty"` -} +var xxx_messageInfo_QueryBTCDelegationsResponse proto.InternalMessageInfo -func (m *QueryUnbondingBTCDelegationsResponse) Reset() { *m = QueryUnbondingBTCDelegationsResponse{} } -func (m *QueryUnbondingBTCDelegationsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryUnbondingBTCDelegationsResponse) ProtoMessage() {} -func (*QueryUnbondingBTCDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{9} -} -func (m *QueryUnbondingBTCDelegationsResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryUnbondingBTCDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil +func (m *QueryBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegation { + if m != nil { + return m.BtcDelegations } + return nil } -func (m *QueryUnbondingBTCDelegationsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.Merge(m, src) -} -func (m *QueryUnbondingBTCDelegationsResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryUnbondingBTCDelegationsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryUnbondingBTCDelegationsResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryUnbondingBTCDelegationsResponse proto.InternalMessageInfo -func (m *QueryUnbondingBTCDelegationsResponse) GetBtcDelegations() []*BTCDelegation { +func (m *QueryBTCDelegationsResponse) GetPagination() *query.PageResponse { if m != nil { - return m.BtcDelegations + return m.Pagination } return nil } @@ -494,7 +436,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{10} + return fileDescriptor_74d49d26f7429697, []int{8} } func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -550,7 +492,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{11} + return fileDescriptor_74d49d26f7429697, []int{9} } func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -599,7 +541,7 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorCurrentPowerRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorCurrentPowerRequest) ProtoMessage() {} func (*QueryBTCValidatorCurrentPowerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{12} + return fileDescriptor_74d49d26f7429697, []int{10} } func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -648,7 +590,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCV func (m *QueryBTCValidatorCurrentPowerResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorCurrentPowerResponse) ProtoMessage() {} func (*QueryBTCValidatorCurrentPowerResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{13} + return fileDescriptor_74d49d26f7429697, []int{11} } func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -706,7 +648,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{14} + return fileDescriptor_74d49d26f7429697, []int{12} } func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -764,7 +706,7 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{15} + return fileDescriptor_74d49d26f7429697, []int{13} } func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -815,7 +757,7 @@ func (m *QueryActivatedHeightRequest) Reset() { *m = QueryActivatedHeigh func (m *QueryActivatedHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightRequest) ProtoMessage() {} func (*QueryActivatedHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{16} + return fileDescriptor_74d49d26f7429697, []int{14} } func (m *QueryActivatedHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -853,7 +795,7 @@ func (m *QueryActivatedHeightResponse) Reset() { *m = QueryActivatedHeig func (m *QueryActivatedHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryActivatedHeightResponse) ProtoMessage() {} func (*QueryActivatedHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{17} + return fileDescriptor_74d49d26f7429697, []int{15} } func (m *QueryActivatedHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -904,7 +846,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCVal func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{18} + return fileDescriptor_74d49d26f7429697, []int{16} } func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -960,7 +902,7 @@ func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCVa func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{19} + return fileDescriptor_74d49d26f7429697, []int{17} } func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1014,7 +956,7 @@ func (m *QueryBTCDelegationRequest) Reset() { *m = QueryBTCDelegationReq func (m *QueryBTCDelegationRequest) String() string { return proto.CompactTextString(m) } func (*QueryBTCDelegationRequest) ProtoMessage() {} func (*QueryBTCDelegationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{20} + return fileDescriptor_74d49d26f7429697, []int{18} } func (m *QueryBTCDelegationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1084,7 +1026,7 @@ func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationRe func (m *QueryBTCDelegationResponse) String() string { return proto.CompactTextString(m) } func (*QueryBTCDelegationResponse) ProtoMessage() {} func (*QueryBTCDelegationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_74d49d26f7429697, []int{21} + return fileDescriptor_74d49d26f7429697, []int{19} } func (m *QueryBTCDelegationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1169,10 +1111,8 @@ func init() { proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") proto.RegisterType((*QueryBTCValidatorRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorRequest") proto.RegisterType((*QueryBTCValidatorResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorResponse") - proto.RegisterType((*QueryPendingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsRequest") - proto.RegisterType((*QueryPendingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryPendingBTCDelegationsResponse") - proto.RegisterType((*QueryUnbondingBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryUnbondingBTCDelegationsRequest") - proto.RegisterType((*QueryUnbondingBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryUnbondingBTCDelegationsResponse") + proto.RegisterType((*QueryBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCDelegationsRequest") + proto.RegisterType((*QueryBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCDelegationsResponse") proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") proto.RegisterType((*QueryBTCValidatorCurrentPowerRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerRequest") @@ -1190,88 +1130,86 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1292 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x4f, 0x1b, 0xc7, - 0x1b, 0x66, 0xf8, 0xf0, 0x0f, 0x5e, 0x3e, 0xc2, 0x6f, 0x92, 0x14, 0xb3, 0x04, 0x03, 0x4b, 0xf8, - 0x08, 0x55, 0x76, 0xc1, 0x54, 0x28, 0x2d, 0x34, 0x69, 0x0c, 0x4d, 0x08, 0x14, 0xc9, 0xdd, 0x90, - 0x46, 0xea, 0xc5, 0x9a, 0xb5, 0x27, 0xeb, 0x2d, 0x66, 0xd7, 0xf1, 0x8e, 0x5d, 0x50, 0xc4, 0xa5, - 0x87, 0x4a, 0xbd, 0x55, 0xed, 0x9f, 0xd0, 0x63, 0x4f, 0xbd, 0xb6, 0xd7, 0x4a, 0xcd, 0xa1, 0x87, - 0x48, 0x95, 0xda, 0xaa, 0x91, 0x50, 0x05, 0x95, 0xfa, 0x27, 0xf4, 0x5a, 0x79, 0x76, 0xcc, 0xae, - 0xf1, 0xee, 0xda, 0xb8, 0xc9, 0x0d, 0xcf, 0xbc, 0x1f, 0xcf, 0xf3, 0xbe, 0xef, 0xbc, 0xfb, 0x08, - 0x98, 0xd2, 0x89, 0x7e, 0x58, 0xb0, 0x2d, 0x55, 0x67, 0x59, 0x87, 0x91, 0x3d, 0xd3, 0x32, 0xd4, - 0xca, 0x92, 0xfa, 0xb4, 0x4c, 0x4b, 0x87, 0x4a, 0xb1, 0x64, 0x33, 0x1b, 0x5f, 0x15, 0x26, 0x8a, - 0x67, 0xa2, 0x54, 0x96, 0xa4, 0x2b, 0x86, 0x6d, 0xd8, 0xdc, 0x42, 0xad, 0xfe, 0xe5, 0x1a, 0x4b, - 0xd7, 0x0c, 0xdb, 0x36, 0x0a, 0x54, 0x25, 0x45, 0x53, 0x25, 0x96, 0x65, 0x33, 0xc2, 0x4c, 0xdb, - 0x72, 0xc4, 0xed, 0x42, 0xd6, 0x76, 0xf6, 0x6d, 0x47, 0xd5, 0x89, 0x43, 0xdd, 0x1c, 0x6a, 0x65, - 0x49, 0xa7, 0x8c, 0x2c, 0xa9, 0x45, 0x62, 0x98, 0x16, 0x37, 0x16, 0xb6, 0x72, 0x30, 0xb2, 0x22, - 0x29, 0x91, 0xfd, 0x5a, 0xbc, 0xd9, 0x60, 0x1b, 0x1f, 0x50, 0x6e, 0x27, 0x5f, 0x01, 0xfc, 0x61, - 0x35, 0x5b, 0x9a, 0x3b, 0x6b, 0xf4, 0x69, 0x99, 0x3a, 0x4c, 0xd6, 0xe0, 0x72, 0xdd, 0xa9, 0x53, - 0xb4, 0x2d, 0x87, 0xe2, 0x55, 0x88, 0xb9, 0x49, 0xe2, 0x68, 0x12, 0xcd, 0xf7, 0x27, 0xc7, 0x95, - 0xc0, 0x02, 0x28, 0xae, 0x5b, 0xaa, 0xfb, 0xf9, 0xf1, 0x44, 0x87, 0x26, 0x5c, 0xe4, 0x2c, 0x8c, - 0xf2, 0x98, 0xa9, 0xdd, 0xf5, 0x8f, 0x48, 0xc1, 0xcc, 0x11, 0x66, 0x97, 0x6a, 0x09, 0xf1, 0x3d, - 0x00, 0x8f, 0xa6, 0x88, 0x3e, 0xab, 0xb8, 0x35, 0x51, 0xaa, 0x35, 0x51, 0xdc, 0xba, 0x8b, 0x9a, - 0x28, 0x69, 0x62, 0x50, 0xe1, 0xab, 0xf9, 0x3c, 0xe5, 0xef, 0x10, 0x48, 0x41, 0x59, 0x04, 0x81, - 0x2d, 0x18, 0xd2, 0x59, 0x36, 0x53, 0x39, 0xbb, 0x89, 0xa3, 0xc9, 0xae, 0xf9, 0xfe, 0xe4, 0x74, - 0x08, 0x11, 0x7f, 0x14, 0x6d, 0x50, 0x67, 0x59, 0x2f, 0x26, 0xbe, 0x5f, 0x07, 0xb9, 0x93, 0x43, - 0x9e, 0x6b, 0x0a, 0xd9, 0x05, 0x52, 0x87, 0xf9, 0x0e, 0xc4, 0x1b, 0x20, 0xd7, 0xea, 0x32, 0x0d, - 0x43, 0x15, 0x52, 0xc8, 0x54, 0x41, 0x17, 0xf7, 0x32, 0x79, 0x7a, 0xc0, 0x6b, 0xd3, 0xa7, 0xf5, - 0x57, 0x48, 0x21, 0xc5, 0xb2, 0xe9, 0xbd, 0x4d, 0x7a, 0x20, 0xd3, 0x80, 0xca, 0x9e, 0x51, 0xde, - 0x84, 0xc1, 0x3a, 0xca, 0xa2, 0xb8, 0x2d, 0x31, 0x1e, 0xf0, 0x33, 0x96, 0xa7, 0x61, 0xca, 0x1d, - 0x0a, 0x6a, 0xe5, 0x4c, 0xcb, 0x48, 0xed, 0xae, 0x6f, 0xd0, 0x02, 0x35, 0xdc, 0x31, 0xae, 0x4d, - 0x8e, 0x03, 0x72, 0x94, 0x91, 0x00, 0xb5, 0x03, 0x97, 0xaa, 0xa0, 0x72, 0xde, 0x95, 0x68, 0xc4, - 0xf5, 0x70, 0x58, 0x5e, 0x1c, 0xad, 0xda, 0x44, 0x5f, 0x58, 0x79, 0x06, 0xa6, 0x79, 0xd2, 0x47, - 0x96, 0x6e, 0x47, 0x60, 0x2b, 0xc3, 0xf5, 0x68, 0xb3, 0xd7, 0x83, 0x2e, 0x07, 0x33, 0x0d, 0xed, - 0x49, 0xdb, 0x9f, 0xd2, 0xd2, 0x5d, 0xb6, 0x49, 0x4d, 0x23, 0xcf, 0x2e, 0xd2, 0x6c, 0xfc, 0x06, - 0xc4, 0xf2, 0xdc, 0x8b, 0x8f, 0x5c, 0xb7, 0x26, 0x7e, 0xc9, 0xdb, 0x30, 0xdb, 0x2c, 0x8b, 0xa0, - 0x37, 0x05, 0x03, 0x15, 0x9b, 0x99, 0x96, 0x91, 0x29, 0x56, 0xef, 0x79, 0x92, 0x6e, 0xad, 0xdf, - 0x3d, 0xe3, 0x2e, 0xf2, 0xb6, 0xa8, 0x94, 0x3f, 0xd8, 0x7a, 0xb9, 0x54, 0xa2, 0x16, 0xe3, 0x06, - 0x17, 0x1a, 0x4f, 0x3d, 0x80, 0x7f, 0x7d, 0x30, 0x01, 0xcc, 0xa3, 0x86, 0xfc, 0xd4, 0x1a, 0x00, - 0x77, 0x36, 0x02, 0xfe, 0x02, 0xc1, 0x1c, 0x4f, 0x72, 0x37, 0xcb, 0xcc, 0x0a, 0xad, 0x7b, 0xfd, - 0xe7, 0xcb, 0x1c, 0x96, 0xe6, 0x5e, 0xc0, 0x83, 0x6e, 0x67, 0x07, 0xfd, 0x84, 0x60, 0xbe, 0x39, - 0x16, 0xc1, 0x59, 0x0b, 0xd9, 0x48, 0x6f, 0xb6, 0xf0, 0x3e, 0x1f, 0x9b, 0x2c, 0xbf, 0x43, 0x19, - 0x79, 0x6d, 0x9b, 0x69, 0x1c, 0xc6, 0x3c, 0x22, 0x84, 0xd1, 0x5c, 0x5d, 0x21, 0xe5, 0x15, 0xb8, - 0x16, 0x7c, 0x1d, 0xdd, 0x4f, 0xf9, 0x2b, 0x24, 0xde, 0xab, 0x9f, 0x4c, 0xe3, 0x7b, 0x6d, 0xed, - 0x3d, 0xbc, 0xaa, 0xae, 0xbd, 0x44, 0x01, 0x33, 0x1f, 0xb4, 0x1d, 0x3e, 0x81, 0x51, 0xdf, 0x76, - 0xb0, 0x4b, 0x01, 0x7b, 0x42, 0x69, 0xba, 0x27, 0xea, 0x43, 0x8f, 0x78, 0x1b, 0xa3, 0xee, 0xe2, - 0xd5, 0x75, 0x72, 0xcb, 0xfb, 0x44, 0xf8, 0x36, 0x95, 0xa8, 0xf3, 0x4d, 0xb8, 0x2c, 0x40, 0x66, - 0xd8, 0x41, 0x26, 0x4f, 0x9c, 0xbc, 0xaf, 0xd8, 0xc3, 0xe2, 0x6a, 0xf7, 0x60, 0x93, 0x38, 0xf9, - 0xea, 0x7b, 0xfe, 0xad, 0xcb, 0xfb, 0xc6, 0xfa, 0x83, 0x9d, 0x6d, 0xcf, 0x98, 0xdb, 0x31, 0x1e, - 0x60, 0x20, 0xb5, 0xf2, 0xc7, 0xf1, 0x44, 0xd2, 0x30, 0x59, 0xbe, 0xac, 0x2b, 0x59, 0x7b, 0x5f, - 0x15, 0xa5, 0xc9, 0xe6, 0x89, 0x69, 0xd5, 0x7e, 0xa8, 0xec, 0xb0, 0x48, 0x1d, 0x25, 0xf5, 0x20, - 0xbd, 0xfc, 0xd6, 0x62, 0xba, 0xac, 0x6f, 0xd3, 0x43, 0xad, 0x47, 0xaf, 0xb6, 0x18, 0xef, 0x02, - 0x78, 0x43, 0xc0, 0x4b, 0xd0, 0x7e, 0xc8, 0xde, 0xda, 0xe0, 0x54, 0x57, 0x8a, 0xc3, 0x48, 0x89, - 0x65, 0xc4, 0x80, 0x76, 0xb9, 0x2b, 0x85, 0x9f, 0xb9, 0x53, 0x8c, 0xc7, 0x01, 0xa8, 0x95, 0xab, - 0x19, 0x74, 0x73, 0x83, 0x3e, 0x6a, 0x89, 0x21, 0xc7, 0x63, 0xd0, 0xc7, 0x6c, 0x46, 0x0a, 0x19, - 0x87, 0xb0, 0x78, 0x0f, 0xbf, 0xed, 0xe5, 0x07, 0x0f, 0x09, 0xf7, 0xf5, 0x2a, 0x1a, 0x8f, 0xf1, - 0x42, 0xf6, 0x9d, 0x15, 0x12, 0xcf, 0xc0, 0x50, 0xed, 0xda, 0xc9, 0x96, 0xcc, 0x22, 0x8b, 0xff, - 0x8f, 0x9b, 0x0c, 0x8a, 0xd3, 0x87, 0xfc, 0xb0, 0xfa, 0x7e, 0x08, 0x5f, 0x21, 0xf1, 0xde, 0x49, - 0x34, 0xdf, 0xab, 0x89, 0x5f, 0xf8, 0x31, 0xfc, 0xbf, 0x6c, 0x79, 0x53, 0x97, 0x31, 0xad, 0x27, - 0x76, 0xbc, 0x8f, 0x0f, 0xc7, 0x42, 0xf8, 0xe4, 0x3d, 0xf2, 0xb9, 0x3c, 0xb0, 0x9e, 0xd8, 0xda, - 0x70, 0xf9, 0xdc, 0x49, 0xf2, 0x9f, 0x61, 0xe8, 0xe1, 0x9d, 0xc5, 0x9f, 0x23, 0x88, 0xb9, 0x2a, - 0x0e, 0xdf, 0x08, 0x09, 0xd9, 0x28, 0x1b, 0xa5, 0x85, 0x56, 0x4c, 0xdd, 0x31, 0x91, 0x67, 0x3e, - 0xfb, 0xe5, 0xaf, 0xaf, 0x3b, 0x27, 0xf0, 0xb8, 0x1a, 0xa5, 0x66, 0xf1, 0x37, 0x08, 0x06, 0xeb, - 0x36, 0x28, 0x5e, 0x8c, 0x4a, 0x12, 0x24, 0x2e, 0xa5, 0xa5, 0x0b, 0x78, 0x08, 0x74, 0x37, 0x39, - 0xba, 0x39, 0x3c, 0xa3, 0x86, 0xea, 0x68, 0xdf, 0xce, 0xc6, 0x3f, 0x20, 0x18, 0xf0, 0x07, 0xc2, - 0x6a, 0xab, 0x29, 0x6b, 0x18, 0x17, 0x5b, 0x77, 0x10, 0x10, 0x37, 0x39, 0xc4, 0x14, 0x7e, 0xaf, - 0x25, 0x88, 0xea, 0xb3, 0xfa, 0x55, 0x7a, 0xa4, 0x9e, 0xdd, 0xe1, 0x1f, 0x11, 0x5c, 0x0d, 0xd4, - 0x6b, 0xf8, 0x56, 0x64, 0x43, 0x23, 0x74, 0xa0, 0xf4, 0x76, 0x1b, 0x9e, 0x82, 0xd8, 0x0a, 0x27, - 0xb6, 0x88, 0x95, 0xb0, 0xc9, 0x70, 0xbd, 0x33, 0xe7, 0x34, 0x1a, 0xfe, 0x19, 0xc1, 0x48, 0x88, - 0xb4, 0xc3, 0xef, 0x44, 0xc1, 0x89, 0x96, 0x8d, 0xd2, 0x6a, 0x5b, 0xbe, 0x82, 0xcc, 0x2d, 0x4e, - 0x26, 0x89, 0x17, 0x43, 0xc8, 0x94, 0x6b, 0xfe, 0x0d, 0x74, 0x7e, 0x45, 0x30, 0x16, 0xa1, 0x20, - 0xf0, 0xed, 0x28, 0x58, 0xcd, 0x65, 0x90, 0x74, 0xa7, 0x6d, 0xff, 0x16, 0xfb, 0x74, 0x7e, 0x00, - 0xdd, 0x4d, 0x7a, 0x84, 0xff, 0x46, 0x30, 0x1a, 0xaa, 0x52, 0xf1, 0x5a, 0xab, 0x0f, 0x21, 0x48, - 0x42, 0x4b, 0xef, 0xb6, 0xe9, 0x2d, 0x28, 0xed, 0x70, 0x4a, 0xf7, 0xf1, 0xfb, 0x6d, 0xbe, 0x29, - 0xae, 0x4f, 0x3d, 0xa6, 0x2f, 0x11, 0xc4, 0xc3, 0x54, 0x2f, 0x5e, 0x6d, 0x15, 0x6a, 0x80, 0xf0, - 0x96, 0xd6, 0xda, 0x73, 0x16, 0x34, 0x37, 0x38, 0xcd, 0xdb, 0x78, 0xed, 0xbf, 0xd0, 0xc4, 0xdf, - 0x22, 0xb8, 0x74, 0x4e, 0xfa, 0xe1, 0x64, 0xd3, 0xa1, 0x6a, 0x90, 0x91, 0xd2, 0xf2, 0x85, 0x7c, - 0x04, 0x05, 0x95, 0x53, 0xb8, 0x81, 0xe7, 0x42, 0x28, 0x90, 0x9a, 0x9f, 0xf8, 0x80, 0xe3, 0x63, - 0x04, 0x23, 0x21, 0xd2, 0x2e, 0x7a, 0x3b, 0x44, 0x8b, 0x54, 0x69, 0xb5, 0x2d, 0x5f, 0xc1, 0x62, - 0x8b, 0xb3, 0xd8, 0xc0, 0xa9, 0x36, 0x1b, 0xe1, 0xdf, 0x17, 0xdf, 0xbb, 0x5f, 0x4a, 0x2f, 0x4d, - 0xd3, 0x2f, 0x65, 0x83, 0x12, 0x6c, 0xfa, 0xa5, 0x6c, 0x94, 0x7b, 0x2d, 0xcd, 0x92, 0x0f, 0xa6, - 0xfa, 0x2c, 0x40, 0x6a, 0x1e, 0xa5, 0x3e, 0x78, 0x7e, 0x92, 0x40, 0x2f, 0x4e, 0x12, 0xe8, 0xcf, - 0x93, 0x04, 0xfa, 0xf2, 0x34, 0xd1, 0xf1, 0xe2, 0x34, 0xd1, 0xf1, 0xfb, 0x69, 0xa2, 0xe3, 0xe3, - 0xa6, 0x3a, 0xef, 0xc0, 0x9f, 0x90, 0x8b, 0x3e, 0x3d, 0xc6, 0xff, 0xb7, 0xb5, 0xfc, 0x6f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x0e, 0x90, 0xa4, 0x93, 0xc3, 0x13, 0x00, 0x00, + // 1255 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc5, + 0x1b, 0xce, 0xa4, 0xa9, 0x7f, 0xc9, 0x9b, 0x7f, 0xfd, 0x4d, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, + 0x9a, 0x3f, 0x0d, 0xea, 0x6e, 0xec, 0xa0, 0x1c, 0x48, 0x68, 0xa9, 0x1b, 0x68, 0x9a, 0x10, 0xc9, + 0x6c, 0x02, 0x95, 0xb8, 0x58, 0xb3, 0xce, 0x74, 0xbd, 0xc4, 0xd9, 0x75, 0xbd, 0x63, 0x93, 0xa8, + 0xca, 0x85, 0x03, 0x12, 0xe2, 0x82, 0xe0, 0x23, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0xf0, 0x01, + 0xe8, 0xb1, 0x12, 0x12, 0x20, 0x2a, 0x45, 0x28, 0x41, 0xe2, 0x6b, 0xa0, 0x9d, 0x1d, 0x67, 0x77, + 0xe3, 0xb5, 0xbd, 0x36, 0xe1, 0x16, 0xcf, 0xcc, 0xfb, 0xbe, 0xcf, 0xf3, 0xcc, 0xb3, 0xef, 0xbc, + 0x0a, 0xdc, 0xd4, 0x89, 0x7e, 0x58, 0xb2, 0x2d, 0x55, 0x67, 0x05, 0x87, 0x91, 0x3d, 0xd3, 0x32, + 0xd4, 0x5a, 0x5a, 0x7d, 0x5a, 0xa5, 0x95, 0x43, 0xa5, 0x5c, 0xb1, 0x99, 0x8d, 0x5f, 0x11, 0x47, + 0x14, 0xff, 0x88, 0x52, 0x4b, 0x4b, 0xd7, 0x0c, 0xdb, 0xb0, 0xf9, 0x09, 0xd5, 0xfd, 0xcb, 0x3b, + 0x2c, 0xdd, 0x30, 0x6c, 0xdb, 0x28, 0x51, 0x95, 0x94, 0x4d, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, + 0x6d, 0xcb, 0x11, 0xbb, 0x0b, 0x05, 0xdb, 0xd9, 0xb7, 0x1d, 0x55, 0x27, 0x0e, 0xf5, 0x6a, 0xa8, + 0xb5, 0xb4, 0x4e, 0x19, 0x49, 0xab, 0x65, 0x62, 0x98, 0x16, 0x3f, 0x2c, 0xce, 0xca, 0xd1, 0xc8, + 0xca, 0xa4, 0x42, 0xf6, 0xeb, 0xf9, 0x66, 0xa3, 0xcf, 0x04, 0x80, 0xf2, 0x73, 0xf2, 0x35, 0xc0, + 0xef, 0xb9, 0xd5, 0x72, 0x3c, 0x58, 0xa3, 0x4f, 0xab, 0xd4, 0x61, 0xb2, 0x06, 0x57, 0x43, 0xab, + 0x4e, 0xd9, 0xb6, 0x1c, 0x8a, 0x57, 0x20, 0xe1, 0x15, 0x49, 0xa2, 0x29, 0x34, 0x3f, 0x98, 0x99, + 0x50, 0x22, 0x05, 0x50, 0xbc, 0xb0, 0x6c, 0xdf, 0xf3, 0xe3, 0xc9, 0x1e, 0x4d, 0x84, 0xc8, 0x05, + 0x18, 0xe3, 0x39, 0xb3, 0x3b, 0x0f, 0x3e, 0x20, 0x25, 0x73, 0x97, 0x30, 0xbb, 0x52, 0x2f, 0x88, + 0xdf, 0x01, 0xf0, 0x69, 0x8a, 0xec, 0xb3, 0x8a, 0xa7, 0x89, 0xe2, 0x6a, 0xa2, 0x78, 0xba, 0x0b, + 0x4d, 0x94, 0x1c, 0x31, 0xa8, 0x88, 0xd5, 0x02, 0x91, 0xf2, 0xf7, 0x08, 0xa4, 0xa8, 0x2a, 0x82, + 0xc0, 0x06, 0x8c, 0xe8, 0xac, 0x90, 0xaf, 0x9d, 0xed, 0x24, 0xd1, 0xd4, 0xa5, 0xf9, 0xc1, 0xcc, + 0x74, 0x13, 0x22, 0xc1, 0x2c, 0xda, 0xb0, 0xce, 0x0a, 0x7e, 0x4e, 0xfc, 0x30, 0x04, 0xb9, 0x97, + 0x43, 0x9e, 0x6b, 0x0b, 0xd9, 0x03, 0x12, 0xc2, 0x7c, 0x0f, 0x92, 0x0d, 0x90, 0xeb, 0xba, 0x4c, + 0xc3, 0x48, 0x8d, 0x94, 0xf2, 0x2e, 0xe8, 0xf2, 0x5e, 0xbe, 0x48, 0x0f, 0xb8, 0x36, 0x03, 0xda, + 0x60, 0x8d, 0x94, 0xb2, 0xac, 0x90, 0xdb, 0x5b, 0xa7, 0x07, 0x32, 0x8d, 0x50, 0xf6, 0x8c, 0xf2, + 0x3a, 0x0c, 0x87, 0x28, 0x0b, 0x71, 0x63, 0x31, 0x1e, 0x0a, 0x32, 0x96, 0xbf, 0x0d, 0x68, 0xbb, + 0x46, 0x4b, 0xd4, 0xf0, 0x0c, 0x5c, 0x87, 0x9a, 0x85, 0x84, 0xc3, 0x08, 0xab, 0x7a, 0xe6, 0x18, + 0xc9, 0x2c, 0x34, 0xaf, 0xe0, 0x47, 0x6f, 0xf3, 0x08, 0x4d, 0x44, 0x9e, 0xb3, 0x41, 0x6f, 0xd7, + 0x36, 0xf8, 0x01, 0xc1, 0x78, 0x24, 0x54, 0x21, 0xca, 0x16, 0x8c, 0xba, 0xa2, 0xec, 0xfa, 0x5b, + 0xc2, 0x08, 0xb7, 0xe2, 0x80, 0xd6, 0x5c, 0x13, 0x05, 0xd2, 0x5e, 0x9c, 0x15, 0x76, 0x61, 0xa6, + 0xe1, 0x26, 0x73, 0xf6, 0xc7, 0xb4, 0x72, 0x9f, 0xad, 0x53, 0xd3, 0x28, 0xb2, 0x4e, 0x7c, 0x81, + 0x5f, 0x85, 0x44, 0x91, 0x47, 0x71, 0x48, 0x7d, 0x9a, 0xf8, 0x25, 0x6f, 0xc2, 0x6c, 0xbb, 0x2a, + 0x42, 0xa7, 0x9b, 0x30, 0x54, 0xb3, 0x99, 0x69, 0x19, 0xf9, 0xb2, 0xbb, 0xcf, 0x8b, 0xf4, 0x69, + 0x83, 0xde, 0x1a, 0x0f, 0x91, 0x37, 0xe1, 0x56, 0x43, 0xb2, 0x07, 0xd5, 0x4a, 0x85, 0x5a, 0x8c, + 0x1f, 0xe8, 0xc8, 0xc9, 0x7a, 0x04, 0xff, 0x70, 0x32, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x1a, + 0x00, 0xf7, 0x36, 0x02, 0xfe, 0x0c, 0xc1, 0x1c, 0x2f, 0x72, 0xbf, 0xc0, 0xcc, 0x1a, 0x0d, 0x35, + 0x8a, 0xf3, 0x32, 0x37, 0x2b, 0x73, 0x51, 0x3e, 0xfd, 0x19, 0xc1, 0x7c, 0x7b, 0x2c, 0x82, 0xb3, + 0xd6, 0xa4, 0x79, 0xbd, 0x16, 0xe3, 0x53, 0x7e, 0x6c, 0xb2, 0xe2, 0x16, 0x65, 0xe4, 0x3f, 0x6b, + 0x62, 0x13, 0xe2, 0x83, 0xe3, 0x44, 0x08, 0xa3, 0xbb, 0x21, 0x21, 0xe5, 0x65, 0xb8, 0x11, 0xbd, + 0xdd, 0xfa, 0x3e, 0xe5, 0x2f, 0x11, 0x4c, 0x37, 0x38, 0x22, 0xa2, 0xf9, 0xc4, 0xfa, 0x1e, 0x2e, + 0xea, 0xd6, 0x5e, 0xa2, 0x08, 0xcf, 0x47, 0xb5, 0x99, 0x8f, 0x60, 0x2c, 0xd0, 0x66, 0xec, 0x4a, + 0x44, 0xc3, 0x51, 0xda, 0x36, 0x9c, 0x70, 0xea, 0xeb, 0x7e, 0xeb, 0x09, 0x6d, 0x5c, 0xdc, 0x4d, + 0x6e, 0xf8, 0xaf, 0x49, 0xa0, 0xe5, 0x09, 0x9d, 0xef, 0xc0, 0x55, 0x01, 0x32, 0xcf, 0x0e, 0xf2, + 0x45, 0xe2, 0x14, 0x03, 0x62, 0x5f, 0x11, 0x5b, 0x3b, 0x07, 0xeb, 0xc4, 0x29, 0xba, 0xdf, 0xf3, + 0x6f, 0x97, 0xa2, 0x9e, 0x8c, 0x40, 0x1b, 0x4e, 0x78, 0x37, 0xc6, 0x13, 0x0c, 0x65, 0x97, 0xff, + 0x38, 0x9e, 0xcc, 0x18, 0x26, 0x2b, 0x56, 0x75, 0xa5, 0x60, 0xef, 0xab, 0x42, 0x9a, 0x42, 0x91, + 0x98, 0x56, 0xfd, 0x87, 0xca, 0x0e, 0xcb, 0xd4, 0x51, 0xb2, 0x8f, 0x72, 0x4b, 0xaf, 0x2f, 0xe6, + 0xaa, 0xfa, 0x26, 0x3d, 0xd4, 0x2e, 0xeb, 0xee, 0x15, 0xe3, 0x1d, 0x00, 0xdf, 0x04, 0x5c, 0x82, + 0xee, 0x53, 0xf6, 0xd7, 0x8d, 0xe3, 0xb6, 0x14, 0x87, 0x91, 0x0a, 0xcb, 0x0b, 0x83, 0x5e, 0xf2, + 0x5a, 0x0a, 0x5f, 0xf3, 0x5c, 0x8c, 0x27, 0x00, 0xa8, 0xb5, 0x5b, 0x3f, 0xd0, 0xc7, 0x0f, 0x0c, + 0x50, 0x4b, 0x98, 0x1c, 0x8f, 0xc3, 0x00, 0xb3, 0x19, 0x29, 0xe5, 0x1d, 0xc2, 0x92, 0x97, 0xf9, + 0x6e, 0x3f, 0x5f, 0xd8, 0x26, 0x3c, 0xd6, 0x57, 0x34, 0x99, 0xe0, 0x42, 0x0e, 0x9c, 0x09, 0x89, + 0x67, 0x60, 0xa4, 0xbe, 0xed, 0x14, 0x2a, 0x66, 0x99, 0x25, 0xff, 0xc7, 0x8f, 0x0c, 0x8b, 0xd5, + 0x6d, 0xbe, 0xe8, 0x7e, 0x3f, 0x84, 0xb7, 0x90, 0x64, 0xff, 0x14, 0x9a, 0xef, 0xd7, 0xc4, 0x2f, + 0xfc, 0x18, 0xfe, 0x5f, 0xb5, 0x7c, 0xd7, 0xe5, 0x4d, 0xeb, 0x89, 0x9d, 0x1c, 0xe0, 0xe6, 0x68, + 0xf1, 0x3e, 0xbf, 0x1f, 0x08, 0x79, 0x64, 0x3d, 0xb1, 0xb5, 0x2b, 0xd5, 0x73, 0x2b, 0x99, 0xcf, + 0x47, 0xe1, 0x32, 0xbf, 0x59, 0xfc, 0x29, 0x82, 0x84, 0x37, 0xf0, 0xe1, 0xdb, 0x4d, 0x52, 0x36, + 0x4e, 0x98, 0xd2, 0x42, 0x9c, 0xa3, 0x9e, 0x4d, 0xe4, 0x99, 0x4f, 0x7e, 0xf9, 0xeb, 0xab, 0xde, + 0x49, 0x3c, 0xa1, 0xb6, 0x1a, 0x7c, 0xf1, 0xd7, 0x08, 0x86, 0x43, 0x1d, 0x14, 0x2f, 0xb6, 0x2a, + 0x12, 0x35, 0x87, 0x4a, 0xe9, 0x0e, 0x22, 0x04, 0xba, 0x3b, 0x1c, 0xdd, 0x1c, 0x9e, 0x51, 0x9b, + 0x8e, 0xdc, 0x81, 0x9e, 0x8d, 0x7f, 0x42, 0x30, 0x14, 0x4c, 0x84, 0xd5, 0xb8, 0x25, 0xeb, 0x18, + 0x17, 0xe3, 0x07, 0x08, 0x88, 0xeb, 0x1c, 0x62, 0x16, 0xbf, 0x15, 0x0b, 0xa2, 0xfa, 0x2c, 0xdc, + 0x4a, 0x8f, 0xd4, 0xb3, 0x3d, 0xfc, 0x0d, 0x82, 0x91, 0xf0, 0x4c, 0x85, 0xdb, 0x49, 0xd6, 0xd8, + 0xad, 0xa5, 0x4c, 0x27, 0x21, 0x82, 0x83, 0xc2, 0x39, 0xcc, 0xe3, 0xd9, 0x16, 0x1c, 0x02, 0xed, + 0x15, 0xff, 0x8a, 0x60, 0xbc, 0xc5, 0xab, 0x8a, 0xef, 0xb6, 0xc2, 0xd0, 0x7e, 0x34, 0x90, 0xee, + 0x75, 0x1d, 0x2f, 0x08, 0x2d, 0x73, 0x42, 0x8b, 0x58, 0x89, 0x79, 0x29, 0x5e, 0x77, 0x39, 0xc2, + 0x7f, 0x23, 0x18, 0x6b, 0x3a, 0xb9, 0xe1, 0xd5, 0xb8, 0xe6, 0x88, 0x1a, 0x2b, 0xa5, 0x37, 0xbb, + 0x8c, 0x16, 0x94, 0xb6, 0x38, 0xa5, 0x87, 0xf8, 0xed, 0x2e, 0x7d, 0xc6, 0x67, 0x36, 0x9f, 0xe9, + 0x4b, 0x04, 0xc9, 0x66, 0x93, 0x20, 0x5e, 0x89, 0x0b, 0x35, 0x62, 0x18, 0x95, 0x56, 0xbb, 0x0b, + 0x16, 0x34, 0xd7, 0x38, 0xcd, 0xbb, 0x78, 0xf5, 0xdf, 0xd0, 0xc4, 0xdf, 0x21, 0x18, 0x3d, 0x37, + 0x0e, 0xe1, 0x4c, 0x5b, 0x53, 0x35, 0x8c, 0x56, 0xd2, 0x52, 0x47, 0x31, 0x82, 0x82, 0xca, 0x29, + 0xdc, 0xc6, 0x73, 0x4d, 0x28, 0x90, 0x7a, 0x9c, 0x78, 0xd4, 0xf0, 0x31, 0x82, 0xeb, 0x4d, 0xc6, + 0x1d, 0xfc, 0x46, 0x5c, 0x35, 0x23, 0x5a, 0xc1, 0x4a, 0x57, 0xb1, 0x82, 0xc5, 0x06, 0x67, 0xb1, + 0x86, 0xb3, 0x5d, 0x5e, 0x44, 0xb0, 0x5f, 0xfc, 0xe8, 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, + 0xc3, 0x74, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0xc0, 0x4b, 0x01, 0x98, 0xea, 0xb3, 0x88, 0xf1, 0xeb, + 0x28, 0xfb, 0xee, 0xf3, 0x93, 0x14, 0x7a, 0x71, 0x92, 0x42, 0x7f, 0x9e, 0xa4, 0xd0, 0x17, 0xa7, + 0xa9, 0x9e, 0x17, 0xa7, 0xa9, 0x9e, 0xdf, 0x4f, 0x53, 0x3d, 0x1f, 0xb6, 0x9d, 0x7d, 0x0e, 0x82, + 0x05, 0xf9, 0x20, 0xa4, 0x27, 0xf8, 0xbf, 0x86, 0x96, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x4c, + 0x47, 0x4f, 0x01, 0x02, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1292,10 +1230,8 @@ type QueryClient interface { BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) // BTCValidator info about one validator BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) - // PendingBTCDelegations queries all pending BTC delegations - PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) - // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature - UnbondingBTCDelegations(ctx context.Context, in *QueryUnbondingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryUnbondingBTCDelegationsResponse, error) + // BTCDelegations queries all BTC delegations under a given status + BTCDelegations(ctx context.Context, in *QueryBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height @@ -1346,18 +1282,9 @@ func (c *queryClient) BTCValidator(ctx context.Context, in *QueryBTCValidatorReq return out, nil } -func (c *queryClient) PendingBTCDelegations(ctx context.Context, in *QueryPendingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryPendingBTCDelegationsResponse, error) { - out := new(QueryPendingBTCDelegationsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/PendingBTCDelegations", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryClient) UnbondingBTCDelegations(ctx context.Context, in *QueryUnbondingBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryUnbondingBTCDelegationsResponse, error) { - out := new(QueryUnbondingBTCDelegationsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/UnbondingBTCDelegations", in, out, opts...) +func (c *queryClient) BTCDelegations(ctx context.Context, in *QueryBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCDelegationsResponse, error) { + out := new(QueryBTCDelegationsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCDelegations", in, out, opts...) if err != nil { return nil, err } @@ -1426,10 +1353,8 @@ type QueryServer interface { BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) // BTCValidator info about one validator BTCValidator(context.Context, *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) - // PendingBTCDelegations queries all pending BTC delegations - PendingBTCDelegations(context.Context, *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) - // UnbondingBTCDelegations queries all unbonding BTC delegations which require Jury signature - UnbondingBTCDelegations(context.Context, *QueryUnbondingBTCDelegationsRequest) (*QueryUnbondingBTCDelegationsResponse, error) + // BTCDelegations queries all BTC delegations under a given status + BTCDelegations(context.Context, *QueryBTCDelegationsRequest) (*QueryBTCDelegationsResponse, error) // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height @@ -1458,11 +1383,8 @@ func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBT func (*UnimplementedQueryServer) BTCValidator(ctx context.Context, req *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCValidator not implemented") } -func (*UnimplementedQueryServer) PendingBTCDelegations(ctx context.Context, req *QueryPendingBTCDelegationsRequest) (*QueryPendingBTCDelegationsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PendingBTCDelegations not implemented") -} -func (*UnimplementedQueryServer) UnbondingBTCDelegations(ctx context.Context, req *QueryUnbondingBTCDelegationsRequest) (*QueryUnbondingBTCDelegationsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method UnbondingBTCDelegations not implemented") +func (*UnimplementedQueryServer) BTCDelegations(ctx context.Context, req *QueryBTCDelegationsRequest) (*QueryBTCDelegationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCDelegations not implemented") } func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context, req *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ActiveBTCValidatorsAtHeight not implemented") @@ -1541,38 +1463,20 @@ func _Query_BTCValidator_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _Query_PendingBTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryPendingBTCDelegationsRequest) +func _Query_BTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBTCDelegationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).PendingBTCDelegations(ctx, in) + return srv.(QueryServer).BTCDelegations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/PendingBTCDelegations", + FullMethod: "/babylon.btcstaking.v1.Query/BTCDelegations", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).PendingBTCDelegations(ctx, req.(*QueryPendingBTCDelegationsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Query_UnbondingBTCDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryUnbondingBTCDelegationsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).UnbondingBTCDelegations(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/UnbondingBTCDelegations", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).UnbondingBTCDelegations(ctx, req.(*QueryUnbondingBTCDelegationsRequest)) + return srv.(QueryServer).BTCDelegations(ctx, req.(*QueryBTCDelegationsRequest)) } return interceptor(ctx, in, info, handler) } @@ -1702,12 +1606,8 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_BTCValidator_Handler, }, { - MethodName: "PendingBTCDelegations", - Handler: _Query_PendingBTCDelegations_Handler, - }, - { - MethodName: "UnbondingBTCDelegations", - Handler: _Query_UnbondingBTCDelegations_Handler, + MethodName: "BTCDelegations", + Handler: _Query_BTCDelegations_Handler, }, { MethodName: "ActiveBTCValidatorsAtHeight", @@ -1943,7 +1843,7 @@ func (m *QueryBTCValidatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *QueryPendingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1953,80 +1853,37 @@ func (m *QueryPendingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryPendingBTCDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryBTCDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryPendingBTCDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryBTCDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - return len(dAtA) - i, nil -} - -func (m *QueryPendingBTCDelegationsResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryPendingBTCDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryPendingBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.BtcDelegations) > 0 { - for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.BtcDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } - i-- - dAtA[i] = 0xa + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x12 } - return len(dAtA) - i, nil -} - -func (m *QueryUnbondingBTCDelegationsRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err + if m.Status != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x8 } - return dAtA[:n], nil -} - -func (m *QueryUnbondingBTCDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryUnbondingBTCDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l return len(dAtA) - i, nil } -func (m *QueryUnbondingBTCDelegationsResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryBTCDelegationsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2036,16 +1893,28 @@ func (m *QueryUnbondingBTCDelegationsResponse) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *QueryUnbondingBTCDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryBTCDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryUnbondingBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } if len(m.BtcDelegations) > 0 { for iNdEx := len(m.BtcDelegations) - 1; iNdEx >= 0; iNdEx-- { { @@ -2637,40 +2506,23 @@ func (m *QueryBTCValidatorResponse) Size() (n int) { return n } -func (m *QueryPendingBTCDelegationsRequest) Size() (n int) { +func (m *QueryBTCDelegationsRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - return n -} - -func (m *QueryPendingBTCDelegationsResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.BtcDelegations) > 0 { - for _, e := range m.BtcDelegations { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) - } + if m.Status != 0 { + n += 1 + sovQuery(uint64(m.Status)) } - return n -} - -func (m *QueryUnbondingBTCDelegationsRequest) Size() (n int) { - if m == nil { - return 0 + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) } - var l int - _ = l return n } -func (m *QueryUnbondingBTCDelegationsResponse) Size() (n int) { +func (m *QueryBTCDelegationsResponse) Size() (n int) { if m == nil { return 0 } @@ -2682,6 +2534,10 @@ func (m *QueryUnbondingBTCDelegationsResponse) Size() (n int) { n += 1 + l + sovQuery(uint64(l)) } } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } return n } @@ -3400,7 +3256,7 @@ func (m *QueryBTCValidatorResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryPendingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { +func (m *QueryBTCDelegationsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3423,65 +3279,34 @@ func (m *QueryPendingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryPendingBTCDelegationsRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryBTCDelegationsRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryPendingBTCDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryBTCDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *QueryPendingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= BTCDelegationStatus(b&0x7F) << shift + if b < 0x80 { + break + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryPendingBTCDelegationsResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryPendingBTCDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3508,8 +3333,10 @@ func (m *QueryPendingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) - if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3534,7 +3361,7 @@ func (m *QueryPendingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryUnbondingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { +func (m *QueryBTCDelegationsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3557,65 +3384,49 @@ func (m *QueryUnbondingBTCDelegationsRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryUnbondingBTCDelegationsRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryBTCDelegationsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryUnbondingBTCDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryBTCDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + if msglen < 0 { + return ErrInvalidLengthQuery } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *QueryUnbondingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery } - if iNdEx >= l { + if postIndex > l { return io.ErrUnexpectedEOF } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) + if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryUnbondingBTCDelegationsResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryUnbondingBTCDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + iNdEx = postIndex + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcDelegations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3642,8 +3453,10 @@ func (m *QueryUnbondingBTCDelegationsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcDelegations = append(m.BtcDelegations, &BTCDelegation{}) - if err := m.BtcDelegations[len(m.BtcDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 522bc859d..07aff7dba 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -141,38 +141,38 @@ func local_request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.M } -func request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryPendingBTCDelegationsRequest - var metadata runtime.ServerMetadata - - msg, err := client.PendingBTCDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} +var ( + filter_Query_BTCDelegations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) -func local_request_Query_PendingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryPendingBTCDelegationsRequest +func request_Query_BTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCDelegationsRequest var metadata runtime.ServerMetadata - msg, err := server.PendingBTCDelegations(ctx, &protoReq) - return msg, metadata, err - -} - -func request_Query_UnbondingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryUnbondingBTCDelegationsRequest - var metadata runtime.ServerMetadata + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCDelegations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } - msg, err := client.UnbondingBTCDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.BTCDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_UnbondingBTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryUnbondingBTCDelegationsRequest +func local_request_Query_BTCDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBTCDelegationsRequest var metadata runtime.ServerMetadata - msg, err := server.UnbondingBTCDelegations(ctx, &protoReq) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCDelegations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.BTCDelegations(ctx, &protoReq) return msg, metadata, err } @@ -598,30 +598,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_PendingBTCDelegations_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_PendingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_Query_UnbondingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_BTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -632,7 +609,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_UnbondingBTCDelegations_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_BTCDelegations_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -640,7 +617,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_UnbondingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_BTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -883,7 +860,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_PendingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_BTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -892,34 +869,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_PendingBTCDelegations_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_BTCDelegations_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_PendingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("GET", pattern_Query_UnbondingBTCDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_UnbondingBTCDelegations_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_UnbondingBTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_BTCDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1053,9 +1010,7 @@ var ( pattern_Query_BTCValidator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "validator"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_PendingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "pending_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) - - pattern_Query_UnbondingBTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "unbonding_btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_BTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -1077,9 +1032,7 @@ var ( forward_Query_BTCValidator_0 = runtime.ForwardResponseMessage - forward_Query_PendingBTCDelegations_0 = runtime.ForwardResponseMessage - - forward_Query_UnbondingBTCDelegations_0 = runtime.ForwardResponseMessage + forward_Query_BTCDelegations_0 = runtime.ForwardResponseMessage forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage From eeacd5f2430857f95b663606f57ef3723dc4289f Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 13 Oct 2023 10:36:18 +1100 Subject: [PATCH 087/202] pop: proof of possession with Bitcoin's ECDSA format (#87) --- crypto/ecdsa/README.md | 11 ++++ crypto/ecdsa/ecdsa.go | 40 +++++++++++++ crypto/ecdsa/ecdsa_test.go | 38 ++++++++++++ proto/babylon/btcstaking/v1/pop.proto | 3 + x/btcstaking/keeper/msg_server.go | 22 ++----- x/btcstaking/types/pop.go | 83 +++++++++++++++++++++++++-- x/btcstaking/types/pop.pb.go | 44 ++++++++------ x/btcstaking/types/pop_test.go | 33 +++++++++-- 8 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 crypto/ecdsa/README.md create mode 100644 crypto/ecdsa/ecdsa.go create mode 100644 crypto/ecdsa/ecdsa_test.go diff --git a/crypto/ecdsa/README.md b/crypto/ecdsa/README.md new file mode 100644 index 000000000..6d389b35a --- /dev/null +++ b/crypto/ecdsa/README.md @@ -0,0 +1,11 @@ +# ECDSA + +This module implements Bitcoin's ECDSA signature algorithm over Secp256k1 curve. +It follows the "message sign" format in [Bitcoin](https://github.com/bitcoin/bitcoin/pull/524). +The format is used in [OKX wallet SDK](https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331). + +References: + +- [Original design and implementation](https://github.com/bitcoin/bitcoin/pull/524) +- [An unofficial spec](https://github.com/fivepiece/sign-verify-message/blob/master/signverifymessage.md) +- [Implementation of OKX wallet SDK](https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331) diff --git a/crypto/ecdsa/ecdsa.go b/crypto/ecdsa/ecdsa.go new file mode 100644 index 000000000..ca11b94ee --- /dev/null +++ b/crypto/ecdsa/ecdsa.go @@ -0,0 +1,40 @@ +package ecdsa + +import ( + "bytes" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const ( + MAGIC_MESSAGE_PREFIX = "Bitcoin Signed Message:\n" +) + +// magicHash encodes the given msg into byte array, then calculates its sha256d hash +// ref: https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/message.ts#L28-L34 +func magicHash(msg string) chainhash.Hash { + buf := bytes.NewBuffer(nil) + // we have to use wire.WriteVarString which encodes the string length into the byte array in Bitcoin's own way + // message prefix + // NOTE: we have control over the buffer so no need to check error + wire.WriteVarString(buf, 0, MAGIC_MESSAGE_PREFIX) //nolint:errcheck + // message + wire.WriteVarString(buf, 0, msg) //nolint:errcheck + bytes := buf.Bytes() + + return chainhash.DoubleHashH(bytes) +} + +func Sign(sk *btcec.PrivateKey, msg string) ([]byte, error) { + msgHash := magicHash(msg) + return ecdsa.SignCompact(sk, msgHash[:], true) +} + +func Verify(pk *btcec.PublicKey, msg string, sigBytes []byte) error { + msgHash := magicHash(msg) + _, _, err := ecdsa.RecoverCompact(sigBytes, msgHash[:]) + return err +} diff --git a/crypto/ecdsa/ecdsa_test.go b/crypto/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..836e49f8a --- /dev/null +++ b/crypto/ecdsa/ecdsa_test.go @@ -0,0 +1,38 @@ +package ecdsa_test + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "testing" + + "github.com/babylonchain/babylon/crypto/ecdsa" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +const ( + // test vector from https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/tests/btc.test.ts#L113-L126 + skHex = "adce25dc25ef89f06a722abdc4b601d706c9efc6bc84075355e6b96ca3871621" + testMsg = "hello world" + testSigBase64 = "IDtG3XPLpiKOp4PjTzCo/ng8gm4MFTTyHeh/DaPC1XYsYaj5Jr4h8dnxmwuJtNkPkH40rEfnrrO8fgZKNOIF5iM=" +) + +func TestECDSA(t *testing.T) { + // decode SK and PK + skBytes, err := hex.DecodeString(skHex) + require.NoError(t, err) + sk, pk := btcec.PrivKeyFromBytes(skBytes) + require.NotNil(t, sk) + require.NotNil(t, pk) + // sign + sig, err := ecdsa.Sign(sk, testMsg) + require.NoError(t, err) + testSigBytes, err := base64.StdEncoding.DecodeString(testSigBase64) + require.NoError(t, err) + // ensure sig is same as that in test vector + require.True(t, bytes.Equal(sig, testSigBytes)) + // verify + err = ecdsa.Verify(pk, testMsg, sig) + require.NoError(t, err) +} diff --git a/proto/babylon/btcstaking/v1/pop.proto b/proto/babylon/btcstaking/v1/pop.proto index 00a99bcaa..fa3ae87d8 100644 --- a/proto/babylon/btcstaking/v1/pop.proto +++ b/proto/babylon/btcstaking/v1/pop.proto @@ -9,6 +9,9 @@ enum BTCSigType { BIP340 = 0; // BIP322 means the btc_sig will follow the BIP-322 encoding BIP322 = 1; + // ECDSA means the btc_sig will follow the ECDSA encoding + // ref: https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331 + ECDSA = 2; } // ProofOfPossession is the proof of possession that a Babylon secp256k1 diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 8ec661805..e8709c9c0 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -77,14 +77,9 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // ensure the validator address does not exist before ctx := sdk.UnwrapSDKContext(goCtx) - if req.Pop.BtcSigType == types.BTCSigType_BIP322 { - if err := req.Pop.VerifyBIP322(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-322 encoding: %v", err) - } - } else if req.Pop.BtcSigType == types.BTCSigType_BIP340 { - if err := req.Pop.Verify(req.BabylonPk, req.BtcPk); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-340 encoding: %v", err) - } + // verify proof of possession + if err := req.Pop.Verify(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession: %v", err) } // ensure commission rate is at least the minimum commission rate in parameters @@ -132,14 +127,9 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) juryPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.JuryKey) - if req.Pop.BtcSigType == types.BTCSigType_BIP322 { - if err := req.Pop.VerifyBIP322(req.BabylonPk, delBTCPK, ms.btcNet); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-322 encoding: %v", err) - } - } else if req.Pop.BtcSigType == types.BTCSigType_BIP340 { - if err := req.Pop.Verify(req.BabylonPk, delBTCPK); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession in BIP-340 encoding: %v", err) - } + // verify proof of possession + if err := req.Pop.Verify(req.BabylonPk, delBTCPK, ms.btcNet); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession: %v", err) } // extract staking tx and its hash diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index a028814fc..f8dc529d4 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/babylonchain/babylon/crypto/bip322" + "github.com/babylonchain/babylon/crypto/ecdsa" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -16,7 +17,7 @@ import ( // NewPoP generates a new proof of possession that sk_Babylon and sk_BTC are held by the same person // a proof of possession contains two signatures: // - pop.BabylonSig = sign(sk_Babylon, pk_BTC) -// - pop.BtcSig = sign(sk_BTC, pop.BabylonSig) +// - pop.BtcSig = schnorr_sign(sk_BTC, pop.BabylonSig) func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPossession, error) { pop := ProofOfPossession{ BtcSigType: BTCSigType_BIP340, // by default, we use BIP-340 encoding for BTC signature @@ -31,7 +32,7 @@ func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPos } pop.BabylonSig = babylonSig - // generate pop.BtcSig = sign(sk_BTC, pop.BabylonSig) + // generate pop.BtcSig = schnorr_sign(sk_BTC, pop.BabylonSig) // NOTE: *schnorr.Sign has to take the hash of the message. // So we have to hash babylonSig before signing babylonSigHash := tmhash.Sum(pop.BabylonSig) @@ -45,6 +46,37 @@ func NewPoP(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPos return &pop, nil } +// NewPoPWithECDSABTCSig generates a new proof of possession where Bitcoin signature is in ECDSA format +// a proof of possession contains two signatures: +// - pop.BabylonSig = sign(sk_Babylon, pk_BTC) +// - pop.BtcSig = ecdsa_sign(sk_BTC, pop.BabylonSig) +func NewPoPWithECDSABTCSig(babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) (*ProofOfPossession, error) { + pop := ProofOfPossession{ + BtcSigType: BTCSigType_ECDSA, + } + + // generate pop.BabylonSig = sign(sk_Babylon, pk_BTC) + btcPK := btcSK.PubKey() + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + babylonSig, err := babylonSK.Sign(*bip340PK) + if err != nil { + return nil, err + } + pop.BabylonSig = babylonSig + + // generate pop.BtcSig = ecdsa_sign(sk_BTC, pop.BabylonSig) + // NOTE: ecdsa.Sign has to take the message as string. + // So we have to hex babylonSig before signing + babylonSigHex := hex.EncodeToString(pop.BabylonSig) + btcSig, err := ecdsa.Sign(btcSK, babylonSigHex) + if err != nil { + return nil, err + } + pop.BtcSig = btcSig + + return &pop, nil +} + func NewPoPFromHex(popHex string) (*ProofOfPossession, error) { popBytes, err := hex.DecodeString(popHex) if err != nil { @@ -65,10 +97,23 @@ func (pop *ProofOfPossession) ToHexStr() (string, error) { return hex.EncodeToString(popBytes), nil } -// Verify verifies the validity of PoP where Bitcoin signature is in BIP-340 +func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey, net *chaincfg.Params) error { + switch pop.BtcSigType { + case BTCSigType_BIP340: + return pop.VerifyBIP340(babylonPK, bip340PK) + case BTCSigType_BIP322: + return pop.VerifyBIP322(babylonPK, bip340PK, net) + case BTCSigType_ECDSA: + return pop.VerifyECDSA(babylonPK, bip340PK) + default: + return fmt.Errorf("invalid BTC signature type") + } +} + +// VerifyBIP340 verifies the validity of PoP where Bitcoin signature is in BIP-340 // 1. verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? // 2. verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? -func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey) error { +func (pop *ProofOfPossession) VerifyBIP340(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey) error { if pop.BtcSigType != BTCSigType_BIP340 { return fmt.Errorf("the Bitcoin signature in this proof of possession is not using BIP-340 encoding") } @@ -90,7 +135,7 @@ func (pop *ProofOfPossession) Verify(babylonPK cryptotypes.PubKey, bip340PK *bbn // So we have to hash babylonSig before verifying the signature babylonSigHash := tmhash.Sum(pop.BabylonSig) if !btcSig.Verify(babylonSigHash, btcPK) { - return fmt.Errorf("failed to verify babylonSig") + return fmt.Errorf("failed to verify pop.BtcSig") } // rule 2: verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? @@ -138,6 +183,34 @@ func (pop *ProofOfPossession) VerifyBIP322(babylonPK cryptotypes.PubKey, bip340P return nil } +// VerifyECDSA verifies the validity of PoP where Bitcoin signature is in ECDSA encoding +// 1. verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? +// 2. verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? +func (pop *ProofOfPossession) VerifyECDSA(babylonPK cryptotypes.PubKey, bip340PK *bbn.BIP340PubKey) error { + if pop.BtcSigType != BTCSigType_ECDSA { + return fmt.Errorf("the Bitcoin signature in this proof of possession is not using ECDSA encoding") + } + + // rule 1: verify(sig=sig_btc, pubkey=pk_btc, msg=pop.BabylonSig)? + btcPK, err := bip340PK.ToBTCPK() + if err != nil { + return err + } + // NOTE: ecdsa.Verify has to take message as a string + // So we have to hex BabylonSig before verifying the signature + bbnSigHex := hex.EncodeToString(pop.BabylonSig) + if err := ecdsa.Verify(btcPK, bbnSigHex, pop.BtcSig); err != nil { + return fmt.Errorf("failed to verify pop.BtcSig") + } + + // rule 2: verify(sig=pop.BabylonSig, pubkey=pk_babylon, msg=pk_btc)? + if !babylonPK.VerifySignature(*bip340PK, pop.BabylonSig) { + return fmt.Errorf("failed to verify pop.BabylonSig") + } + + return nil +} + func (p *ProofOfPossession) ValidateBasic() error { if len(p.BabylonSig) == 0 { return fmt.Errorf("empty Babylon signature") diff --git a/x/btcstaking/types/pop.pb.go b/x/btcstaking/types/pop.pb.go index dace92982..672a3f88a 100644 --- a/x/btcstaking/types/pop.pb.go +++ b/x/btcstaking/types/pop.pb.go @@ -30,16 +30,21 @@ const ( BTCSigType_BIP340 BTCSigType = 0 // BIP322 means the btc_sig will follow the BIP-322 encoding BTCSigType_BIP322 BTCSigType = 1 + // ECDSA means the btc_sig will follow the ECDSA encoding + // ref: https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331 + BTCSigType_ECDSA BTCSigType = 2 ) var BTCSigType_name = map[int32]string{ 0: "BIP340", 1: "BIP322", + 2: "ECDSA", } var BTCSigType_value = map[string]int32{ "BIP340": 0, "BIP322": 1, + "ECDSA": 2, } func (x BTCSigType) String() string { @@ -182,25 +187,26 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/pop.proto", fileDescriptor_9d6ceb088d9e9f3a) } var fileDescriptor_9d6ceb088d9e9f3a = []byte{ - // 288 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4b, 0xc3, 0x30, - 0x1c, 0xc5, 0x1b, 0x07, 0x1b, 0xfb, 0x3b, 0x64, 0x06, 0xc4, 0x9e, 0xb2, 0x39, 0x3c, 0x0c, 0x0f, - 0xa9, 0xcb, 0x04, 0xef, 0xdd, 0x49, 0x10, 0x2c, 0xdd, 0x4e, 0x5e, 0x46, 0xd3, 0x75, 0x5d, 0x50, - 0x9b, 0xd2, 0xc4, 0x61, 0xbf, 0x85, 0xf8, 0xa9, 0x3c, 0xee, 0xe8, 0x51, 0xda, 0x2f, 0x22, 0xad, - 0x29, 0xf5, 0xe0, 0xed, 0xbd, 0xe4, 0xe5, 0x97, 0xc7, 0x83, 0x11, 0x0f, 0x78, 0xfe, 0x2c, 0x13, - 0x87, 0xeb, 0x50, 0xe9, 0xe0, 0x49, 0x24, 0xb1, 0xb3, 0x9f, 0x39, 0xa9, 0x4c, 0x69, 0x9a, 0x49, - 0x2d, 0xf1, 0x99, 0x09, 0xd0, 0x36, 0x40, 0xf7, 0xb3, 0xc9, 0x07, 0x82, 0x53, 0x2f, 0x93, 0x72, - 0xfb, 0xb0, 0xf5, 0xa4, 0x52, 0x91, 0x52, 0x42, 0x26, 0x78, 0x01, 0x03, 0xae, 0xc3, 0xb5, 0x12, - 0xf1, 0x5a, 0xe7, 0x69, 0x64, 0xa3, 0x31, 0x9a, 0x9e, 0xb0, 0x0b, 0xfa, 0x2f, 0x83, 0xba, 0xab, - 0xc5, 0x52, 0xc4, 0xab, 0x3c, 0x8d, 0x7c, 0xe0, 0x3a, 0x34, 0x1a, 0x8f, 0xe0, 0xd8, 0xe4, 0x2b, - 0x90, 0x7d, 0x34, 0x46, 0xd3, 0x81, 0x0f, 0xe6, 0x68, 0x29, 0x62, 0x7c, 0x0e, 0x3d, 0xf3, 0x8b, - 0xdd, 0xa9, 0x2f, 0xbb, 0xbf, 0xaf, 0x27, 0xb7, 0xd0, 0x77, 0xef, 0xbc, 0x39, 0x63, 0x55, 0xca, - 0x86, 0x5e, 0xb0, 0xd9, 0x64, 0x91, 0x52, 0x75, 0x8d, 0xbe, 0xdf, 0x58, 0x3c, 0x84, 0x4e, 0x0b, - 0xae, 0xe4, 0xd5, 0x25, 0x40, 0x5b, 0x06, 0x03, 0x74, 0x2b, 0xcc, 0xcd, 0xf5, 0xd0, 0x6a, 0x34, - 0x63, 0x43, 0xe4, 0xde, 0x7f, 0x16, 0x04, 0x1d, 0x0a, 0x82, 0xbe, 0x0b, 0x82, 0xde, 0x4b, 0x62, - 0x1d, 0x4a, 0x62, 0x7d, 0x95, 0xc4, 0x7a, 0x64, 0xb1, 0xd0, 0xbb, 0x57, 0x4e, 0x43, 0xf9, 0xe2, - 0x98, 0xa2, 0xe1, 0x2e, 0x10, 0x49, 0x63, 0x9c, 0xb7, 0xbf, 0xfb, 0x56, 0xd3, 0x28, 0xde, 0xad, - 0xf7, 0x9d, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xc5, 0x76, 0x11, 0x82, 0x01, 0x00, 0x00, + // 298 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4f, 0xc2, 0x30, + 0x1c, 0xc5, 0x57, 0x88, 0x10, 0xfe, 0x12, 0x33, 0x9b, 0x18, 0x77, 0x2a, 0xc8, 0x89, 0x78, 0x68, + 0xa5, 0x98, 0x78, 0x16, 0xf4, 0x60, 0x62, 0x22, 0x01, 0x4e, 0x5e, 0xc8, 0x3a, 0xc6, 0x68, 0xd4, + 0x75, 0xa1, 0x95, 0xc8, 0xb7, 0x30, 0x7e, 0x2a, 0x8f, 0x1c, 0x3d, 0x9a, 0xed, 0x8b, 0x98, 0xcd, + 0x2e, 0xf3, 0xe0, 0xed, 0xbd, 0xf6, 0xf5, 0xd7, 0x97, 0x07, 0x1d, 0xe1, 0x8b, 0xdd, 0xb3, 0x8a, + 0x99, 0x30, 0x81, 0x36, 0xfe, 0x93, 0x8c, 0x23, 0xb6, 0x1d, 0xb0, 0x44, 0x25, 0x34, 0xd9, 0x28, + 0xa3, 0xf0, 0x89, 0x0d, 0xd0, 0x2a, 0x40, 0xb7, 0x83, 0xde, 0x07, 0x82, 0xe3, 0xc9, 0x46, 0xa9, + 0xd5, 0xc3, 0x6a, 0xa2, 0xb4, 0x0e, 0xb5, 0x96, 0x2a, 0xc6, 0x63, 0x68, 0x0b, 0x13, 0x2c, 0xb4, + 0x8c, 0x16, 0x66, 0x97, 0x84, 0x1e, 0xea, 0xa2, 0xfe, 0x11, 0x3f, 0xa3, 0xff, 0x32, 0xe8, 0x68, + 0x3e, 0x9e, 0xc9, 0x68, 0xbe, 0x4b, 0xc2, 0x29, 0x08, 0x13, 0x58, 0x8d, 0x3b, 0x70, 0x68, 0xf3, + 0x39, 0xc8, 0xab, 0x75, 0x51, 0xbf, 0x3d, 0x05, 0x7b, 0x34, 0x93, 0x11, 0x3e, 0x85, 0xa6, 0xfd, + 0xc5, 0xab, 0x17, 0x97, 0x8d, 0xdf, 0xd7, 0xbd, 0x2b, 0x68, 0x8d, 0xee, 0x26, 0x43, 0xce, 0xf3, + 0x94, 0x07, 0x4d, 0x7f, 0xb9, 0xdc, 0x84, 0x5a, 0x17, 0x35, 0x5a, 0xd3, 0xd2, 0x62, 0x17, 0xea, + 0x15, 0x38, 0x97, 0xe7, 0x0c, 0xa0, 0x2a, 0x83, 0x01, 0x1a, 0x39, 0xe6, 0xf2, 0xc2, 0x75, 0x4a, + 0xcd, 0xb9, 0x8b, 0x70, 0x0b, 0x0e, 0x6e, 0xc7, 0x37, 0xb3, 0x6b, 0xb7, 0x36, 0xba, 0xff, 0x4c, + 0x09, 0xda, 0xa7, 0x04, 0x7d, 0xa7, 0x04, 0xbd, 0x67, 0xc4, 0xd9, 0x67, 0xc4, 0xf9, 0xca, 0x88, + 0xf3, 0xc8, 0x23, 0x69, 0xd6, 0xaf, 0x82, 0x06, 0xea, 0x85, 0xd9, 0xce, 0xc1, 0xda, 0x97, 0x71, + 0x69, 0xd8, 0xdb, 0xdf, 0xa9, 0xf3, 0x95, 0xb4, 0x68, 0x14, 0x53, 0x0f, 0x7f, 0x02, 0x00, 0x00, + 0xff, 0xff, 0x89, 0x18, 0xe3, 0x67, 0x8d, 0x01, 0x00, 0x00, } func (m *ProofOfPossession) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/pop_test.go b/x/btcstaking/types/pop_test.go index 97c2b7467..f226b87a5 100644 --- a/x/btcstaking/types/pop_test.go +++ b/x/btcstaking/types/pop_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func newInvalidPoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) *types.ProofOfPossession { +func newInvalidBIP340PoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.PrivateKey) *types.ProofOfPossession { pop := types.ProofOfPossession{} randomNum := datagen.RandomInt(r, 2) // 0 or 1 @@ -45,7 +45,7 @@ func newInvalidPoP(r *rand.Rand, babylonSK cryptotypes.PrivKey, btcSK *btcec.Pri return &pop } -func FuzzPoP(f *testing.F) { +func FuzzPoP_BIP340(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -63,14 +63,37 @@ func FuzzPoP(f *testing.F) { // generate and verify PoP, correct case pop, err := types.NewPoP(babylonSK, btcSK) require.NoError(t, err) - err = pop.Verify(babylonPK, bip340PK) + err = pop.VerifyBIP340(babylonPK, bip340PK) require.NoError(t, err) // generate and verify PoP, invalid case - invalidPoP := newInvalidPoP(r, babylonSK, btcSK) - err = invalidPoP.Verify(babylonPK, bip340PK) + invalidPoP := newInvalidBIP340PoP(r, babylonSK, btcSK) + err = invalidPoP.VerifyBIP340(babylonPK, bip340PK) require.Error(t, err) }) } // TODO: fuzz test for BIP322 PoP + +func FuzzPoP_ECDSA(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // generate BTC key pair + btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + + // generate Babylon key pair + babylonSK, babylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) + require.NoError(t, err) + + // generate and verify PoP, correct case + pop, err := types.NewPoPWithECDSABTCSig(babylonSK, btcSK) + require.NoError(t, err) + err = pop.VerifyECDSA(babylonPK, bip340PK) + require.NoError(t, err) + }) +} From 41bc3201748c0ce815dd3650f612d726dfd20a12 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 17 Oct 2023 20:46:32 +1100 Subject: [PATCH 088/202] chore: verify PK consistency in ECDSA PoP (#89) --- crypto/ecdsa/ecdsa.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crypto/ecdsa/ecdsa.go b/crypto/ecdsa/ecdsa.go index ca11b94ee..fed6dc1fb 100644 --- a/crypto/ecdsa/ecdsa.go +++ b/crypto/ecdsa/ecdsa.go @@ -2,9 +2,11 @@ package ecdsa import ( "bytes" + "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -35,6 +37,14 @@ func Sign(sk *btcec.PrivateKey, msg string) ([]byte, error) { func Verify(pk *btcec.PublicKey, msg string, sigBytes []byte) error { msgHash := magicHash(msg) - _, _, err := ecdsa.RecoverCompact(sigBytes, msgHash[:]) - return err + recoveredPK, _, err := ecdsa.RecoverCompact(sigBytes, msgHash[:]) + if err != nil { + return err + } + pkBytes := schnorr.SerializePubKey(pk) + recoveredPKBytes := schnorr.SerializePubKey(recoveredPK) + if !bytes.Equal(pkBytes, recoveredPKBytes) { + return fmt.Errorf("the recovered PK does not match the given PK") + } + return nil } From 8f9d049865157239e499ff12cc870dbd07ac4e9b Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 19 Oct 2023 15:10:15 +1100 Subject: [PATCH 089/202] chore: moving voting power update to `BeginBlock` (#90) --- test/e2e/btc_staking_e2e_test.go | 4 ++++ x/btcstaking/abci.go | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 4bddaf411..41f027c6e 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -202,6 +202,10 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { activeDel := activeDels.Dels[0] s.NotNil(activeDel.JurySig) + // wait for a block so that above txs take effect and the voting power table + // is updated in the next block's BeginBlock + nonValidatorNode.WaitForNextBlock() + // ensure BTC staking is activated activatedHeight := nonValidatorNode.QueryActivatedHeight() s.Positive(activatedHeight) diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index eaf61365f..eb54889d4 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -12,10 +12,6 @@ import ( func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) -} - -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { - defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) // index BTC height at the current height k.IndexBTCHeight(ctx) @@ -28,5 +24,10 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { k.RecordRewardDistCache(ctx) } +} + +func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + return []abci.ValidatorUpdate{} } From fe135083e700e7905443b79efc6cb75a848fb42e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 25 Oct 2023 12:47:13 +1100 Subject: [PATCH 090/202] btcstaking: returning last commit hash in validator voting power queries (#91) --- proto/babylon/btcstaking/v1/query.proto | 10 +- x/btcstaking/keeper/grpc_query.go | 11 +- x/btcstaking/keeper/grpc_query_test.go | 17 +- x/btcstaking/types/query.pb.go | 280 +++++++++++++++++------- 4 files changed, 228 insertions(+), 90 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index ae84a4e71..8f754476f 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -137,8 +137,11 @@ message QueryBTCValidatorPowerAtHeightRequest { // QueryBTCValidatorPowerAtHeightResponse is the response type for the // Query/BTCValidatorPowerAtHeight RPC method. message QueryBTCValidatorPowerAtHeightResponse { + // last_commit_hash is the last commit hash of the block at the given height + // it will be used for the BTC validator program to generate finality signatures + bytes last_commit_hash = 1; // voting_power is the voting power of the BTC validator - uint64 voting_power = 1; + uint64 voting_power = 2; } // QueryBTCValidatorCurrentPowerRequest is the request type for the @@ -155,8 +158,11 @@ message QueryBTCValidatorCurrentPowerRequest { message QueryBTCValidatorCurrentPowerResponse { // height is the current height uint64 height = 1; + // last_commit_hash is the last commit hash of the block at the current height + // it will be used for the BTC validator program to generate finality signatures + bytes last_commit_hash = 2; // voting_power is the voting power of the BTC validator - uint64 voting_power = 2; + uint64 voting_power = 3; } // QueryActiveBTCValidatorsAtHeightRequest is the request type for the diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index d19324e23..9f4addd16 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -124,7 +124,10 @@ func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryB sdkCtx := sdk.UnwrapSDKContext(ctx) power := k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), req.Height) - return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil + return &types.QueryBTCValidatorPowerAtHeightResponse{ + LastCommitHash: sdkCtx.BlockHeader().LastCommitHash, + VotingPower: power, + }, nil } // BTCValidatorCurrentPower returns the voting power of the specified validator @@ -154,7 +157,11 @@ func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBT power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) } - return &types.QueryBTCValidatorCurrentPowerResponse{Height: curHeight, VotingPower: power}, nil + return &types.QueryBTCValidatorCurrentPowerResponse{ + Height: curHeight, + LastCommitHash: sdkCtx.BlockHeader().LastCommitHash, + VotingPower: power, + }, nil } // ActiveBTCValidatorsAtHeight returns the active BTC validators at the provided height diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 26e5843df..d6ff10b54 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -11,6 +11,7 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/golang/mock/gomock" @@ -352,8 +353,14 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { require.NoError(t, err) // add this BTC validator keeper.SetBTCValidator(ctx, btcVal) - // set random voting power at random height + // set block header with random last commit hash and height randomHeight := datagen.RandomInt(r, 100) + 1 + randomLch := datagen.GenRandomByteArray(r, 32) + ctx = ctx.WithBlockHeader(tmproto.Header{ + Height: int64(randomHeight), + LastCommitHash: randomLch, + }) + // set random voting power at random height randomPower := datagen.RandomInt(r, 100) + 1 keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) @@ -364,6 +371,7 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { resp, err := keeper.BTCValidatorPowerAtHeight(ctx, req) require.NoError(t, err) require.Equal(t, randomPower, resp.VotingPower) + require.Equal(t, randomLch, resp.LastCommitHash) }) } @@ -382,7 +390,11 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { keeper.SetBTCValidator(ctx, btcVal) // set random voting power at random height randomHeight := datagen.RandomInt(r, 100) + 1 - ctx = ctx.WithBlockHeight(int64(randomHeight)) + randomLch := datagen.GenRandomByteArray(r, 32) + ctx = ctx.WithBlockHeader(tmproto.Header{ + Height: int64(randomHeight), + LastCommitHash: randomLch, + }) randomPower := datagen.RandomInt(r, 100) + 1 keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) @@ -393,6 +405,7 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { resp, err := keeper.BTCValidatorCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight, resp.Height) + require.Equal(t, randomLch, resp.LastCommitHash) require.Equal(t, randomPower, resp.VotingPower) // if height increments but voting power hasn't recorded yet, then diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index c73571379..bbfda1daa 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -482,8 +482,11 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) GetHeight() uint64 { // QueryBTCValidatorPowerAtHeightResponse is the response type for the // Query/BTCValidatorPowerAtHeight RPC method. type QueryBTCValidatorPowerAtHeightResponse struct { + // last_commit_hash is the last commit hash of the block at the given height + // it will be used for the BTC validator program to generate finality signatures + LastCommitHash []byte `protobuf:"bytes,1,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` // voting_power is the voting power of the BTC validator - VotingPower uint64 `protobuf:"varint,1,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { @@ -521,6 +524,13 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse proto.InternalMessageInfo +func (m *QueryBTCValidatorPowerAtHeightResponse) GetLastCommitHash() []byte { + if m != nil { + return m.LastCommitHash + } + return nil +} + func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower @@ -582,8 +592,11 @@ func (m *QueryBTCValidatorCurrentPowerRequest) GetValBtcPkHex() string { type QueryBTCValidatorCurrentPowerResponse struct { // height is the current height Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // last_commit_hash is the last commit hash of the block at the current height + // it will be used for the BTC validator program to generate finality signatures + LastCommitHash []byte `protobuf:"bytes,2,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` // voting_power is the voting power of the BTC validator - VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + VotingPower uint64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCValidatorCurrentPowerResponse{} } @@ -626,6 +639,13 @@ func (m *QueryBTCValidatorCurrentPowerResponse) GetHeight() uint64 { return 0 } +func (m *QueryBTCValidatorCurrentPowerResponse) GetLastCommitHash() []byte { + if m != nil { + return m.LastCommitHash + } + return nil +} + func (m *QueryBTCValidatorCurrentPowerResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower @@ -1130,86 +1150,88 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1255 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc5, - 0x1b, 0xce, 0xa4, 0xa9, 0x7f, 0xc9, 0x9b, 0x7f, 0xfd, 0x4d, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, - 0x9a, 0x3f, 0x0d, 0xea, 0x6e, 0xec, 0xa0, 0x1c, 0x48, 0x68, 0xa9, 0x1b, 0x68, 0x9a, 0x10, 0xc9, - 0x6c, 0x02, 0x95, 0xb8, 0x58, 0xb3, 0xce, 0x74, 0xbd, 0xc4, 0xd9, 0x75, 0xbd, 0x63, 0x93, 0xa8, - 0xca, 0x85, 0x03, 0x12, 0xe2, 0x82, 0xe0, 0x23, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0xf0, 0x01, - 0xe8, 0xb1, 0x12, 0x12, 0x20, 0x2a, 0x45, 0x28, 0x41, 0xe2, 0x6b, 0xa0, 0x9d, 0x1d, 0x67, 0x77, - 0xe3, 0xb5, 0xbd, 0x36, 0xe1, 0x16, 0xcf, 0xcc, 0xfb, 0xbe, 0xcf, 0xf3, 0xcc, 0xb3, 0xef, 0xbc, - 0x0a, 0xdc, 0xd4, 0x89, 0x7e, 0x58, 0xb2, 0x2d, 0x55, 0x67, 0x05, 0x87, 0x91, 0x3d, 0xd3, 0x32, - 0xd4, 0x5a, 0x5a, 0x7d, 0x5a, 0xa5, 0x95, 0x43, 0xa5, 0x5c, 0xb1, 0x99, 0x8d, 0x5f, 0x11, 0x47, - 0x14, 0xff, 0x88, 0x52, 0x4b, 0x4b, 0xd7, 0x0c, 0xdb, 0xb0, 0xf9, 0x09, 0xd5, 0xfd, 0xcb, 0x3b, - 0x2c, 0xdd, 0x30, 0x6c, 0xdb, 0x28, 0x51, 0x95, 0x94, 0x4d, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, - 0x6d, 0xcb, 0x11, 0xbb, 0x0b, 0x05, 0xdb, 0xd9, 0xb7, 0x1d, 0x55, 0x27, 0x0e, 0xf5, 0x6a, 0xa8, - 0xb5, 0xb4, 0x4e, 0x19, 0x49, 0xab, 0x65, 0x62, 0x98, 0x16, 0x3f, 0x2c, 0xce, 0xca, 0xd1, 0xc8, - 0xca, 0xa4, 0x42, 0xf6, 0xeb, 0xf9, 0x66, 0xa3, 0xcf, 0x04, 0x80, 0xf2, 0x73, 0xf2, 0x35, 0xc0, - 0xef, 0xb9, 0xd5, 0x72, 0x3c, 0x58, 0xa3, 0x4f, 0xab, 0xd4, 0x61, 0xb2, 0x06, 0x57, 0x43, 0xab, - 0x4e, 0xd9, 0xb6, 0x1c, 0x8a, 0x57, 0x20, 0xe1, 0x15, 0x49, 0xa2, 0x29, 0x34, 0x3f, 0x98, 0x99, - 0x50, 0x22, 0x05, 0x50, 0xbc, 0xb0, 0x6c, 0xdf, 0xf3, 0xe3, 0xc9, 0x1e, 0x4d, 0x84, 0xc8, 0x05, - 0x18, 0xe3, 0x39, 0xb3, 0x3b, 0x0f, 0x3e, 0x20, 0x25, 0x73, 0x97, 0x30, 0xbb, 0x52, 0x2f, 0x88, - 0xdf, 0x01, 0xf0, 0x69, 0x8a, 0xec, 0xb3, 0x8a, 0xa7, 0x89, 0xe2, 0x6a, 0xa2, 0x78, 0xba, 0x0b, - 0x4d, 0x94, 0x1c, 0x31, 0xa8, 0x88, 0xd5, 0x02, 0x91, 0xf2, 0xf7, 0x08, 0xa4, 0xa8, 0x2a, 0x82, - 0xc0, 0x06, 0x8c, 0xe8, 0xac, 0x90, 0xaf, 0x9d, 0xed, 0x24, 0xd1, 0xd4, 0xa5, 0xf9, 0xc1, 0xcc, - 0x74, 0x13, 0x22, 0xc1, 0x2c, 0xda, 0xb0, 0xce, 0x0a, 0x7e, 0x4e, 0xfc, 0x30, 0x04, 0xb9, 0x97, - 0x43, 0x9e, 0x6b, 0x0b, 0xd9, 0x03, 0x12, 0xc2, 0x7c, 0x0f, 0x92, 0x0d, 0x90, 0xeb, 0xba, 0x4c, - 0xc3, 0x48, 0x8d, 0x94, 0xf2, 0x2e, 0xe8, 0xf2, 0x5e, 0xbe, 0x48, 0x0f, 0xb8, 0x36, 0x03, 0xda, - 0x60, 0x8d, 0x94, 0xb2, 0xac, 0x90, 0xdb, 0x5b, 0xa7, 0x07, 0x32, 0x8d, 0x50, 0xf6, 0x8c, 0xf2, - 0x3a, 0x0c, 0x87, 0x28, 0x0b, 0x71, 0x63, 0x31, 0x1e, 0x0a, 0x32, 0x96, 0xbf, 0x0d, 0x68, 0xbb, - 0x46, 0x4b, 0xd4, 0xf0, 0x0c, 0x5c, 0x87, 0x9a, 0x85, 0x84, 0xc3, 0x08, 0xab, 0x7a, 0xe6, 0x18, - 0xc9, 0x2c, 0x34, 0xaf, 0xe0, 0x47, 0x6f, 0xf3, 0x08, 0x4d, 0x44, 0x9e, 0xb3, 0x41, 0x6f, 0xd7, - 0x36, 0xf8, 0x01, 0xc1, 0x78, 0x24, 0x54, 0x21, 0xca, 0x16, 0x8c, 0xba, 0xa2, 0xec, 0xfa, 0x5b, - 0xc2, 0x08, 0xb7, 0xe2, 0x80, 0xd6, 0x5c, 0x13, 0x05, 0xd2, 0x5e, 0x9c, 0x15, 0x76, 0x61, 0xa6, - 0xe1, 0x26, 0x73, 0xf6, 0xc7, 0xb4, 0x72, 0x9f, 0xad, 0x53, 0xd3, 0x28, 0xb2, 0x4e, 0x7c, 0x81, - 0x5f, 0x85, 0x44, 0x91, 0x47, 0x71, 0x48, 0x7d, 0x9a, 0xf8, 0x25, 0x6f, 0xc2, 0x6c, 0xbb, 0x2a, - 0x42, 0xa7, 0x9b, 0x30, 0x54, 0xb3, 0x99, 0x69, 0x19, 0xf9, 0xb2, 0xbb, 0xcf, 0x8b, 0xf4, 0x69, - 0x83, 0xde, 0x1a, 0x0f, 0x91, 0x37, 0xe1, 0x56, 0x43, 0xb2, 0x07, 0xd5, 0x4a, 0x85, 0x5a, 0x8c, - 0x1f, 0xe8, 0xc8, 0xc9, 0x7a, 0x04, 0xff, 0x70, 0x32, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x1a, - 0x00, 0xf7, 0x36, 0x02, 0xfe, 0x0c, 0xc1, 0x1c, 0x2f, 0x72, 0xbf, 0xc0, 0xcc, 0x1a, 0x0d, 0x35, - 0x8a, 0xf3, 0x32, 0x37, 0x2b, 0x73, 0x51, 0x3e, 0xfd, 0x19, 0xc1, 0x7c, 0x7b, 0x2c, 0x82, 0xb3, - 0xd6, 0xa4, 0x79, 0xbd, 0x16, 0xe3, 0x53, 0x7e, 0x6c, 0xb2, 0xe2, 0x16, 0x65, 0xe4, 0x3f, 0x6b, - 0x62, 0x13, 0xe2, 0x83, 0xe3, 0x44, 0x08, 0xa3, 0xbb, 0x21, 0x21, 0xe5, 0x65, 0xb8, 0x11, 0xbd, - 0xdd, 0xfa, 0x3e, 0xe5, 0x2f, 0x11, 0x4c, 0x37, 0x38, 0x22, 0xa2, 0xf9, 0xc4, 0xfa, 0x1e, 0x2e, - 0xea, 0xd6, 0x5e, 0xa2, 0x08, 0xcf, 0x47, 0xb5, 0x99, 0x8f, 0x60, 0x2c, 0xd0, 0x66, 0xec, 0x4a, - 0x44, 0xc3, 0x51, 0xda, 0x36, 0x9c, 0x70, 0xea, 0xeb, 0x7e, 0xeb, 0x09, 0x6d, 0x5c, 0xdc, 0x4d, - 0x6e, 0xf8, 0xaf, 0x49, 0xa0, 0xe5, 0x09, 0x9d, 0xef, 0xc0, 0x55, 0x01, 0x32, 0xcf, 0x0e, 0xf2, - 0x45, 0xe2, 0x14, 0x03, 0x62, 0x5f, 0x11, 0x5b, 0x3b, 0x07, 0xeb, 0xc4, 0x29, 0xba, 0xdf, 0xf3, - 0x6f, 0x97, 0xa2, 0x9e, 0x8c, 0x40, 0x1b, 0x4e, 0x78, 0x37, 0xc6, 0x13, 0x0c, 0x65, 0x97, 0xff, - 0x38, 0x9e, 0xcc, 0x18, 0x26, 0x2b, 0x56, 0x75, 0xa5, 0x60, 0xef, 0xab, 0x42, 0x9a, 0x42, 0x91, - 0x98, 0x56, 0xfd, 0x87, 0xca, 0x0e, 0xcb, 0xd4, 0x51, 0xb2, 0x8f, 0x72, 0x4b, 0xaf, 0x2f, 0xe6, - 0xaa, 0xfa, 0x26, 0x3d, 0xd4, 0x2e, 0xeb, 0xee, 0x15, 0xe3, 0x1d, 0x00, 0xdf, 0x04, 0x5c, 0x82, - 0xee, 0x53, 0xf6, 0xd7, 0x8d, 0xe3, 0xb6, 0x14, 0x87, 0x91, 0x0a, 0xcb, 0x0b, 0x83, 0x5e, 0xf2, - 0x5a, 0x0a, 0x5f, 0xf3, 0x5c, 0x8c, 0x27, 0x00, 0xa8, 0xb5, 0x5b, 0x3f, 0xd0, 0xc7, 0x0f, 0x0c, - 0x50, 0x4b, 0x98, 0x1c, 0x8f, 0xc3, 0x00, 0xb3, 0x19, 0x29, 0xe5, 0x1d, 0xc2, 0x92, 0x97, 0xf9, - 0x6e, 0x3f, 0x5f, 0xd8, 0x26, 0x3c, 0xd6, 0x57, 0x34, 0x99, 0xe0, 0x42, 0x0e, 0x9c, 0x09, 0x89, - 0x67, 0x60, 0xa4, 0xbe, 0xed, 0x14, 0x2a, 0x66, 0x99, 0x25, 0xff, 0xc7, 0x8f, 0x0c, 0x8b, 0xd5, - 0x6d, 0xbe, 0xe8, 0x7e, 0x3f, 0x84, 0xb7, 0x90, 0x64, 0xff, 0x14, 0x9a, 0xef, 0xd7, 0xc4, 0x2f, - 0xfc, 0x18, 0xfe, 0x5f, 0xb5, 0x7c, 0xd7, 0xe5, 0x4d, 0xeb, 0x89, 0x9d, 0x1c, 0xe0, 0xe6, 0x68, - 0xf1, 0x3e, 0xbf, 0x1f, 0x08, 0x79, 0x64, 0x3d, 0xb1, 0xb5, 0x2b, 0xd5, 0x73, 0x2b, 0x99, 0xcf, - 0x47, 0xe1, 0x32, 0xbf, 0x59, 0xfc, 0x29, 0x82, 0x84, 0x37, 0xf0, 0xe1, 0xdb, 0x4d, 0x52, 0x36, - 0x4e, 0x98, 0xd2, 0x42, 0x9c, 0xa3, 0x9e, 0x4d, 0xe4, 0x99, 0x4f, 0x7e, 0xf9, 0xeb, 0xab, 0xde, - 0x49, 0x3c, 0xa1, 0xb6, 0x1a, 0x7c, 0xf1, 0xd7, 0x08, 0x86, 0x43, 0x1d, 0x14, 0x2f, 0xb6, 0x2a, - 0x12, 0x35, 0x87, 0x4a, 0xe9, 0x0e, 0x22, 0x04, 0xba, 0x3b, 0x1c, 0xdd, 0x1c, 0x9e, 0x51, 0x9b, - 0x8e, 0xdc, 0x81, 0x9e, 0x8d, 0x7f, 0x42, 0x30, 0x14, 0x4c, 0x84, 0xd5, 0xb8, 0x25, 0xeb, 0x18, - 0x17, 0xe3, 0x07, 0x08, 0x88, 0xeb, 0x1c, 0x62, 0x16, 0xbf, 0x15, 0x0b, 0xa2, 0xfa, 0x2c, 0xdc, - 0x4a, 0x8f, 0xd4, 0xb3, 0x3d, 0xfc, 0x0d, 0x82, 0x91, 0xf0, 0x4c, 0x85, 0xdb, 0x49, 0xd6, 0xd8, - 0xad, 0xa5, 0x4c, 0x27, 0x21, 0x82, 0x83, 0xc2, 0x39, 0xcc, 0xe3, 0xd9, 0x16, 0x1c, 0x02, 0xed, - 0x15, 0xff, 0x8a, 0x60, 0xbc, 0xc5, 0xab, 0x8a, 0xef, 0xb6, 0xc2, 0xd0, 0x7e, 0x34, 0x90, 0xee, - 0x75, 0x1d, 0x2f, 0x08, 0x2d, 0x73, 0x42, 0x8b, 0x58, 0x89, 0x79, 0x29, 0x5e, 0x77, 0x39, 0xc2, - 0x7f, 0x23, 0x18, 0x6b, 0x3a, 0xb9, 0xe1, 0xd5, 0xb8, 0xe6, 0x88, 0x1a, 0x2b, 0xa5, 0x37, 0xbb, - 0x8c, 0x16, 0x94, 0xb6, 0x38, 0xa5, 0x87, 0xf8, 0xed, 0x2e, 0x7d, 0xc6, 0x67, 0x36, 0x9f, 0xe9, - 0x4b, 0x04, 0xc9, 0x66, 0x93, 0x20, 0x5e, 0x89, 0x0b, 0x35, 0x62, 0x18, 0x95, 0x56, 0xbb, 0x0b, - 0x16, 0x34, 0xd7, 0x38, 0xcd, 0xbb, 0x78, 0xf5, 0xdf, 0xd0, 0xc4, 0xdf, 0x21, 0x18, 0x3d, 0x37, - 0x0e, 0xe1, 0x4c, 0x5b, 0x53, 0x35, 0x8c, 0x56, 0xd2, 0x52, 0x47, 0x31, 0x82, 0x82, 0xca, 0x29, - 0xdc, 0xc6, 0x73, 0x4d, 0x28, 0x90, 0x7a, 0x9c, 0x78, 0xd4, 0xf0, 0x31, 0x82, 0xeb, 0x4d, 0xc6, - 0x1d, 0xfc, 0x46, 0x5c, 0x35, 0x23, 0x5a, 0xc1, 0x4a, 0x57, 0xb1, 0x82, 0xc5, 0x06, 0x67, 0xb1, - 0x86, 0xb3, 0x5d, 0x5e, 0x44, 0xb0, 0x5f, 0xfc, 0xe8, 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, - 0xc3, 0x74, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0xc0, 0x4b, 0x01, 0x98, 0xea, 0xb3, 0x88, 0xf1, 0xeb, - 0x28, 0xfb, 0xee, 0xf3, 0x93, 0x14, 0x7a, 0x71, 0x92, 0x42, 0x7f, 0x9e, 0xa4, 0xd0, 0x17, 0xa7, - 0xa9, 0x9e, 0x17, 0xa7, 0xa9, 0x9e, 0xdf, 0x4f, 0x53, 0x3d, 0x1f, 0xb6, 0x9d, 0x7d, 0x0e, 0x82, - 0x05, 0xf9, 0x20, 0xa4, 0x27, 0xf8, 0xbf, 0x86, 0x96, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x4c, - 0x47, 0x4f, 0x01, 0x02, 0x13, 0x00, 0x00, + // 1282 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xdf, 0x4f, 0x23, 0x55, + 0x14, 0xe6, 0xf2, 0xa3, 0xc2, 0x01, 0x0a, 0xde, 0x5d, 0xdd, 0x52, 0x96, 0xc2, 0x0e, 0x0b, 0x74, + 0x31, 0x3b, 0x43, 0x8b, 0xe1, 0x41, 0x70, 0xd7, 0x2d, 0xe8, 0xb2, 0xac, 0x24, 0x75, 0x40, 0x37, + 0xf1, 0xa5, 0xb9, 0x2d, 0x77, 0xa7, 0x23, 0x65, 0xa6, 0xdb, 0xb9, 0xad, 0x90, 0x0d, 0x2f, 0x3e, + 0x98, 0x18, 0x7d, 0x30, 0xfa, 0x27, 0xf8, 0xa0, 0x89, 0x4f, 0x3e, 0xf8, 0xa2, 0x7f, 0x80, 0xfb, + 0xb8, 0x89, 0x89, 0x1a, 0x37, 0x21, 0x06, 0x4c, 0xfc, 0x37, 0xcc, 0xdc, 0xb9, 0x65, 0x66, 0xe8, + 0xb4, 0x9d, 0x56, 0x7c, 0xa3, 0xf7, 0xde, 0x73, 0xce, 0xf7, 0x7d, 0xf7, 0xcc, 0x77, 0x4f, 0x80, + 0x1b, 0x79, 0x92, 0x3f, 0x2a, 0x99, 0x86, 0x92, 0x67, 0x05, 0x8b, 0x91, 0x7d, 0xdd, 0xd0, 0x94, + 0x5a, 0x4a, 0x79, 0x52, 0xa5, 0x95, 0x23, 0xb9, 0x5c, 0x31, 0x99, 0x89, 0x5f, 0x11, 0x47, 0x64, + 0xf7, 0x88, 0x5c, 0x4b, 0xc5, 0xaf, 0x6a, 0xa6, 0x66, 0xf2, 0x13, 0x8a, 0xfd, 0x97, 0x73, 0x38, + 0x7e, 0x5d, 0x33, 0x4d, 0xad, 0x44, 0x15, 0x52, 0xd6, 0x15, 0x62, 0x18, 0x26, 0x23, 0x4c, 0x37, + 0x0d, 0x4b, 0xec, 0x2e, 0x16, 0x4c, 0xeb, 0xc0, 0xb4, 0x94, 0x3c, 0xb1, 0xa8, 0x53, 0x43, 0xa9, + 0xa5, 0xf2, 0x94, 0x91, 0x94, 0x52, 0x26, 0x9a, 0x6e, 0xf0, 0xc3, 0xe2, 0xac, 0x14, 0x8c, 0xac, + 0x4c, 0x2a, 0xe4, 0xa0, 0x9e, 0x6f, 0x3e, 0xf8, 0x8c, 0x07, 0x28, 0x3f, 0x27, 0x5d, 0x05, 0xfc, + 0x9e, 0x5d, 0x2d, 0xcb, 0x83, 0x55, 0xfa, 0xa4, 0x4a, 0x2d, 0x26, 0xa9, 0x70, 0xc5, 0xb7, 0x6a, + 0x95, 0x4d, 0xc3, 0xa2, 0x78, 0x15, 0x22, 0x4e, 0x91, 0x18, 0x9a, 0x41, 0xc9, 0xe1, 0xf4, 0x94, + 0x1c, 0x28, 0x80, 0xec, 0x84, 0x65, 0xfa, 0x9f, 0x9d, 0x4c, 0xf7, 0xa8, 0x22, 0x44, 0x2a, 0xc0, + 0x04, 0xcf, 0x99, 0xd9, 0x5d, 0xff, 0x80, 0x94, 0xf4, 0x3d, 0xc2, 0xcc, 0x4a, 0xbd, 0x20, 0x7e, + 0x07, 0xc0, 0xa5, 0x29, 0xb2, 0xcf, 0xcb, 0x8e, 0x26, 0xb2, 0xad, 0x89, 0xec, 0xe8, 0x2e, 0x34, + 0x91, 0xb3, 0x44, 0xa3, 0x22, 0x56, 0xf5, 0x44, 0x4a, 0x3f, 0x20, 0x88, 0x07, 0x55, 0x11, 0x04, + 0xb6, 0x20, 0x9a, 0x67, 0x85, 0x5c, 0xed, 0x7c, 0x27, 0x86, 0x66, 0xfa, 0x92, 0xc3, 0xe9, 0xd9, + 0x26, 0x44, 0xbc, 0x59, 0xd4, 0xd1, 0x3c, 0x2b, 0xb8, 0x39, 0xf1, 0x7d, 0x1f, 0xe4, 0x5e, 0x0e, + 0x79, 0xa1, 0x2d, 0x64, 0x07, 0x88, 0x0f, 0xf3, 0x5d, 0x88, 0x35, 0x40, 0xae, 0xeb, 0x32, 0x0b, + 0xd1, 0x1a, 0x29, 0xe5, 0x6c, 0xd0, 0xe5, 0xfd, 0x5c, 0x91, 0x1e, 0x72, 0x6d, 0x86, 0xd4, 0xe1, + 0x1a, 0x29, 0x65, 0x58, 0x21, 0xbb, 0xbf, 0x49, 0x0f, 0x25, 0x1a, 0xa0, 0xec, 0x39, 0xe5, 0x4d, + 0x18, 0xf5, 0x51, 0x16, 0xe2, 0x86, 0x62, 0x3c, 0xe2, 0x65, 0x2c, 0x7d, 0xe7, 0xd1, 0x76, 0x83, + 0x96, 0xa8, 0xe6, 0x34, 0x70, 0x1d, 0x6a, 0x06, 0x22, 0x16, 0x23, 0xac, 0xea, 0x34, 0x47, 0x34, + 0xbd, 0xd8, 0xbc, 0x82, 0x1b, 0xbd, 0xc3, 0x23, 0x54, 0x11, 0x79, 0xa1, 0x0d, 0x7a, 0xbb, 0x6e, + 0x83, 0x1f, 0x11, 0x4c, 0x06, 0x42, 0x15, 0xa2, 0x6c, 0xc3, 0x98, 0x2d, 0xca, 0x9e, 0xbb, 0x25, + 0x1a, 0xe1, 0x66, 0x18, 0xd0, 0xaa, 0xdd, 0x44, 0x9e, 0xb4, 0x97, 0xd7, 0x0a, 0x7b, 0x30, 0xd7, + 0x70, 0x93, 0x59, 0xf3, 0x63, 0x5a, 0xb9, 0xc7, 0x36, 0xa9, 0xae, 0x15, 0x59, 0x27, 0x7d, 0x81, + 0x5f, 0x85, 0x48, 0x91, 0x47, 0x71, 0x48, 0xfd, 0xaa, 0xf8, 0x25, 0x55, 0x61, 0xbe, 0x5d, 0x15, + 0xa1, 0x53, 0x12, 0xc6, 0x4b, 0xc4, 0x62, 0xb9, 0x82, 0x79, 0x70, 0xa0, 0xb3, 0x5c, 0x91, 0x58, + 0x45, 0x5e, 0x68, 0x44, 0x8d, 0xda, 0xeb, 0xeb, 0x7c, 0x79, 0x93, 0x58, 0x45, 0x7c, 0x03, 0x46, + 0x6a, 0x26, 0xd3, 0x0d, 0x2d, 0x57, 0xb6, 0x33, 0x89, 0x8a, 0xc3, 0xce, 0x1a, 0x4f, 0x2e, 0x3d, + 0x84, 0x9b, 0x0d, 0x65, 0xd7, 0xab, 0x95, 0x0a, 0x35, 0x18, 0x3f, 0xd0, 0x51, 0xcf, 0x7f, 0x81, + 0x02, 0xa4, 0xf2, 0x67, 0x13, 0x1c, 0x5c, 0x15, 0x90, 0x57, 0x85, 0x40, 0x6e, 0xbd, 0xa1, 0xb8, + 0xf5, 0x35, 0x72, 0xfb, 0x0c, 0xc1, 0x02, 0x87, 0x73, 0xaf, 0xc0, 0xf4, 0x1a, 0xf5, 0xb9, 0xcf, + 0xc5, 0xbb, 0x6b, 0x06, 0xe8, 0xb2, 0x9a, 0xff, 0x17, 0x04, 0xc9, 0xf6, 0x58, 0x84, 0x3a, 0x6a, + 0x13, 0x47, 0x7c, 0x2d, 0x84, 0x3f, 0x3c, 0xd2, 0x59, 0x71, 0x9b, 0x32, 0xf2, 0xbf, 0x39, 0xe3, + 0x94, 0xf8, 0x8a, 0x39, 0x11, 0xc2, 0xe8, 0x9e, 0x4f, 0x48, 0x69, 0x05, 0xae, 0x07, 0x6f, 0xb7, + 0xbe, 0x79, 0xe9, 0x2b, 0x04, 0xb3, 0x0d, 0xbd, 0x13, 0xe0, 0x68, 0xa1, 0x3e, 0xb2, 0xcb, 0xba, + 0xb5, 0x17, 0x28, 0xe0, 0xf3, 0x08, 0xf2, 0xae, 0x8f, 0x60, 0xc2, 0xe3, 0x5d, 0x66, 0x25, 0xc0, + 0xc5, 0xe4, 0xb6, 0x2e, 0xe6, 0x4f, 0x7d, 0xcd, 0xf5, 0x33, 0xdf, 0xc6, 0xe5, 0xdd, 0xe4, 0x96, + 0xfb, 0x44, 0x79, 0x7c, 0x54, 0xe8, 0x7c, 0x1b, 0xae, 0x08, 0x90, 0x39, 0x76, 0xc8, 0x3f, 0x44, + 0x8f, 0xd8, 0xe3, 0x62, 0x6b, 0xf7, 0xd0, 0xfe, 0x16, 0xed, 0x4f, 0xff, 0xf7, 0xbe, 0xa0, 0x77, + 0xc8, 0xe3, 0xed, 0x11, 0xe7, 0xc6, 0x1c, 0xa7, 0xca, 0xac, 0xfc, 0x79, 0x32, 0x9d, 0xd6, 0x74, + 0x56, 0xac, 0xe6, 0xe5, 0x82, 0x79, 0xa0, 0x08, 0x69, 0x0a, 0x45, 0xa2, 0x1b, 0xf5, 0x1f, 0x0a, + 0x3b, 0x2a, 0x53, 0x4b, 0xce, 0x3c, 0xc8, 0x2e, 0xbf, 0xbe, 0x94, 0xad, 0xe6, 0x1f, 0xd2, 0x23, + 0x75, 0x20, 0x6f, 0x5f, 0x31, 0xde, 0x05, 0x70, 0x9b, 0xc0, 0x31, 0x88, 0xae, 0x53, 0x0e, 0xd6, + 0x1b, 0xc7, 0xb6, 0x14, 0x8b, 0x91, 0x0a, 0xcb, 0x89, 0x06, 0x15, 0x96, 0xc2, 0xd7, 0x9c, 0x2e, + 0xc6, 0x53, 0x00, 0xd4, 0xd8, 0xab, 0x1f, 0xe8, 0xe7, 0x07, 0x86, 0xa8, 0x21, 0x9a, 0x1c, 0x4f, + 0xc2, 0x10, 0x33, 0x19, 0x29, 0xe5, 0x2c, 0xc2, 0x62, 0x03, 0x7c, 0x77, 0x90, 0x2f, 0xec, 0x10, + 0x1e, 0xeb, 0x2a, 0x1a, 0x8b, 0x70, 0x21, 0x87, 0xce, 0x85, 0xc4, 0x73, 0x10, 0xad, 0x6f, 0x5b, + 0x85, 0x8a, 0x5e, 0x66, 0xb1, 0x97, 0xf8, 0x91, 0x51, 0xb1, 0xba, 0xc3, 0x17, 0xed, 0xef, 0x87, + 0x70, 0x0b, 0x89, 0x0d, 0xce, 0xa0, 0xe4, 0xa0, 0x2a, 0x7e, 0xe1, 0x47, 0xf0, 0x72, 0xd5, 0x70, + 0xbb, 0x2e, 0xa7, 0x1b, 0x8f, 0xcd, 0xd8, 0x10, 0x6f, 0x8e, 0x16, 0x8f, 0xfe, 0xfb, 0x9e, 0x90, + 0x07, 0xc6, 0x63, 0x53, 0x1d, 0xaf, 0x5e, 0x58, 0x49, 0x7f, 0x3e, 0x06, 0x03, 0xfc, 0x66, 0xf1, + 0xa7, 0x08, 0x22, 0xce, 0x14, 0x89, 0x6f, 0x35, 0x49, 0xd9, 0x38, 0xb6, 0xc6, 0x17, 0xc3, 0x1c, + 0x75, 0xda, 0x44, 0x9a, 0xfb, 0xe4, 0xd7, 0xbf, 0xbf, 0xee, 0x9d, 0xc6, 0x53, 0x4a, 0xab, 0x69, + 0x1a, 0x7f, 0x83, 0x60, 0xd4, 0xe7, 0xa0, 0x78, 0xa9, 0x55, 0x91, 0xa0, 0xe1, 0x36, 0x9e, 0xea, + 0x20, 0x42, 0xa0, 0xbb, 0xcd, 0xd1, 0x2d, 0xe0, 0x39, 0xa5, 0xe9, 0x1c, 0xef, 0xf1, 0x6c, 0xfc, + 0x33, 0x82, 0x11, 0x6f, 0x22, 0xac, 0x84, 0x2d, 0x59, 0xc7, 0xb8, 0x14, 0x3e, 0x40, 0x40, 0xdc, + 0xe4, 0x10, 0x33, 0xf8, 0xad, 0x50, 0x10, 0x95, 0xa7, 0x7e, 0x2b, 0x3d, 0x56, 0xce, 0xf7, 0xf0, + 0xb7, 0x08, 0xa2, 0xfe, 0x41, 0x0d, 0xb7, 0x93, 0xac, 0xd1, 0xad, 0xe3, 0xe9, 0x4e, 0x42, 0x04, + 0x07, 0x99, 0x73, 0x48, 0xe2, 0xf9, 0x16, 0x1c, 0x3c, 0xf6, 0x8a, 0x7f, 0x43, 0x30, 0xd9, 0xe2, + 0x55, 0xc5, 0x77, 0x5a, 0x61, 0x68, 0x3f, 0x1a, 0xc4, 0xef, 0x76, 0x1d, 0x2f, 0x08, 0xad, 0x70, + 0x42, 0x4b, 0x58, 0x0e, 0x79, 0x29, 0x8e, 0xbb, 0x1c, 0xe3, 0x7f, 0x10, 0x4c, 0x34, 0x1d, 0x07, + 0xf1, 0x5a, 0xd8, 0xe6, 0x08, 0x9a, 0x55, 0xe3, 0x6f, 0x76, 0x19, 0x2d, 0x28, 0x6d, 0x73, 0x4a, + 0xf7, 0xf1, 0xdb, 0x5d, 0xf6, 0x19, 0x9f, 0xd9, 0x5c, 0xa6, 0x2f, 0x10, 0xc4, 0x9a, 0xcd, 0x8c, + 0x78, 0x35, 0x2c, 0xd4, 0x80, 0xb9, 0x35, 0xbe, 0xd6, 0x5d, 0xb0, 0xa0, 0xb9, 0xc1, 0x69, 0xde, + 0xc1, 0x6b, 0xff, 0x85, 0x26, 0xfe, 0x1e, 0xc1, 0xd8, 0x85, 0x71, 0x08, 0xa7, 0xdb, 0x36, 0x55, + 0xc3, 0x68, 0x15, 0x5f, 0xee, 0x28, 0x46, 0x50, 0x50, 0x38, 0x85, 0x5b, 0x78, 0xa1, 0x09, 0x05, + 0x52, 0x8f, 0x13, 0x8f, 0x1a, 0x3e, 0x41, 0x70, 0xad, 0xc9, 0xb8, 0x83, 0xdf, 0x08, 0xab, 0x66, + 0x80, 0x15, 0xac, 0x76, 0x15, 0x2b, 0x58, 0x6c, 0x71, 0x16, 0x1b, 0x38, 0xd3, 0xe5, 0x45, 0x78, + 0xfd, 0xe2, 0x27, 0xe7, 0xf5, 0x70, 0xcb, 0xb4, 0x7d, 0x3d, 0x1a, 0xa6, 0xa3, 0x78, 0xaa, 0x83, + 0x88, 0x0e, 0x7a, 0xc9, 0x03, 0x53, 0x79, 0x1a, 0x30, 0x7e, 0x1d, 0x67, 0xde, 0x7d, 0x76, 0x9a, + 0x40, 0xcf, 0x4f, 0x13, 0xe8, 0xaf, 0xd3, 0x04, 0xfa, 0xf2, 0x2c, 0xd1, 0xf3, 0xfc, 0x2c, 0xd1, + 0xf3, 0xc7, 0x59, 0xa2, 0xe7, 0xc3, 0xb6, 0xb3, 0xcf, 0xa1, 0xb7, 0x20, 0x1f, 0x84, 0xf2, 0x11, + 0xfe, 0xff, 0xa6, 0xe5, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xa6, 0x39, 0xc6, 0x57, 0x13, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1990,7 +2012,14 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byt if m.VotingPower != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) i-- - dAtA[i] = 0x8 + dAtA[i] = 0x10 + } + if len(m.LastCommitHash) > 0 { + i -= len(m.LastCommitHash) + copy(dAtA[i:], m.LastCommitHash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.LastCommitHash))) + i-- + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -2048,7 +2077,14 @@ func (m *QueryBTCValidatorCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte if m.VotingPower != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) i-- - dAtA[i] = 0x10 + dAtA[i] = 0x18 + } + if len(m.LastCommitHash) > 0 { + i -= len(m.LastCommitHash) + copy(dAtA[i:], m.LastCommitHash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.LastCommitHash))) + i-- + dAtA[i] = 0x12 } if m.Height != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.Height)) @@ -2563,6 +2599,10 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { } var l int _ = l + l = len(m.LastCommitHash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } if m.VotingPower != 0 { n += 1 + sovQuery(uint64(m.VotingPower)) } @@ -2591,6 +2631,10 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Size() (n int) { if m.Height != 0 { n += 1 + sovQuery(uint64(m.Height)) } + l = len(m.LastCommitHash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } if m.VotingPower != 0 { n += 1 + sovQuery(uint64(m.VotingPower)) } @@ -3612,6 +3656,40 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.LastCommitHash == nil { + m.LastCommitHash = []byte{} + } + iNdEx = postIndex + case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) } @@ -3782,6 +3860,40 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { } } case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) + if m.LastCommitHash == nil { + m.LastCommitHash = []byte{} + } + iNdEx = postIndex + case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) } From 7c3744b15d983964296e65e973f2686a10d845c0 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 25 Oct 2023 20:51:02 +1100 Subject: [PATCH 091/202] Revert "btcstaking: returning last commit hash in validator voting power queries" (#92) --- proto/babylon/btcstaking/v1/query.proto | 10 +- x/btcstaking/keeper/grpc_query.go | 11 +- x/btcstaking/keeper/grpc_query_test.go | 17 +- x/btcstaking/types/query.pb.go | 280 +++++++----------------- 4 files changed, 90 insertions(+), 228 deletions(-) diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 8f754476f..ae84a4e71 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -137,11 +137,8 @@ message QueryBTCValidatorPowerAtHeightRequest { // QueryBTCValidatorPowerAtHeightResponse is the response type for the // Query/BTCValidatorPowerAtHeight RPC method. message QueryBTCValidatorPowerAtHeightResponse { - // last_commit_hash is the last commit hash of the block at the given height - // it will be used for the BTC validator program to generate finality signatures - bytes last_commit_hash = 1; // voting_power is the voting power of the BTC validator - uint64 voting_power = 2; + uint64 voting_power = 1; } // QueryBTCValidatorCurrentPowerRequest is the request type for the @@ -158,11 +155,8 @@ message QueryBTCValidatorCurrentPowerRequest { message QueryBTCValidatorCurrentPowerResponse { // height is the current height uint64 height = 1; - // last_commit_hash is the last commit hash of the block at the current height - // it will be used for the BTC validator program to generate finality signatures - bytes last_commit_hash = 2; // voting_power is the voting power of the BTC validator - uint64 voting_power = 3; + uint64 voting_power = 2; } // QueryActiveBTCValidatorsAtHeightRequest is the request type for the diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 9f4addd16..d19324e23 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -124,10 +124,7 @@ func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryB sdkCtx := sdk.UnwrapSDKContext(ctx) power := k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), req.Height) - return &types.QueryBTCValidatorPowerAtHeightResponse{ - LastCommitHash: sdkCtx.BlockHeader().LastCommitHash, - VotingPower: power, - }, nil + return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil } // BTCValidatorCurrentPower returns the voting power of the specified validator @@ -157,11 +154,7 @@ func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBT power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) } - return &types.QueryBTCValidatorCurrentPowerResponse{ - Height: curHeight, - LastCommitHash: sdkCtx.BlockHeader().LastCommitHash, - VotingPower: power, - }, nil + return &types.QueryBTCValidatorCurrentPowerResponse{Height: curHeight, VotingPower: power}, nil } // ActiveBTCValidatorsAtHeight returns the active BTC validators at the provided height diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index d6ff10b54..26e5843df 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -11,7 +11,6 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/golang/mock/gomock" @@ -353,14 +352,8 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { require.NoError(t, err) // add this BTC validator keeper.SetBTCValidator(ctx, btcVal) - // set block header with random last commit hash and height - randomHeight := datagen.RandomInt(r, 100) + 1 - randomLch := datagen.GenRandomByteArray(r, 32) - ctx = ctx.WithBlockHeader(tmproto.Header{ - Height: int64(randomHeight), - LastCommitHash: randomLch, - }) // set random voting power at random height + randomHeight := datagen.RandomInt(r, 100) + 1 randomPower := datagen.RandomInt(r, 100) + 1 keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) @@ -371,7 +364,6 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { resp, err := keeper.BTCValidatorPowerAtHeight(ctx, req) require.NoError(t, err) require.Equal(t, randomPower, resp.VotingPower) - require.Equal(t, randomLch, resp.LastCommitHash) }) } @@ -390,11 +382,7 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { keeper.SetBTCValidator(ctx, btcVal) // set random voting power at random height randomHeight := datagen.RandomInt(r, 100) + 1 - randomLch := datagen.GenRandomByteArray(r, 32) - ctx = ctx.WithBlockHeader(tmproto.Header{ - Height: int64(randomHeight), - LastCommitHash: randomLch, - }) + ctx = ctx.WithBlockHeight(int64(randomHeight)) randomPower := datagen.RandomInt(r, 100) + 1 keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) @@ -405,7 +393,6 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { resp, err := keeper.BTCValidatorCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight, resp.Height) - require.Equal(t, randomLch, resp.LastCommitHash) require.Equal(t, randomPower, resp.VotingPower) // if height increments but voting power hasn't recorded yet, then diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index bbfda1daa..c73571379 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -482,11 +482,8 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) GetHeight() uint64 { // QueryBTCValidatorPowerAtHeightResponse is the response type for the // Query/BTCValidatorPowerAtHeight RPC method. type QueryBTCValidatorPowerAtHeightResponse struct { - // last_commit_hash is the last commit hash of the block at the given height - // it will be used for the BTC validator program to generate finality signatures - LastCommitHash []byte `protobuf:"bytes,1,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` // voting_power is the voting power of the BTC validator - VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + VotingPower uint64 `protobuf:"varint,1,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { @@ -524,13 +521,6 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorPowerAtHeightResponse) GetLastCommitHash() []byte { - if m != nil { - return m.LastCommitHash - } - return nil -} - func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower @@ -592,11 +582,8 @@ func (m *QueryBTCValidatorCurrentPowerRequest) GetValBtcPkHex() string { type QueryBTCValidatorCurrentPowerResponse struct { // height is the current height Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` - // last_commit_hash is the last commit hash of the block at the current height - // it will be used for the BTC validator program to generate finality signatures - LastCommitHash []byte `protobuf:"bytes,2,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` // voting_power is the voting power of the BTC validator - VotingPower uint64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCValidatorCurrentPowerResponse{} } @@ -639,13 +626,6 @@ func (m *QueryBTCValidatorCurrentPowerResponse) GetHeight() uint64 { return 0 } -func (m *QueryBTCValidatorCurrentPowerResponse) GetLastCommitHash() []byte { - if m != nil { - return m.LastCommitHash - } - return nil -} - func (m *QueryBTCValidatorCurrentPowerResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower @@ -1150,88 +1130,86 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1282 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xdf, 0x4f, 0x23, 0x55, - 0x14, 0xe6, 0xf2, 0xa3, 0xc2, 0x01, 0x0a, 0xde, 0x5d, 0xdd, 0x52, 0x96, 0xc2, 0x0e, 0x0b, 0x74, - 0x31, 0x3b, 0x43, 0x8b, 0xe1, 0x41, 0x70, 0xd7, 0x2d, 0xe8, 0xb2, 0xac, 0x24, 0x75, 0x40, 0x37, - 0xf1, 0xa5, 0xb9, 0x2d, 0x77, 0xa7, 0x23, 0x65, 0xa6, 0xdb, 0xb9, 0xad, 0x90, 0x0d, 0x2f, 0x3e, - 0x98, 0x18, 0x7d, 0x30, 0xfa, 0x27, 0xf8, 0xa0, 0x89, 0x4f, 0x3e, 0xf8, 0xa2, 0x7f, 0x80, 0xfb, - 0xb8, 0x89, 0x89, 0x1a, 0x37, 0x21, 0x06, 0x4c, 0xfc, 0x37, 0xcc, 0xdc, 0xb9, 0x65, 0x66, 0xe8, - 0xb4, 0x9d, 0x56, 0x7c, 0xa3, 0xf7, 0xde, 0x73, 0xce, 0xf7, 0x7d, 0xf7, 0xcc, 0x77, 0x4f, 0x80, - 0x1b, 0x79, 0x92, 0x3f, 0x2a, 0x99, 0x86, 0x92, 0x67, 0x05, 0x8b, 0x91, 0x7d, 0xdd, 0xd0, 0x94, - 0x5a, 0x4a, 0x79, 0x52, 0xa5, 0x95, 0x23, 0xb9, 0x5c, 0x31, 0x99, 0x89, 0x5f, 0x11, 0x47, 0x64, - 0xf7, 0x88, 0x5c, 0x4b, 0xc5, 0xaf, 0x6a, 0xa6, 0x66, 0xf2, 0x13, 0x8a, 0xfd, 0x97, 0x73, 0x38, - 0x7e, 0x5d, 0x33, 0x4d, 0xad, 0x44, 0x15, 0x52, 0xd6, 0x15, 0x62, 0x18, 0x26, 0x23, 0x4c, 0x37, - 0x0d, 0x4b, 0xec, 0x2e, 0x16, 0x4c, 0xeb, 0xc0, 0xb4, 0x94, 0x3c, 0xb1, 0xa8, 0x53, 0x43, 0xa9, - 0xa5, 0xf2, 0x94, 0x91, 0x94, 0x52, 0x26, 0x9a, 0x6e, 0xf0, 0xc3, 0xe2, 0xac, 0x14, 0x8c, 0xac, - 0x4c, 0x2a, 0xe4, 0xa0, 0x9e, 0x6f, 0x3e, 0xf8, 0x8c, 0x07, 0x28, 0x3f, 0x27, 0x5d, 0x05, 0xfc, - 0x9e, 0x5d, 0x2d, 0xcb, 0x83, 0x55, 0xfa, 0xa4, 0x4a, 0x2d, 0x26, 0xa9, 0x70, 0xc5, 0xb7, 0x6a, - 0x95, 0x4d, 0xc3, 0xa2, 0x78, 0x15, 0x22, 0x4e, 0x91, 0x18, 0x9a, 0x41, 0xc9, 0xe1, 0xf4, 0x94, - 0x1c, 0x28, 0x80, 0xec, 0x84, 0x65, 0xfa, 0x9f, 0x9d, 0x4c, 0xf7, 0xa8, 0x22, 0x44, 0x2a, 0xc0, - 0x04, 0xcf, 0x99, 0xd9, 0x5d, 0xff, 0x80, 0x94, 0xf4, 0x3d, 0xc2, 0xcc, 0x4a, 0xbd, 0x20, 0x7e, - 0x07, 0xc0, 0xa5, 0x29, 0xb2, 0xcf, 0xcb, 0x8e, 0x26, 0xb2, 0xad, 0x89, 0xec, 0xe8, 0x2e, 0x34, - 0x91, 0xb3, 0x44, 0xa3, 0x22, 0x56, 0xf5, 0x44, 0x4a, 0x3f, 0x20, 0x88, 0x07, 0x55, 0x11, 0x04, - 0xb6, 0x20, 0x9a, 0x67, 0x85, 0x5c, 0xed, 0x7c, 0x27, 0x86, 0x66, 0xfa, 0x92, 0xc3, 0xe9, 0xd9, - 0x26, 0x44, 0xbc, 0x59, 0xd4, 0xd1, 0x3c, 0x2b, 0xb8, 0x39, 0xf1, 0x7d, 0x1f, 0xe4, 0x5e, 0x0e, - 0x79, 0xa1, 0x2d, 0x64, 0x07, 0x88, 0x0f, 0xf3, 0x5d, 0x88, 0x35, 0x40, 0xae, 0xeb, 0x32, 0x0b, - 0xd1, 0x1a, 0x29, 0xe5, 0x6c, 0xd0, 0xe5, 0xfd, 0x5c, 0x91, 0x1e, 0x72, 0x6d, 0x86, 0xd4, 0xe1, - 0x1a, 0x29, 0x65, 0x58, 0x21, 0xbb, 0xbf, 0x49, 0x0f, 0x25, 0x1a, 0xa0, 0xec, 0x39, 0xe5, 0x4d, - 0x18, 0xf5, 0x51, 0x16, 0xe2, 0x86, 0x62, 0x3c, 0xe2, 0x65, 0x2c, 0x7d, 0xe7, 0xd1, 0x76, 0x83, - 0x96, 0xa8, 0xe6, 0x34, 0x70, 0x1d, 0x6a, 0x06, 0x22, 0x16, 0x23, 0xac, 0xea, 0x34, 0x47, 0x34, - 0xbd, 0xd8, 0xbc, 0x82, 0x1b, 0xbd, 0xc3, 0x23, 0x54, 0x11, 0x79, 0xa1, 0x0d, 0x7a, 0xbb, 0x6e, - 0x83, 0x1f, 0x11, 0x4c, 0x06, 0x42, 0x15, 0xa2, 0x6c, 0xc3, 0x98, 0x2d, 0xca, 0x9e, 0xbb, 0x25, - 0x1a, 0xe1, 0x66, 0x18, 0xd0, 0xaa, 0xdd, 0x44, 0x9e, 0xb4, 0x97, 0xd7, 0x0a, 0x7b, 0x30, 0xd7, - 0x70, 0x93, 0x59, 0xf3, 0x63, 0x5a, 0xb9, 0xc7, 0x36, 0xa9, 0xae, 0x15, 0x59, 0x27, 0x7d, 0x81, - 0x5f, 0x85, 0x48, 0x91, 0x47, 0x71, 0x48, 0xfd, 0xaa, 0xf8, 0x25, 0x55, 0x61, 0xbe, 0x5d, 0x15, - 0xa1, 0x53, 0x12, 0xc6, 0x4b, 0xc4, 0x62, 0xb9, 0x82, 0x79, 0x70, 0xa0, 0xb3, 0x5c, 0x91, 0x58, - 0x45, 0x5e, 0x68, 0x44, 0x8d, 0xda, 0xeb, 0xeb, 0x7c, 0x79, 0x93, 0x58, 0x45, 0x7c, 0x03, 0x46, - 0x6a, 0x26, 0xd3, 0x0d, 0x2d, 0x57, 0xb6, 0x33, 0x89, 0x8a, 0xc3, 0xce, 0x1a, 0x4f, 0x2e, 0x3d, - 0x84, 0x9b, 0x0d, 0x65, 0xd7, 0xab, 0x95, 0x0a, 0x35, 0x18, 0x3f, 0xd0, 0x51, 0xcf, 0x7f, 0x81, - 0x02, 0xa4, 0xf2, 0x67, 0x13, 0x1c, 0x5c, 0x15, 0x90, 0x57, 0x85, 0x40, 0x6e, 0xbd, 0xa1, 0xb8, - 0xf5, 0x35, 0x72, 0xfb, 0x0c, 0xc1, 0x02, 0x87, 0x73, 0xaf, 0xc0, 0xf4, 0x1a, 0xf5, 0xb9, 0xcf, - 0xc5, 0xbb, 0x6b, 0x06, 0xe8, 0xb2, 0x9a, 0xff, 0x17, 0x04, 0xc9, 0xf6, 0x58, 0x84, 0x3a, 0x6a, - 0x13, 0x47, 0x7c, 0x2d, 0x84, 0x3f, 0x3c, 0xd2, 0x59, 0x71, 0x9b, 0x32, 0xf2, 0xbf, 0x39, 0xe3, - 0x94, 0xf8, 0x8a, 0x39, 0x11, 0xc2, 0xe8, 0x9e, 0x4f, 0x48, 0x69, 0x05, 0xae, 0x07, 0x6f, 0xb7, - 0xbe, 0x79, 0xe9, 0x2b, 0x04, 0xb3, 0x0d, 0xbd, 0x13, 0xe0, 0x68, 0xa1, 0x3e, 0xb2, 0xcb, 0xba, - 0xb5, 0x17, 0x28, 0xe0, 0xf3, 0x08, 0xf2, 0xae, 0x8f, 0x60, 0xc2, 0xe3, 0x5d, 0x66, 0x25, 0xc0, - 0xc5, 0xe4, 0xb6, 0x2e, 0xe6, 0x4f, 0x7d, 0xcd, 0xf5, 0x33, 0xdf, 0xc6, 0xe5, 0xdd, 0xe4, 0x96, - 0xfb, 0x44, 0x79, 0x7c, 0x54, 0xe8, 0x7c, 0x1b, 0xae, 0x08, 0x90, 0x39, 0x76, 0xc8, 0x3f, 0x44, - 0x8f, 0xd8, 0xe3, 0x62, 0x6b, 0xf7, 0xd0, 0xfe, 0x16, 0xed, 0x4f, 0xff, 0xf7, 0xbe, 0xa0, 0x77, - 0xc8, 0xe3, 0xed, 0x11, 0xe7, 0xc6, 0x1c, 0xa7, 0xca, 0xac, 0xfc, 0x79, 0x32, 0x9d, 0xd6, 0x74, - 0x56, 0xac, 0xe6, 0xe5, 0x82, 0x79, 0xa0, 0x08, 0x69, 0x0a, 0x45, 0xa2, 0x1b, 0xf5, 0x1f, 0x0a, - 0x3b, 0x2a, 0x53, 0x4b, 0xce, 0x3c, 0xc8, 0x2e, 0xbf, 0xbe, 0x94, 0xad, 0xe6, 0x1f, 0xd2, 0x23, - 0x75, 0x20, 0x6f, 0x5f, 0x31, 0xde, 0x05, 0x70, 0x9b, 0xc0, 0x31, 0x88, 0xae, 0x53, 0x0e, 0xd6, - 0x1b, 0xc7, 0xb6, 0x14, 0x8b, 0x91, 0x0a, 0xcb, 0x89, 0x06, 0x15, 0x96, 0xc2, 0xd7, 0x9c, 0x2e, - 0xc6, 0x53, 0x00, 0xd4, 0xd8, 0xab, 0x1f, 0xe8, 0xe7, 0x07, 0x86, 0xa8, 0x21, 0x9a, 0x1c, 0x4f, - 0xc2, 0x10, 0x33, 0x19, 0x29, 0xe5, 0x2c, 0xc2, 0x62, 0x03, 0x7c, 0x77, 0x90, 0x2f, 0xec, 0x10, - 0x1e, 0xeb, 0x2a, 0x1a, 0x8b, 0x70, 0x21, 0x87, 0xce, 0x85, 0xc4, 0x73, 0x10, 0xad, 0x6f, 0x5b, - 0x85, 0x8a, 0x5e, 0x66, 0xb1, 0x97, 0xf8, 0x91, 0x51, 0xb1, 0xba, 0xc3, 0x17, 0xed, 0xef, 0x87, - 0x70, 0x0b, 0x89, 0x0d, 0xce, 0xa0, 0xe4, 0xa0, 0x2a, 0x7e, 0xe1, 0x47, 0xf0, 0x72, 0xd5, 0x70, - 0xbb, 0x2e, 0xa7, 0x1b, 0x8f, 0xcd, 0xd8, 0x10, 0x6f, 0x8e, 0x16, 0x8f, 0xfe, 0xfb, 0x9e, 0x90, - 0x07, 0xc6, 0x63, 0x53, 0x1d, 0xaf, 0x5e, 0x58, 0x49, 0x7f, 0x3e, 0x06, 0x03, 0xfc, 0x66, 0xf1, - 0xa7, 0x08, 0x22, 0xce, 0x14, 0x89, 0x6f, 0x35, 0x49, 0xd9, 0x38, 0xb6, 0xc6, 0x17, 0xc3, 0x1c, - 0x75, 0xda, 0x44, 0x9a, 0xfb, 0xe4, 0xd7, 0xbf, 0xbf, 0xee, 0x9d, 0xc6, 0x53, 0x4a, 0xab, 0x69, - 0x1a, 0x7f, 0x83, 0x60, 0xd4, 0xe7, 0xa0, 0x78, 0xa9, 0x55, 0x91, 0xa0, 0xe1, 0x36, 0x9e, 0xea, - 0x20, 0x42, 0xa0, 0xbb, 0xcd, 0xd1, 0x2d, 0xe0, 0x39, 0xa5, 0xe9, 0x1c, 0xef, 0xf1, 0x6c, 0xfc, - 0x33, 0x82, 0x11, 0x6f, 0x22, 0xac, 0x84, 0x2d, 0x59, 0xc7, 0xb8, 0x14, 0x3e, 0x40, 0x40, 0xdc, - 0xe4, 0x10, 0x33, 0xf8, 0xad, 0x50, 0x10, 0x95, 0xa7, 0x7e, 0x2b, 0x3d, 0x56, 0xce, 0xf7, 0xf0, - 0xb7, 0x08, 0xa2, 0xfe, 0x41, 0x0d, 0xb7, 0x93, 0xac, 0xd1, 0xad, 0xe3, 0xe9, 0x4e, 0x42, 0x04, - 0x07, 0x99, 0x73, 0x48, 0xe2, 0xf9, 0x16, 0x1c, 0x3c, 0xf6, 0x8a, 0x7f, 0x43, 0x30, 0xd9, 0xe2, - 0x55, 0xc5, 0x77, 0x5a, 0x61, 0x68, 0x3f, 0x1a, 0xc4, 0xef, 0x76, 0x1d, 0x2f, 0x08, 0xad, 0x70, - 0x42, 0x4b, 0x58, 0x0e, 0x79, 0x29, 0x8e, 0xbb, 0x1c, 0xe3, 0x7f, 0x10, 0x4c, 0x34, 0x1d, 0x07, - 0xf1, 0x5a, 0xd8, 0xe6, 0x08, 0x9a, 0x55, 0xe3, 0x6f, 0x76, 0x19, 0x2d, 0x28, 0x6d, 0x73, 0x4a, - 0xf7, 0xf1, 0xdb, 0x5d, 0xf6, 0x19, 0x9f, 0xd9, 0x5c, 0xa6, 0x2f, 0x10, 0xc4, 0x9a, 0xcd, 0x8c, - 0x78, 0x35, 0x2c, 0xd4, 0x80, 0xb9, 0x35, 0xbe, 0xd6, 0x5d, 0xb0, 0xa0, 0xb9, 0xc1, 0x69, 0xde, - 0xc1, 0x6b, 0xff, 0x85, 0x26, 0xfe, 0x1e, 0xc1, 0xd8, 0x85, 0x71, 0x08, 0xa7, 0xdb, 0x36, 0x55, - 0xc3, 0x68, 0x15, 0x5f, 0xee, 0x28, 0x46, 0x50, 0x50, 0x38, 0x85, 0x5b, 0x78, 0xa1, 0x09, 0x05, - 0x52, 0x8f, 0x13, 0x8f, 0x1a, 0x3e, 0x41, 0x70, 0xad, 0xc9, 0xb8, 0x83, 0xdf, 0x08, 0xab, 0x66, - 0x80, 0x15, 0xac, 0x76, 0x15, 0x2b, 0x58, 0x6c, 0x71, 0x16, 0x1b, 0x38, 0xd3, 0xe5, 0x45, 0x78, - 0xfd, 0xe2, 0x27, 0xe7, 0xf5, 0x70, 0xcb, 0xb4, 0x7d, 0x3d, 0x1a, 0xa6, 0xa3, 0x78, 0xaa, 0x83, - 0x88, 0x0e, 0x7a, 0xc9, 0x03, 0x53, 0x79, 0x1a, 0x30, 0x7e, 0x1d, 0x67, 0xde, 0x7d, 0x76, 0x9a, - 0x40, 0xcf, 0x4f, 0x13, 0xe8, 0xaf, 0xd3, 0x04, 0xfa, 0xf2, 0x2c, 0xd1, 0xf3, 0xfc, 0x2c, 0xd1, - 0xf3, 0xc7, 0x59, 0xa2, 0xe7, 0xc3, 0xb6, 0xb3, 0xcf, 0xa1, 0xb7, 0x20, 0x1f, 0x84, 0xf2, 0x11, - 0xfe, 0xff, 0xa6, 0xe5, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xa6, 0x39, 0xc6, 0x57, 0x13, - 0x00, 0x00, + // 1255 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc5, + 0x1b, 0xce, 0xa4, 0xa9, 0x7f, 0xc9, 0x9b, 0x7f, 0xfd, 0x4d, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, + 0x9a, 0x3f, 0x0d, 0xea, 0x6e, 0xec, 0xa0, 0x1c, 0x48, 0x68, 0xa9, 0x1b, 0x68, 0x9a, 0x10, 0xc9, + 0x6c, 0x02, 0x95, 0xb8, 0x58, 0xb3, 0xce, 0x74, 0xbd, 0xc4, 0xd9, 0x75, 0xbd, 0x63, 0x93, 0xa8, + 0xca, 0x85, 0x03, 0x12, 0xe2, 0x82, 0xe0, 0x23, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0xf0, 0x01, + 0xe8, 0xb1, 0x12, 0x12, 0x20, 0x2a, 0x45, 0x28, 0x41, 0xe2, 0x6b, 0xa0, 0x9d, 0x1d, 0x67, 0x77, + 0xe3, 0xb5, 0xbd, 0x36, 0xe1, 0x16, 0xcf, 0xcc, 0xfb, 0xbe, 0xcf, 0xf3, 0xcc, 0xb3, 0xef, 0xbc, + 0x0a, 0xdc, 0xd4, 0x89, 0x7e, 0x58, 0xb2, 0x2d, 0x55, 0x67, 0x05, 0x87, 0x91, 0x3d, 0xd3, 0x32, + 0xd4, 0x5a, 0x5a, 0x7d, 0x5a, 0xa5, 0x95, 0x43, 0xa5, 0x5c, 0xb1, 0x99, 0x8d, 0x5f, 0x11, 0x47, + 0x14, 0xff, 0x88, 0x52, 0x4b, 0x4b, 0xd7, 0x0c, 0xdb, 0xb0, 0xf9, 0x09, 0xd5, 0xfd, 0xcb, 0x3b, + 0x2c, 0xdd, 0x30, 0x6c, 0xdb, 0x28, 0x51, 0x95, 0x94, 0x4d, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, + 0x6d, 0xcb, 0x11, 0xbb, 0x0b, 0x05, 0xdb, 0xd9, 0xb7, 0x1d, 0x55, 0x27, 0x0e, 0xf5, 0x6a, 0xa8, + 0xb5, 0xb4, 0x4e, 0x19, 0x49, 0xab, 0x65, 0x62, 0x98, 0x16, 0x3f, 0x2c, 0xce, 0xca, 0xd1, 0xc8, + 0xca, 0xa4, 0x42, 0xf6, 0xeb, 0xf9, 0x66, 0xa3, 0xcf, 0x04, 0x80, 0xf2, 0x73, 0xf2, 0x35, 0xc0, + 0xef, 0xb9, 0xd5, 0x72, 0x3c, 0x58, 0xa3, 0x4f, 0xab, 0xd4, 0x61, 0xb2, 0x06, 0x57, 0x43, 0xab, + 0x4e, 0xd9, 0xb6, 0x1c, 0x8a, 0x57, 0x20, 0xe1, 0x15, 0x49, 0xa2, 0x29, 0x34, 0x3f, 0x98, 0x99, + 0x50, 0x22, 0x05, 0x50, 0xbc, 0xb0, 0x6c, 0xdf, 0xf3, 0xe3, 0xc9, 0x1e, 0x4d, 0x84, 0xc8, 0x05, + 0x18, 0xe3, 0x39, 0xb3, 0x3b, 0x0f, 0x3e, 0x20, 0x25, 0x73, 0x97, 0x30, 0xbb, 0x52, 0x2f, 0x88, + 0xdf, 0x01, 0xf0, 0x69, 0x8a, 0xec, 0xb3, 0x8a, 0xa7, 0x89, 0xe2, 0x6a, 0xa2, 0x78, 0xba, 0x0b, + 0x4d, 0x94, 0x1c, 0x31, 0xa8, 0x88, 0xd5, 0x02, 0x91, 0xf2, 0xf7, 0x08, 0xa4, 0xa8, 0x2a, 0x82, + 0xc0, 0x06, 0x8c, 0xe8, 0xac, 0x90, 0xaf, 0x9d, 0xed, 0x24, 0xd1, 0xd4, 0xa5, 0xf9, 0xc1, 0xcc, + 0x74, 0x13, 0x22, 0xc1, 0x2c, 0xda, 0xb0, 0xce, 0x0a, 0x7e, 0x4e, 0xfc, 0x30, 0x04, 0xb9, 0x97, + 0x43, 0x9e, 0x6b, 0x0b, 0xd9, 0x03, 0x12, 0xc2, 0x7c, 0x0f, 0x92, 0x0d, 0x90, 0xeb, 0xba, 0x4c, + 0xc3, 0x48, 0x8d, 0x94, 0xf2, 0x2e, 0xe8, 0xf2, 0x5e, 0xbe, 0x48, 0x0f, 0xb8, 0x36, 0x03, 0xda, + 0x60, 0x8d, 0x94, 0xb2, 0xac, 0x90, 0xdb, 0x5b, 0xa7, 0x07, 0x32, 0x8d, 0x50, 0xf6, 0x8c, 0xf2, + 0x3a, 0x0c, 0x87, 0x28, 0x0b, 0x71, 0x63, 0x31, 0x1e, 0x0a, 0x32, 0x96, 0xbf, 0x0d, 0x68, 0xbb, + 0x46, 0x4b, 0xd4, 0xf0, 0x0c, 0x5c, 0x87, 0x9a, 0x85, 0x84, 0xc3, 0x08, 0xab, 0x7a, 0xe6, 0x18, + 0xc9, 0x2c, 0x34, 0xaf, 0xe0, 0x47, 0x6f, 0xf3, 0x08, 0x4d, 0x44, 0x9e, 0xb3, 0x41, 0x6f, 0xd7, + 0x36, 0xf8, 0x01, 0xc1, 0x78, 0x24, 0x54, 0x21, 0xca, 0x16, 0x8c, 0xba, 0xa2, 0xec, 0xfa, 0x5b, + 0xc2, 0x08, 0xb7, 0xe2, 0x80, 0xd6, 0x5c, 0x13, 0x05, 0xd2, 0x5e, 0x9c, 0x15, 0x76, 0x61, 0xa6, + 0xe1, 0x26, 0x73, 0xf6, 0xc7, 0xb4, 0x72, 0x9f, 0xad, 0x53, 0xd3, 0x28, 0xb2, 0x4e, 0x7c, 0x81, + 0x5f, 0x85, 0x44, 0x91, 0x47, 0x71, 0x48, 0x7d, 0x9a, 0xf8, 0x25, 0x6f, 0xc2, 0x6c, 0xbb, 0x2a, + 0x42, 0xa7, 0x9b, 0x30, 0x54, 0xb3, 0x99, 0x69, 0x19, 0xf9, 0xb2, 0xbb, 0xcf, 0x8b, 0xf4, 0x69, + 0x83, 0xde, 0x1a, 0x0f, 0x91, 0x37, 0xe1, 0x56, 0x43, 0xb2, 0x07, 0xd5, 0x4a, 0x85, 0x5a, 0x8c, + 0x1f, 0xe8, 0xc8, 0xc9, 0x7a, 0x04, 0xff, 0x70, 0x32, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x1a, + 0x00, 0xf7, 0x36, 0x02, 0xfe, 0x0c, 0xc1, 0x1c, 0x2f, 0x72, 0xbf, 0xc0, 0xcc, 0x1a, 0x0d, 0x35, + 0x8a, 0xf3, 0x32, 0x37, 0x2b, 0x73, 0x51, 0x3e, 0xfd, 0x19, 0xc1, 0x7c, 0x7b, 0x2c, 0x82, 0xb3, + 0xd6, 0xa4, 0x79, 0xbd, 0x16, 0xe3, 0x53, 0x7e, 0x6c, 0xb2, 0xe2, 0x16, 0x65, 0xe4, 0x3f, 0x6b, + 0x62, 0x13, 0xe2, 0x83, 0xe3, 0x44, 0x08, 0xa3, 0xbb, 0x21, 0x21, 0xe5, 0x65, 0xb8, 0x11, 0xbd, + 0xdd, 0xfa, 0x3e, 0xe5, 0x2f, 0x11, 0x4c, 0x37, 0x38, 0x22, 0xa2, 0xf9, 0xc4, 0xfa, 0x1e, 0x2e, + 0xea, 0xd6, 0x5e, 0xa2, 0x08, 0xcf, 0x47, 0xb5, 0x99, 0x8f, 0x60, 0x2c, 0xd0, 0x66, 0xec, 0x4a, + 0x44, 0xc3, 0x51, 0xda, 0x36, 0x9c, 0x70, 0xea, 0xeb, 0x7e, 0xeb, 0x09, 0x6d, 0x5c, 0xdc, 0x4d, + 0x6e, 0xf8, 0xaf, 0x49, 0xa0, 0xe5, 0x09, 0x9d, 0xef, 0xc0, 0x55, 0x01, 0x32, 0xcf, 0x0e, 0xf2, + 0x45, 0xe2, 0x14, 0x03, 0x62, 0x5f, 0x11, 0x5b, 0x3b, 0x07, 0xeb, 0xc4, 0x29, 0xba, 0xdf, 0xf3, + 0x6f, 0x97, 0xa2, 0x9e, 0x8c, 0x40, 0x1b, 0x4e, 0x78, 0x37, 0xc6, 0x13, 0x0c, 0x65, 0x97, 0xff, + 0x38, 0x9e, 0xcc, 0x18, 0x26, 0x2b, 0x56, 0x75, 0xa5, 0x60, 0xef, 0xab, 0x42, 0x9a, 0x42, 0x91, + 0x98, 0x56, 0xfd, 0x87, 0xca, 0x0e, 0xcb, 0xd4, 0x51, 0xb2, 0x8f, 0x72, 0x4b, 0xaf, 0x2f, 0xe6, + 0xaa, 0xfa, 0x26, 0x3d, 0xd4, 0x2e, 0xeb, 0xee, 0x15, 0xe3, 0x1d, 0x00, 0xdf, 0x04, 0x5c, 0x82, + 0xee, 0x53, 0xf6, 0xd7, 0x8d, 0xe3, 0xb6, 0x14, 0x87, 0x91, 0x0a, 0xcb, 0x0b, 0x83, 0x5e, 0xf2, + 0x5a, 0x0a, 0x5f, 0xf3, 0x5c, 0x8c, 0x27, 0x00, 0xa8, 0xb5, 0x5b, 0x3f, 0xd0, 0xc7, 0x0f, 0x0c, + 0x50, 0x4b, 0x98, 0x1c, 0x8f, 0xc3, 0x00, 0xb3, 0x19, 0x29, 0xe5, 0x1d, 0xc2, 0x92, 0x97, 0xf9, + 0x6e, 0x3f, 0x5f, 0xd8, 0x26, 0x3c, 0xd6, 0x57, 0x34, 0x99, 0xe0, 0x42, 0x0e, 0x9c, 0x09, 0x89, + 0x67, 0x60, 0xa4, 0xbe, 0xed, 0x14, 0x2a, 0x66, 0x99, 0x25, 0xff, 0xc7, 0x8f, 0x0c, 0x8b, 0xd5, + 0x6d, 0xbe, 0xe8, 0x7e, 0x3f, 0x84, 0xb7, 0x90, 0x64, 0xff, 0x14, 0x9a, 0xef, 0xd7, 0xc4, 0x2f, + 0xfc, 0x18, 0xfe, 0x5f, 0xb5, 0x7c, 0xd7, 0xe5, 0x4d, 0xeb, 0x89, 0x9d, 0x1c, 0xe0, 0xe6, 0x68, + 0xf1, 0x3e, 0xbf, 0x1f, 0x08, 0x79, 0x64, 0x3d, 0xb1, 0xb5, 0x2b, 0xd5, 0x73, 0x2b, 0x99, 0xcf, + 0x47, 0xe1, 0x32, 0xbf, 0x59, 0xfc, 0x29, 0x82, 0x84, 0x37, 0xf0, 0xe1, 0xdb, 0x4d, 0x52, 0x36, + 0x4e, 0x98, 0xd2, 0x42, 0x9c, 0xa3, 0x9e, 0x4d, 0xe4, 0x99, 0x4f, 0x7e, 0xf9, 0xeb, 0xab, 0xde, + 0x49, 0x3c, 0xa1, 0xb6, 0x1a, 0x7c, 0xf1, 0xd7, 0x08, 0x86, 0x43, 0x1d, 0x14, 0x2f, 0xb6, 0x2a, + 0x12, 0x35, 0x87, 0x4a, 0xe9, 0x0e, 0x22, 0x04, 0xba, 0x3b, 0x1c, 0xdd, 0x1c, 0x9e, 0x51, 0x9b, + 0x8e, 0xdc, 0x81, 0x9e, 0x8d, 0x7f, 0x42, 0x30, 0x14, 0x4c, 0x84, 0xd5, 0xb8, 0x25, 0xeb, 0x18, + 0x17, 0xe3, 0x07, 0x08, 0x88, 0xeb, 0x1c, 0x62, 0x16, 0xbf, 0x15, 0x0b, 0xa2, 0xfa, 0x2c, 0xdc, + 0x4a, 0x8f, 0xd4, 0xb3, 0x3d, 0xfc, 0x0d, 0x82, 0x91, 0xf0, 0x4c, 0x85, 0xdb, 0x49, 0xd6, 0xd8, + 0xad, 0xa5, 0x4c, 0x27, 0x21, 0x82, 0x83, 0xc2, 0x39, 0xcc, 0xe3, 0xd9, 0x16, 0x1c, 0x02, 0xed, + 0x15, 0xff, 0x8a, 0x60, 0xbc, 0xc5, 0xab, 0x8a, 0xef, 0xb6, 0xc2, 0xd0, 0x7e, 0x34, 0x90, 0xee, + 0x75, 0x1d, 0x2f, 0x08, 0x2d, 0x73, 0x42, 0x8b, 0x58, 0x89, 0x79, 0x29, 0x5e, 0x77, 0x39, 0xc2, + 0x7f, 0x23, 0x18, 0x6b, 0x3a, 0xb9, 0xe1, 0xd5, 0xb8, 0xe6, 0x88, 0x1a, 0x2b, 0xa5, 0x37, 0xbb, + 0x8c, 0x16, 0x94, 0xb6, 0x38, 0xa5, 0x87, 0xf8, 0xed, 0x2e, 0x7d, 0xc6, 0x67, 0x36, 0x9f, 0xe9, + 0x4b, 0x04, 0xc9, 0x66, 0x93, 0x20, 0x5e, 0x89, 0x0b, 0x35, 0x62, 0x18, 0x95, 0x56, 0xbb, 0x0b, + 0x16, 0x34, 0xd7, 0x38, 0xcd, 0xbb, 0x78, 0xf5, 0xdf, 0xd0, 0xc4, 0xdf, 0x21, 0x18, 0x3d, 0x37, + 0x0e, 0xe1, 0x4c, 0x5b, 0x53, 0x35, 0x8c, 0x56, 0xd2, 0x52, 0x47, 0x31, 0x82, 0x82, 0xca, 0x29, + 0xdc, 0xc6, 0x73, 0x4d, 0x28, 0x90, 0x7a, 0x9c, 0x78, 0xd4, 0xf0, 0x31, 0x82, 0xeb, 0x4d, 0xc6, + 0x1d, 0xfc, 0x46, 0x5c, 0x35, 0x23, 0x5a, 0xc1, 0x4a, 0x57, 0xb1, 0x82, 0xc5, 0x06, 0x67, 0xb1, + 0x86, 0xb3, 0x5d, 0x5e, 0x44, 0xb0, 0x5f, 0xfc, 0xe8, 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, + 0xc3, 0x74, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0xc0, 0x4b, 0x01, 0x98, 0xea, 0xb3, 0x88, 0xf1, 0xeb, + 0x28, 0xfb, 0xee, 0xf3, 0x93, 0x14, 0x7a, 0x71, 0x92, 0x42, 0x7f, 0x9e, 0xa4, 0xd0, 0x17, 0xa7, + 0xa9, 0x9e, 0x17, 0xa7, 0xa9, 0x9e, 0xdf, 0x4f, 0x53, 0x3d, 0x1f, 0xb6, 0x9d, 0x7d, 0x0e, 0x82, + 0x05, 0xf9, 0x20, 0xa4, 0x27, 0xf8, 0xbf, 0x86, 0x96, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x4c, + 0x47, 0x4f, 0x01, 0x02, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2012,14 +1990,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byt if m.VotingPower != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) i-- - dAtA[i] = 0x10 - } - if len(m.LastCommitHash) > 0 { - i -= len(m.LastCommitHash) - copy(dAtA[i:], m.LastCommitHash) - i = encodeVarintQuery(dAtA, i, uint64(len(m.LastCommitHash))) - i-- - dAtA[i] = 0xa + dAtA[i] = 0x8 } return len(dAtA) - i, nil } @@ -2077,14 +2048,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte if m.VotingPower != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.VotingPower)) i-- - dAtA[i] = 0x18 - } - if len(m.LastCommitHash) > 0 { - i -= len(m.LastCommitHash) - copy(dAtA[i:], m.LastCommitHash) - i = encodeVarintQuery(dAtA, i, uint64(len(m.LastCommitHash))) - i-- - dAtA[i] = 0x12 + dAtA[i] = 0x10 } if m.Height != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.Height)) @@ -2599,10 +2563,6 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { } var l int _ = l - l = len(m.LastCommitHash) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } if m.VotingPower != 0 { n += 1 + sovQuery(uint64(m.VotingPower)) } @@ -2631,10 +2591,6 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Size() (n int) { if m.Height != 0 { n += 1 + sovQuery(uint64(m.Height)) } - l = len(m.LastCommitHash) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } if m.VotingPower != 0 { n += 1 + sovQuery(uint64(m.VotingPower)) } @@ -3656,40 +3612,6 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.LastCommitHash == nil { - m.LastCommitHash = []byte{} - } - iNdEx = postIndex - case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) } @@ -3860,40 +3782,6 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { } } case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.LastCommitHash == nil { - m.LastCommitHash = []byte{} - } - iNdEx = postIndex - case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) } From 386936519f3dd234bb0d54386637c11a8f69e668 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 27 Oct 2023 16:58:26 +0200 Subject: [PATCH 092/202] Improved btc light client (#93) * Update btc light client --- app/ante_btc_validation_decorator.go | 5 +- go.mod | 2 +- go.sum | 4 +- proto/babylon/btclightclient/v1/tx.proto | 15 +- .../zoneconcierge/v1/zoneconcierge.proto | 6 + test/e2e/configurer/chain/commands.go | 2 +- testutil/datagen/btc_header_chain.go | 118 +++ testutil/datagen/btc_header_info.go | 170 ++++- testutil/datagen/btc_header_tree.go | 257 ------- testutil/datagen/btc_transaction.go | 3 +- types/btcutils.go | 23 +- wasmbinding/bindings/query.go | 2 +- x/btccheckpoint/keeper/keeper.go | 10 - x/btccheckpoint/keeper/keeper_test.go | 8 +- x/btccheckpoint/keeper/msg_server_test.go | 163 ++--- x/btccheckpoint/types/expected_keepers.go | 5 +- x/btccheckpoint/types/mock_keepers.go | 12 +- x/btclightclient/client/cli/tx.go | 6 +- x/btclightclient/genesis.go | 4 + x/btclightclient/genesis_test.go | 11 +- x/btclightclient/keeper/base_btc_header.go | 9 +- x/btclightclient/keeper/grpc_query.go | 15 +- x/btclightclient/keeper/grpc_query_test.go | 126 ++-- x/btclightclient/keeper/keeper.go | 272 +++---- x/btclightclient/keeper/keeper_test.go | 690 +++++++++--------- x/btclightclient/keeper/msg_server.go | 147 +--- x/btclightclient/keeper/msg_server_test.go | 219 +++--- x/btclightclient/keeper/state.go | 386 +++------- x/btclightclient/keeper/state_test.go | 600 ++------------- x/btclightclient/keeper/utils_test.go | 104 ++- x/btclightclient/types/btc_header_info.go | 4 - x/btclightclient/types/btc_light_client.go | 431 +++++++++++ x/btclightclient/types/codec.go | 4 +- x/btclightclient/types/genesis.go | 46 +- x/btclightclient/types/keys.go | 31 +- x/btclightclient/types/keys_test.go | 20 +- x/btclightclient/types/msgs.go | 51 +- x/btclightclient/types/msgs_test.go | 31 +- x/btclightclient/types/querier_test.go | 11 +- x/btclightclient/types/tx.pb.go | 194 ++--- x/btclightclient/types/utils.go | 18 + x/btclightclient/types/work.go | 7 +- x/monitor/keeper/grpc_query_test.go | 39 +- x/zoneconcierge/keeper/epochs.go | 21 +- x/zoneconcierge/keeper/grpc_query_test.go | 3 +- x/zoneconcierge/keeper/hooks.go | 11 +- .../keeper/ibc_packet_btc_timestamp.go | 65 +- x/zoneconcierge/types/expected_keepers.go | 6 +- x/zoneconcierge/types/keys.go | 2 +- x/zoneconcierge/types/mocked_keepers.go | 167 ++--- x/zoneconcierge/types/zoneconcierge.pb.go | 302 ++++++-- 51 files changed, 2332 insertions(+), 2526 deletions(-) create mode 100644 testutil/datagen/btc_header_chain.go delete mode 100644 testutil/datagen/btc_header_tree.go create mode 100644 x/btclightclient/types/btc_light_client.go create mode 100644 x/btclightclient/types/utils.go diff --git a/app/ante_btc_validation_decorator.go b/app/ante_btc_validation_decorator.go index 109789023..79f800257 100644 --- a/app/ante_btc_validation_decorator.go +++ b/app/ante_btc_validation_decorator.go @@ -38,10 +38,9 @@ func (bvd BtcValidationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulat return ctx, btccheckpointtypes.ErrInvalidCheckpointProof.Wrap(err.Error()) } - case *btclightclient.MsgInsertHeader: + case *btclightclient.MsgInsertHeaders: powLimit := bvd.BtcCfg.PowLimit() - err := msg.ValidateHeader(&powLimit) - + err := msg.ValidateHeaders(&powLimit) if err != nil { return ctx, btclightclient.ErrInvalidProofOfWOrk } diff --git a/go.mod b/go.mod index 5d4414b98..b221a8a12 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/babylonchain/babylon require ( github.com/CosmWasm/wasmd v0.40.0 - github.com/btcsuite/btcd v0.23.4 + github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 github.com/cosmos/cosmos-sdk v0.47.3 diff --git a/go.sum b/go.sum index 070e1094e..64fb56bb8 100644 --- a/go.sum +++ b/go.sum @@ -268,8 +268,8 @@ github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6Z github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= -github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ= -github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 h1:FZR6mILlSI/GDx8ydNVBZAlXlRXsoRBWX2Un64mpfsI= +github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= diff --git a/proto/babylon/btclightclient/v1/tx.proto b/proto/babylon/btclightclient/v1/tx.proto index 30127aaee..52014be2b 100644 --- a/proto/babylon/btclightclient/v1/tx.proto +++ b/proto/babylon/btclightclient/v1/tx.proto @@ -7,18 +7,17 @@ option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; // Msg defines the Msg service. service Msg { - // InsertHeader adds a header to the BTC light client chain maintained by - // Babylon. - rpc InsertHeader(MsgInsertHeader) returns (MsgInsertHeaderResponse) {}; + // InsertHeaders adds a batch of headers to the BTC light client chain + rpc InsertHeaders(MsgInsertHeaders) returns (MsgInsertHeadersResponse) {}; } -// MsgInsertHeader defines the message for incoming header bytes -message MsgInsertHeader { +// MsgInsertHeaders defines the message for multiple incoming header bytes +message MsgInsertHeaders { string signer = 1; - bytes header = 2 + repeated bytes headers = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderBytes" ]; } +// MsgInsertHeadersResponse defines the response for the InsertHeaders transaction +message MsgInsertHeadersResponse {} -// MsgInsertHeaderResponse defines the response for the InsertHeader transaction -message MsgInsertHeaderResponse {} diff --git a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto index 53fa995a8..0c24a491b 100644 --- a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto +++ b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto @@ -9,6 +9,7 @@ import "babylon/btccheckpoint/v1/btccheckpoint.proto"; import "babylon/checkpointing/v1/bls_key.proto"; import "babylon/checkpointing/v1/checkpoint.proto"; import "babylon/epoching/v1/epoching.proto"; +import "babylon/btclightclient/v1/btclightclient.proto"; option go_package = "github.com/babylonchain/babylon/x/zoneconcierge/types"; @@ -131,3 +132,8 @@ message ProofFinalizedChainInfo { // checkpoint submission repeated babylon.btccheckpoint.v1.TransactionInfo proof_epoch_submitted = 7; } + +// Btc light client chain segment grown during last finalized epoch +message BTCChainSegment { + repeated babylon.btclightclient.v1.BTCHeaderInfo btc_headers = 2; +} diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index a085f8e53..b18612f81 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -55,7 +55,7 @@ func (n *NodeConfig) BankSend(amount string, sendAddress string, receiveAddress func (n *NodeConfig) SendHeaderHex(headerHex string) { n.LogActionF("btclightclient sending header %s", headerHex) - cmd := []string{"babylond", "tx", "btclightclient", "insert-header", headerHex, "--from=val", "--gas=500000"} + cmd := []string{"babylond", "tx", "btclightclient", "insert-headers", headerHex, "--from=val", "--gas=500000"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully inserted header %s", headerHex) diff --git a/testutil/datagen/btc_header_chain.go b/testutil/datagen/btc_header_chain.go new file mode 100644 index 000000000..f2d37ed7f --- /dev/null +++ b/testutil/datagen/btc_header_chain.go @@ -0,0 +1,118 @@ +package datagen + +import ( + "math/rand" + + sdkmath "cosmossdk.io/math" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" +) + +// Init header is always simnet header due to need to solve pow +var initHeader = chaincfg.SimNetParams.GenesisBlock.Header + +type BTCHeaderPartialChain struct { + // slice of Headers forming valid chain + Headers []*wire.BlockHeader + initialHeaderHeight uint64 + inititialHeaderTotalWork sdkmath.Uint +} + +func NewBTCHeaderChainWithLength( + r *rand.Rand, + initialHeaderHeight uint64, + initialHeaderTotalWork uint64, + length uint32) *BTCHeaderPartialChain { + return NewBTCHeaderChainFromParent( + r, + initialHeaderHeight, + sdkmath.NewUint(initialHeaderTotalWork), + &initHeader, + length, + ) +} + +func NewBTCHeaderChainFromParentInfo( + r *rand.Rand, + parent *types.BTCHeaderInfo, + length uint32, +) *BTCHeaderPartialChain { + return NewBTCHeaderChainFromParent( + r, + parent.Height+1, + *parent.Work, + parent.Header.ToBlockHeader(), + length, + ) +} + +func NewBTCHeaderChainFromParent( + r *rand.Rand, + initialHeaderHeight uint64, + initialHeaderTotalWork sdkmath.Uint, + parent *wire.BlockHeader, + length uint32, +) *BTCHeaderPartialChain { + headers := GenRandomValidChainStartingFrom( + r, + initialHeaderHeight, + parent, + nil, + length, + ) + return &BTCHeaderPartialChain{ + Headers: headers, + initialHeaderHeight: initialHeaderHeight, + inititialHeaderTotalWork: initialHeaderTotalWork, + } +} + +func (c *BTCHeaderPartialChain) GetChainInfo() []*types.BTCHeaderInfo { + return ChainToInfoChain(c.Headers, c.initialHeaderHeight, c.inititialHeaderTotalWork) +} + +func (c *BTCHeaderPartialChain) ChainToBytes() []bbn.BTCHeaderBytes { + chainBytes := make([]bbn.BTCHeaderBytes, 0) + for _, header := range c.Headers { + h := header + bytes := bbn.NewBTCHeaderBytesFromBlockHeader(h) + chainBytes = append(chainBytes, bytes) + } + + return chainBytes +} + +func (c *BTCHeaderPartialChain) GetTipInfo() *types.BTCHeaderInfo { + chainInfo := ChainToInfoChain(c.Headers, c.initialHeaderHeight, c.inititialHeaderTotalWork) + return chainInfo[len(chainInfo)-1] +} + +func (c *BTCHeaderPartialChain) TipHeader() *wire.BlockHeader { + return c.Headers[len(c.Headers)-1] +} + +func (c *BTCHeaderPartialChain) GetRandomHeaderInfo(r *rand.Rand) *types.BTCHeaderInfo { + randIdx := RandomInt(r, len(c.Headers)) + headerInfo := ChainToInfoChain(c.Headers, c.initialHeaderHeight, c.inititialHeaderTotalWork) + return headerInfo[randIdx] +} + +func (c *BTCHeaderPartialChain) GetRandomHeaderInfoNoTip(r *rand.Rand) *types.BTCHeaderInfo { + randIdx := RandomInt(r, len(c.Headers)-1) + headerInfo := ChainToInfoChain(c.Headers, c.initialHeaderHeight, c.inititialHeaderTotalWork) + return headerInfo[randIdx] +} + +func (c *BTCHeaderPartialChain) ChainLength() int { + return len(c.Headers) +} + +func (c *BTCHeaderPartialChain) GetHeadersMap() map[string]*wire.BlockHeader { + headersMap := make(map[string]*wire.BlockHeader) + for _, header := range c.Headers { + headersMap[header.BlockHash().String()] = header + } + return headersMap +} diff --git a/testutil/datagen/btc_header_info.go b/testutil/datagen/btc_header_info.go index 4c7d1b378..25284ee19 100644 --- a/testutil/datagen/btc_header_info.go +++ b/testutil/datagen/btc_header_info.go @@ -1,18 +1,69 @@ package datagen import ( + "math/big" + "math/rand" + "time" + sdkmath "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" - "math/big" - "math/rand" - "time" ) +type RetargetInfo struct { + LastRetargetHeader *wire.BlockHeader + Params *chaincfg.Params +} + +type TimeBetweenBlocksInfo struct { + Time time.Duration +} + +// Difficulty calculation copied from btcd +// https://github.com/btcsuite/btcd/blob/master/blockchain/difficulty.go#L221 +func calculateAdjustedDifficulty( + lastRetargetHeader *wire.BlockHeader, + currentHeaderTimestamp time.Time, + params *chaincfg.Params) uint32 { + + targetTimespan := int64(params.TargetTimespan / time.Second) + adjustmentFactor := params.RetargetAdjustmentFactor + minRetargetTimespan := targetTimespan / adjustmentFactor + maxRetargetTimespan := targetTimespan * adjustmentFactor + + // Limit the amount of adjustment that can occur to the previous + // difficulty. + actualTimespan := currentHeaderTimestamp.Unix() - lastRetargetHeader.Timestamp.Unix() + adjustedTimespan := actualTimespan + if actualTimespan < minRetargetTimespan { + adjustedTimespan = minRetargetTimespan + } else if actualTimespan > maxRetargetTimespan { + adjustedTimespan = maxRetargetTimespan + } + + // Calculate new target difficulty as: + // currentDifficulty * (adjustedTimespan / targetTimespan) + // The result uses integer division which means it will be slightly + // rounded down. Bitcoind also uses integer division to calculate this + // result. + oldTarget := blockchain.CompactToBig(lastRetargetHeader.Bits) + newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan)) + newTarget.Div(newTarget, big.NewInt(targetTimespan)) + + // Limit new value to the proof of work limit. + if newTarget.Cmp(params.PowLimit) > 0 { + newTarget.Set(params.PowLimit) + } + + newTargetBits := blockchain.BigToCompact(newTarget) + return newTargetBits +} + func GenRandomBtcdHeader(r *rand.Rand) *wire.BlockHeader { version := GenRandomBTCHeaderVersion(r) bits := GenRandomBTCHeaderBits(r) @@ -145,15 +196,8 @@ func GenRandomBTCHeaderInfoWithParent(r *rand.Rand, parent *btclightclienttypes. // WARNING: if parent is from network with a lot of work (mainnet) it may never finish // use only with simnet headers func GenRandomValidBTCHeaderInfoWithParent(r *rand.Rand, parent btclightclienttypes.BTCHeaderInfo) *btclightclienttypes.BTCHeaderInfo { - randHeader := GenRandomBtcdHeader(r) parentHeader := parent.Header.ToBlockHeader() - - randHeader.Version = parentHeader.Version - randHeader.PrevBlock = parentHeader.BlockHash() - randHeader.Bits = parentHeader.Bits - randHeader.Timestamp = parentHeader.Timestamp.Add(50 * time.Second) - SolveBlock(randHeader) - + randHeader := GenRandomBtcdValidHeader(r, parentHeader, nil, nil) headerBytes := bbn.NewBTCHeaderBytesFromBlockHeader(randHeader) accumulatedWork := btclightclienttypes.CalcWork(&headerBytes) @@ -167,6 +211,110 @@ func GenRandomValidBTCHeaderInfoWithParent(r *rand.Rand, parent btclightclientty } } +// random duration between 4 and 12mins in seconds +func GenRandomTimeBetweenBlocks(r *rand.Rand) time.Duration { + return time.Duration(r.Int63n(8*60)+4*60) * time.Second +} + +func GenRandomBtcdValidHeader( + r *rand.Rand, + parent *wire.BlockHeader, + timeAfterParent *TimeBetweenBlocksInfo, + retargetInfo *RetargetInfo, +) *wire.BlockHeader { + randHeader := GenRandomBtcdHeader(r) + randHeader.Version = 4 + randHeader.PrevBlock = parent.BlockHash() + + if timeAfterParent == nil { + // random time after + randHeader.Timestamp = parent.Timestamp.Add(GenRandomTimeBetweenBlocks(r)) + } else { + randHeader.Timestamp = parent.Timestamp.Add(timeAfterParent.Time) + } + + if retargetInfo == nil { + // If no retarget info is provided, then we assume that the difficulty is the same as the parent + randHeader.Bits = parent.Bits + } else { + // If retarget info is provided, then we calculate the difficulty based on the info provided + randHeader.Bits = calculateAdjustedDifficulty( + retargetInfo.LastRetargetHeader, + parent.Timestamp, + retargetInfo.Params, + ) + } + SolveBlock(randHeader) + return randHeader +} + +func GenRandomValidChainStartingFrom( + r *rand.Rand, + parentHeaderHeight uint64, + parentHeader *wire.BlockHeader, + timeBetweenBlocks *TimeBetweenBlocksInfo, + numHeaders uint32, +) []*wire.BlockHeader { + if numHeaders == 0 { + return []*wire.BlockHeader{} + } + + headers := make([]*wire.BlockHeader, numHeaders) + for i := uint32(0); i < numHeaders; i++ { + if i == 0 { + headers[i] = GenRandomBtcdValidHeader(r, parentHeader, timeBetweenBlocks, nil) + continue + } + + headers[i] = GenRandomBtcdValidHeader(r, headers[i-1], timeBetweenBlocks, nil) + } + return headers +} + +func ChainToInfoChain( + chain []*wire.BlockHeader, + initialHeaderNumber uint64, + initialHeaderTotalWork sdkmath.Uint, +) []*btclightclienttypes.BTCHeaderInfo { + if len(chain) == 0 { + return []*btclightclienttypes.BTCHeaderInfo{} + } + + infoChain := make([]*btclightclienttypes.BTCHeaderInfo, len(chain)) + + totalDifficulty := initialHeaderTotalWork + + for i, header := range chain { + headerWork := btclightclienttypes.CalcHeaderWork(header) + headerTotalDifficulty := btclightclienttypes.CumulativeWork(headerWork, totalDifficulty) + hash := header.BlockHash() + headerBytes := bbn.NewBTCHeaderBytesFromBlockHeader(header) + headerHash := bbn.NewBTCHeaderHashBytesFromChainhash(&hash) + headerNumber := initialHeaderNumber + uint64(i) + + headerInfo := btclightclienttypes.NewBTCHeaderInfo( + &headerBytes, + &headerHash, + headerNumber, + &headerTotalDifficulty, + ) + + infoChain[i] = headerInfo + + totalDifficulty = headerTotalDifficulty + } + + return infoChain +} + +func HeaderToHeaderBytes(headers []*wire.BlockHeader) []bbn.BTCHeaderBytes { + headerBytes := make([]bbn.BTCHeaderBytes, len(headers)) + for i, header := range headers { + headerBytes[i] = bbn.NewBTCHeaderBytesFromBlockHeader(header) + } + return headerBytes +} + func GenRandomBTCHeaderInfoWithBits(r *rand.Rand, bits *sdkmath.Uint) *btclightclienttypes.BTCHeaderInfo { return GenRandomBTCHeaderInfoWithParentAndBits(r, nil, bits) } diff --git a/testutil/datagen/btc_header_tree.go b/testutil/datagen/btc_header_tree.go deleted file mode 100644 index 43151da0c..000000000 --- a/testutil/datagen/btc_header_tree.go +++ /dev/null @@ -1,257 +0,0 @@ -package datagen - -import ( - blctypes "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "math/rand" -) - -type BTCHeaderTree struct { - // headers is a map of unique identifies to BTCHeaderInfo objects - headers map[string]*blctypes.BTCHeaderInfo - // children is a map of unique identifies to unique identifiers for children - children map[string][]string -} - -func NewBTCHeaderTree() *BTCHeaderTree { - headers := make(map[string]*blctypes.BTCHeaderInfo, 0) - children := make(map[string][]string, 0) - return &BTCHeaderTree{headers: headers, children: children} -} - -// Add adds a node into storage. If the `parent` is set, -// it is also added to the list of `parent`. -func (t *BTCHeaderTree) Add(node *blctypes.BTCHeaderInfo, parent *blctypes.BTCHeaderInfo) { - t.headers[node.Hash.String()] = node - if parent != nil { - t.children[parent.Hash.String()] = append(t.children[parent.Hash.String()], node.Hash.String()) - } -} - -// Contains checks whether a node is maintained in the internal storage -func (t *BTCHeaderTree) Contains(node *blctypes.BTCHeaderInfo) bool { - if _, ok := t.headers[node.Hash.String()]; ok { - return true - } - return false -} - -// GetRoot returns the root of the tree -- i.e. the node without an existing parent -func (t *BTCHeaderTree) GetRoot() *blctypes.BTCHeaderInfo { - for _, header := range t.headers { - if t.GetParent(header) == nil { - return header - } - } - return nil -} - -// GetTip returns the header in the tree with the most work -func (t *BTCHeaderTree) GetTip() *blctypes.BTCHeaderInfo { - maxWork := sdk.NewUint(0) - var tip *blctypes.BTCHeaderInfo - for _, node := range t.headers { - if node.Work.GT(maxWork) { - maxWork = *node.Work - tip = node - } - } - return tip -} - -// GetMainChain returns the tree fork with the most work -func (t *BTCHeaderTree) GetMainChain() []*blctypes.BTCHeaderInfo { - tip := t.GetTip() - return t.GetNodeAncestry(tip) -} - -// RandNumChildren randomly generates 0-2 children with the following probabilities: -// If zeroChildrenAllowed is not set: -// -// 1 child: 75% -// 2 children: 25% -// -// Otherwise, -// -// 0 children: 25% -// 1 child: 50% -// 2 children: 25% -func (t *BTCHeaderTree) RandNumChildren(r *rand.Rand, zeroChildrenAllowed bool) int { - // Randomly identify the number of children - numChildren := 0 - // If the flag is not set, then we need to generate a child for sure - if !zeroChildrenAllowed { - numChildren = 1 // 75% chance of 1 child now - } - if OneInN(r, 2) { - // 50% of the times, one child - numChildren = 1 - } else if OneInN(r, 2) { - // 25% of the times, 2 children - // Implies that 25% of the times 0 children - numChildren = 2 - } - return numChildren -} - -// GenRandomBTCHeaderTree recursively generates a random tree of BTCHeaderInfo objects rooted at `parent`. -// The tree generation is accomplished by randomly selecting the number of children using the `RandNumChildren()`. -// Then, for each child, a random BTCHeaderInfo object is generated and a new tree rooted -// at that child is recursively generated. -// For each node that is generated, the callback function is invoked in order to identify -// whether we should continue generating or not as well as help with maintenance -// tasks (e.g. inserting headers into keeper storage). -func (t *BTCHeaderTree) GenRandomBTCHeaderTree(r *rand.Rand, minHeight uint64, maxHeight uint64, - parent *blctypes.BTCHeaderInfo, callback func(info *blctypes.BTCHeaderInfo) bool) { - - if maxHeight == 0 { - // If we generate more, we exceed the maximum height - return - } - - const maxRetries = 3 - retries := 0 - // Generate the children of the parent - for i := 0; i < t.RandNumChildren(r, minHeight <= 1); i++ { - childInfo := GenRandomBTCHeaderInfoWithParent(r, parent) - - // Rare occasion that we get the same hash, skip - if t.Contains(childInfo) { - // Only retry up to 3 times to generate the child - if retries < maxRetries { - i -= 1 - } - retries += 1 - continue - } - - // Only generate `minHeight-1` subtrees for the first child - childMinHeight := uint64(0) - if i == 0 && minHeight-1 > 0 { - childMinHeight = minHeight - 1 - } - if callback(childInfo) { - t.Add(childInfo, parent) - t.GenRandomBTCHeaderTree(r, childMinHeight, maxHeight-1, childInfo, callback) - } - } -} - -// RandomNode selects a random header from the list of nodes -func (t *BTCHeaderTree) RandomNode(r *rand.Rand) *blctypes.BTCHeaderInfo { - randIdx := RandomInt(r, len(t.headers)) - var idx uint64 = 0 - for _, node := range t.headers { - if idx == randIdx { - return node - } - idx += 1 - } - return nil -} - -// getNodeAncestryUpToUtil recursively iterates the parents of the node until the root node is reached -func (t *BTCHeaderTree) getNodeAncestryUpToUtil(ancestry *[]*blctypes.BTCHeaderInfo, - node *blctypes.BTCHeaderInfo, upTo *blctypes.BTCHeaderInfo) { - - if upTo != nil && node.Eq(upTo) { - return - } - *ancestry = append(*ancestry, node) - parent := t.GetParent(node) - if parent != nil { - t.getNodeAncestryUpToUtil(ancestry, parent, upTo) - } -} - -// GetNodeAncestryUpTo returns an ancestry list starting from the tree node and -// leading to a child of the `upTo` parameter if it is not nil. -func (t *BTCHeaderTree) GetNodeAncestryUpTo(node *blctypes.BTCHeaderInfo, - upTo *blctypes.BTCHeaderInfo) []*blctypes.BTCHeaderInfo { - - ancestry := make([]*blctypes.BTCHeaderInfo, 0) - t.getNodeAncestryUpToUtil(&ancestry, node, upTo) - return ancestry -} - -// GetNodeAncestry returns an ancestry list starting from the tree node and -// leading to the root of the tree. -func (t *BTCHeaderTree) GetNodeAncestry(node *blctypes.BTCHeaderInfo) []*blctypes.BTCHeaderInfo { - return t.GetNodeAncestryUpTo(node, nil) -} - -// RandomAncestor retrieves the ancestry list and returns an ancestor from it. -// Can include the node itself. -func (t *BTCHeaderTree) RandomAncestor(r *rand.Rand, node *blctypes.BTCHeaderInfo) *blctypes.BTCHeaderInfo { - ancestry := t.GetNodeAncestry(node) - idx := RandomInt(r, len(ancestry)) - return ancestry[idx] -} - -// IsOnNodeChain returns true or false depending on whether the node -// is equal or a descendant of the `ancestor` parameter. -func (t *BTCHeaderTree) IsOnNodeChain(node *blctypes.BTCHeaderInfo, ancestor *blctypes.BTCHeaderInfo) bool { - if node.Eq(ancestor) { - return true - } - ancestryUpTo := t.GetNodeAncestryUpTo(node, ancestor) - lastElement := ancestryUpTo[len(ancestryUpTo)-1] - parent := t.GetParent(lastElement) - if parent != nil && parent.Eq(ancestor) { - return true - } - return false -} - -// GetChildren returns the children of a node as a list of BTCHeaderInfo objects -func (t *BTCHeaderTree) GetChildren(node *blctypes.BTCHeaderInfo) []*blctypes.BTCHeaderInfo { - if !t.Contains(node) { - panic("Retrieving children of non existent node") - } - childrenHash := t.children[node.Hash.String()] - children := make([]*blctypes.BTCHeaderInfo, 0) - for _, childHash := range childrenHash { - children = append(children, t.headers[childHash]) - } - return children -} - -// getNodeDescendantsUtil recursively iterates the descendants of a node and adds them to a list -func (t *BTCHeaderTree) getNodeDescendantsUtil(descendants *[]*blctypes.BTCHeaderInfo, node *blctypes.BTCHeaderInfo) { - *descendants = append(*descendants, node) - for _, child := range t.GetChildren(node) { - t.getNodeDescendantsUtil(descendants, child) - } -} - -// GetNodeDescendants returns a list of the descendants of a node -func (t *BTCHeaderTree) GetNodeDescendants(node *blctypes.BTCHeaderInfo) []*blctypes.BTCHeaderInfo { - descendants := make([]*blctypes.BTCHeaderInfo, 0) - t.getNodeDescendantsUtil(&descendants, node) - return descendants -} - -// RandomDescendant returns a random descendant of the node -func (t *BTCHeaderTree) RandomDescendant(r *rand.Rand, node *blctypes.BTCHeaderInfo) *blctypes.BTCHeaderInfo { - descendants := t.GetNodeDescendants(node) - idx := RandomInt(r, len(descendants)) - return descendants[idx] -} - -// GetHeadersMap returns a mapping between node hashes and nodes -func (t *BTCHeaderTree) GetHeadersMap() map[string]*blctypes.BTCHeaderInfo { - return t.headers -} - -// Size returns the number of nodes that are maintained -func (t *BTCHeaderTree) Size() int { - return len(t.headers) -} - -// GetParent returns the parent of the node, or nil if it doesn't exist -func (t *BTCHeaderTree) GetParent(node *blctypes.BTCHeaderInfo) *blctypes.BTCHeaderInfo { - if header, ok := t.headers[node.Header.ParentHash().String()]; ok { - return header - } - return nil -} diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 20774fb17..3e0dc8509 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -312,7 +312,8 @@ func CreateBlockWithTransaction( transactions = append(transactions, tx) randHeader := GenRandomBtcdHeader(r) - randHeader.Version = ph.Version + // for simnet, requirement for block versions to be >= 4, is enabled from initial block + randHeader.Version = 4 randHeader.PrevBlock = ph.BlockHash() randHeader.Bits = ph.Bits randHeader.Timestamp = ph.Timestamp.Add(50 * time.Second) diff --git a/types/btcutils.go b/types/btcutils.go index 4f4c08540..cb43f8848 100644 --- a/types/btcutils.go +++ b/types/btcutils.go @@ -7,8 +7,8 @@ import ( "time" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" ) // ValidateBTCHeader @@ -37,18 +37,6 @@ func ValidateBTCHeader(header *wire.BlockHeader, powLimit *big.Int) error { return nil } -func GetBaseBTCHeaderHex() string { - // TODO: get this from a configuration file - hex := "00006020c6c5a20e29da938a252c945411eba594cbeba021a1e20000000000000000000039e4bd0cd0b5232bb380a9576fcfe7d8fb043523f7a158187d9473e44c1740e6b4fa7c62ba01091789c24c22" - return hex -} - -func GetBaseBTCHeaderHeight() uint64 { - // TODO: get this from a configuration file - height := uint64(736056) - return height -} - func GetMaxDifficulty() big.Int { // Maximum btc difficulty possible // Use it to set the difficulty bits of blocks as well as the upper PoW limit @@ -61,12 +49,3 @@ func GetMaxDifficulty() big.Int { } return *maxDifficulty } - -func GetBaseBTCHeaderBytes() BTCHeaderBytes { - hex := GetBaseBTCHeaderHex() - headerBytes, err := NewBTCHeaderBytesFromHex(hex) - if err != nil { - panic("Base BTC header hex cannot be converted to bytes") - } - return headerBytes -} diff --git a/wasmbinding/bindings/query.go b/wasmbinding/bindings/query.go index 7bef9606f..cd23181f9 100644 --- a/wasmbinding/bindings/query.go +++ b/wasmbinding/bindings/query.go @@ -14,7 +14,7 @@ type BtcHeaderByHash struct { } type BtcHeaderByHeight struct { - Height uint64 `json:"height,omitempty"` + Height uint64 `json:"height"` } type CurrentEpochResponse struct { diff --git a/x/btccheckpoint/keeper/keeper.go b/x/btccheckpoint/keeper/keeper.go index 87a3a5b33..2a9a7b05a 100644 --- a/x/btccheckpoint/keeper/keeper.go +++ b/x/btccheckpoint/keeper/keeper.go @@ -51,10 +51,6 @@ const ( submissionUnknownErr submissionBtcError = submissionBtcError( "One of submission blocks is not known to btclightclient", ) - - subbmisionOnForkErr submissionBtcError = submissionBtcError( - "One of submission blocks is not on the btc mainchain ", - ) ) func NewKeeper( @@ -117,12 +113,6 @@ func (k Keeper) headerDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) // one of blocks is not known to light client return 0, submissionUnknownErr } - - if blockDepth < 0 { - // one of submission blocks is on fork, treat whole submission as being on fork - return 0, subbmisionOnForkErr - } - return uint64(blockDepth), nil } diff --git a/x/btccheckpoint/keeper/keeper_test.go b/x/btccheckpoint/keeper/keeper_test.go index 97a360232..5eed1abd6 100644 --- a/x/btccheckpoint/keeper/keeper_test.go +++ b/x/btccheckpoint/keeper/keeper_test.go @@ -49,8 +49,8 @@ func TestKeeper_GetSubmissionBtcInfo(t *testing.T) { {Index: tt.args.Key2.TxIdx, Hash: hash2}, }} - k.BTCLightClient.SetDepth(hash1, int64(tt.args.Key1.Depth)) - k.BTCLightClient.SetDepth(hash2, int64(tt.args.Key2.Depth)) + k.BTCLightClient.SetDepth(hash1, tt.args.Key1.Depth) + k.BTCLightClient.SetDepth(hash2, tt.args.Key2.Depth) info, err := k.BTCCheckpoint.GetSubmissionBtcInfo(k.SdkCtx, sk) @@ -90,8 +90,8 @@ func FuzzGetSubmissionBtcInfo(f *testing.F) { {Index: txidx2, Hash: hash2}, }} - k.BTCLightClient.SetDepth(hash1, int64(depth1)) - k.BTCLightClient.SetDepth(hash2, int64(depth2)) + k.BTCLightClient.SetDepth(hash1, uint64(depth1)) + k.BTCLightClient.SetDepth(hash2, uint64(depth2)) info, err := k.BTCCheckpoint.GetSubmissionBtcInfo(k.SdkCtx, sk) require.NoError(t, err) diff --git a/x/btccheckpoint/keeper/msg_server_test.go b/x/btccheckpoint/keeper/msg_server_test.go index c05ef5698..0129a0528 100644 --- a/x/btccheckpoint/keeper/msg_server_test.go +++ b/x/btccheckpoint/keeper/msg_server_test.go @@ -97,8 +97,8 @@ func TestRejectDuplicatedSubmission(t *testing.T) { msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) // Now we will return depth enough for moving submission to be submitted - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(1)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(1)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(1)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(1)) _, err := tk.insertProofMsg(msg) @@ -135,50 +135,13 @@ func TestRejectUnknownToBtcLightClient(t *testing.T) { require.ErrorContainsf(t, err, btcctypes.ErrInvalidHeader.Error(), "Processing should return invalid header error") // even if one header is known, submission should still be considered invalid - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(1)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(1)) _, err = tk.insertProofMsg(msg) require.ErrorContainsf(t, err, btcctypes.ErrInvalidHeader.Error(), "Processing should return invalid header error") } -func TestRejectSubmissionsNotOnMainchain(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - epoch := uint64(1) - raw, _ := dg.RandomRawCheckpointDataForEpoch(r, epoch) - - blck1 := dg.CreateBlock(r, 1, 7, 7, raw.FirstPart) - blck2 := dg.CreateBlock(r, 2, 14, 3, raw.SecondPart) - - tk := InitTestKeepers(t) - - msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) - - // both headers on fork, fail - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(-1)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(-1)) - - _, err := tk.insertProofMsg(msg) - - require.ErrorContainsf(t, err, btcctypes.ErrInvalidHeader.Error(), "Processing should return invalid header error") - - // one header on fork, one on main chain, fail - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(0)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(-1)) - - _, err = tk.insertProofMsg(msg) - - require.ErrorContainsf(t, err, btcctypes.ErrInvalidHeader.Error(), "Processing should return invalid header error") - - // two headers on main chain, success - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(0)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(0)) - - _, err = tk.insertProofMsg(msg) - - require.NoError(t, err, "Processing msg should succeed") -} - func TestSubmitValidNewCheckpoint(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) epoch := uint64(1) @@ -192,8 +155,8 @@ func TestSubmitValidNewCheckpoint(t *testing.T) { msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) // Now we will return depth enough for moving submission to be submitted - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(1)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(1)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(1)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(1)) _, err := tk.insertProofMsg(msg) @@ -258,8 +221,8 @@ func TestRejectSubmissionWithoutSubmissionsForPreviousEpoch(t *testing.T) { msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) // Now we will return depth enough for moving submission to be submitted - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(0)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(1)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(0)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(1)) _, err := tk.insertProofMsg(msg) @@ -283,8 +246,8 @@ func TestRejectSubmissionWithoutAncestorsOnMainchainInPreviousEpoch(t *testing.T msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{epoch1Block1, epoch1Block2}) // Now we will return depth enough for moving submission to be submitted - tk.BTCLightClient.SetDepth(epoch1Block1.HeaderBytes.Hash(), int64(5)) - tk.BTCLightClient.SetDepth(epoch1Block2.HeaderBytes.Hash(), int64(4)) + tk.BTCLightClient.SetDepth(epoch1Block1.HeaderBytes.Hash(), uint64(5)) + tk.BTCLightClient.SetDepth(epoch1Block2.HeaderBytes.Hash(), uint64(4)) _, err := tk.insertProofMsg(msg) @@ -297,8 +260,8 @@ func TestRejectSubmissionWithoutAncestorsOnMainchainInPreviousEpoch(t *testing.T msg2 := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{epoch2Block1, epoch2Block2}) // Both headers are deeper than epoch 1 submission, fail - tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), int64(7)) - tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), int64(6)) + tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), uint64(7)) + tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), uint64(6)) _, err = tk.insertProofMsg(msg2) @@ -310,8 +273,8 @@ func TestRejectSubmissionWithoutAncestorsOnMainchainInPreviousEpoch(t *testing.T ) // one header deeper than headers of previous epoch, one fresher, fail - tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), int64(7)) - tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), int64(3)) + tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), uint64(7)) + tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), uint64(3)) _, err = tk.insertProofMsg(msg2) @@ -323,8 +286,8 @@ func TestRejectSubmissionWithoutAncestorsOnMainchainInPreviousEpoch(t *testing.T ) // one header on the same depth as previous epoch, one fresher, fail - tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), int64(4)) - tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), int64(3)) + tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), uint64(4)) + tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), uint64(3)) _, err = tk.insertProofMsg(msg2) @@ -336,8 +299,8 @@ func TestRejectSubmissionWithoutAncestorsOnMainchainInPreviousEpoch(t *testing.T ) // Both Headers fresher that previous epoch, succeed - tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), int64(3)) - tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), int64(2)) + tk.BTCLightClient.SetDepth(epoch2Block1.HeaderBytes.Hash(), uint64(3)) + tk.BTCLightClient.SetDepth(epoch2Block2.HeaderBytes.Hash(), uint64(2)) _, err = tk.insertProofMsg(msg2) @@ -350,38 +313,38 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { tk := InitTestKeepers(t) msg1 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(5)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), int64(4)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(5)) + tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(4)) _, err := tk.insertProofMsg(msg1) require.NoError(t, err, "failed to insert submission for epoch 1") msg1a := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg1a), int64(4)) - tk.BTCLightClient.SetDepth(b2Hash(msg1a), int64(5)) + tk.BTCLightClient.SetDepth(b1Hash(msg1a), uint64(4)) + tk.BTCLightClient.SetDepth(b2Hash(msg1a), uint64(5)) _, err = tk.insertProofMsg(msg1a) require.NoError(t, err, "failed to insert submission for epoch 1") msg2 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 2) - tk.BTCLightClient.SetDepth(b1Hash(msg2), int64(3)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), int64(2)) + tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(3)) + tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(2)) _, err = tk.insertProofMsg(msg2) require.NoError(t, err, "failed to insert submission for epoch 2") msg2a := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 2) - tk.BTCLightClient.SetDepth(b1Hash(msg2a), int64(3)) - tk.BTCLightClient.SetDepth(b2Hash(msg2a), int64(2)) + tk.BTCLightClient.SetDepth(b1Hash(msg2a), uint64(3)) + tk.BTCLightClient.SetDepth(b2Hash(msg2a), uint64(2)) _, err = tk.insertProofMsg(msg2a) require.NoError(t, err, "failed to insert submission for epoch 2") msg3 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 3) - tk.BTCLightClient.SetDepth(b1Hash(msg3), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg3), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg3), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg3), uint64(0)) _, err = tk.insertProofMsg(msg3) require.NoError(t, err, "failed to insert submission for epoch 3") msg3a := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 3) - tk.BTCLightClient.SetDepth(b1Hash(msg3a), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg3a), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg3a), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg3a), uint64(0)) _, err = tk.insertProofMsg(msg3a) require.NoError(t, err, "failed to insert submission for epoch 3") @@ -396,7 +359,7 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { // Due to reorg one submission from epoch 1 lands on fork, which means it is no // longer vaiable. It should be pruned. Other subbmissions should be left // intact - tk.BTCLightClient.SetDepth(b2Hash(msg1), -1) + tk.BTCLightClient.DeleteHeader(b2Hash(msg1)) tk.onTipChange() @@ -416,7 +379,7 @@ func TestClearChildEpochsWhenNoParenNotOnMainChain(t *testing.T) { // second submission from epoch 1 got orphaned. Clear it, and submissions from // child epochs - tk.BTCLightClient.SetDepth(b2Hash(msg1a), -1) + tk.BTCLightClient.DeleteHeader(b2Hash(msg1a)) tk.onTipChange() @@ -436,20 +399,20 @@ func TestLeaveOnlyBestSubmissionWhenEpochFinalized(t *testing.T) { wDeep := defaultParams.CheckpointFinalizationTimeout msg1 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(0)) _, err := tk.insertProofMsg(msg1) require.NoError(t, err, "failed to insert submission") msg2 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg2), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(0)) _, err = tk.insertProofMsg(msg2) require.NoError(t, err, "failed to insert submission") msg3 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg3), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg3), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg3), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg3), uint64(0)) _, err = tk.insertProofMsg(msg3) require.NoError(t, err, "failed to insert submission") @@ -458,12 +421,12 @@ func TestLeaveOnlyBestSubmissionWhenEpochFinalized(t *testing.T) { require.Len(t, ed.Keys, 3) // deepest submission is submission in msg3 - tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), int64(wDeep+1)) - tk.BTCLightClient.SetDepth(b1Hash(msg2), int64(wDeep+2)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), int64(wDeep+3)) - tk.BTCLightClient.SetDepth(b1Hash(msg3), int64(wDeep+4)) - tk.BTCLightClient.SetDepth(b2Hash(msg3), int64(wDeep+5)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(wDeep)) + tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(wDeep+1)) + tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(wDeep+2)) + tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(wDeep+3)) + tk.BTCLightClient.SetDepth(b1Hash(msg3), uint64(wDeep+4)) + tk.BTCLightClient.SetDepth(b2Hash(msg3), uint64(wDeep+5)) tk.onTipChange() @@ -485,14 +448,14 @@ func TestTxIdxShouldBreakTies(t *testing.T) { wDeep := defaultParams.CheckpointFinalizationTimeout msg1 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(0)) _, err := tk.insertProofMsg(msg1) require.NoError(t, err, "failed to insert submission") msg2 := dg.GenerateMessageWithRandomSubmitterForEpoch(r, 1) - tk.BTCLightClient.SetDepth(b1Hash(msg2), int64(1)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), int64(0)) + tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(1)) + tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(0)) _, err = tk.insertProofMsg(msg2) require.NoError(t, err, "failed to insert submission") @@ -503,10 +466,10 @@ func TestTxIdxShouldBreakTies(t *testing.T) { // Both submissions have the same depth the most fresh block i.e // it is the same block // When finalizing the one with lower TxIx should be treated as better - tk.BTCLightClient.SetDepth(b1Hash(msg1), int64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), int64(wDeep+1)) - tk.BTCLightClient.SetDepth(b1Hash(msg2), int64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), int64(wDeep+3)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(wDeep)) + tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(wDeep+1)) + tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(wDeep)) + tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(wDeep+3)) tk.onTipChange() @@ -543,8 +506,8 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) // Now we will return depth enough for moving submission to confirmed - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(1)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(1)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(1)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(1)) _, err := tk.insertProofMsg(msg) @@ -564,8 +527,8 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { } // Now we will return depth enough for moving submission to confirmed - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(kDeep)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(kDeep)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(kDeep)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(kDeep)) // fire tip change callback tk.onTipChange() @@ -581,8 +544,8 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { t.Errorf("Epoch should be in submitted stated") } - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(wDeep)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(wDeep)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(wDeep)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(wDeep)) tk.onTipChange() @@ -629,9 +592,9 @@ func FuzzConfirmAndDinalizeManyEpochs(f *testing.F) { msg := dg.GenerateMessageWithRandomSubmitter([]*dg.BlockCreationResult{blck1, blck2}) if epoch <= uint64(numFinalizedEpochs) { - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(finalizationDepth)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(finalizationDepth)) finalizationDepth = finalizationDepth - 1 - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(finalizationDepth)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(finalizationDepth)) // first submission is always deepest one, and second block is the most recent one if j == 1 { @@ -639,18 +602,18 @@ func FuzzConfirmAndDinalizeManyEpochs(f *testing.F) { } finalizationDepth = finalizationDepth - 1 } else if epoch <= uint64(numFinalizedEpochs+numConfirmedEpochs) { - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(confirmationDepth)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(confirmationDepth)) confirmationDepth = confirmationDepth - 1 - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(confirmationDepth)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(confirmationDepth)) // first submission is always deepest one, and second block is the most recent one if j == 1 { bestSumbissionInfos[epoch] = uint64(confirmationDepth) } confirmationDepth = confirmationDepth - 1 } else { - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), int64(sumbissionDepth)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(sumbissionDepth)) sumbissionDepth = sumbissionDepth - 1 - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), int64(sumbissionDepth)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(sumbissionDepth)) // first submission is always deepest one, and second block is the most recent one if j == 1 { bestSumbissionInfos[epoch] = uint64(sumbissionDepth) diff --git a/x/btccheckpoint/types/expected_keepers.go b/x/btccheckpoint/types/expected_keepers.go index 79f50f014..9a047165e 100644 --- a/x/btccheckpoint/types/expected_keepers.go +++ b/x/btccheckpoint/types/expected_keepers.go @@ -26,9 +26,8 @@ type BTCLightClientKeeper interface { // in case this is false it should return error BlockHeight(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) - // MainChainDepth returns the depth of the header in the main chain or -1 if it does not exist in it - // Error is returned if header is unknown to lightclient - MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (int64, error) + // MainChainDepth returns the depth of the header in the main chain or error if the header does not exist + MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) } type CheckpointingKeeper interface { diff --git a/x/btccheckpoint/types/mock_keepers.go b/x/btccheckpoint/types/mock_keepers.go index 1e734b040..1d426b64a 100644 --- a/x/btccheckpoint/types/mock_keepers.go +++ b/x/btccheckpoint/types/mock_keepers.go @@ -9,7 +9,7 @@ import ( ) type MockBTCLightClientKeeper struct { - headers map[string]int64 + headers map[string]uint64 } type MockCheckpointingKeeper struct { @@ -21,7 +21,7 @@ type MockIncentiveKeeper struct { func NewMockBTCLightClientKeeper() *MockBTCLightClientKeeper { lc := MockBTCLightClientKeeper{ - headers: make(map[string]int64), + headers: make(map[string]uint64), } return &lc } @@ -45,16 +45,20 @@ func (mc *MockCheckpointingKeeper) ReturnSuccess() { mc.returnError = false } -func (mc *MockBTCLightClientKeeper) SetDepth(header *bbn.BTCHeaderHashBytes, dd int64) { +func (mc *MockBTCLightClientKeeper) SetDepth(header *bbn.BTCHeaderHashBytes, dd uint64) { mc.headers[header.String()] = dd } +func (mc *MockBTCLightClientKeeper) DeleteHeader(header *bbn.BTCHeaderHashBytes) { + delete(mc.headers, header.String()) +} + func (mb MockBTCLightClientKeeper) BlockHeight(ctx sdk.Context, header *bbn.BTCHeaderHashBytes) (uint64, error) { // todo not used return uint64(10), nil } -func (ck MockBTCLightClientKeeper) MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (int64, error) { +func (ck MockBTCLightClientKeeper) MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) { depth, ok := ck.headers[headerBytes.String()] if ok { return depth, nil diff --git a/x/btclightclient/client/cli/tx.go b/x/btclightclient/client/cli/tx.go index 043ef24ba..bcf27f541 100644 --- a/x/btclightclient/client/cli/tx.go +++ b/x/btclightclient/client/cli/tx.go @@ -27,8 +27,8 @@ func GetTxCmd() *cobra.Command { func CmdTxInsertHeader() *cobra.Command { cmd := &cobra.Command{ - Use: "insert-header [header-bytes]", - Short: "submit BTC header bytes", + Use: "insert-headers [headers-bytes]", + Short: "submit BTC headers bytes", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -36,7 +36,7 @@ func CmdTxInsertHeader() *cobra.Command { return err } - msg, err := types.NewMsgInsertHeader(clientCtx.GetFromAddress(), args[0]) + msg, err := types.NewMsgInsertHeaders(clientCtx.GetFromAddress(), args[0]) if err != nil { return err } diff --git a/x/btclightclient/genesis.go b/x/btclightclient/genesis.go index 3230a5052..6de07fb09 100644 --- a/x/btclightclient/genesis.go +++ b/x/btclightclient/genesis.go @@ -9,6 +9,10 @@ import ( // InitGenesis initializes the capability module's state from a provided genesis // state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + if err := genState.Validate(); err != nil { + panic(err) + } + k.SetBaseBTCHeader(ctx, genState.BaseBtcHeader) } diff --git a/x/btclightclient/genesis_test.go b/x/btclightclient/genesis_test.go index 89e2b8b99..3f9ca2a2f 100644 --- a/x/btclightclient/genesis_test.go +++ b/x/btclightclient/genesis_test.go @@ -3,8 +3,6 @@ package btclightclient_test import ( "testing" - bbn "github.com/babylonchain/babylon/types" - keepertest "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/testutil/nullify" "github.com/babylonchain/babylon/x/btclightclient" @@ -13,14 +11,9 @@ import ( ) func TestGenesis(t *testing.T) { - headerBytes := bbn.GetBaseBTCHeaderBytes() - headerHeight := bbn.GetBaseBTCHeaderHeight() - headerHash := headerBytes.Hash() - headerWork := types.CalcWork(&headerBytes) - baseHeaderInfo := types.NewBTCHeaderInfo(&headerBytes, headerHash, headerHeight, &headerWork) - + baseHeaderInfo := types.SimnetGenesisBlock() genesisState := types.GenesisState{ - BaseBtcHeader: *baseHeaderInfo, + BaseBtcHeader: baseHeaderInfo, } k, ctx := keepertest.BTCLightClientKeeper(t) diff --git a/x/btclightclient/keeper/base_btc_header.go b/x/btclightclient/keeper/base_btc_header.go index 3e13947e6..bc96dccf0 100644 --- a/x/btclightclient/keeper/base_btc_header.go +++ b/x/btclightclient/keeper/base_btc_header.go @@ -6,15 +6,14 @@ import ( ) func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) *types.BTCHeaderInfo { - return k.headersState(ctx).GetBaseBTCHeader() + return k.headersState(ctx).BaseHeader() } -// SetBaseBTCHeader checks whether a base BTC header exists and -// if not inserts it into storage +// SetBaseBTCHeader checks whether a base BTC header exist, if not inserts it into storage func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BTCHeaderInfo) { - existingHeader := k.headersState(ctx).GetBaseBTCHeader() + existingHeader := k.headersState(ctx).BaseHeader() if existingHeader != nil { panic("A base BTC Header has already been set") } - k.headersState(ctx).CreateHeader(&baseBTCHeader) + k.headersState(ctx).insertHeader(&baseBTCHeader) } diff --git a/x/btclightclient/keeper/grpc_query.go b/x/btclightclient/keeper/grpc_query.go index 4b37d4c09..c8796c8c6 100644 --- a/x/btclightclient/keeper/grpc_query.go +++ b/x/btclightclient/keeper/grpc_query.go @@ -99,12 +99,11 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest) var nextKey []byte if req.Pagination.Reverse { var start, end uint64 - baseHeader := k.headersState(sdkCtx).GetBaseBTCHeader() + baseHeader := k.headersState(sdkCtx).BaseHeader() // The base header is located at the end of the mainchain // which requires starting at the end - mainchain := k.headersState(sdkCtx).GetMainChain() - // Reverse the mainchain -- we want to retrieve results starting from the base header - bbn.Reverse(mainchain) + mainchain := k.GetMainChainFrom(sdkCtx, 0) + if keyHeader == nil { keyHeader = baseHeader start = 0 @@ -138,7 +137,7 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest) // -1 because the depth denotes how many headers have been built on top of it depth := startHeaderDepth + req.Pagination.Limit - 1 // Retrieve the mainchain up to the depth - mainchain := k.headersState(sdkCtx).GetMainChainUpTo(depth) + mainchain := k.GetMainChainUpTo(sdkCtx, depth) // Check whether the key provided is part of the mainchain if uint64(len(mainchain)) <= startHeaderDepth || !mainchain[startHeaderDepth].Eq(keyHeader) { return nil, status.Error(codes.InvalidArgument, "header specified by key is not a part of the mainchain") @@ -175,7 +174,7 @@ func (k Keeper) BaseHeader(ctx context.Context, req *types.QueryBaseHeaderReques sdkCtx := sdk.UnwrapSDKContext(ctx) - baseHeader := k.headersState(sdkCtx).GetBaseBTCHeader() + baseHeader := k.headersState(sdkCtx).BaseHeader() return &types.QueryBaseHeaderResponse{Header: baseHeader}, nil } @@ -200,9 +199,5 @@ func (k Keeper) HeaderDepth(ctx context.Context, req *types.QueryHeaderDepthRequ return nil, err } - if depth < 0 { - return nil, types.ErrHeaderOnFork - } - return &types.QueryHeaderDepthResponse{Depth: uint64(depth)}, nil } diff --git a/x/btclightclient/keeper/grpc_query_test.go b/x/btclightclient/keeper/grpc_query_test.go index 98f02cdaa..92218af53 100644 --- a/x/btclightclient/keeper/grpc_query_test.go +++ b/x/btclightclient/keeper/grpc_query_test.go @@ -26,8 +26,7 @@ func FuzzHashesQuery(f *testing.F) { 5. If the pagination key is not a valid hash, an error is returned. Data Generation: - - Generate a random tree of headers and insert their hashes - into the hashToHeight storage. + - Generate a random chain of headers and insert into storage. - Generate a random `limit` to the query as an integer between 1 and the total number of hashes. Do checks 2-4 by initially querying without a key and then querying @@ -62,13 +61,23 @@ func FuzzHashesQuery(f *testing.F) { t.Errorf("Invalid key led to a nil error") } - // Generate a random tree of headers - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) + baseHeader, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+100, + ) + // Get the headers map - headersMap := tree.GetHeadersMap() - // Generate a random limit - treeSize := uint64(tree.Size()) - limit := uint64(r.Int63n(int64(tree.Size())) + 1) + headersMap := chain.GetHeadersMap() + headersMap[baseHeader.Hash.String()] = baseHeader.Header.ToBlockHeader() + + // + 1 is necessary to account for the base header + totalChainLength := chain.ChainLength() + 1 + chainSize := uint64(totalChainLength) + limit := uint64(r.Int63n(int64(totalChainLength)) + 1) // Generate a page request with a limit and a nil key pagination = constructRequestWithLimit(r, limit) // Generate the initial query @@ -77,7 +86,7 @@ func FuzzHashesQuery(f *testing.F) { // Will be used later to evaluate whether all the hashes were returned hashesFound := make(map[string]bool, 0) - for headersRetrieved := uint64(0); headersRetrieved < treeSize; headersRetrieved += limit { + for headersRetrieved := uint64(0); headersRetrieved < chainSize; headersRetrieved += limit { resp, err = blcKeeper.Hashes(sdkCtx, hashesRequest) if err != nil { t.Errorf("Valid request led to an error %s", err) @@ -86,16 +95,16 @@ func FuzzHashesQuery(f *testing.F) { t.Fatalf("Valid request led to a nil response") } // If we are on the last page the elements retrieved should be equal to the remaining ones - if headersRetrieved+limit >= treeSize && uint64(len(resp.Hashes)) != treeSize-headersRetrieved { - t.Fatalf("On the last page expected %d elements but got %d", treeSize-headersRetrieved, len(resp.Hashes)) + if headersRetrieved+limit >= chainSize && uint64(len(resp.Hashes)) != chainSize-headersRetrieved { + t.Fatalf("On the last page expected %d elements but got %d", chainSize-headersRetrieved, len(resp.Hashes)) } // Otherwise, the elements retrieved should be equal to the limit - if headersRetrieved+limit < treeSize && uint64(len(resp.Hashes)) != limit { + if headersRetrieved+limit < chainSize && uint64(len(resp.Hashes)) != limit { t.Fatalf("On an intermediate page expected %d elements but got %d", limit, len(resp.Hashes)) } for _, hash := range resp.Hashes { - // Check if the hash was generated by the tree + // Check if the hash was generated by the chain if _, ok := headersMap[hash.String()]; !ok { t.Fatalf("Hashes returned a hash that was not created") } @@ -120,7 +129,7 @@ func FuzzContainsQuery(f *testing.F) { 2. The query returns true or false depending on whether the hash exists. Data generation: - - Generate a random tree of headers and insert into storage. + - Generate a random chain of headers and insert into storage. - Generate a random header but do not insert it into storage. */ datagen.AddRandomSeedsToFuzzer(f, 10) @@ -138,8 +147,15 @@ func FuzzContainsQuery(f *testing.F) { t.Errorf("Nil input led to a nil error") } - // Generate a random tree of headers and insert it into storage - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) + // Generate a random chain of headers and insert it into storage + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+100, + ) // Test with a non-existent header query, _ := types.NewQueryContainsRequest(datagen.GenRandomBTCHeaderInfo(r).Hash.MarshalHex()) @@ -155,7 +171,7 @@ func FuzzContainsQuery(f *testing.F) { } // Test with an existing header - query, _ = types.NewQueryContainsRequest(tree.RandomNode(r).Hash.MarshalHex()) + query, _ = types.NewQueryContainsRequest(chain.GetRandomHeaderInfo(r).Hash.MarshalHex()) resp, err = blcKeeper.Contains(sdkCtx, query) if err != nil { t.Errorf("Valid input let to an error: %s", err) @@ -182,8 +198,7 @@ func FuzzMainChainQuery(f *testing.F) { 7. End of pagination: the last elements are returned properly and the next_key is set to nil. Data Generation: - - Generate a random tree of headers with different PoW and insert them into the headers storage. - - Calculate the main chain using the `HeadersState().MainChain()` function (here we only test the query) + - Generate a random chain of headers with different PoW and insert them into the headers storage. */ datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -214,8 +229,15 @@ func FuzzMainChainQuery(f *testing.F) { t.Errorf("Invalid key led to a nil error") } - // Generate a random tree of headers and insert it into storage - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) + // Generate a random chain of headers and insert it into storage + base, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+100, + ) // Check whether the key being set to an element that does not exist leads to an error pagination = constructRequestWithKey(r, datagen.GenRandomBTCHeaderInfo(r).Hash.MustMarshal()) @@ -228,27 +250,17 @@ func FuzzMainChainQuery(f *testing.F) { t.Errorf("Key corresponding to a header that does not exist led to a nil error") } - // Get the mainchain - mainchain := tree.GetMainChain() + mainchain := make([]*types.BTCHeaderInfo, 0) - // Check whether the key being set to a non-mainchain element leads to an error - // Select a random header - header := tree.RandomNode(r) - // Get the tip - tip := tree.GetTip() - // if the header is not on the mainchain, we can test our assumption - // if it is, randomness will ensure that it does on another test case - if !tree.IsOnNodeChain(tip, header) { - pagination = constructRequestWithKeyAndLimit(r, header.Hash.MustMarshal(), uint64(len(mainchain))) - mainchainRequest = types.NewQueryMainChainRequest(pagination) - resp, err = blcKeeper.MainChain(sdkCtx, mainchainRequest) - if resp != nil { - t.Errorf("Key corresponding to header that is not on the mainchain led to a non-nil response") - } - if err == nil { - t.Errorf("Key corresponding to a header that is not on the mainchain led to a nil error") - } - } + mainchain = append(mainchain, base) + mainchain = append(mainchain, chain.GetChainInfo()...) + // we need to reverse the mainchain because the query returns the mainchain from highest to lowest header + bbn.Reverse(mainchain) + // // Check whether the key being set to a non-mainchain element leads to an error + // // Select a random header + // header := chain.GetRandomHeaderInfo(r) + // // Get the tip + // tip := chah.GetTip() // Index into the current element of mainchain that we are iterating mcIdx := 0 @@ -314,7 +326,7 @@ func FuzzTipQuery(f *testing.F) { 2. The query returns the tip BTC header Data generation: - - Generate a random tree of headers and insert into storage + - Generate a random chain of headers and insert into storage */ datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -331,7 +343,15 @@ func FuzzTipQuery(f *testing.F) { t.Errorf("Nil input led to a nil error") } - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) + // Generate a random chain of headers and insert it into storage + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+100, + ) resp, err = blcKeeper.Tip(sdkCtx, types.NewQueryTipRequest()) if err != nil { @@ -340,8 +360,8 @@ func FuzzTipQuery(f *testing.F) { if resp == nil { t.Fatalf("Valid input led to nil response") } - if !resp.Header.Eq(tree.GetTip()) { - t.Errorf("Invalid header returned. Expected %s, got %s", tree.GetTip().Hash, resp.Header.Hash) + if !resp.Header.Eq(chain.GetTipInfo()) { + t.Errorf("Invalid header returned. Expected %s, got %s", chain.GetTipInfo().Hash, resp.Header.Hash) } }) } @@ -353,7 +373,7 @@ func FuzzBaseHeaderQuery(f *testing.F) { 2. The query returns the base BTC header Data generation: - - Generate a random tree of headers and insert into storage. + - Generate a random chain of headers and insert into storage. */ datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -370,7 +390,15 @@ func FuzzBaseHeaderQuery(f *testing.F) { t.Errorf("Nil input led to a nil error") } - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) + // Generate a random chain of headers and insert it into storage + base, _ := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+100, + ) resp, err = blcKeeper.BaseHeader(sdkCtx, types.NewQueryBaseHeaderRequest()) if err != nil { @@ -379,8 +407,8 @@ func FuzzBaseHeaderQuery(f *testing.F) { if resp == nil { t.Fatalf("Valid input led to nil response") } - if !resp.Header.Eq(tree.GetRoot()) { - t.Errorf("Invalid header returned. Expected %s, got %s", tree.GetRoot().Hash, resp.Header.Hash) + if !resp.Header.Eq(base) { + t.Errorf("Invalid header returned. Expected %s, got %s", base.Hash, resp.Header.Hash) } }) } diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index 8231ba260..336c4bd2b 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -4,6 +4,7 @@ import ( "fmt" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/wire" "github.com/cometbft/cometbft/libs/log" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -19,15 +20,19 @@ type ( memKey storetypes.StoreKey hooks types.BTCLightClientHooks btcConfig bbn.BtcConfig + bl *types.BtcLightClient } ) +var _ types.BtcChainReadStore = (*headersState)(nil) + func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, btcConfig bbn.BtcConfig, ) Keeper { + bl := types.NewBtcLightClientFromParams(btcConfig.NetParams()) return Keeper{ cdc: cdc, @@ -35,6 +40,7 @@ func NewKeeper( memKey: memKey, hooks: nil, btcConfig: btcConfig, + bl: bl, } } @@ -52,90 +58,49 @@ func (k *Keeper) SetHooks(bh types.BTCLightClientHooks) *Keeper { return k } -// InsertHeader inserts a btcd header into the header state -func (k Keeper) InsertHeader(ctx sdk.Context, header *bbn.BTCHeaderBytes) error { - if header == nil { - return types.ErrEmptyMessage - } - headerHash := header.Hash() - parentHash := header.ParentHash() +func (k Keeper) insertHeaders( + ctx sdk.Context, + headers []*wire.BlockHeader, +) error { - // Check whether the header already exists, if yes reject - headerExists := k.headersState(ctx).HeaderExists(headerHash) - if headerExists { - return types.ErrDuplicateHeader - } + headerState := k.headersState(ctx) - // Check whether the parent exists, if not reject - parentExists := k.headersState(ctx).HeaderExists(parentHash) - if !parentExists { - return types.ErrHeaderParentDoesNotExist - } + result, err := k.bl.InsertHeaders( + headerState, + headers, + ) - // Retrieve the height of the parent to calculate the current height - parentHeight, err := k.headersState(ctx).GetHeaderHeight(parentHash) if err != nil { - // Height should always exist if the previous checks have passed - panic("Height for parent is not maintained") + return err } - // Retrieve the work of the parent to calculate the cumulative work - parentWork, err := k.headersState(ctx).GetHeaderWork(parentHash) - if err != nil { - // Work should always exist if the previous checks have passed - panic("Work for parent is not maintained") + // if we have rollback, first delete all headers up to the rollback point + if result.RollbackInfo != nil { + headerState.rollBackHeadersUpTo(result.RollbackInfo.HeaderToRollbackTo.Height) + // trigger rollback event + k.triggerRollBack(ctx, result.RollbackInfo.HeaderToRollbackTo) } - // Calculate the cumulative work - headerWork := types.CalcWork(header) - cumulativeWork := types.CumulativeWork(headerWork, *parentWork) - - // Construct the BTCHeaderInfo object - headerInfo := types.NewBTCHeaderInfo(header, headerHash, parentHeight+1, &cumulativeWork) - - // Retrieve the previous tip for future usage - previousTip := k.headersState(ctx).GetTip() - - // Create the header - k.headersState(ctx).CreateHeader(headerInfo) - - // Get the new tip - currentTip := k.headersState(ctx).GetTip() + for _, header := range result.HeadersToInsert { + h := header + headerState.insertHeader(h) + k.triggerHeaderInserted(ctx, h) + k.triggerRollForward(ctx, h) + } + return nil +} - // Variable maintaining the headers that have been added to the main chain - var addedToMainChain []*types.BTCHeaderInfo +func (k Keeper) InsertHeaders(ctx sdk.Context, headers []bbn.BTCHeaderBytes) error { + if len(headers) == 0 { + return types.ErrEmptyMessage + } - k.triggerHeaderInserted(ctx, headerInfo) - // The tip has changed, we need to send events - if !currentTip.Eq(previousTip) { - if !currentTip.Eq(headerInfo) { - panic("The tip was updated but with a different header than the one provided") - } - // Get the highest common ancestor between the new tip and the old tip - // There are two cases: - // 1. The new tip extends the old tip - // - The highest common ancestor is the old tip - // - No need to send a roll-back event - // 2. There has been a chain re-org - // - Need to send a roll-back event - var hca *types.BTCHeaderInfo - if currentTip.HasParent(previousTip) { - hca = previousTip - } else { - hca = k.headersState(ctx).GetHighestCommonAncestor(previousTip, currentTip) - // chain re-org: trigger a roll-back event to the highest common ancestor - k.triggerRollBack(ctx, hca) - } - // Find the newly added headers to the main chain - addedToMainChain = k.headersState(ctx).GetInOrderAncestorsUntil(currentTip, hca) - // Iterate through the added headers and trigger a roll-forward event - for _, added := range addedToMainChain { - // tipHeight + 1 - len(addedToMainChain) -> height of the highest common ancestor - k.triggerRollForward(ctx, added) - } + blockHeaders := make([]*wire.BlockHeader, len(headers)) + for i, header := range headers { + blockHeaders[i] = header.ToBlockHeader() } - return nil + return k.insertHeaders(ctx, blockHeaders) } // BlockHeight returns the height of the provided header @@ -143,108 +108,45 @@ func (k Keeper) BlockHeight(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) if headerHash == nil { return 0, types.ErrEmptyMessage } - return k.headersState(ctx).GetHeaderHeight(headerHash) + + headerInfo, err := k.headersState(ctx).GetHeaderByHash(headerHash) + + if err != nil { + return 0, err + } + + return headerInfo.Height, nil } -// MainChainDepth returns the depth of the header in the main chain or -1 if it does not exist in it -func (k Keeper) MainChainDepth(ctx sdk.Context, headerHashBytes *bbn.BTCHeaderHashBytes) (int64, error) { +// MainChainDepth returns the depth of the header in the main chain, or error if it does not exists +func (k Keeper) MainChainDepth(ctx sdk.Context, headerHashBytes *bbn.BTCHeaderHashBytes) (uint64, error) { if headerHashBytes == nil { - return -1, types.ErrEmptyMessage + return 0, types.ErrEmptyMessage } // Retrieve the header. If it does not exist, return an error headerInfo, err := k.headersState(ctx).GetHeaderByHash(headerHashBytes) if err != nil { - return -1, err + return 0, err } - // Retrieve the tip tipInfo := k.headersState(ctx).GetTip() - // If the height of the requested header is larger than the tip, return -1 + // sanity check, to avoid silent error if something is wrong. if tipInfo.Height < headerInfo.Height { - return -1, nil + // panic, as tip should always be higher than the header than every header + panic("tip height is less than header height") } - // The depth is the number of blocks that have been build on top of the header - // For example: - // Tip: 0-deep - // Tip height is 10, headerInfo height is 5: 5-deep etc. headerDepth := tipInfo.Height - headerInfo.Height - mainchain := k.headersState(ctx).GetMainChainUpTo(headerDepth) - - // If we got an empty mainchain or the header does not equal the last element of the mainchain - // then the header is not maintained inside the mainchain. - if len(mainchain) == 0 || !headerInfo.Eq(mainchain[len(mainchain)-1]) { - return -1, nil - } - return int64(headerDepth), nil -} - -// IsHeaderKDeep returns true if a header is at least k-deep on the main chain -func (k Keeper) IsHeaderKDeep(ctx sdk.Context, headerHashBytes *bbn.BTCHeaderHashBytes, depth uint64) (bool, error) { - if headerHashBytes == nil { - return false, types.ErrEmptyMessage - } - mainchainDepth, err := k.MainChainDepth(ctx, headerHashBytes) - if err != nil { - return false, err - } - // If MainChainDepth returned a negative depth, then the header is not on the mainchain - if mainchainDepth < 0 { - return false, nil - } - // return true if the provided depth is more than equal the mainchain depth - return depth >= uint64(mainchainDepth), nil -} - -// IsAncestor returns true/false depending on whether `parent` is an ancestor of `child`. -// Returns false if the parent and the child are the same header. -func (k Keeper) IsAncestor(ctx sdk.Context, parentHashBytes *bbn.BTCHeaderHashBytes, childHashBytes *bbn.BTCHeaderHashBytes) (bool, error) { - // nil checks - if parentHashBytes == nil || childHashBytes == nil { - return false, types.ErrEmptyMessage - } - // Retrieve parent and child header - parentHeader, err := k.headersState(ctx).GetHeaderByHash(parentHashBytes) - if err != nil { - return false, types.ErrHeaderDoesNotExist.Wrapf("parent does not exist") - } - childHeader, err := k.headersState(ctx).GetHeaderByHash(childHashBytes) - if err != nil { - return false, types.ErrHeaderDoesNotExist.Wrapf("child does not exist") - } - - // If the height of the child is equal or less than the parent, then the input is invalid - if childHeader.Height <= parentHeader.Height { - return false, nil - } - - // Retrieve the ancestry - ancestry := k.headersState(ctx).GetHeaderAncestryUpTo(childHeader, childHeader.Height-parentHeader.Height) - // If it is empty, return false - if len(ancestry) == 0 { - return false, nil - } - // Return whether the last element of the ancestry is equal to the parent - return ancestry[len(ancestry)-1].Eq(parentHeader), nil + return headerDepth, nil } func (k Keeper) GetTipInfo(ctx sdk.Context) *types.BTCHeaderInfo { return k.headersState(ctx).GetTip() } -// TODO: The following functions, GetHeaderByHash and GetHeaderByHeight, are super inefficient -// and should be replaced with a better implementation. This requires changing the -// underlying data model for the whole btclightclient module. -// GetHeaderByHash returns header with given hash from main chain or returns nil if such header is not found -// or is not on main chain +// GetHeaderByHash returns header with given hash, if it does not exists returns nil func (k Keeper) GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *types.BTCHeaderInfo { - depth, err := k.MainChainDepth(ctx, hash) - - if depth < 0 || err != nil { - return nil - } - info, err := k.headersState(ctx).GetHeaderByHash(hash) if err != nil { @@ -256,35 +158,57 @@ func (k Keeper) GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) * // GetHeaderByHeight returns header with given height from main chain, returns nil if such header is not found func (k Keeper) GetHeaderByHeight(ctx sdk.Context, height uint64) *types.BTCHeaderInfo { - var info *types.BTCHeaderInfo - - k.headersState(ctx).HeadersByHeight(height, func(hi *types.BTCHeaderInfo) bool { - depth, err := k.MainChainDepth(ctx, hi.Hash) + header, err := k.headersState(ctx).GetHeaderByHeight(height) - if depth < 0 || err != nil { - return false - } - - info = hi - return true - }) - - return info -} + if err != nil { + return nil + } -// GetHighestCommonAncestor traverses the ancestors of both headers -// to identify the common ancestor with the highest height -func (k Keeper) GetHighestCommonAncestor(ctx sdk.Context, header1 *types.BTCHeaderInfo, header2 *types.BTCHeaderInfo) *types.BTCHeaderInfo { - return k.headersState(ctx).GetHighestCommonAncestor(header1, header2) + return header } -// GetInOrderAncestorsUntil returns the list of nodes starting from the block *after* the `ancestor` and ending with the `descendant`. -func (k Keeper) GetInOrderAncestorsUntil(ctx sdk.Context, descendant *types.BTCHeaderInfo, ancestor *types.BTCHeaderInfo) []*types.BTCHeaderInfo { - return k.headersState(ctx).GetInOrderAncestorsUntil(descendant, ancestor) +// GetMainChainFrom returns the current canonical chain from the given height up to the tip +// If the height is higher than the tip, it returns an empty slice +// If startHeight is 0, it returns the entire main chain +func (k Keeper) GetMainChainFrom(ctx sdk.Context, startHeight uint64) []*types.BTCHeaderInfo { + headers := make([]*types.BTCHeaderInfo, 0) + accHeaderFn := func(header *types.BTCHeaderInfo) bool { + headers = append(headers, header) + return false + } + k.headersState(ctx).IterateForwardHeaders(startHeight, accHeaderFn) + return headers } // GetMainChainUpTo returns the current canonical chain as a collection of block headers // starting from the tip and ending on the header that has `depth` distance from it. func (k Keeper) GetMainChainUpTo(ctx sdk.Context, depth uint64) []*types.BTCHeaderInfo { - return k.headersState(ctx).GetMainChainUpTo(depth) + headers := make([]*types.BTCHeaderInfo, 0) + + var currentDepth = uint64(0) + accHeaderFn := func(header *types.BTCHeaderInfo) bool { + // header header is at depth 0. + if currentDepth > depth { + return true + } + + headers = append(headers, header) + currentDepth++ + return false + } + + k.headersState(ctx).IterateReverseHeaders(accHeaderFn) + + return headers +} + +// Retrieves whole header chain in reverse order +func (K Keeper) GetMainChainReverse(ctx sdk.Context) []*types.BTCHeaderInfo { + headers := make([]*types.BTCHeaderInfo, 0) + accHeaderFn := func(header *types.BTCHeaderInfo) bool { + headers = append(headers, header) + return false + } + K.headersState(ctx).IterateReverseHeaders(accHeaderFn) + return headers } diff --git a/x/btclightclient/keeper/keeper_test.go b/x/btclightclient/keeper/keeper_test.go index b510be086..a8a89ff1e 100644 --- a/x/btclightclient/keeper/keeper_test.go +++ b/x/btclightclient/keeper/keeper_test.go @@ -4,100 +4,27 @@ import ( "math/rand" "testing" + sdkmath "cosmossdk.io/math" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/btcsuite/btcd/chaincfg" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" - "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) -func FuzzKeeperIsHeaderKDeep(f *testing.F) { - /* - Checks: - 1. if the BTCHeaderBytes object is nil, an error is returned - 2. if the header does not exist, an error is returned - 3. if the header exists but it is higher than `depth`, false is returned - 4. if the header exists and is equal to `depth`, true is returned - 5. if the header exists and is higher than `depth`, true is returned - 6. if the header exists and is equal or higher to `depth` but not on the main chain, false is returned - - Data Generation: - - Generate a random tree of headers. - - Get the mainchain and select appropriate headers. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - depth := r.Uint64() - - // Test nil input - isDeep, err := blcKeeper.IsHeaderKDeep(ctx, nil, depth) - if err == nil { - t.Errorf("Nil input led to nil error") - } - if isDeep { - t.Errorf("Nil input led to a true result") - } - - // Test header not existing - nonExistentHeader := datagen.GenRandomBTCHeaderBytes(r, nil, nil) - isDeep, err = blcKeeper.IsHeaderKDeep(ctx, nonExistentHeader.Hash(), depth) - if err == nil { - t.Errorf("Non existent header led to nil error") - } - if isDeep { - t.Errorf("Non existent header led to a true result") - } - - // Generate a random tree of headers with at least one node - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - // Get a random header from the tree - header := tree.RandomNode(r) - // Get the tip of the chain and check whether the header is on the chain that it defines - // In that case, the true/false result depends on the depth parameter that we provide. - // Otherwise, the result should always be false, regardless of the parameter. - tip := tree.GetTip() - if tree.IsOnNodeChain(tip, header) { - mainchain := tree.GetMainChain() - // Select a random depth based on the main-chain length - randDepth := uint64(r.Int63n(int64(len(mainchain)))) - isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Hash, randDepth) - // Identify whether the function should return true or false - headerDepth := tip.Height - header.Height - // If the random depth that we chose is more than the headerDepth, then it should return true - expectedIsDeep := randDepth >= headerDepth - if err != nil { - t.Errorf("Existent header led to a non-nil error") - } - if expectedIsDeep != isDeep { - t.Errorf("Expected result %t for header with depth %d when parameter depth is %d", expectedIsDeep, headerDepth, randDepth) - } - } else { - // The depth provided does not matter, we should always get false. - randDepth := r.Uint64() - isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Hash, randDepth) - if err != nil { - t.Errorf("Existent header led to a non-nil error %s", err) - } - if isDeep { - t.Errorf("Got a true result for header that is not part of the mainchain") - } - } - }) -} - func FuzzKeeperMainChainDepth(f *testing.F) { /* Checks: 1. if the BTCHeaderBytes object is nil, an error is returned and the height is -1 2. if the BTCHeaderBytes object does not exist in storage, (-1, error) is returned - 3. if the BTCHeaderBytes object has a height that is higher than the tip, (-1, error) is returned - 4. if the header is not on the main chain, (-1, nil) is returned - 5. if the header exists and is on the mainchain, (depth, nil) is returned + 3. if the header is not on the main chain, (0, nil) is returned + 4. if the header exists and is on the mainchain, (depth, nil) is returned Data Generation: - - Generate a random tree of headers. + - Generate a random chain of headers. - Random generation of a header that is not inserted into storage. - Random selection of a header from the main chain and outside of it. */ @@ -111,7 +38,7 @@ func FuzzKeeperMainChainDepth(f *testing.F) { if err == nil { t.Errorf("Nil input led to nil error") } - if depth != -1 { + if depth != 0 { t.Errorf("Nil input led to a result that is not -1") } @@ -121,36 +48,24 @@ func FuzzKeeperMainChainDepth(f *testing.F) { if err == nil { t.Errorf("Non existent header led to nil error") } - if depth != -1 { + if depth != 0 { t.Errorf("Non existing header led to a result that is not -1") } - // Generate a random tree of headers with at least one node - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - // Get a random header from the tree - header := tree.RandomNode(r) - // Get the tip of the chain and check whether the header is on the chain that it defines - // In that case, the depth result depends on the depth of the header on the mainchain. - // Otherwise, the result should always be -1 - tip := tree.GetTip() - // Get the depth - depth, err = blcKeeper.MainChainDepth(ctx, header.Hash) - if err != nil { - t.Errorf("Existent and header led to error") - } - if tree.IsOnNodeChain(tip, header) { - expectedDepth := tip.Height - header.Height - if depth < 0 { - t.Errorf("Mainchain header led to negative depth") - } - if uint64(depth) != expectedDepth { - t.Errorf("Got depth %d, expected %d", depth, expectedDepth) - } - } else { - if depth >= 0 { - t.Errorf("Non-mainchain header let to >= 0 result") - } - } + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+10, + ) + randomHeader := chain.GetRandomHeaderInfo(r) + depth, err = blcKeeper.MainChainDepth(ctx, randomHeader.Hash) + require.NoError(t, err) + chainTip := chain.GetTipInfo() + headerDepth := chainTip.Height - randomHeader.Height + require.Equal(t, headerDepth, depth) }) } @@ -162,7 +77,7 @@ func FuzzKeeperBlockHeight(f *testing.F) { 3. if the BTCHeaderBytes object exists, (height, nil) is returned. Data Generation: - - Generate a random tree of headers. + - Generate a random chain of headers. - Random generation of a header that is not inserted into storage. - Random selection of a header from the main chain and outside of it. */ @@ -190,8 +105,16 @@ func FuzzKeeperBlockHeight(f *testing.F) { t.Errorf("Non existing header led to a result that is not -1") } - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - header := tree.RandomNode(r) + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+10, + ) + + header := chain.GetRandomHeaderInfo(r) height, err = blcKeeper.BlockHeight(ctx, header.Hash) if err != nil { t.Errorf("Existent header led to an error") @@ -202,289 +125,332 @@ func FuzzKeeperBlockHeight(f *testing.F) { }) } -func FuzzKeeperIsAncestor(f *testing.F) { - /* - Checks: - 1. If the child hash or the parent hash are nil, an error is returned - 2. If the child has a lower height than the parent, an error is returned - 3. If the child and the parent are the same, false is returned - 4. If the parent is an ancestor of child then `true` is returned. - - Data generation: - - Generate a random tree of headers and insert it into storage. - - Select a random header and select a random descendant and a random ancestor to test (2-4). - */ +func FuzzKeeperInsertValidChainExtension(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - nonExistentParent := datagen.GenRandomBTCHeaderInfo(r) - nonExistentChild := datagen.GenRandomBTCHeaderInfo(r) + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + datagen.RandomInt(r, 50)+10, + datagen.RandomInt(r, 50)+10, + ) - // nil inputs test - isAncestor, err := blcKeeper.IsAncestor(ctx, nil, nil) - if err == nil { - t.Errorf("Nil input led to nil error") - } - if isAncestor { - t.Errorf("Nil input led to true result") - } - isAncestor, err = blcKeeper.IsAncestor(ctx, nonExistentParent.Hash, nil) - if err == nil { - t.Errorf("Nil input led to nil error") - } - if isAncestor { - t.Errorf("Nil input led to true result") - } - isAncestor, err = blcKeeper.IsAncestor(ctx, nil, nonExistentChild.Hash) - if err == nil { - t.Errorf("Nil input led to nil error") - } - if isAncestor { - t.Errorf("Nil input led to true result") - } + mockHooks := NewMockHooks() + blcKeeper.SetHooks(mockHooks) - // non-existent test - isAncestor, err = blcKeeper.IsAncestor(ctx, nonExistentParent.Hash, nonExistentChild.Hash) - if err == nil { - t.Errorf("Non existent headers led to nil error") - } - if isAncestor { - t.Errorf("Non existent headers led to true result") - } + newChainLength := uint32(datagen.RandomInt(r, 10) + 1) - // Generate random tree of headers - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - header := tree.RandomNode(r) - ancestor := tree.RandomNode(r) + chainToInsert := datagen.GenRandomValidChainStartingFrom( + r, + chain.GetTipInfo().Height, + chain.GetTipInfo().Header.ToBlockHeader(), + nil, + newChainLength, + ) + chainExtensionWork := chainWork(chainToInsert) - if ancestor.Eq(header) { - // Same headers test - isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) - if err != nil { - t.Errorf("Valid input led to an error") - } - if isAncestor { - t.Errorf("Same header input led to true result") - } - } else if ancestor.Height >= header.Height { // Descendant test - isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) - if err != nil { - t.Errorf("Providing a descendant as a parent led to a non-nil error") - } - if isAncestor { - t.Errorf("Providing a descendant as a parent led to a true result") - } - } else { // Ancestor test - isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) - if err != nil { - t.Errorf("Valid input led to an error") - } - if isAncestor != tree.IsOnNodeChain(header, ancestor) { // The result should be whether it is an ancestor or not - t.Errorf("Got invalid ancestry result. Expected %t, got %t", tree.IsOnNodeChain(header, ancestor), isAncestor) - } + ctx = ctx.WithEventManager(sdk.NewEventManager()) + oldTip := blcKeeper.HeadersState(ctx).GetTip() + extendedChainWork := oldTip.Work.Add(*chainExtensionWork) + extendedChainHeight := uint64(uint32(oldTip.Height) + newChainLength) + + err := blcKeeper.InsertHeaders(ctx, chainToChainBytes(chainToInsert)) + require.NoError(t, err) + + // updated tip + newTip := blcKeeper.HeadersState(ctx).GetTip() + require.False(t, newTip.Eq(oldTip)) + // check tip + checkTip( + t, + ctx, + blcKeeper, + extendedChainWork, + extendedChainHeight, + chainToInsert[len(chainToInsert)-1], + ) + // check all inserted headers + for _, header := range chainToInsert { + headerHash := header.BlockHash() + hash := bbn.NewBTCHeaderHashBytesFromChainhash(&headerHash) + headerInfoByHash := blcKeeper.GetHeaderByHash(ctx, &hash) + require.NotNil(t, headerInfoByHash) + headerInfoByHeight := blcKeeper.GetHeaderByHeight(ctx, headerInfoByHash.Height) + require.NotNil(t, headerInfoByHeight) + require.True(t, allFieldsEqual(headerInfoByHash, headerInfoByHeight)) + } + + // check events and hooks + rollForwadType, _ := sdk.TypedEventToEvent(&types.EventBTCRollForward{}) + headerInsertedType, _ := sdk.TypedEventToEvent(&types.EventBTCHeaderInserted{}) + + events := ctx.EventManager().Events() + numEvents := len(events) + require.Len(t, mockHooks.AfterBTCHeaderInsertedStore, len(chainToInsert)) + require.Len(t, mockHooks.AfterBTCRollForwardStore, len(chainToInsert)) + require.Len(t, mockHooks.AfterBTCRollBackStore, 0) + require.Equal(t, numEvents, len(chainToInsert)*2) + + for i, header := range chainToInsert { + headerHash := header.BlockHash() + hash := bbn.NewBTCHeaderHashBytesFromChainhash(&headerHash) + require.True(t, mockHooks.AfterBTCHeaderInsertedStore[i].Hash.Eq(&hash)) + require.True(t, mockHooks.AfterBTCRollForwardStore[i].Hash.Eq(&hash)) + // event should be in order inserted -> roll forward + require.Equal(t, events[i*2].Type, headerInsertedType.Type) + require.Equal(t, events[i*2+1].Type, rollForwadType.Type) } }) } -func FuzzKeeperInsertHeader(f *testing.F) { - /* - Checks: - 1. if the BTCHeaderBytes object is nil, an error is returned - 2. if the BTCHeaderBytes object corresponds to an existing header, an error is returned - 3. if the BTCHeaderBytes object parent is not maintained, an error is returned - 4. if all the checks pass: - 4a. corresponding objects have been created on the headers, hashToHeight, and hashToWork storages - 4b. the cumulative work of the added header is its own + its parent's - 4c. the height of the added header is its parent's + 1 - 4d. the object added to the headers storage corresponds to a header info with the above attributes - 4e. the tip is properly updated and corresponding roll-forward and roll-backward events are triggered. Three cases: - 4e1. The new header builds on top of the existing tip - - New header becomes the tip - - Roll-Forward event to the new tip - 4e2. The new header builds on a fork and the cumulative work is less than the tip - - The tip does not change - - No events are triggered - 4e3. The new header builds on a fork and the cumulative work is more than the tip - - New header becomes the tip - - Roll-backward event to the highest common ancestor (can use the `GetHighestCommonAncestor` function) - - Roll-forward event to all the elements of the fork after the highest common ancestor - - Data Generation: - - Generate a random tree of headers and insert them into storage. - - Construct BTCHeaderBytes object that corresponds to existing header - - Construct BTCHeaderBytes object for which its parent is not maintained - - Construct BTCHeaderBytes objects that: - * Build on top of the tip - * Build on top of a header that is `rand.Intn(tipHeight)` headers back from the tip. - - This should emulate both 4e2 and 4e3. - */ +func FuzzKeeperInsertValidBetterChain(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - - // Test nil input - err := blcKeeper.InsertHeader(ctx, nil) - if err == nil { - t.Errorf("Nil input led to nil error") - } + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + datagen.RandomInt(r, 50)+10, + datagen.RandomInt(r, 50)+10, + ) - existingHeader := tree.RandomNode(r) - err = blcKeeper.InsertHeader(ctx, existingHeader.Header) - if err == nil { - t.Errorf("Existing header led to nil error") - } - - nonExistentHeader := datagen.GenRandomBTCHeaderInfo(r) - err = blcKeeper.InsertHeader(ctx, nonExistentHeader.Header) - if err == nil { - t.Errorf("Header with non-existent parent led to nil error") - } - - // Create mock hooks that just store with what they were called mockHooks := NewMockHooks() blcKeeper.SetHooks(mockHooks) - // Select a random header and build a header on top of it - parentHeader := tree.RandomNode(r) - header := datagen.GenRandomBTCHeaderInfoWithParent(r, parentHeader) - - // Assign a new event manager - // We do this because the tree building might have led to events getting sent - // and we want to ignore those. + forkHeaderParent := chain.GetRandomHeaderInfoNoTip(r) + // new chain will always be better that existing one + newChainLength := uint32(chain.ChainLength() + 1) + chainToInsert := datagen.GenRandomValidChainStartingFrom( + r, + forkHeaderParent.Height, + forkHeaderParent.Header.ToBlockHeader(), + nil, + newChainLength, + ) + chainExtensionWork := chainWork(chainToInsert) ctx = ctx.WithEventManager(sdk.NewEventManager()) - // Get the tip in order to check if the header build on top of the tip + extendedChainWork := forkHeaderParent.Work.Add(*chainExtensionWork) + extendedChainHeight := uint64(uint32(forkHeaderParent.Height) + newChainLength) + oldTip := blcKeeper.HeadersState(ctx).GetTip() + removedBranch := blcKeeper.GetMainChainFrom(ctx, forkHeaderParent.Height+1) - // Insert the header into storage - err = blcKeeper.InsertHeader(ctx, header.Header) - if err != nil { - t.Errorf("Valid header led to an error") - } + require.True(t, len(removedBranch) > 0) - // Get the new tip - newTip := blcKeeper.HeadersState(ctx).GetTip() + err := blcKeeper.InsertHeaders(ctx, chainToChainBytes(chainToInsert)) + require.NoError(t, err) - // Get event types. Those will be useful to test the types of the emitted events - rollForwadType, _ := sdk.TypedEventToEvent(&types.EventBTCRollForward{}) + // updated tip + newTip := blcKeeper.HeadersState(ctx).GetTip() + require.False(t, newTip.Eq(oldTip)) + // check tip + checkTip( + t, + ctx, + blcKeeper, + extendedChainWork, + extendedChainHeight, + chainToInsert[len(chainToInsert)-1], + ) + + // check all headers from removed branch were removed + for _, headerInfo := range removedBranch { + headerInfoByHash := blcKeeper.GetHeaderByHash(ctx, headerInfo.Hash) + require.Nil(t, headerInfoByHash) + } + + // check all inserted headers + for _, header := range chainToInsert { + headerHash := header.BlockHash() + hash := bbn.NewBTCHeaderHashBytesFromChainhash(&headerHash) + headerInfoByHash := blcKeeper.GetHeaderByHash(ctx, &hash) + require.NotNil(t, headerInfoByHash) + headerInfoByHeight := blcKeeper.GetHeaderByHeight(ctx, headerInfoByHash.Height) + require.NotNil(t, headerInfoByHeight) + require.True(t, allFieldsEqual(headerInfoByHash, headerInfoByHeight)) + } + + // check events and hooks rollBackType, _ := sdk.TypedEventToEvent(&types.EventBTCRollBack{}) + rollForwadType, _ := sdk.TypedEventToEvent(&types.EventBTCRollForward{}) headerInsertedType, _ := sdk.TypedEventToEvent(&types.EventBTCHeaderInserted{}) - // The headerInserted hook call should contain the new header - if len(mockHooks.AfterBTCHeaderInsertedStore) != 1 { - t.Fatalf("Expected a single BTCHeaderInserted hook to be invoked. Got %d", len(mockHooks.AfterBTCHeaderInsertedStore)) - } - if !mockHooks.AfterBTCHeaderInsertedStore[0].Eq(header) { - t.Errorf("The headerInfo inside the BTCHeaderInserted hook is not the new header") - } - // Check that an event has been triggered for the new header - if len(ctx.EventManager().Events()) == 0 { - t.Fatalf("No events were triggered") - } + events := ctx.EventManager().Events() + numEvents := len(events) + require.Len(t, mockHooks.AfterBTCHeaderInsertedStore, len(chainToInsert)) + require.Len(t, mockHooks.AfterBTCRollForwardStore, len(chainToInsert)) + // there is one roll back event + require.Len(t, mockHooks.AfterBTCRollBackStore, 1) + require.Equal(t, numEvents, len(chainToInsert)*2+1) + + // Events should be ordered: + // Rollback, Insert, RollForward, Insert, RollForward, ... + for i, header := range chainToInsert { + if i == 0 { + // rollback event + require.Equal(t, events[0].Type, rollBackType.Type) + // rollback should indicate highest common ancestor i.e fork header parent + require.True(t, mockHooks.AfterBTCRollBackStore[0].Hash.Eq(forkHeaderParent.Hash)) + continue + } + + headerHash := header.BlockHash() + hash := bbn.NewBTCHeaderHashBytesFromChainhash(&headerHash) + require.True(t, mockHooks.AfterBTCHeaderInsertedStore[i].Hash.Eq(&hash)) + require.True(t, mockHooks.AfterBTCRollForwardStore[i].Hash.Eq(&hash)) - // The header creation event should have been the one that was first generated - if ctx.EventManager().Events()[0].Type != headerInsertedType.Type { - t.Errorf("The first event does not have the BTCHeaderInserted type") + require.Equal(t, events[i*2+1].Type, headerInsertedType.Type) + require.Equal(t, events[i*2].Type, rollForwadType.Type) } + }) +} - // If the new header builds on top of the tip - if oldTip.Eq(parentHeader) { - // The new tip should be equal to the new header - if !newTip.Eq(header) { - t.Errorf("Inserted header builts on top of the previous tip but does not become the new tip") - } - // The rollforward hook must be sent once - if len(mockHooks.AfterBTCRollForwardStore) != 1 { - t.Fatalf("Expected a single BTCRollForward hook to be invoked. Got %d", len(mockHooks.AfterBTCRollForwardStore)) - } - // The rollfoward hook call should contain the new header - if !mockHooks.AfterBTCRollForwardStore[0].Eq(header) { - t.Errorf("The headerInfo inside the BTCRollForward hook is not the new header") - } - // No rollback hooks must be invoked - if len(mockHooks.AfterBTCRollBackStore) != 0 { - t.Fatalf("Expected the BTCRollBack hook to not be invoked") - } - // 2 events because the first one is for the header creation - if len(ctx.EventManager().Events()) != 2 { - t.Fatalf("We expected only two events. One for header creation and one for rolling forward.") - } - // The second event should be the roll forward one - if ctx.EventManager().Events()[1].Type != rollForwadType.Type { - t.Errorf("The second event does not have the roll forward type") - } - } else if oldTip.Work.GT(*header.Work) { - // If the tip has a greater work than the newly inserted header - // no events should be sent and the tip should not change - if !oldTip.Eq(newTip) { - t.Errorf("Header with less work inserted but the tip changed") - } - // No rollforward hooks should be invoked - if len(mockHooks.AfterBTCRollForwardStore) != 0 { - t.Fatalf("Expected the BTCRollForward hook to not be invoked") - } - // No rollback hooks should be invoked - if len(mockHooks.AfterBTCRollBackStore) != 0 { - t.Fatalf("Expected the BTCRollBack hook to not be invoked") - } - // No other events other than BTCHeaderInserted should be invoked - if len(ctx.EventManager().Events()) != 1 { - t.Errorf("Extra events have been invoked when the tip wasn't updated") - } - } else { - // The tip has been updated. It should be towards the new header - if !newTip.Eq(header) { - t.Errorf("Inserted header has more work than the previous tip but does not become the new tip") - } - // Get the highest common ancestor of the old tip and the new header - hca := blcKeeper.HeadersState(ctx).GetHighestCommonAncestor(header, oldTip) - // Get the ancestry of the header up to the highest common ancestor - ancestry := tree.GetNodeAncestryUpTo(header, hca) - // We should have as many invocations of the roll-forward hook as the ancestors - // up to the highest common one - if len(ancestry) != len(mockHooks.AfterBTCRollForwardStore) { - t.Fatalf("Expected as many invocations of the roll-forward hook as the number of ancestors.") - } - for i := 0; i < len(ancestry); i++ { - // Compare the nodes in reverse order, since the rollfoward events should be in an oldest header first manner. - if !ancestry[i].Eq(mockHooks.AfterBTCRollForwardStore[len(ancestry)-i-1]) { - t.Errorf("Headers do not match. Expected %s got %s", ancestry[i].Hash, mockHooks.AfterBTCRollForwardStore[len(ancestry)-i-1].Hash) - } - } +func FuzzKeeperInsertInvalidChain(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) + _, _ = genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + datagen.RandomInt(r, 50)+10, + ) + currentTip := blcKeeper.GetTipInfo(ctx) + require.NotNil(t, currentTip) + + // Inserting nil headers should result with error + errNil := blcKeeper.InsertHeaders(ctx, nil) + require.Error(t, errNil) + + // Inserting empty headers should result with error + errEmpty := blcKeeper.InsertHeaders(ctx, []bbn.BTCHeaderBytes{}) + require.Error(t, errEmpty) + + // Inserting header without existing parent should result with error + chain := datagen.NewBTCHeaderChainWithLength(r, 0, 0, 10) + errNoParent := blcKeeper.InsertHeaders(ctx, chain.ChainToBytes()[1:]) + require.Error(t, errNoParent) + + // Inserting header chain with invalid header should result in error + newChainLength := uint32(datagen.RandomInt(r, 10) + 5) + // valid chain with at least 5 headers + chainToInsert := datagen.GenRandomValidChainStartingFrom( + r, + chain.GetTipInfo().Height, + chain.GetTipInfo().Header.ToBlockHeader(), + nil, + newChainLength, + ) + + // bump the nonce, it should fail validation and tip should not change + chainToInsert[3].Nonce = chainToInsert[3].Nonce + 1 + errInvalidHeader := blcKeeper.InsertHeaders(ctx, chainToChainBytes(chainToInsert)) + require.Error(t, errInvalidHeader) + newTip := blcKeeper.GetTipInfo(ctx) + // tip did not change + require.True(t, allFieldsEqual(currentTip, newTip)) + + // Inserting header chain with less work than current chain work should result in error + headerBeforeTip := blcKeeper.GetHeaderByHeight(ctx, currentTip.Height-1) + require.NotNil(t, headerBeforeTip) + worseChain := datagen.GenRandomValidChainStartingFrom( + r, + headerBeforeTip.Height, + headerBeforeTip.Header.ToBlockHeader(), + nil, + 1, + ) + errWorseChain := blcKeeper.InsertHeaders(ctx, chainToChainBytes(worseChain)) + require.Error(t, errWorseChain) + }) +} - // The rollback hook should be invoked for the highest common ancestor - if len(mockHooks.AfterBTCRollBackStore) != 1 { - t.Fatalf("Expected the BTCRollBack hook to be invoked once") - } - if !mockHooks.AfterBTCRollBackStore[0].Eq(hca) { - t.Errorf("Expected the BTCRollBack hook to be invoked for the highest common ancestor") - } +func FuzzKeeperValdateHeaderAtDifficultyAdjustmentBoundaries(f *testing.F) { + // less seeds as we generate longer chains + datagen.AddRandomSeedsToFuzzer(f, 3) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + numBlockPerRetarget := types.BlocksPerRetarget(&chaincfg.SimNetParams) + blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - // Test the invoked events - invokedEvents := ctx.EventManager().Events() - // There should be a total of len(ancestry) + 2 events - if len(invokedEvents) != len(ancestry)+2 { - t.Errorf("More events than expected were invoked %d %d", len(invokedEvents), len(ancestry)+2) - } - // Only test that there is a certain number of rollForward and rollBack events - // Testing the attributes is a much more complex approach - rollForwardCnt := 0 - rollBackCnt := 0 - for i := 0; i < len(invokedEvents); i++ { - if invokedEvents[i].Type == rollForwadType.Type { - rollForwardCnt += 1 - } - if invokedEvents[i].Type == rollBackType.Type { - rollBackCnt += 1 - } - } - if rollForwardCnt != len(ancestry) || rollBackCnt != 1 { - t.Errorf("Wrong number of roll forward and roll back events") - } - } + genesisHeader := bbn.NewBTCHeaderBytesFromBlockHeader(&chaincfg.SimNetParams.GenesisBlock.Header) + genesisHash := bbn.NewBTCHeaderHashBytesFromChainhash(chaincfg.SimNetParams.GenesisHash) + genesisWork := sdkmath.NewUint(0) + + genesisInfo := types.NewBTCHeaderInfo( + &genesisHeader, + &genesisHash, + 0, + &genesisWork, + ) + + require.True(t, types.IsRetargetBlock(genesisInfo, &chaincfg.SimNetParams)) + blcKeeper.SetBaseBTCHeader(ctx, *genesisInfo) + randomChain := datagen.NewBTCHeaderChainFromParentInfo( + r, + genesisInfo, + uint32(numBlockPerRetarget), + ) + + // this will always fail as last header is at adjustment boundary, but we created + // it without adjustment + err := blcKeeper.InsertHeaders(ctx, randomChain.ChainToBytes()) + require.Error(t, err) + + randomChainWithoutLastHeader := randomChain.Headers[:len(randomChain.Headers)-1] + chain := chainToChainBytes(randomChainWithoutLastHeader) + // now all headers are valid, and we are below adjustment boundary + err = blcKeeper.InsertHeaders(ctx, chain) + require.NoError(t, err) + + currentTip := blcKeeper.GetTipInfo(ctx) + require.NotNil(t, currentTip) + require.Equal(t, currentTip.Height, uint64(numBlockPerRetarget)-1) + + invalidAdjustedHeader := datagen.GenRandomBtcdValidHeader( + r, + currentTip.Header.ToBlockHeader(), + nil, + nil, + ) + // try to insert header at adjustment boundary without adjustment should fail + err = blcKeeper.InsertHeaders(ctx, []bbn.BTCHeaderBytes{bbn.NewBTCHeaderBytesFromBlockHeader(invalidAdjustedHeader)}) + require.Error(t, err) + + // Inserting valid adjusted header should succeed + rt := datagen.RetargetInfo{ + LastRetargetHeader: genesisHeader.ToBlockHeader(), + Params: &chaincfg.SimNetParams, + } + validAdjustedHeader := datagen.GenRandomBtcdValidHeader( + r, + // current tip heigh is 2015 + currentTip.Header.ToBlockHeader(), + nil, + &rt, + ) + validAdjustedHeaderBytes := bbn.NewBTCHeaderBytesFromBlockHeader(validAdjustedHeader) + + err = blcKeeper.InsertHeaders(ctx, []bbn.BTCHeaderBytes{bbn.NewBTCHeaderBytesFromBlockHeader(validAdjustedHeader)}) + require.NoError(t, err) + + newTip := blcKeeper.GetTipInfo(ctx) + require.NotNil(t, newTip) + // tip should be at adjustment boundary now + require.Equal(t, newTip.Height, uint64(numBlockPerRetarget)) + require.True(t, newTip.Header.Eq(&validAdjustedHeaderBytes)) + require.True(t, types.IsRetargetBlock(newTip, &chaincfg.SimNetParams)) }) } diff --git a/x/btclightclient/keeper/msg_server.go b/x/btclightclient/keeper/msg_server.go index 2956332f0..6349ecee0 100644 --- a/x/btclightclient/keeper/msg_server.go +++ b/x/btclightclient/keeper/msg_server.go @@ -2,12 +2,8 @@ package keeper import ( "context" - "math/big" "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcd/btcutil" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -16,156 +12,19 @@ type msgServer struct { k Keeper } -func MsgInsertHeaderWrapped(ctx context.Context, k Keeper, msg *types.MsgInsertHeader, - powLimit big.Int, reduceMinDifficulty bool, retargetAdjustmentFactor int64, powCheck bool, -) (*types.MsgInsertHeaderResponse, error) { - // Perform the checks that checkBlockHeaderContext of btcd does - // https://github.com/btcsuite/btcd/blob/master/blockchain/validate.go#L644 - // We skip the time, checkpoint, and version checks - // TODO: Implement an AnteHandler that performs these checks - // so as to not pollute the mempool with transactions - // that will get rejected. +func (m msgServer) InsertHeaders(ctx context.Context, msg *types.MsgInsertHeaders) (*types.MsgInsertHeadersResponse, error) { if msg == nil { return nil, types.ErrEmptyMessage.Wrapf("message is nil") } - if msg.Header == nil { - return nil, types.ErrEmptyMessage.Wrapf("message header is nit") - } - - // Get the SDK wrapped context sdkCtx := sdk.UnwrapSDKContext(ctx) - parentHash := msg.Header.ParentHash() - // Retrieve parent - parent, err := k.headersState(sdkCtx).GetHeaderByHash(parentHash) - // parent does not exist - if err != nil { - return nil, err - } - - /* - Verify the work of the new header. - Bitcoin core does this verification at: - https://github.com/bitcoin/bitcoin/blob/a688ff9046a9df58a373086445ab5796cccf9dd3/src/validation.cpp#L3468 - This function is invoked to identify the value that the `Bits` field should have: - https://github.com/bitcoin/bitcoin/blob/a688ff9046a9df58a373086445ab5796cccf9dd3/src/pow.cpp#L13 - - **Goal** - The goal of this check is to avoid the flooding of the btclightclient with headers that are easy to generate. - We want to avoid adding very complex checks here since they can be a source of bugs. Therefore, - we are ok getting headers that could not be part of the canonical chain, as long as sufficient - work has been put to generate them. - - The algorithm works as follows: - Every `params.DifficultyAdjustmentInterval()` the required work of the header is subject to change - in order to help maintain a 10-minutes average time between blocks. - - The configuration contains a `params.ReduceMinDifficulty` parameter that allows headers - to have the minimum amount of work allowed by the network regardless of the previous header's work. - For the mainnet this is set to false, while for the testnet/simnet this is set to true. - - Note: despite the naming, `params.powLimit` refers to the *minimum* work that is allowed. - However, due to the format of the Bits field, when converting those to big ints, - the following check reveals whether the work is more than the minimum: - workBigInt < powLimitBigInt - - 1. If the new block is NOT the first header of the adjustment interval: - a. If `params.ReduceMinDifficulty` has been set - i. If the time of the new block is after 20 minutes from the last block (`params.nPowTargetSpacing*2`) - The expected work of the header is `params.powLimit`. - ii. Otherwise, - The expected work of the header is equal to one of its ancestors. - The ancestor is selected by traversing all ancestors in the given `params.DifficultyAdjustmentInterval()` - and selecting the first one that has either work that is more than the `params.powLimit` or the first one - in the interval. - b. Otherwise, - i. The expected work of the header is equal to the one of its parent. - - From the above and given our goal, a valid check for this case would be that the header - has work that is at least more than the `params.powLimit`. - We will not check whether the work of the new header is exactly the same as the one that is - expected. + err := m.k.InsertHeaders(sdkCtx, msg.Headers) - 2. Otherwise, - a. Get the first block of the `params.DifficultyAdjustmentInterval()` - b. Calculate the timespan between the last block and the first block of the interval. - c. Ensure that the expected work won't wildly fluctuate from the work of the parent: - i. If timespan < `params.nPowTargetTimespan / params.retargetAdjustmentFactor` - timespan = `params.nPowTargetTimespan / params.retargetAdjustmentFactor` - ii. if timespan > `params.nPowTargetTimespan * retarget.AdjustmentFactor` - timespan = `params.nPowTargetTimespan * params.retargetAdjustmentFactor` - - For both the mainnet and the testnet/simnet `params.retargetAdjustmentFactor = 4`. - d. Get PoW of the last block and calculate: - newPow = parentPow * timespan / `params.nPowTargetTimespan` - - From the above calculation and based on (c), we can get the property: - parentPow / `params.retargetAdjustmentFactor` <= newPow <= parentPow * `params.retargetAdjustmentFactor` - e. If newPow > `params.PowLimit` - newPow = `params.PowLimit` - - From the above and given our goal, a valid check for this case would be that: - i. The header has work that is at least more than the `params.powLimit` - ii. The header has work that is between the multiple and dividend of the parent work and `params.retargetAdjustmentFactor` - - Given the stated goal, we would like to reduce complexity as much as possible. Therefore, - here we have decided to not differentiate cases (1) and (2). More specifically, the checks that we do are: - 1. Always verify that the work is at least more than `params.powLimit`. - 2. In the case that `params.ReduceMinDifficulty` has been set to `false`, check - that the header has work that is between the multiple and dividend of the parent work and `params.retargetAdjustmentFactor` - This only happens on the mainnet. - - The above checks lead to some more clutter on the testnet, since some headers won't do check (2) while they should, - but the testnet already allows for minimum work headers so that can be tolerated. For the mainnet, - we will do all required checks, but we will still not test the exact value of the `Bits` field. - Instead we will verify that the `Bits` field value is going to be in a valid range and if someone - does BTC mainnet work to add clutter on the BBN chain, then we can tolerate that. - */ - - if powCheck { - msgBlock := &wire.MsgBlock{Header: *(msg.Header.ToBlockHeader())} - block := btcutil.NewBlock(msgBlock) - err = blockchain.CheckProofOfWork(block, &powLimit) - if err != nil { - return nil, types.ErrInvalidProofOfWOrk - } - } - - if !reduceMinDifficulty { - // The new block will either be the first block of a recalculation event - // which happens every 2,016 blocks or a normal block. - // In the second case, it's difficulty should be exactly the same as it's parent - // while in the second case it should have a maximum difference of a factor of 4 from it - // See: https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch10.asciidoc#retargeting-to-adjust-difficulty - // We consolidate those into a single check. - oldDifficulty := blockchain.CompactToBig(parent.Header.Bits()) - currentDifficulty := blockchain.CompactToBig(msg.Header.Bits()) - maxCurrentDifficulty := new(big.Int).Mul(oldDifficulty, big.NewInt(retargetAdjustmentFactor)) - minCurrentDifficulty := new(big.Int).Div(oldDifficulty, big.NewInt(retargetAdjustmentFactor)) - if currentDifficulty.Cmp(maxCurrentDifficulty) > 0 || currentDifficulty.Cmp(minCurrentDifficulty) < 0 { - return nil, types.ErrInvalidDifficulty.Wrap("difficulty not relevant to parent difficulty") - } - } - - // All good, insert the header - err = k.InsertHeader(sdkCtx, msg.Header) if err != nil { return nil, err } - return &types.MsgInsertHeaderResponse{}, nil -} - -func (m msgServer) InsertHeader(ctx context.Context, msg *types.MsgInsertHeader) (*types.MsgInsertHeaderResponse, error) { - return MsgInsertHeaderWrapped( - ctx, - m.k, - msg, - m.k.btcConfig.PowLimit(), - m.k.btcConfig.ReduceMinDifficulty(), - m.k.btcConfig.RetargetAdjustmentFactor(), - true, - ) + return &types.MsgInsertHeadersResponse{}, nil } // NewMsgServerImpl returns an implementation of the MsgServer interface diff --git a/x/btclightclient/keeper/msg_server_test.go b/x/btclightclient/keeper/msg_server_test.go index b3fa85907..85b7de00f 100644 --- a/x/btclightclient/keeper/msg_server_test.go +++ b/x/btclightclient/keeper/msg_server_test.go @@ -2,13 +2,11 @@ package keeper_test import ( "context" - "math/big" "math/rand" "testing" - sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/testutil/datagen" - "github.com/btcsuite/btcd/chaincfg" + "github.com/stretchr/testify/require" keepertest "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btclightclient/keeper" @@ -21,112 +19,121 @@ func setupMsgServer(t testing.TB) (types.MsgServer, *keeper.Keeper, context.Cont return keeper.NewMsgServerImpl(*k), k, sdk.WrapSDKContext(ctx) } -func FuzzMsgServerInsertHeader(f *testing.F) { - /* - Test that: - 1. if the input message is nil, (nil, error) is returned - 2. if the msg does not contain a header, (nil, error) is returned - 3. if the parent of the header does not exist, (nil, error) is returned - 4. if the work of the header is not within the limits of the new header, (nil, error) is returned - 5. if all checks pass, the header is inserted into storage and an (empty MsgInsertHeaderResponse, nil) is returned - - we do not need to perform insertion checks since those are performed on FuzzKeeperInsertHeader - Building: - - Construct a random tree and insert into storage - - Generate a random header for which its parent does not exist - - Select a random header from the tree and construct BTCHeaderBytes objects on top of it with different work - 1. 4 times the work of parent - 2. 1 < work < 4 times the work of parent - 3. work > 4 times the work of the parent - 4. parent 4 times the work of the header - 5. parent 1 < work < 4 times the work of the header - 6. parent > 4 times the work of the header - */ - datagen.AddRandomSeedsToFuzzer(f, 10) +// Property: Inserting valid chain which has current tip as parent, should always update the chain +// and tip +func FuzzMsgServerInsertNewTip(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 5) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, blcKeeper, sdkCtx := setupMsgServer(t) - - defaultParams := chaincfg.MainNetParams - powLimit := defaultParams.PowLimit - reduceMinDifficulty := defaultParams.ReduceMinDifficulty - retargetAdjustmentFactor := defaultParams.RetargetAdjustmentFactor - - // If the input message is nil, (nil, error) is returned - var msg *types.MsgInsertHeader = nil - resp, err := keeper.MsgInsertHeaderWrapped(sdkCtx, *blcKeeper, msg, *powLimit, reduceMinDifficulty, - retargetAdjustmentFactor, false) - if resp != nil { - t.Errorf("Nil message returned a response") - } - if err == nil { - t.Errorf("Nil message did not return an error") - } - - // If the message does not contain a header, (nil, error) is returned. - msg = &types.MsgInsertHeader{} - resp, err = keeper.MsgInsertHeaderWrapped(sdkCtx, *blcKeeper, msg, *powLimit, reduceMinDifficulty, - retargetAdjustmentFactor, false) - if resp != nil { - t.Errorf("Message without a header returned a response") - } - if err == nil { - t.Errorf("Message without a header did not return an error") - } - - // If the header has a parent that does not exist, (nil, error) is returned - headerParentNotExists := datagen.GenRandomBTCHeaderInfo(r).Header - msg = &types.MsgInsertHeader{Header: headerParentNotExists} - resp, err = keeper.MsgInsertHeaderWrapped(sdkCtx, *blcKeeper, msg, *powLimit, reduceMinDifficulty, - retargetAdjustmentFactor, false) - if resp != nil { - t.Errorf("Message with header with non-existent parent returned a response") - } - if err == nil { - t.Errorf("Message with header with non-existent parent did not return an error") - } + srv, blcKeeper, sdkCtx := setupMsgServer(t) + ctx := sdk.UnwrapSDKContext(sdkCtx) + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + datagen.RandomInt(r, 50)+10, + datagen.RandomInt(r, 50)+10, + ) + initTip := chain.GetTipInfo() + + checkTip( + t, + ctx, + blcKeeper, + *initTip.Work, + initTip.Height, + initTip.Header.ToBlockHeader(), + ) + + chainExenstionLength := uint32(r.Int31n(200) + 1) + chainExtension := datagen.GenRandomValidChainStartingFrom( + r, + initTip.Height, + initTip.Header.ToBlockHeader(), + nil, + chainExenstionLength, + ) + chainExtensionWork := chainWork(chainExtension) + + msg := &types.MsgInsertHeaders{Headers: chainToChainBytes(chainExtension)} + + _, err := srv.InsertHeaders(sdkCtx, msg) + require.NoError(t, err) + extendedChainWork := initTip.Work.Add(*chainExtensionWork) + extendedChainHeight := uint64(uint32(initTip.Height) + chainExenstionLength) + + checkTip( + t, + ctx, + blcKeeper, + extendedChainWork, + extendedChainHeight, + chainExtension[len(chainExtension)-1], + ) + }) +} + +// Property: Inserting valid better chain should always update the chain and tip +func FuzzMsgServerReorgChain(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 5) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + srv, blcKeeper, sdkCtx := setupMsgServer(t) ctx := sdk.UnwrapSDKContext(sdkCtx) - // Construct a tree and insert it into storage - tree := genRandomTree(r, blcKeeper, ctx, uint64(2), 10) - parentHeader := tree.RandomNode(r) - // Do not work with different cases. Select a random integer between 1-retargetAdjustmentFactor+1 - // 1/retargetAdjustmentFactor times, the work is going to be invalid - parentHeaderDifficulty := parentHeader.Header.Difficulty() - // Avoid retargetAdjustmentFactor itself, since the many conversions might lead to inconsistencies - mul := datagen.RandomInt(r, int(retargetAdjustmentFactor-1)) + 1 - if datagen.OneInN(r, 10) { // Give an invalid mul sometimes - mul = uint64(retargetAdjustmentFactor + 1) - } - headerDifficultyMul := sdkmath.NewUintFromBigInt(new(big.Int).Mul(parentHeaderDifficulty, big.NewInt(int64(mul)))) - headerDifficultyDiv := sdkmath.NewUintFromBigInt(new(big.Int).Div(parentHeaderDifficulty, big.NewInt(int64(mul)))) - - // Do tests - headerMoreWork := datagen.GenRandomBTCHeaderInfoWithParentAndBits(r, parentHeader, &headerDifficultyMul) - msg = &types.MsgInsertHeader{Header: headerMoreWork.Header} - resp, err = keeper.MsgInsertHeaderWrapped(sdkCtx, *blcKeeper, msg, *powLimit, reduceMinDifficulty, - retargetAdjustmentFactor, false) - if mul > uint64(retargetAdjustmentFactor) && resp != nil { - t.Errorf("Invalid header work led to a response getting returned") - } - if mul > uint64(retargetAdjustmentFactor) && err == nil { - t.Errorf("Invalid header work did not lead to an error %d %s %s %s", mul, headerDifficultyMul, headerDifficultyDiv, parentHeaderDifficulty) - } - if mul <= uint64(retargetAdjustmentFactor) && err != nil { - t.Errorf("Valid header work led to an error") - } - - headerLessWork := datagen.GenRandomBTCHeaderInfoWithParentAndBits(r, parentHeader, &headerDifficultyDiv) - msg = &types.MsgInsertHeader{Header: headerLessWork.Header} - resp, err = keeper.MsgInsertHeaderWrapped(sdkCtx, *blcKeeper, msg, *powLimit, reduceMinDifficulty, - retargetAdjustmentFactor, false) - if mul > uint64(retargetAdjustmentFactor) && resp != nil { - t.Errorf("Invalid header work led to a response getting returned") - } - if mul > uint64(retargetAdjustmentFactor) && err == nil { - t.Errorf("Invalid header work did not lead to an error") - } - if mul <= uint64(retargetAdjustmentFactor) && err != nil { - t.Errorf("Valid header work led to an error %d %s", mul, err) - } + + chainLength := datagen.RandomInt(r, 50) + 10 + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + datagen.RandomInt(r, 50)+10, + chainLength, + ) + initTip := chain.GetTipInfo() + + checkTip( + t, + ctx, + blcKeeper, + *initTip.Work, + initTip.Height, + initTip.Header.ToBlockHeader(), + ) + + reorgDepth := r.Intn(int(chainLength-1)) + 1 + + forkHeaderHeight := initTip.Height - uint64(reorgDepth) + forkHeader := blcKeeper.GetHeaderByHeight(ctx, uint64(forkHeaderHeight)) + require.NotNil(t, forkHeader) + + // fork chain will always be longer that current c + forkChainLen := reorgDepth + 10 + chainExtension := datagen.GenRandomValidChainStartingFrom( + r, + forkHeader.Height, + forkHeader.Header.ToBlockHeader(), + nil, + uint32(forkChainLen), + ) + chainExtensionWork := chainWork(chainExtension) + msg := &types.MsgInsertHeaders{Headers: chainToChainBytes(chainExtension)} + + _, err := srv.InsertHeaders(sdkCtx, msg) + require.NoError(t, err) + + extendedChainWork := forkHeader.Work.Add(*chainExtensionWork) + extendedChainHeight := forkHeader.Height + uint64(forkChainLen) + + checkTip( + t, + ctx, + blcKeeper, + extendedChainWork, + extendedChainHeight, + chainExtension[len(chainExtension)-1], + ) }) } diff --git a/x/btclightclient/keeper/state.go b/x/btclightclient/keeper/state.go index 439d74356..04c8057b8 100644 --- a/x/btclightclient/keeper/state.go +++ b/x/btclightclient/keeper/state.go @@ -1,7 +1,8 @@ package keeper import ( - sdkmath "cosmossdk.io/math" + "fmt" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/cosmos/cosmos-sdk/codec" @@ -13,8 +14,6 @@ type headersState struct { cdc codec.BinaryCodec headers sdk.KVStore hashToHeight sdk.KVStore - hashToWork sdk.KVStore - tip sdk.KVStore } func (k Keeper) headersState(ctx sdk.Context) headersState { @@ -24,301 +23,107 @@ func (k Keeper) headersState(ctx sdk.Context) headersState { cdc: k.cdc, headers: prefix.NewStore(store, types.HeadersObjectPrefix), hashToHeight: prefix.NewStore(store, types.HashToHeightPrefix), - hashToWork: prefix.NewStore(store, types.HashToWorkPrefix), - tip: prefix.NewStore(store, types.TipPrefix), } } -// CreateHeader Insert the header into the following storages: +// insertHeader Insert the header into the following storages: // - hash->height -// - hash->work -// - (height, hash)->header storage -func (s headersState) CreateHeader(headerInfo *types.BTCHeaderInfo) { - headerHash := headerInfo.Hash - height := headerInfo.Height - cumulativeWork := headerInfo.Work - +// - height -> HeaderInfo +func (s headersState) insertHeader(h *types.BTCHeaderInfo) { // Get necessary keys according - headersKey := types.HeadersObjectKey(height, headerHash) - heightKey := types.HeadersObjectHeightKey(headerHash) - workKey := types.HeadersObjectWorkKey(headerHash) + headersKey := types.HeadersObjectKey(h.Height) + heightKey := types.HeadersObjectHeightKey(h.Hash) // save concrete object - s.headers.Set(headersKey, s.cdc.MustMarshal(headerInfo)) - // map header to height - s.hashToHeight.Set(heightKey, sdk.Uint64ToBigEndian(height)) - // map header to work - workBytes, err := cumulativeWork.Marshal() - if err != nil { - panic("Work cannot be marshalled") - } - s.hashToWork.Set(workKey, workBytes) - - s.updateLongestChain(headerInfo) + s.headers.Set(headersKey, s.cdc.MustMarshal(h)) + s.hashToHeight.Set(heightKey, sdk.Uint64ToBigEndian(h.Height)) } -// CreateTip sets the provided header as the tip -func (s headersState) CreateTip(headerInfo *types.BTCHeaderInfo) { - // Retrieve the key for the tip storage - tipKey := types.TipKey() - s.tip.Set(tipKey, s.cdc.MustMarshal(headerInfo)) -} - -// GetHeader Retrieve a header by its height and hash -func (s headersState) GetHeader(height uint64, hash *bbn.BTCHeaderHashBytes) (*types.BTCHeaderInfo, error) { - // Keyed by (height, hash) - headersKey := types.HeadersObjectKey(height, hash) +func (s headersState) deleteHeader(h *types.BTCHeaderInfo) { + // Get necessary keys + headersKey := types.HeadersObjectKey(h.Height) + heightKey := types.HeadersObjectHeightKey(h.Hash) - if !s.headers.Has(headersKey) { - return nil, types.ErrHeaderDoesNotExist.Wrap("no header with provided height and hash") - } - // Retrieve the raw bytes - rawBytes := s.headers.Get(headersKey) - - return headerInfoFromStoredBytes(s.cdc, rawBytes), nil + // save concrete object + s.headers.Delete(headersKey) + s.hashToHeight.Delete(heightKey) } -// GetHeaderHeight Retrieve the Height of a header -func (s headersState) GetHeaderHeight(hash *bbn.BTCHeaderHashBytes) (uint64, error) { - // Keyed by hash - hashKey := types.HeadersObjectHeightKey(hash) - - // Retrieve the raw bytes for the height - if !s.hashToHeight.Has(hashKey) { - return 0, types.ErrHeaderDoesNotExist.Wrap("no header with provided hash") - } - - bz := s.hashToHeight.Get(hashKey) - // Convert to uint64 form - height := sdk.BigEndianToUint64(bz) - return height, nil -} +func (s headersState) rollBackHeadersUpTo(height uint64) { + headersToDelete := make([]*types.BTCHeaderInfo, 0) -// GetHeaderWork Retrieve the work of a header -func (s headersState) GetHeaderWork(hash *bbn.BTCHeaderHashBytes) (*sdkmath.Uint, error) { - // Keyed by hash - hashKey := types.HeadersObjectHeightKey(hash) - // Retrieve the raw bytes for the work - bz := s.hashToWork.Get(hashKey) - if bz == nil { - return nil, types.ErrHeaderDoesNotExist.Wrap("no header with provided hash") - } + handleInfoFn := func(header *types.BTCHeaderInfo) bool { + if len(headersToDelete) == 0 && height >= header.Height { + // first header in iteration i.e the one with highest height and rollback to block + // higher than current tip has been requested. stop the iteration + return true + } - // Convert to *big.Int form - work := new(sdkmath.Uint) - err := work.Unmarshal(bz) - if err != nil { - panic("Stored header cannot be unmarshalled to sdk.Uint") - } - return work, nil -} + if header.Height == height { + return true + } -// GetHeaderByHash Retrieve a header by its hash -func (s headersState) GetHeaderByHash(hash *bbn.BTCHeaderHashBytes) (*types.BTCHeaderInfo, error) { - // Get the height of the header in order to use it along with the hash - // as a (height, hash) key for the object storage - height, err := s.GetHeaderHeight(hash) - if err != nil { - return nil, err + headersToDelete = append(headersToDelete, header) + return false } - return s.GetHeader(height, hash) -} -// GetBaseBTCHeader retrieves the BTC header with the minimum height -func (s headersState) GetBaseBTCHeader() *types.BTCHeaderInfo { - // Retrieve the canonical chain - canonicalChain := s.GetMainChain() - // If the canonical chain is empty, then there is no base header - if len(canonicalChain) == 0 { - return nil - } - // The base btc header is the oldest one from the canonical chain - return canonicalChain[len(canonicalChain)-1] -} + s.IterateReverseHeaders(handleInfoFn) -// GetTip returns the tip of the canonical chain -func (s headersState) GetTip() *types.BTCHeaderInfo { - if !s.TipExists() { - return nil + // delete rollbacked headers from storage and set up new tip + for _, header := range headersToDelete { + s.deleteHeader(header) } - // Get the key to the tip storage - tipKey := types.TipKey() - return headerInfoFromStoredBytes(s.cdc, s.tip.Get(tipKey)) } -// HeadersByHeight Retrieve headers by their height using an accumulator function -func (s headersState) HeadersByHeight(height uint64, f func(*types.BTCHeaderInfo) bool) { - // The s.headers store is keyed by (height, hash) - // By getting the prefix key using the height, - // we are getting a store of `hash -> header` that contains all hashes - // with a particular height. - store := prefix.NewStore(s.headers, sdk.Uint64ToBigEndian(height)) +// GetHeader Retrieve a header by its height and hash +func (s headersState) GetHeaderByHeight(height uint64) (*types.BTCHeaderInfo, error) { + headersKey := types.HeadersObjectKey(height) - iter := store.Iterator(nil, nil) - defer iter.Close() + // Retrieve the raw bytes + rawBytes := s.headers.Get(headersKey) - // Iterate through the prefix store and retrieve each header object. - // Using the header object invoke the accumulator function. - for ; iter.Valid(); iter.Next() { - header := headerInfoFromStoredBytes(s.cdc, iter.Value()) - // The accumulator function notifies us whether the iteration should stop. - stop := f(header) - if stop { - break - } + if rawBytes == nil { + return nil, types.ErrHeaderDoesNotExist.Wrap("no header with provided height") } -} -// getDescendingHeadersUpTo returns a collection of descending headers according to their height -func (s headersState) getDescendingHeadersUpTo(startHeight uint64, depth uint64) []*types.BTCHeaderInfo { - var headers []*types.BTCHeaderInfo - s.iterateReverseHeaders(func(header *types.BTCHeaderInfo) bool { - // Use `depth+1` because we want to first gather all the headers - // with a depth of `depth`. - if startHeight-header.Height == depth+1 { - return true - } - headers = append(headers, header) - return false - }) - return headers + return headerInfoFromStoredBytes(s.cdc, rawBytes), nil } -// GetHeaderAncestryUpTo returns a list of headers starting from the header parameter and leading to -// -// the header that has a `depth` distance from it. -func (s headersState) GetHeaderAncestryUpTo(currentHeader *types.BTCHeaderInfo, depth uint64) []*types.BTCHeaderInfo { - // Retrieve a collection of headers in descending height order - // Use depth+1 since we want all headers at the depth height. - headers := s.getDescendingHeadersUpTo(currentHeader.Height, depth) - - var chain []*types.BTCHeaderInfo - chain = append(chain, currentHeader) - // Set the current header to be that of the tip - // Iterate through the collection and: - // - Discard anything with a higher height from the current header - // - Find the parent of the header and set the current header to it - // Return the current header - for _, header := range headers { - if currentHeader.HasParent(header) { - currentHeader = header - chain = append(chain, header) - } - } +// GetHeaderByHash Retrieve a header by its hash +func (s headersState) GetHeaderByHash(hash *bbn.BTCHeaderHashBytes) (*types.BTCHeaderInfo, error) { + // Keyed by hash + hashKey := types.HeadersObjectHeightKey(hash) - return chain -} + heightBytes := s.hashToHeight.Get(hashKey) -// GetMainChainUpTo returns the current canonical chain as a collection of block headers -// starting from the tip and ending on the header that has `depth` distance from it. -func (s headersState) GetMainChainUpTo(depth uint64) []*types.BTCHeaderInfo { - // If there is no tip, there is no base header - if !s.TipExists() { - return nil + if heightBytes == nil { + return nil, types.ErrHeaderDoesNotExist.Wrap("no header with provided hash") } - return s.GetHeaderAncestryUpTo(s.GetTip(), depth) -} -// GetMainChain retrieves the main chain as a collection of block headers starting from the tip -// -// and ending on the base BTC header. -func (s headersState) GetMainChain() []*types.BTCHeaderInfo { - if !s.TipExists() { - return nil - } - tip := s.GetTip() - // By providing the depth as the tip.Height, we ensure that we will go as deep as possible - return s.GetMainChainUpTo(tip.Height) -} + // Retrieve the raw bytes + headerBytes := s.headers.Get(heightBytes) -// GetHighestCommonAncestor traverses the ancestors of both headers -// to identify the common ancestor with the highest height -func (s headersState) GetHighestCommonAncestor(header1 *types.BTCHeaderInfo, header2 *types.BTCHeaderInfo) *types.BTCHeaderInfo { - // The algorithm works as follows: - // 1. Initialize a hashmap hash -> bool denoting whether the hash - // of an ancestor of either header1 or header2 has been encountered - // 2. Maintain ancestor1 and ancestor2 as variables that point - // to the current ancestor hash of the header1 and header2 parameters - // 3. Whenever a node is encountered with a hash that is equal to ancestor{1,2}, - // update the ancestor{1,2} variables. - // 4. If ancestor1 or ancestor2 is set to the hash table, - // then that's the hash of the earliest ancestor - // 5. Using the hash of the heighest ancestor wait until we get the header bytes - // in order to avoid an extra access. - if header1.HasParent(header2) { - return header2 - } - if header2.HasParent(header1) { - return header1 + if headerBytes == nil { + height := sdk.BigEndianToUint64(heightBytes) + // panic here, as it means we got mapping hash->height but no mapping height->header + // and those should always be in sync + errMsg := fmt.Sprintf("header height exists but header does not. HeaderHash: %s, HeaderHeight: %d", hash.String(), height) + panic(errMsg) } - ancestor1 := header1.Hash - ancestor2 := header2.Hash - encountered := make(map[string]bool, 0) - encountered[ancestor1.String()] = true - encountered[ancestor2.String()] = true - var found *bbn.BTCHeaderHashBytes = nil - - var resHeader *types.BTCHeaderInfo = nil - - s.iterateReverseHeaders(func(header *types.BTCHeaderInfo) bool { - // During iteration, we will encounter an ancestor for which its header hash - // has been set on the hash map. - // However, we do not have the entry yet, so we set the found flag to that hash - // and when we encounter it during iteration we return it. - if found != nil && header.Hash.Eq(found) { - resHeader = header - return true - } else { - if ancestor1.Eq(header.Hash) { - ancestor1 = header.Header.ParentHash() - if encountered[ancestor1.String()] { - found = ancestor1 - } - encountered[ancestor1.String()] = true - } - if ancestor2.Eq(header.Hash) { - ancestor2 = header.Header.ParentHash() - if encountered[ancestor2.String()] { - found = ancestor2 - } - encountered[ancestor2.String()] = true - } - } - return false - }) - return resHeader + return headerInfoFromStoredBytes(s.cdc, headerBytes), nil } -// GetInOrderAncestorsUntil returns the list of nodes starting from the block *after* the `ancestor` and ending with the `descendant`. -func (s headersState) GetInOrderAncestorsUntil(descendant *types.BTCHeaderInfo, ancestor *types.BTCHeaderInfo) []*types.BTCHeaderInfo { - if ancestor.Height > descendant.Height { - panic("Ancestor has a higher height than descendant") - } - if ancestor.Height == descendant.Height { - // return an empty list - return []*types.BTCHeaderInfo{} - } - - if descendant.HasParent(ancestor) { - return []*types.BTCHeaderInfo{descendant} - } - - ancestors := s.GetHeaderAncestryUpTo(descendant, descendant.Height-ancestor.Height) - if !ancestors[len(ancestors)-1].Eq(ancestor) { - // `ancestor` is not an ancestor of `descendant`, return an empty list - return []*types.BTCHeaderInfo{} - } - - // Discard the last element of the ancestry which corresponds to `ancestor` - ancestors = ancestors[:len(ancestors)-1] - - // Reverse the ancestry - for i, j := 0, len(ancestors)-1; i < j; i, j = i+1, j-1 { - ancestors[i], ancestors[j] = ancestors[j], ancestors[i] +// GetTip returns the tip of the canonical chain +func (s headersState) GetTip() *types.BTCHeaderInfo { + var tip *types.BTCHeaderInfo + handleTipFn := func(header *types.BTCHeaderInfo) bool { + // first retrieved header is tip + tip = header + return true } - - return ancestors + s.IterateReverseHeaders(handleTipFn) + return tip } // HeaderExists Check whether a hash is maintained in storage @@ -326,36 +131,44 @@ func (s headersState) HeaderExists(hash *bbn.BTCHeaderHashBytes) bool { if hash == nil { return false } - return s.hashToHeight.Has(hash.MustMarshal()) + + _, err := s.GetHeaderByHash(hash) + + return err == nil } // TipExists checks whether the tip of the canonical chain has been set func (s headersState) TipExists() bool { - tipKey := types.TipKey() - return s.tip.Has(tipKey) + return s.GetTip() != nil } -// updateLongestChain checks whether the tip should be updated and returns true if it does -func (s headersState) updateLongestChain(headerInfo *types.BTCHeaderInfo) { - // If there is no existing tip, then the header is set as the tip - if !s.TipExists() { - s.CreateTip(headerInfo) - return - } - - // Get the current tip header hash - tip := s.GetTip() +func (s headersState) IterateReverseHeaders(fn func(*types.BTCHeaderInfo) bool) { + // Iterate it in reverse in order to get highest heights first + iter := s.headers.ReverseIterator(nil, nil) + defer iter.Close() - // If the work of the current tip is less than the work of the provided header, - // the provided header is set as the tip. - if headerInfo.Work.GT(*tip.Work) { - s.CreateTip(headerInfo) + for ; iter.Valid(); iter.Next() { + header := headerInfoFromStoredBytes(s.cdc, iter.Value()) + stop := fn(header) + if stop { + break + } } } -func (s headersState) iterateReverseHeaders(fn func(*types.BTCHeaderInfo) bool) { - // Iterate it in reverse in order to get highest heights first - iter := s.headers.ReverseIterator(nil, nil) +// IterateForwardHeaders iterates over all headers in store in increasing order +// - if startPoint is 0, it will start from the lowest height +// - if startPoint is lower that the lowest height, it will start from the lowest height +// - if startPoint is higher than the highest height, it will not iterate at all i.e provided +// callback will not be called +func (s headersState) IterateForwardHeaders(startPoint uint64, fn func(*types.BTCHeaderInfo) bool) { + // Iterate it in increasing order to get lowest heights first + var startKey []byte = nil + if startPoint != 0 { + startKey = types.HeadersObjectKey(startPoint) + } + + iter := s.headers.Iterator(startKey, nil) defer iter.Close() for ; iter.Valid(); iter.Next() { @@ -366,3 +179,14 @@ func (s headersState) iterateReverseHeaders(fn func(*types.BTCHeaderInfo) bool) } } } + +func (s headersState) BaseHeader() *types.BTCHeaderInfo { + var baseHeader *types.BTCHeaderInfo + handleBaseHeaderFn := func(header *types.BTCHeaderInfo) bool { + // first retrieved header is base header + baseHeader = header + return true + } + s.IterateForwardHeaders(0, handleBaseHeaderFn) + return baseHeader +} diff --git a/x/btclightclient/keeper/state_test.go b/x/btclightclient/keeper/state_test.go index f7b2aecc4..296509e62 100644 --- a/x/btclightclient/keeper/state_test.go +++ b/x/btclightclient/keeper/state_test.go @@ -4,558 +4,100 @@ import ( "math/rand" "testing" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) func FuzzHeadersStateCreateHeader(f *testing.F) { /* Checks: 1. A headerInfo provided as an argument leads to the following storage objects being created: - - A (height, headerHash) -> headerInfo object + - A (height) -> headerInfo object - A (headerHash) -> height object - - A (headerHash) -> work object - - A () -> tip object depending on conditions: - * If the tip does not exist, then the headerInfo is the tip - * If the tip exists, and the header inserted has greater work than it, then it becomes the tip Data generation: - Create four headers: 1. The Base header. This will test whether the tip is set. - 2. A header that builds on top of the base header. - This will test whether the tip is properly updated once more work is added. - 3. A header that builds on top of the base header but with less work than (2). - This will test whether the tip is not updated when less work than it is added. - 4. A header that builds on top of the base header but with equal work to (2). - This will test whether the tip is not updated when equal work to it is added. - - No need to create a tree, since this function does not consider the existence or the chain, - it just inserts into state and updates the tip based on a simple work comparison. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - // Generate a tree with a single root node - tree := genRandomTree(r, blcKeeper, ctx, 1, 1) - baseHeader := tree.GetRoot() - - // Test whether the tip and storages are set - tip := blcKeeper.HeadersState(ctx).GetTip() - if tip == nil { - t.Fatalf("Creation of base header did not lead to creation of tip") - } - if !baseHeader.Eq(tip) { - t.Errorf("Tip does not correspond to the one submitted %s %s", baseHeader.Hash, tip.Hash) - } - headerObj, err := blcKeeper.HeadersState(ctx).GetHeader(baseHeader.Height, baseHeader.Hash) - if err != nil { - t.Errorf("Could not retrieve created header") - } - if !baseHeader.Eq(headerObj) { - t.Errorf("Created object does not correspond to the one submitted") - } - work, err := blcKeeper.HeadersState(ctx).GetHeaderWork(baseHeader.Hash) - if err != nil { - t.Errorf("Could not retrieve work of created header") - } - if !baseHeader.Work.Equal(*work) { - t.Errorf("Created object work does not correspond to the one submitted") - } - height, err := blcKeeper.HeadersState(ctx).GetHeaderHeight(baseHeader.Hash) - if err != nil { - t.Errorf("Could not retrieve height of created header") - } - if height != baseHeader.Height { - t.Errorf("Created object height does not correspond to the one submitted") - } - - // Test whether a new header updates the tip. - // The smaller number, the bigger the difficulty - mostDifficulty := sdk.NewUint(10) - lessDifficulty := mostDifficulty.Add(sdk.NewUint(1)) - // Create an object that builds on top of base header - childMostWork := datagen.GenRandomBTCHeaderInfoWithParentAndBits(r, baseHeader, &mostDifficulty) - blcKeeper.HeadersState(ctx).CreateHeader(childMostWork) - // Check whether the tip was updated - tip = blcKeeper.HeadersState(ctx).GetTip() - if tip == nil { - t.Fatalf("Tip became nil instead of getting updated") - } - if !childMostWork.Eq(tip) { - t.Errorf("Tip did not get properly updated") - } - - childEqualWork := datagen.GenRandomBTCHeaderInfoWithParentAndBits(r, baseHeader, &mostDifficulty) - blcKeeper.HeadersState(ctx).CreateHeader(childEqualWork) - // Check whether the tip was updated - tip = blcKeeper.HeadersState(ctx).GetTip() - if !childMostWork.Eq(tip) { - t.Errorf("Tip got updated when it shouldn't") - } - - childLessWork := datagen.GenRandomBTCHeaderInfoWithParentAndBits(r, baseHeader, &lessDifficulty) - blcKeeper.HeadersState(ctx).CreateHeader(childLessWork) - // Check whether the tip was updated - tip = blcKeeper.HeadersState(ctx).GetTip() - if !childMostWork.Eq(tip) { - t.Errorf("Tip got updated when it shouldn't") - } - - }) -} - -func FuzzHeadersStateTipOps(f *testing.F) { - /* - Functions Tested: - 1. CreateTip - 2. GetTip - 3. TipExists - - Checks: - * CreateTip - 1. The `headerInfo` object passed is set as the tip. - * GetTip - 1. If the tip does not exist, nil is returned. - 2. The element maintained in the tip storage is returned. - * TipExists - 1. Returns true/false depending on the existence of a tip. - - Data generation: - - Create two headers: - 1. A header that will be set as the tip. - 2. A header that will override it. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - headerInfo1 := datagen.GenRandomBTCHeaderInfo(r) - headerInfo2 := datagen.GenRandomBTCHeaderInfo(r) - - retrievedHeaderInfo := blcKeeper.HeadersState(ctx).GetTip() - if retrievedHeaderInfo != nil { - t.Errorf("GetTip did not return nil for empty tip") - } - - if blcKeeper.HeadersState(ctx).TipExists() { - t.Errorf("TipExists returned true when no tip has been set") - } - - blcKeeper.HeadersState(ctx).CreateTip(headerInfo1) - retrievedHeaderInfo = blcKeeper.HeadersState(ctx).GetTip() - - if !headerInfo1.Eq(retrievedHeaderInfo) { - t.Errorf("HeaderInfo object did not get stored in tip") - } - - if !blcKeeper.HeadersState(ctx).TipExists() { - t.Errorf("TipExists returned false when a tip had been set") - } - - blcKeeper.HeadersState(ctx).CreateTip(headerInfo2) - retrievedHeaderInfo = blcKeeper.HeadersState(ctx).GetTip() - if !headerInfo2.Eq(retrievedHeaderInfo) { - t.Errorf("Tip did not get overriden") - } - if !blcKeeper.HeadersState(ctx).TipExists() { - t.Errorf("TipExists returned false when a tip had been set") - } - }) -} - -func FuzzHeadersStateGetHeaderOps(f *testing.F) { - /* - Functions tested: - 1. GetHeader - 2. GetHeaderHeight - 3. GetHeaderWork - 4. GetHeaderByHash - 5. HeaderExists - - Checks: - * GetHeader - 1. If the header specified by a height and a hash does not exist, (nil, error) is returned - 2. If the header specified by a height and a hash exists, (headerInfo, nil) is returned - * GetHeaderHeight - 1. If the header specified by the hash does not exist, (0, error) is returned - 2. If the header specified by the hash exists, (height, nil) is returned - * GetHeaderWork - 1. If the header specified by the hash does not exist, (nil, error) is returned - 2. If the header specified by the hash exists, (work, nil) is returned. - * GetHeaderByHash - 1. If the header specified by the hash does not exist (nil, error) is returned - 2. If the header specified by the hash exists (headerInfo, nil) is returned. - * HeaderExists - 1. Returns false if the header passed is nil. - 2. Returns true/false depending on the existence of the header. - - Data generation: - - Create a header and store it using the `CreateHeader` method. Do retrievals to check conditions. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - headerInfo := datagen.GenRandomBTCHeaderInfo(r) - wrongHash := datagen.MutateHash(r, headerInfo.Hash) - wrongHeight := headerInfo.Height + datagen.RandomInt(r, 10) + 1 - - // ****** HeaderExists tests ****** - if blcKeeper.HeadersState(ctx).HeaderExists(nil) { - t.Errorf("HeaderExists returned true for nil input") - } - if blcKeeper.HeadersState(ctx).HeaderExists(headerInfo.Hash) { - t.Errorf("HeaderExists returned true for not created header") - } - blcKeeper.HeadersState(ctx).CreateHeader(headerInfo) - if !blcKeeper.HeadersState(ctx).HeaderExists(headerInfo.Hash) { - t.Errorf("HeaderExists returned false for created header") - } - // ****** GetHeader tests ****** - // correct retrieval - retrievedHeaderInfo, err := blcKeeper.HeadersState(ctx).GetHeader(headerInfo.Height, headerInfo.Hash) - if err != nil { - t.Errorf("GetHeader returned error for valid retrieval: %s", err) - } - if retrievedHeaderInfo == nil || !retrievedHeaderInfo.Eq(headerInfo) { - t.Errorf("GetHeader returned a header that is nil or does not equal the one inserted") - } - retrievedHeaderInfo, err = blcKeeper.HeadersState(ctx).GetHeader(headerInfo.Height, wrongHash) - if retrievedHeaderInfo != nil || err == nil { - t.Errorf("GetHeader returned a filled HeaderInfo or the error is nil for invalid input") - } - - retrievedHeaderInfo, err = blcKeeper.HeadersState(ctx).GetHeader(wrongHeight, headerInfo.Hash) - if retrievedHeaderInfo != nil || err == nil { - t.Errorf("GetHeader returned a filled HeaderInfo or the error is nil for invalid input") - } - - retrievedHeaderInfo, err = blcKeeper.HeadersState(ctx).GetHeader(wrongHeight, wrongHash) - if retrievedHeaderInfo != nil || err == nil { - t.Errorf("GetHeader returned a filled HeaderInfo or the error is nil for invalid input") - } - - // ****** GetHeaderHeight tests ****** - height, err := blcKeeper.HeadersState(ctx).GetHeaderHeight(headerInfo.Hash) - if err != nil { - t.Errorf("GetHeaderHeight returned an error for valid retrieval: %s", err) - } - if height != headerInfo.Height { - t.Errorf("GetHeaderHeight returned incorrect height") - } - height, err = blcKeeper.HeadersState(ctx).GetHeaderHeight(wrongHash) - if err == nil || height != 0 { - t.Errorf("GetHeaderHeight returned nil error or a height different than zero for invalid input") - } - - // ****** GetHeaderWork tests ****** - work, err := blcKeeper.HeadersState(ctx).GetHeaderWork(headerInfo.Hash) - if err != nil { - t.Errorf("GetHeaderWork returned an error for valid retrieval: %s", err) - } - if work == nil || !work.Equal(*headerInfo.Work) { - t.Errorf("GetHeaderWork returned nil or incorrect work") - } - work, err = blcKeeper.HeadersState(ctx).GetHeaderWork(wrongHash) - if err == nil || work != nil { - t.Errorf("GetHeaderWork returned nil error or a work different than nil for invalid input") - } - - // ****** GetHeaderByHash tests ****** - retrievedHeaderInfo, err = blcKeeper.HeadersState(ctx).GetHeaderByHash(headerInfo.Hash) - if err != nil { - t.Errorf("GetHeaderByHash returned an error for valid retrieval: %s", err) - } - if retrievedHeaderInfo == nil || !retrievedHeaderInfo.Eq(headerInfo) { - t.Errorf("GetHeaderByHash returned a header that is nil or does not equal the one inserted") - } - - retrievedHeaderInfo, err = blcKeeper.HeadersState(ctx).GetHeaderByHash(wrongHash) - if retrievedHeaderInfo != nil || err == nil { - t.Errorf("GetHeaderByHash returned a filled HeaderInfo or the error is nil for invalid input") - } - }) -} - -func FuzzHeadersStateGetBaseBTCHeader(f *testing.F) { - /* - Checks: - 1. If no headers exist, nil is returned - 2. The oldest element of the main chain is returned. - - Data generation: - - Generate a random tree and retrieve the main chain from it. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - nilBaseHeader := blcKeeper.HeadersState(ctx).GetBaseBTCHeader() - if nilBaseHeader != nil { - t.Errorf("Non-existent base BTC header led to non-nil return") - } - - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - expectedBaseHeader := tree.GetRoot() - - gotBaseHeader := blcKeeper.HeadersState(ctx).GetBaseBTCHeader() - - if !expectedBaseHeader.Eq(gotBaseHeader) { - t.Errorf("Expected base header %s got %s", expectedBaseHeader.Hash, gotBaseHeader.Hash) - } - }) -} - -func FuzzHeadersStateHeadersByHeight(f *testing.F) { - /* - Checks: - 1. If the height does not correspond to any headers, the function parameter is never invoked. - 2. If the height corresponds to headers, the function is invoked for all of those headers. - 3. If the height corresponds to headers, the function is invoked until a stop signal is given. - - Data generation: - - Generate a `rand.Intn(N)` number of headers with a particular height and insert them into storage. - - The randomness of the number of headers should guarantee that (1) and (2) are observed. - - Generate a random stop signal 1/N times. + 2. Create random chain of of headers, and insert them into the state + 3. All operations should be consistent with each other. */ datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - maxHeaders := 256 // maximum 255 headers with particular height - numHeaders := datagen.RandomInt(r, maxHeaders) - - // This will contain a mapping between all the header hashes that were created - // and a boolean value. - hashCount := make(map[string]bool) - // Setup a tree with a single header - tree := genRandomTree(r, blcKeeper, ctx, 1, 1) - baseHeader := tree.GetRoot() - height := baseHeader.Height + 1 + state := blcKeeper.HeadersState(ctx) - // Generate numHeaders with particular height - var i uint64 - for i = 0; i < numHeaders; i++ { - headerInfo := datagen.GenRandomBTCHeaderInfoWithParent(r, baseHeader) - hashCount[headerInfo.Hash.MarshalHex()] = true - blcKeeper.InsertHeader(ctx, headerInfo.Header) //nolint:errcheck - } + // operations no empty state + require.Nil(t, state.GetTip()) + require.Nil(t, state.BaseHeader()) + require.False(t, state.TipExists()) - var headersAdded uint64 = 0 - var stopHeight uint64 = 0 - blcKeeper.HeadersState(ctx).HeadersByHeight(height, func(header *types.BTCHeaderInfo) bool { - headersAdded += 1 - if _, ok := hashCount[header.Hash.MarshalHex()]; !ok { - t.Errorf("HeadersByHeight returned header that was not created") - } - hashCount[header.Hash.MarshalHex()] = true - if datagen.OneInN(r, maxHeaders) { - // Only set it once - if stopHeight != 0 { - stopHeight = headersAdded - } - return true - } + numForward := 0 + numBackward := 0 + state.IterateForwardHeaders(0, func(headerInfo *types.BTCHeaderInfo) bool { + numForward++ return false }) - if stopHeight != 0 && stopHeight != headersAdded { - t.Errorf("Stop signal was not respected. %d headers were added while %d were expected", stopHeight, headersAdded) - } - - for _, cnt := range hashCount { - if !cnt && headersAdded == numHeaders { - // If there is a header hash that the count is not set - // and all the headers were iterated, then something went wrong - t.Errorf("Function did not iterate all headers") - } - } - }) -} - -func FuzzHeadersStateGetMainChain(f *testing.F) { - /* - Functions Tested: - 1. GetMainChain - 2. GetMainChainUpTo - - Checks: - * GetMainChain - 1. We get the entire main chain. - * GetMainChainUpTo - 1. We get the main chain containing `depth + 1` elements. - - Data generation: - - Generate a random tree and retrieve the main chain from it. - - Randomly generate the depth - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - expectedMainChain := tree.GetMainChain() - gotMainChain := blcKeeper.HeadersState(ctx).GetMainChain() - - if len(expectedMainChain) != len(gotMainChain) { - t.Fatalf("Expected main chain length of %d, got %d", len(expectedMainChain), len(gotMainChain)) - } - - for i := 0; i < len(expectedMainChain); i++ { - if !expectedMainChain[i].Eq(gotMainChain[i]) { - t.Errorf("Expected header %s at position %d, got %s", expectedMainChain[i].Hash, i, gotMainChain[i].Hash) - } - } - - // depth is a random integer - upToDepth := datagen.RandomInt(r, len(expectedMainChain)) - expectedMainChainUpTo := expectedMainChain[:upToDepth+1] - gotMainChainUpTo := blcKeeper.HeadersState(ctx).GetMainChainUpTo(upToDepth) - if len(expectedMainChainUpTo) != len(gotMainChainUpTo) { - t.Fatalf("Expected main chain length of %d, got %d", len(expectedMainChainUpTo), len(gotMainChainUpTo)) - } - - for i := 0; i < len(expectedMainChainUpTo); i++ { - if !expectedMainChainUpTo[i].Eq(gotMainChainUpTo[i]) { - t.Errorf("Expected header %s at position %d, got %s", expectedMainChainUpTo[i].Hash, i, gotMainChainUpTo[i].Hash) - } - } - }) -} - -func FuzzHeadersStateGetHighestCommonAncestor(f *testing.F) { - /* - Checks: - 1. The header returned is an ancestor of both headers. - 2. There is no header that is an ancestor of both headers that has a higher height - than the one returned. - 3. There is always a header that is returned, since all headers are built on top of the same root. - - Data generation: - - Generate a random tree of headers and store it. - - Select two random headers and call `GetHighestCommonAncestor` for them. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - // Generate a random tree with at least one node - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - // Retrieve a random common ancestor - commonAncestor := tree.RandomNode(r) - - // Generate two child nodes for the common ancestor - childRoot1 := datagen.GenRandomBTCHeaderInfoWithParent(r, commonAncestor) - childRoot2 := datagen.GenRandomBTCHeaderInfoWithParent(r, commonAncestor) - if tree.Contains(childRoot1) || tree.Contains(childRoot2) { - // Unlucky case where we get the same hash. Should be extremely rare. - // Instead of adding extra complexity to this test case, just skip it - t.Skip() - } - // Insert them into storage - blcKeeper.InsertHeader(ctx, childRoot1.Header) //nolint:errcheck - blcKeeper.InsertHeader(ctx, childRoot2.Header) //nolint:errcheck - // Add them into the data structures maintained by the tree - tree.Add(childRoot1, commonAncestor) - tree.Add(childRoot2, commonAncestor) - - // Generate subtrees rooted at the descendant nodes - genRandomTreeWithParent(r, blcKeeper, ctx, 1, 10, childRoot1, tree) - genRandomTreeWithParent(r, blcKeeper, ctx, 1, 10, childRoot2, tree) - - // Get a random descendant from each of the subtrees - descendant1 := tree.RandomDescendant(r, childRoot1) - descendant2 := tree.RandomDescendant(r, childRoot2) - - retrievedHighestCommonAncestor := blcKeeper.HeadersState(ctx).GetHighestCommonAncestor(descendant1, descendant2) - if retrievedHighestCommonAncestor == nil { - t.Fatalf("No common ancestor found between the nodes %s and %s. Expected ancestor: %s", descendant1.Hash, descendant2.Hash, commonAncestor.Hash) - } - if !commonAncestor.Eq(retrievedHighestCommonAncestor) { - t.Errorf("Did not retrieve the correct highest common ancestor. Got %s, expected %s", retrievedHighestCommonAncestor.Hash, commonAncestor.Hash) - } - }) -} - -func FuzzHeadersStateGetInOrderAncestorsUntil(f *testing.F) { - /* - Checks: - 1. All the ancestors are contained in the returned list. - 2. The ancestors do not include the `ancestor` parameter. - 3. The ancestors are in order starting from the `ancestor`'s child and leading to the `descendant` parameter. - - Data generation: - - Generate a random tree of headers and store it. - - Select a random header which will serve as the `descendant`. Cannot be the base header. - - Select a random header that is an ancestor of `descendant`. - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - - // Generate a tree of any size. - // We can work with even one header, since this should lead to an empty result. - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - - // Get a random header from the tree - descendant := tree.RandomNode(r) - // Get a random ancestor from it - ancestor := tree.RandomAncestor(r, descendant) - // Get the ancestry of the descendant. - // It is in reverse order from the one that GetInOrderAncestorsUntil returns, since it starts with the descendant. - expectedAncestorsReverse := tree.GetNodeAncestryUpTo(descendant, ancestor) - gotAncestors := blcKeeper.HeadersState(ctx).GetInOrderAncestorsUntil(descendant, ancestor) - if len(gotAncestors) != len(expectedAncestorsReverse) { - t.Errorf("Got different ancestor list sizes. Expected %d got %d", len(expectedAncestorsReverse), len(gotAncestors)) - } - - for i := 0; i < len(expectedAncestorsReverse); i++ { - reverseIdx := len(expectedAncestorsReverse) - i - 1 - if !expectedAncestorsReverse[i].Eq(gotAncestors[reverseIdx]) { - t.Errorf("Ancestors do not match. Expected %s got %s", expectedAncestorsReverse[i].Hash, gotAncestors[reverseIdx].Hash) - } - } - }) -} - -func FuzzHeadersStateGetHeaderAncestryUpTo(f *testing.F) { - /* - Checks: - 1. All the ancestors up to the depth are in the returned list. - 2. The ancestors start from the parameter and lead to the ancestor. - - Data generation: - - Generate a random tree of headers and store it. - - Select a random header which will serve as the `header` parameter. - - Select a random depth in the range of [0, header.Height-baseHeader.Height] - */ - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - tree := genRandomTree(r, blcKeeper, ctx, 1, 10) - - descendant := tree.RandomNode(r) - ancestor := tree.RandomAncestor(r, descendant) - - ancestors := blcKeeper.HeadersState(ctx).GetHeaderAncestryUpTo(descendant, descendant.Height-ancestor.Height) - // Use the parent of the ancestor since UpTo does not include the ancestor in the result - expectedAncestors := tree.GetNodeAncestryUpTo(descendant, tree.GetParent(ancestor)) - - if len(ancestors) != len(expectedAncestors) { - t.Errorf("Got different ancestor list sizes. Expected %d, got %d", len(expectedAncestors), len(ancestors)) - } - - for i := 0; i < len(ancestors); i++ { - if !ancestors[i].Eq(expectedAncestors[i]) { - t.Errorf("Ancestors do not match. Expected %s, got %s", expectedAncestors[i].Hash, ancestors[i].Hash) - } + state.IterateReverseHeaders(func(headerInfo *types.BTCHeaderInfo) bool { + numBackward++ + return false + }) + require.Equal(t, 0, numForward) + require.Equal(t, 0, numBackward) + + _, err := state.GetHeaderByHeight(datagen.GenRandomBTCHeight(r)) + require.Error(t, err) + rh := datagen.GenRandomBtcdHash(r) + randomHash := bbn.NewBTCHeaderHashBytesFromChainhash(&rh) + _, err = state.GetHeaderByHash(&randomHash) + require.Error(t, err) + + // 10 to 60 headers + chainLength := datagen.RandomInt(r, 50) + 10 + // height from 10 to 60 + initchainHeight := datagen.RandomInt(r, 50) + 10 + + // populate the state with random chain + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + initchainHeight, + chainLength, + ) + + // operations populates state + require.NotNil(t, state.GetTip()) + require.NotNil(t, state.BaseHeader()) + require.True(t, state.TipExists()) + + numForward = 0 + numBackward = 0 + state.IterateForwardHeaders(0, func(headerInfo *types.BTCHeaderInfo) bool { + numForward++ + return false + }) + state.IterateReverseHeaders(func(headerInfo *types.BTCHeaderInfo) bool { + numBackward++ + return false + }) + require.Equal(t, chainLength+1, uint64(numForward)) + require.Equal(t, chainLength+1, uint64(numBackward)) + + chainInfos := chain.GetChainInfo() + + for _, info := range chainInfos { + byHash, err := state.GetHeaderByHash(info.Hash) + require.NoError(t, err) + byHeight, err := state.GetHeaderByHeight(info.Height) + require.NoError(t, err) + require.True(t, allFieldsEqual(byHash, byHeight)) + require.True(t, allFieldsEqual(byHash, info)) } }) } diff --git a/x/btclightclient/keeper/utils_test.go b/x/btclightclient/keeper/utils_test.go index e8cab00ea..639458f72 100644 --- a/x/btclightclient/keeper/utils_test.go +++ b/x/btclightclient/keeper/utils_test.go @@ -1,12 +1,19 @@ package keeper_test import ( - "fmt" + "math/big" + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" - "math/rand" + "github.com/stretchr/testify/require" ) // Mock hooks interface @@ -43,40 +50,75 @@ func (m *MockHooks) AfterBTCHeaderInserted(_ sdk.Context, headerInfo *types.BTCH m.AfterBTCHeaderInsertedStore = append(m.AfterBTCHeaderInsertedStore, headerInfo) } -// Methods for generating trees +func allFieldsEqual(a *types.BTCHeaderInfo, b *types.BTCHeaderInfo) bool { + return a.Height == b.Height && a.Hash.Eq(b.Hash) && a.Header.Eq(b.Header) && a.Work.Equal(*b.Work) +} + +// this function must not be used at difficulty adjustment boundaries, as then +// difficulty adjustment calculation will fail +func genRandomChain( + t *testing.T, + r *rand.Rand, + k *keeper.Keeper, + ctx sdk.Context, + initialHeight uint64, + chainLength uint64, +) (*types.BTCHeaderInfo, *datagen.BTCHeaderPartialChain) { + genesisHeader := datagen.NewBTCHeaderChainWithLength(r, initialHeight, 0, 1) + genesisHeaderInfo := genesisHeader.GetChainInfo()[0] + k.SetBaseBTCHeader(ctx, *genesisHeaderInfo) + randomChain := datagen.NewBTCHeaderChainFromParentInfo( + r, + genesisHeaderInfo, + uint32(chainLength), + ) + err := k.InsertHeaders(ctx, randomChain.ChainToBytes()) + require.NoError(t, err) + tip := k.GetTipInfo(ctx) + randomChainTipInfo := randomChain.GetTipInfo() + require.True(t, allFieldsEqual(tip, randomChainTipInfo)) + return genesisHeaderInfo, randomChain +} -// genRandomTree generates a tree of headers. It accomplishes this by generating a root -// which will serve as the base header and then invokes the `genRandomTreeWithRoot` utility. -// The `minTreeHeight` and `maxTreeHeight` parameters denote the minimum and maximum height -// of the tree that is generated. For example, a `minTreeHeight` of 1, -// means that the tree should have at least one node (the root), while -// a `maxTreeHeight` of 4, denotes that the maximum height of the tree should be 4. -func genRandomTree(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, minHeight uint64, maxHeight uint64) *datagen.BTCHeaderTree { - tree := datagen.NewBTCHeaderTree() - // Generate the root for the tree - root := datagen.GenRandomBTCHeaderInfo(r) - tree.Add(root, nil) - k.SetBaseBTCHeader(ctx, *root) +func checkTip( + t *testing.T, + ctx sdk.Context, + blcKeeper *keeper.Keeper, + expectedWork sdkmath.Uint, + expectedHeight uint64, + expectedTipHeader *wire.BlockHeader) { - genRandomTreeWithParent(r, k, ctx, minHeight-1, maxHeight-1, root, tree) + currentTip := blcKeeper.GetTipInfo(ctx) + blockByHeight := blcKeeper.GetHeaderByHeight(ctx, currentTip.Height) + blockByHash := blcKeeper.GetHeaderByHash(ctx, currentTip.Hash) - return tree -} + // Consistency check between tip and block by height and block by hash + require.NotNil(t, blockByHeight) + require.NotNil(t, currentTip) + require.NotNil(t, blockByHash) + require.True(t, allFieldsEqual(currentTip, blockByHeight)) + require.True(t, allFieldsEqual(currentTip, blockByHash)) -// genRandomTreeWithParent is a utility function for inserting the headers -// While the tree is generated, the headers that are generated for it are inserted into storage. -func genRandomTreeWithParent(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, minHeight uint64, - maxHeight uint64, root *types.BTCHeaderInfo, tree *datagen.BTCHeaderTree) { + // check all tip fields + require.True(t, currentTip.Work.Equal(expectedWork)) + require.Equal(t, currentTip.Height, expectedHeight) + expectedTipHeaderHash := expectedTipHeader.BlockHash() + require.True(t, currentTip.Hash.ToChainhash().IsEqual(&expectedTipHeaderHash)) + require.True(t, currentTip.Header.Hash().ToChainhash().IsEqual(&expectedTipHeaderHash)) +} - if minHeight > maxHeight { - panic("Min height more than max height") +func chainToChainBytes(chain []*wire.BlockHeader) []bbn.BTCHeaderBytes { + chainBytes := make([]bbn.BTCHeaderBytes, len(chain)) + for i, header := range chain { + chainBytes[i] = bbn.NewBTCHeaderBytesFromBlockHeader(header) } + return chainBytes +} - tree.GenRandomBTCHeaderTree(r, minHeight, maxHeight, root, func(header *types.BTCHeaderInfo) bool { - err := k.InsertHeader(ctx, header.Header) - if err != nil { - panic(fmt.Sprintf("header insertion failed: %s", err)) - } - return true - }) +func chainWork(chain []*wire.BlockHeader) *sdkmath.Uint { + totalWork := sdkmath.NewUint(0) + for _, header := range chain { + totalWork = sdkmath.NewUintFromBigInt(new(big.Int).Add(totalWork.BigInt(), blockchain.CalcWork(header.Bits))) + } + return &totalWork } diff --git a/x/btclightclient/types/btc_header_info.go b/x/btclightclient/types/btc_header_info.go index baabc45a0..bb16a839c 100644 --- a/x/btclightclient/types/btc_header_info.go +++ b/x/btclightclient/types/btc_header_info.go @@ -21,7 +21,3 @@ func (m *BTCHeaderInfo) HasParent(parent *BTCHeaderInfo) bool { func (m *BTCHeaderInfo) Eq(other *BTCHeaderInfo) bool { return m.Hash.Eq(other.Hash) } - -func (m BTCHeaderInfo) Validate() error { - return nil -} diff --git a/x/btclightclient/types/btc_light_client.go b/x/btclightclient/types/btc_light_client.go new file mode 100644 index 000000000..3ea54ef4b --- /dev/null +++ b/x/btclightclient/types/btc_light_client.go @@ -0,0 +1,431 @@ +package types + +import ( + "fmt" + "time" + + sdkmath "cosmossdk.io/math" + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +type BtcChainReadStore interface { + GetHeaderByHash(hash *bbn.BTCHeaderHashBytes) (*BTCHeaderInfo, error) + GetHeaderByHeight(height uint64) (*BTCHeaderInfo, error) + GetTip() *BTCHeaderInfo +} + +// Copy from neutrino light client +// https://github.com/lightninglabs/neutrino/blob/master/blockmanager.go#L2875 +type lightChainCtx struct { + params *chaincfg.Params + blocksPerRetarget int32 + minRetargetTimespan int64 + maxRetargetTimespan int64 +} + +var _ blockchain.ChainCtx = (*lightChainCtx)(nil) + +func newLightChainCtx(params *chaincfg.Params, blocksPerRetarget int32, + minRetargetTimespan, maxRetargetTimespan int64) *lightChainCtx { + + return &lightChainCtx{ + params: params, + blocksPerRetarget: blocksPerRetarget, + minRetargetTimespan: minRetargetTimespan, + maxRetargetTimespan: maxRetargetTimespan, + } +} + +func newLightChainCtxFromParams(params *chaincfg.Params) *lightChainCtx { + targetTimespan := int64(params.TargetTimespan / time.Second) + targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second) + adjustmentFactor := params.RetargetAdjustmentFactor + + blocksPerRetarget := int32(targetTimespan / targetTimePerBlock) + minRetargetTimespan := targetTimespan / adjustmentFactor + maxRetargetTimespan := targetTimespan * adjustmentFactor + + return newLightChainCtx( + params, blocksPerRetarget, minRetargetTimespan, maxRetargetTimespan, + ) +} + +func (l *lightChainCtx) ChainParams() *chaincfg.Params { + return l.params +} + +func (l *lightChainCtx) BlocksPerRetarget() int32 { + return l.blocksPerRetarget +} + +func (l *lightChainCtx) MinRetargetTimespan() int64 { + return l.minRetargetTimespan +} + +func (l *lightChainCtx) MaxRetargetTimespan() int64 { + return l.maxRetargetTimespan +} + +// We never check checkpoints in our on-chain light client. Required by blockchain.ChainCtx interface +func (l *lightChainCtx) VerifyCheckpoint(int32, *chainhash.Hash) bool { + return false +} + +// If VerifyCheckpoint returns false, this function is never called. Required by blockchain.ChainCtx interface +func (l *lightChainCtx) FindPreviousCheckpoint() (blockchain.HeaderCtx, error) { + return nil, nil +} + +type localHeaderInfo struct { + header *wire.BlockHeader + height uint64 + totalWork sdkmath.Uint +} + +func newLocalHeaderInfo( + header *wire.BlockHeader, + height uint64, + totalWork sdkmath.Uint) *localHeaderInfo { + + return &localHeaderInfo{ + header: header, + height: height, + totalWork: totalWork, + } +} + +func toLocalInfo(i *BTCHeaderInfo) *localHeaderInfo { + if i == nil { + return nil + } + + return newLocalHeaderInfo(i.Header.ToBlockHeader(), i.Height, *i.Work) +} + +func (i *localHeaderInfo) toBTCHeaderInfo() *BTCHeaderInfo { + blockHash := i.header.BlockHash() + headerBytes := bbn.NewBTCHeaderBytesFromBlockHeader(i.header) + headerHash := bbn.NewBTCHeaderHashBytesFromChainhash(&blockHash) + + return NewBTCHeaderInfo( + &headerBytes, + &headerHash, + i.height, + &i.totalWork, + ) +} + +func toBTCHeaderInfos(infos []*localHeaderInfo) []*BTCHeaderInfo { + result := make([]*BTCHeaderInfo, len(infos)) + + for i, info := range infos { + result[i] = info.toBTCHeaderInfo() + } + + return result +} + +// based on neutrio light client +// https://github.com/lightninglabs/neutrino/blob/master/blockmanager.go#L2944 +type lightHeaderCtx struct { + height uint64 + bits uint32 + timestamp int64 + store *storeWithExtensionChain +} + +var _ blockchain.HeaderCtx = (*lightHeaderCtx)(nil) + +func newLightHeaderCtx(height uint64, header *wire.BlockHeader, + store *storeWithExtensionChain) *lightHeaderCtx { + + return &lightHeaderCtx{ + height: height, + bits: header.Bits, + timestamp: header.Timestamp.Unix(), + store: store, + } +} + +func (l *lightHeaderCtx) Height() int32 { + return int32(l.height) +} + +func (l *lightHeaderCtx) Bits() uint32 { + return l.bits +} + +func (l *lightHeaderCtx) Timestamp() int64 { + return l.timestamp +} + +func (l *lightHeaderCtx) Parent() blockchain.HeaderCtx { + // The parent is just an ancestor with distance 1. + anc := l.RelativeAncestorCtx(1) + + if anc == nil { + return nil + } + + return anc +} + +func (l *lightHeaderCtx) RelativeAncestorCtx( + distance int32) blockchain.HeaderCtx { + + ancestorHeight := l.Height() - distance + + if ancestorHeight < 0 { + // We don't have this header. + return nil + } + + ancU64 := uint64(ancestorHeight) + + ancestor := l.store.getHeaderAtHeight(ancU64) + + if ancestor == nil { + return nil + } + + return newLightHeaderCtx( + ancU64, ancestor.header, l.store, + ) +} + +type BtcLightClient struct { + params *chaincfg.Params + ctx *lightChainCtx +} + +func NewBtcLightClient( + params *chaincfg.Params, + ctx *lightChainCtx) *BtcLightClient { + return &BtcLightClient{ + params: params, + ctx: ctx, + } +} + +func NewBtcLightClientFromParams(params *chaincfg.Params) *BtcLightClient { + return NewBtcLightClient(params, newLightChainCtxFromParams(params)) +} + +func headersFormChain(headers []*wire.BlockHeader) bool { + var ( + lastHeader chainhash.Hash + emptyHash chainhash.Hash + ) + for _, blockHeader := range headers { + blockHash := blockHeader.BlockHash() + + // If we haven't yet set lastHeader, set it now. + if lastHeader == emptyHash { + lastHeader = blockHash + continue + } + + // Ensure that blockHeader.PrevBlock matches lastHeader. + if blockHeader.PrevBlock != lastHeader { + return false + } + + lastHeader = blockHash + } + + return true +} + +type DisableHeaderInTheFutureValidationTimeSource struct { + h *wire.BlockHeader +} + +func NewDisableHeaderInTheFutureValidationTimeSource(header *wire.BlockHeader) *DisableHeaderInTheFutureValidationTimeSource { + return &DisableHeaderInTheFutureValidationTimeSource{ + h: header, + } +} + +// AdjustedTime returns the current time adjusted by the median time +// offset as calculated from the time samples added by AddTimeSample. +func (d *DisableHeaderInTheFutureValidationTimeSource) AdjustedTime() time.Time { + return d.h.Timestamp +} + +func (d *DisableHeaderInTheFutureValidationTimeSource) AddTimeSample(id string, timeVal time.Time) { + //no op +} + +func (d *DisableHeaderInTheFutureValidationTimeSource) Offset() time.Duration { + return 0 * time.Second +} + +type storeWithExtensionChain struct { + headers []*localHeaderInfo + store BtcChainReadStore +} + +func newStoreWithExtensionChain( + store BtcChainReadStore, + maxExentsionHeaders int, +) *storeWithExtensionChain { + + return &storeWithExtensionChain{ + // large capacity to avoid reallocation + headers: make([]*localHeaderInfo, 0, maxExentsionHeaders), + store: store, + } +} + +func (s *storeWithExtensionChain) addHeader(header *localHeaderInfo) { + s.headers = append(s.headers, header) +} + +func (s *storeWithExtensionChain) getHeaderAtHeight(height uint64) *localHeaderInfo { + if len(s.headers) == 0 || height < s.headers[0].height { + h, err := s.store.GetHeaderByHeight(height) + + if err != nil { + return nil + } + return newLocalHeaderInfo(h.Header.ToBlockHeader(), height, *h.Work) + } else { + headerIndex := height - s.headers[0].height + return s.headers[headerIndex] + } +} + +func (l *BtcLightClient) processNewHeadersChain( + store *storeWithExtensionChain, + chainParent *localHeaderInfo, + chain []*wire.BlockHeader) error { + // init info about parent as current tip + parentHeaderInfo := chainParent + + for _, blockHeader := range chain { + h := blockHeader + + err := l.checkHeader( + store, parentHeaderInfo, h, + ) + + if err != nil { + return err + } + + childWork := CalcHeaderWork(h) + newHeaderInfo := newLocalHeaderInfo( + h, + parentHeaderInfo.height+1, + CumulativeWork(parentHeaderInfo.totalWork, childWork), + ) + store.addHeader(newHeaderInfo) + parentHeaderInfo = newHeaderInfo + } + + return nil +} + +type RollbackInfo struct { + HeaderToRollbackTo *BTCHeaderInfo +} + +type InsertResult struct { + HeadersToInsert []*BTCHeaderInfo + // if rollback is not nil, it means that we need to rollback to the header provided header + RollbackInfo *RollbackInfo +} + +func (l *BtcLightClient) InsertHeaders(readStore BtcChainReadStore, headers []*wire.BlockHeader) (*InsertResult, error) { + headersLen := len(headers) + if headersLen == 0 { + return nil, fmt.Errorf("cannot insert empty headers") + } + + if !headersFormChain(headers) { + return nil, fmt.Errorf("headers do not form a chain") + } + + currentTip := toLocalInfo(readStore.GetTip()) + + if currentTip == nil { + return nil, fmt.Errorf("cannot insert headers when tip is nil") + } + + currentTipHash := currentTip.header.BlockHash() + + firstHeaderOfExtensionChain := headers[0] + + store := newStoreWithExtensionChain(readStore, headersLen) + + if firstHeaderOfExtensionChain.PrevBlock.IsEqual(¤tTipHash) { + // most common case - extending of current tip + if err := l.processNewHeadersChain(store, currentTip, headers); err != nil { + return nil, err + } + + return &InsertResult{ + HeadersToInsert: toBTCHeaderInfos(store.headers), + RollbackInfo: nil, + }, nil + } else { + // here we received potential new fork + parentHash := bbn.NewBTCHeaderHashBytesFromChainhash(&firstHeaderOfExtensionChain.PrevBlock) + forkParent, err := readStore.GetHeaderByHash(&parentHash) + + if err != nil { + return nil, err + } + + forkParentInfo := toLocalInfo(forkParent) + + if err := l.processNewHeadersChain(store, forkParentInfo, headers); err != nil { + return nil, err + } + + tipOfNewChain := store.headers[len(store.headers)-1] + + if tipOfNewChain.totalWork.LTE(currentTip.totalWork) { + return nil, fmt.Errorf("the new chain has less or equal work than the current tip") + } + + return &InsertResult{ + HeadersToInsert: toBTCHeaderInfos(store.headers), + RollbackInfo: &RollbackInfo{ + // we need to rollback to fork parent + HeaderToRollbackTo: forkParent, + }, + }, nil + } +} + +// checkHeader checks if the header is valid and can be added to the store. +// One criticial condition is that to properly validate difficulty adjustments +// we should have at least one header which is at difficulty adjustment boundary +// in store. +func (l *BtcLightClient) checkHeader( + s *storeWithExtensionChain, + parentHeaderInfo *localHeaderInfo, + blockHeader *wire.BlockHeader, +) error { + parentHeaderCtx := newLightHeaderCtx( + parentHeaderInfo.height, parentHeaderInfo.header, s, + ) + + var emptyFlags blockchain.BehaviorFlags + err := blockchain.CheckBlockHeaderContext( + blockHeader, parentHeaderCtx, emptyFlags, l.ctx, true, + ) + if err != nil { + return err + } + + return blockchain.CheckBlockHeaderSanity( + blockHeader, l.params.PowLimit, NewDisableHeaderInTheFutureValidationTimeSource(blockHeader), + emptyFlags, + ) +} diff --git a/x/btclightclient/types/codec.go b/x/btclightclient/types/codec.go index 3a094f1e8..4bf793fb8 100644 --- a/x/btclightclient/types/codec.go +++ b/x/btclightclient/types/codec.go @@ -12,9 +12,9 @@ func RegisterCodec(cdc *codec.LegacyAmino) { func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { - // Register messages + // Register messages registry.RegisterImplementations((*sdk.Msg)(nil), - &MsgInsertHeader{}, + &MsgInsertHeaders{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/btclightclient/types/genesis.go b/x/btclightclient/types/genesis.go index f9e5226f9..278e2af70 100644 --- a/x/btclightclient/types/genesis.go +++ b/x/btclightclient/types/genesis.go @@ -1,33 +1,51 @@ package types import ( + "fmt" + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/chaincfg" ) +func SimnetGenesisBlock() BTCHeaderInfo { + // By default we use the genesis block of the simnet, as it is the best for testing + var header = chaincfg.SimNetParams.GenesisBlock.Header + var headerHash = chaincfg.SimNetParams.GenesisHash + + bytes := bbn.NewBTCHeaderBytesFromBlockHeader(&header) + hash := bbn.NewBTCHeaderHashBytesFromChainhash(headerHash) + work := CalcWork(&bytes) + + return *NewBTCHeaderInfo( + &bytes, + &hash, + 0, + &work, + ) +} + // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() *GenesisState { - headerBytes := bbn.GetBaseBTCHeaderBytes() - headerHeight := bbn.GetBaseBTCHeaderHeight() - headerHash := headerBytes.Hash() - // The cumulative work for the Base BTC header is only the work - // for that particular header. This means that it is very important - // that no forks will happen that discard the base header because we - // will not be able to detect those. Cumulative work will build based - // on the sum of the work of the chain starting from the base header. - headerWork := CalcWork(&headerBytes) - baseHeaderInfo := NewBTCHeaderInfo(&headerBytes, headerHash, headerHeight, &headerWork) + defaultBaseHeader := SimnetGenesisBlock() return &GenesisState{ - BaseBtcHeader: *baseHeaderInfo, + BaseBtcHeader: defaultBaseHeader, } } // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { - err := gs.BaseBtcHeader.Validate() - if err != nil { - return err + // We Require that genesis block is difficulty adjustment block, so that we can + // properly calculate the difficulty adjustments in the future. + // TODO: Even though number of block per re-target depends on the network, in reality it + // is always 2016. Maybe we should consider moving it to param, or try to pass + // it through + isRetarget := IsRetargetBlock(&gs.BaseBtcHeader, &chaincfg.MainNetParams) + + if !isRetarget { + return fmt.Errorf("genesis block must be a difficulty adjustment block") } + return nil } diff --git a/x/btclightclient/types/keys.go b/x/btclightclient/types/keys.go index 4203eaf9b..363b2719f 100644 --- a/x/btclightclient/types/keys.go +++ b/x/btclightclient/types/keys.go @@ -24,35 +24,14 @@ const ( var ( HeadersPrefix = []byte{0x0} // reserve this namespace for headers - HeadersObjectPrefix = append(HeadersPrefix, 0x0) // where we save the concrete header bytes - HashToHeightPrefix = append(HeadersPrefix, 0x1) // where we map hash to height - HashToWorkPrefix = append(HeadersPrefix, 0x2) // where we map hash to height - TipPrefix = append(HeadersPrefix, 0x3) // where we store the tip + HeadersObjectPrefix = append(HeadersPrefix, 0x0) // reserve this namespace mapping: Height -> BTCHeaderInfo + HashToHeightPrefix = append(HeadersPrefix, 0x1) // reserve this namespace mapping: Hash -> Height ) -func HeadersObjectKey(height uint64, hash *bbn.BTCHeaderHashBytes) []byte { - he := sdk.Uint64ToBigEndian(height) - hashBytes := hash.MustMarshal() - - var prefix []byte - prefix = append(prefix, he...) - return append(prefix, hashBytes...) +func HeadersObjectKey(height uint64) []byte { + return sdk.Uint64ToBigEndian(height) } func HeadersObjectHeightKey(hash *bbn.BTCHeaderHashBytes) []byte { - var prefix []byte - return append(prefix, hash.MustMarshal()...) -} - -func HeadersObjectWorkKey(hash *bbn.BTCHeaderHashBytes) []byte { - var prefix []byte - return append(prefix, hash.MustMarshal()...) -} - -func TipKey() []byte { - return TipPrefix -} - -func KeyPrefix(p string) []byte { - return []byte(p) + return hash.MustMarshal() } diff --git a/x/btclightclient/types/keys_test.go b/x/btclightclient/types/keys_test.go index 7c1530abf..e075a6c4e 100644 --- a/x/btclightclient/types/keys_test.go +++ b/x/btclightclient/types/keys_test.go @@ -16,19 +16,14 @@ func FuzzHeadersObjectKey(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - hexHash := datagen.GenRandomHexStr(r, bbn.BTCHeaderHashLen) height := r.Uint64() // get chainhash and height heightBytes := sdk.Uint64ToBigEndian(height) - headerHash, _ := bbn.NewBTCHeaderHashBytesFromHex(hexHash) - // construct the expected key - headerHashBytes := headerHash.MustMarshal() var expectedKey []byte expectedKey = append(expectedKey, heightBytes...) - expectedKey = append(expectedKey, headerHashBytes...) - gotKey := types.HeadersObjectKey(height, &headerHash) + gotKey := types.HeadersObjectKey(height) if !bytes.Equal(expectedKey, gotKey) { t.Errorf("Expected headers object key %s got %s", expectedKey, gotKey) } @@ -50,18 +45,5 @@ func FuzzHeadersObjectHeightAndWorkKey(f *testing.F) { if !bytes.Equal(expectedHeightKey, gotHeightKey) { t.Errorf("Expected headers object height key %s got %s", expectedHeightKey, gotHeightKey) } - - var expectedWorkKey []byte - expectedWorkKey = append(expectedWorkKey, headerHashBytes...) - gotWorkKey := types.HeadersObjectWorkKey(&headerHash) - if !bytes.Equal(expectedWorkKey, gotWorkKey) { - t.Errorf("Expected headers object work key %s got %s", expectedWorkKey, gotWorkKey) - } }) } - -func TestTipKey(t *testing.T) { - if !bytes.Equal(types.TipKey(), types.TipPrefix) { - t.Errorf("Expected tip key %s got %s", types.TipKey(), types.TipPrefix) - } -} diff --git a/x/btclightclient/types/msgs.go b/x/btclightclient/types/msgs.go index 6c33c1c56..95d44df18 100644 --- a/x/btclightclient/types/msgs.go +++ b/x/btclightclient/types/msgs.go @@ -1,24 +1,45 @@ package types import ( + "encoding/hex" + "fmt" "math/big" bbn "github.com/babylonchain/babylon/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -// Ensure that MsgInsertHeader implements all functions of the Msg interface -var _ sdk.Msg = (*MsgInsertHeader)(nil) +var _ sdk.Msg = (*MsgInsertHeaders)(nil) + +func NewMsgInsertHeaders(signer sdk.AccAddress, headersHex string) (*MsgInsertHeaders, error) { + if len(headersHex) == 0 { + return nil, fmt.Errorf("empty headers list") + } + + decoded, err := hex.DecodeString(headersHex) -func NewMsgInsertHeader(signer sdk.AccAddress, headerHex string) (*MsgInsertHeader, error) { - headerBytes, err := bbn.NewBTCHeaderBytesFromHex(headerHex) if err != nil { return nil, err } - return &MsgInsertHeader{Signer: signer.String(), Header: &headerBytes}, nil + + if len(decoded)%bbn.BTCHeaderLen != 0 { + return nil, fmt.Errorf("invalid length of encoded headers: %d", len(decoded)) + } + numOfHeaders := len(decoded) / bbn.BTCHeaderLen + headers := make([]bbn.BTCHeaderBytes, numOfHeaders) + + for i := 0; i < numOfHeaders; i++ { + headerSlice := decoded[i*bbn.BTCHeaderLen : (i+1)*bbn.BTCHeaderLen] + headerBytes, err := bbn.NewBTCHeaderBytesFromBytes(headerSlice) + if err != nil { + return nil, err + } + headers[i] = headerBytes + } + return &MsgInsertHeaders{Signer: signer.String(), Headers: headers}, nil } -func (msg *MsgInsertHeader) ValidateBasic() error { +func (msg *MsgInsertHeaders) ValidateBasic() error { // This function validates stateless message elements // msg.Header is validated in ante-handler _, err := sdk.AccAddressFromBech32(msg.Signer) @@ -28,11 +49,7 @@ func (msg *MsgInsertHeader) ValidateBasic() error { return nil } -func (msg *MsgInsertHeader) ValidateHeader(powLimit *big.Int) error { - return bbn.ValidateBTCHeader(msg.Header.ToBlockHeader(), powLimit) -} - -func (msg *MsgInsertHeader) GetSigners() []sdk.AccAddress { +func (msg *MsgInsertHeaders) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { // Panic, since the GetSigners method is called after ValidateBasic @@ -42,3 +59,15 @@ func (msg *MsgInsertHeader) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } + +func (msg *MsgInsertHeaders) ValidateHeaders(powLimit *big.Int) error { + // TOOD: Limit number of headers in message? + for _, header := range msg.Headers { + err := bbn.ValidateBTCHeader(header.ToBlockHeader(), powLimit) + if err != nil { + return err + } + } + + return nil +} diff --git a/x/btclightclient/types/msgs_test.go b/x/btclightclient/types/msgs_test.go index 27641d364..9e05a4b75 100644 --- a/x/btclightclient/types/msgs_test.go +++ b/x/btclightclient/types/msgs_test.go @@ -11,6 +11,7 @@ import ( bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) func FuzzMsgInsertHeader(f *testing.F) { @@ -58,23 +59,41 @@ func FuzzMsgInsertHeader(f *testing.F) { t.Skip() } + numHeaders := r.Intn(20) + 1 + headersHex := "" + for i := 0; i < numHeaders; i++ { + headersHex += newHeaderHex + } + + // empty string + _, err := types.NewMsgInsertHeaders(signer, "") + require.NotNil(t, err) + + // hex string with invalid length + invalidLength := uint64(r.Int31n(79) + 1) + _, err = types.NewMsgInsertHeaders(signer, headersHex+datagen.GenRandomHexStr(r, invalidLength)) + require.NotNil(t, err) + // Check the message creation - msgInsertHeader, err := types.NewMsgInsertHeader(signer, newHeaderHex) + msgInsertHeader, err := types.NewMsgInsertHeaders(signer, headersHex) if err != nil { t.Errorf("Valid parameters led to error") } if msgInsertHeader == nil { t.Fatalf("nil returned") } - if msgInsertHeader.Header == nil { - t.Errorf("nil header") + if msgInsertHeader.Headers == nil || len(msgInsertHeader.Headers) != numHeaders { + t.Errorf("invalid number of headers") } - if !bytes.Equal(newHeader.MustMarshal(), msgInsertHeader.Header.MustMarshal()) { - t.Errorf("Expected header bytes %s got %s", newHeader.MustMarshal(), msgInsertHeader.Header.MustMarshal()) + + for _, header := range msgInsertHeader.Headers { + if !bytes.Equal(newHeader.MustMarshal(), header.MustMarshal()) { + t.Errorf("Expected header bytes %s got %s", newHeader.MustMarshal(), header.MustMarshal()) + } } // Validate the message - err = msgInsertHeader.ValidateHeader(&maxDifficulty) + err = msgInsertHeader.ValidateHeaders(&maxDifficulty) if err != nil && errorKind == 0 { t.Errorf("Valid message %s failed with %s", headerHex, err) } diff --git a/x/btclightclient/types/querier_test.go b/x/btclightclient/types/querier_test.go index e659a1041..5a23c0c12 100644 --- a/x/btclightclient/types/querier_test.go +++ b/x/btclightclient/types/querier_test.go @@ -12,10 +12,10 @@ import ( ) func TestNewQueryHashesRequest(t *testing.T) { - headerBytes := bbn.GetBaseBTCHeaderBytes() - headerHashBytes := headerBytes.Hash() + baseHeader := types.SimnetGenesisBlock() + req := query.PageRequest{ - Key: headerHashBytes.MustMarshal(), + Key: baseHeader.Hash.MustMarshal(), } newQueryHashes := types.NewQueryHashesRequest(&req) if newQueryHashes == nil { @@ -55,9 +55,10 @@ func FuzzNewQueryContainsRequest(f *testing.F) { } func TestNewQueryMainChainRequest(t *testing.T) { - headerBytes := bbn.GetBaseBTCHeaderBytes() + baseHeader := types.SimnetGenesisBlock() + req := query.PageRequest{ - Key: headerBytes.MustMarshal(), + Key: baseHeader.Header.MustMarshal(), } newQueryMainChain := types.NewQueryMainChainRequest(&req) if newQueryMainChain == nil { diff --git a/x/btclightclient/types/tx.pb.go b/x/btclightclient/types/tx.pb.go index 4790b7e81..cbb091e00 100644 --- a/x/btclightclient/types/tx.pb.go +++ b/x/btclightclient/types/tx.pb.go @@ -29,24 +29,24 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// MsgInsertHeader defines the message for incoming header bytes -type MsgInsertHeader struct { - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - Header *github_com_babylonchain_babylon_types.BTCHeaderBytes `protobuf:"bytes,2,opt,name=header,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderBytes" json:"header,omitempty"` +// MsgInsertHeaders defines the message for multiple incoming header bytes +type MsgInsertHeaders struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + Headers []github_com_babylonchain_babylon_types.BTCHeaderBytes `protobuf:"bytes,2,rep,name=headers,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderBytes" json:"headers,omitempty"` } -func (m *MsgInsertHeader) Reset() { *m = MsgInsertHeader{} } -func (m *MsgInsertHeader) String() string { return proto.CompactTextString(m) } -func (*MsgInsertHeader) ProtoMessage() {} -func (*MsgInsertHeader) Descriptor() ([]byte, []int) { +func (m *MsgInsertHeaders) Reset() { *m = MsgInsertHeaders{} } +func (m *MsgInsertHeaders) String() string { return proto.CompactTextString(m) } +func (*MsgInsertHeaders) ProtoMessage() {} +func (*MsgInsertHeaders) Descriptor() ([]byte, []int) { return fileDescriptor_5f638eee60234021, []int{0} } -func (m *MsgInsertHeader) XXX_Unmarshal(b []byte) error { +func (m *MsgInsertHeaders) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgInsertHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgInsertHeaders) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgInsertHeader.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgInsertHeaders.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -56,41 +56,41 @@ func (m *MsgInsertHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, err return b[:n], nil } } -func (m *MsgInsertHeader) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgInsertHeader.Merge(m, src) +func (m *MsgInsertHeaders) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgInsertHeaders.Merge(m, src) } -func (m *MsgInsertHeader) XXX_Size() int { +func (m *MsgInsertHeaders) XXX_Size() int { return m.Size() } -func (m *MsgInsertHeader) XXX_DiscardUnknown() { - xxx_messageInfo_MsgInsertHeader.DiscardUnknown(m) +func (m *MsgInsertHeaders) XXX_DiscardUnknown() { + xxx_messageInfo_MsgInsertHeaders.DiscardUnknown(m) } -var xxx_messageInfo_MsgInsertHeader proto.InternalMessageInfo +var xxx_messageInfo_MsgInsertHeaders proto.InternalMessageInfo -func (m *MsgInsertHeader) GetSigner() string { +func (m *MsgInsertHeaders) GetSigner() string { if m != nil { return m.Signer } return "" } -// MsgInsertHeaderResponse defines the response for the InsertHeader transaction -type MsgInsertHeaderResponse struct { +// MsgInsertHeadersResponse defines the response for the InsertHeaders transaction +type MsgInsertHeadersResponse struct { } -func (m *MsgInsertHeaderResponse) Reset() { *m = MsgInsertHeaderResponse{} } -func (m *MsgInsertHeaderResponse) String() string { return proto.CompactTextString(m) } -func (*MsgInsertHeaderResponse) ProtoMessage() {} -func (*MsgInsertHeaderResponse) Descriptor() ([]byte, []int) { +func (m *MsgInsertHeadersResponse) Reset() { *m = MsgInsertHeadersResponse{} } +func (m *MsgInsertHeadersResponse) String() string { return proto.CompactTextString(m) } +func (*MsgInsertHeadersResponse) ProtoMessage() {} +func (*MsgInsertHeadersResponse) Descriptor() ([]byte, []int) { return fileDescriptor_5f638eee60234021, []int{1} } -func (m *MsgInsertHeaderResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgInsertHeadersResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgInsertHeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgInsertHeadersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgInsertHeaderResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgInsertHeadersResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -100,21 +100,21 @@ func (m *MsgInsertHeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]b return b[:n], nil } } -func (m *MsgInsertHeaderResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgInsertHeaderResponse.Merge(m, src) +func (m *MsgInsertHeadersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgInsertHeadersResponse.Merge(m, src) } -func (m *MsgInsertHeaderResponse) XXX_Size() int { +func (m *MsgInsertHeadersResponse) XXX_Size() int { return m.Size() } -func (m *MsgInsertHeaderResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgInsertHeaderResponse.DiscardUnknown(m) +func (m *MsgInsertHeadersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgInsertHeadersResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgInsertHeaderResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgInsertHeadersResponse proto.InternalMessageInfo func init() { - proto.RegisterType((*MsgInsertHeader)(nil), "babylon.btclightclient.v1.MsgInsertHeader") - proto.RegisterType((*MsgInsertHeaderResponse)(nil), "babylon.btclightclient.v1.MsgInsertHeaderResponse") + proto.RegisterType((*MsgInsertHeaders)(nil), "babylon.btclightclient.v1.MsgInsertHeaders") + proto.RegisterType((*MsgInsertHeadersResponse)(nil), "babylon.btclightclient.v1.MsgInsertHeadersResponse") } func init() { @@ -122,24 +122,24 @@ func init() { } var fileDescriptor_5f638eee60234021 = []byte{ - // 265 bytes of a gzipped FileDescriptorProto + // 266 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0x65, 0x86, 0xfa, 0x25, 0x15, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0x35, 0x7a, 0xa8, 0x6a, 0xf4, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xaa, 0xf4, 0x41, - 0x2c, 0x88, 0x06, 0xa5, 0x6a, 0x2e, 0x7e, 0xdf, 0xe2, 0x74, 0xcf, 0xbc, 0xe2, 0xd4, 0xa2, 0x12, - 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0x22, 0x21, 0x31, 0x2e, 0xb6, 0xe2, 0xcc, 0xf4, 0xbc, 0xd4, 0x22, - 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x28, 0x4f, 0x28, 0x80, 0x8b, 0x2d, 0x03, 0xac, 0x42, - 0x82, 0x49, 0x81, 0x51, 0x83, 0xc7, 0xc9, 0xe2, 0xd6, 0x3d, 0x79, 0x93, 0xf4, 0xcc, 0x92, 0x8c, - 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0xd5, 0xc9, 0x19, 0x89, 0x99, 0x79, 0x30, 0x8e, - 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x53, 0x88, 0x33, 0xc4, 0x70, 0xa7, 0xca, 0x92, 0xd4, - 0xe2, 0x20, 0xa8, 0x39, 0x4a, 0x92, 0x5c, 0xe2, 0x68, 0x96, 0x07, 0xa5, 0x16, 0x17, 0xe4, 0xe7, - 0x15, 0xa7, 0x1a, 0x95, 0x73, 0x31, 0xfb, 0x16, 0xa7, 0x0b, 0x15, 0x70, 0xf1, 0xa0, 0xb8, 0x4d, - 0x4b, 0x0f, 0xa7, 0x07, 0xf5, 0xd0, 0x8c, 0x92, 0x32, 0x22, 0x5e, 0x2d, 0xcc, 0x5a, 0x25, 0x06, - 0xa7, 0x80, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, - 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x23, 0xe4, 0xd7, - 0x0a, 0xf4, 0x98, 0x01, 0x7b, 0x3e, 0x89, 0x0d, 0x1c, 0xd2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x26, 0x30, 0x43, 0x4d, 0xc0, 0x01, 0x00, 0x00, + 0x2c, 0x88, 0x06, 0xa5, 0x3a, 0x2e, 0x01, 0xdf, 0xe2, 0x74, 0xcf, 0xbc, 0xe2, 0xd4, 0xa2, 0x12, + 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0xa2, 0x62, 0x21, 0x31, 0x2e, 0xb6, 0xe2, 0xcc, 0xf4, 0xbc, 0xd4, + 0x22, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x28, 0x4f, 0x28, 0x88, 0x8b, 0x3d, 0x03, 0xa2, + 0x44, 0x82, 0x49, 0x81, 0x59, 0x83, 0xc7, 0xc9, 0xe2, 0xd6, 0x3d, 0x79, 0x93, 0xf4, 0xcc, 0x92, + 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0xe5, 0xc9, 0x19, 0x89, 0x99, 0x79, 0x30, + 0x8e, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x53, 0x88, 0x33, 0xc4, 0x78, 0xa7, 0xca, 0x92, + 0xd4, 0xe2, 0x20, 0x98, 0x41, 0x4a, 0x52, 0x5c, 0x12, 0xe8, 0xf6, 0x07, 0xa5, 0x16, 0x17, 0xe4, + 0xe7, 0x15, 0xa7, 0x1a, 0x55, 0x71, 0x31, 0xfb, 0x16, 0xa7, 0x0b, 0x15, 0x73, 0xf1, 0xa2, 0xba, + 0x4f, 0x5b, 0x0f, 0xa7, 0x2f, 0xf5, 0xd0, 0x0d, 0x93, 0x32, 0x26, 0x41, 0x31, 0xcc, 0x66, 0x25, + 0x06, 0xa7, 0x80, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, + 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x23, 0xe4, + 0xe1, 0x0a, 0xf4, 0x08, 0x02, 0x87, 0x40, 0x12, 0x1b, 0x38, 0xc0, 0x8d, 0x01, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x75, 0x7e, 0xfc, 0x67, 0xc7, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -154,9 +154,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // InsertHeader adds a header to the BTC light client chain maintained by - // Babylon. - InsertHeader(ctx context.Context, in *MsgInsertHeader, opts ...grpc.CallOption) (*MsgInsertHeaderResponse, error) + // InsertHeaders adds a batch of headers to the BTC light client chain + InsertHeaders(ctx context.Context, in *MsgInsertHeaders, opts ...grpc.CallOption) (*MsgInsertHeadersResponse, error) } type msgClient struct { @@ -167,9 +166,9 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } -func (c *msgClient) InsertHeader(ctx context.Context, in *MsgInsertHeader, opts ...grpc.CallOption) (*MsgInsertHeaderResponse, error) { - out := new(MsgInsertHeaderResponse) - err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Msg/InsertHeader", in, out, opts...) +func (c *msgClient) InsertHeaders(ctx context.Context, in *MsgInsertHeaders, opts ...grpc.CallOption) (*MsgInsertHeadersResponse, error) { + out := new(MsgInsertHeadersResponse) + err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Msg/InsertHeaders", in, out, opts...) if err != nil { return nil, err } @@ -178,37 +177,36 @@ func (c *msgClient) InsertHeader(ctx context.Context, in *MsgInsertHeader, opts // MsgServer is the server API for Msg service. type MsgServer interface { - // InsertHeader adds a header to the BTC light client chain maintained by - // Babylon. - InsertHeader(context.Context, *MsgInsertHeader) (*MsgInsertHeaderResponse, error) + // InsertHeaders adds a batch of headers to the BTC light client chain + InsertHeaders(context.Context, *MsgInsertHeaders) (*MsgInsertHeadersResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. type UnimplementedMsgServer struct { } -func (*UnimplementedMsgServer) InsertHeader(ctx context.Context, req *MsgInsertHeader) (*MsgInsertHeaderResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method InsertHeader not implemented") +func (*UnimplementedMsgServer) InsertHeaders(ctx context.Context, req *MsgInsertHeaders) (*MsgInsertHeadersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method InsertHeaders not implemented") } func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } -func _Msg_InsertHeader_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgInsertHeader) +func _Msg_InsertHeaders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgInsertHeaders) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).InsertHeader(ctx, in) + return srv.(MsgServer).InsertHeaders(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btclightclient.v1.Msg/InsertHeader", + FullMethod: "/babylon.btclightclient.v1.Msg/InsertHeaders", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).InsertHeader(ctx, req.(*MsgInsertHeader)) + return srv.(MsgServer).InsertHeaders(ctx, req.(*MsgInsertHeaders)) } return interceptor(ctx, in, info, handler) } @@ -218,15 +216,15 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ HandlerType: (*MsgServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "InsertHeader", - Handler: _Msg_InsertHeader_Handler, + MethodName: "InsertHeaders", + Handler: _Msg_InsertHeaders_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btclightclient/v1/tx.proto", } -func (m *MsgInsertHeader) Marshal() (dAtA []byte, err error) { +func (m *MsgInsertHeaders) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -236,27 +234,29 @@ func (m *MsgInsertHeader) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgInsertHeader) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgInsertHeaders) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgInsertHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgInsertHeaders) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Header != nil { - { - size := m.Header.Size() - i -= size - if _, err := m.Header.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.Headers) > 0 { + for iNdEx := len(m.Headers) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.Headers[iNdEx].Size() + i -= size + if _, err := m.Headers[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) } - i = encodeVarintTx(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 } if len(m.Signer) > 0 { i -= len(m.Signer) @@ -268,7 +268,7 @@ func (m *MsgInsertHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgInsertHeaderResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgInsertHeadersResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -278,12 +278,12 @@ func (m *MsgInsertHeaderResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgInsertHeaderResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgInsertHeadersResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgInsertHeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgInsertHeadersResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -302,7 +302,7 @@ func encodeVarintTx(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MsgInsertHeader) Size() (n int) { +func (m *MsgInsertHeaders) Size() (n int) { if m == nil { return 0 } @@ -312,14 +312,16 @@ func (m *MsgInsertHeader) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.Header != nil { - l = m.Header.Size() - n += 1 + l + sovTx(uint64(l)) + if len(m.Headers) > 0 { + for _, e := range m.Headers { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } } return n } -func (m *MsgInsertHeaderResponse) Size() (n int) { +func (m *MsgInsertHeadersResponse) Size() (n int) { if m == nil { return 0 } @@ -334,7 +336,7 @@ func sovTx(x uint64) (n int) { func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MsgInsertHeader) Unmarshal(dAtA []byte) error { +func (m *MsgInsertHeaders) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -357,10 +359,10 @@ func (m *MsgInsertHeader) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgInsertHeader: wiretype end group for non-group") + return fmt.Errorf("proto: MsgInsertHeaders: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgInsertHeader: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgInsertHeaders: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -397,7 +399,7 @@ func (m *MsgInsertHeader) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Headers", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -425,8 +427,8 @@ func (m *MsgInsertHeader) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BTCHeaderBytes - m.Header = &v - if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Headers = append(m.Headers, v) + if err := m.Headers[len(m.Headers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -451,7 +453,7 @@ func (m *MsgInsertHeader) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgInsertHeaderResponse) Unmarshal(dAtA []byte) error { +func (m *MsgInsertHeadersResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -474,10 +476,10 @@ func (m *MsgInsertHeaderResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgInsertHeaderResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgInsertHeadersResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgInsertHeaderResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgInsertHeadersResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: diff --git a/x/btclightclient/types/utils.go b/x/btclightclient/types/utils.go new file mode 100644 index 000000000..e5181ea4a --- /dev/null +++ b/x/btclightclient/types/utils.go @@ -0,0 +1,18 @@ +package types + +import ( + "time" + + "github.com/btcsuite/btcd/chaincfg" +) + +func BlocksPerRetarget(params *chaincfg.Params) int32 { + targetTimespan := int64(params.TargetTimespan / time.Second) + targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second) + return int32(targetTimespan / targetTimePerBlock) +} + +func IsRetargetBlock(info *BTCHeaderInfo, params *chaincfg.Params) bool { + hI32 := int32(info.Height) + return hI32%BlocksPerRetarget(params) == 0 +} diff --git a/x/btclightclient/types/work.go b/x/btclightclient/types/work.go index 45a114cbc..b5ef4148a 100644 --- a/x/btclightclient/types/work.go +++ b/x/btclightclient/types/work.go @@ -4,10 +4,15 @@ import ( sdkmath "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/wire" ) func CalcWork(header *bbn.BTCHeaderBytes) sdkmath.Uint { - return sdkmath.NewUintFromBigInt(blockchain.CalcWork(header.Bits())) + return CalcHeaderWork(header.ToBlockHeader()) +} + +func CalcHeaderWork(header *wire.BlockHeader) sdkmath.Uint { + return sdkmath.NewUintFromBigInt(blockchain.CalcWork(header.Bits)) } func CumulativeWork(childWork sdkmath.Uint, parentWork sdkmath.Uint) sdkmath.Uint { diff --git a/x/monitor/keeper/grpc_query_test.go b/x/monitor/keeper/grpc_query_test.go index c4775ddf7..61c8fbdbe 100644 --- a/x/monitor/keeper/grpc_query_test.go +++ b/x/monitor/keeper/grpc_query_test.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/testutil/mocks" - btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching/testepoching" types2 "github.com/babylonchain/babylon/x/epoching/types" @@ -35,15 +34,17 @@ func FuzzQueryEndedEpochBtcHeight(f *testing.F) { epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) - // Insert header tree - tree := datagen.NewBTCHeaderTree() root := lck.GetBaseBTCHeader(ctx) - tree.Add(root, nil) - tree.GenRandomBTCHeaderTree(r, 1, 10, root, func(header *btclightclienttypes.BTCHeaderInfo) bool { - err := lck.InsertHeader(ctx, header.Header) - require.NoError(t, err) - return true - }) + chain := datagen.GenRandomValidChainStartingFrom( + r, + 0, + root.Header.ToBlockHeader(), + nil, + 10, + ) + headerBytes := datagen.HeaderToHeaderBytes(chain) + err := lck.InsertHeaders(ctx, headerBytes) + require.NoError(t, err) // EndBlock of block 1 ctx = helper.EndBlock() @@ -96,15 +97,17 @@ func FuzzQueryReportedCheckpointBtcHeight(f *testing.F) { epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) - // Insert header tree - tree := datagen.NewBTCHeaderTree() root := lck.GetBaseBTCHeader(ctx) - tree.Add(root, nil) - tree.GenRandomBTCHeaderTree(r, 1, 10, root, func(header *btclightclienttypes.BTCHeaderInfo) bool { - err := lck.InsertHeader(ctx, header.Header) - require.NoError(t, err) - return true - }) + chain := datagen.GenRandomValidChainStartingFrom( + r, + 0, + root.Header.ToBlockHeader(), + nil, + 10, + ) + headerBytes := datagen.HeaderToHeaderBytes(chain) + err := lck.InsertHeaders(ctx, headerBytes) + require.NoError(t, err) // Add checkpoint valBlsSet, privKeys := datagen.GenerateValidatorSetWithBLSPrivKeys(int(datagen.RandomIntOtherThan(r, 0, 10))) @@ -121,7 +124,7 @@ func FuzzQueryReportedCheckpointBtcHeight(f *testing.F) { mockEk.EXPECT().GetValidatorSet(gomock.Any(), gomock.Eq(mockCkptWithMeta.Ckpt.EpochNum)).Return(valSet).AnyTimes() // make sure voting power is always sufficient mockEk.EXPECT().GetTotalVotingPower(gomock.Any(), gomock.Eq(mockCkptWithMeta.Ckpt.EpochNum)).Return(int64(0)).AnyTimes() - err := ck.AddRawCheckpoint( + err = ck.AddRawCheckpoint( ctx, mockCkptWithMeta, ) diff --git a/x/zoneconcierge/keeper/epochs.go b/x/zoneconcierge/keeper/epochs.go index ac0511a19..679ecfa36 100644 --- a/x/zoneconcierge/keeper/epochs.go +++ b/x/zoneconcierge/keeper/epochs.go @@ -1,30 +1,29 @@ package keeper import ( - btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" "github.com/babylonchain/babylon/x/zoneconcierge/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetFinalizingBTCTip gets the BTC tip when the last epoch is finalised -func (k Keeper) GetFinalizingBTCTip(ctx sdk.Context) *btclctypes.BTCHeaderInfo { +// GetLastSentSegment get last broadcasted btc light client segment +func (k Keeper) GetLastSentSegment(ctx sdk.Context) *types.BTCChainSegment { store := ctx.KVStore(k.storeKey) if !store.Has(types.FinalizingBTCTipKey) { return nil } - btcTipBytes := store.Get(types.FinalizingBTCTipKey) - var btcTip btclctypes.BTCHeaderInfo - k.cdc.MustUnmarshal(btcTipBytes, &btcTip) - return &btcTip + segmentBytes := store.Get(types.FinalizingBTCTipKey) + var segment types.BTCChainSegment + k.cdc.MustUnmarshal(segmentBytes, &segment) + return &segment } -// setFinalizingBTCTip sets the last finalised BTC tip +// setLastSentSegment sets the last segment which was broadcasted to the other light clients // called upon each AfterRawCheckpointFinalized hook invocation -func (k Keeper) setFinalizingBTCTip(ctx sdk.Context, btcTip *btclctypes.BTCHeaderInfo) { +func (k Keeper) setLastSentSegment(ctx sdk.Context, segment *types.BTCChainSegment) { store := ctx.KVStore(k.storeKey) - btcTipBytes := k.cdc.MustMarshal(btcTip) - store.Set(types.FinalizingBTCTipKey, btcTipBytes) + segmentBytes := k.cdc.MustMarshal(segment) + store.Set(types.FinalizingBTCTipKey, segmentBytes) } // GetFinalizedEpoch gets the last finalised epoch diff --git a/x/zoneconcierge/keeper/grpc_query_test.go b/x/zoneconcierge/keeper/grpc_query_test.go index a02807007..41b4c6004 100644 --- a/x/zoneconcierge/keeper/grpc_query_test.go +++ b/x/zoneconcierge/keeper/grpc_query_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" tmtypes "github.com/cometbft/cometbft/types" @@ -398,7 +399,7 @@ func FuzzFinalizedChainInfo(f *testing.F) { // mock btclc keeper btclcKeeper := zctypes.NewMockBTCLightClientKeeper(ctrl) mockBTCHeaderInfo := datagen.GenRandomBTCHeaderInfo(r) - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(mockBTCHeaderInfo).AnyTimes() + btclcKeeper.EXPECT().GetMainChainFrom(gomock.Any(), gomock.Any()).Return([]*btclightclienttypes.BTCHeaderInfo{mockBTCHeaderInfo}).AnyTimes() // mock Tendermint client // TODO: integration tests with Tendermint diff --git a/x/zoneconcierge/keeper/hooks.go b/x/zoneconcierge/keeper/hooks.go index 82d573955..1c96005e1 100644 --- a/x/zoneconcierge/keeper/hooks.go +++ b/x/zoneconcierge/keeper/hooks.go @@ -94,13 +94,14 @@ func (h Hooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error // upon an epoch has been finalised, update the last finalised epoch h.k.setFinalizedEpoch(ctx, epoch) + headersToBroadcast := h.k.getHeadersToBroadcast(ctx) // send BTC timestamp to all open channels with ZoneConcierge - h.k.BroadcastBTCTimestamps(ctx, epoch) - - // retrieve and update the last finalising BTC tip - btcTip := h.k.btclcKeeper.GetTipInfo(ctx) - h.k.setFinalizingBTCTip(ctx, btcTip) + h.k.BroadcastBTCTimestamps(ctx, epoch, headersToBroadcast) + // Update the last broadcasted segment + h.k.setLastSentSegment(ctx, &types.BTCChainSegment{ + BtcHeaders: headersToBroadcast, + }) return nil } diff --git a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go index 1b44e9229..6844a51ee 100644 --- a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go +++ b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go @@ -41,25 +41,11 @@ func (k Keeper) getChainID(ctx sdk.Context, channel channeltypes.IdentifiedChann return tmClient.ChainId, nil } -// getBTCHeadersDuringLastFinalizedEpoch gets BTC headers between -// - the block AFTER the common ancestor of BTC tip at epoch `lastFinalizedEpoch-1` and BTC tip at epoch `lastFinalizedEpoch` -// - BTC tip at epoch `lastFinalizedEpoch` -// where `lastFinalizedEpoch` is the last finalised epoch -func (k Keeper) getBTCHeadersDuringLastFinalizedEpoch(ctx sdk.Context) []*btclctypes.BTCHeaderInfo { - oldBTCTip := k.GetFinalizingBTCTip(ctx) // NOTE: BTC tip in KVStore has not been updated yet - if oldBTCTip == nil { - // this happens upon the first finalised epoch. Use base header instead - oldBTCTip = k.btclcKeeper.GetBaseBTCHeader(ctx) - } - curBTCTip := k.btclcKeeper.GetTipInfo(ctx) - commonAncestor := k.btclcKeeper.GetHighestCommonAncestor(ctx, oldBTCTip, curBTCTip) - btcHeaders := k.btclcKeeper.GetInOrderAncestorsUntil(ctx, curBTCTip, commonAncestor) - - return btcHeaders -} - // getFinalizedInfo returns metadata and proofs that are identical to all BTC timestamps in the same epoch -func (k Keeper) getFinalizedInfo(ctx sdk.Context, epochNum uint64) (*finalizedInfo, error) { +func (k Keeper) getFinalizedInfo( + ctx sdk.Context, + epochNum uint64, + headersToBroadcast []*btclctypes.BTCHeaderInfo) (*finalizedInfo, error) { finalizedEpochInfo, err := k.epochingKeeper.GetHistoricalEpoch(ctx, epochNum) if err != nil { return nil, err @@ -92,9 +78,6 @@ func (k Keeper) getFinalizedInfo(ctx sdk.Context, epochNum uint64) (*finalizedIn return nil, err } - // get new BTC headers since the 2nd last finalised epoch and the last finalised epoch - btcHeaders := k.getBTCHeadersDuringLastFinalizedEpoch(ctx) - // construct finalizedInfo finalizedInfo := &finalizedInfo{ EpochInfo: finalizedEpochInfo, @@ -102,7 +85,7 @@ func (k Keeper) getFinalizedInfo(ctx sdk.Context, epochNum uint64) (*finalizedIn BTCSubmissionKey: btcSubmissionKey, ProofEpochSealed: proofEpochSealed, ProofEpochSubmitted: proofEpochSubmitted, - BTCHeaders: btcHeaders, + BTCHeaders: headersToBroadcast, } return finalizedInfo, nil @@ -175,8 +158,42 @@ func (k Keeper) createBTCTimestamp(ctx sdk.Context, chainID string, channel chan return btcTimestamp, nil } +// getHeadersToBroadcast retrieves headers to be broadcasted to all open IBC channels to ZoneConcierge +// The header to be broadcasted are: +// - either the whole known chain if we did not broadcast any headers yet +// - headers from the child of the most recent header we sent which is still in the main chain up to the current tip +func (k Keeper) getHeadersToBroadcast(ctx sdk.Context) []*btclctypes.BTCHeaderInfo { + lastSegment := k.GetLastSentSegment(ctx) + + if lastSegment == nil { + // we did not send any headers yet, so we need to sent whole known chain + return k.btclcKeeper.GetMainChainFrom(ctx, 0) + } + + // we already sent some headers, so we need to send headers from the child of the most recent header we sent + // which is still in the main chain. + // In most cases it will be header just after the tip, but in case of the forks it may as well be some older header + // of the segment + var initHeader *btclctypes.BTCHeaderInfo + for i := len(lastSegment.BtcHeaders) - 1; i >= 0; i-- { + header := lastSegment.BtcHeaders[i] + if k.btclcKeeper.GetHeaderByHash(ctx, header.Hash) != nil { + initHeader = header + break + } + } + + headersToSend := k.btclcKeeper.GetMainChainFrom(ctx, initHeader.Height+1) + + return headersToSend +} + // BroadcastBTCTimestamps sends an IBC packet of BTC timestamp to all open IBC channels to ZoneConcierge -func (k Keeper) BroadcastBTCTimestamps(ctx sdk.Context, epochNum uint64) { +func (k Keeper) BroadcastBTCTimestamps( + ctx sdk.Context, + epochNum uint64, + headersToBroadcast []*btclctypes.BTCHeaderInfo, +) { // Babylon does not broadcast BTC timestamps until finalising epoch 1 if epochNum < 1 { k.Logger(ctx).Info("Babylon does not finalize epoch 1 yet, skip broadcasting BTC timestamps") @@ -193,7 +210,7 @@ func (k Keeper) BroadcastBTCTimestamps(ctx sdk.Context, epochNum uint64) { k.Logger(ctx).Info("there exists open IBC channels with ZoneConcierge, generating BTC timestamps", "number of channels", len(openZCChannels)) // get all metadata shared across BTC timestamps in the same epoch - finalizedInfo, err := k.getFinalizedInfo(ctx, epochNum) + finalizedInfo, err := k.getFinalizedInfo(ctx, epochNum, headersToBroadcast) if err != nil { k.Logger(ctx).Error("failed to generate metadata shared across BTC timestamps in the same epoch, skip broadcasting BTC timestamps", "error", err) return diff --git a/x/zoneconcierge/types/expected_keepers.go b/x/zoneconcierge/types/expected_keepers.go index 69d22c16b..dd07af30e 100644 --- a/x/zoneconcierge/types/expected_keepers.go +++ b/x/zoneconcierge/types/expected_keepers.go @@ -3,6 +3,7 @@ package types import ( context "context" + bbn "github.com/babylonchain/babylon/types" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" @@ -81,10 +82,9 @@ type ScopedKeeper interface { type BTCLightClientKeeper interface { GetTipInfo(ctx sdk.Context) *btclctypes.BTCHeaderInfo - GetBaseBTCHeader(ctx sdk.Context) *btclctypes.BTCHeaderInfo - GetHighestCommonAncestor(ctx sdk.Context, header1 *btclctypes.BTCHeaderInfo, header2 *btclctypes.BTCHeaderInfo) *btclctypes.BTCHeaderInfo - GetInOrderAncestorsUntil(ctx sdk.Context, descendant *btclctypes.BTCHeaderInfo, ancestor *btclctypes.BTCHeaderInfo) []*btclctypes.BTCHeaderInfo + GetMainChainFrom(ctx sdk.Context, startHeight uint64) []*btclctypes.BTCHeaderInfo GetMainChainUpTo(ctx sdk.Context, depth uint64) []*btclctypes.BTCHeaderInfo + GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo } type BtcCheckpointKeeper interface { diff --git a/x/zoneconcierge/types/keys.go b/x/zoneconcierge/types/keys.go index b8b2bc1da..ce46c87db 100644 --- a/x/zoneconcierge/types/keys.go +++ b/x/zoneconcierge/types/keys.go @@ -34,7 +34,7 @@ var ( ForkKey = []byte{0x14} // ForkKey defines the key to store the forks for each CZ in store EpochChainInfoKey = []byte{0x15} // EpochChainInfoKey defines the key to store each epoch's latests chain info for each CZ in store FinalizedEpochKey = []byte{0x16} // FinalizedEpochKey defines the key to store the last finalised epoch - FinalizingBTCTipKey = []byte{0x17} // FinalizingBTCTipKey defines the key to store the BTC tip when the last epoch is finalised + FinalizingBTCTipKey = []byte{0x17} // FinalizingBTCTipKey is key holding last btc light client segment sent to other cosmos zones ParamsKey = []byte{0x18} // key prefix for the parameters ) diff --git a/x/zoneconcierge/types/mocked_keepers.go b/x/zoneconcierge/types/mocked_keepers.go index 8cd850397..2453d64ff 100644 --- a/x/zoneconcierge/types/mocked_keepers.go +++ b/x/zoneconcierge/types/mocked_keepers.go @@ -8,18 +8,19 @@ import ( context "context" reflect "reflect" - types "github.com/babylonchain/babylon/x/btccheckpoint/types" - types0 "github.com/babylonchain/babylon/x/btclightclient/types" - types1 "github.com/babylonchain/babylon/x/checkpointing/types" - types2 "github.com/babylonchain/babylon/x/epoching/types" + types "github.com/babylonchain/babylon/types" + types0 "github.com/babylonchain/babylon/x/btccheckpoint/types" + types1 "github.com/babylonchain/babylon/x/btclightclient/types" + types2 "github.com/babylonchain/babylon/x/checkpointing/types" + types3 "github.com/babylonchain/babylon/x/epoching/types" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" coretypes "github.com/cometbft/cometbft/rpc/core/types" - types3 "github.com/cosmos/cosmos-sdk/types" - types4 "github.com/cosmos/cosmos-sdk/x/auth/types" - types5 "github.com/cosmos/cosmos-sdk/x/capability/types" - types6 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - types7 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - types8 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + types4 "github.com/cosmos/cosmos-sdk/types" + types5 "github.com/cosmos/cosmos-sdk/x/auth/types" + types6 "github.com/cosmos/cosmos-sdk/x/capability/types" + types7 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + types8 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + types9 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" ) @@ -48,10 +49,10 @@ func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { } // GetModuleAccount mocks base method. -func (m *MockAccountKeeper) GetModuleAccount(ctx types3.Context, name string) types4.ModuleAccountI { +func (m *MockAccountKeeper) GetModuleAccount(ctx types4.Context, name string) types5.ModuleAccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAccount", ctx, name) - ret0, _ := ret[0].(types4.ModuleAccountI) + ret0, _ := ret[0].(types5.ModuleAccountI) return ret0 } @@ -62,10 +63,10 @@ func (mr *MockAccountKeeperMockRecorder) GetModuleAccount(ctx, name interface{}) } // GetModuleAddress mocks base method. -func (m *MockAccountKeeper) GetModuleAddress(name string) types3.AccAddress { +func (m *MockAccountKeeper) GetModuleAddress(name string) types4.AccAddress { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAddress", name) - ret0, _ := ret[0].(types3.AccAddress) + ret0, _ := ret[0].(types4.AccAddress) return ret0 } @@ -99,7 +100,7 @@ func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { } // BlockedAddr mocks base method. -func (m *MockBankKeeper) BlockedAddr(addr types3.AccAddress) bool { +func (m *MockBankKeeper) BlockedAddr(addr types4.AccAddress) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BlockedAddr", addr) ret0, _ := ret[0].(bool) @@ -113,7 +114,7 @@ func (mr *MockBankKeeperMockRecorder) BlockedAddr(addr interface{}) *gomock.Call } // BurnCoins mocks base method. -func (m *MockBankKeeper) BurnCoins(ctx types3.Context, moduleName string, amt types3.Coins) error { +func (m *MockBankKeeper) BurnCoins(ctx types4.Context, moduleName string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BurnCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -127,7 +128,7 @@ func (mr *MockBankKeeperMockRecorder) BurnCoins(ctx, moduleName, amt interface{} } // MintCoins mocks base method. -func (m *MockBankKeeper) MintCoins(ctx types3.Context, moduleName string, amt types3.Coins) error { +func (m *MockBankKeeper) MintCoins(ctx types4.Context, moduleName string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MintCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -141,7 +142,7 @@ func (mr *MockBankKeeperMockRecorder) MintCoins(ctx, moduleName, amt interface{} } // SendCoins mocks base method. -func (m *MockBankKeeper) SendCoins(ctx types3.Context, fromAddr, toAddr types3.AccAddress, amt types3.Coins) error { +func (m *MockBankKeeper) SendCoins(ctx types4.Context, fromAddr, toAddr types4.AccAddress, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) ret0, _ := ret[0].(error) @@ -155,7 +156,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt inter } // SendCoinsFromAccountToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx types3.Context, senderAddr types3.AccAddress, recipientModule string, amt types3.Coins) error { +func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx types4.Context, senderAddr types4.AccAddress, recipientModule string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) ret0, _ := ret[0].(error) @@ -169,7 +170,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAd } // SendCoinsFromModuleToAccount mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types3.Context, senderModule string, recipientAddr types3.AccAddress, amt types3.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types4.Context, senderModule string, recipientAddr types4.AccAddress, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) ret0, _ := ret[0].(error) @@ -206,7 +207,7 @@ func (m *MockICS4Wrapper) EXPECT() *MockICS4WrapperMockRecorder { } // SendPacket mocks base method. -func (m *MockICS4Wrapper) SendPacket(ctx types3.Context, channelCap *types5.Capability, sourcePort, sourceChannel string, timeoutHeight types6.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { +func (m *MockICS4Wrapper) SendPacket(ctx types4.Context, channelCap *types6.Capability, sourcePort, sourceChannel string, timeoutHeight types7.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendPacket", ctx, channelCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) ret0, _ := ret[0].(uint64) @@ -244,10 +245,10 @@ func (m *MockChannelKeeper) EXPECT() *MockChannelKeeperMockRecorder { } // GetAllChannels mocks base method. -func (m *MockChannelKeeper) GetAllChannels(ctx types3.Context) []types8.IdentifiedChannel { +func (m *MockChannelKeeper) GetAllChannels(ctx types4.Context) []types9.IdentifiedChannel { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllChannels", ctx) - ret0, _ := ret[0].([]types8.IdentifiedChannel) + ret0, _ := ret[0].([]types9.IdentifiedChannel) return ret0 } @@ -258,10 +259,10 @@ func (mr *MockChannelKeeperMockRecorder) GetAllChannels(ctx interface{}) *gomock } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types3.Context, srcPort, srcChan string) (types8.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types4.Context, srcPort, srcChan string) (types9.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types8.Channel) + ret0, _ := ret[0].(types9.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -273,7 +274,7 @@ func (mr *MockChannelKeeperMockRecorder) GetChannel(ctx, srcPort, srcChan interf } // GetChannelClientState mocks base method. -func (m *MockChannelKeeper) GetChannelClientState(ctx types3.Context, portID, channelID string) (string, exported.ClientState, error) { +func (m *MockChannelKeeper) GetChannelClientState(ctx types4.Context, portID, channelID string) (string, exported.ClientState, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannelClientState", ctx, portID, channelID) ret0, _ := ret[0].(string) @@ -289,7 +290,7 @@ func (mr *MockChannelKeeperMockRecorder) GetChannelClientState(ctx, portID, chan } // GetNextSequenceSend mocks base method. -func (m *MockChannelKeeper) GetNextSequenceSend(ctx types3.Context, portID, channelID string) (uint64, bool) { +func (m *MockChannelKeeper) GetNextSequenceSend(ctx types4.Context, portID, channelID string) (uint64, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetNextSequenceSend", ctx, portID, channelID) ret0, _ := ret[0].(uint64) @@ -327,7 +328,7 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { } // GetClientConsensusState mocks base method. -func (m *MockClientKeeper) GetClientConsensusState(ctx types3.Context, clientID string) (exported.ConsensusState, bool) { +func (m *MockClientKeeper) GetClientConsensusState(ctx types4.Context, clientID string) (exported.ConsensusState, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetClientConsensusState", ctx, clientID) ret0, _ := ret[0].(exported.ConsensusState) @@ -365,10 +366,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types3.Context, connectionID string) (types7.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types4.Context, connectionID string) (types8.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types7.ConnectionEnd) + ret0, _ := ret[0].(types8.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -403,10 +404,10 @@ func (m *MockPortKeeper) EXPECT() *MockPortKeeperMockRecorder { } // BindPort mocks base method. -func (m *MockPortKeeper) BindPort(ctx types3.Context, portID string) *types5.Capability { +func (m *MockPortKeeper) BindPort(ctx types4.Context, portID string) *types6.Capability { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BindPort", ctx, portID) - ret0, _ := ret[0].(*types5.Capability) + ret0, _ := ret[0].(*types6.Capability) return ret0 } @@ -440,7 +441,7 @@ func (m *MockScopedKeeper) EXPECT() *MockScopedKeeperMockRecorder { } // AuthenticateCapability mocks base method. -func (m *MockScopedKeeper) AuthenticateCapability(ctx types3.Context, cap *types5.Capability, name string) bool { +func (m *MockScopedKeeper) AuthenticateCapability(ctx types4.Context, cap *types6.Capability, name string) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AuthenticateCapability", ctx, cap, name) ret0, _ := ret[0].(bool) @@ -454,7 +455,7 @@ func (mr *MockScopedKeeperMockRecorder) AuthenticateCapability(ctx, cap, name in } // ClaimCapability mocks base method. -func (m *MockScopedKeeper) ClaimCapability(ctx types3.Context, cap *types5.Capability, name string) error { +func (m *MockScopedKeeper) ClaimCapability(ctx types4.Context, cap *types6.Capability, name string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ClaimCapability", ctx, cap, name) ret0, _ := ret[0].(error) @@ -468,10 +469,10 @@ func (mr *MockScopedKeeperMockRecorder) ClaimCapability(ctx, cap, name interface } // GetCapability mocks base method. -func (m *MockScopedKeeper) GetCapability(ctx types3.Context, name string) (*types5.Capability, bool) { +func (m *MockScopedKeeper) GetCapability(ctx types4.Context, name string) (*types6.Capability, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCapability", ctx, name) - ret0, _ := ret[0].(*types5.Capability) + ret0, _ := ret[0].(*types6.Capability) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -483,11 +484,11 @@ func (mr *MockScopedKeeperMockRecorder) GetCapability(ctx, name interface{}) *go } // LookupModules mocks base method. -func (m *MockScopedKeeper) LookupModules(ctx types3.Context, name string) ([]string, *types5.Capability, error) { +func (m *MockScopedKeeper) LookupModules(ctx types4.Context, name string) ([]string, *types6.Capability, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LookupModules", ctx, name) ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(*types5.Capability) + ret1, _ := ret[1].(*types6.Capability) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -521,53 +522,39 @@ func (m *MockBTCLightClientKeeper) EXPECT() *MockBTCLightClientKeeperMockRecorde return m.recorder } -// GetBaseBTCHeader mocks base method. -func (m *MockBTCLightClientKeeper) GetBaseBTCHeader(ctx types3.Context) *types0.BTCHeaderInfo { +// GetHeaderByHash mocks base method. +func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx types4.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBaseBTCHeader", ctx) - ret0, _ := ret[0].(*types0.BTCHeaderInfo) + ret := m.ctrl.Call(m, "GetHeaderByHash", ctx, hash) + ret0, _ := ret[0].(*types1.BTCHeaderInfo) return ret0 } -// GetBaseBTCHeader indicates an expected call of GetBaseBTCHeader. -func (mr *MockBTCLightClientKeeperMockRecorder) GetBaseBTCHeader(ctx interface{}) *gomock.Call { +// GetHeaderByHash indicates an expected call of GetHeaderByHash. +func (mr *MockBTCLightClientKeeperMockRecorder) GetHeaderByHash(ctx, hash interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseBTCHeader", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetBaseBTCHeader), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeaderByHash", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetHeaderByHash), ctx, hash) } -// GetHighestCommonAncestor mocks base method. -func (m *MockBTCLightClientKeeper) GetHighestCommonAncestor(ctx types3.Context, header1, header2 *types0.BTCHeaderInfo) *types0.BTCHeaderInfo { +// GetMainChainFrom mocks base method. +func (m *MockBTCLightClientKeeper) GetMainChainFrom(ctx types4.Context, startHeight uint64) []*types1.BTCHeaderInfo { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetHighestCommonAncestor", ctx, header1, header2) - ret0, _ := ret[0].(*types0.BTCHeaderInfo) + ret := m.ctrl.Call(m, "GetMainChainFrom", ctx, startHeight) + ret0, _ := ret[0].([]*types1.BTCHeaderInfo) return ret0 } -// GetHighestCommonAncestor indicates an expected call of GetHighestCommonAncestor. -func (mr *MockBTCLightClientKeeperMockRecorder) GetHighestCommonAncestor(ctx, header1, header2 interface{}) *gomock.Call { +// GetMainChainFrom indicates an expected call of GetMainChainFrom. +func (mr *MockBTCLightClientKeeperMockRecorder) GetMainChainFrom(ctx, startHeight interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHighestCommonAncestor", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetHighestCommonAncestor), ctx, header1, header2) -} - -// GetInOrderAncestorsUntil mocks base method. -func (m *MockBTCLightClientKeeper) GetInOrderAncestorsUntil(ctx types3.Context, descendant, ancestor *types0.BTCHeaderInfo) []*types0.BTCHeaderInfo { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInOrderAncestorsUntil", ctx, descendant, ancestor) - ret0, _ := ret[0].([]*types0.BTCHeaderInfo) - return ret0 -} - -// GetInOrderAncestorsUntil indicates an expected call of GetInOrderAncestorsUntil. -func (mr *MockBTCLightClientKeeperMockRecorder) GetInOrderAncestorsUntil(ctx, descendant, ancestor interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInOrderAncestorsUntil", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetInOrderAncestorsUntil), ctx, descendant, ancestor) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMainChainFrom", reflect.TypeOf((*MockBTCLightClientKeeper)(nil).GetMainChainFrom), ctx, startHeight) } // GetMainChainUpTo mocks base method. -func (m *MockBTCLightClientKeeper) GetMainChainUpTo(ctx types3.Context, depth uint64) []*types0.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetMainChainUpTo(ctx types4.Context, depth uint64) []*types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMainChainUpTo", ctx, depth) - ret0, _ := ret[0].([]*types0.BTCHeaderInfo) + ret0, _ := ret[0].([]*types1.BTCHeaderInfo) return ret0 } @@ -578,10 +565,10 @@ func (mr *MockBTCLightClientKeeperMockRecorder) GetMainChainUpTo(ctx, depth inte } // GetTipInfo mocks base method. -func (m *MockBTCLightClientKeeper) GetTipInfo(ctx types3.Context) *types0.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetTipInfo(ctx types4.Context) *types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTipInfo", ctx) - ret0, _ := ret[0].(*types0.BTCHeaderInfo) + ret0, _ := ret[0].(*types1.BTCHeaderInfo) return ret0 } @@ -615,11 +602,11 @@ func (m *MockBtcCheckpointKeeper) EXPECT() *MockBtcCheckpointKeeperMockRecorder } // GetBestSubmission mocks base method. -func (m *MockBtcCheckpointKeeper) GetBestSubmission(ctx types3.Context, e uint64) (types.BtcStatus, *types.SubmissionKey, error) { +func (m *MockBtcCheckpointKeeper) GetBestSubmission(ctx types4.Context, e uint64) (types0.BtcStatus, *types0.SubmissionKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBestSubmission", ctx, e) - ret0, _ := ret[0].(types.BtcStatus) - ret1, _ := ret[1].(*types.SubmissionKey) + ret0, _ := ret[0].(types0.BtcStatus) + ret1, _ := ret[1].(*types0.SubmissionKey) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -631,10 +618,10 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetBestSubmission(ctx, e interfac } // GetEpochBestSubmissionBtcInfo mocks base method. -func (m *MockBtcCheckpointKeeper) GetEpochBestSubmissionBtcInfo(ctx types3.Context, ed *types.EpochData) *types.SubmissionBtcInfo { +func (m *MockBtcCheckpointKeeper) GetEpochBestSubmissionBtcInfo(ctx types4.Context, ed *types0.EpochData) *types0.SubmissionBtcInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochBestSubmissionBtcInfo", ctx, ed) - ret0, _ := ret[0].(*types.SubmissionBtcInfo) + ret0, _ := ret[0].(*types0.SubmissionBtcInfo) return ret0 } @@ -645,10 +632,10 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetEpochBestSubmissionBtcInfo(ctx } // GetEpochData mocks base method. -func (m *MockBtcCheckpointKeeper) GetEpochData(ctx types3.Context, e uint64) *types.EpochData { +func (m *MockBtcCheckpointKeeper) GetEpochData(ctx types4.Context, e uint64) *types0.EpochData { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochData", ctx, e) - ret0, _ := ret[0].(*types.EpochData) + ret0, _ := ret[0].(*types0.EpochData) return ret0 } @@ -659,10 +646,10 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetEpochData(ctx, e interface{}) } // GetParams mocks base method. -func (m *MockBtcCheckpointKeeper) GetParams(ctx types3.Context) types.Params { +func (m *MockBtcCheckpointKeeper) GetParams(ctx types4.Context) types0.Params { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetParams", ctx) - ret0, _ := ret[0].(types.Params) + ret0, _ := ret[0].(types0.Params) return ret0 } @@ -673,10 +660,10 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetParams(ctx interface{}) *gomoc } // GetSubmissionData mocks base method. -func (m *MockBtcCheckpointKeeper) GetSubmissionData(ctx types3.Context, sk types.SubmissionKey) *types.SubmissionData { +func (m *MockBtcCheckpointKeeper) GetSubmissionData(ctx types4.Context, sk types0.SubmissionKey) *types0.SubmissionData { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSubmissionData", ctx, sk) - ret0, _ := ret[0].(*types.SubmissionData) + ret0, _ := ret[0].(*types0.SubmissionData) return ret0 } @@ -710,10 +697,10 @@ func (m *MockCheckpointingKeeper) EXPECT() *MockCheckpointingKeeperMockRecorder } // GetBLSPubKeySet mocks base method. -func (m *MockCheckpointingKeeper) GetBLSPubKeySet(ctx types3.Context, epochNumber uint64) ([]*types1.ValidatorWithBlsKey, error) { +func (m *MockCheckpointingKeeper) GetBLSPubKeySet(ctx types4.Context, epochNumber uint64) ([]*types2.ValidatorWithBlsKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBLSPubKeySet", ctx, epochNumber) - ret0, _ := ret[0].([]*types1.ValidatorWithBlsKey) + ret0, _ := ret[0].([]*types2.ValidatorWithBlsKey) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -725,10 +712,10 @@ func (mr *MockCheckpointingKeeperMockRecorder) GetBLSPubKeySet(ctx, epochNumber } // GetRawCheckpoint mocks base method. -func (m *MockCheckpointingKeeper) GetRawCheckpoint(ctx types3.Context, epochNumber uint64) (*types1.RawCheckpointWithMeta, error) { +func (m *MockCheckpointingKeeper) GetRawCheckpoint(ctx types4.Context, epochNumber uint64) (*types2.RawCheckpointWithMeta, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRawCheckpoint", ctx, epochNumber) - ret0, _ := ret[0].(*types1.RawCheckpointWithMeta) + ret0, _ := ret[0].(*types2.RawCheckpointWithMeta) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -763,10 +750,10 @@ func (m *MockEpochingKeeper) EXPECT() *MockEpochingKeeperMockRecorder { } // GetEpoch mocks base method. -func (m *MockEpochingKeeper) GetEpoch(ctx types3.Context) *types2.Epoch { +func (m *MockEpochingKeeper) GetEpoch(ctx types4.Context) *types3.Epoch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpoch", ctx) - ret0, _ := ret[0].(*types2.Epoch) + ret0, _ := ret[0].(*types3.Epoch) return ret0 } @@ -777,10 +764,10 @@ func (mr *MockEpochingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call } // GetHistoricalEpoch mocks base method. -func (m *MockEpochingKeeper) GetHistoricalEpoch(ctx types3.Context, epochNumber uint64) (*types2.Epoch, error) { +func (m *MockEpochingKeeper) GetHistoricalEpoch(ctx types4.Context, epochNumber uint64) (*types3.Epoch, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHistoricalEpoch", ctx, epochNumber) - ret0, _ := ret[0].(*types2.Epoch) + ret0, _ := ret[0].(*types3.Epoch) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -792,7 +779,7 @@ func (mr *MockEpochingKeeperMockRecorder) GetHistoricalEpoch(ctx, epochNumber in } // ProveAppHashInEpoch mocks base method. -func (m *MockEpochingKeeper) ProveAppHashInEpoch(ctx types3.Context, height, epochNumber uint64) (*crypto.Proof, error) { +func (m *MockEpochingKeeper) ProveAppHashInEpoch(ctx types4.Context, height, epochNumber uint64) (*crypto.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ProveAppHashInEpoch", ctx, height, epochNumber) ret0, _ := ret[0].(*crypto.Proof) diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index c5466c08b..59f6f8009 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -6,6 +6,7 @@ package types import ( fmt "fmt" types3 "github.com/babylonchain/babylon/x/btccheckpoint/types" + types4 "github.com/babylonchain/babylon/x/btclightclient/types" types2 "github.com/babylonchain/babylon/x/checkpointing/types" types1 "github.com/babylonchain/babylon/x/epoching/types" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" @@ -521,6 +522,51 @@ func (m *ProofFinalizedChainInfo) GetProofEpochSubmitted() []*types3.Transaction return nil } +// Btc light client chain segment grown during last finalized epoch +type BTCChainSegment struct { + BtcHeaders []*types4.BTCHeaderInfo `protobuf:"bytes,2,rep,name=btc_headers,json=btcHeaders,proto3" json:"btc_headers,omitempty"` +} + +func (m *BTCChainSegment) Reset() { *m = BTCChainSegment{} } +func (m *BTCChainSegment) String() string { return proto.CompactTextString(m) } +func (*BTCChainSegment) ProtoMessage() {} +func (*BTCChainSegment) Descriptor() ([]byte, []int) { + return fileDescriptor_ab886e1868e5c5cd, []int{6} +} +func (m *BTCChainSegment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BTCChainSegment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BTCChainSegment.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BTCChainSegment) XXX_Merge(src proto.Message) { + xxx_messageInfo_BTCChainSegment.Merge(m, src) +} +func (m *BTCChainSegment) XXX_Size() int { + return m.Size() +} +func (m *BTCChainSegment) XXX_DiscardUnknown() { + xxx_messageInfo_BTCChainSegment.DiscardUnknown(m) +} + +var xxx_messageInfo_BTCChainSegment proto.InternalMessageInfo + +func (m *BTCChainSegment) GetBtcHeaders() []*types4.BTCHeaderInfo { + if m != nil { + return m.BtcHeaders + } + return nil +} + func init() { proto.RegisterType((*IndexedHeader)(nil), "babylon.zoneconcierge.v1.IndexedHeader") proto.RegisterType((*Forks)(nil), "babylon.zoneconcierge.v1.Forks") @@ -528,6 +574,7 @@ func init() { proto.RegisterType((*FinalizedChainInfo)(nil), "babylon.zoneconcierge.v1.FinalizedChainInfo") proto.RegisterType((*ProofEpochSealed)(nil), "babylon.zoneconcierge.v1.ProofEpochSealed") proto.RegisterType((*ProofFinalizedChainInfo)(nil), "babylon.zoneconcierge.v1.ProofFinalizedChainInfo") + proto.RegisterType((*BTCChainSegment)(nil), "babylon.zoneconcierge.v1.BTCChainSegment") } func init() { @@ -535,64 +582,67 @@ func init() { } var fileDescriptor_ab886e1868e5c5cd = []byte{ - // 901 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xcf, 0x6e, 0x1b, 0x45, - 0x1c, 0xce, 0xc6, 0x4e, 0x42, 0xc6, 0x71, 0x08, 0xd3, 0x42, 0x37, 0x01, 0x1c, 0xcb, 0x95, 0x8a, - 0x8b, 0x60, 0x2d, 0x07, 0x38, 0xc0, 0x05, 0xe1, 0xd0, 0xd2, 0xb4, 0x88, 0xa2, 0x89, 0x5b, 0x10, - 0x12, 0x5a, 0xcd, 0xee, 0x8e, 0xbd, 0xa3, 0xac, 0x67, 0xac, 0x9d, 0x89, 0x6b, 0xf7, 0x29, 0x7a, - 0xe6, 0x2d, 0x78, 0x0b, 0x8e, 0x3d, 0x72, 0x03, 0x25, 0xe2, 0x0d, 0xb8, 0x70, 0x43, 0xfb, 0x9b, - 0x19, 0x7b, 0x4d, 0x70, 0x9b, 0x4b, 0xb4, 0x33, 0xf3, 0xfd, 0xbe, 0xf9, 0xe6, 0xfb, 0xfd, 0x89, - 0xd1, 0x47, 0x11, 0x8d, 0x66, 0x99, 0x14, 0x9d, 0xe7, 0x52, 0xb0, 0x58, 0x8a, 0x98, 0xb3, 0x7c, - 0xc8, 0x3a, 0x93, 0xee, 0xf2, 0x46, 0x30, 0xce, 0xa5, 0x96, 0xd8, 0xb7, 0xe8, 0x60, 0xf9, 0x70, - 0xd2, 0x3d, 0xb8, 0x39, 0x94, 0x43, 0x09, 0xa0, 0x4e, 0xf1, 0x65, 0xf0, 0x07, 0x87, 0x43, 0x29, - 0x87, 0x19, 0xeb, 0xc0, 0x2a, 0x3a, 0x1f, 0x74, 0x34, 0x1f, 0x31, 0xa5, 0xe9, 0x68, 0x6c, 0x01, - 0xef, 0x69, 0x26, 0x12, 0x96, 0x8f, 0xb8, 0xd0, 0x1d, 0x3d, 0x1b, 0x33, 0x65, 0xfe, 0xda, 0xd3, - 0xf7, 0x4b, 0xa7, 0x71, 0x3e, 0x1b, 0x6b, 0x59, 0x30, 0xc9, 0x81, 0x3d, 0x9e, 0x6b, 0x8f, 0x74, - 0x1c, 0xa7, 0x2c, 0x3e, 0x1b, 0xcb, 0x02, 0x39, 0xe9, 0x2e, 0x6f, 0x58, 0xf4, 0x1d, 0x87, 0x5e, - 0x9c, 0x70, 0x31, 0x04, 0x74, 0xa6, 0xc2, 0x33, 0x36, 0xb3, 0xb8, 0xbb, 0x2b, 0x71, 0x57, 0x28, - 0x5b, 0x0e, 0xca, 0xc6, 0x32, 0x4e, 0x2d, 0xca, 0x7d, 0x1b, 0x4c, 0xeb, 0x97, 0x75, 0x54, 0x3f, - 0x11, 0x09, 0x9b, 0xb2, 0xe4, 0x01, 0xa3, 0x09, 0xcb, 0xf1, 0x3e, 0x7a, 0x23, 0x4e, 0x29, 0x17, - 0x21, 0x4f, 0x7c, 0xaf, 0xe9, 0xb5, 0xb7, 0xc9, 0x16, 0xac, 0x4f, 0x12, 0x8c, 0x51, 0x35, 0xa5, - 0x2a, 0xf5, 0xd7, 0x9b, 0x5e, 0x7b, 0x87, 0xc0, 0x37, 0x7e, 0x07, 0x6d, 0xa6, 0x8c, 0x0f, 0x53, - 0xed, 0x57, 0x9a, 0x5e, 0xbb, 0x4a, 0xec, 0x0a, 0x7f, 0x8a, 0xaa, 0x85, 0x9b, 0x7e, 0xb5, 0xe9, - 0xb5, 0x6b, 0x47, 0x07, 0x81, 0xb1, 0x3a, 0x70, 0x56, 0x07, 0x7d, 0x67, 0x75, 0xaf, 0xfa, 0xe2, - 0x8f, 0x43, 0x8f, 0x00, 0x1a, 0x7f, 0x89, 0x76, 0xad, 0xe8, 0x30, 0x05, 0x39, 0xfe, 0x06, 0xc4, - 0xfb, 0xc1, 0xc2, 0xeb, 0xc0, 0xe4, 0xc0, 0xc8, 0x25, 0x75, 0x8b, 0xb7, 0xea, 0x6f, 0x23, 0xb7, - 0x11, 0xc2, 0x4b, 0xfd, 0x4d, 0x50, 0xb5, 0x63, 0x37, 0xef, 0x15, 0x7b, 0xf8, 0x0e, 0x7a, 0xd3, - 0x81, 0xf4, 0x34, 0x84, 0x27, 0x6d, 0xc1, 0x93, 0x5c, 0x6c, 0x7f, 0xfa, 0x80, 0xaa, 0xb4, 0xf5, - 0x10, 0x6d, 0xdc, 0x97, 0xf9, 0x99, 0xc2, 0x5f, 0xa1, 0x2d, 0x23, 0x47, 0xf9, 0x95, 0x66, 0xa5, - 0x5d, 0x3b, 0xfa, 0x20, 0x58, 0x55, 0x6a, 0xc1, 0x92, 0x9b, 0xc4, 0xc5, 0xb5, 0xfe, 0xf6, 0xd0, - 0xf6, 0x31, 0xf8, 0x28, 0x06, 0xf2, 0x55, 0x26, 0x7f, 0x8b, 0xea, 0x19, 0xd5, 0x4c, 0x69, 0xe7, - 0xc0, 0x3a, 0x38, 0x70, 0xed, 0x1b, 0x77, 0x4c, 0xb4, 0xf5, 0xa3, 0x87, 0xec, 0x3a, 0x1c, 0x14, - 0x2f, 0x81, 0x24, 0xd5, 0x8e, 0x0e, 0x57, 0x93, 0xc1, 0x83, 0x49, 0xcd, 0x04, 0x99, 0xd7, 0x7f, - 0x81, 0xf6, 0xe7, 0x8d, 0xc1, 0x12, 0x2b, 0x4b, 0x85, 0xb1, 0x3c, 0x17, 0x1a, 0xf2, 0x5b, 0x25, - 0xb7, 0x4a, 0x00, 0x73, 0xb3, 0x3a, 0x2e, 0x8e, 0x5b, 0xbf, 0x56, 0x10, 0xbe, 0xcf, 0x05, 0xcd, - 0xf8, 0x73, 0x96, 0x5c, 0xeb, 0xfd, 0x4f, 0xd0, 0xcd, 0x81, 0x0b, 0x08, 0x2d, 0x48, 0x0c, 0xa4, - 0xb5, 0xe1, 0xf6, 0x6a, 0xe5, 0x73, 0x76, 0x82, 0x07, 0x57, 0x6f, 0xfc, 0x1c, 0x21, 0x28, 0x08, - 0x43, 0x56, 0xb1, 0x55, 0xe9, 0xc8, 0xe6, 0x5d, 0x31, 0xe9, 0x06, 0x50, 0x23, 0x64, 0x1b, 0xb6, - 0x20, 0xf4, 0x3b, 0xb4, 0x9b, 0xd3, 0x67, 0xe1, 0xa2, 0xbf, 0x6c, 0x51, 0x2f, 0x52, 0xb2, 0xd4, - 0x8b, 0x05, 0x07, 0xa1, 0xcf, 0x8e, 0xe7, 0x7b, 0xa4, 0x9e, 0x97, 0x97, 0xf8, 0x09, 0xc2, 0x91, - 0x8e, 0x43, 0x75, 0x1e, 0x8d, 0xb8, 0x52, 0x5c, 0x8a, 0xa2, 0xbd, 0x6d, 0xa1, 0x2f, 0x38, 0x97, - 0x87, 0xc4, 0xa4, 0x1b, 0x9c, 0xce, 0xf1, 0x8f, 0xd8, 0x8c, 0xec, 0x45, 0x3a, 0x5e, 0xda, 0xc1, - 0xdf, 0xa0, 0x0d, 0x18, 0x3f, 0x50, 0xf2, 0xb5, 0xa3, 0xee, 0x6a, 0xa7, 0xbe, 0x2f, 0x60, 0x57, - 0xb3, 0x42, 0x4c, 0x7c, 0xeb, 0x1f, 0x0f, 0xed, 0x01, 0x04, 0x9c, 0x38, 0x65, 0x34, 0x63, 0x09, - 0x26, 0xa8, 0x3e, 0xa1, 0x19, 0x4f, 0xa8, 0x96, 0x79, 0xa8, 0x98, 0xf6, 0x3d, 0x68, 0x84, 0x8f, - 0x57, 0x7b, 0xf0, 0xd4, 0xc1, 0x7f, 0xe0, 0x3a, 0xed, 0x65, 0xaa, 0x50, 0xbd, 0x33, 0xe7, 0x38, - 0x65, 0x1a, 0xdf, 0x43, 0x7b, 0x70, 0x63, 0x58, 0xca, 0x8c, 0x49, 0xf3, 0xbb, 0xe5, 0x7e, 0x37, - 0xb3, 0xd5, 0xa8, 0x7e, 0x3c, 0x56, 0x64, 0x77, 0x3c, 0x17, 0x07, 0xf9, 0x79, 0x88, 0x6e, 0x94, - 0x69, 0x26, 0x34, 0x03, 0x81, 0x95, 0xd7, 0x33, 0xed, 0x2d, 0x98, 0x9e, 0xd2, 0xec, 0x94, 0xe9, - 0xd6, 0x5f, 0xeb, 0xe8, 0xd6, 0x0a, 0x7b, 0xf0, 0xd7, 0xe8, 0x2d, 0x73, 0x8f, 0x9e, 0x86, 0x5c, - 0x84, 0x51, 0x26, 0xe3, 0x33, 0x5b, 0x0a, 0xfb, 0x57, 0xe7, 0x53, 0x7f, 0x0a, 0x3c, 0x56, 0x6d, - 0x7f, 0x7a, 0x22, 0x7a, 0x45, 0x00, 0x7e, 0x84, 0xde, 0x36, 0x2c, 0xa6, 0x8f, 0x0a, 0x26, 0x33, - 0xa9, 0xfe, 0x67, 0xd2, 0x95, 0xf5, 0x12, 0x0c, 0x61, 0xa6, 0xbb, 0x4e, 0xec, 0x24, 0xfb, 0x11, - 0xe1, 0xf2, 0xd3, 0x15, 0xe4, 0xca, 0x16, 0xc0, 0x87, 0xaf, 0x29, 0x80, 0x52, 0x76, 0xcb, 0x46, - 0xd8, 0x7c, 0xff, 0xec, 0x64, 0x5a, 0xe6, 0xa2, 0xd4, 0xb4, 0x66, 0x89, 0xbf, 0x05, 0x79, 0xbf, - 0xbb, 0xba, 0x4e, 0xfb, 0x39, 0x15, 0x8a, 0xc6, 0x9a, 0x4b, 0x53, 0x55, 0x37, 0x4a, 0xdc, 0x8e, - 0xa5, 0xf7, 0xf8, 0xb7, 0x8b, 0x86, 0xf7, 0xf2, 0xa2, 0xe1, 0xfd, 0x79, 0xd1, 0xf0, 0x5e, 0x5c, - 0x36, 0xd6, 0x5e, 0x5e, 0x36, 0xd6, 0x7e, 0xbf, 0x6c, 0xac, 0xfd, 0xf4, 0xd9, 0x90, 0xeb, 0xf4, - 0x3c, 0x0a, 0x62, 0x39, 0xea, 0xd8, 0x3b, 0x60, 0x0a, 0xb8, 0x45, 0x67, 0xfa, 0x9f, 0x1f, 0x03, - 0x60, 0x77, 0xb4, 0x09, 0xff, 0x59, 0x3e, 0xf9, 0x37, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x27, 0xa0, - 0x60, 0x32, 0x08, 0x00, 0x00, + // 959 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0x4e, 0x42, 0xc6, 0x71, 0x1a, 0xa6, 0x85, 0x6e, 0x02, 0x38, 0x96, 0x2b, 0x15, + 0x17, 0xc1, 0x5a, 0x0e, 0x70, 0x80, 0x0b, 0xc2, 0xa6, 0xa5, 0x6e, 0x11, 0x45, 0x63, 0xb7, 0x20, + 0x04, 0x5a, 0xed, 0x9f, 0xf1, 0xee, 0x2a, 0xeb, 0x1d, 0x6b, 0x67, 0xe2, 0xda, 0xfd, 0x14, 0x3d, + 0xf3, 0x2d, 0xf8, 0x16, 0x1c, 0x7b, 0xe4, 0x06, 0x4a, 0xc4, 0x37, 0xe0, 0xc2, 0x0d, 0xcd, 0x9b, + 0x99, 0xf5, 0x9a, 0xe0, 0xb6, 0x97, 0x68, 0x67, 0xe6, 0xf7, 0x7e, 0xf3, 0x9b, 0xf7, 0x7b, 0xef, + 0x39, 0xe8, 0x43, 0xdf, 0xf3, 0x97, 0x29, 0xcb, 0xba, 0xcf, 0x58, 0x46, 0x03, 0x96, 0x05, 0x09, + 0xcd, 0x23, 0xda, 0x9d, 0xf7, 0xd6, 0x37, 0x9c, 0x59, 0xce, 0x04, 0xc3, 0xb6, 0x46, 0x3b, 0xeb, + 0x87, 0xf3, 0xde, 0xf1, 0x8d, 0x88, 0x45, 0x0c, 0x40, 0x5d, 0xf9, 0xa5, 0xf0, 0xc7, 0x27, 0x11, + 0x63, 0x51, 0x4a, 0xbb, 0xb0, 0xf2, 0xcf, 0x27, 0x5d, 0x91, 0x4c, 0x29, 0x17, 0xde, 0x74, 0xa6, + 0x01, 0xef, 0x0a, 0x9a, 0x85, 0x34, 0x9f, 0x26, 0x99, 0xe8, 0x8a, 0xe5, 0x8c, 0x72, 0xf5, 0x57, + 0x9f, 0xbe, 0x57, 0x3a, 0x0d, 0xf2, 0xe5, 0x4c, 0x30, 0xc9, 0xc4, 0x26, 0xfa, 0xb8, 0xd0, 0xee, + 0x8b, 0x20, 0x88, 0x69, 0x70, 0x36, 0x63, 0x12, 0x39, 0xef, 0xad, 0x6f, 0x68, 0xf4, 0x6d, 0x83, + 0x5e, 0x9d, 0x24, 0x59, 0x04, 0xe8, 0x94, 0xbb, 0x67, 0x74, 0xa9, 0x71, 0x77, 0x36, 0xe2, 0xae, + 0x50, 0xb6, 0x0d, 0x94, 0xce, 0x58, 0x10, 0x6b, 0x94, 0xf9, 0xd6, 0x18, 0xa7, 0x24, 0x32, 0x4d, + 0xa2, 0x58, 0xfe, 0xa5, 0x85, 0xca, 0xd2, 0x8e, 0xc2, 0xb7, 0x7f, 0xa9, 0xa0, 0xc6, 0x30, 0x0b, + 0xe9, 0x82, 0x86, 0xf7, 0xa9, 0x17, 0xd2, 0x1c, 0x1f, 0xa1, 0x37, 0x82, 0xd8, 0x4b, 0x32, 0x37, + 0x09, 0x6d, 0xab, 0x65, 0x75, 0xf6, 0xc8, 0x2e, 0xac, 0x87, 0x21, 0xc6, 0xa8, 0x16, 0x7b, 0x3c, + 0xb6, 0x2b, 0x2d, 0xab, 0xb3, 0x4f, 0xe0, 0x1b, 0xbf, 0x8d, 0x76, 0x62, 0x2a, 0x69, 0xed, 0x6a, + 0xcb, 0xea, 0xd4, 0x88, 0x5e, 0xe1, 0x4f, 0x50, 0x4d, 0x66, 0xdf, 0xae, 0xb5, 0xac, 0x4e, 0xfd, + 0xf4, 0xd8, 0x51, 0xd6, 0x38, 0xc6, 0x1a, 0x67, 0x6c, 0xac, 0xe9, 0xd7, 0x9e, 0xff, 0x71, 0x62, + 0x11, 0x40, 0xe3, 0x2f, 0xd0, 0x81, 0x7e, 0x80, 0x1b, 0x83, 0x1c, 0x7b, 0x1b, 0xe2, 0x6d, 0x67, + 0xe5, 0x8d, 0xa3, 0x3c, 0x53, 0x72, 0x49, 0x43, 0xe3, 0xb5, 0xfa, 0x5b, 0xc8, 0x6c, 0xb8, 0x90, + 0x19, 0x7b, 0x07, 0x54, 0xed, 0xeb, 0xcd, 0xbb, 0x72, 0x0f, 0xdf, 0x46, 0xd7, 0x0c, 0x48, 0x2c, + 0x5c, 0x78, 0xd2, 0x2e, 0x3c, 0xc9, 0xc4, 0x8e, 0x17, 0xf7, 0x3d, 0x1e, 0xb7, 0x1f, 0xa0, 0xed, + 0x7b, 0x2c, 0x3f, 0xe3, 0xf8, 0x4b, 0xb4, 0xab, 0xe4, 0x70, 0xbb, 0xda, 0xaa, 0x76, 0xea, 0xa7, + 0xef, 0x3b, 0x9b, 0x4a, 0xd3, 0x59, 0xcb, 0x26, 0x31, 0x71, 0xed, 0xbf, 0x2d, 0xb4, 0x37, 0x80, + 0x3c, 0x66, 0x13, 0xf6, 0xb2, 0x24, 0x7f, 0x83, 0x1a, 0xa9, 0x27, 0x28, 0x17, 0x26, 0x03, 0x15, + 0xc8, 0xc0, 0x6b, 0xdf, 0xb8, 0xaf, 0xa2, 0x75, 0x3e, 0xfa, 0x48, 0xaf, 0xdd, 0x89, 0x7c, 0x09, + 0x98, 0x54, 0x3f, 0x3d, 0xd9, 0x4c, 0x06, 0x0f, 0x26, 0x75, 0x15, 0xa4, 0x5e, 0xff, 0x39, 0x3a, + 0x2a, 0x1a, 0x89, 0x86, 0x5a, 0x16, 0x77, 0x03, 0x76, 0x9e, 0x09, 0xf0, 0xb7, 0x46, 0x6e, 0x96, + 0x00, 0xea, 0x66, 0x3e, 0x90, 0xc7, 0xed, 0x5f, 0xab, 0x08, 0xdf, 0x4b, 0x32, 0x2f, 0x4d, 0x9e, + 0xd1, 0xf0, 0xb5, 0xde, 0xff, 0x18, 0xdd, 0x98, 0x98, 0x00, 0x57, 0x83, 0xb2, 0x09, 0xd3, 0x69, + 0xb8, 0xb5, 0x59, 0x79, 0xc1, 0x4e, 0xf0, 0xe4, 0xea, 0x8d, 0x9f, 0x21, 0x04, 0x05, 0xa1, 0xc8, + 0xaa, 0xba, 0x2a, 0x0d, 0x59, 0xd1, 0x45, 0xf3, 0x9e, 0x03, 0x35, 0x42, 0xf6, 0x60, 0x0b, 0x42, + 0xbf, 0x45, 0x07, 0xb9, 0xf7, 0xd4, 0x5d, 0xf5, 0xa3, 0x2e, 0xea, 0x95, 0x25, 0x6b, 0xbd, 0x2b, + 0x39, 0x88, 0xf7, 0x74, 0x50, 0xec, 0x91, 0x46, 0x5e, 0x5e, 0xe2, 0xc7, 0x08, 0xfb, 0x22, 0x70, + 0xf9, 0xb9, 0x3f, 0x4d, 0x38, 0x4f, 0x58, 0x26, 0xc7, 0x81, 0x2e, 0xf4, 0x15, 0xe7, 0xfa, 0x50, + 0x99, 0xf7, 0x9c, 0x51, 0x81, 0x7f, 0x48, 0x97, 0xe4, 0xd0, 0x17, 0xc1, 0xda, 0x0e, 0xfe, 0x1a, + 0x6d, 0xc3, 0xb8, 0x82, 0x92, 0xaf, 0x9f, 0xf6, 0x36, 0x67, 0xea, 0x3b, 0x09, 0xbb, 0xea, 0x0a, + 0x51, 0xf1, 0xed, 0x7f, 0x2c, 0x74, 0x08, 0x10, 0xc8, 0xc4, 0x88, 0x7a, 0x29, 0x0d, 0x31, 0x41, + 0x8d, 0xb9, 0x97, 0x26, 0xa1, 0x27, 0x58, 0xee, 0x72, 0x2a, 0x6c, 0x0b, 0x1a, 0xe1, 0xa3, 0xcd, + 0x39, 0x78, 0x62, 0xe0, 0xdf, 0x27, 0x22, 0xee, 0xa7, 0x5c, 0xaa, 0xde, 0x2f, 0x38, 0x46, 0x54, + 0xe0, 0xbb, 0xe8, 0x10, 0x6e, 0x74, 0x4b, 0xce, 0x28, 0x9b, 0xdf, 0x29, 0xf7, 0xbb, 0x9a, 0xc5, + 0x4a, 0xf5, 0xa3, 0x19, 0x27, 0x07, 0xb3, 0x42, 0x1c, 0xf8, 0xf3, 0x00, 0x5d, 0x2f, 0xd3, 0xcc, + 0xbd, 0x14, 0x04, 0x56, 0x5f, 0xcd, 0x74, 0xb8, 0x62, 0x7a, 0xe2, 0xa5, 0x23, 0x2a, 0xda, 0x7f, + 0x55, 0xd0, 0xcd, 0x0d, 0xe9, 0xc1, 0x5f, 0xa1, 0x37, 0xd5, 0x3d, 0x62, 0xe1, 0x26, 0x99, 0xeb, + 0xa7, 0x2c, 0x38, 0xd3, 0xa5, 0x70, 0x74, 0x75, 0x3e, 0x8d, 0x17, 0xc0, 0xa3, 0xd5, 0x8e, 0x17, + 0xc3, 0xac, 0x2f, 0x03, 0xf0, 0x43, 0xf4, 0x96, 0x62, 0x51, 0x7d, 0x24, 0x99, 0xd4, 0xa4, 0xfa, + 0x9f, 0x49, 0x57, 0xd6, 0x4b, 0x30, 0x84, 0xa9, 0xee, 0x1a, 0xea, 0x49, 0xf6, 0x03, 0xc2, 0xe5, + 0xa7, 0x73, 0xf0, 0x4a, 0x17, 0xc0, 0x07, 0xaf, 0x28, 0x80, 0x92, 0xbb, 0xe5, 0x44, 0x68, 0xbf, + 0x7f, 0x36, 0x32, 0x35, 0xb3, 0x2c, 0x35, 0x21, 0x68, 0x68, 0xef, 0x82, 0xef, 0x77, 0x36, 0xd7, + 0xe9, 0x38, 0xf7, 0x32, 0xee, 0x05, 0x22, 0x61, 0xaa, 0xaa, 0xae, 0x97, 0xb8, 0x0d, 0x4b, 0xfb, + 0x27, 0x74, 0xad, 0x3f, 0x1e, 0x40, 0x6e, 0x47, 0x34, 0x9a, 0xd2, 0x4c, 0xe0, 0x21, 0xaa, 0xcb, + 0xb6, 0x30, 0x83, 0xb6, 0x02, 0xf7, 0x74, 0xca, 0xf7, 0x94, 0x7f, 0xbe, 0xe6, 0x3d, 0xa7, 0x3f, + 0x1e, 0x98, 0x6c, 0x4c, 0x18, 0x41, 0xbe, 0x08, 0xf4, 0xe8, 0xe9, 0x3f, 0xfa, 0xed, 0xa2, 0x69, + 0xbd, 0xb8, 0x68, 0x5a, 0x7f, 0x5e, 0x34, 0xad, 0xe7, 0x97, 0xcd, 0xad, 0x17, 0x97, 0xcd, 0xad, + 0xdf, 0x2f, 0x9b, 0x5b, 0x3f, 0x7e, 0x1a, 0x25, 0x22, 0x3e, 0xf7, 0x9d, 0x80, 0x4d, 0xbb, 0x9a, + 0x19, 0x66, 0x8c, 0x59, 0x74, 0x17, 0xff, 0xf9, 0xd7, 0x04, 0xcc, 0xf4, 0x77, 0xe0, 0x77, 0xeb, + 0xe3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x48, 0xbe, 0x03, 0x62, 0xc0, 0x08, 0x00, 0x00, } func (m *IndexedHeader) Marshal() (dAtA []byte, err error) { @@ -991,6 +1041,43 @@ func (m *ProofFinalizedChainInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *BTCChainSegment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BTCChainSegment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BTCChainSegment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BtcHeaders) > 0 { + for iNdEx := len(m.BtcHeaders) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.BtcHeaders[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + return len(dAtA) - i, nil +} + func encodeVarintZoneconcierge(dAtA []byte, offset int, v uint64) int { offset -= sovZoneconcierge(v) base := offset @@ -1159,6 +1246,21 @@ func (m *ProofFinalizedChainInfo) Size() (n int) { return n } +func (m *BTCChainSegment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.BtcHeaders) > 0 { + for _, e := range m.BtcHeaders { + l = e.Size() + n += 1 + l + sovZoneconcierge(uint64(l)) + } + } + return n +} + func sovZoneconcierge(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2292,6 +2394,90 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { } return nil } +func (m *BTCChainSegment) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZoneconcierge + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BTCChainSegment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BTCChainSegment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BtcHeaders", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZoneconcierge + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthZoneconcierge + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthZoneconcierge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BtcHeaders = append(m.BtcHeaders, &types4.BTCHeaderInfo{}) + if err := m.BtcHeaders[len(m.BtcHeaders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipZoneconcierge(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthZoneconcierge + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipZoneconcierge(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 06a9a8d88092fbb79ad004bbd3fcdf6fc1efcbd6 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:42:34 -0400 Subject: [PATCH 093/202] feat: Add SlashingRate param in btcstaking module (#95) * proto def * proto def * genesis setup * fix * more comments * comprehensive comments * improve checks * fix checks * change slashing_percentage data type to cosmos.Dec * fix slashing_percentage usages to cosmos.Dec * fix validation check for default params * fix checks and tests * change default percentage to 10 * fix template * change slashing percentage to slashing rate in proto * replace usages with slashingRate * fix comments and checks * fix error message --- cmd/babylond/cmd/flags.go | 8 +- cmd/babylond/cmd/genesis.go | 9 ++- cmd/babylond/cmd/testnet.go | 3 +- proto/babylon/btcstaking/v1/params.proto | 10 ++- x/btcstaking/keeper/msg_server_test.go | 2 + x/btcstaking/types/genesis_test.go | 17 +++- x/btcstaking/types/params.go | 16 +++- x/btcstaking/types/params.pb.go | 99 ++++++++++++++++++------ 8 files changed, 133 insertions(+), 31 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 1c522280c..b282f1582 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -1,9 +1,10 @@ package cmd import ( - sdk "github.com/cosmos/cosmos-sdk/types" "time" + sdk "github.com/cosmos/cosmos-sdk/types" + babylonApp "github.com/babylonchain/babylon/app" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -30,6 +31,7 @@ const ( flagJuryPk = "jury-pk" flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" + flagSlashingRate = "slashing-rate" flagMinPubRand = "min-pub-rand" flagMinCommissionRate = "min-commission-rate" ) @@ -53,6 +55,7 @@ type GenesisCLIArgs struct { JuryPK string SlashingAddress string MinSlashingTransactionFeeSat int64 + SlashingRate sdk.Dec MinPubRand uint64 MinCommissionRate sdk.Dec } @@ -76,6 +79,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") + cmd.Flags().String(flagSlashingRate, "0.1", "Bitcoin staking slashing rate") // finality args cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") // inflation args @@ -103,6 +107,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) + slashingRate, _ := cmd.Flags().GetString(flagSlashingRate) minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) @@ -131,6 +136,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { SlashingAddress: slashingAddress, MinSlashingTransactionFeeSat: minSlashingFee, MinCommissionRate: sdk.MustNewDecFromStr(minCommissionRate), + SlashingRate: sdk.MustNewDecFromStr(slashingRate), MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index dccf43742..6a6e43047 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -3,9 +3,10 @@ package cmd import ( "encoding/json" "fmt" + "time" + btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types" finalitytypes "github.com/babylonchain/babylon/x/finality/types" - "time" appparams "github.com/babylonchain/babylon/app/params" bbn "github.com/babylonchain/babylon/types" @@ -69,7 +70,8 @@ Example: genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, + genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, + genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) } else if network == "mainnet" { @@ -229,7 +231,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, juryPk string, slashingAddress string, minSlashingFee int64, - minCommissionRate sdk.Dec, minPubRand uint64, inflationRateChange float64, + minCommissionRate sdk.Dec, slashingRate sdk.Dec, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -310,6 +312,7 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtcstakingParams.SlashingAddress = slashingAddress genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee genParams.BtcstakingParams.MinCommissionRate = minCommissionRate + genParams.BtcstakingParams.SlashingRate = slashingRate if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 28a0ed512..4c72beaca 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -99,7 +99,8 @@ Example: genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, + genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MinPubRand, + genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index af8806a8f..970b91d1c 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package babylon.btcstaking.v1; import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; @@ -15,7 +16,7 @@ message Params { // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin string slashing_address = 2; - // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified + // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? int64 min_slashing_tx_fee_sat = 3; @@ -24,4 +25,11 @@ message Params { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; + // slashing_rate determines the portion of the staked amount to be slashed, + // expressed as a decimal (e.g., 0.5 for 50%). + string slashing_rate = 5 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 66b690b4e..22a29a839 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -100,6 +100,7 @@ func getJuryInfo(t *testing.T, SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), + SlashingRate: sdk.MustNewDecFromStr("0.1"), }) require.NoError(t, err) return jurySK, juryPK, slashingAddr @@ -406,6 +407,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), + SlashingRate: sdk.MustNewDecFromStr("0.1"), }) require.NoError(t, err) diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index 9869f6db7..ee9d39639 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -1,9 +1,10 @@ package types_test import ( - sdkmath "cosmossdk.io/math" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/stretchr/testify/require" ) @@ -27,10 +28,24 @@ func TestGenesisState_Validate(t *testing.T) { SlashingAddress: types.DefaultParams().SlashingAddress, MinSlashingTxFeeSat: 500, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), }, }, valid: true, }, + { + desc: "invalid slashing rate in genesis", + genState: &types.GenesisState{ + Params: types.Params{ + JuryPk: types.DefaultParams().JuryPk, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate + }, + }, + valid: false, + }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 84035ecb7..6fd3c392b 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -1,8 +1,9 @@ package types import ( - "cosmossdk.io/math" "fmt" + + "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" @@ -43,6 +44,8 @@ func DefaultParams() Params { SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, MinCommissionRate: math.LegacyZeroDec(), + // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. + SlashingRate: math.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 } } @@ -73,6 +76,14 @@ func validateMinCommissionRate(rate sdk.Dec) error { return nil } +// validateSlashingRate checks if the slashing rate is within the valid range (0, 1]. +func validateSlashingRate(slashingRate sdk.Dec) error { + if slashingRate.LTE(math.LegacyZeroDec()) || slashingRate.GT(math.LegacyOneDec()) { + return fmt.Errorf("slashing rate must be in the range (0, 1] i.e., 0 exclusive and 1 inclusive") + } + return nil +} + // Validate validates the set of params func (p Params) Validate() error { if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { @@ -81,6 +92,9 @@ func (p Params) Validate() error { if err := validateMinCommissionRate(p.MinCommissionRate); err != nil { return err } + if err := validateSlashingRate(p.SlashingRate); err != nil { + return err + } return nil } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index ab6c064c8..b6f007013 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -6,6 +6,7 @@ package types import ( fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/cosmos-proto" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -39,6 +40,9 @@ type Params struct { MinSlashingTxFeeSat int64 `protobuf:"varint,3,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators MinCommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_commission_rate"` + // slashing_rate determines the portion of the staked amount to be slashed, + // expressed as a decimal (e.g., 0.5 for 50%). + SlashingRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=slashing_rate,json=slashingRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slashing_rate"` } func (m *Params) Reset() { *m = Params{} } @@ -96,29 +100,32 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 348 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xc1, 0x4f, 0xea, 0x30, - 0x00, 0xc6, 0x57, 0x20, 0xbc, 0xbc, 0xe5, 0x25, 0x4f, 0xa7, 0xc6, 0xc5, 0xc3, 0x46, 0x38, 0x18, - 0x3c, 0xb8, 0x89, 0x10, 0x0f, 0xde, 0x9c, 0xc6, 0xc4, 0x68, 0xe2, 0x32, 0x3c, 0x79, 0xb0, 0xe9, - 0x46, 0x1d, 0x75, 0xb4, 0x5d, 0xd6, 0x42, 0xd8, 0x7f, 0xe1, 0xd1, 0x23, 0x7f, 0x0e, 0x47, 0x8e, - 0x86, 0x03, 0x31, 0xf0, 0x8f, 0x98, 0x8d, 0x81, 0xdc, 0x3c, 0xb5, 0xfd, 0xfa, 0xeb, 0xf7, 0xb5, - 0x5f, 0xd5, 0xba, 0x8f, 0xfc, 0xb4, 0xcf, 0x99, 0xed, 0xcb, 0x40, 0x48, 0x14, 0x11, 0x16, 0xda, - 0xc3, 0xa6, 0x1d, 0xa3, 0x04, 0x51, 0x61, 0xc5, 0x09, 0x97, 0x5c, 0x3b, 0x28, 0x18, 0xeb, 0x87, - 0xb1, 0x86, 0xcd, 0xa3, 0xfd, 0x90, 0x87, 0x3c, 0x27, 0xec, 0x6c, 0xb6, 0x82, 0xeb, 0xe3, 0x92, - 0x5a, 0x75, 0xf3, 0xd3, 0xda, 0xa3, 0xfa, 0xe7, 0x6d, 0x90, 0xa4, 0x30, 0x8e, 0x74, 0x50, 0x03, - 0x8d, 0x7f, 0xce, 0xc5, 0x6c, 0x6e, 0x9e, 0x87, 0x44, 0xf6, 0x06, 0xbe, 0x15, 0x70, 0x6a, 0x17, - 0xbe, 0x41, 0x0f, 0x11, 0xb6, 0x5e, 0xd8, 0x32, 0x8d, 0xb1, 0xb0, 0x9c, 0x3b, 0xb7, 0xd5, 0x3e, - 0x73, 0x07, 0xfe, 0x3d, 0x4e, 0xbd, 0x6a, 0x66, 0xe3, 0x46, 0xda, 0x89, 0xba, 0x23, 0xfa, 0x48, - 0xf4, 0x08, 0x0b, 0x21, 0xea, 0x76, 0x13, 0x2c, 0x84, 0x5e, 0xaa, 0x81, 0xc6, 0x5f, 0xef, 0xff, - 0x5a, 0xbf, 0x5a, 0xc9, 0x5a, 0x5b, 0x3d, 0xa4, 0x84, 0xc1, 0x0d, 0x2e, 0x47, 0xf0, 0x15, 0x63, - 0x28, 0x90, 0xd4, 0xcb, 0x35, 0xd0, 0x28, 0x7b, 0x7b, 0x94, 0xb0, 0x4e, 0xb1, 0xfb, 0x34, 0xba, - 0xc5, 0xb8, 0x83, 0xa4, 0xf6, 0xa2, 0x66, 0x32, 0x0c, 0x38, 0xa5, 0x44, 0x08, 0xc2, 0x19, 0x4c, - 0x90, 0xc4, 0x7a, 0x25, 0xcb, 0x70, 0xac, 0xc9, 0xdc, 0x54, 0x66, 0x73, 0xf3, 0x78, 0xeb, 0x05, - 0x01, 0x17, 0x94, 0x8b, 0x62, 0x38, 0x15, 0xdd, 0xa8, 0xb8, 0xfe, 0x0d, 0x0e, 0xbc, 0x5d, 0x4a, - 0xd8, 0xf5, 0xc6, 0xc9, 0x43, 0x12, 0x5f, 0x56, 0x3e, 0xc6, 0xa6, 0xe2, 0x3c, 0x4c, 0x16, 0x06, - 0x98, 0x2e, 0x0c, 0xf0, 0xb5, 0x30, 0xc0, 0xfb, 0xd2, 0x50, 0xa6, 0x4b, 0x43, 0xf9, 0x5c, 0x1a, - 0xca, 0xf3, 0xaf, 0xe5, 0x8c, 0xb6, 0xff, 0x29, 0x8f, 0xf2, 0xab, 0x79, 0xef, 0xad, 0xef, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x26, 0xeb, 0xc5, 0x78, 0xca, 0x01, 0x00, 0x00, + // 389 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, + 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, + 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, + 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x24, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xe3, 0x21, 0x12, + 0x10, 0x0e, 0x44, 0x4a, 0xa9, 0x99, 0x99, 0x8b, 0x2d, 0x00, 0x6c, 0xb0, 0x90, 0x3f, 0x17, 0x7b, + 0x56, 0x69, 0x51, 0x65, 0x7c, 0x41, 0xb6, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd9, 0xad, + 0x7b, 0xf2, 0x46, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0x2b, + 0x93, 0x33, 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x3d, 0x27, 0xcf, + 0x00, 0x63, 0x13, 0x83, 0x80, 0xd2, 0x24, 0xef, 0xd4, 0xca, 0x20, 0x36, 0x90, 0x31, 0x01, 0xd9, + 0x42, 0x9a, 0x5c, 0x02, 0xc5, 0x39, 0x89, 0xc5, 0x19, 0x99, 0x79, 0xe9, 0xf1, 0x89, 0x29, 0x29, + 0x45, 0xa9, 0xc5, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xfc, 0x30, 0x71, 0x47, 0x88, + 0xb0, 0x90, 0x09, 0x97, 0x78, 0x6e, 0x66, 0x5e, 0x3c, 0x5c, 0x79, 0x49, 0x45, 0x7c, 0x5a, 0x6a, + 0x6a, 0x7c, 0x71, 0x62, 0x89, 0x04, 0xb3, 0x02, 0xa3, 0x06, 0x73, 0x90, 0x70, 0x6e, 0x66, 0x5e, + 0x30, 0x54, 0x36, 0xa4, 0xc2, 0x2d, 0x35, 0x35, 0x38, 0xb1, 0x44, 0x28, 0x8e, 0x0b, 0x24, 0x1c, + 0x9f, 0x9c, 0x9f, 0x9b, 0x9b, 0x59, 0x5c, 0x9c, 0x99, 0x9f, 0x17, 0x5f, 0x94, 0x58, 0x92, 0x2a, + 0xc1, 0x02, 0xb2, 0xc3, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, 0xe4, 0xd5, 0x90, 0x7c, + 0x00, 0xf1, 0x3a, 0x94, 0xd2, 0x2d, 0x4e, 0xc9, 0x86, 0x3a, 0xdf, 0x25, 0x35, 0x39, 0x48, 0x30, + 0x37, 0x33, 0xcf, 0x19, 0x6e, 0x52, 0x50, 0x62, 0x49, 0xaa, 0x50, 0x22, 0x17, 0x2f, 0xdc, 0x45, + 0x60, 0x93, 0x59, 0xc1, 0x26, 0xdb, 0x90, 0x66, 0xf2, 0xa5, 0x2d, 0xba, 0x5c, 0xd0, 0x30, 0x07, + 0xd9, 0xc3, 0x03, 0x33, 0x12, 0x64, 0x85, 0x15, 0xcb, 0x8c, 0x05, 0xf2, 0x0c, 0x4e, 0x3e, 0x27, + 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, + 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x45, 0x30, 0xfc, 0x2b, 0x90, 0x53, 0x09, + 0xd8, 0xce, 0x24, 0x36, 0x70, 0xd4, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x85, 0xbe, + 0x67, 0x48, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -141,6 +148,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.SlashingRate.Size() + i -= size + if _, err := m.SlashingRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a { size := m.MinCommissionRate.Size() i -= size @@ -208,6 +225,8 @@ func (m *Params) Size() (n int) { } l = m.MinCommissionRate.Size() n += 1 + l + sovParams(uint64(l)) + l = m.SlashingRate.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -366,6 +385,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SlashingRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) From 4a3cc448095f436472550da878de377832235fb7 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 6 Nov 2023 11:03:16 +1100 Subject: [PATCH 094/202] finality: improve tallying algorithm when many blocks are not finalised (#97) --- x/finality/keeper/tallying.go | 92 +++++++++++++++++------------- x/finality/keeper/tallying_test.go | 59 +++++++++++++------ x/finality/types/keys.go | 11 ++-- 3 files changed, 100 insertions(+), 62 deletions(-) diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index 33f6eb6dd..13eece9e1 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -16,11 +16,6 @@ import ( // - non-finalisable blocks (i.e., block with no active validator) // but without block that has validator set AND does not receive QC func (k Keeper) TallyBlocks(ctx sdk.Context) { - // blocksToFinalize is the set of blocks to finalise within this tallying attempt - blocksToFinalize := []*types.IndexedBlock{} - // valSets is the BTC validator set at each height with a non-finalised block - valSets := map[uint64]map[string]uint64{} - activatedHeight, err := k.BTCStakingKeeper.GetBTCStakingActivatedHeight(ctx) if err != nil { // invoking TallyBlocks when BTC staking protocol is not activated is a programming error @@ -28,56 +23,53 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { ctx.BlockHeight(), activatedHeight)) } - // find all blocks that are non-finalised AND have validator set, from latest to the earliest activated height + // start finalising blocks since max(activatedHeight, nextHeightToFinalize) + startHeight := k.getNextHeightToFinalize(ctx) + if startHeight < activatedHeight { + startHeight = activatedHeight + } + + // find all blocks that are non-finalised AND have validator set since max(activatedHeight, lastFinalizedHeight+1) // There are 4 different scenarios as follows - // - has validators, non-finalised: can finalise, add to blocksToFinalize - // - does not have validators, non-finalised: non-finalisable, skip - // - has validators, finalised, break here + // - has validators, non-finalised: tally and try to finalise + // - does not have validators, non-finalised: non-finalisable, continue + // - has validators, finalised: impossible to happen, panic // - does not have validators, finalised: impossible to happen, panic // After this for loop, the blocks since earliest activated height are either finalised or non-finalisable - blockRevIter := k.blockStore(ctx).ReverseIterator(sdk.Uint64ToBigEndian(uint64(activatedHeight)), nil) - for ; blockRevIter.Valid(); blockRevIter.Next() { - // get the indexed block - ibBytes := blockRevIter.Value() - var ib types.IndexedBlock - k.cdc.MustUnmarshal(ibBytes, &ib) + for i := startHeight; i <= uint64(ctx.BlockHeight()); i++ { + ib, err := k.GetBlock(ctx, i) + if err != nil { + panic(err) // failing to get an existing block is a programming error + } + // get the validator set of this block valSet := k.BTCStakingKeeper.GetVotingPowerTable(ctx, ib.Height) if valSet != nil && !ib.Finalized { - // has validators, non-finalised: can finalise, add block and valset - blocksToFinalize = append(blocksToFinalize, &ib) - valSets[ib.Height] = valSet + // has validators, non-finalised: tally and try to finalise the block + voterBTCPKs := k.GetVoters(ctx, ib.Height) + if tally(valSet, voterBTCPKs) { + // if this block gets >2/3 votes, finalise it + k.finalizeBlock(ctx, ib, voterBTCPKs) + } else { + // if not, then this block and all subsequent blocks should not be finalised + // thus, we need to break here + break + } } else if valSet == nil && !ib.Finalized { - // does not have validators, non-finalised: not finalisable, skip + // does not have validators, non-finalised: not finalisable, + // increment the next height to finalise and continue + k.setNextHeightToFinalize(ctx, ib.Height+1) continue } else if valSet != nil && ib.Finalized { // has validators and the block has finalised - // this means that the entire prefix has been finalised, break here - break + // this can only be a programming error + panic(fmt.Errorf("block %d is finalized, but last finalized height in DB does not reach here", ib.Height)) } else if valSet == nil && ib.Finalized { // does not have validators, finalised: impossible to happen, panic panic(fmt.Errorf("block %d is finalized, but does not have a validator set", ib.Height)) } } - // closing the iterator right now before finalising the finalisable blocks - // this is to follow the contract at https://github.com/cosmos/cosmos-sdk/blob/v0.47.4/store/types/store.go#L239-L240 - blockRevIter.Close() - - // for each of these blocks from earliest to latest, tally the block w.r.t. existing votes - for i := len(blocksToFinalize) - 1; i >= 0; i-- { - blockToFinalize := blocksToFinalize[i] - voterBTCPKs := k.GetVoters(ctx, blockToFinalize.Height) - valSet := valSets[blockToFinalize.Height] - if tally(valSet, voterBTCPKs) { - // if this block gets >2/3 votes, finalise it - k.finalizeBlock(ctx, blockToFinalize, voterBTCPKs) - } else { - // if not, then this block and all subsequent blocks should not be finalised - // thus, we need to break here - break - } - } } // finalizeBlock sets a block to be finalised in KVStore and distributes rewards to @@ -86,6 +78,8 @@ func (k Keeper) finalizeBlock(ctx sdk.Context, block *types.IndexedBlock, voterB // set block to be finalised in KVStore block.Finalized = true k.SetBlock(ctx, block) + // set next height to finalise as height+1 + k.setNextHeightToFinalize(ctx, block.Height+1) // distribute rewards to BTC staking stakeholders w.r.t. the reward distribution cache rdc, err := k.BTCStakingKeeper.GetRewardDistCache(ctx, block.Height) if err != nil { @@ -110,5 +104,23 @@ func tally(valSet map[string]uint64, voterBTCPKs map[string]struct{}) bool { votedPower += power } } - return votedPower > totalPower*2/3 + return votedPower*3 > totalPower*2 +} + +// setNextHeightToFinalize sets the next height to finalise as the given height +func (k Keeper) setNextHeightToFinalize(ctx sdk.Context, height uint64) { + store := ctx.KVStore(k.storeKey) + heightBytes := sdk.Uint64ToBigEndian(height) + store.Set(types.NextHeightToFinalizeKey, heightBytes) +} + +// getNextHeightToFinalize gets the next height to finalise +func (k Keeper) getNextHeightToFinalize(ctx sdk.Context) uint64 { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.NextHeightToFinalizeKey) + if bz == nil { + return 0 + } + height := sdk.BigEndianToUint64(bz) + return height } diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 12ba86bc9..d6135d83d 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func FuzzTallying(f *testing.F) { +func FuzzTallying_PanicCases(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -39,14 +39,29 @@ func FuzzTallying(f *testing.F) { Finalized: true, }) // activate BTC staking protocol at height 1 + ctx = ctx.WithBlockHeight(1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(1), nil).Times(1) bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(uint64(1))).Return(nil).Times(1) require.Panics(t, func() { fKeeper.TallyBlocks(ctx) }) + }) +} + +func FuzzTallying_FinalizingNoBlock(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + bsKeeper := types.NewMockBTCStakingKeeper(ctrl) + iKeeper := types.NewMockIncentiveKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) // activate BTC staking protocol at a random height activatedHeight := datagen.RandomInt(r, 10) + 1 - // Case 3: index a list of blocks, don't give them QCs, and tally them + // index a list of blocks, don't give them QCs, and tally them // Expect they are not finalised for i := activatedHeight; i < activatedHeight+10; i++ { // index blocks @@ -55,22 +70,12 @@ func FuzzTallying(f *testing.F) { LastCommitHash: datagen.GenRandomByteArray(r, 32), Finalized: false, }) - // 1 vote - votedValPK, err := datagen.GenRandomBIP340PubKey(r) + // this block does not have QC + err := giveNoQCToHeight(r, ctx, bsKeeper, fKeeper, i) require.NoError(t, err) - votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) - require.NoError(t, err) - fKeeper.SetSig(ctx, i, votedValPK, votedSig) - // 4 BTC vals - valSet := map[string]uint64{ - hex.EncodeToString(votedValPK.MustMarshal()): 1, - hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, - hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, - hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, - } - bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(i)).Return(valSet).Times(1) } // add mock queries to GetBTCStakingActivatedHeight + ctx = ctx.WithBlockHeight(int64(activatedHeight) + 10 - 1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) // tally blocks and none of them should be finalised fKeeper.TallyBlocks(ctx) @@ -79,8 +84,26 @@ func FuzzTallying(f *testing.F) { require.NoError(t, err) require.False(t, ib.Finalized) } + }) + +} + +func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) - // Case 4: index a list of blocks, give some of them QCs, and tally them. + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + bsKeeper := types.NewMockBTCStakingKeeper(ctrl) + iKeeper := types.NewMockIncentiveKeeper(ctrl) + fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, iKeeper) + + // activate BTC staking protocol at a random height + activatedHeight := datagen.RandomInt(r, 10) + 1 + + // index a list of blocks, give some of them QCs, and tally them. // Expect they are all finalised numWithQCs := datagen.RandomInt(r, 5) + 1 for i := activatedHeight; i < activatedHeight+10; i++ { @@ -105,6 +128,7 @@ func FuzzTallying(f *testing.F) { iKeeper.EXPECT().RewardBTCStaking(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) bsKeeper.EXPECT().RemoveRewardDistCache(gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) // add mock queries to GetBTCStakingActivatedHeight + ctx = ctx.WithBlockHeight(int64(activatedHeight) + 10 - 1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) // tally blocks and none of them should be finalised fKeeper.TallyBlocks(ctx) @@ -118,6 +142,7 @@ func FuzzTallying(f *testing.F) { } } }) + } func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakingKeeper, fKeeper *keeper.Keeper, height uint64) error { @@ -162,7 +187,7 @@ func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStak hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, } - bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).Times(1) + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).MaxTimes(1) return nil } diff --git a/x/finality/types/keys.go b/x/finality/types/keys.go index b949e2271..0a5ce2e40 100644 --- a/x/finality/types/keys.go +++ b/x/finality/types/keys.go @@ -15,11 +15,12 @@ const ( ) var ( - BlockKey = []byte{0x01} // key prefix for blocks - VoteKey = []byte{0x02} // key prefix for votes - PubRandKey = []byte{0x03} // key prefix for public randomness - ParamsKey = []byte{0x04} // key prefix for the parameters - EvidenceKey = []byte{0x05} // key prefix for evidences + BlockKey = []byte{0x01} // key prefix for blocks + VoteKey = []byte{0x02} // key prefix for votes + PubRandKey = []byte{0x03} // key prefix for public randomness + ParamsKey = []byte{0x04} // key prefix for the parameters + EvidenceKey = []byte{0x05} // key prefix for evidences + NextHeightToFinalizeKey = []byte{0x06} // key prefix for next height to finalise ) func KeyPrefix(p string) []byte { From 9c29ee854685e2635ae29ea00c23fcd6ba79237e Mon Sep 17 00:00:00 2001 From: EdwardFanfan <150119061+EdwardFanfan@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:37:14 +0800 Subject: [PATCH 095/202] README: Add medium link (#400) Co-authored-by: edwardhorp --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e853020a2..3e8dc4656 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Bringing Bitcoin security to Cosmos and beyond. [![Whitepaper](https://badgen.net/badge/icon/whitepaper?label=)](https://arxiv.org/abs/2207.08392) [![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/babylon_chain) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/babylonchain) +[![Medium](https://badgen.net/badge/icon/medium?icon=medium&label)](https://medium.com/babylonchain-io) ## Build and install From a680fcc5261fd872237328c5b3a129f5248f57c7 Mon Sep 17 00:00:00 2001 From: Cirrus Gai Date: Thu, 9 Nov 2023 16:33:55 +0800 Subject: [PATCH 096/202] chore: Rename jury to covenant (#99) --- btcstaking/staking.go | 38 +- btcstaking/staking_test.go | 26 +- cmd/babylond/cmd/flags.go | 10 +- cmd/babylond/cmd/genesis.go | 6 +- cmd/babylond/cmd/testnet.go | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 38 +- proto/babylon/btcstaking/v1/events.proto | 6 +- proto/babylon/btcstaking/v1/params.proto | 4 +- proto/babylon/btcstaking/v1/tx.proto | 34 +- test/e2e/btc_staking_e2e_test.go | 56 +-- .../configurer/chain/commands_btcstaking.go | 14 +- testutil/datagen/btcstaking.go | 24 +- x/btcstaking/client/cli/tx.go | 28 +- x/btcstaking/keeper/btc_delegators.go | 28 +- x/btcstaking/keeper/grpc_query.go | 2 +- x/btcstaking/keeper/grpc_query_test.go | 36 +- x/btcstaking/keeper/incentive_test.go | 6 +- x/btcstaking/keeper/msg_server.go | 88 ++-- x/btcstaking/keeper/msg_server_test.go | 106 ++--- .../keeper/voting_power_table_test.go | 6 +- x/btcstaking/types/btc_slashing_tx.go | 25 +- x/btcstaking/types/btc_slashing_tx_test.go | 10 +- x/btcstaking/types/btcstaking.go | 20 +- x/btcstaking/types/btcstaking.pb.go | 234 +++++----- x/btcstaking/types/btcstaking_test.go | 18 +- x/btcstaking/types/codec.go | 8 +- x/btcstaking/types/errors.go | 6 +- x/btcstaking/types/events.pb.go | 4 +- x/btcstaking/types/genesis_test.go | 4 +- x/btcstaking/types/msg.go | 20 +- x/btcstaking/types/params.go | 4 +- x/btcstaking/types/params.pb.go | 62 +-- x/btcstaking/types/tx.pb.go | 408 +++++++++--------- x/btcstaking/types/types.go | 4 +- 34 files changed, 693 insertions(+), 692 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 3b147824d..a658415e2 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -109,7 +109,7 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (int64, erro type StakingScriptData struct { StakerKey *btcec.PublicKey ValidatorKey *btcec.PublicKey - JuryKey *btcec.PublicKey + CovenantKey *btcec.PublicKey StakingTime uint16 } @@ -126,17 +126,17 @@ type StakingOutputInfo struct { func NewStakingScriptData( stakerKey, validatorKey, - juryKey *btcec.PublicKey, + covenantKey *btcec.PublicKey, stakingTime uint16) (*StakingScriptData, error) { - if stakerKey == nil || validatorKey == nil || juryKey == nil { - return nil, fmt.Errorf("staker, validator and jury keys cannot be nil") + if stakerKey == nil || validatorKey == nil || covenantKey == nil { + return nil, fmt.Errorf("staker, validator and covenant keys cannot be nil") } return &StakingScriptData{ StakerKey: stakerKey, ValidatorKey: validatorKey, - JuryKey: juryKey, + CovenantKey: covenantKey, StakingTime: stakingTime, }, nil } @@ -145,7 +145,7 @@ func NewStakingScriptData( // OP_CHECKSIG // OP_NOTIF // -// OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL +// OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL // // OP_ELSE // @@ -161,7 +161,7 @@ func (sd *StakingScriptData) BuildStakingScript() ([]byte, error) { builder.AddOp(txscript.OP_CHECKSIG) builder.AddData(schnorr.SerializePubKey(sd.ValidatorKey)) builder.AddOp(txscript.OP_CHECKSIGADD) - builder.AddData(schnorr.SerializePubKey(sd.JuryKey)) + builder.AddData(schnorr.SerializePubKey(sd.CovenantKey)) builder.AddOp(txscript.OP_CHECKSIGADD) builder.AddInt64(expectedMultiSigSigners) builder.AddOp(txscript.OP_NUMEQUAL) @@ -181,7 +181,7 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { // OP_CHECKSIG // OP_NOTIF // - // OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL + // OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL // // OP_ELSE // @@ -284,8 +284,8 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { return nil, err } - // Jury public key - juryPk, err := schnorr.ParsePubKey(template[7].extractedData) + // Covenant public key + covenantPk, err := schnorr.ParsePubKey(template[7].extractedData) if err != nil { return nil, err @@ -305,7 +305,7 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { scriptData, err := NewStakingScriptData( stakerPk1, validatorPk, - juryPk, + covenantPk, uint16(template[12].extractedInt), ) @@ -376,12 +376,12 @@ func BuildUnspendableTaprootPkScript(rawScript []byte, net *chaincfg.Params) ([] func BuildStakingOutput( stakerKey, validatorKey, - juryKey *btcec.PublicKey, + covenantKey *btcec.PublicKey, stTime uint16, stAmount btcutil.Amount, net *chaincfg.Params) (*wire.TxOut, []byte, error) { - sd, err := NewStakingScriptData(stakerKey, validatorKey, juryKey, stTime) + sd, err := NewStakingScriptData(stakerKey, validatorKey, covenantKey, stTime) if err != nil { return nil, nil, err @@ -705,39 +705,39 @@ func CheckTransactions( return nil, fmt.Errorf("slashing transaction min fee must be larger than 0") } - //1. Check slashing tx + // 1. Check slashing tx if err := IsSlashingTx(slashingTx, slashingAddress); err != nil { return nil, err } - //2. Check staking script. + // 2. Check staking script. scriptData, err := ParseStakingTransactionScript(script) if err != nil { return nil, err } - //3. Check that staking transaction has output committing to the provided script + // 3. Check that staking transaction has output committing to the provided script stakingOutputIdx, err := GetIdxOutputCommitingToScript(fundingTransaction, script, net) if err != nil { return nil, err } - //4. Check that slashing transaction input is pointing to staking transaction + // 4. Check that slashing transaction input is pointing to staking transaction stakingTxHash := fundingTransaction.TxHash() if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { return nil, fmt.Errorf("slashing transaction must spend staking output") } - //5. Check that index of the fund output matches index of the input in slashing transaction + // 5. Check that index of the fund output matches index of the input in slashing transaction if slashingTx.TxIn[0].PreviousOutPoint.Index != uint32(stakingOutputIdx) { return nil, fmt.Errorf("slashing transaction input must spend staking output") } stakingOutput := fundingTransaction.TxOut[stakingOutputIdx] - //6. Check fees + // 6. Check fees if slashingTx.TxOut[0].Value <= 0 || stakingOutput.Value <= 0 { return nil, fmt.Errorf("values of slashing and staking transaction must be larger than 0") } diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 2ffb05e25..c66537d84 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -22,14 +22,14 @@ import ( func genValidStakingScriptData(t *testing.T, r *rand.Rand) *btcstaking.StakingScriptData { stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) - juryPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + covenantPrivKeyBytes := datagen.GenRandomByteArray(r, 32) stakingTime := uint16(r.Intn(math.MaxUint16)) _, stakerPublicKey := btcec.PrivKeyFromBytes(stakerPrivKeyBytes) _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) - _, juryPublicKey := btcec.PrivKeyFromBytes(juryPrivKeyBytes) + _, covenantPublicKey := btcec.PrivKeyFromBytes(covenantPrivKeyBytes) - sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, juryPublicKey, stakingTime) + sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, covenantPublicKey, stakingTime) return sd } @@ -49,7 +49,7 @@ func FuzzGeneratingParsingValidStakingScript(f *testing.F) { require.Equal(t, parsedScript.StakingTime, sd.StakingTime) require.Equal(t, schnorr.SerializePubKey(sd.StakerKey), schnorr.SerializePubKey(parsedScript.StakerKey)) require.Equal(t, schnorr.SerializePubKey(sd.ValidatorKey), schnorr.SerializePubKey(parsedScript.ValidatorKey)) - require.Equal(t, schnorr.SerializePubKey(sd.JuryKey), schnorr.SerializePubKey(parsedScript.JuryKey)) + require.Equal(t, schnorr.SerializePubKey(sd.CovenantKey), schnorr.SerializePubKey(parsedScript.CovenantKey)) }) } @@ -76,7 +76,7 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { stakingOutput, _, err := btcstaking.BuildStakingOutput( sd.StakerKey, sd.ValidatorKey, - sd.JuryKey, + sd.CovenantKey, sd.StakingTime, btcutil.Amount(r.Intn(5000)+minStakingValue), &chaincfg.MainNetParams, @@ -196,7 +196,7 @@ func TestStakingScriptExecutionSingleStaker(t *testing.T) { validatorPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) - juryPrivKey, err := btcec.NewPrivateKey() + covenantPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) @@ -210,7 +210,7 @@ func TestStakingScriptExecutionSingleStaker(t *testing.T) { stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerPrivKey.PubKey(), validatorPrivKey.PubKey(), - juryPrivKey.PubKey(), + covenantPrivKey.PubKey(), stakingTimeBlocks, stakingValue, &chaincfg.MainNetParams, @@ -272,7 +272,7 @@ func TestStakingScriptExecutionMulitSig(t *testing.T) { validatorPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) - juryPrivKey, err := btcec.NewPrivateKey() + covenantPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) @@ -286,7 +286,7 @@ func TestStakingScriptExecutionMulitSig(t *testing.T) { stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerPrivKey.PubKey(), validatorPrivKey.PubKey(), - juryPrivKey.PubKey(), + covenantPrivKey.PubKey(), stakingTimeBlocks, stakingValue, &chaincfg.MainNetParams, @@ -322,17 +322,17 @@ func TestStakingScriptExecutionMulitSig(t *testing.T) { require.NoError(t, err) - witnessJury, err := btcstaking.BuildWitnessToSpendStakingOutput( + witnessCovenant, err := btcstaking.BuildWitnessToSpendStakingOutput( spendStakeTx, stakingOutput, stakingScript, - juryPrivKey, + covenantPrivKey, ) require.NoError(t, err) // To Construct valid witness, for multisig case we need: - // - jury signature - witnessJury[0] + // - covenant signature - witnessCovenant[0] // - validator signature - witnessValidator[0] // - staker signature - witnessStaker[0] // - empty signature - which is just an empty byte array which signals we are going to use multisig. @@ -340,7 +340,7 @@ func TestStakingScriptExecutionMulitSig(t *testing.T) { // - whole script - witnessStaker[1] (any other wittness[1] will work as well) // - control block - witnessStaker[2] (any other wittness[2] will work as well) spendStakeTx.TxIn[0].Witness = [][]byte{ - witnessJury[0], witnessValidator[0], witnessStaker[0], []byte{}, witnessStaker[1], witnessStaker[2], + witnessCovenant[0], witnessValidator[0], witnessStaker[0], []byte{}, witnessStaker[1], witnessStaker[2], } prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index b282f1582..8e63aa2cb 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -28,7 +28,7 @@ const ( flagBlocksPerYear = "blocks-per-year" flagGenesisTime = "genesis-time" flagBlockGasLimit = "block-gas-limit" - flagJuryPk = "jury-pk" + flagCovenantPk = "covenant-pk" flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" flagSlashingRate = "slashing-rate" @@ -52,7 +52,7 @@ type GenesisCLIArgs struct { BlocksPerYear uint64 GenesisTime time.Time BlockGasLimit int64 - JuryPK string + CovenantPK string SlashingAddress string MinSlashingTransactionFeeSat int64 SlashingRate sdk.Dec @@ -75,7 +75,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") // btcstaking args - cmd.Flags().String(flagJuryPk, btcstypes.DefaultParams().JuryPk.MarshalHex(), "Bitcoin staking jury public key") + cmd.Flags().String(flagCovenantPk, btcstypes.DefaultParams().CovenantPk.MarshalHex(), "Bitcoin staking covenant public key") cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") @@ -103,7 +103,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { epochInterval, _ := cmd.Flags().GetUint64(flagEpochInterval) baseBtcHeaderHex, _ := cmd.Flags().GetString(flagBaseBtcHeaderHex) baseBtcHeaderHeight, _ := cmd.Flags().GetUint64(flagBaseBtcHeaderHeight) - juryPk, _ := cmd.Flags().GetString(flagJuryPk) + covenantPk, _ := cmd.Flags().GetString(flagCovenantPk) slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) @@ -132,7 +132,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { EpochInterval: epochInterval, BaseBtcHeaderHeight: baseBtcHeaderHeight, BaseBtcHeaderHex: baseBtcHeaderHex, - JuryPK: juryPk, + CovenantPK: covenantPk, SlashingAddress: slashingAddress, MinSlashingTransactionFeeSat: minSlashingFee, MinCommissionRate: sdk.MustNewDecFromStr(minCommissionRate), diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 6a6e43047..b1a0b08df 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -68,7 +68,7 @@ Example: genesisParams = TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.JuryPK, + genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, @@ -230,7 +230,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, - baseBtcHeaderHeight uint64, juryPk string, slashingAddress string, minSlashingFee int64, + baseBtcHeaderHeight uint64, covenantPk string, slashingAddress string, minSlashingFee int64, minCommissionRate sdk.Dec, slashingRate sdk.Dec, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -305,7 +305,7 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtclightclientBaseBtcHeader = *baseBtcHeaderInfo genParams.BtcstakingParams = btcstakingtypes.DefaultParams() - genParams.BtcstakingParams.JuryPk, err = bbn.NewBIP340PubKeyFromHex(juryPk) + genParams.BtcstakingParams.CovenantPk, err = bbn.NewBIP340PubKeyFromHex(covenantPk) if err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 4c72beaca..b172c13c1 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -98,7 +98,7 @@ Example: genesisParams := TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, - genesisCliArgs.JuryPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, + genesisCliArgs.CovenantPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 8836cc5ef..3bb7e7103 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -80,16 +80,16 @@ message BTCDelegation { BabylonBTCTaprootTx staking_tx = 8; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. + // validator or covenant yet. bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // jury_sig is the signature signature on the slashing tx - // by the jury (i.e., SK corresponding to jury_pk in params) + // covenant_sig is the signature signature on the slashing tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. - bytes jury_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. @@ -106,23 +106,23 @@ message BTCUndelegation { BabylonBTCTaprootTx unbonding_tx = 1; // slashing_tx is the slashing tx for unbodning transactions // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. + // validator or covenant yet. bytes slashing_tx = 2 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbodning tx output. bytes delegator_slashing_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // jury_slashing_sig is the signature on the slashing tx - // by the jury (i.e., SK corresponding to jury_pk in params) + // covenant_slashing_sig is the signature on the slashing tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon - bytes jury_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // jury_unbonding_sig is the signature on the unbonding tx - // by the jury (i.e., SK corresponding to jury_pk in params) + bytes covenant_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // covenant_unbonding_sig is the signature on the unbonding tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator - bytes jury_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to jury_pk in params) + // by the validator (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } @@ -141,10 +141,10 @@ message BTCUndelegationInfo { // It will be nil if validator didn't sign it yet bytes validator_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // jury_unbonding_sig is the signature on the unbonding tx - // by the jury (i.e., SK corresponding to jury_pk in params) - // it will be nil if jury didn't sign it yet - bytes jury_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // covenant_unbonding_sig is the signature on the unbonding tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) + // it will be nil if covenant didn't sign it yet + bytes covenant_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. @@ -173,17 +173,17 @@ message BabylonBTCTaprootTx { // 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through // MsgBTCUndelegate message. enum BTCDelegationStatus { - // PENDING defines a delegation that is waiting for a jury signature to become active. + // PENDING defines a delegation that is waiting for a covenant signature to become active. PENDING = 0; // ACTIVE defines a delegation that has voting power ACTIVE = 1; // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx - // from staker, but not yet received signatures from validator and jury. + // from staker, but not yet received signatures from validator and covenant. // Delegation in this state already lost its voting power. UNBONDING = 2; // UNBONDED defines a delegation no longer has voting power: // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx and then receiving signatures from validator and jury for this + // - or receiving unbonding tx and then receiving signatures from validator and covenant for this // unbonding tx. UNBONDED = 3; // ANY is any of the above status diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto index eb636aacc..ef1117d46 100644 --- a/proto/babylon/btcstaking/v1/events.proto +++ b/proto/babylon/btcstaking/v1/events.proto @@ -11,10 +11,10 @@ message EventNewBTCValidator { BTCValidator btc_val = 1; } // EventNewBTCDelegation is the event emitted when a BTC delegation is created // NOTE: the BTC delegation is not active thus does not have voting power yet -// only after it receives a jury signature it becomes activated and has voting power +// only after it receives a covenant signature it becomes activated and has voting power message EventNewBTCDelegation { BTCDelegation btc_del = 1; } -// EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation +// EventActivateBTCDelegation is the event emitted when covenant activates a BTC delegation // such that the BTC delegation starts to have voting power in its timelock period message EventActivateBTCDelegation { BTCDelegation btc_del = 1; } @@ -52,4 +52,4 @@ message EventUnbondedBTCDelegation { string unbonding_tx_hash = 4; // from_state is the last state the BTC delegation was at BTCDelegationStatus from_state = 5; -} \ No newline at end of file +} diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 970b91d1c..b2a97f4fb 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -10,9 +10,9 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; message Params { option (gogoproto.goproto_stringer) = false; - // jury_pk is the public key of jury + // covenant_pk is the public key of covenant // the PK follows encoding in BIP-340 spec on Bitcoin - bytes jury_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + bytes covenant_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin string slashing_address = 2; diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 727194485..035dbadb7 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -21,12 +21,12 @@ service Msg { rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); // BtcUndelegate undelegates funds from exsitng btc delegation rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); - // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation - rpc AddJurySig(MsgAddJurySig) returns (MsgAddJurySigResponse); - // AddJuryUnbondingSigs handles two signatures from jury for: + // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation + rpc AddCovenantSig(MsgAddCovenantSig) returns (MsgAddCovenantSigResponse); + // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker - rpc AddJuryUnbondingSigs(MsgAddJuryUnbondingSigs) returns (MsgAddJuryUnbondingSigsResponse); + rpc AddCovenantUnbondingSigs(MsgAddCovenantUnbondingSigs) returns (MsgAddCovenantUnbondingSigsResponse); // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker rpc AddValidatorUnbondingSig(MsgAddValidatorUnbondingSig) returns (MsgAddValidatorUnbondingSigResponse); // UpdateParams updates the btcstaking module parameters. @@ -71,7 +71,7 @@ message MsgCreateBTCDelegation { bytes slashing_tx = 6 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - // The staking tx output further needs signatures from jury and validator in + // The staking tx output further needs signatures from covenant and validator in // order to be spendable. bytes delegator_sig = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } @@ -95,8 +95,8 @@ message MsgBTCUndelegate { // MsgBtcUndelegateResponse is the response for MsgBtcUndelegate message MsgBTCUndelegateResponse {} -// MsgAddJurySig is the message for handling a signature from jury -message MsgAddJurySig { +// MsgAddCovenantSig is the message for handling a signature from covenant +message MsgAddCovenantSig { string signer = 1; // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec @@ -107,12 +107,12 @@ message MsgAddJurySig { // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 4; - // sig is the signature of the jury + // sig is the signature of the covenant // the signature follows encoding in BIP-340 spec bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// MsgAddJurySigResponse is the response for MsgAddJurySig -message MsgAddJurySigResponse {} +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig +message MsgAddCovenantSigResponse {} // MsgUpdateParams defines a message for updating btcstaking module parameters. message MsgUpdateParams { @@ -133,8 +133,8 @@ message MsgUpdateParams { // MsgUpdateParamsResponse is the response to the MsgUpdateParams message. message MsgUpdateParamsResponse {} -// MsgAddJuryUnbondingSigs is the message for handling a signature from jury -message MsgAddJuryUnbondingSigs { +// MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant +message MsgAddCovenantUnbondingSigs { string signer = 1; // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec @@ -145,16 +145,16 @@ message MsgAddJuryUnbondingSigs { // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 4; - // unbonding_tx_sig is the signature of the jury on the unbonding tx submitted to babylon + // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // slashing_unbonding_tx_sig is the signature of the jury on slashing tx corresponding to unbodning tx submitted to babylon + // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon // the signature follows encoding in BIP-340 spec bytes slashing_unbonding_tx_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// MsgAddJurySigResponse is the response for MsgAddJurySig -message MsgAddJuryUnbondingSigsResponse {} +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig +message MsgAddCovenantUnbondingSigsResponse {} // MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker message MsgAddValidatorUnbondingSig { @@ -172,5 +172,5 @@ message MsgAddValidatorUnbondingSig { // the signature follows encoding in BIP-340 spec bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } -// MsgAddJurySigResponse is the response for MsgAddJurySig +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddValidatorUnbondingSigResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 41f027c6e..f65962ccc 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -34,8 +34,8 @@ var ( btcVal *bstypes.BTCValidator // BTC delegation delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) - // jury - jurySK, _ = btcec.PrivKeyFromBytes( + // covenant + covenantSK, _ = btcec.PrivKeyFromBytes( []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, ) @@ -109,7 +109,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { net, delBTCSK, btcVal.BtcPk.MustToBTCPK(), - params.JuryPk.MustToBTCPK(), + params.CovenantPk.MustToBTCPK(), stakingTimeBlocks, stakingValue, params.SlashingAddress, @@ -148,7 +148,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { pendingDels := pendingDelSet[0] s.Len(pendingDels.Dels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels.Dels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) - s.Nil(pendingDels.Dels[0].JurySig) + s.Nil(pendingDels.Dels[0].CovenantSig) // check delegation delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHashStr()) @@ -157,9 +157,9 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.Equal(expectedScript, delegation.StakingScript) } -// Test2SubmitJurySignature is an end-to-end test for user -// story 2: jury approves the BTC delegation -func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { +// Test2SubmitCovenantSignature is an end-to-end test for user +// story 2: covenant approves the BTC delegation +func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) @@ -171,7 +171,7 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { pendingDels := pendingDelsSet[0] s.Len(pendingDels.Dels, 1) pendingDel := pendingDels.Dels[0] - s.Nil(pendingDel.JurySig) + s.Nil(pendingDel.CovenantSig) slashingTx := pendingDel.SlashingTx stakingTx := pendingDel.StakingTx @@ -180,27 +180,27 @@ func (s *BTCStakingTestSuite) Test2SubmitJurySignature() { stakingTxHash := stakingTx.MustGetTxHashStr() /* - generate and insert new jury signature, in order to activate the BTC delegation + generate and insert new covenant signature, in order to activate the BTC delegation */ - jurySig, err := slashingTx.Sign( + covenantSig, err := slashingTx.Sign( stakingMsgTx, stakingTx.Script, - jurySK, + covenantSK, net, ) s.NoError(err) - nonValidatorNode.AddJurySig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, jurySig) + nonValidatorNode.AddCovenantSig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, covenantSig) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - // ensure the BTC delegation has jury sig now + // ensure the BTC delegation has covenant sig now activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) s.Len(activeDelsSet, 1) activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) activeDel := activeDels.Dels[0] - s.NotNil(activeDel.JurySig) + s.NotNil(activeDel.CovenantSig) // wait for a block so that above txs take effect and the voting power table // is updated in the next block's BeginBlock @@ -379,9 +379,9 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) activeDel := activeDels.Dels[0] - s.NotNil(activeDel.JurySig) + s.NotNil(activeDel.CovenantSig) - // params for juryPk and slashing address + // params for covenantPk and slashing address params := nonValidatorNode.QueryBTCStakingParams() stakingTx := activeDel.StakingTx @@ -402,7 +402,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { net, delBTCSK, btcVal.BtcPk.MustToBTCPK(), - params.JuryPk.MustToBTCPK(), + params.CovenantPk.MustToBTCPK(), wire.NewOutPoint(stakingTxChainHash, uint32(stakingOutputIdx)), initialization.BabylonBtcFinalizationPeriod+1, stakingValue-fee, @@ -433,7 +433,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { s.NotNil(delegation.BtcUndelegation) } -// Test6SubmitStakerUnbonding is an end-to-end test for jury and validator submitting signatures +// Test6SubmitStakerUnbonding is an end-to-end test for covenant and validator submitting signatures // for unbonding transaction func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { chainA := s.configurer.GetChainConfig(0) @@ -451,8 +451,8 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { s.NotNil(delegation.BtcUndelegation) s.Nil(delegation.BtcUndelegation.ValidatorUnbondingSig) - s.Nil(delegation.BtcUndelegation.JuryUnbondingSig) - s.Nil(delegation.BtcUndelegation.JurySlashingSig) + s.Nil(delegation.BtcUndelegation.CovenantUnbondingSig) + s.Nil(delegation.BtcUndelegation.CovenantSlashingSig) // First sent validator signature stakingTxMsg, err := delegation.StakingTx.ToMsgTx() @@ -486,25 +486,25 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegationWithValSig.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), ) - // Next send jury signatures - juryUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( + // Next send covenant signatures + covenantUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( stakingTxMsg, delegation.StakingTx.Script, - jurySK, + covenantSK, net, ) s.NoError(err) unbondingTxMsg, err := delegation.BtcUndelegation.UnbondingTx.ToMsgTx() s.NoError(err) - jurySlashingSig, err := delegation.BtcUndelegation.SlashingTx.Sign( + covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.Sign( unbondingTxMsg, delegation.BtcUndelegation.UnbondingTx.Script, - jurySK, + covenantSK, net, ) s.NoError(err) - nonValidatorNode.AddJuryUnbondingSigs(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, juryUnbondingSig, jurySlashingSig) + nonValidatorNode.AddCovenantUnbondingSigs(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, covenantUnbondingSig, covenantSlashingSig) nonValidatorNode.WaitForNextBlock() // Check all signatures are properly registered @@ -513,8 +513,8 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegationWithSigs := allDelegationsWithSigs[0].Dels[0] s.NotNil(delegationWithSigs.BtcUndelegation) s.NotNil(delegationWithSigs.BtcUndelegation.ValidatorUnbondingSig) - s.NotNil(delegationWithSigs.BtcUndelegation.JuryUnbondingSig) - s.NotNil(delegationWithSigs.BtcUndelegation.JurySlashingSig) + s.NotNil(delegationWithSigs.BtcUndelegation.CovenantUnbondingSig) + s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSig) btcTip, err = nonValidatorNode.QueryTip() s.NoError(err) s.Equal( diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 282309e3a..33d1de0b6 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -60,17 +60,17 @@ func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstyp n.LogActionF("successfully created BTC delegation") } -func (n *NodeConfig) AddJurySig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { - n.LogActionF("adding jury signature") +func (n *NodeConfig) AddCovenantSig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { + n.LogActionF("adding covenant signature") valPKHex := valPK.MarshalHex() delPKHex := delPK.MarshalHex() sigHex := sig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "add-jury-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - n.LogActionF("successfully added jury sig") + n.LogActionF("successfully added covenant sig") } func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand, sig *bbn.BIP340Signature) { @@ -150,7 +150,7 @@ func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bb n.LogActionF("successfully added validator unbonding sig") } -func (n *NodeConfig) AddJuryUnbondingSigs( +func (n *NodeConfig) AddCovenantUnbondingSigs( valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, @@ -163,8 +163,8 @@ func (n *NodeConfig) AddJuryUnbondingSigs( unbondingTxSigHex := unbondingTxSig.ToHexStr() slashUnbondingTxSigHex := slashUnbondingTxSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "add-jury-unbonding-sigs", valPKHex, delPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", valPKHex, delPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - n.LogActionF("successfully added jury unbonding sigs") + n.LogActionF("successfully added covenant unbonding sigs") } diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index ed3d97382..fafe8f32a 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -63,11 +63,11 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe }, nil } -func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btcec.PrivateKey, jurySK *btcec.PrivateKey, slashingAddr string, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { +func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btcec.PrivateKey, covenantSK *btcec.PrivateKey, slashingAddr string, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { net := &chaincfg.SimNetParams delPK := delSK.PubKey() delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) - juryBTCPK := jurySK.PubKey() + covenantBTCPK := covenantSK.PubKey() valPK, err := valBTCPK.ToBTCPK() if err != nil { return nil, err @@ -88,17 +88,17 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc return nil, err } // staking/slashing tx - stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, net, delSK, valPK, juryBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) + stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, net, delSK, valPK, covenantBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) if err != nil { return nil, err } - // jury sig and delegator sig + // covenant sig and delegator sig stakingMsgTx, err := stakingTx.ToMsgTx() if err != nil { return nil, err } - jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, jurySK, net) + covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSK, net) if err != nil { return nil, err } @@ -116,7 +116,7 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc EndHeight: endHeight, TotalSat: totalSat, DelegatorSig: delegatorSig, - JurySig: jurySig, + CovenantSig: covenantSig, StakingTx: stakingTx, SlashingTx: slashingTx, }, nil @@ -128,7 +128,7 @@ func GenBTCStakingSlashingTxWithOutPoint( outPoint *wire.OutPoint, stakerSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, - juryPK *btcec.PublicKey, + covenantPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string, @@ -137,7 +137,7 @@ func GenBTCStakingSlashingTxWithOutPoint( stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerSK.PubKey(), validatorPK, - juryPK, + covenantPK, stakingTimeBlocks, btcutil.Amount(stakingValue), btcNet, @@ -198,7 +198,7 @@ func GenBTCStakingSlashingTx( btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, - juryPK *btcec.PublicKey, + covenantPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string, @@ -206,7 +206,7 @@ func GenBTCStakingSlashingTx( // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) outPoint := &spend.prevOut - return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress, true) + return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddress, true) } func GenBTCUnbondingSlashingTx( @@ -214,11 +214,11 @@ func GenBTCUnbondingSlashingTx( btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, - juryPK *btcec.PublicKey, + covenantPK *btcec.PublicKey, stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, slashingAddress string, ) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { - return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, stakingTransactionOutpoint, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddress, false) + return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, stakingTransactionOutpoint, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddress, false) } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 59dfe3772..09464e8bf 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -39,9 +39,9 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand( NewCreateBTCValidatorCmd(), NewCreateBTCDelegationCmd(), - NewAddJurySigCmd(), + NewAddCovenantSigCmd(), NewCreateBTCUndelegationCmd(), - NewAddJuryUnbondingSigsCmd(), + NewAddCovenantUnbondingSigsCmd(), NewAddValidatorUnbondingSigCmd(), ) @@ -205,13 +205,13 @@ func NewCreateBTCDelegationCmd() *cobra.Command { return cmd } -func NewAddJurySigCmd() *cobra.Command { +func NewAddCovenantSigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-jury-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", + Use: "add-covenant-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", Args: cobra.ExactArgs(4), - Short: "Add a jury signature", + Short: "Add a covenant signature", Long: strings.TrimSpace( - `Add a jury signature.`, // TODO: example + `Add a covenant signature.`, // TODO: example ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -234,13 +234,13 @@ func NewAddJurySigCmd() *cobra.Command { // get staking tx hash stakingTxHash := args[2] - // get jury sigature + // get covenant sigature sig, err := bbn.NewBIP340SignatureFromHex(args[3]) if err != nil { return err } - msg := types.MsgAddJurySig{ + msg := types.MsgAddCovenantSig{ Signer: clientCtx.FromAddress.String(), ValPk: valPK, DelPk: delPK, @@ -305,11 +305,11 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { return cmd } -func NewAddJuryUnbondingSigsCmd() *cobra.Command { +func NewAddCovenantUnbondingSigsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-jury-unbonding-sigs [val_pk] [del_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", + Use: "add-covenant-unbonding-sigs [val_pk] [del_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", Args: cobra.ExactArgs(5), - Short: "Add jury signatures for unbonding tx and slash unbonding tx", + Short: "Add covenant signatures for unbonding tx and slash unbonding tx", RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { @@ -331,19 +331,19 @@ func NewAddJuryUnbondingSigsCmd() *cobra.Command { // get staking tx hash stakingTxHash := args[2] - // get jury sigature for unbonding tx + // get covenant sigature for unbonding tx unbondingSig, err := bbn.NewBIP340SignatureFromHex(args[3]) if err != nil { return err } - // get jury sigature for slash unbonding tx + // get covenant sigature for slash unbonding tx slashUnbondingSig, err := bbn.NewBIP340SignatureFromHex(args[4]) if err != nil { return err } - msg := types.MsgAddJuryUnbondingSigs{ + msg := types.MsgAddCovenantUnbondingSigs{ Signer: clientCtx.FromAddress.String(), ValPk: valPK, DelPk: delPK, diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index 7065b54b3..bb5eab287 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -70,18 +70,18 @@ func (k Keeper) updateBTCDelegation( return nil } -// AddJurySigToBTCDelegation adds a given jury sig to a BTC delegation +// AddCovenantSigToBTCDelegation adds a given covenant sig to a BTC delegation // with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddJurySigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, jurySig *bbn.BIP340Signature) error { - addJurySig := func(btcDel *types.BTCDelegation) error { - if btcDel.JurySig != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has a jury signature", stakingTxHash) +func (k Keeper) AddCovenantSigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, covenantSig *bbn.BIP340Signature) error { + addCovenantSig := func(btcDel *types.BTCDelegation) error { + if btcDel.CovenantSig != nil { + return fmt.Errorf("the BTC delegation with staking tx hash %s already has a covenant signature", stakingTxHash) } - btcDel.JurySig = jurySig + btcDel.CovenantSig = covenantSig return nil } - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addJurySig) + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addCovenantSig) } func (k Keeper) AddUndelegationToBTCDelegation( @@ -125,7 +125,7 @@ func (k Keeper) AddValidatorSigToUndelegation( return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addValidatorSig) } -func (k Keeper) AddJurySigsToUndelegation( +func (k Keeper) AddCovenantSigsToUndelegation( ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, @@ -133,21 +133,21 @@ func (k Keeper) AddJurySigsToUndelegation( unbondingTxSig *bbn.BIP340Signature, slashUnbondingTxSig *bbn.BIP340Signature, ) error { - addJurySigs := func(btcDel *types.BTCDelegation) error { + addCovenantSigs := func(btcDel *types.BTCDelegation) error { if btcDel.BtcUndelegation == nil { return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) } - if btcDel.BtcUndelegation.JuryUnbondingSig != nil || btcDel.BtcUndelegation.JurySlashingSig != nil { - return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid jury signatures", stakingTxHash) + if btcDel.BtcUndelegation.CovenantUnbondingSig != nil || btcDel.BtcUndelegation.CovenantSlashingSig != nil { + return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid covenant signatures", stakingTxHash) } - btcDel.BtcUndelegation.JuryUnbondingSig = unbondingTxSig - btcDel.BtcUndelegation.JurySlashingSig = slashUnbondingTxSig + btcDel.BtcUndelegation.CovenantUnbondingSig = unbondingTxSig + btcDel.BtcUndelegation.CovenantSlashingSig = slashUnbondingTxSig return nil } - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addJurySigs) + return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addCovenantSigs) } // hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index d19324e23..262cda22a 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -284,7 +284,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation undelegationInfo = &types.BTCUndelegationInfo{ UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, ValidatorUnbondingSig: btcDel.BtcUndelegation.ValidatorUnbondingSig, - JuryUnbondingSig: btcDel.BtcUndelegation.JuryUnbondingSig, + CovenantUnbondingSig: btcDel.BtcUndelegation.CovenantUnbondingSig, } } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 26e5843df..442fd6c30 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -171,8 +171,8 @@ func FuzzPendingBTCDelegations(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -196,11 +196,11 @@ func FuzzPendingBTCDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { - // remove jury sig in random BTC delegations to make them inactive - btcDel.JurySig = nil + // remove covenant sig in random BTC delegations to make them inactive + btcDel.CovenantSig = nil pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } err = keeper.AddBTCDelegation(ctx, btcDel) @@ -259,8 +259,8 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -284,20 +284,20 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { - // add unbonding object in random BTC delegations to make them ready to receive jury sig + // add unbonding object in random BTC delegations to make them ready to receive covenant sig btcDel.BtcUndelegation = &types.BTCUndelegation{ // doesn't matter what we put here - ValidatorUnbondingSig: btcDel.JurySig, + ValidatorUnbondingSig: btcDel.CovenantSig, } if datagen.RandomInt(r, 2) == 1 { // these BTC delegations are unbonded - btcDel.BtcUndelegation.JuryUnbondingSig = btcDel.JurySig - btcDel.BtcUndelegation.JurySlashingSig = btcDel.JurySig + btcDel.BtcUndelegation.CovenantUnbondingSig = btcDel.CovenantSig + btcDel.BtcUndelegation.CovenantSlashingSig = btcDel.CovenantSig } else { // these BTC delegations are unbonding unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel @@ -420,8 +420,8 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { // Setup keeper and context keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -449,7 +449,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, 10000) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, 10000) // timelock period: 1-1000 require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -518,8 +518,8 @@ func FuzzBTCValidatorDelegations(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -537,7 +537,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, jurySK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) require.NoError(t, err) expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel err = keeper.AddBTCDelegation(ctx, btcDel) diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 914bb1886..5e312c6c3 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -28,8 +28,8 @@ func FuzzRecordRewardDistCache(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -56,7 +56,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index e8709c9c0..9e12603ca 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -125,7 +125,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.StakerKey) valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) - juryPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.JuryKey) + covenantPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.CovenantKey) // verify proof of possession if err := req.Pop.Verify(req.BabylonPk, delBTCPK, ms.btcNet); err != nil { @@ -154,10 +154,10 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } } - // ensure staking tx is using correct jury PK - paramJuryPK := params.JuryPk - if !juryPK.Equals(paramJuryPK) { - return nil, types.ErrInvalidJuryPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramJuryPK), hex.EncodeToString(*juryPK)) + // ensure staking tx is using correct covenant PK + paramCovenantPK := params.CovenantPk + if !covenantPK.Equals(paramCovenantPK) { + return nil, types.ErrInvalidCovenantPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramCovenantPK), hex.EncodeToString(*covenantPK)) } // get startheight and endheight of the timelock @@ -219,7 +219,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // all good, construct BTCDelegation and insert BTC delegation // NOTE: the BTC delegation does not have voting power yet. It will // have voting power only when 1) its corresponding staking tx is k-deep, - // and 2) it receives a jury signature + // and 2) it receives a covenant signature newBTCDel := &types.BTCDelegation{ BabylonPk: req.BabylonPk, BtcPk: delBTCPK, @@ -231,7 +231,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre StakingTx: req.StakingTx, SlashingTx: req.SlashingTx, DelegatorSig: req.DelegatorSig, - JurySig: nil, // NOTE: jury signature will be submitted in a separate msg by jury + CovenantSig: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant BtcUndelegation: nil, } if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { @@ -305,13 +305,13 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time must be larger than finalization time") } - // 5. Check Jury Key from script is consistent with params + // 5. Check Covenant Key from script is consistent with params publicKeyInfos := types.KeyDataFromScript(unbondingOutputInfo.StakingScriptData) - if !publicKeyInfos.JuryKey.Equals(params.JuryPk) { - return nil, types.ErrInvalidJuryPK.Wrapf( + if !publicKeyInfos.CovenantKey.Equals(params.CovenantPk) { + return nil, types.ErrInvalidCovenantPK.Wrapf( "expected: %s; actual: %s", - hex.EncodeToString(*params.JuryPk), - hex.EncodeToString(*publicKeyInfos.JuryKey), + hex.EncodeToString(*params.CovenantPk), + hex.EncodeToString(*publicKeyInfos.CovenantKey), ) } @@ -348,7 +348,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // Given that unbonding tx must not be replacable and we do not allow sending it second time, it places // burden on staker to choose right fee. // Unbonding tx should not be replaceable at babylon level (and by extension on btc level), as this would - // allow staker to spam the network with unbonding txs, which would force jury and validator to send signatures. + // allow staker to spam the network with unbonding txs, which would force covenant and validator to send signatures. return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx fee must be larger that 0") } @@ -356,12 +356,12 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele UnbondingTx: req.UnbondingTx, SlashingTx: req.SlashingTx, DelegatorSlashingSig: req.DelegatorSlashingSig, - // following objects needs to be filled by jury and validator + // following objects needs to be filled by covenant and validator // Jurry needs to provide two sigs: // - one for unbonding tx // - one for slashing tx of unbonding tx - JurySlashingSig: nil, - JuryUnbondingSig: nil, + CovenantSlashingSig: nil, + CovenantUnbondingSig: nil, ValidatorUnbondingSig: nil, } @@ -388,8 +388,8 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return &types.MsgBTCUndelegateResponse{}, nil } -// AddJurySig adds a signature from jury to a BTC delegation -func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) (*types.MsgAddJurySigResponse, error) { +// AddCovenantSig adds a signature from covenant to a BTC delegation +func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCovenantSig) (*types.MsgAddCovenantSigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // ensure BTC delegation exists @@ -397,8 +397,8 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) if err != nil { return nil, err } - if btcDel.HasJurySig() { - return nil, types.ErrDuplicatedJurySig + if btcDel.HasCovenantSig() { + return nil, types.ErrDuplicatedCovenantSig } stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) @@ -407,26 +407,26 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) } - juryPK, err := ms.GetParams(ctx).JuryPk.ToBTCPK() + covenantPK, err := ms.GetParams(ctx).CovenantPk.ToBTCPK() if err != nil { - // failing to cast a verified jury PK a programming error - panic(fmt.Errorf("failed to cast a verified jury public key")) + // failing to cast a verified covenant PK a programming error + panic(fmt.Errorf("failed to cast a verified covenant public key")) } - // verify signature w.r.t. jury PK and signature + // verify signature w.r.t. covenant PK and signature err = btcDel.SlashingTx.VerifySignature( stakingOutputInfo.StakingPkScript, int64(stakingOutputInfo.StakingAmount), btcDel.StakingTx.Script, - juryPK, + covenantPK, req.Sig, ) if err != nil { - return nil, types.ErrInvalidJurySig.Wrap(err.Error()) + return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) } // all good, add signature to BTC delegation and set it back to KVStore - if err := ms.AddJurySigToBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.Sig); err != nil { + if err := ms.AddCovenantSigToBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.Sig); err != nil { panic("failed to set BTC delegation that has passed verification") } @@ -435,12 +435,12 @@ func (ms msgServer) AddJurySig(goCtx context.Context, req *types.MsgAddJurySig) panic(fmt.Errorf("failed to emit EventActivateBTCDelegation: %w", err)) } - return &types.MsgAddJurySigResponse{}, nil + return &types.MsgAddCovenantSigResponse{}, nil } -func (ms msgServer) AddJuryUnbondingSigs( +func (ms msgServer) AddCovenantUnbondingSigs( goCtx context.Context, - req *types.MsgAddJuryUnbondingSigs) (*types.MsgAddJuryUnbondingSigsResponse, error) { + req *types.MsgAddCovenantUnbondingSigs) (*types.MsgAddCovenantUnbondingSigsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout @@ -458,18 +458,18 @@ func (ms msgServer) AddJuryUnbondingSigs( return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) } - // 3. Check that we did not recevie jury signature yet - if btcDel.BtcUndelegation.HasJurySigs() { - return nil, types.ErrDuplicatedJurySig.Wrap("Jury signature for undelegation already received") + // 3. Check that we did not recevie covenant signature yet + if btcDel.BtcUndelegation.HasCovenantSigs() { + return nil, types.ErrDuplicatedCovenantSig.Wrap("Covenant signature for undelegation already received") } // 4. Check that we already received validator signature if !btcDel.BtcUndelegation.HasValidatorSig() { - // Jury should provide signature only after validator to avoid validator and staker + // Covenant should provide signature only after validator to avoid validator and staker // collusion i.e sending unbonding tx to btc without leaving validator signature on babylon chain. // TODO: Maybe it is worth accepting signatures and just emmiting some kind of warning event ? as if this msg // processing fails, it will still be included on babylon chain, so anybody could still retrieve - // all jury signatures included in msg. And with warning we will at least have some kind of + // all covenant signatures included in msg. And with warning we will at least have some kind of // indication that something is wrong. return nil, types.ErrUnbondingUnexpectedValidatorSig } @@ -481,10 +481,10 @@ func (ms msgServer) AddJuryUnbondingSigs( panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) } - juryPK, err := ms.GetParams(ctx).JuryPk.ToBTCPK() + covenantPK, err := ms.GetParams(ctx).CovenantPk.ToBTCPK() if err != nil { - // failing to cast a verified jury PK is a programming error - panic(fmt.Errorf("failed to cast a verified jury public key")) + // failing to cast a verified covenant PK is a programming error + panic(fmt.Errorf("failed to cast a verified covenant public key")) } // UnbondingTx has exactly one input and one output so we may re-use the same @@ -493,11 +493,11 @@ func (ms msgServer) AddJuryUnbondingSigs( stakingOutputInfo.StakingPkScript, int64(stakingOutputInfo.StakingAmount), btcDel.StakingTx.Script, - juryPK, + covenantPK, req.UnbondingTxSig, ) if err != nil { - return nil, types.ErrInvalidJurySig.Wrap(err.Error()) + return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) } // 5. Verify signature of slashing tx against unbonding tx output @@ -511,7 +511,7 @@ func (ms msgServer) AddJuryUnbondingSigs( unbondingOutputInfo.StakingPkScript, int64(unbondingOutputInfo.StakingAmount), btcDel.BtcUndelegation.UnbondingTx.Script, - juryPK, + covenantPK, req.SlashingUnbondingTxSig, ) if err != nil { @@ -519,7 +519,7 @@ func (ms msgServer) AddJuryUnbondingSigs( } // all good, add signature to BTC delegation and set it back to KVStore - if err := ms.AddJurySigsToUndelegation( + if err := ms.AddCovenantSigsToUndelegation( ctx, req.ValPk, req.DelPk, @@ -603,9 +603,9 @@ func (ms msgServer) AddValidatorUnbondingSig( panic("failed to set BTC delegation that has passed verification") } - // if the BTC undelegation has jury sigs, then after above operations the + // if the BTC undelegation has covenant sigs, then after above operations the // BTC delegation will become unbonded - if btcDel.BtcUndelegation.HasJurySigs() { + if btcDel.BtcUndelegation.HasCovenantSigs() { event := &types.EventUnbondedBTCDelegation{ BtcPk: btcDel.BtcPk, ValBtcPk: btcDel.ValBtcPk, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 22a29a839..58f5b7f19 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -84,26 +84,26 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { }) } -func getJuryInfo(t *testing.T, +func getCovenantInfo(t *testing.T, r *rand.Rand, goCtx context.Context, ms types.MsgServer, net *chaincfg.Params, bsKeeper *keeper.Keeper, sdkCtx sdk.Context) (*btcec.PrivateKey, *btcec.PublicKey, btcutil.Address) { - jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) + covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(sdkCtx, types.Params{ - JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), SlashingRate: sdk.MustNewDecFromStr("0.1"), }) require.NoError(t, err) - return jurySK, juryPK, slashingAddr + return covenantSK, covenantPK, slashingAddr } @@ -139,7 +139,7 @@ func createDelegation( btclcKeeper *types.MockBTCLightClientKeeper, net *chaincfg.Params, validatorPK *btcec.PublicKey, - juryPK *btcec.PublicKey, + covenantPK *btcec.PublicKey, slashingAddr string, stakingTime uint16, ) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { @@ -152,7 +152,7 @@ func createDelegation( net, delSK, validatorPK, - juryPK, + covenantPK, stakingTimeBlocks, stakingValue, slashingAddr, @@ -207,7 +207,7 @@ func createDelegation( return stakingTxHash, delSK, delPK, msgCreateBTCDel } -func createJurySig( +func createCovenantSig( t *testing.T, r *rand.Rand, goCtx context.Context, @@ -215,37 +215,37 @@ func createJurySig( bsKeeper *keeper.Keeper, sdkCtx sdk.Context, net *chaincfg.Params, - jurySK *btcec.PrivateKey, + covenantSK *btcec.PrivateKey, msgCreateBTCDel *types.MsgCreateBTCDelegation, delegation *types.BTCDelegation, ) { stakingMsgTx, err := msgCreateBTCDel.StakingTx.ToMsgTx() require.NoError(t, err) stakingTxHash := msgCreateBTCDel.StakingTx.MustGetTxHashStr() - jurySig, err := msgCreateBTCDel.SlashingTx.Sign( + covenantSig, err := msgCreateBTCDel.SlashingTx.Sign( stakingMsgTx, msgCreateBTCDel.StakingTx.Script, - jurySK, + covenantSK, net, ) require.NoError(t, err) - msgAddJurySig := &types.MsgAddJurySig{ + msgAddCovenantSig := &types.MsgAddCovenantSig{ Signer: msgCreateBTCDel.Signer, ValPk: delegation.ValBtcPk, DelPk: delegation.BtcPk, StakingTxHash: stakingTxHash, - Sig: jurySig, + Sig: covenantSig, } - _, err = ms.AddJurySig(goCtx, msgAddJurySig) + _, err = ms.AddCovenantSig(goCtx, msgAddCovenantSig) require.NoError(t, err) /* - ensure jury sig is added successfully + ensure covenant sig is added successfully */ - actualDelWithJurySig, err := bsKeeper.GetBTCDelegation(sdkCtx, delegation.ValBtcPk, delegation.BtcPk, stakingTxHash) + actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, delegation.ValBtcPk, delegation.BtcPk, stakingTxHash) require.NoError(t, err) - require.Equal(t, actualDelWithJurySig.JurySig.MustMarshal(), jurySig.MustMarshal()) - require.True(t, actualDelWithJurySig.HasJurySig()) + require.Equal(t, actualDelWithCovenantSig.CovenantSig.MustMarshal(), covenantSig.MustMarshal()) + require.True(t, actualDelWithCovenantSig.HasCovenantSig()) } func getDelegationAndCheckValues( @@ -268,8 +268,8 @@ func getDelegationAndCheckValues( // ensure the BTC delegation in DB is correctly formatted err = actualDel.ValidateBasic() require.NoError(t, err) - // delegation is not activated by jury yet - require.False(t, actualDel.HasJurySig()) + // delegation is not activated by covenant yet + require.False(t, actualDel.HasCovenantSig()) return actualDel } @@ -284,7 +284,7 @@ func createUndelegation( stakingTxHash string, delSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, - juryPK *btcec.PublicKey, + covenantPK *btcec.PublicKey, slashingAddr string, ) *types.MsgBTCUndelegate { stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) @@ -297,7 +297,7 @@ func createUndelegation( net, delSK, validatorPK, - juryPK, + covenantPK, wire.NewOutPoint(stkTxHash, stkOutputIdx), uint16(defaultParams.CheckpointFinalizationTimeout)+1, int64(actualDel.TotalSat)-1000, @@ -329,7 +329,7 @@ func createUndelegation( return msg } -func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { +func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -345,8 +345,8 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - // set jury PK to params - jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + // set covenant PK to params + covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) /* generate and insert new BTC validator @@ -365,7 +365,7 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { btclcKeeper, net, validatorPK, - juryPK, + covenantPK, slashingAddr.String(), 1000, ) @@ -377,9 +377,9 @@ func FuzzCreateBTCDelegationAndAddJurySig(f *testing.F) { actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) /* - generate and insert new jury signature + generate and insert new covenant signature */ - createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) }) } @@ -397,13 +397,13 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - // set jury PK to params - _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + // set covenant PK to params + _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ - JuryPk: bbn.NewBIP340PubKeyFromBTCPK(juryPK), + CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), @@ -423,7 +423,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() @@ -483,7 +483,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) _, validatorPK, _ := createValidator(t, r, goCtx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, @@ -494,12 +494,12 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { btclcKeeper, net, validatorPK, - juryPK, + covenantPK, slashingAddr.String(), 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) undelegateMsg := createUndelegation( t, @@ -512,7 +512,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { stakingTxHash, delSK, validatorPK, - juryPK, + covenantPK, slashingAddr.String(), ) @@ -523,13 +523,13 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, undelegateMsg.UnbondingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JurySlashingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.JuryUnbondingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.ValidatorUnbondingSig) }) } -func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { +func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -545,7 +545,7 @@ func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - jurySK, juryPK, slashingAddr := getJuryInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) valSk, validatorPK, _ := createValidator(t, r, goCtx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, @@ -556,12 +556,12 @@ func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { btclcKeeper, net, validatorPK, - juryPK, + covenantPK, slashingAddr.String(), 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createJurySig(t, r, goCtx, ms, bsKeeper, ctx, net, jurySK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) undelegateMsg := createUndelegation( t, @@ -574,7 +574,7 @@ func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { stakingTxHash, delSK, validatorPK, - juryPK, + covenantPK, slashingAddr.String(), ) @@ -608,12 +608,12 @@ func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { require.NotNil(t, delWithValSig.BtcUndelegation) require.NotNil(t, delWithValSig.BtcUndelegation.ValidatorUnbondingSig) - // Check sending jury signatures + // Check sending covenant signatures // unbonding tx spends staking tx - unbondingTxSignatureJury, err := undelegateMsg.UnbondingTx.Sign( + unbondingTxSignatureCovenant, err := undelegateMsg.UnbondingTx.Sign( stakingTxMsg, del.StakingTx.Script, - jurySK, + covenantSK, net, ) require.NoError(t, err) @@ -622,33 +622,33 @@ func FuzzAddJuryAndValidatorSignaturesToUnbondind(f *testing.F) { require.NoError(t, err) // slash unbodning tx spends unbonding tx - slashUnbondingTxSignatureJury, err := undelegateMsg.SlashingTx.Sign( + slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.Sign( unbondingTxMsg, undelegateMsg.UnbondingTx.Script, - jurySK, + covenantSK, net, ) require.NoError(t, err) - jurySigsMsg := types.MsgAddJuryUnbondingSigs{ + covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ Signer: datagen.GenRandomAccount().Address, ValPk: del.ValBtcPk, DelPk: del.BtcPk, StakingTxHash: stakingTxHash, - UnbondingTxSig: unbondingTxSignatureJury, - SlashingUnbondingTxSig: slashUnbondingTxSignatureJury, + UnbondingTxSig: unbondingTxSignatureCovenant, + SlashingUnbondingTxSig: slashUnbondingTxSignatureCovenant, } btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.AddJuryUnbondingSigs(goCtx, &jurySigsMsg) + _, err = ms.AddCovenantUnbondingSigs(goCtx, &covenantSigsMsg) require.NoError(t, err) delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.ValidatorUnbondingSig) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.JurySlashingSig) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.JuryUnbondingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSig) }) } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 09ba168ca..e1ced92b8 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -28,8 +28,8 @@ func FuzzVotingPowerTable(f *testing.F) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - // jury and slashing addr - jurySK, _, err := datagen.GenRandomBTCKeyPair(r) + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -53,7 +53,7 @@ func FuzzVotingPowerTable(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, jurySK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 8b6434e75..3a636f830 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -5,12 +5,13 @@ import ( "encoding/hex" "fmt" - "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" + + "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" ) type BTCSlashingTx []byte @@ -100,7 +101,7 @@ func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) return btcstaking.IsSlashingTx(msgTx, decodedAddr) } -// Sign generates a signature on the slashing tx signed by staker, validator or jury +// Sign generates a signature on the slashing tx signed by staker, validator or covenant func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk *btcec.PrivateKey, net *chaincfg.Params) (*bbn.BIP340Signature, error) { msgTx, err := tx.ToMsgTx() if err != nil { @@ -120,7 +121,7 @@ func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk return &sig, nil } -// VerifySignature verifies a signature on the slashing tx signed by staker, validator or jury +// VerifySignature verifies a signature on the slashing tx signed by staker, validator or covenant func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { msgTx, err := tx.ToMsgTx() if err != nil { @@ -140,8 +141,8 @@ func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount i // - the staking tx // - validator signature // - delegator signature -// - jury signature -func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valSig, delSig, jurySig *bbn.BIP340Signature) (*wire.MsgTx, error) { +// - covenant signature +func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valSig, delSig, covenantSig *bbn.BIP340Signature) (*wire.MsgTx, error) { // get staking script stakingScript := stakingTx.Script @@ -154,9 +155,9 @@ func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valS if err != nil { return nil, fmt.Errorf("failed to convert BTC delegator signature to Schnorr signature format: %w", err) } - jurySchnorrSig, err := jurySig.ToBTCSig() + covenantSchnorrSig, err := covenantSig.ToBTCSig() if err != nil { - return nil, fmt.Errorf("failed to convert jury signature to Schnorr signature format: %w", err) + return nil, fmt.Errorf("failed to convert covenant signature to Schnorr signature format: %w", err) } // build witness from each signature @@ -168,13 +169,13 @@ func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valS if err != nil { return nil, fmt.Errorf("failed to build witness for BTC delegator: %w", err) } - juryWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, jurySchnorrSig) + covenantWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, covenantSchnorrSig) if err != nil { - return nil, fmt.Errorf("failed to build witness for jury: %w", err) + return nil, fmt.Errorf("failed to build witness for covenant: %w", err) } // To Construct valid witness, for multisig case we need: - // - jury signature - witnessJury[0] + // - covenant signature - witnessCovenant[0] // - validator signature - witnessValidator[0] // - staker signature - witnessStaker[0] // - empty signature - which is just an empty byte array which signals we are going to use multisig. @@ -186,7 +187,7 @@ func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valS return nil, fmt.Errorf("failed to convert slashing tx to Bitcoin format: %w", err) } slashingMsgTx.TxIn[0].Witness = [][]byte{ - juryWitness[0], valWitness[0], delWitness[0], []byte{}, delWitness[1], delWitness[2], + covenantWitness[0], valWitness[0], delWitness[0], []byte{}, delWitness[1], delWitness[2], } return slashingMsgTx, nil diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 195598480..16ce9ee4f 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -29,11 +29,11 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - jurySK, juryPK, err := datagen.GenRandomBTCKeyPair(r) + covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) // generate staking/slashing tx - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, valPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, valPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) stakingOutInfo, err := stakingTx.GetBabylonOutputInfo(net) require.NoError(t, err) @@ -46,7 +46,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) delSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, delSK, net) require.NoError(t, err) - jurySig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, jurySK, net) + covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSK, net) require.NoError(t, err) // verify signatures first @@ -54,11 +54,11 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, delPK, delSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, juryPK, jurySig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, covenantPK, covenantSig) require.NoError(t, err) // build slashing tx with witness - slashingMsgTxWithWitness, err := slashingTx.ToMsgTxWithWitness(stakingTx, valSig, delSig, jurySig) + slashingMsgTxWithWitness, err := slashingTx.ToMsgTxWithWitness(stakingTx, valSig, delSig, covenantSig) require.NoError(t, err) // verify slashing tx with witness diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7bfe774b6..d57811b2c 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -90,13 +90,13 @@ func (d *BTCDelegation) ValidateBasic() error { return nil } -// HasJurySig returns whether a BTC delegation has a jury signature -func (d *BTCDelegation) HasJurySig() bool { - return d.JurySig != nil +// HasCovenantSig returns whether a BTC delegation has a covenant signature +func (d *BTCDelegation) HasCovenantSig() bool { + return d.CovenantSig != nil } -func (ud *BTCUndelegation) HasJurySigs() bool { - return ud.JurySlashingSig != nil && ud.JuryUnbondingSig != nil +func (ud *BTCUndelegation) HasCovenantSigs() bool { + return ud.CovenantSlashingSig != nil && ud.CovenantUnbondingSig != nil } func (ud *BTCUndelegation) HasValidatorSig() bool { @@ -104,7 +104,7 @@ func (ud *BTCUndelegation) HasValidatorSig() bool { } func (ud *BTCUndelegation) HasAllSignatures() bool { - return ud.HasJurySigs() && ud.HasValidatorSig() + return ud.HasCovenantSigs() && ud.HasValidatorSig() } // GetStatus returns the status of the BTC Delegation based on a BTC height and a w value @@ -112,8 +112,8 @@ func (ud *BTCUndelegation) HasAllSignatures() bool { // we can only have expired delegations. If we accept optimistic submissions, // we could also have delegations that are in the waiting, so we need an extra status. // This is covered by expired for now as it is the default value. -// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a jury sig -// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a jury sig +// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig +// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig // Expired: Delegation timelock func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { if d.BtcUndelegation != nil { @@ -123,7 +123,7 @@ func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatu // If we received an undelegation but is still does not have all required signature, // delegation receives UNBONING status. // Voting power from this delegation is removed from the total voting power and now we - // are waiting for signatures from validator and jury for delegation to become expired. + // are waiting for signatures from validator and covenant for delegation to become expired. // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain // we may consider adding unbonding time on Babylon chain later to avoid situation where // we can lose to much voting power in to short time. @@ -131,7 +131,7 @@ func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatu } if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { - if d.HasJurySig() { + if d.HasCovenantSig() { return BTCDelegationStatus_ACTIVE } else { return BTCDelegationStatus_PENDING diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 75331b101..c460f28b7 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -37,17 +37,17 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type BTCDelegationStatus int32 const ( - // PENDING defines a delegation that is waiting for a jury signature to become active. + // PENDING defines a delegation that is waiting for a covenant signature to become active. BTCDelegationStatus_PENDING BTCDelegationStatus = 0 // ACTIVE defines a delegation that has voting power BTCDelegationStatus_ACTIVE BTCDelegationStatus = 1 // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx - // from staker, but not yet received signatures from validator and jury. + // from staker, but not yet received signatures from validator and covenant. // Delegation in this state already lost its voting power. BTCDelegationStatus_UNBONDING BTCDelegationStatus = 2 // UNBONDED defines a delegation no longer has voting power: // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx and then receiving signatures from validator and jury for this + // - or receiving unbonding tx and then receiving signatures from validator and covenant for this // unbonding tx. BTCDelegationStatus_UNBONDED BTCDelegationStatus = 3 // ANY is any of the above status @@ -275,16 +275,16 @@ type BTCDelegation struct { StakingTx *BabylonBTCTaprootTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. + // validator or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` - // jury_sig is the signature signature on the slashing tx - // by the jury (i.e., SK corresponding to jury_pk in params) + // covenant_sig is the signature signature on the slashing tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. - JurySig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=jury_sig,json=jurySig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_sig,omitempty"` + CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not @@ -382,23 +382,23 @@ type BTCUndelegation struct { UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // slashing_tx is the slashing tx for unbodning transactions // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or jury yet. + // validator or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,2,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbodning tx output. DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` - // jury_slashing_sig is the signature on the slashing tx - // by the jury (i.e., SK corresponding to jury_pk in params) + // covenant_slashing_sig is the signature on the slashing tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon - JurySlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=jury_slashing_sig,json=jurySlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_slashing_sig,omitempty"` - // jury_unbonding_sig is the signature on the unbonding tx - // by the jury (i.e., SK corresponding to jury_pk in params) + CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` + // covenant_unbonding_sig is the signature on the unbonding tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator - JuryUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=jury_unbonding_sig,json=juryUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_unbonding_sig,omitempty"` + CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to jury_pk in params) + // by the validator (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` } @@ -454,10 +454,10 @@ type BTCUndelegationInfo struct { // It must be provided after processing undelagate message by Babylon // It will be nil if validator didn't sign it yet ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` - // jury_unbonding_sig is the signature on the unbonding tx - // by the jury (i.e., SK corresponding to jury_pk in params) - // it will be nil if jury didn't sign it yet - JuryUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=jury_unbonding_sig,json=juryUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"jury_unbonding_sig,omitempty"` + // covenant_unbonding_sig is the signature on the unbonding tx + // by the covenant (i.e., SK corresponding to covenant_pk in params) + // it will be nil if covenant didn't sign it yet + CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -663,72 +663,72 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1030 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x6e, 0xdb, 0x46, - 0x10, 0x36, 0x25, 0x59, 0xb6, 0x46, 0x4a, 0x2d, 0x6f, 0x1c, 0x47, 0x75, 0x50, 0xc9, 0x51, 0x03, - 0x43, 0x08, 0x1a, 0xa9, 0x76, 0x7e, 0x90, 0x16, 0x68, 0x81, 0xc8, 0x32, 0x1a, 0xa1, 0xb1, 0xa3, - 0x52, 0x74, 0xfa, 0x73, 0x28, 0xb1, 0x24, 0xd7, 0x14, 0x2b, 0x99, 0xcb, 0x72, 0x57, 0xaa, 0x74, - 0xef, 0x03, 0xf4, 0xd6, 0x17, 0xe9, 0x03, 0xf4, 0x98, 0x63, 0xd0, 0x53, 0xe1, 0x83, 0x51, 0xd8, - 0x2f, 0x52, 0x70, 0xb9, 0x14, 0x69, 0xc7, 0x4e, 0xeb, 0xca, 0x3d, 0x89, 0xb3, 0x33, 0xf3, 0xcd, - 0xcc, 0xf7, 0x8d, 0xc8, 0x85, 0x0d, 0x03, 0x1b, 0x93, 0x01, 0x75, 0x1b, 0x06, 0x37, 0x19, 0xc7, - 0x7d, 0xc7, 0xb5, 0x1b, 0xa3, 0xcd, 0x84, 0x55, 0xf7, 0x7c, 0xca, 0x29, 0xba, 0x25, 0xe3, 0xea, - 0x09, 0xcf, 0x68, 0x73, 0x6d, 0xc5, 0xa6, 0x36, 0x15, 0x11, 0x8d, 0xe0, 0x29, 0x0c, 0x5e, 0x7b, - 0xdf, 0xa4, 0xec, 0x90, 0x32, 0x3d, 0x74, 0x84, 0x86, 0x74, 0x55, 0x43, 0xab, 0x61, 0xfa, 0x13, - 0x8f, 0xd3, 0x06, 0x23, 0xa6, 0xb7, 0xf5, 0xf8, 0x49, 0x7f, 0xb3, 0xd1, 0x27, 0x93, 0x28, 0xe6, - 0x9e, 0x8c, 0x89, 0xfb, 0x31, 0x08, 0xc7, 0x9b, 0x8d, 0x33, 0x1d, 0xad, 0x55, 0x2e, 0xee, 0xdc, - 0xa3, 0x5e, 0x18, 0x50, 0x3d, 0x4e, 0x43, 0xa1, 0xa9, 0x6d, 0xbf, 0xc2, 0x03, 0xc7, 0xc2, 0x9c, - 0xfa, 0x68, 0x07, 0xf2, 0x16, 0x61, 0xa6, 0xef, 0x78, 0xdc, 0xa1, 0x6e, 0x49, 0x59, 0x57, 0x6a, - 0xf9, 0xad, 0x0f, 0xeb, 0xb2, 0xbf, 0x78, 0x2a, 0x51, 0xad, 0xde, 0x8a, 0x43, 0xd5, 0x64, 0x1e, - 0xfa, 0x06, 0xc0, 0xa4, 0x87, 0x87, 0x0e, 0x63, 0x01, 0x4a, 0x6a, 0x5d, 0xa9, 0xe5, 0x9a, 0x4f, - 0x8f, 0x8e, 0x2b, 0x1b, 0xb6, 0xc3, 0x7b, 0x43, 0xa3, 0x6e, 0xd2, 0xc3, 0x46, 0x34, 0xa5, 0xf8, - 0x79, 0xc0, 0xac, 0x7e, 0x83, 0x4f, 0x3c, 0xc2, 0xea, 0x2d, 0x62, 0xfe, 0xf1, 0xdb, 0x03, 0x90, - 0x25, 0x5b, 0xc4, 0x54, 0x13, 0x58, 0xe8, 0x73, 0x00, 0x39, 0x94, 0xee, 0xf5, 0x4b, 0x69, 0xd1, - 0x5f, 0x25, 0xea, 0x2f, 0x64, 0xac, 0x3e, 0x65, 0xac, 0xde, 0x19, 0x1a, 0x5f, 0x92, 0x89, 0x9a, - 0x93, 0x29, 0x9d, 0x3e, 0xda, 0x85, 0xac, 0xc1, 0xcd, 0x20, 0x37, 0xb3, 0xae, 0xd4, 0x0a, 0xcd, - 0x27, 0x47, 0xc7, 0x95, 0xad, 0x44, 0x57, 0x32, 0xd2, 0xec, 0x61, 0xc7, 0x8d, 0x0c, 0xd9, 0x58, - 0xb3, 0xdd, 0x79, 0xf8, 0xe8, 0x63, 0x09, 0x39, 0x6f, 0x70, 0xb3, 0xd3, 0x47, 0x9f, 0x42, 0xda, - 0xa3, 0x5e, 0x69, 0x5e, 0xf4, 0x51, 0xab, 0x5f, 0xb8, 0x01, 0xf5, 0x8e, 0x4f, 0xe9, 0xc1, 0xcb, - 0x83, 0x0e, 0x65, 0x8c, 0x88, 0x29, 0xd4, 0x20, 0x09, 0x3d, 0x82, 0x55, 0x36, 0xc0, 0xac, 0x47, - 0x2c, 0x3d, 0x1a, 0xa9, 0x47, 0x1c, 0xbb, 0xc7, 0x4b, 0xd9, 0x75, 0xa5, 0x96, 0x51, 0x57, 0xa4, - 0xb7, 0x19, 0x3a, 0x9f, 0x0b, 0x1f, 0xfa, 0x08, 0xd0, 0x34, 0x8b, 0x9b, 0x51, 0xc6, 0x82, 0xc8, - 0x28, 0x46, 0x19, 0xdc, 0x0c, 0xa3, 0xab, 0x3f, 0xa7, 0x60, 0x25, 0x29, 0xf0, 0xd7, 0x0e, 0xef, - 0xed, 0x12, 0x8e, 0x13, 0x3c, 0x28, 0xd7, 0xc1, 0xc3, 0x2a, 0x64, 0x65, 0x27, 0x29, 0xd1, 0x89, - 0xb4, 0xd0, 0x5d, 0x28, 0x8c, 0x28, 0x77, 0x5c, 0x5b, 0xf7, 0xe8, 0x4f, 0xc4, 0x17, 0x82, 0x65, - 0xd4, 0x7c, 0x78, 0xd6, 0x09, 0x8e, 0xde, 0x41, 0x43, 0xe6, 0xca, 0x34, 0xcc, 0x5f, 0x42, 0xc3, - 0xaf, 0x59, 0xb8, 0xd1, 0xd4, 0xb6, 0x5b, 0x64, 0x40, 0x6c, 0xcc, 0xdf, 0xde, 0x23, 0x65, 0x86, - 0x3d, 0x4a, 0x5d, 0xe3, 0x1e, 0xa5, 0xff, 0xcb, 0x1e, 0x69, 0x00, 0x23, 0x3c, 0xd0, 0xaf, 0x65, - 0xad, 0x17, 0x47, 0x78, 0xd0, 0x14, 0x1d, 0xdd, 0x85, 0x02, 0xe3, 0xd8, 0xe7, 0x67, 0xa9, 0xcd, - 0x8b, 0x33, 0xa9, 0xc1, 0x07, 0x00, 0xc4, 0xb5, 0xce, 0x2e, 0x6d, 0x8e, 0xb8, 0x96, 0x74, 0xdf, - 0x81, 0x1c, 0xa7, 0x1c, 0x0f, 0x74, 0x86, 0xa3, 0x05, 0x5d, 0x14, 0x07, 0x5d, 0xcc, 0x51, 0x1b, - 0x40, 0x4e, 0xa6, 0xf3, 0x71, 0x69, 0x51, 0xcc, 0x7d, 0xff, 0x92, 0xb9, 0xa5, 0xf2, 0x4d, 0x6d, - 0x5b, 0xc3, 0x9e, 0x4f, 0x29, 0xd7, 0xc6, 0x6a, 0x4e, 0xfa, 0xb5, 0x31, 0xda, 0x82, 0xbc, 0x10, - 0x5c, 0x62, 0xe5, 0x04, 0x01, 0xcb, 0x47, 0xc7, 0x95, 0x40, 0xf2, 0xae, 0xf4, 0x68, 0x63, 0x15, - 0xd8, 0xf4, 0x19, 0x7d, 0x0f, 0x37, 0xac, 0x70, 0x19, 0xa8, 0xaf, 0x33, 0xc7, 0x2e, 0x81, 0xc8, - 0xfa, 0xe4, 0xe8, 0xb8, 0xf2, 0xf8, 0x2a, 0xb4, 0x75, 0x1d, 0xdb, 0xc5, 0x7c, 0xe8, 0x13, 0xb5, - 0x30, 0xc5, 0xeb, 0x3a, 0x36, 0xd2, 0x60, 0xf1, 0x87, 0xa1, 0x3f, 0x11, 0xd0, 0xf9, 0x59, 0xa1, - 0x17, 0x02, 0xa8, 0x00, 0xf5, 0x2b, 0x28, 0x06, 0x2a, 0x0f, 0x5d, 0x6b, 0xba, 0xc8, 0xa5, 0x82, - 0xa0, 0x6e, 0xe3, 0x32, 0xea, 0xb4, 0xed, 0xfd, 0x44, 0xb4, 0xba, 0x64, 0x70, 0x33, 0x79, 0x50, - 0x7d, 0x9d, 0x81, 0xa5, 0x73, 0x41, 0x68, 0x17, 0x0a, 0x43, 0xd7, 0xa0, 0xae, 0x25, 0x19, 0x55, - 0xae, 0xac, 0x4e, 0x7e, 0x9a, 0xff, 0xb6, 0x3e, 0xa9, 0x7f, 0xa3, 0x0f, 0x85, 0xd5, 0x84, 0x3e, - 0x51, 0x76, 0xc0, 0x66, 0x7a, 0x56, 0x36, 0x57, 0x62, 0xa1, 0x24, 0x6e, 0x40, 0x2d, 0x81, 0xe5, - 0x50, 0xb0, 0x64, 0xad, 0xcc, 0xac, 0xb5, 0x96, 0x84, 0x72, 0x89, 0x32, 0x36, 0x20, 0x51, 0x26, - 0xe6, 0x37, 0xa8, 0x33, 0x3f, 0x6b, 0x9d, 0x62, 0x00, 0xba, 0x1f, 0x61, 0x06, 0x85, 0x7e, 0x84, - 0xdb, 0xa3, 0xe8, 0xa5, 0x7f, 0xae, 0x5a, 0x76, 0xd6, 0x6a, 0xb7, 0xa6, 0xc8, 0xc9, 0x92, 0xd5, - 0xdf, 0x53, 0x70, 0xf3, 0xdc, 0x2a, 0xb5, 0xdd, 0x03, 0x7a, 0xdd, 0xeb, 0xf4, 0x8e, 0xc9, 0x52, - 0xff, 0xcf, 0x64, 0x97, 0xa8, 0x96, 0xbe, 0x76, 0xd5, 0xaa, 0x5d, 0xb8, 0x1d, 0x7f, 0xa6, 0xa8, - 0x1f, 0x7f, 0xaf, 0x18, 0x7a, 0x0a, 0x19, 0x8b, 0x0c, 0x58, 0x49, 0x59, 0x4f, 0xd7, 0xf2, 0x5b, - 0xf7, 0x2e, 0xff, 0xbf, 0xc7, 0x49, 0xaa, 0xc8, 0xa8, 0xee, 0xc1, 0x9d, 0x8b, 0x41, 0xdb, 0xae, - 0x45, 0xc6, 0xa8, 0x01, 0x2b, 0xf1, 0x9b, 0x58, 0xef, 0x61, 0xd6, 0xd3, 0x07, 0x0e, 0xe3, 0xa2, - 0x50, 0x41, 0x5d, 0x9e, 0xbe, 0x67, 0x9f, 0x63, 0xd6, 0x7b, 0xe1, 0x30, 0x5e, 0xfd, 0x0c, 0x6e, - 0x5e, 0x20, 0x12, 0x7a, 0x0f, 0x52, 0x52, 0xdc, 0x82, 0x9a, 0xe2, 0xe3, 0xe0, 0x4a, 0x10, 0x5e, - 0x08, 0x43, 0x59, 0x54, 0x69, 0xdd, 0xd7, 0xc4, 0x96, 0xc4, 0x5d, 0x74, 0x39, 0xe6, 0x43, 0x86, - 0xf2, 0xb0, 0xd0, 0xd9, 0xd9, 0x6b, 0xb5, 0xf7, 0xbe, 0x28, 0xce, 0x21, 0x80, 0xec, 0xb3, 0x6d, - 0xad, 0xfd, 0x6a, 0xa7, 0xa8, 0xa0, 0x1b, 0x90, 0xdb, 0xdf, 0x6b, 0xbe, 0x0c, 0x5d, 0x29, 0x54, - 0x80, 0xc5, 0xd0, 0xdc, 0x69, 0x15, 0xd3, 0x68, 0x01, 0xd2, 0xcf, 0xf6, 0xbe, 0x2d, 0x66, 0x9a, - 0x2f, 0x5e, 0x9f, 0x94, 0x95, 0x37, 0x27, 0x65, 0xe5, 0xaf, 0x93, 0xb2, 0xf2, 0xcb, 0x69, 0x79, - 0xee, 0xcd, 0x69, 0x79, 0xee, 0xcf, 0xd3, 0xf2, 0xdc, 0x77, 0xff, 0xf8, 0x19, 0x1c, 0x27, 0xaf, - 0xc7, 0x42, 0x29, 0x23, 0x2b, 0xae, 0xc7, 0x0f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x4e, 0x0e, - 0x34, 0x40, 0xfb, 0x0b, 0x00, 0x00, + // 1033 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdb, 0x6e, 0xdb, 0x46, + 0x13, 0x36, 0x25, 0x59, 0xb6, 0x46, 0xf4, 0x1f, 0x65, 0x7d, 0x88, 0x7e, 0x07, 0x95, 0x1c, 0x35, + 0x30, 0x84, 0xa0, 0x91, 0x6a, 0xe7, 0x80, 0xb4, 0x40, 0x0b, 0x44, 0x96, 0xd1, 0x08, 0x8d, 0x1d, + 0x95, 0xa2, 0xd3, 0x03, 0x8a, 0x0a, 0x4b, 0x72, 0x2d, 0x11, 0x92, 0xb9, 0x2c, 0x77, 0xa5, 0xca, + 0xf7, 0x7d, 0x80, 0xbe, 0x41, 0x5f, 0xa2, 0x0f, 0x91, 0xcb, 0xa0, 0x57, 0x81, 0x2f, 0x8c, 0xc2, + 0x7e, 0x91, 0x82, 0xcb, 0xe5, 0xc1, 0x8e, 0xed, 0xd6, 0x95, 0x73, 0x25, 0xcd, 0xce, 0xcc, 0x37, + 0x33, 0xdf, 0x37, 0xe0, 0x2e, 0xac, 0x1b, 0xd8, 0x38, 0x1c, 0x52, 0xa7, 0x6e, 0x70, 0x93, 0x71, + 0x3c, 0xb0, 0x9d, 0x5e, 0x7d, 0xbc, 0x91, 0xb0, 0x6a, 0xae, 0x47, 0x39, 0x45, 0xcb, 0x32, 0xae, + 0x96, 0xf0, 0x8c, 0x37, 0x56, 0x97, 0x7a, 0xb4, 0x47, 0x45, 0x44, 0xdd, 0xff, 0x17, 0x04, 0xaf, + 0xfe, 0xdf, 0xa4, 0xec, 0x80, 0xb2, 0x6e, 0xe0, 0x08, 0x0c, 0xe9, 0xaa, 0x04, 0x56, 0xdd, 0xf4, + 0x0e, 0x5d, 0x4e, 0xeb, 0x8c, 0x98, 0xee, 0xe6, 0x93, 0xa7, 0x83, 0x8d, 0xfa, 0x80, 0x1c, 0x86, + 0x31, 0xf7, 0x65, 0x4c, 0xdc, 0x8f, 0x41, 0x38, 0xde, 0xa8, 0x9f, 0xe9, 0x68, 0xb5, 0x7c, 0x71, + 0xe7, 0x2e, 0x75, 0x83, 0x80, 0xca, 0x71, 0x1a, 0xd4, 0x86, 0xbe, 0xf5, 0x1a, 0x0f, 0x6d, 0x0b, + 0x73, 0xea, 0xa1, 0x6d, 0xc8, 0x5b, 0x84, 0x99, 0x9e, 0xed, 0x72, 0x9b, 0x3a, 0x45, 0x65, 0x4d, + 0xa9, 0xe6, 0x37, 0x3f, 0xae, 0xc9, 0xfe, 0xe2, 0xa9, 0x44, 0xb5, 0x5a, 0x33, 0x0e, 0xd5, 0x92, + 0x79, 0xe8, 0x3b, 0x00, 0x93, 0x1e, 0x1c, 0xd8, 0x8c, 0xf9, 0x28, 0xa9, 0x35, 0xa5, 0x9a, 0x6b, + 0x3c, 0x3b, 0x3a, 0x2e, 0xaf, 0xf7, 0x6c, 0xde, 0x1f, 0x19, 0x35, 0x93, 0x1e, 0xd4, 0xc3, 0x29, + 0xc5, 0xcf, 0x43, 0x66, 0x0d, 0xea, 0xfc, 0xd0, 0x25, 0xac, 0xd6, 0x24, 0xe6, 0x9f, 0x7f, 0x3c, + 0x04, 0x59, 0xb2, 0x49, 0x4c, 0x2d, 0x81, 0x85, 0xbe, 0x04, 0x90, 0x43, 0x75, 0xdd, 0x41, 0x31, + 0x2d, 0xfa, 0x2b, 0x87, 0xfd, 0x05, 0x8c, 0xd5, 0x22, 0xc6, 0x6a, 0xed, 0x91, 0xf1, 0x35, 0x39, + 0xd4, 0x72, 0x32, 0xa5, 0x3d, 0x40, 0x3b, 0x90, 0x35, 0xb8, 0xe9, 0xe7, 0x66, 0xd6, 0x94, 0xaa, + 0xda, 0x78, 0x7a, 0x74, 0x5c, 0xde, 0x4c, 0x74, 0x25, 0x23, 0xcd, 0x3e, 0xb6, 0x9d, 0xd0, 0x90, + 0x8d, 0x35, 0x5a, 0xed, 0x47, 0x8f, 0x3f, 0x95, 0x90, 0xb3, 0x06, 0x37, 0xdb, 0x03, 0xf4, 0x39, + 0xa4, 0x5d, 0xea, 0x16, 0x67, 0x45, 0x1f, 0xd5, 0xda, 0x85, 0x1b, 0x50, 0x6b, 0x7b, 0x94, 0xee, + 0xbf, 0xda, 0x6f, 0x53, 0xc6, 0x88, 0x98, 0x42, 0xf3, 0x93, 0xd0, 0x63, 0x58, 0x61, 0x43, 0xcc, + 0xfa, 0xc4, 0xea, 0x86, 0x23, 0xf5, 0x89, 0xdd, 0xeb, 0xf3, 0x62, 0x76, 0x4d, 0xa9, 0x66, 0xb4, + 0x25, 0xe9, 0x6d, 0x04, 0xce, 0x17, 0xc2, 0x87, 0x3e, 0x01, 0x14, 0x65, 0x71, 0x33, 0xcc, 0x98, + 0x13, 0x19, 0x85, 0x30, 0x83, 0x9b, 0x41, 0x74, 0xe5, 0xd7, 0x14, 0x2c, 0x25, 0x05, 0xfe, 0xd6, + 0xe6, 0xfd, 0x1d, 0xc2, 0x71, 0x82, 0x07, 0xe5, 0x26, 0x78, 0x58, 0x81, 0xac, 0xec, 0x24, 0x25, + 0x3a, 0x91, 0x16, 0xba, 0x07, 0xea, 0x98, 0x72, 0xdb, 0xe9, 0x75, 0x5d, 0xfa, 0x0b, 0xf1, 0x84, + 0x60, 0x19, 0x2d, 0x1f, 0x9c, 0xb5, 0xfd, 0xa3, 0x2b, 0x68, 0xc8, 0x5c, 0x9b, 0x86, 0xd9, 0x4b, + 0x68, 0xf8, 0x3d, 0x0b, 0x0b, 0x0d, 0x7d, 0xab, 0x49, 0x86, 0xa4, 0x87, 0xf9, 0xfb, 0x7b, 0xa4, + 0x4c, 0xb1, 0x47, 0xa9, 0x1b, 0xdc, 0xa3, 0xf4, 0x7f, 0xd9, 0x23, 0x1d, 0x60, 0x8c, 0x87, 0xdd, + 0x1b, 0x59, 0xeb, 0xf9, 0x31, 0x1e, 0x36, 0x44, 0x47, 0xf7, 0x40, 0x65, 0x1c, 0x7b, 0xfc, 0x2c, + 0xb5, 0x79, 0x71, 0x26, 0x35, 0xf8, 0x08, 0x80, 0x38, 0xd6, 0xd9, 0xa5, 0xcd, 0x11, 0xc7, 0x92, + 0xee, 0xbb, 0x90, 0xe3, 0x94, 0xe3, 0x61, 0x97, 0xe1, 0x70, 0x41, 0xe7, 0xc5, 0x41, 0x07, 0x73, + 0xd4, 0x02, 0x90, 0x93, 0x75, 0xf9, 0xa4, 0x38, 0x2f, 0xe6, 0x7e, 0x70, 0xc9, 0xdc, 0x52, 0xf9, + 0x86, 0xbe, 0xa5, 0x63, 0xd7, 0xa3, 0x94, 0xeb, 0x13, 0x2d, 0x27, 0xfd, 0xfa, 0x04, 0x6d, 0x42, + 0x5e, 0x08, 0x2e, 0xb1, 0x72, 0x82, 0x80, 0xdb, 0x47, 0xc7, 0x65, 0x5f, 0xf2, 0x8e, 0xf4, 0xe8, + 0x13, 0x0d, 0x58, 0xf4, 0x1f, 0xfd, 0x04, 0x0b, 0x56, 0xb0, 0x0c, 0xd4, 0xeb, 0x32, 0xbb, 0x57, + 0x04, 0x91, 0xf5, 0xd9, 0xd1, 0x71, 0xf9, 0xc9, 0x75, 0x68, 0xeb, 0xd8, 0x3d, 0x07, 0xf3, 0x91, + 0x47, 0x34, 0x35, 0xc2, 0xeb, 0xd8, 0x3d, 0xf4, 0x23, 0xa8, 0x26, 0x1d, 0x13, 0x07, 0x3b, 0x5c, + 0xc0, 0xe7, 0xa7, 0x85, 0xcf, 0x87, 0x70, 0x3e, 0xfa, 0x37, 0x50, 0xf0, 0xd5, 0x1e, 0x39, 0x56, + 0xb4, 0xd0, 0x45, 0x55, 0x50, 0xb8, 0x7e, 0x19, 0x85, 0xfa, 0xd6, 0x5e, 0x22, 0x5a, 0xbb, 0x65, + 0x70, 0x33, 0x79, 0x50, 0x79, 0x97, 0x81, 0x5b, 0xe7, 0x82, 0xd0, 0x0e, 0xa8, 0x23, 0xc7, 0xa0, + 0x8e, 0x25, 0x99, 0x55, 0xae, 0xad, 0x52, 0x3e, 0xca, 0x7f, 0x5f, 0xa7, 0xd4, 0xbf, 0xd1, 0x89, + 0xc2, 0x4a, 0x42, 0xa7, 0x30, 0xdb, 0x67, 0x34, 0x3d, 0x2d, 0xa3, 0x4b, 0xb1, 0x60, 0x12, 0xd7, + 0xa7, 0xf6, 0x00, 0x96, 0x63, 0xe1, 0x92, 0xf5, 0x32, 0xd3, 0xd6, 0x5b, 0x8c, 0x14, 0x4c, 0x94, + 0xa3, 0xb0, 0x12, 0x95, 0x8b, 0xb9, 0xf6, 0xeb, 0xcd, 0x4e, 0x3d, 0x5f, 0x08, 0xbc, 0x17, 0xe2, + 0xfa, 0x05, 0x7f, 0x86, 0x3b, 0xe3, 0xf0, 0x32, 0x38, 0x57, 0x31, 0x3b, 0x6d, 0xc5, 0xe5, 0x08, + 0x39, 0x59, 0xb2, 0xf2, 0x26, 0x05, 0x8b, 0xe7, 0x56, 0xab, 0xe5, 0xec, 0xd3, 0x9b, 0x5e, 0xaf, + 0x2b, 0x26, 0x4b, 0x7d, 0x98, 0xc9, 0xae, 0x50, 0x2f, 0xfd, 0x41, 0xd4, 0xab, 0x74, 0xe0, 0x4e, + 0x7c, 0x8d, 0x51, 0x2f, 0xbe, 0xcf, 0x18, 0x7a, 0x06, 0x19, 0x8b, 0x0c, 0x59, 0x51, 0x59, 0x4b, + 0x57, 0xf3, 0x9b, 0xf7, 0x2f, 0xff, 0x0e, 0xc4, 0x49, 0x9a, 0xc8, 0xa8, 0xec, 0xc2, 0xdd, 0x8b, + 0x41, 0x5b, 0x8e, 0x45, 0x26, 0xa8, 0x0e, 0x4b, 0xf1, 0x97, 0xba, 0xdb, 0xc7, 0xac, 0xdf, 0x1d, + 0xda, 0x8c, 0x8b, 0x42, 0xaa, 0x76, 0x3b, 0xfa, 0x0e, 0xbf, 0xc0, 0xac, 0xff, 0xd2, 0x66, 0xbc, + 0xf2, 0x05, 0x2c, 0x5e, 0x20, 0x16, 0xfa, 0x1f, 0xa4, 0xa4, 0xc8, 0xaa, 0x96, 0xe2, 0x13, 0xff, + 0xc9, 0x10, 0x3c, 0x18, 0x03, 0x79, 0x34, 0x69, 0x3d, 0xd0, 0xc5, 0xb6, 0xc4, 0x5d, 0x74, 0x38, + 0xe6, 0x23, 0x86, 0xf2, 0x30, 0xd7, 0xde, 0xde, 0x6d, 0xb6, 0x76, 0xbf, 0x2a, 0xcc, 0x20, 0x80, + 0xec, 0xf3, 0x2d, 0xbd, 0xf5, 0x7a, 0xbb, 0xa0, 0xa0, 0x05, 0xc8, 0xed, 0xed, 0x36, 0x5e, 0x05, + 0xae, 0x14, 0x52, 0x61, 0x3e, 0x30, 0xb7, 0x9b, 0x85, 0x34, 0x9a, 0x83, 0xf4, 0xf3, 0xdd, 0xef, + 0x0b, 0x99, 0xc6, 0xcb, 0x37, 0x27, 0x25, 0xe5, 0xed, 0x49, 0x49, 0xf9, 0xeb, 0xa4, 0xa4, 0xfc, + 0x76, 0x5a, 0x9a, 0x79, 0x7b, 0x5a, 0x9a, 0x79, 0x77, 0x5a, 0x9a, 0xf9, 0xe1, 0x1f, 0xaf, 0xc9, + 0x49, 0xf2, 0xf9, 0x2c, 0xd4, 0x32, 0xb2, 0xe2, 0xf9, 0xfc, 0xe8, 0xef, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x3a, 0x84, 0xbc, 0x0b, 0x1b, 0x0c, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -911,11 +911,11 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x62 } - if m.JurySig != nil { + if m.CovenantSig != nil { { - size := m.JurySig.Size() + size := m.CovenantSig.Size() i -= size - if _, err := m.JurySig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CovenantSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -1057,11 +1057,11 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - if m.JuryUnbondingSig != nil { + if m.CovenantUnbondingSig != nil { { - size := m.JuryUnbondingSig.Size() + size := m.CovenantUnbondingSig.Size() i -= size - if _, err := m.JuryUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CovenantUnbondingSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -1069,11 +1069,11 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - if m.JurySlashingSig != nil { + if m.CovenantSlashingSig != nil { { - size := m.JurySlashingSig.Size() + size := m.CovenantSlashingSig.Size() i -= size - if _, err := m.JurySlashingSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CovenantSlashingSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -1140,11 +1140,11 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.JuryUnbondingSig != nil { + if m.CovenantUnbondingSig != nil { { - size := m.JuryUnbondingSig.Size() + size := m.CovenantUnbondingSig.Size() i -= size - if _, err := m.JuryUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CovenantUnbondingSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -1399,8 +1399,8 @@ func (m *BTCDelegation) Size() (n int) { l = m.DelegatorSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.JurySig != nil { - l = m.JurySig.Size() + if m.CovenantSig != nil { + l = m.CovenantSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } if m.BtcUndelegation != nil { @@ -1428,12 +1428,12 @@ func (m *BTCUndelegation) Size() (n int) { l = m.DelegatorSlashingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.JurySlashingSig != nil { - l = m.JurySlashingSig.Size() + if m.CovenantSlashingSig != nil { + l = m.CovenantSlashingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.JuryUnbondingSig != nil { - l = m.JuryUnbondingSig.Size() + if m.CovenantUnbondingSig != nil { + l = m.CovenantUnbondingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } if m.ValidatorUnbondingSig != nil { @@ -1457,8 +1457,8 @@ func (m *BTCUndelegationInfo) Size() (n int) { l = m.ValidatorUnbondingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.JuryUnbondingSig != nil { - l = m.JuryUnbondingSig.Size() + if m.CovenantUnbondingSig != nil { + l = m.CovenantUnbondingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } return n @@ -2281,7 +2281,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field JurySig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2309,8 +2309,8 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.JurySig = &v - if err := m.JurySig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantSig = &v + if err := m.CovenantSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2508,7 +2508,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field JurySlashingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2536,14 +2536,14 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.JurySlashingSig = &v - if err := m.JurySlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantSlashingSig = &v + if err := m.CovenantSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field JuryUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2571,8 +2571,8 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.JuryUnbondingSig = &v - if err := m.JuryUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantUnbondingSig = &v + if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2734,7 +2734,7 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field JuryUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2762,8 +2762,8 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.JuryUnbondingSig = &v - if err := m.JuryUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantUnbondingSig = &v + if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index b6d57eef1..273afaa0b 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -22,14 +22,14 @@ func FuzzStakingTx(f *testing.F) { require.NoError(t, err) _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - _, juryPK, err := datagen.GenRandomBTCKeyPair(r) + _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, net, stakerSK, validatorPK, juryPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, net, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) require.NoError(t, err) err = stakingTx.ValidateBasic() @@ -41,7 +41,7 @@ func FuzzStakingTx(f *testing.F) { // NOTE: given that PK derived from SK has 2 possibilities on a curve, we can only compare x value but not y value require.Equal(t, stakingOutputInfo.StakingScriptData.StakerKey.SerializeCompressed()[1:], stakerPK.SerializeCompressed()[1:]) require.Equal(t, stakingOutputInfo.StakingScriptData.ValidatorKey.SerializeCompressed()[1:], validatorPK.SerializeCompressed()[1:]) - require.Equal(t, stakingOutputInfo.StakingScriptData.JuryKey.SerializeCompressed()[1:], juryPK.SerializeCompressed()[1:]) + require.Equal(t, stakingOutputInfo.StakingScriptData.CovenantKey.SerializeCompressed()[1:], covenantPK.SerializeCompressed()[1:]) require.Equal(t, stakingOutputInfo.StakingScriptData.StakingTime, stakingTimeBlocks) require.Equal(t, int64(stakingOutputInfo.StakingAmount), stakingValue) }) @@ -57,11 +57,11 @@ func FuzzBTCDelegation(f *testing.F) { // randomise voting power btcDel.TotalSat = datagen.RandomInt(r, 100000) - // randomise jury sig - hasJurySig := datagen.RandomInt(r, 2) == 0 - if hasJurySig { - jurySig := bbn.BIP340Signature([]byte{1, 2, 3}) - btcDel.JurySig = &jurySig + // randomise covenant sig + hasCovenantSig := datagen.RandomInt(r, 2) == 0 + if hasCovenantSig { + covenantSig := bbn.BIP340Signature([]byte{1, 2, 3}) + btcDel.CovenantSig = &covenantSig } // randomise start height and end height @@ -73,7 +73,7 @@ func FuzzBTCDelegation(f *testing.F) { w := datagen.RandomInt(r, 50) // test expected voting power - hasVotingPower := hasJurySig && btcDel.StartHeight <= btcHeight && btcHeight+w <= btcDel.EndHeight + hasVotingPower := hasCovenantSig && btcDel.StartHeight <= btcHeight && btcHeight+w <= btcDel.EndHeight actualVotingPower := btcDel.VotingPower(btcHeight, w) if hasVotingPower { require.Equal(t, btcDel.TotalSat, actualVotingPower) diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index 9c77cb321..fe6704e44 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -10,10 +10,10 @@ import ( func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgCreateBTCValidator{}, "btcstaking/MsgCreateBTCValidator", nil) cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) - cdc.RegisterConcrete(&MsgAddJurySig{}, "btcstaking/MsgAddJurySig", nil) + cdc.RegisterConcrete(&MsgAddCovenantSig{}, "btcstaking/MsgAddCovenantSig", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) cdc.RegisterConcrete(&MsgBTCUndelegate{}, "btcstaking/MsgBtcUndelegate", nil) - cdc.RegisterConcrete(&MsgAddJuryUnbondingSigs{}, "btcstaking/MsgAddJuryUnbondingSigs", nil) + cdc.RegisterConcrete(&MsgAddCovenantUnbondingSigs{}, "btcstaking/MsgAddCovenantUnbondingSigs", nil) cdc.RegisterConcrete(&MsgAddValidatorUnbondingSig{}, "btcstaking/MsgAddValidatorUnbondingSig", nil) } @@ -23,10 +23,10 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgCreateBTCValidator{}, &MsgCreateBTCDelegation{}, - &MsgAddJurySig{}, + &MsgAddCovenantSig{}, &MsgUpdateParams{}, &MsgBTCUndelegate{}, - &MsgAddJuryUnbondingSigs{}, + &MsgAddCovenantUnbondingSigs{}, &MsgAddValidatorUnbondingSig{}, ) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 0a937c18f..ed5640e49 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -14,11 +14,11 @@ var ( ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1105, "the BTC staking protocol is not activated yet") ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1106, "the BTC height is not found") ErrReusedStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is already used") - ErrInvalidJuryPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong jury PK") + ErrInvalidCovenantPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong covenant PK") ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1109, "the BTC staking tx is not valid") ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1110, "the BTC slashing tx is not valid") - ErrDuplicatedJurySig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received jury signature") - ErrInvalidJurySig = errorsmod.Register(ModuleName, 1112, "the jury signature is not valid") + ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received covenant signature") + ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1112, "the covenant signature is not valid") ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1113, "commission cannot be less than min rate") ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1114, "Unexpected delegation state") ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1115, "the BTC unbonding tx is not valid") diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index e36063cae..02edf82bb 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -71,7 +71,7 @@ func (m *EventNewBTCValidator) GetBtcVal() *BTCValidator { // EventNewBTCDelegation is the event emitted when a BTC delegation is created // NOTE: the BTC delegation is not active thus does not have voting power yet -// only after it receives a jury signature it becomes activated and has voting power +// only after it receives a covenant signature it becomes activated and has voting power type EventNewBTCDelegation struct { BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` } @@ -116,7 +116,7 @@ func (m *EventNewBTCDelegation) GetBtcDel() *BTCDelegation { return nil } -// EventActivateBTCDelegation is the event emitted when jury activates a BTC delegation +// EventActivateBTCDelegation is the event emitted when covenant activates a BTC delegation // such that the BTC delegation starts to have voting power in its timelock period type EventActivateBTCDelegation struct { BtcDel *BTCDelegation `protobuf:"bytes,1,opt,name=btc_del,json=btcDel,proto3" json:"btc_del,omitempty"` diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index ee9d39639..f271c0f7a 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -24,7 +24,7 @@ func TestGenesisState_Validate(t *testing.T) { desc: "valid genesis state", genState: &types.GenesisState{ Params: types.Params{ - JuryPk: types.DefaultParams().JuryPk, + CovenantPk: types.DefaultParams().CovenantPk, SlashingAddress: types.DefaultParams().SlashingAddress, MinSlashingTxFeeSat: 500, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), @@ -37,7 +37,7 @@ func TestGenesisState_Validate(t *testing.T) { desc: "invalid slashing rate in genesis", genState: &types.GenesisState{ Params: types.Params{ - JuryPk: types.DefaultParams().JuryPk, + CovenantPk: types.DefaultParams().CovenantPk, SlashingAddress: types.DefaultParams().SlashingAddress, MinSlashingTxFeeSat: 500, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index eb3625384..5d8cd5962 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -14,9 +14,9 @@ var ( _ sdk.Msg = &MsgUpdateParams{} _ sdk.Msg = &MsgCreateBTCValidator{} _ sdk.Msg = &MsgCreateBTCDelegation{} - _ sdk.Msg = &MsgAddJurySig{} + _ sdk.Msg = &MsgAddCovenantSig{} _ sdk.Msg = &MsgBTCUndelegate{} - _ sdk.Msg = &MsgAddJuryUnbondingSigs{} + _ sdk.Msg = &MsgAddCovenantUnbondingSigs{} _ sdk.Msg = &MsgAddValidatorUnbondingSig{} ) @@ -118,7 +118,7 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { return nil } -func (m *MsgAddJurySig) GetSigners() []sdk.AccAddress { +func (m *MsgAddCovenantSig) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Signer) if err != nil { panic(err) @@ -126,7 +126,7 @@ func (m *MsgAddJurySig) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } -func (m *MsgAddJurySig) ValidateBasic() error { +func (m *MsgAddCovenantSig) ValidateBasic() error { if m.ValPk == nil { return fmt.Errorf("empty BTC validator public key") } @@ -134,7 +134,7 @@ func (m *MsgAddJurySig) ValidateBasic() error { return fmt.Errorf("empty BTC delegation public key") } if m.Sig == nil { - return fmt.Errorf("empty jury signature") + return fmt.Errorf("empty covenant signature") } if len(m.StakingTxHash) != chainhash.MaxHashStringSize { return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) @@ -183,7 +183,7 @@ func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } -func (m *MsgAddJuryUnbondingSigs) GetSigners() []sdk.AccAddress { +func (m *MsgAddCovenantUnbondingSigs) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Signer) if err != nil { panic(err) @@ -191,7 +191,7 @@ func (m *MsgAddJuryUnbondingSigs) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } -func (m *MsgAddJuryUnbondingSigs) ValidateBasic() error { +func (m *MsgAddCovenantUnbondingSigs) ValidateBasic() error { if m.ValPk == nil { return fmt.Errorf("empty BTC validator public key") } @@ -199,10 +199,10 @@ func (m *MsgAddJuryUnbondingSigs) ValidateBasic() error { return fmt.Errorf("empty BTC delegation public key") } if m.UnbondingTxSig == nil { - return fmt.Errorf("empty jury signature") + return fmt.Errorf("empty covenant signature") } if m.SlashingUnbondingTxSig == nil { - return fmt.Errorf("empty jury signature") + return fmt.Errorf("empty covenant signature") } if len(m.StakingTxHash) != chainhash.MaxHashStringSize { return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) @@ -226,7 +226,7 @@ func (m *MsgAddValidatorUnbondingSig) ValidateBasic() error { return fmt.Errorf("empty BTC delegation public key") } if m.UnbondingTxSig == nil { - return fmt.Errorf("empty jury signature") + return fmt.Errorf("empty covenant signature") } if len(m.StakingTxHash) != chainhash.MaxHashStringSize { return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 6fd3c392b..290b456a1 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -15,7 +15,7 @@ import ( var _ paramtypes.ParamSet = (*Params)(nil) -func defaultJuryPk() *bbn.BIP340PubKey { +func defaultCovenantPk() *bbn.BIP340PubKey { // 32 bytes skBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} _, defaultPK := btcec.PrivKeyFromBytes(skBytes) @@ -40,7 +40,7 @@ func ParamKeyTable() paramtypes.KeyTable { // DefaultParams returns a default set of parameters func DefaultParams() Params { return Params{ - JuryPk: defaultJuryPk(), + CovenantPk: defaultCovenantPk(), SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, MinCommissionRate: math.LegacyZeroDec(), diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index b6f007013..fea9b1be5 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -28,9 +28,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { - // jury_pk is the public key of jury + // covenant_pk is the public key of covenant // the PK follows encoding in BIP-340 spec on Bitcoin - JuryPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=jury_pk,json=juryPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"jury_pk,omitempty"` + CovenantPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=covenant_pk,json=covenantPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"covenant_pk,omitempty"` // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin SlashingAddress string `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` @@ -100,32 +100,32 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 389 bytes of a gzipped FileDescriptorProto + // 393 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x24, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xe3, 0x21, 0x12, - 0x10, 0x0e, 0x44, 0x4a, 0xa9, 0x99, 0x99, 0x8b, 0x2d, 0x00, 0x6c, 0xb0, 0x90, 0x3f, 0x17, 0x7b, - 0x56, 0x69, 0x51, 0x65, 0x7c, 0x41, 0xb6, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd9, 0xad, - 0x7b, 0xf2, 0x46, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0x2b, - 0x93, 0x33, 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x3d, 0x27, 0xcf, - 0x00, 0x63, 0x13, 0x83, 0x80, 0xd2, 0x24, 0xef, 0xd4, 0xca, 0x20, 0x36, 0x90, 0x31, 0x01, 0xd9, - 0x42, 0x9a, 0x5c, 0x02, 0xc5, 0x39, 0x89, 0xc5, 0x19, 0x99, 0x79, 0xe9, 0xf1, 0x89, 0x29, 0x29, - 0x45, 0xa9, 0xc5, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xfc, 0x30, 0x71, 0x47, 0x88, - 0xb0, 0x90, 0x09, 0x97, 0x78, 0x6e, 0x66, 0x5e, 0x3c, 0x5c, 0x79, 0x49, 0x45, 0x7c, 0x5a, 0x6a, - 0x6a, 0x7c, 0x71, 0x62, 0x89, 0x04, 0xb3, 0x02, 0xa3, 0x06, 0x73, 0x90, 0x70, 0x6e, 0x66, 0x5e, - 0x30, 0x54, 0x36, 0xa4, 0xc2, 0x2d, 0x35, 0x35, 0x38, 0xb1, 0x44, 0x28, 0x8e, 0x0b, 0x24, 0x1c, - 0x9f, 0x9c, 0x9f, 0x9b, 0x9b, 0x59, 0x5c, 0x9c, 0x99, 0x9f, 0x17, 0x5f, 0x94, 0x58, 0x92, 0x2a, - 0xc1, 0x02, 0xb2, 0xc3, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, 0xe4, 0xd5, 0x90, 0x7c, - 0x00, 0xf1, 0x3a, 0x94, 0xd2, 0x2d, 0x4e, 0xc9, 0x86, 0x3a, 0xdf, 0x25, 0x35, 0x39, 0x48, 0x30, - 0x37, 0x33, 0xcf, 0x19, 0x6e, 0x52, 0x50, 0x62, 0x49, 0xaa, 0x50, 0x22, 0x17, 0x2f, 0xdc, 0x45, - 0x60, 0x93, 0x59, 0xc1, 0x26, 0xdb, 0x90, 0x66, 0xf2, 0xa5, 0x2d, 0xba, 0x5c, 0xd0, 0x30, 0x07, - 0xd9, 0xc3, 0x03, 0x33, 0x12, 0x64, 0x85, 0x15, 0xcb, 0x8c, 0x05, 0xf2, 0x0c, 0x4e, 0x3e, 0x27, - 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, - 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x45, 0x30, 0xfc, 0x2b, 0x90, 0x53, 0x09, - 0xd8, 0xce, 0x24, 0x36, 0x70, 0xd4, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x85, 0xbe, - 0x67, 0x48, 0x02, 0x00, 0x00, + 0x10, 0x0e, 0x44, 0x4a, 0xa9, 0x9b, 0x99, 0x8b, 0x2d, 0x00, 0x6c, 0xb0, 0x50, 0x38, 0x17, 0x77, + 0x72, 0x7e, 0x59, 0x6a, 0x5e, 0x62, 0x5e, 0x49, 0x7c, 0x41, 0xb6, 0x04, 0xa3, 0x02, 0xa3, 0x06, + 0x8f, 0x93, 0xd9, 0xad, 0x7b, 0xf2, 0x46, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, + 0xb9, 0xfa, 0x50, 0x6b, 0x93, 0x33, 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, + 0x62, 0x3d, 0x27, 0xcf, 0x00, 0x63, 0x13, 0x83, 0x80, 0xd2, 0x24, 0xef, 0xd4, 0xca, 0x20, 0x2e, + 0x98, 0x51, 0x01, 0xd9, 0x42, 0x9a, 0x5c, 0x02, 0xc5, 0x39, 0x89, 0xc5, 0x19, 0x99, 0x79, 0xe9, + 0xf1, 0x89, 0x29, 0x29, 0x45, 0xa9, 0xc5, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xfc, + 0x30, 0x71, 0x47, 0x88, 0xb0, 0x90, 0x09, 0x97, 0x78, 0x6e, 0x66, 0x5e, 0x3c, 0x5c, 0x79, 0x49, + 0x45, 0x7c, 0x5a, 0x6a, 0x6a, 0x7c, 0x71, 0x62, 0x89, 0x04, 0xb3, 0x02, 0xa3, 0x06, 0x73, 0x90, + 0x70, 0x6e, 0x66, 0x5e, 0x30, 0x54, 0x36, 0xa4, 0xc2, 0x2d, 0x35, 0x35, 0x38, 0xb1, 0x44, 0x28, + 0x8e, 0x0b, 0x24, 0x1c, 0x9f, 0x9c, 0x9f, 0x9b, 0x9b, 0x59, 0x5c, 0x9c, 0x99, 0x9f, 0x17, 0x5f, + 0x94, 0x58, 0x92, 0x2a, 0xc1, 0x02, 0xb2, 0xc3, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, + 0xe4, 0xd5, 0x90, 0x7c, 0x01, 0x09, 0x02, 0x28, 0xa5, 0x5b, 0x9c, 0x92, 0x0d, 0xf5, 0x82, 0x4b, + 0x6a, 0x72, 0x90, 0x60, 0x6e, 0x66, 0x9e, 0x33, 0xdc, 0xa4, 0xa0, 0xc4, 0x92, 0x54, 0xa1, 0x44, + 0x2e, 0x5e, 0xb8, 0x8b, 0xc0, 0x26, 0xb3, 0x82, 0x4d, 0xb6, 0x21, 0xcd, 0xe4, 0x4b, 0x5b, 0x74, + 0xb9, 0xa0, 0x61, 0x0f, 0xb2, 0x87, 0x07, 0x66, 0x24, 0xc8, 0x0a, 0x2b, 0x96, 0x19, 0x0b, 0xe4, + 0x19, 0x9c, 0x7c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, + 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x8a, 0x60, 0x1c, + 0x54, 0x20, 0xa7, 0x16, 0xb0, 0x9d, 0x49, 0x6c, 0xe0, 0x28, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, + 0xff, 0x67, 0xdc, 0x2d, 0x7f, 0x50, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -180,11 +180,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if m.JuryPk != nil { + if m.CovenantPk != nil { { - size := m.JuryPk.Size() + size := m.CovenantPk.Size() i -= size - if _, err := m.JuryPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.CovenantPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintParams(dAtA, i, uint64(size)) @@ -212,8 +212,8 @@ func (m *Params) Size() (n int) { } var l int _ = l - if m.JuryPk != nil { - l = m.JuryPk.Size() + if m.CovenantPk != nil { + l = m.CovenantPk.Size() n += 1 + l + sovParams(uint64(l)) } l = len(m.SlashingAddress) @@ -267,7 +267,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field JuryPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantPk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -295,8 +295,8 @@ func (m *Params) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.JuryPk = &v - if err := m.JuryPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantPk = &v + if err := m.CovenantPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 7a2e0b177..f5c12dc93 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -165,7 +165,7 @@ type MsgCreateBTCDelegation struct { SlashingTx *BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - // The staking tx output further needs signatures from jury and validator in + // The staking tx output further needs signatures from covenant and validator in // order to be spendable. DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,7,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` } @@ -372,8 +372,8 @@ func (m *MsgBTCUndelegateResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo -// MsgAddJurySig is the message for handling a signature from jury -type MsgAddJurySig struct { +// MsgAddCovenantSig is the message for handling a signature from covenant +type MsgAddCovenantSig struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec @@ -384,23 +384,23 @@ type MsgAddJurySig struct { // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // sig is the signature of the jury + // sig is the signature of the covenant // the signature follows encoding in BIP-340 spec Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` } -func (m *MsgAddJurySig) Reset() { *m = MsgAddJurySig{} } -func (m *MsgAddJurySig) String() string { return proto.CompactTextString(m) } -func (*MsgAddJurySig) ProtoMessage() {} -func (*MsgAddJurySig) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantSig) Reset() { *m = MsgAddCovenantSig{} } +func (m *MsgAddCovenantSig) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantSig) ProtoMessage() {} +func (*MsgAddCovenantSig) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{6} } -func (m *MsgAddJurySig) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantSig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddJurySig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddJurySig.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantSig.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -410,48 +410,48 @@ func (m *MsgAddJurySig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error return b[:n], nil } } -func (m *MsgAddJurySig) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddJurySig.Merge(m, src) +func (m *MsgAddCovenantSig) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantSig.Merge(m, src) } -func (m *MsgAddJurySig) XXX_Size() int { +func (m *MsgAddCovenantSig) XXX_Size() int { return m.Size() } -func (m *MsgAddJurySig) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddJurySig.DiscardUnknown(m) +func (m *MsgAddCovenantSig) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantSig.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddJurySig proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantSig proto.InternalMessageInfo -func (m *MsgAddJurySig) GetSigner() string { +func (m *MsgAddCovenantSig) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgAddJurySig) GetStakingTxHash() string { +func (m *MsgAddCovenantSig) GetStakingTxHash() string { if m != nil { return m.StakingTxHash } return "" } -// MsgAddJurySigResponse is the response for MsgAddJurySig -type MsgAddJurySigResponse struct { +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig +type MsgAddCovenantSigResponse struct { } -func (m *MsgAddJurySigResponse) Reset() { *m = MsgAddJurySigResponse{} } -func (m *MsgAddJurySigResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddJurySigResponse) ProtoMessage() {} -func (*MsgAddJurySigResponse) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantSigResponse) Reset() { *m = MsgAddCovenantSigResponse{} } +func (m *MsgAddCovenantSigResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantSigResponse) ProtoMessage() {} +func (*MsgAddCovenantSigResponse) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{7} } -func (m *MsgAddJurySigResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantSigResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddJurySigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantSigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddJurySigResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantSigResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -461,17 +461,17 @@ func (m *MsgAddJurySigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byt return b[:n], nil } } -func (m *MsgAddJurySigResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddJurySigResponse.Merge(m, src) +func (m *MsgAddCovenantSigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantSigResponse.Merge(m, src) } -func (m *MsgAddJurySigResponse) XXX_Size() int { +func (m *MsgAddCovenantSigResponse) XXX_Size() int { return m.Size() } -func (m *MsgAddJurySigResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddJurySigResponse.DiscardUnknown(m) +func (m *MsgAddCovenantSigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantSigResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddJurySigResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantSigResponse proto.InternalMessageInfo // MsgUpdateParams defines a message for updating btcstaking module parameters. type MsgUpdateParams struct { @@ -570,8 +570,8 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo -// MsgAddJuryUnbondingSigs is the message for handling a signature from jury -type MsgAddJuryUnbondingSigs struct { +// MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant +type MsgAddCovenantUnbondingSigs struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec @@ -582,26 +582,26 @@ type MsgAddJuryUnbondingSigs struct { // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // unbonding_tx_sig is the signature of the jury on the unbonding tx submitted to babylon + // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` - // slashing_unbonding_tx_sig is the signature of the jury on slashing tx corresponding to unbodning tx submitted to babylon + // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon // the signature follows encoding in BIP-340 spec SlashingUnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=slashing_unbonding_tx_sig,json=slashingUnbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_unbonding_tx_sig,omitempty"` } -func (m *MsgAddJuryUnbondingSigs) Reset() { *m = MsgAddJuryUnbondingSigs{} } -func (m *MsgAddJuryUnbondingSigs) String() string { return proto.CompactTextString(m) } -func (*MsgAddJuryUnbondingSigs) ProtoMessage() {} -func (*MsgAddJuryUnbondingSigs) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantUnbondingSigs) Reset() { *m = MsgAddCovenantUnbondingSigs{} } +func (m *MsgAddCovenantUnbondingSigs) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantUnbondingSigs) ProtoMessage() {} +func (*MsgAddCovenantUnbondingSigs) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{10} } -func (m *MsgAddJuryUnbondingSigs) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantUnbondingSigs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddJuryUnbondingSigs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantUnbondingSigs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddJuryUnbondingSigs.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantUnbondingSigs.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -611,48 +611,48 @@ func (m *MsgAddJuryUnbondingSigs) XXX_Marshal(b []byte, deterministic bool) ([]b return b[:n], nil } } -func (m *MsgAddJuryUnbondingSigs) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddJuryUnbondingSigs.Merge(m, src) +func (m *MsgAddCovenantUnbondingSigs) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantUnbondingSigs.Merge(m, src) } -func (m *MsgAddJuryUnbondingSigs) XXX_Size() int { +func (m *MsgAddCovenantUnbondingSigs) XXX_Size() int { return m.Size() } -func (m *MsgAddJuryUnbondingSigs) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddJuryUnbondingSigs.DiscardUnknown(m) +func (m *MsgAddCovenantUnbondingSigs) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantUnbondingSigs.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddJuryUnbondingSigs proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantUnbondingSigs proto.InternalMessageInfo -func (m *MsgAddJuryUnbondingSigs) GetSigner() string { +func (m *MsgAddCovenantUnbondingSigs) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgAddJuryUnbondingSigs) GetStakingTxHash() string { +func (m *MsgAddCovenantUnbondingSigs) GetStakingTxHash() string { if m != nil { return m.StakingTxHash } return "" } -// MsgAddJurySigResponse is the response for MsgAddJurySig -type MsgAddJuryUnbondingSigsResponse struct { +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig +type MsgAddCovenantUnbondingSigsResponse struct { } -func (m *MsgAddJuryUnbondingSigsResponse) Reset() { *m = MsgAddJuryUnbondingSigsResponse{} } -func (m *MsgAddJuryUnbondingSigsResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddJuryUnbondingSigsResponse) ProtoMessage() {} -func (*MsgAddJuryUnbondingSigsResponse) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantUnbondingSigsResponse) Reset() { *m = MsgAddCovenantUnbondingSigsResponse{} } +func (m *MsgAddCovenantUnbondingSigsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantUnbondingSigsResponse) ProtoMessage() {} +func (*MsgAddCovenantUnbondingSigsResponse) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{11} } -func (m *MsgAddJuryUnbondingSigsResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddJuryUnbondingSigsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -662,17 +662,17 @@ func (m *MsgAddJuryUnbondingSigsResponse) XXX_Marshal(b []byte, deterministic bo return b[:n], nil } } -func (m *MsgAddJuryUnbondingSigsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.Merge(m, src) +func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.Merge(m, src) } -func (m *MsgAddJuryUnbondingSigsResponse) XXX_Size() int { +func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Size() int { return m.Size() } -func (m *MsgAddJuryUnbondingSigsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddJuryUnbondingSigsResponse.DiscardUnknown(m) +func (m *MsgAddCovenantUnbondingSigsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddJuryUnbondingSigsResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse proto.InternalMessageInfo // MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker type MsgAddValidatorUnbondingSig struct { @@ -738,7 +738,7 @@ func (m *MsgAddValidatorUnbondingSig) GetStakingTxHash() string { return "" } -// MsgAddJurySigResponse is the response for MsgAddJurySig +// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig type MsgAddValidatorUnbondingSigResponse struct { } @@ -782,12 +782,12 @@ func init() { proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") proto.RegisterType((*MsgBTCUndelegate)(nil), "babylon.btcstaking.v1.MsgBTCUndelegate") proto.RegisterType((*MsgBTCUndelegateResponse)(nil), "babylon.btcstaking.v1.MsgBTCUndelegateResponse") - proto.RegisterType((*MsgAddJurySig)(nil), "babylon.btcstaking.v1.MsgAddJurySig") - proto.RegisterType((*MsgAddJurySigResponse)(nil), "babylon.btcstaking.v1.MsgAddJurySigResponse") + proto.RegisterType((*MsgAddCovenantSig)(nil), "babylon.btcstaking.v1.MsgAddCovenantSig") + proto.RegisterType((*MsgAddCovenantSigResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") - proto.RegisterType((*MsgAddJuryUnbondingSigs)(nil), "babylon.btcstaking.v1.MsgAddJuryUnbondingSigs") - proto.RegisterType((*MsgAddJuryUnbondingSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddJuryUnbondingSigsResponse") + proto.RegisterType((*MsgAddCovenantUnbondingSigs)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigs") + proto.RegisterType((*MsgAddCovenantUnbondingSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigsResponse") proto.RegisterType((*MsgAddValidatorUnbondingSig)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSig") proto.RegisterType((*MsgAddValidatorUnbondingSigResponse)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSigResponse") } @@ -795,77 +795,77 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1110 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0xe3, 0xc4, - 0x1b, 0x6e, 0x92, 0x36, 0x3f, 0xf5, 0x6d, 0xbb, 0xbb, 0x3f, 0xd3, 0x6d, 0xd3, 0xa0, 0x4d, 0x4a, - 0x76, 0x29, 0x65, 0xd5, 0xda, 0x34, 0xbb, 0xad, 0xa0, 0x48, 0x48, 0xeb, 0x16, 0x89, 0xb2, 0x44, - 0x04, 0x27, 0x45, 0x88, 0x03, 0x61, 0x6c, 0x4f, 0x1d, 0x2b, 0x89, 0xc7, 0xf2, 0x4c, 0xaa, 0x44, - 0x48, 0x1c, 0x38, 0x72, 0xe2, 0xc4, 0x05, 0x89, 0xcf, 0xc0, 0x61, 0x4f, 0x1c, 0x38, 0xef, 0x71, - 0xd5, 0x13, 0xea, 0x21, 0x5a, 0xb5, 0x07, 0xbe, 0x06, 0xb2, 0x3d, 0xfe, 0x93, 0x62, 0x77, 0x1b, - 0xc2, 0x09, 0x71, 0x4a, 0xc6, 0xef, 0xf3, 0x3e, 0xef, 0x3b, 0xcf, 0x33, 0x7f, 0x6c, 0x28, 0xa9, - 0x48, 0x1d, 0x76, 0x89, 0x25, 0xa9, 0x4c, 0xa3, 0x0c, 0x75, 0x4c, 0xcb, 0x90, 0x4e, 0x77, 0x24, - 0x36, 0x10, 0x6d, 0x87, 0x30, 0x22, 0xdc, 0xe5, 0x71, 0x31, 0x8a, 0x8b, 0xa7, 0x3b, 0xc5, 0x65, - 0x83, 0x18, 0xc4, 0x43, 0x48, 0xee, 0x3f, 0x1f, 0x5c, 0x5c, 0xd3, 0x08, 0xed, 0x11, 0xda, 0xf2, - 0x03, 0xfe, 0x80, 0x87, 0x56, 0xfd, 0x91, 0xd4, 0xa3, 0x1e, 0x7f, 0x8f, 0x1a, 0x3c, 0x50, 0xe1, - 0x01, 0xcd, 0x19, 0xda, 0x8c, 0x48, 0x14, 0x6b, 0x76, 0x75, 0x77, 0xaf, 0xb3, 0x23, 0x75, 0xf0, - 0x30, 0x48, 0xae, 0x24, 0x37, 0x69, 0x23, 0x07, 0xf5, 0x02, 0xcc, 0x46, 0x32, 0x26, 0xd6, 0xb6, - 0x8f, 0xdb, 0x8a, 0xe1, 0xb4, 0x36, 0xd6, 0x3a, 0x36, 0x31, 0x2d, 0xc6, 0xa1, 0xd1, 0x03, 0x8e, - 0x7e, 0xc0, 0xbb, 0x8b, 0x18, 0x55, 0xcc, 0xd0, 0x8e, 0x34, 0xce, 0x59, 0x4e, 0xe9, 0x8f, 0xd8, - 0x3e, 0xa0, 0xf2, 0x53, 0x0e, 0xee, 0xd6, 0xa8, 0x71, 0xe0, 0x60, 0xc4, 0xb0, 0xdc, 0x3c, 0xf8, - 0x1c, 0x75, 0x4d, 0x1d, 0x31, 0xe2, 0x08, 0x2b, 0x90, 0xa7, 0xa6, 0x61, 0x61, 0xa7, 0x90, 0x59, - 0xcf, 0x6c, 0xce, 0x2b, 0x7c, 0x24, 0x7c, 0x08, 0x0b, 0x3a, 0xa6, 0x9a, 0x63, 0xda, 0xcc, 0x24, - 0x56, 0x21, 0xbb, 0x9e, 0xd9, 0x5c, 0xa8, 0xde, 0x17, 0xb9, 0xa6, 0x91, 0x13, 0x5e, 0x3b, 0xe2, - 0x61, 0x04, 0x55, 0xe2, 0x79, 0xc2, 0x17, 0x00, 0x1a, 0xe9, 0xf5, 0x4c, 0x4a, 0x5d, 0x96, 0x9c, - 0x5b, 0x42, 0x7e, 0xf7, 0x7c, 0x54, 0xde, 0x30, 0x4c, 0xd6, 0xee, 0xab, 0xa2, 0x46, 0x7a, 0x52, - 0x60, 0x80, 0xf7, 0xb3, 0x4d, 0xf5, 0x8e, 0xc4, 0x86, 0x36, 0xa6, 0xe2, 0x21, 0xd6, 0xce, 0x9e, - 0x6d, 0x03, 0x2f, 0x79, 0x88, 0x35, 0x25, 0xc6, 0x25, 0x7c, 0x00, 0xc0, 0x67, 0xdd, 0xb2, 0x3b, - 0x85, 0x59, 0xaf, 0xbf, 0x72, 0xd0, 0x9f, 0x6f, 0xa6, 0x18, 0x9a, 0x29, 0xd6, 0xfb, 0xea, 0x53, - 0x3c, 0x54, 0xe6, 0x79, 0x4a, 0xbd, 0x23, 0xd4, 0x20, 0xaf, 0x32, 0xcd, 0xcd, 0x9d, 0x5b, 0xcf, - 0x6c, 0x2e, 0xca, 0x7b, 0xe7, 0xa3, 0x72, 0x35, 0xd6, 0x15, 0x47, 0x6a, 0x6d, 0x64, 0x5a, 0xc1, - 0x80, 0x37, 0x26, 0x1f, 0xd5, 0x1f, 0x3d, 0x7e, 0x87, 0x53, 0xce, 0xa9, 0x4c, 0xab, 0x77, 0x84, - 0x7d, 0xc8, 0xd9, 0xc4, 0x2e, 0xe4, 0xbd, 0x3e, 0x36, 0xc5, 0xc4, 0x55, 0x2b, 0xd6, 0x1d, 0x42, - 0x4e, 0x3e, 0x3d, 0xa9, 0x13, 0x4a, 0xb1, 0x37, 0x0b, 0xc5, 0x4d, 0xaa, 0x94, 0xe1, 0x5e, 0xa2, - 0x39, 0x0a, 0xa6, 0x36, 0xb1, 0x28, 0xae, 0x8c, 0x72, 0xb0, 0x12, 0x47, 0x1c, 0xe2, 0x2e, 0x36, - 0x90, 0x27, 0x70, 0x9a, 0x7f, 0xe3, 0xf2, 0x64, 0x27, 0x96, 0x87, 0xcf, 0x27, 0xf7, 0x37, 0xe6, - 0x23, 0x1c, 0x01, 0x70, 0x50, 0x8b, 0x0d, 0xb8, 0x35, 0x0f, 0x53, 0x28, 0x64, 0xff, 0xa9, 0xdc, - 0x3c, 0x68, 0x22, 0xdb, 0x21, 0x84, 0x35, 0x07, 0xca, 0x3c, 0x8f, 0x37, 0x07, 0xc2, 0x67, 0x70, - 0x3b, 0xa2, 0x6a, 0x99, 0xd6, 0x09, 0xf1, 0xec, 0x5a, 0xa8, 0xbe, 0x1d, 0xe7, 0x8b, 0x6d, 0x9b, - 0xd3, 0x1d, 0xb1, 0xe9, 0x20, 0x8b, 0x22, 0xcd, 0x95, 0xe7, 0xc8, 0x3a, 0x21, 0xca, 0x52, 0x48, - 0xe7, 0x0e, 0x85, 0x2a, 0x2c, 0xd0, 0x2e, 0xa2, 0x6d, 0xde, 0x5e, 0xde, 0x73, 0xff, 0xff, 0xe7, - 0xa3, 0xf2, 0x92, 0xdc, 0x3c, 0x68, 0xf0, 0x48, 0x73, 0xa0, 0x00, 0x0d, 0xff, 0x0b, 0x5f, 0xc1, - 0x92, 0xee, 0x6b, 0x4e, 0x9c, 0x16, 0x35, 0x8d, 0xc2, 0xff, 0xbc, 0xac, 0xf7, 0xce, 0x47, 0xe5, - 0xdd, 0x49, 0xd6, 0x4c, 0xc3, 0x34, 0x2c, 0xc4, 0xfa, 0x0e, 0x56, 0x16, 0x43, 0xbe, 0x86, 0x69, - 0x54, 0xd6, 0xa1, 0x94, 0xec, 0x6f, 0xb8, 0x04, 0x7e, 0xce, 0xc2, 0x9d, 0x1a, 0x35, 0xe4, 0xe6, - 0xc1, 0xb1, 0xc5, 0x53, 0x71, 0xaa, 0xf9, 0x35, 0x58, 0xec, 0x5b, 0x2a, 0xb1, 0x74, 0x3e, 0xc7, - 0xec, 0xc4, 0x16, 0x2c, 0x84, 0xf9, 0xcd, 0xc1, 0x55, 0xc5, 0x72, 0x37, 0x51, 0x8c, 0xc0, 0x4a, - 0x4c, 0xb1, 0x20, 0xdb, 0x95, 0x6e, 0x76, 0x5a, 0xe9, 0x96, 0x23, 0xe9, 0x38, 0xaf, 0x2b, 0x61, - 0x11, 0x0a, 0x57, 0xf5, 0x09, 0xc5, 0xfb, 0x35, 0x0b, 0x4b, 0x35, 0x6a, 0x3c, 0xd1, 0xf5, 0x8f, - 0xfb, 0xce, 0xb0, 0x61, 0x1a, 0xd7, 0x28, 0x97, 0x3f, 0x45, 0xdd, 0x60, 0xcb, 0x4c, 0x71, 0x2a, - 0x9c, 0xa2, 0xae, 0x7f, 0xc8, 0xe8, 0xd8, 0xa3, 0xcb, 0x4d, 0x47, 0xa7, 0x63, 0x97, 0x6e, 0x63, - 0x6c, 0x37, 0xb4, 0x11, 0x6d, 0x7b, 0x6a, 0xce, 0xc7, 0x96, 0xf8, 0x47, 0x88, 0xb6, 0x85, 0xa7, - 0x90, 0x73, 0x95, 0x9e, 0x9b, 0x56, 0x69, 0x97, 0xa5, 0xb2, 0xea, 0x5d, 0x1d, 0x91, 0x76, 0xa1, - 0xaa, 0x3f, 0x66, 0xe0, 0x76, 0x8d, 0x1a, 0xc7, 0xb6, 0x8e, 0x18, 0xae, 0x7b, 0x77, 0xa1, 0xb0, - 0x07, 0xf3, 0xa8, 0xcf, 0xda, 0xc4, 0x31, 0xd9, 0xd0, 0x97, 0x56, 0x2e, 0x9c, 0x3d, 0xdb, 0x5e, - 0xe6, 0x07, 0xcf, 0x13, 0x5d, 0x77, 0x30, 0xa5, 0x0d, 0xe6, 0x98, 0x96, 0xa1, 0x44, 0x50, 0xe1, - 0x7d, 0xc8, 0xfb, 0xb7, 0x29, 0x5f, 0xab, 0xf7, 0xd2, 0x4e, 0x1c, 0x0f, 0x24, 0xcf, 0x3e, 0x1f, - 0x95, 0x67, 0x14, 0x9e, 0xb2, 0x7f, 0xeb, 0xbb, 0x3f, 0x7e, 0x79, 0x18, 0x91, 0x55, 0xd6, 0x60, - 0xf5, 0x4a, 0x5f, 0x61, 0xcf, 0x67, 0x39, 0x2f, 0xc6, 0x67, 0x73, 0x1c, 0x2c, 0xf2, 0x86, 0x69, - 0xd0, 0x7f, 0xf9, 0x9a, 0xd0, 0xe0, 0x4e, 0xfc, 0x4c, 0x68, 0xfd, 0x23, 0x0b, 0xe4, 0x56, 0xec, - 0x98, 0x70, 0xb7, 0x15, 0x83, 0xb5, 0x70, 0xaf, 0xff, 0xa5, 0x5a, 0x7e, 0xda, 0x6a, 0x2b, 0x01, - 0xf7, 0xf1, 0x58, 0xd5, 0xca, 0x1b, 0x50, 0x4e, 0xf1, 0x34, 0xf4, 0xfd, 0x65, 0x16, 0x5e, 0xf7, - 0x31, 0xe1, 0xed, 0x1a, 0x07, 0xfe, 0xe7, 0xfd, 0xd4, 0xde, 0x57, 0xde, 0x84, 0xfb, 0xd7, 0x28, - 0x1c, 0x38, 0x51, 0xfd, 0x2d, 0x0f, 0xb9, 0x1a, 0x35, 0x84, 0x01, 0x08, 0x09, 0xaf, 0xa3, 0x5b, - 0x29, 0xfb, 0x3e, 0xf1, 0xfd, 0xa8, 0xf8, 0x78, 0x12, 0x74, 0xd0, 0x81, 0xf0, 0x0d, 0xbc, 0x96, - 0xf4, 0x26, 0xb5, 0x7d, 0x03, 0xb2, 0x08, 0x5e, 0xdc, 0x9d, 0x08, 0x1e, 0x16, 0x37, 0x61, 0x69, - 0xfc, 0x0e, 0x7f, 0x2b, 0x9d, 0x67, 0x0c, 0x58, 0x94, 0x6e, 0x08, 0x0c, 0x4b, 0x7d, 0x0d, 0x10, - 0xbb, 0xf1, 0x1e, 0xa4, 0xa7, 0x47, 0xa8, 0xe2, 0xd6, 0x4d, 0x50, 0x61, 0x85, 0x6f, 0x61, 0x39, - 0xf1, 0x24, 0x15, 0x5f, 0xc9, 0x32, 0x86, 0x2f, 0xee, 0x4d, 0x86, 0x0f, 0xeb, 0x7f, 0x9f, 0x81, - 0x42, 0xea, 0x96, 0xae, 0x5e, 0x4b, 0x9a, 0x98, 0x53, 0xdc, 0x9f, 0x3c, 0x27, 0x6c, 0xe6, 0x04, - 0x16, 0xc7, 0xae, 0xc2, 0x8d, 0x74, 0xae, 0x38, 0xae, 0x28, 0xde, 0x0c, 0x17, 0xd4, 0x91, 0x3f, - 0x79, 0x7e, 0x51, 0xca, 0xbc, 0xb8, 0x28, 0x65, 0x5e, 0x5e, 0x94, 0x32, 0x3f, 0x5c, 0x96, 0x66, - 0x5e, 0x5c, 0x96, 0x66, 0x7e, 0xbf, 0x2c, 0xcd, 0x7c, 0xf9, 0xca, 0x93, 0x64, 0x10, 0xff, 0x40, - 0xf4, 0x76, 0xb5, 0x9a, 0xf7, 0x3e, 0x10, 0x1f, 0xfd, 0x19, 0x00, 0x00, 0xff, 0xff, 0x92, 0x66, - 0xef, 0x13, 0x88, 0x0f, 0x00, 0x00, + // 1107 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x3f, 0x6f, 0xdb, 0xc6, + 0x1b, 0xb6, 0x24, 0x5b, 0x3f, 0xf8, 0xf5, 0x9f, 0x24, 0xfc, 0x39, 0x8e, 0xac, 0x20, 0x92, 0xa1, + 0xb4, 0xae, 0x1b, 0xc4, 0x64, 0xac, 0xc4, 0x46, 0xeb, 0x02, 0x05, 0x42, 0xb9, 0x40, 0x8d, 0x54, + 0xa8, 0x4a, 0xc9, 0x45, 0xd1, 0xa1, 0xc2, 0x89, 0x3c, 0x53, 0x84, 0x24, 0x1e, 0xc1, 0x3b, 0x0b, + 0x12, 0xba, 0x75, 0xec, 0xd4, 0xa9, 0x4b, 0x81, 0x7e, 0x86, 0x0e, 0x99, 0x3b, 0x67, 0x0c, 0x32, + 0xb4, 0x85, 0x07, 0x21, 0xb0, 0x87, 0x7e, 0x8d, 0x82, 0xe4, 0xf1, 0x8f, 0x14, 0xd2, 0x91, 0xaa, + 0x4e, 0x45, 0x27, 0xe9, 0x78, 0xcf, 0xfb, 0xbc, 0xef, 0x3d, 0xcf, 0x7b, 0x77, 0x24, 0x14, 0x5a, + 0xa8, 0x35, 0xec, 0x12, 0x53, 0x6a, 0x31, 0x95, 0x32, 0xd4, 0x31, 0x4c, 0x5d, 0xea, 0xef, 0x4b, + 0x6c, 0x20, 0x5a, 0x36, 0x61, 0x44, 0xb8, 0xcd, 0xe7, 0xc5, 0x70, 0x5e, 0xec, 0xef, 0xe7, 0x37, + 0x74, 0xa2, 0x13, 0x17, 0x21, 0x39, 0xff, 0x3c, 0x70, 0x7e, 0x4b, 0x25, 0xb4, 0x47, 0x68, 0xd3, + 0x9b, 0xf0, 0x06, 0x7c, 0xea, 0x8e, 0x37, 0x92, 0x7a, 0xd4, 0xe5, 0xef, 0x51, 0x9d, 0x4f, 0x94, + 0xf8, 0x84, 0x6a, 0x0f, 0x2d, 0x46, 0x24, 0x8a, 0x55, 0xab, 0x7c, 0x70, 0xd8, 0xd9, 0x97, 0x3a, + 0x78, 0xe8, 0x07, 0x97, 0xe2, 0x8b, 0xb4, 0x90, 0x8d, 0x7a, 0x3e, 0x66, 0x27, 0x1e, 0x13, 0x29, + 0xdb, 0xc3, 0x3d, 0x8c, 0xe0, 0xd4, 0x36, 0x56, 0x3b, 0x16, 0x31, 0x4c, 0xc6, 0xa1, 0xe1, 0x03, + 0x8e, 0x7e, 0x87, 0x57, 0x17, 0x32, 0xb6, 0x30, 0x43, 0xfb, 0xd2, 0x38, 0x67, 0x31, 0xa1, 0x3e, + 0x62, 0x79, 0x80, 0xd2, 0x4f, 0x19, 0xb8, 0x5d, 0xa5, 0x7a, 0xc5, 0xc6, 0x88, 0x61, 0xb9, 0x51, + 0xf9, 0x12, 0x75, 0x0d, 0x0d, 0x31, 0x62, 0x0b, 0x9b, 0x90, 0xa5, 0x86, 0x6e, 0x62, 0x3b, 0x97, + 0xda, 0x4e, 0xed, 0x2e, 0x2b, 0x7c, 0x24, 0x7c, 0x02, 0x2b, 0x1a, 0xa6, 0xaa, 0x6d, 0x58, 0xcc, + 0x20, 0x66, 0x2e, 0xbd, 0x9d, 0xda, 0x5d, 0x29, 0xdf, 0x17, 0xb9, 0xa6, 0xa1, 0x13, 0x6e, 0x39, + 0xe2, 0x71, 0x08, 0x55, 0xa2, 0x71, 0xc2, 0x57, 0x00, 0x2a, 0xe9, 0xf5, 0x0c, 0x4a, 0x1d, 0x96, + 0x8c, 0x93, 0x42, 0xfe, 0xe0, 0x62, 0x54, 0xdc, 0xd1, 0x0d, 0xd6, 0x3e, 0x6f, 0x89, 0x2a, 0xe9, + 0x49, 0xbe, 0x01, 0xee, 0xcf, 0x1e, 0xd5, 0x3a, 0x12, 0x1b, 0x5a, 0x98, 0x8a, 0xc7, 0x58, 0x7d, + 0xf5, 0x7c, 0x0f, 0x78, 0xca, 0x63, 0xac, 0x2a, 0x11, 0x2e, 0xe1, 0x63, 0x00, 0xbe, 0xea, 0xa6, + 0xd5, 0xc9, 0x2d, 0xba, 0xf5, 0x15, 0xfd, 0xfa, 0x3c, 0x33, 0xc5, 0xc0, 0x4c, 0xb1, 0x76, 0xde, + 0x7a, 0x86, 0x87, 0xca, 0x32, 0x0f, 0xa9, 0x75, 0x84, 0x2a, 0x64, 0x5b, 0x4c, 0x75, 0x62, 0x97, + 0xb6, 0x53, 0xbb, 0xab, 0xf2, 0xe1, 0xc5, 0xa8, 0x58, 0x8e, 0x54, 0xc5, 0x91, 0x6a, 0x1b, 0x19, + 0xa6, 0x3f, 0xe0, 0x85, 0xc9, 0x27, 0xb5, 0xc7, 0x4f, 0x1e, 0x71, 0xca, 0xa5, 0x16, 0x53, 0x6b, + 0x1d, 0xe1, 0x08, 0x32, 0x16, 0xb1, 0x72, 0x59, 0xb7, 0x8e, 0x5d, 0x31, 0xb6, 0x6b, 0xc5, 0x9a, + 0x4d, 0xc8, 0xd9, 0xe7, 0x67, 0x35, 0x42, 0x29, 0x76, 0x57, 0xa1, 0x38, 0x41, 0xa5, 0x22, 0xdc, + 0x8b, 0x35, 0x47, 0xc1, 0xd4, 0x22, 0x26, 0xc5, 0xa5, 0x51, 0x06, 0x36, 0xa3, 0x88, 0x63, 0xdc, + 0xc5, 0x3a, 0x72, 0x05, 0x4e, 0xf2, 0x6f, 0x5c, 0x9e, 0xf4, 0xcc, 0xf2, 0xf0, 0xf5, 0x64, 0xfe, + 0xc6, 0x7a, 0x84, 0x13, 0x00, 0x0e, 0x6a, 0xb2, 0x01, 0xb7, 0xe6, 0x41, 0x02, 0x85, 0xec, 0x3d, + 0x95, 0x1b, 0x95, 0x06, 0xb2, 0x6c, 0x42, 0x58, 0x63, 0xa0, 0x2c, 0xf3, 0xf9, 0xc6, 0x40, 0xf8, + 0x02, 0x6e, 0x84, 0x54, 0x4d, 0xc3, 0x3c, 0x23, 0xae, 0x5d, 0x2b, 0xe5, 0xf7, 0xa3, 0x7c, 0x91, + 0x6d, 0xd3, 0xdf, 0x17, 0x1b, 0x36, 0x32, 0x29, 0x52, 0x1d, 0x79, 0x4e, 0xcc, 0x33, 0xa2, 0xac, + 0x05, 0x74, 0xce, 0x50, 0x28, 0xc3, 0x0a, 0xed, 0x22, 0xda, 0xe6, 0xe5, 0x65, 0x5d, 0xf7, 0x6f, + 0x5d, 0x8c, 0x8a, 0x6b, 0x72, 0xa3, 0x52, 0xe7, 0x33, 0x8d, 0x81, 0x02, 0x34, 0xf8, 0x2f, 0x7c, + 0x03, 0x6b, 0x9a, 0xa7, 0x39, 0xb1, 0x9b, 0xd4, 0xd0, 0x73, 0xff, 0x73, 0xa3, 0x3e, 0xbc, 0x18, + 0x15, 0x0f, 0x66, 0xe9, 0x99, 0xba, 0xa1, 0x9b, 0x88, 0x9d, 0xdb, 0x58, 0x59, 0x0d, 0xf8, 0xea, + 0x86, 0x5e, 0xda, 0x86, 0x42, 0xbc, 0xbf, 0x41, 0x0b, 0xfc, 0x9c, 0x86, 0x9b, 0x55, 0xaa, 0xcb, + 0x8d, 0xca, 0xa9, 0xc9, 0x43, 0x71, 0xa2, 0xf9, 0x55, 0x58, 0x3d, 0x37, 0x5b, 0xc4, 0xd4, 0xf8, + 0x1a, 0xd3, 0x33, 0x5b, 0xb0, 0x12, 0xc4, 0x37, 0x06, 0x93, 0x8a, 0x65, 0xa6, 0x51, 0x8c, 0xc0, + 0x66, 0x44, 0x31, 0x3f, 0xda, 0x91, 0x6e, 0x71, 0x5e, 0xe9, 0x36, 0x42, 0xe9, 0x38, 0xaf, 0x23, + 0x61, 0x1e, 0x72, 0x93, 0xfa, 0x04, 0xe2, 0xfd, 0x9a, 0x86, 0x5b, 0x55, 0xaa, 0x3f, 0xd5, 0xb4, + 0x0a, 0xe9, 0x63, 0x13, 0x99, 0xac, 0x6e, 0xe8, 0xd7, 0xa8, 0x97, 0xed, 0xa3, 0xae, 0xbf, 0x6d, + 0xe6, 0x38, 0x19, 0xfa, 0xa8, 0xeb, 0x1d, 0x34, 0x1a, 0x76, 0xe9, 0x32, 0xf3, 0xd1, 0x69, 0xd8, + 0xa1, 0xdb, 0x19, 0xdb, 0x11, 0x6d, 0x44, 0xdb, 0xae, 0xa2, 0xcb, 0x91, 0x36, 0xff, 0x14, 0xd1, + 0xb6, 0xf0, 0x0c, 0x32, 0x8e, 0xda, 0x4b, 0xf3, 0xaa, 0xed, 0xb0, 0x94, 0xee, 0xc2, 0xd6, 0x1b, + 0xfa, 0x05, 0xea, 0xfe, 0x98, 0x82, 0x1b, 0x55, 0xaa, 0x9f, 0x5a, 0x1a, 0x62, 0xb8, 0xe6, 0xde, + 0x89, 0xc2, 0x21, 0x2c, 0xa3, 0x73, 0xd6, 0x26, 0xb6, 0xc1, 0x86, 0x9e, 0xbc, 0x72, 0xee, 0xd5, + 0xf3, 0xbd, 0x0d, 0x7e, 0x00, 0x3d, 0xd5, 0x34, 0x1b, 0x53, 0x5a, 0x67, 0xb6, 0x61, 0xea, 0x4a, + 0x08, 0x15, 0x3e, 0x82, 0xac, 0x77, 0xab, 0xf2, 0x9e, 0xbd, 0x97, 0x74, 0xf2, 0xb8, 0x20, 0x79, + 0xf1, 0xc5, 0xa8, 0xb8, 0xa0, 0xf0, 0x90, 0xa3, 0xf5, 0xef, 0xfe, 0xfc, 0xe5, 0x41, 0x48, 0x56, + 0xda, 0x82, 0x3b, 0x13, 0x75, 0x05, 0x35, 0xff, 0x96, 0x81, 0xbb, 0xe3, 0x2b, 0x3a, 0xf5, 0x1b, + 0xbe, 0x6e, 0xe8, 0xf4, 0x5f, 0xde, 0x1b, 0x2a, 0xdc, 0x8c, 0x9e, 0x0f, 0xcd, 0x7f, 0xa4, 0x51, + 0xd6, 0x23, 0x47, 0x86, 0xb3, 0xbd, 0x18, 0x6c, 0x05, 0xfb, 0xfe, 0x8d, 0x6c, 0xd9, 0x79, 0xb3, + 0x6d, 0xfa, 0xdc, 0xa7, 0x63, 0x59, 0x4b, 0xef, 0xc2, 0xfd, 0x6b, 0x7c, 0x0d, 0xfc, 0x7f, 0x9d, + 0xf6, 0xfd, 0x0f, 0x6e, 0xdb, 0x28, 0xf0, 0x3f, 0xff, 0xe7, 0xf6, 0x3f, 0x74, 0x22, 0x56, 0x61, + 0xdf, 0x89, 0xf2, 0xef, 0x59, 0xc8, 0x54, 0xa9, 0x2e, 0x0c, 0x40, 0x88, 0x79, 0x3d, 0x7d, 0x98, + 0xb0, 0xff, 0x63, 0xdf, 0x97, 0xf2, 0x4f, 0x66, 0x41, 0xfb, 0x15, 0x08, 0xdf, 0xc2, 0xff, 0xe3, + 0xde, 0xac, 0xf6, 0xa6, 0x20, 0x0b, 0xe1, 0xf9, 0x83, 0x99, 0xe0, 0x41, 0x72, 0x03, 0xd6, 0xc6, + 0xef, 0xf4, 0xf7, 0x92, 0x79, 0xc6, 0x80, 0x79, 0x69, 0x4a, 0x60, 0x90, 0xaa, 0x0b, 0xeb, 0x13, + 0x37, 0xe0, 0x6e, 0x32, 0xc5, 0x38, 0x32, 0xff, 0x68, 0x5a, 0x64, 0x90, 0xed, 0xfb, 0x14, 0xe4, + 0x12, 0x8f, 0xd7, 0xf2, 0x54, 0x74, 0x63, 0x31, 0xf9, 0xa3, 0xd9, 0x63, 0x26, 0x8b, 0x89, 0xdf, + 0xeb, 0xd7, 0x17, 0x13, 0x1b, 0xf3, 0x96, 0x62, 0xae, 0xed, 0x78, 0xe1, 0x0c, 0x56, 0xc7, 0xee, + 0xca, 0x9d, 0x64, 0xae, 0x28, 0x2e, 0x2f, 0x4e, 0x87, 0xf3, 0xf3, 0xc8, 0x9f, 0xbd, 0xb8, 0x2c, + 0xa4, 0x5e, 0x5e, 0x16, 0x52, 0xaf, 0x2f, 0x0b, 0xa9, 0x1f, 0xae, 0x0a, 0x0b, 0x2f, 0xaf, 0x0a, + 0x0b, 0x7f, 0x5c, 0x15, 0x16, 0xbe, 0x7e, 0xeb, 0x11, 0x33, 0x88, 0x7e, 0x49, 0xba, 0xdb, 0xbd, + 0x95, 0x75, 0xbf, 0x24, 0x1f, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xda, 0x28, 0xdf, 0xb1, + 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -886,12 +886,12 @@ type MsgClient interface { CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation - AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) - // AddJuryUnbondingSigs handles two signatures from jury for: + // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation + AddCovenantSig(ctx context.Context, in *MsgAddCovenantSig, opts ...grpc.CallOption) (*MsgAddCovenantSigResponse, error) + // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker - AddJuryUnbondingSigs(ctx context.Context, in *MsgAddJuryUnbondingSigs, opts ...grpc.CallOption) (*MsgAddJuryUnbondingSigsResponse, error) + AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker AddValidatorUnbondingSig(ctx context.Context, in *MsgAddValidatorUnbondingSig, opts ...grpc.CallOption) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. @@ -933,18 +933,18 @@ func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opt return out, nil } -func (c *msgClient) AddJurySig(ctx context.Context, in *MsgAddJurySig, opts ...grpc.CallOption) (*MsgAddJurySigResponse, error) { - out := new(MsgAddJurySigResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddJurySig", in, out, opts...) +func (c *msgClient) AddCovenantSig(ctx context.Context, in *MsgAddCovenantSig, opts ...grpc.CallOption) (*MsgAddCovenantSigResponse, error) { + out := new(MsgAddCovenantSigResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddCovenantSig", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *msgClient) AddJuryUnbondingSigs(ctx context.Context, in *MsgAddJuryUnbondingSigs, opts ...grpc.CallOption) (*MsgAddJuryUnbondingSigsResponse, error) { - out := new(MsgAddJuryUnbondingSigsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddJuryUnbondingSigs", in, out, opts...) +func (c *msgClient) AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) { + out := new(MsgAddCovenantUnbondingSigsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddCovenantUnbondingSigs", in, out, opts...) if err != nil { return nil, err } @@ -977,12 +977,12 @@ type MsgServer interface { CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // BtcUndelegate undelegates funds from exsitng btc delegation BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) - // AddJurySig handles a signature from jury for slashing tx of staking tx for delegation - AddJurySig(context.Context, *MsgAddJurySig) (*MsgAddJurySigResponse, error) - // AddJuryUnbondingSigs handles two signatures from jury for: + // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation + AddCovenantSig(context.Context, *MsgAddCovenantSig) (*MsgAddCovenantSigResponse, error) + // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker - AddJuryUnbondingSigs(context.Context, *MsgAddJuryUnbondingSigs) (*MsgAddJuryUnbondingSigsResponse, error) + AddCovenantUnbondingSigs(context.Context, *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker AddValidatorUnbondingSig(context.Context, *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. @@ -1002,11 +1002,11 @@ func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *Msg func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCUndelegate not implemented") } -func (*UnimplementedMsgServer) AddJurySig(ctx context.Context, req *MsgAddJurySig) (*MsgAddJurySigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddJurySig not implemented") +func (*UnimplementedMsgServer) AddCovenantSig(ctx context.Context, req *MsgAddCovenantSig) (*MsgAddCovenantSigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddCovenantSig not implemented") } -func (*UnimplementedMsgServer) AddJuryUnbondingSigs(ctx context.Context, req *MsgAddJuryUnbondingSigs) (*MsgAddJuryUnbondingSigsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddJuryUnbondingSigs not implemented") +func (*UnimplementedMsgServer) AddCovenantUnbondingSigs(ctx context.Context, req *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddCovenantUnbondingSigs not implemented") } func (*UnimplementedMsgServer) AddValidatorUnbondingSig(ctx context.Context, req *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddValidatorUnbondingSig not implemented") @@ -1073,38 +1073,38 @@ func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -func _Msg_AddJurySig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddJurySig) +func _Msg_AddCovenantSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddCovenantSig) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).AddJurySig(ctx, in) + return srv.(MsgServer).AddCovenantSig(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/AddJurySig", + FullMethod: "/babylon.btcstaking.v1.Msg/AddCovenantSig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddJurySig(ctx, req.(*MsgAddJurySig)) + return srv.(MsgServer).AddCovenantSig(ctx, req.(*MsgAddCovenantSig)) } return interceptor(ctx, in, info, handler) } -func _Msg_AddJuryUnbondingSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddJuryUnbondingSigs) +func _Msg_AddCovenantUnbondingSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddCovenantUnbondingSigs) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).AddJuryUnbondingSigs(ctx, in) + return srv.(MsgServer).AddCovenantUnbondingSigs(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/AddJuryUnbondingSigs", + FullMethod: "/babylon.btcstaking.v1.Msg/AddCovenantUnbondingSigs", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddJuryUnbondingSigs(ctx, req.(*MsgAddJuryUnbondingSigs)) + return srv.(MsgServer).AddCovenantUnbondingSigs(ctx, req.(*MsgAddCovenantUnbondingSigs)) } return interceptor(ctx, in, info, handler) } @@ -1162,12 +1162,12 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Handler: _Msg_BTCUndelegate_Handler, }, { - MethodName: "AddJurySig", - Handler: _Msg_AddJurySig_Handler, + MethodName: "AddCovenantSig", + Handler: _Msg_AddCovenantSig_Handler, }, { - MethodName: "AddJuryUnbondingSigs", - Handler: _Msg_AddJuryUnbondingSigs_Handler, + MethodName: "AddCovenantUnbondingSigs", + Handler: _Msg_AddCovenantUnbondingSigs_Handler, }, { MethodName: "AddValidatorUnbondingSig", @@ -1509,7 +1509,7 @@ func (m *MsgBTCUndelegateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } -func (m *MsgAddJurySig) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantSig) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1519,12 +1519,12 @@ func (m *MsgAddJurySig) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddJurySig) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSig) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddJurySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1582,7 +1582,7 @@ func (m *MsgAddJurySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgAddJurySigResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantSigResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1592,12 +1592,12 @@ func (m *MsgAddJurySigResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddJurySigResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddJurySigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1668,7 +1668,7 @@ func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *MsgAddJuryUnbondingSigs) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantUnbondingSigs) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1678,12 +1678,12 @@ func (m *MsgAddJuryUnbondingSigs) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddJuryUnbondingSigs) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantUnbondingSigs) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddJuryUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1753,7 +1753,7 @@ func (m *MsgAddJuryUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *MsgAddJuryUnbondingSigsResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantUnbondingSigsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1763,12 +1763,12 @@ func (m *MsgAddJuryUnbondingSigsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddJuryUnbondingSigsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantUnbondingSigsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddJuryUnbondingSigsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantUnbondingSigsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2005,7 +2005,7 @@ func (m *MsgBTCUndelegateResponse) Size() (n int) { return n } -func (m *MsgAddJurySig) Size() (n int) { +func (m *MsgAddCovenantSig) Size() (n int) { if m == nil { return 0 } @@ -2034,7 +2034,7 @@ func (m *MsgAddJurySig) Size() (n int) { return n } -func (m *MsgAddJurySigResponse) Size() (n int) { +func (m *MsgAddCovenantSigResponse) Size() (n int) { if m == nil { return 0 } @@ -2067,7 +2067,7 @@ func (m *MsgUpdateParamsResponse) Size() (n int) { return n } -func (m *MsgAddJuryUnbondingSigs) Size() (n int) { +func (m *MsgAddCovenantUnbondingSigs) Size() (n int) { if m == nil { return 0 } @@ -2100,7 +2100,7 @@ func (m *MsgAddJuryUnbondingSigs) Size() (n int) { return n } -func (m *MsgAddJuryUnbondingSigsResponse) Size() (n int) { +func (m *MsgAddCovenantUnbondingSigsResponse) Size() (n int) { if m == nil { return 0 } @@ -3048,7 +3048,7 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3071,10 +3071,10 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJurySig: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantSig: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJurySig: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantSig: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3267,7 +3267,7 @@ func (m *MsgAddJurySig) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantSigResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3290,10 +3290,10 @@ func (m *MsgAddJurySigResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJurySigResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantSigResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJurySigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantSigResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -3482,7 +3482,7 @@ func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJuryUnbondingSigs) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3505,10 +3505,10 @@ func (m *MsgAddJuryUnbondingSigs) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJuryUnbondingSigs: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantUnbondingSigs: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJuryUnbondingSigs: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantUnbondingSigs: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3736,7 +3736,7 @@ func (m *MsgAddJuryUnbondingSigs) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddJuryUnbondingSigsResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantUnbondingSigsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3759,10 +3759,10 @@ func (m *MsgAddJuryUnbondingSigsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddJuryUnbondingSigsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantUnbondingSigsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddJuryUnbondingSigsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantUnbondingSigsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go index ac33196e6..b8cb5f3b1 100644 --- a/x/btcstaking/types/types.go +++ b/x/btcstaking/types/types.go @@ -8,13 +8,13 @@ import ( type PublicKeyInfo struct { StakerKey *bbn.BIP340PubKey ValidatorKey *bbn.BIP340PubKey - JuryKey *bbn.BIP340PubKey + CovenantKey *bbn.BIP340PubKey } func KeyDataFromScript(scriptData *btcstaking.StakingScriptData) *PublicKeyInfo { return &PublicKeyInfo{ StakerKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.StakerKey), ValidatorKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.ValidatorKey), - JuryKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.JuryKey), + CovenantKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.CovenantKey), } } From 9591bec429dcb7c4fc9fcc8a244c857a75e9e250 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Mon, 13 Nov 2023 13:06:15 +0100 Subject: [PATCH 097/202] Clean up btc lc errors (#101) --- types/retry/retry.go | 3 ++- x/btclightclient/keeper/keeper_test.go | 3 +++ x/btclightclient/types/btc_light_client.go | 6 +++--- x/btclightclient/types/errors.go | 11 +++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/types/retry/retry.go b/types/retry/retry.go index ab66927f3..ec0845c15 100644 --- a/types/retry/retry.go +++ b/types/retry/retry.go @@ -13,6 +13,8 @@ import ( // unrecoverableErrors is a list of errors which are unsafe and should not be retried. var unrecoverableErrors = []error{ btclctypes.ErrHeaderParentDoesNotExist, + btclctypes.ErrChainWithNotEnoughWork, + btclctypes.ErrInvalidHeader, btcctypes.ErrProvidedHeaderDoesNotHaveAncestor, btcctypes.ErrInvalidHeader, btcctypes.ErrNoCheckpointsForPreviousEpoch, @@ -23,7 +25,6 @@ var unrecoverableErrors = []error{ // expectedErrors is a list of errors which can safely be ignored and should not be retried. var expectedErrors = []error{ - btclctypes.ErrDuplicateHeader, btcctypes.ErrDuplicatedSubmission, btcctypes.ErrInvalidHeader, // TODO Add more errors here diff --git a/x/btclightclient/keeper/keeper_test.go b/x/btclightclient/keeper/keeper_test.go index a8a89ff1e..df9d6bb53 100644 --- a/x/btclightclient/keeper/keeper_test.go +++ b/x/btclightclient/keeper/keeper_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "math/rand" "testing" @@ -342,6 +343,7 @@ func FuzzKeeperInsertInvalidChain(f *testing.F) { chain := datagen.NewBTCHeaderChainWithLength(r, 0, 0, 10) errNoParent := blcKeeper.InsertHeaders(ctx, chain.ChainToBytes()[1:]) require.Error(t, errNoParent) + require.True(t, errors.Is(errNoParent, types.ErrHeaderParentDoesNotExist)) // Inserting header chain with invalid header should result in error newChainLength := uint32(datagen.RandomInt(r, 10) + 5) @@ -374,6 +376,7 @@ func FuzzKeeperInsertInvalidChain(f *testing.F) { ) errWorseChain := blcKeeper.InsertHeaders(ctx, chainToChainBytes(worseChain)) require.Error(t, errWorseChain) + require.True(t, errors.Is(errWorseChain, types.ErrChainWithNotEnoughWork)) }) } diff --git a/x/btclightclient/types/btc_light_client.go b/x/btclightclient/types/btc_light_client.go index 3ea54ef4b..f3b36b438 100644 --- a/x/btclightclient/types/btc_light_client.go +++ b/x/btclightclient/types/btc_light_client.go @@ -314,7 +314,7 @@ func (l *BtcLightClient) processNewHeadersChain( ) if err != nil { - return err + return fmt.Errorf("provided header contains invalid header. Error msg: %s: %w", err.Error(), ErrInvalidHeader) } childWork := CalcHeaderWork(h) @@ -378,7 +378,7 @@ func (l *BtcLightClient) InsertHeaders(readStore BtcChainReadStore, headers []*w forkParent, err := readStore.GetHeaderByHash(&parentHash) if err != nil { - return nil, err + return nil, fmt.Errorf("cannot find parent header with hash %s for provided chain: %w", parentHash.String(), ErrHeaderParentDoesNotExist) } forkParentInfo := toLocalInfo(forkParent) @@ -390,7 +390,7 @@ func (l *BtcLightClient) InsertHeaders(readStore BtcChainReadStore, headers []*w tipOfNewChain := store.headers[len(store.headers)-1] if tipOfNewChain.totalWork.LTE(currentTip.totalWork) { - return nil, fmt.Errorf("the new chain has less or equal work than the current tip") + return nil, fmt.Errorf("new chain work %d, is not better than current tip work %d: %w", tipOfNewChain.totalWork, currentTip.totalWork, ErrChainWithNotEnoughWork) } return &InsertResult{ diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index d6dfc1e63..e5c648207 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -9,10 +9,9 @@ import ( // x/btclightclient module sentinel errors var ( ErrHeaderDoesNotExist = errorsmod.Register(ModuleName, 1100, "header does not exist") - ErrDuplicateHeader = errorsmod.Register(ModuleName, 1101, "header with provided hash already exists") - ErrHeaderParentDoesNotExist = errorsmod.Register(ModuleName, 1102, "parent for provided hash is not maintained") - ErrInvalidDifficulty = errorsmod.Register(ModuleName, 1103, "invalid difficulty bits") - ErrEmptyMessage = errorsmod.Register(ModuleName, 1104, "empty message provided") - ErrInvalidProofOfWOrk = errorsmod.Register(ModuleName, 1105, "provided header has invalid proof of work") - ErrHeaderOnFork = errorsmod.Register(ModuleName, 1106, "provided header is on a fork") + ErrHeaderParentDoesNotExist = errorsmod.Register(ModuleName, 1101, "parent for provided hash is not maintained") + ErrEmptyMessage = errorsmod.Register(ModuleName, 1102, "empty message provided") + ErrInvalidProofOfWOrk = errorsmod.Register(ModuleName, 1103, "provided header has invalid proof of work") + ErrInvalidHeader = errorsmod.Register(ModuleName, 1104, "provided header does not satisfy header validation rules") + ErrChainWithNotEnoughWork = errorsmod.Register(ModuleName, 1105, "provided chain has not enough work") ) From da303e54f93b4578a413b7b2dac7f3404f27c06b Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 14 Nov 2023 09:46:01 +1100 Subject: [PATCH 098/202] zoneconcierge: replacing private IBC-Go fork with PostHandler (#100) --- app/app.go | 17 +- go.mod | 62 ++++---- go.sum | 143 +++++++++-------- testutil/datagen/tendermint.go | 12 +- testutil/keeper/zoneconcierge.go | 1 + .../extended-client-keeper/hooks.go | 45 ------ .../extended-client-keeper/keeper.go | 150 ------------------ .../keeper/canonical_chain_indexer_test.go | 8 +- .../keeper/epoch_chain_info_indexer_test.go | 4 +- x/zoneconcierge/keeper/fork_indexer_test.go | 3 +- x/zoneconcierge/keeper/grpc_query_test.go | 18 +-- x/zoneconcierge/keeper/header_handler.go | 68 ++++++++ x/zoneconcierge/keeper/hooks.go | 63 -------- .../keeper/ibc_header_decorator.go | 98 ++++++++++++ x/zoneconcierge/keeper/keeper.go | 3 + x/zoneconcierge/keeper/keeper_test.go | 14 +- x/zoneconcierge/module_test.go | 59 ++++++- x/zoneconcierge/types/expected_keepers.go | 3 +- x/zoneconcierge/types/types.go | 10 ++ 19 files changed, 379 insertions(+), 402 deletions(-) delete mode 100644 x/zoneconcierge/extended-client-keeper/hooks.go delete mode 100644 x/zoneconcierge/extended-client-keeper/keeper.go create mode 100644 x/zoneconcierge/keeper/header_handler.go create mode 100644 x/zoneconcierge/keeper/ibc_header_decorator.go diff --git a/app/app.go b/app/app.go index 33ac735d5..ab83fe029 100644 --- a/app/app.go +++ b/app/app.go @@ -130,7 +130,6 @@ import ( govclient "github.com/cosmos/cosmos-sdk/x/gov/client" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - extendedkeeper "github.com/babylonchain/babylon/x/zoneconcierge/extended-client-keeper" ibcfee "github.com/cosmos/ibc-go/v7/modules/apps/29-fee" ibcfeekeeper "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/keeper" ibcfeetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" @@ -592,6 +591,7 @@ func NewBabylonApp( keys[zctypes.StoreKey], keys[zctypes.MemStoreKey], app.IBCFeeKeeper, + app.IBCKeeper.ClientKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, @@ -605,15 +605,6 @@ func NewBabylonApp( scopedZoneConciergeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - - // replace IBC keeper's client keeper with our ExtendedKeeper - extendedClientKeeper := extendedkeeper.NewExtendedKeeper(appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper) - // make zcKeeper to hooks onto extendedClientKeeper so that zcKeeper can receive notifications of new headers - extendedClientKeeper = *extendedClientKeeper.SetHooks( - extendedkeeper.NewMultiClientHooks(zcKeeper.Hooks()), - ) - app.IBCKeeper.ClientKeeper = extendedClientKeeper - app.ZoneConciergeKeeper = *zcKeeper // Create Transfer Keepers @@ -939,6 +930,12 @@ func NewBabylonApp( ) app.SetAnteHandler(anteHandler) + // set postHandler + postHandler := sdk.ChainPostDecorators( + zckeeper.NewIBCHeaderDecorator(app.ZoneConciergeKeeper), + ) + app.SetPostHandler(postHandler) + // must be before Loading version // requires the snapshot store to be created and registered as a BaseAppOption // see cmd/wasmd/root.go: 206 - 214 approx diff --git a/go.mod b/go.mod index b221a8a12..d5fa83621 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 - github.com/cosmos/cosmos-sdk v0.47.3 + github.com/cosmos/cosmos-sdk v0.47.5 github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 @@ -18,15 +18,15 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/supranational/blst v0.3.8 - google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect - google.golang.org/grpc v1.55.0 + google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/grpc v1.56.2 gopkg.in/yaml.v2 v2.4.0 ) require ( cosmossdk.io/api v0.3.1 - cosmossdk.io/errors v1.0.0-beta.7 - cosmossdk.io/math v1.0.1 + cosmossdk.io/errors v1.0.0 + cosmossdk.io/math v1.1.2 cosmossdk.io/tools/rosetta v0.2.1 github.com/CosmWasm/wasmvm v1.2.3 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d @@ -35,14 +35,14 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/gogoproto v1.4.10 - github.com/cosmos/ibc-go/v7 v7.0.1 + github.com/cosmos/ibc-go/v7 v7.3.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/golang/mock v1.6.0 github.com/jinzhu/copier v0.3.5 github.com/ory/dockertest/v3 v3.9.1 github.com/vulpine-io/io-test v1.0.0 - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc - google.golang.org/protobuf v1.30.0 + google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 + google.golang.org/protobuf v1.31.0 ) require ( @@ -93,7 +93,7 @@ require ( github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect @@ -116,23 +116,23 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.12.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect nhooyr.io/websocket v1.8.6 // indirect ) require ( - cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go v0.110.4 // indirect + cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/storage v1.29.0 // indirect + cloud.google.com/go/iam v1.1.0 // indirect + cloud.google.com/go/storage v1.30.1 // indirect cosmossdk.io/core v0.5.1 // indirect - cosmossdk.io/depinject v1.0.0-alpha.3 // indirect - cosmossdk.io/log v1.1.0 // indirect + cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + cosmossdk.io/log v1.2.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -141,6 +141,9 @@ require ( github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.10.0 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect @@ -155,17 +158,18 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect + github.com/getsentry/sentry-go v0.23.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.3 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.8.0 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -173,6 +177,8 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/linxGnu/grocksdb v1.7.16 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -183,7 +189,8 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/runc v1.1.5 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/rs/zerolog v1.29.1 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/zerolog v1.30.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect @@ -192,15 +199,15 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/zondax/ledger-go v0.14.1 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.122.0 // indirect + google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect pgregory.net/rapid v0.5.5 // indirect sigs.k8s.io/yaml v1.3.0 // indirect @@ -211,7 +218,6 @@ replace ( // slay the dragonberry github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/ibc-go/v7 => github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 diff --git a/go.sum b/go.sum index 64fb56bb8..b77f2a223 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -70,8 +70,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -111,13 +111,12 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= +cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -175,8 +174,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -192,14 +191,14 @@ cosmossdk.io/api v0.3.1 h1:NNiOclKRR0AOlO4KIqeaG6PS6kswOMhHD0ir0SscNXE= cosmossdk.io/api v0.3.1/go.mod h1:DfHfMkiNA2Uhy8fj0JJlOCYOBp4eWUUJ1te5zBGNyIw= cosmossdk.io/core v0.5.1 h1:vQVtFrIYOQJDV3f7rw4pjjVqc1id4+mE0L9hHP66pyI= cosmossdk.io/core v0.5.1/go.mod h1:KZtwHCLjcFuo0nmDc24Xy6CRNEL9Vl/MeimQ2aC7NLE= -cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= -cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= -cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/log v1.1.0 h1:v0ogPHYeTzPcBTcPR1A3j1hkei4pZama8kz8LKlCMv0= -cosmossdk.io/log v1.1.0/go.mod h1:6zjroETlcDs+mm62gd8Ig7mZ+N+fVOZS91V17H+M4N4= -cosmossdk.io/math v1.0.1 h1:Qx3ifyOPaMLNH/89WeZFH268yCvU4xEcnPLu3sJqPPg= -cosmossdk.io/math v1.0.1/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= +cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc= +cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= +cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= +cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4= +cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= +cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -230,7 +229,6 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -252,8 +250,6 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61 h1:0NPV8yfawKAYrw96b2ZR70QHIAwB1QcKWyf2BWcRvmU= -github.com/babylonchain/ibc-go/v7 v7.0.0-20230726130104-6d9787ab5b61/go.mod h1:OOcjKIRku/j1Xs1RgKK0yvKRrJ5iFuZYMetR1n3yMlc= 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= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -332,8 +328,13 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= -github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU= +github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= @@ -355,8 +356,8 @@ github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= github.com/cosmos/cosmos-proto v1.0.0-beta.2 h1:X3OKvWgK9Gsejo0F1qs5l8Qn6xJV/AzgIWR2wZ8Nua8= github.com/cosmos/cosmos-proto v1.0.0-beta.2/go.mod h1:+XRCLJ14pr5HFEHIUcn51IKXD1Fy3rkEQqt4WqmN4V0= -github.com/cosmos/cosmos-sdk v0.47.3 h1:r0hGmZoAzP2D+MaPaFGHwAaTdFQq3pNpHaUp1BsffbM= -github.com/cosmos/cosmos-sdk v0.47.3/go.mod h1:c4OfLdAykA9zsj1CqrxBRqXzVz48I++JSvIMPSPcEmk= +github.com/cosmos/cosmos-sdk v0.47.5 h1:n1+WjP/VM/gAEOx3TqU2/Ny734rj/MX1kpUnn7zVJP8= +github.com/cosmos/cosmos-sdk v0.47.5/go.mod h1:EHwCeN9IXonsjKcjpS12MqeStdZvIdxt3VYXhus3G3c= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= @@ -369,6 +370,8 @@ github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoK github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= +github.com/cosmos/ibc-go/v7 v7.3.1 h1:bil1IjnHdyWDASFYKfwdRiNtFP6WK3osW7QFEAgU4I8= +github.com/cosmos/ibc-go/v7 v7.3.1/go.mod h1:wvx4pPBofe5ZdMNV3OFRxSI4auEP5Qfqf8JXLLNV04g= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU= @@ -383,9 +386,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbdT9+Q5kgLpmmsHYl0= -github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= @@ -458,11 +460,14 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= +github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -479,12 +484,12 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -498,7 +503,6 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= @@ -600,8 +604,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -622,8 +626,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -756,9 +760,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= @@ -784,8 +790,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -883,6 +889,8 @@ github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6 github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -929,17 +937,18 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= -github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= +github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1093,8 +1102,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1106,8 +1115,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1134,8 +1143,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1198,8 +1207,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1241,7 +1250,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1348,13 +1357,13 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1366,8 +1375,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1494,8 +1503,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1613,12 +1622,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= +google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk= +google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1660,8 +1669,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1678,8 +1687,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1713,7 +1722,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/testutil/datagen/tendermint.go b/testutil/datagen/tendermint.go index 928bb3f4f..a0a44edef 100644 --- a/testutil/datagen/tendermint.go +++ b/testutil/datagen/tendermint.go @@ -4,7 +4,7 @@ import ( "math/rand" "time" - extendedkeeper "github.com/babylonchain/babylon/x/zoneconcierge/extended-client-keeper" + zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" ) @@ -30,10 +30,10 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp } } -func HeaderToHeaderInfo(header *ibctmtypes.Header) *extendedkeeper.HeaderInfo { - return &extendedkeeper.HeaderInfo{ - Hash: header.Header.LastCommitHash, - ChaindId: header.Header.ChainID, - Height: uint64(header.Header.Height), +func HeaderToHeaderInfo(header *ibctmtypes.Header) *zctypes.HeaderInfo { + return &zctypes.HeaderInfo{ + Hash: header.Header.LastCommitHash, + ChainId: header.Header.ChainID, + Height: uint64(header.Header.Height), } } diff --git a/testutil/keeper/zoneconcierge.go b/testutil/keeper/zoneconcierge.go index 5d11eff1d..819d1348a 100644 --- a/testutil/keeper/zoneconcierge.go +++ b/testutil/keeper/zoneconcierge.go @@ -86,6 +86,7 @@ func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, c storeKey, memStoreKey, nil, // TODO: mock this keeper + nil, // TODO: mock this keeper zoneconciergeChannelKeeper{}, zoneconciergePortKeeper{}, nil, // TODO: mock this keeper diff --git a/x/zoneconcierge/extended-client-keeper/hooks.go b/x/zoneconcierge/extended-client-keeper/hooks.go deleted file mode 100644 index 516e7ecac..000000000 --- a/x/zoneconcierge/extended-client-keeper/hooks.go +++ /dev/null @@ -1,45 +0,0 @@ -package extended_client_keeper - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// ClientHooks defines the hook interface for client -type ClientHooks interface { - AfterHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *HeaderInfo, isOnFork bool) -} - -type HeaderInfo struct { - Hash []byte - ChaindId string - Height uint64 - Time time.Time -} - -// MultiClientHooks is a concrete implementation of ClientHooks -// It allows other modules to hook onto client ExtendedKeeper -var _ ClientHooks = &MultiClientHooks{} - -type MultiClientHooks []ClientHooks - -func NewMultiClientHooks(hooks ...ClientHooks) MultiClientHooks { - return hooks -} - -// invoke hooks in each keeper that hooks onto ExtendedKeeper -func (h MultiClientHooks) AfterHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *HeaderInfo, isOnFork bool) { - for i := range h { - h[i].AfterHeaderWithValidCommit(ctx, txHash, header, isOnFork) - } -} - -// ensure ExtendedKeeper implements ClientHooks interfaces -var _ ClientHooks = ExtendedKeeper{} - -func (ek ExtendedKeeper) AfterHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *HeaderInfo, isOnFork bool) { - if ek.hooks != nil { - ek.hooks.AfterHeaderWithValidCommit(ctx, txHash, header, isOnFork) - } -} diff --git a/x/zoneconcierge/extended-client-keeper/keeper.go b/x/zoneconcierge/extended-client-keeper/keeper.go deleted file mode 100644 index 68474423c..000000000 --- a/x/zoneconcierge/extended-client-keeper/keeper.go +++ /dev/null @@ -1,150 +0,0 @@ -package extended_client_keeper - -import ( - sdkerrors "cosmossdk.io/errors" - metrics "github.com/armon/go-metrics" - "github.com/cometbft/cometbft/crypto/tmhash" - "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - clientkeeper "github.com/cosmos/ibc-go/v7/modules/core/02-client/keeper" - "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" -) - -// ExtendedKeeper is same as the original clientkeeper.Keeper, except that -// - it provides hooks for notifying other modules on received headers -// - it applies different verification rules on received headers -// (notably, intercepting headers rather than freezing clients upon errors that indicate dishonest majority) -type ExtendedKeeper struct { - clientkeeper.Keeper - cdc codec.BinaryCodec // since some code needs to use k.cdc - hooks ClientHooks -} - -// GetHeaderInfo returns the information necessary for header timestamping or nil -// if provided message is not a header -func GetHeaderInfo(ctx sdk.Context, m exported.ClientMessage) *HeaderInfo { - switch msg := m.(type) { - case *ibctmtypes.Header: - return &HeaderInfo{ - Hash: msg.Header.LastCommitHash, - ChaindId: msg.Header.ChainID, - Height: uint64(msg.Header.Height), - Time: msg.Header.Time, - } - default: - return nil - } -} - -// NewExtendedKeeper creates a new NewExtendedKeeper instance -func NewExtendedKeeper(cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper) ExtendedKeeper { - // set KeyTable if it has not already been set - if !paramSpace.HasKeyTable() { - paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) - } - - k := clientkeeper.NewKeeper(cdc, key, paramSpace, sk, uk) - return ExtendedKeeper{ - Keeper: k, - cdc: cdc, - hooks: nil, - } -} - -// SetHooks sets the hooks for ExtendedKeeper -func (ek *ExtendedKeeper) SetHooks(ch ClientHooks) *ExtendedKeeper { - if ek.hooks != nil { - panic("cannot set hooks twice") - } - ek.hooks = ch - - return ek -} - -// UpdateClient updates the consensus state and the state root from a provided header. -// The implementation is the same as the original IBC-Go implementation, except from: -// 1. Not freezing the client when finding a misbehaviour for header message -// 2. Calling a AfterHeaderWithValidCommit callback when receiving valid header messages (either misbehaving or not) -func (k ExtendedKeeper) UpdateClient(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { - // In case of nil message nothing changes in comparison to the original IBC-Go implementation - if clientMsg == nil { - return k.Keeper.UpdateClient(ctx, clientID, clientMsg) - } - - clientState, found := k.GetClientState(ctx, clientID) - if !found { - return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) - } - - clientStore := k.ClientStore(ctx, clientID) - - if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { - return sdkerrors.Wrapf(types.ErrClientNotActive, "cannot update client (%s) with status %s", clientID, status) - } - - if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, clientMsg); err != nil { - return err - } - - foundMisbehaviour := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, clientMsg) - - headerInfo := GetHeaderInfo(ctx, clientMsg) - - // found misbehaviour and it was not an header, freeze client - if foundMisbehaviour && headerInfo == nil { - clientState.UpdateStateOnMisbehaviour(ctx, k.cdc, clientStore, clientMsg) - - k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) - - defer telemetry.IncrCounterWithLabels( - []string{"ibc", "client", "misbehaviour"}, - 1, - []metrics.Label{ - telemetry.NewLabel(types.LabelClientType, clientState.ClientType()), - telemetry.NewLabel(types.LabelClientID, clientID), - telemetry.NewLabel(types.LabelMsgType, "update"), - }, - ) - - clientkeeper.EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) - - return nil - } else if foundMisbehaviour && headerInfo != nil { - // found misbehaviour and it was an header, this is most probably means - // conflicting headers misbehaviour. - ctx.Logger().Debug("received a header that has QC but is on a fork") - txHash := tmhash.Sum(ctx.TxBytes()) - k.AfterHeaderWithValidCommit(ctx, txHash, headerInfo, true) - return nil - } - - // there was no misbehaviour and we receivied an header, call the callback - if headerInfo != nil { - txHash := tmhash.Sum(ctx.TxBytes()) // get hash of the tx that includes this header - k.AfterHeaderWithValidCommit(ctx, txHash, headerInfo, false) - } - - consensusHeights := clientState.UpdateState(ctx, k.cdc, clientStore, clientMsg) - - k.Logger(ctx).Info("client state updated", "client-id", clientID, "heights", consensusHeights) - - defer telemetry.IncrCounterWithLabels( - []string{"ibc", "client", "update"}, - 1, - []metrics.Label{ - telemetry.NewLabel(types.LabelClientType, clientState.ClientType()), - telemetry.NewLabel(types.LabelClientID, clientID), - telemetry.NewLabel(types.LabelUpdateType, "msg"), - }, - ) - - // emitting events in the keeper emits for both begin block and handler client updates - clientkeeper.EmitUpdateClientEvent(ctx, clientID, clientState.ClientType(), consensusHeights, k.cdc, clientMsg) - - return nil -} diff --git a/x/zoneconcierge/keeper/canonical_chain_indexer_test.go b/x/zoneconcierge/keeper/canonical_chain_indexer_test.go index 365854120..e0f00c845 100644 --- a/x/zoneconcierge/keeper/canonical_chain_indexer_test.go +++ b/x/zoneconcierge/keeper/canonical_chain_indexer_test.go @@ -18,11 +18,10 @@ func FuzzCanonicalChainIndexer(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 - headers := SimulateHeadersViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders) + headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders) // check if the canonical chain index is correct or not for i := uint64(0); i < numHeaders; i++ { @@ -54,7 +53,6 @@ func FuzzFindClosestHeader(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // no header at the moment, FindClosestHeader invocation should give error _, err := zcKeeper.FindClosestHeader(ctx, czChain.ChainID, 100) @@ -62,7 +60,7 @@ func FuzzFindClosestHeader(f *testing.F) { // simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 - headers := SimulateHeadersViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders) + headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders) header, err := zcKeeper.FindClosestHeader(ctx, czChain.ChainID, numHeaders) require.NoError(t, err) @@ -73,7 +71,7 @@ func FuzzFindClosestHeader(f *testing.F) { // simulate a random number of blocks // where the new batch of headers has a gap with the previous batch - SimulateHeadersViaHook(ctx, r, hooks, czChain.ChainID, numHeaders+gap+1, numHeaders) + SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, numHeaders+gap+1, numHeaders) // get a random height that is in this gap randomHeightInGap := datagen.RandomInt(r, int(gap+1)) + numHeaders diff --git a/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go b/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go index db7f71cfe..cbe7fc35e 100644 --- a/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go +++ b/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go @@ -31,7 +31,7 @@ func FuzzEpochChainInfoIndexer(f *testing.F) { // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders, numForkHeaders) + SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) // end this epoch hooks.AfterEpochEnds(ctx, epochNum) @@ -85,7 +85,7 @@ func FuzzGetEpochHeaders(f *testing.F) { numHeadersList = append(numHeadersList, datagen.RandomInt(r, 100)+1) numForkHeadersList = append(numForkHeadersList, datagen.RandomInt(r, 10)+1) // trigger hooks to append these headers and fork headers - expectedHeaders, _ := SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) + expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) expectedHeadersMap[epochNum] = expectedHeaders // prepare nextHeight for the next request nextHeightList = append(nextHeightList, nextHeightList[i]+numHeadersList[i]) diff --git a/x/zoneconcierge/keeper/fork_indexer_test.go b/x/zoneconcierge/keeper/fork_indexer_test.go index 769df4db5..a5175b17e 100644 --- a/x/zoneconcierge/keeper/fork_indexer_test.go +++ b/x/zoneconcierge/keeper/fork_indexer_test.go @@ -18,12 +18,11 @@ func FuzzForkIndexer(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - _, forkHeaders := SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders, numForkHeaders) + _, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) // check if the fork is updated or not forks := zcKeeper.GetForks(ctx, czChain.ChainID, numHeaders-1) diff --git a/x/zoneconcierge/keeper/grpc_query_test.go b/x/zoneconcierge/keeper/grpc_query_test.go index 41b4c6004..1b3f81327 100644 --- a/x/zoneconcierge/keeper/grpc_query_test.go +++ b/x/zoneconcierge/keeper/grpc_query_test.go @@ -37,7 +37,6 @@ func FuzzChainList(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // invoke the hook a random number of times with random chain IDs numHeaders := datagen.RandomInt(r, 100) + 1 @@ -52,7 +51,7 @@ func FuzzChainList(f *testing.F) { allChainIDs = append(allChainIDs, chainID) } header := datagen.GenRandomIBCTMHeader(r, chainID, 0) - hooks.AfterHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) + zcKeeper.HandleHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) } limit := datagen.RandomInt(r, len(allChainIDs)) + 1 @@ -84,7 +83,6 @@ func FuzzChainsInfo(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() var ( chainsInfo []chainInfo @@ -95,7 +93,7 @@ func FuzzChainsInfo(f *testing.F) { chainID := datagen.GenRandomHexStr(r, 30) numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - SimulateHeadersAndForksViaHook(ctx, r, hooks, chainID, 0, numHeaders, numForkHeaders) + SimulateNewHeadersAndForks(ctx, r, &zcKeeper, chainID, 0, numHeaders, numForkHeaders) chainIDs = append(chainIDs, chainID) chainsInfo = append(chainsInfo, chainInfo{ @@ -128,12 +126,11 @@ func FuzzHeader(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 2 numForkHeaders := datagen.RandomInt(r, 10) + 1 - headers, forkHeaders := SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders, numForkHeaders) + headers, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) // find header at a random height and assert correctness against the expected header randomHeight := datagen.RandomInt(r, int(numHeaders-1)) @@ -192,7 +189,7 @@ func FuzzEpochChainsInfo(f *testing.F) { numForkHeaders := datagen.RandomInt(r, 10) + 1 // trigger hooks to append these headers and fork headers - SimulateHeadersAndForksViaHook(ctx, r, hooks, chainID, chainHeaderStartHeights[j], numHeaders, numForkHeaders) + SimulateNewHeadersAndForks(ctx, r, &zcKeeper, chainID, chainHeaderStartHeights[j], numHeaders, numForkHeaders) epochToChainInfo[epochNum][chainID] = chainInfo{ chainID: chainID, @@ -260,12 +257,11 @@ func FuzzListHeaders(f *testing.F) { zcKeeper := babylonApp.ZoneConciergeKeeper ctx := babylonChain.GetContext() - hooks := zcKeeper.Hooks() // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - headers, _ := SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, 0, numHeaders, numForkHeaders) + headers, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) // a request with randomised pagination limit := datagen.RandomInt(r, int(numHeaders)) + 1 @@ -324,7 +320,7 @@ func FuzzListEpochHeaders(f *testing.F) { numHeadersList = append(numHeadersList, datagen.RandomInt(r, 100)+1) numForkHeadersList = append(numForkHeadersList, datagen.RandomInt(r, 10)+1) // trigger hooks to append these headers and fork headers - expectedHeaders, _ := SimulateHeadersAndForksViaHook(ctx, r, hooks, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) + expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) expectedHeadersMap[epochNum] = expectedHeaders // prepare nextHeight for the next request nextHeightList = append(nextHeightList, nextHeightList[i]+numHeadersList[i]) @@ -424,7 +420,7 @@ func FuzzFinalizedChainInfo(f *testing.F) { // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - SimulateHeadersAndForksViaHook(ctx, r, hooks, czChainID, 0, numHeaders, numForkHeaders) + SimulateNewHeadersAndForks(ctx, r, zcKeeper, czChainID, 0, numHeaders, numForkHeaders) chainIDs = append(chainIDs, czChainID) chainsInfo = append(chainsInfo, chainInfo{ diff --git a/x/zoneconcierge/keeper/header_handler.go b/x/zoneconcierge/keeper/header_handler.go new file mode 100644 index 000000000..78d259d3c --- /dev/null +++ b/x/zoneconcierge/keeper/header_handler.go @@ -0,0 +1,68 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/x/zoneconcierge/types" +) + +// HandleHeaderWithValidCommit handles a CZ header with a valid QC +func (k Keeper) HandleHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *types.HeaderInfo, isOnFork bool) { + babylonHeader := ctx.BlockHeader() + indexedHeader := types.IndexedHeader{ + ChainId: header.ChainId, + Hash: header.Hash, + Height: header.Height, + Time: &header.Time, + BabylonHeader: &babylonHeader, + BabylonEpoch: k.GetEpoch(ctx).EpochNumber, + BabylonTxHash: txHash, + } + + var ( + chainInfo *types.ChainInfo + err error + ) + if !k.HasChainInfo(ctx, indexedHeader.ChainId) { + // chain info does not exist yet, initialise chain info for this chain + chainInfo, err = k.InitChainInfo(ctx, indexedHeader.ChainId) + if err != nil { + panic(fmt.Errorf("failed to initialize chain info of %s: %w", indexedHeader.ChainId, err)) + } + } else { + // get chain info + chainInfo, err = k.GetChainInfo(ctx, indexedHeader.ChainId) + if err != nil { + panic(fmt.Errorf("failed to get chain info of %s: %w", indexedHeader.ChainId, err)) + } + } + + if isOnFork { + // insert header to fork index + if err := k.insertForkHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { + panic(err) + } + // update the latest fork in chain info + if err := k.tryToUpdateLatestForkHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { + panic(err) + } + } else { + // ensure the header is the latest one, otherwise ignore it + // NOTE: while an old header is considered acceptable in IBC-Go (see Case_valid_past_update), but + // ZoneConcierge should not checkpoint it since Babylon requires monotonic checkpointing + if !chainInfo.IsLatestHeader(&indexedHeader) { + return + } + + // insert header to canonical chain index + if err := k.insertHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { + panic(err) + } + // update the latest canonical header in chain info + if err := k.updateLatestHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { + panic(err) + } + } +} diff --git a/x/zoneconcierge/keeper/hooks.go b/x/zoneconcierge/keeper/hooks.go index 1c96005e1..ebbfedb97 100644 --- a/x/zoneconcierge/keeper/hooks.go +++ b/x/zoneconcierge/keeper/hooks.go @@ -1,13 +1,10 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - extendedkeeper "github.com/babylonchain/babylon/x/zoneconcierge/extended-client-keeper" "github.com/babylonchain/babylon/x/zoneconcierge/types" ) @@ -16,71 +13,11 @@ type Hooks struct { } // ensures Hooks implements ClientHooks interfaces -var _ extendedkeeper.ClientHooks = Hooks{} var _ checkpointingtypes.CheckpointingHooks = Hooks{} var _ epochingtypes.EpochingHooks = Hooks{} func (k Keeper) Hooks() Hooks { return Hooks{k} } -// AfterHeaderWithValidCommit is triggered upon each CZ header with a valid QC -func (h Hooks) AfterHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *extendedkeeper.HeaderInfo, isOnFork bool) { - babylonHeader := ctx.BlockHeader() - indexedHeader := types.IndexedHeader{ - ChainId: header.ChaindId, - Hash: header.Hash, - Height: header.Height, - Time: &header.Time, - BabylonHeader: &babylonHeader, - BabylonEpoch: h.k.GetEpoch(ctx).EpochNumber, - BabylonTxHash: txHash, - } - - var ( - chainInfo *types.ChainInfo - err error - ) - if !h.k.HasChainInfo(ctx, indexedHeader.ChainId) { - // chain info does not exist yet, initialise chain info for this chain - chainInfo, err = h.k.InitChainInfo(ctx, indexedHeader.ChainId) - if err != nil { - panic(fmt.Errorf("failed to initialize chain info of %s: %w", indexedHeader.ChainId, err)) - } - } else { - // get chain info - chainInfo, err = h.k.GetChainInfo(ctx, indexedHeader.ChainId) - if err != nil { - panic(fmt.Errorf("failed to get chain info of %s: %w", indexedHeader.ChainId, err)) - } - } - - if isOnFork { - // insert header to fork index - if err := h.k.insertForkHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { - panic(err) - } - // update the latest fork in chain info - if err := h.k.tryToUpdateLatestForkHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { - panic(err) - } - } else { - // ensure the header is the latest one, otherwise ignore it - // NOTE: while an old header is considered acceptable in IBC-Go (see Case_valid_past_update), but - // ZoneConcierge should not checkpoint it since Babylon requires monotonic checkpointing - if !chainInfo.IsLatestHeader(&indexedHeader) { - return - } - - // insert header to canonical chain index - if err := h.k.insertHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { - panic(err) - } - // update the latest canonical header in chain info - if err := h.k.updateLatestHeader(ctx, indexedHeader.ChainId, &indexedHeader); err != nil { - panic(err) - } - } -} - // AfterEpochEnds is triggered upon an epoch has ended func (h Hooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) { // upon an epoch has ended, index the current chain info for each CZ diff --git a/x/zoneconcierge/keeper/ibc_header_decorator.go b/x/zoneconcierge/keeper/ibc_header_decorator.go new file mode 100644 index 000000000..cea7ffb9f --- /dev/null +++ b/x/zoneconcierge/keeper/ibc_header_decorator.go @@ -0,0 +1,98 @@ +package keeper + +import ( + "github.com/babylonchain/babylon/x/zoneconcierge/types" + "github.com/cometbft/cometbft/crypto/tmhash" + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) (*types.HeaderInfo, *ibctmtypes.ClientState) { + // ensure the message is MsgUpdateClient + msgUpdateClient, ok := m.(*clienttypes.MsgUpdateClient) + if !ok { + return nil, nil + } + // unpack ClientMsg inside MsgUpdateClient + clientMsg, err := clienttypes.UnpackClientMessage(msgUpdateClient.ClientMessage) + if err != nil { + return nil, nil + } + // ensure the ClientMsg is a Tendermint header + ibctmHeader, ok := clientMsg.(*ibctmtypes.Header) + if !ok { + return nil, nil + } + + // all good, we get the headerInfo + headerInfo := &types.HeaderInfo{ + ClientId: msgUpdateClient.ClientId, + ChainId: ibctmHeader.Header.ChainID, + Hash: ibctmHeader.Header.LastCommitHash, + Height: uint64(ibctmHeader.Header.Height), + Time: ibctmHeader.Header.Time, + } + + // ensure the corresponding clientState exists + clientState, exist := d.k.clientKeeper.GetClientState(ctx, msgUpdateClient.ClientId) + if !exist { + return nil, nil + } + // ensure the clientState is a Tendermint clientState + tmClientState, ok := clientState.(*ibctmtypes.ClientState) + if !ok { + return nil, nil + } + + return headerInfo, tmClientState +} + +type IBCHeaderDecorator struct { + k Keeper +} + +// NewIBCHeaderDecorator creates a new IBCHeaderDecorator +func NewIBCHeaderDecorator(k Keeper) *IBCHeaderDecorator { + return &IBCHeaderDecorator{ + k: k, + } +} + +func (d IBCHeaderDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate, success bool, next sdk.PostHandler) (newCtx sdk.Context, err error) { + // ignore unsuccessful tx + // NOTE: tx with a misbehaving header will still succeed, but will make the client to be frozen + if !success { + return next(ctx, tx, simulate, success) + } + + // calculate tx hash + txHash := tmhash.Sum(ctx.TxBytes()) + + for _, msg := range tx.GetMsgs() { + // try to extract the headerInfo and the client's status + headerInfo, clientState := d.getHeaderAndClientState(ctx, msg) + if headerInfo == nil { + continue + } + + // FrozenHeight is non-zero -> client is frozen -> this is a fork header + // NOTE: A valid tx can ONLY have a single fork header msg, and this fork + // header msg can ONLY be the LAST msg in this tx. If there is a fork + // header before a canonical header in a tx, then the client will be + // frozen upon the fork header, and the subsequent canonical header will + // fail, eventually failing the entire tx. All state updates due to this + // failed tx will be rolled back. + isOnFork := !clientState.FrozenHeight.IsZero() + d.k.HandleHeaderWithValidCommit(ctx, txHash, headerInfo, isOnFork) + + // unfreeze client (by setting FrozenHeight to zero again) if the client is frozen + // due to a fork header + if isOnFork { + clientState.FrozenHeight = clienttypes.ZeroHeight() + d.k.clientKeeper.SetClientState(ctx, headerInfo.ClientId, clientState) + } + } + + return next(ctx, tx, simulate, success) +} diff --git a/x/zoneconcierge/keeper/keeper.go b/x/zoneconcierge/keeper/keeper.go index 8231d6a10..f3784b12b 100644 --- a/x/zoneconcierge/keeper/keeper.go +++ b/x/zoneconcierge/keeper/keeper.go @@ -18,6 +18,7 @@ type ( memKey storetypes.StoreKey ics4Wrapper types.ICS4Wrapper + clientKeeper types.ClientKeeper channelKeeper types.ChannelKeeper portKeeper types.PortKeeper authKeeper types.AccountKeeper @@ -40,6 +41,7 @@ func NewKeeper( storeKey, memKey storetypes.StoreKey, ics4Wrapper types.ICS4Wrapper, + clientKeeper types.ClientKeeper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, @@ -58,6 +60,7 @@ func NewKeeper( storeKey: storeKey, memKey: memKey, ics4Wrapper: ics4Wrapper, + clientKeeper: clientKeeper, channelKeeper: channelKeeper, portKeeper: portKeeper, authKeeper: authKeeper, diff --git a/x/zoneconcierge/keeper/keeper_test.go b/x/zoneconcierge/keeper/keeper_test.go index eb1b63dc1..1c8d388b8 100644 --- a/x/zoneconcierge/keeper/keeper_test.go +++ b/x/zoneconcierge/keeper/keeper_test.go @@ -35,25 +35,25 @@ func SetupTest(t *testing.T) (*ibctesting.Coordinator, *ibctesting.TestChain, *i return coordinator, babylonChain, czChain, bbnApp } -// SimulateHeadersViaHook generates a non-zero number of canonical headers via the hook -func SimulateHeadersViaHook(ctx sdk.Context, r *rand.Rand, hooks zckeeper.Hooks, chainID string, startHeight uint64, numHeaders uint64) []*ibctmtypes.Header { +// SimulateNewHeaders generates a non-zero number of canonical headers +func SimulateNewHeaders(ctx sdk.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64) []*ibctmtypes.Header { headers := []*ibctmtypes.Header{} // invoke the hook a number of times to simulate a number of blocks for i := uint64(0); i < numHeaders; i++ { header := datagen.GenRandomIBCTMHeader(r, chainID, startHeight+i) - hooks.AfterHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) + k.HandleHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) headers = append(headers, header) } return headers } -// SimulateHeadersViaHook generates a random non-zero number of canonical headers and fork headers via the hook -func SimulateHeadersAndForksViaHook(ctx sdk.Context, r *rand.Rand, hooks zckeeper.Hooks, chainID string, startHeight uint64, numHeaders uint64, numForkHeaders uint64) ([]*ibctmtypes.Header, []*ibctmtypes.Header) { +// SimulateNewHeadersAndForks generates a random non-zero number of canonical headers and fork headers +func SimulateNewHeadersAndForks(ctx sdk.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64, numForkHeaders uint64) ([]*ibctmtypes.Header, []*ibctmtypes.Header) { headers := []*ibctmtypes.Header{} // invoke the hook a number of times to simulate a number of blocks for i := uint64(0); i < numHeaders; i++ { header := datagen.GenRandomIBCTMHeader(r, chainID, startHeight+i) - hooks.AfterHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) + k.HandleHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), false) headers = append(headers, header) } @@ -61,7 +61,7 @@ func SimulateHeadersAndForksViaHook(ctx sdk.Context, r *rand.Rand, hooks zckeepe forkHeaders := []*ibctmtypes.Header{} for i := uint64(0); i < numForkHeaders; i++ { header := datagen.GenRandomIBCTMHeader(r, chainID, startHeight+numHeaders-1) - hooks.AfterHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), true) + k.HandleHeaderWithValidCommit(ctx, datagen.GenRandomByteArray(r, 32), datagen.HeaderToHeaderInfo(header), true) forkHeaders = append(forkHeaders, header) } return headers, forkHeaders diff --git a/x/zoneconcierge/module_test.go b/x/zoneconcierge/module_test.go index b0b96e45b..c3442e614 100644 --- a/x/zoneconcierge/module_test.go +++ b/x/zoneconcierge/module_test.go @@ -12,9 +12,10 @@ import ( tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/codec" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - clientexported "github.com/cosmos/ibc-go/v7/modules/core/02-client/exported" + clientkeeper "github.com/cosmos/ibc-go/v7/modules/core/02-client/keeper" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" @@ -42,7 +43,7 @@ type ZoneConciergeTestSuite struct { // System states of the simulated Babylon chain cdc codec.Codec ctx sdk.Context - keeper clientexported.ClientKeeper + keeper clientkeeper.Keeper zcKeeper zckeeper.Keeper consensusState *ibctmtypes.ConsensusState header *ibctmtypes.Header @@ -234,7 +235,7 @@ func (suite *ZoneConciergeTestSuite) TestUpdateClientTendermint() { conflictConsState := updateHeader.ConsensusState() conflictConsState.Root = commitmenttypes.NewMerkleRoot([]byte("conflicting apphash")) suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, updateHeader.GetHeight(), conflictConsState) - }, false, true}, // Babylon modification: fork headers are rejected before being passed to ClientState, and are recorded in the fork index + }, false, true}, // Babylon modification: fork headers will be recorded in the fork index {"misbehaviour in dishonest majority CZ: monotonic time violation", func() { clientState := path.EndpointA.GetClientState().(*ibctmtypes.ClientState) clientID := path.EndpointA.ClientID @@ -256,7 +257,7 @@ func (suite *ZoneConciergeTestSuite) TestUpdateClientTendermint() { suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.babylonChain.GetContext(), clientID, clientState) updateHeader = createFutureUpdateFn(trustedHeight) - }, false, true}, // Babylon modification: non-monotonic headers are rejected before being passed to ClientState, and are recorded in the fork index + }, false, true}, // Babylon modification: non-monotonic headers will be recorded in the fork index {"client state not found", func() { updateHeader = createFutureUpdateFn(path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height)) @@ -296,7 +297,55 @@ func (suite *ZoneConciergeTestSuite) TestUpdateClientTendermint() { clientState = path.EndpointA.GetClientState() } - err := suite.babylonChain.App.GetIBCKeeper().ClientKeeper.UpdateClient(suite.babylonChain.GetContext(), path.EndpointA.ClientID, updateHeader) + msg, err := clienttypes.NewMsgUpdateClient(path.EndpointA.ClientID, updateHeader, path.EndpointA.Chain.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + + // define SendMsgsOverride such that SendMsgs does not make expectation on whether + // the tx is successful or not. Here ZoneConcierge only cares about whether headers + // are indexed as expected + suite.babylonChain.SendMsgsOverride = func(msgs ...sdk.Msg) (*sdk.Result, error) { + chain := suite.babylonChain + // ensure the chain has the latest time + chain.Coordinator.UpdateTimeForChain(chain) + + // generate a signed mock tx + tx, err := simtestutil.GenSignedMockTx( + rand.New(rand.NewSource(time.Now().UnixNano())), + chain.TxConfig, + msgs, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chain.ChainID, + []uint64{chain.SenderAccount.GetAccountNumber()}, + []uint64{chain.SenderAccount.GetSequence()}, + chain.SenderPrivKey, + ) + if err != nil { + return nil, err + } + + // Simulate a sending a transaction + _, res, err := chain.App.GetBaseApp().SimDeliver(chain.TxConfig.TxEncoder(), tx) + if err != nil { + return nil, err + } + + // NextBlock calls app.Commit() + chain.NextBlock() + + // increment sequence for successful transaction execution + err = chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + if err != nil { + return nil, err + } + + // increment time + chain.Coordinator.IncrementTime() + + return res, nil + } + // send message! + _, err = suite.babylonChain.SendMsgs(msg) if tc.expPass { suite.Require().NoError(err, err) diff --git a/x/zoneconcierge/types/expected_keepers.go b/x/zoneconcierge/types/expected_keepers.go index dd07af30e..082a4570d 100644 --- a/x/zoneconcierge/types/expected_keepers.go +++ b/x/zoneconcierge/types/expected_keepers.go @@ -59,7 +59,8 @@ type ChannelKeeper interface { // ClientKeeper defines the expected IBC client keeper type ClientKeeper interface { - GetClientConsensusState(ctx sdk.Context, clientID string) (connection ibcexported.ConsensusState, found bool) + GetClientState(ctx sdk.Context, clientID string) (ibcexported.ClientState, bool) + SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) } // ConnectionKeeper defines the expected IBC connection keeper diff --git a/x/zoneconcierge/types/types.go b/x/zoneconcierge/types/types.go index 6e95fb34e..1354f1342 100644 --- a/x/zoneconcierge/types/types.go +++ b/x/zoneconcierge/types/types.go @@ -1,5 +1,7 @@ package types +import "time" + // IsLatestHeader checks if a given header is higher than the latest header in chain info func (ci *ChainInfo) IsLatestHeader(header *IndexedHeader) bool { if ci.LatestHeader != nil && ci.LatestHeader.Height > header.Height { @@ -7,3 +9,11 @@ func (ci *ChainInfo) IsLatestHeader(header *IndexedHeader) bool { } return true } + +type HeaderInfo struct { + ClientId string + ChainId string + Hash []byte + Height uint64 + Time time.Time +} From dac26d8c7e218c3654ec75b56396a71f9bfa3931 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 15 Nov 2023 14:18:26 +1100 Subject: [PATCH 099/202] btcstaking: filter out top N BTC validators as active validator set (#102) --- proto/babylon/btcstaking/v1/params.proto | 2 + x/btcstaking/keeper/msg_server_test.go | 22 ++- x/btcstaking/keeper/voting_power_table.go | 24 ++- .../keeper/voting_power_table_test.go | 181 ++++++++++++++++++ x/btcstaking/types/btcstaking.go | 19 ++ x/btcstaking/types/genesis_test.go | 22 ++- x/btcstaking/types/params.go | 19 +- x/btcstaking/types/params.pb.go | 90 ++++++--- 8 files changed, 329 insertions(+), 50 deletions(-) diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index b2a97f4fb..9a3e56fb2 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -32,4 +32,6 @@ message Params { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; + // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol + uint32 max_active_btc_validators = 6; } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 58f5b7f19..399348602 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -96,11 +96,12 @@ func getCovenantInfo(t *testing.T, slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(sdkCtx, types.Params{ - CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - SlashingAddress: slashingAddr.String(), - MinSlashingTxFeeSat: 10, - MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.MustNewDecFromStr("0.1"), + CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + SlashingAddress: slashingAddr.String(), + MinSlashingTxFeeSat: 10, + MinCommissionRate: sdk.MustNewDecFromStr("0.01"), + SlashingRate: sdk.MustNewDecFromStr("0.1"), + MaxActiveBtcValidators: 100, }) require.NoError(t, err) return covenantSK, covenantPK, slashingAddr @@ -403,11 +404,12 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { slashingAddr, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ - CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - SlashingAddress: slashingAddr.String(), - MinSlashingTxFeeSat: 10, - MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.MustNewDecFromStr("0.1"), + CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + SlashingAddress: slashingAddr.String(), + MinSlashingTxFeeSat: 10, + MinCommissionRate: sdk.MustNewDecFromStr("0.01"), + SlashingRate: sdk.MustNewDecFromStr("0.1"), + MaxActiveBtcValidators: 100, }) require.NoError(t, err) diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index f0ff56d3e..62defef79 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -22,9 +22,9 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { // get value of w wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - // iterate all BTC validators + // filter out all BTC validators with positive voting power + activeBTCVals := []*types.BTCValidatorWithMeta{} btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) - defer btcValIter.Close() for ; btcValIter.Valid(); btcValIter.Next() { valBTCPKBytes := btcValIter.Key() valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) @@ -61,9 +61,27 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { btcDelIter.Close() if valPower > 0 { - k.SetVotingPower(ctx, valBTCPKBytes, babylonTipHeight, valPower) + activeBTCVals = append(activeBTCVals, &types.BTCValidatorWithMeta{ + BtcPk: valBTCPK, + VotingPower: valPower, + // other fields do not matter + }) } } + btcValIter.Close() + + // return directly if there is no active BTC validator + if len(activeBTCVals) == 0 { + return + } + + // filter out top `MaxActiveBtcValidators` active validators in terms of voting power + activeBTCVals = types.FilterTopNBTCValidators(activeBTCVals, k.GetParams(ctx).MaxActiveBtcValidators) + + // set voting power for each active BTC validators + for _, btcVal := range activeBTCVals { + k.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonTipHeight, btcVal.VotingPower) + } } // SetVotingPower sets the voting power of a given BTC validator at a given Babylon height diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index e1ced92b8..1debc8e3e 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -6,6 +6,7 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -168,3 +169,183 @@ func FuzzVotingPowerTable(f *testing.F) { require.Equal(t, activatedHeight, activatedHeight2) }) } + +func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + + // generate a random batch of validators, each with a BTC delegation with random power + btcValsWithMeta := []*types.BTCValidatorWithMeta{} + numBTCVals := datagen.RandomInt(r, 300) + 1 + for i := uint64(0); i < numBTCVals; i++ { + // generate BTC validator + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + + // delegate to this BTC validator + stakingValue := datagen.RandomInt(r, 100000) + 100000 + valBTCPK := btcVal.BtcPk + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.AddBTCDelegation(ctx, btcDel) + require.NoError(t, err) + + // record voting power + btcValsWithMeta = append(btcValsWithMeta, &types.BTCValidatorWithMeta{ + BtcPk: btcVal.BtcPk, + VotingPower: stakingValue, + }) + } + + maxActiveBTCValsParam := keeper.GetParams(ctx).MaxActiveBtcValidators + // get a map of expected active BTC validators + expectedActiveBTCVals := types.FilterTopNBTCValidators(btcValsWithMeta, maxActiveBTCValsParam) + expectedActiveBTCValMap := map[string]uint64{} + for _, btcVal := range expectedActiveBTCVals { + expectedActiveBTCValMap[btcVal.BtcPk.MarshalHex()] = btcVal.VotingPower + } + + // record voting power table + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.IndexBTCHeight(ctx) + keeper.RecordVotingPowerTable(ctx) + + // only BTC validators in expectedActiveBTCValMap have voting power + for _, btcVal := range btcValsWithMeta { + power := keeper.GetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonHeight) + if expectedPower, ok := expectedActiveBTCValMap[btcVal.BtcPk.MarshalHex()]; ok { + require.Equal(t, expectedPower, power) + } else { + require.Equal(t, uint64(0), power) + } + } + + // also, get voting power table and assert there is + // min(len(expectedActiveBTCVals), MaxActiveBtcValidators) active BTC validators + powerTable := keeper.GetVotingPowerTable(ctx, babylonHeight) + expectedNumActiveBTCVals := len(expectedActiveBTCValMap) + if expectedNumActiveBTCVals > int(maxActiveBTCValsParam) { + expectedNumActiveBTCVals = int(maxActiveBTCValsParam) + } + require.Len(t, powerTable, expectedNumActiveBTCVals) + // assert consistency of voting power + for pkHex, expectedPower := range expectedActiveBTCValMap { + require.Equal(t, powerTable[pkHex], expectedPower) + } + }) +} + +func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + + // covenant and slashing addr + covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + + // generate a random batch of validators, each with a BTC delegation with random power + btcValsWithMeta := []*types.BTCValidatorWithMeta{} + numBTCVals := uint64(200) // there has to be more than `maxActiveBtcValidators` validators + for i := uint64(0); i < numBTCVals; i++ { + // generate BTC validator + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + keeper.SetBTCValidator(ctx, btcVal) + + // delegate to this BTC validator + stakingValue := datagen.RandomInt(r, 100000) + 100000 + valBTCPK := btcVal.BtcPk + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.AddBTCDelegation(ctx, btcDel) + require.NoError(t, err) + + // record voting power + btcValsWithMeta = append(btcValsWithMeta, &types.BTCValidatorWithMeta{ + BtcPk: btcVal.BtcPk, + VotingPower: stakingValue, + }) + } + + // record voting power table + babylonHeight := datagen.RandomInt(r, 10) + 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.IndexBTCHeight(ctx) + keeper.RecordVotingPowerTable(ctx) + + // get maps of active/inactive BTC validators + activeBTCValMap := map[string]uint64{} + inactiveBTCValMap := map[string]uint64{} + for _, btcVal := range btcValsWithMeta { + power := keeper.GetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonHeight) + if power > 0 { + activeBTCValMap[btcVal.BtcPk.MarshalHex()] = power + } else { + inactiveBTCValMap[btcVal.BtcPk.MarshalHex()] = power + } + } + + // delegate a huge amount of tokens to one of the inactive BTC validator + var activatedValBTCPK *bbn.BIP340PubKey + for valBTCPKHex := range inactiveBTCValMap { + stakingValue := uint64(10000000) + activatedValBTCPK, _ = bbn.NewBIP340PubKeyFromHex(valBTCPKHex) + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + btcDel, err := datagen.GenRandomBTCDelegation(r, activatedValBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + require.NoError(t, err) + err = keeper.AddBTCDelegation(ctx, btcDel) + require.NoError(t, err) + + break + } + + // record voting power table + babylonHeight += 1 + ctx = ctx.WithBlockHeight(int64(babylonHeight)) + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) + keeper.IndexBTCHeight(ctx) + keeper.RecordVotingPowerTable(ctx) + + // ensure that the activated BTC validator now has entered the active validator set + // i.e., has voting power + power := keeper.GetVotingPower(ctx, activatedValBTCPK.MustMarshal(), babylonHeight) + require.Positive(t, power) + }) +} diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index d57811b2c..b28cffbb1 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" + "sort" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" @@ -332,3 +333,21 @@ func (tx *BabylonBTCTaprootTx) VerifySignature(stakingPkScript []byte, stakingAm *sig, ) } + +// FilterTopNBTCValidators returns the top n validators based on VotingPower. +func FilterTopNBTCValidators(validators []*BTCValidatorWithMeta, n uint32) []*BTCValidatorWithMeta { + numVals := uint32(len(validators)) + + // if the given validator set is no bigger than n, no need to do anything + if numVals <= n { + return validators + } + + // Sort the validators slice, from higher to lower voting power + sort.SliceStable(validators, func(i, j int) bool { + return validators[i].VotingPower > validators[j].VotingPower + }) + + // Return the top n elements + return validators[:n] +} diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index f271c0f7a..da62eedb7 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -24,11 +24,12 @@ func TestGenesisState_Validate(t *testing.T) { desc: "valid genesis state", genState: &types.GenesisState{ Params: types.Params{ - CovenantPk: types.DefaultParams().CovenantPk, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), + CovenantPk: types.DefaultParams().CovenantPk, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), + MaxActiveBtcValidators: 100, }, }, valid: true, @@ -37,11 +38,12 @@ func TestGenesisState_Validate(t *testing.T) { desc: "invalid slashing rate in genesis", genState: &types.GenesisState{ Params: types.Params{ - CovenantPk: types.DefaultParams().CovenantPk, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate + CovenantPk: types.DefaultParams().CovenantPk, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate + MaxActiveBtcValidators: 100, }, }, valid: false, diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 290b456a1..e9f914d0d 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -13,6 +13,10 @@ import ( "gopkg.in/yaml.v2" ) +const ( + defaultMaxActiveBtcValidators uint32 = 100 +) + var _ paramtypes.ParamSet = (*Params)(nil) func defaultCovenantPk() *bbn.BIP340PubKey { @@ -45,7 +49,8 @@ func DefaultParams() Params { MinSlashingTxFeeSat: 1000, MinCommissionRate: math.LegacyZeroDec(), // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. - SlashingRate: math.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 + SlashingRate: math.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 + MaxActiveBtcValidators: defaultMaxActiveBtcValidators, } } @@ -84,6 +89,15 @@ func validateSlashingRate(slashingRate sdk.Dec) error { return nil } +// validateMaxActiveBTCValidators checks if the maximum number of +// active BTC validators is at least the default value +func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { + if maxActiveBtcValidators < defaultMaxActiveBtcValidators { + return fmt.Errorf("maximum number of BTC validators is at least %d", defaultMaxActiveBtcValidators) + } + return nil +} + // Validate validates the set of params func (p Params) Validate() error { if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { @@ -95,6 +109,9 @@ func (p Params) Validate() error { if err := validateSlashingRate(p.SlashingRate); err != nil { return err } + if err := validateMaxActiveBTCValidators(p.MaxActiveBtcValidators); err != nil { + return err + } return nil } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index fea9b1be5..3b7c0ca17 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -43,6 +43,8 @@ type Params struct { // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). SlashingRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=slashing_rate,json=slashingRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slashing_rate"` + // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol + MaxActiveBtcValidators uint32 `protobuf:"varint,6,opt,name=max_active_btc_validators,json=maxActiveBtcValidators,proto3" json:"max_active_btc_validators,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -91,6 +93,13 @@ func (m *Params) GetMinSlashingTxFeeSat() int64 { return 0 } +func (m *Params) GetMaxActiveBtcValidators() uint32 { + if m != nil { + return m.MaxActiveBtcValidators + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "babylon.btcstaking.v1.Params") } @@ -100,32 +109,34 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 393 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, - 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x2e, 0x2e, 0x49, 0xcc, 0xce, 0xcc, 0x4b, 0xd7, 0x2f, - 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, - 0x85, 0xaa, 0xd1, 0x43, 0xa8, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, - 0xd0, 0x07, 0xb1, 0x20, 0x8a, 0xa5, 0x24, 0x93, 0xf3, 0x8b, 0x73, 0xf3, 0x8b, 0xe3, 0x21, 0x12, - 0x10, 0x0e, 0x44, 0x4a, 0xa9, 0x9b, 0x99, 0x8b, 0x2d, 0x00, 0x6c, 0xb0, 0x50, 0x38, 0x17, 0x77, - 0x72, 0x7e, 0x59, 0x6a, 0x5e, 0x62, 0x5e, 0x49, 0x7c, 0x41, 0xb6, 0x04, 0xa3, 0x02, 0xa3, 0x06, - 0x8f, 0x93, 0xd9, 0xad, 0x7b, 0xf2, 0x46, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, - 0xb9, 0xfa, 0x50, 0x6b, 0x93, 0x33, 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, - 0x62, 0x3d, 0x27, 0xcf, 0x00, 0x63, 0x13, 0x83, 0x80, 0xd2, 0x24, 0xef, 0xd4, 0xca, 0x20, 0x2e, - 0x98, 0x51, 0x01, 0xd9, 0x42, 0x9a, 0x5c, 0x02, 0xc5, 0x39, 0x89, 0xc5, 0x19, 0x99, 0x79, 0xe9, - 0xf1, 0x89, 0x29, 0x29, 0x45, 0xa9, 0xc5, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0xfc, - 0x30, 0x71, 0x47, 0x88, 0xb0, 0x90, 0x09, 0x97, 0x78, 0x6e, 0x66, 0x5e, 0x3c, 0x5c, 0x79, 0x49, - 0x45, 0x7c, 0x5a, 0x6a, 0x6a, 0x7c, 0x71, 0x62, 0x89, 0x04, 0xb3, 0x02, 0xa3, 0x06, 0x73, 0x90, - 0x70, 0x6e, 0x66, 0x5e, 0x30, 0x54, 0x36, 0xa4, 0xc2, 0x2d, 0x35, 0x35, 0x38, 0xb1, 0x44, 0x28, - 0x8e, 0x0b, 0x24, 0x1c, 0x9f, 0x9c, 0x9f, 0x9b, 0x9b, 0x59, 0x5c, 0x9c, 0x99, 0x9f, 0x17, 0x5f, - 0x94, 0x58, 0x92, 0x2a, 0xc1, 0x02, 0xb2, 0xc3, 0x49, 0xef, 0xc4, 0x3d, 0x79, 0x86, 0x5b, 0xf7, - 0xe4, 0xd5, 0x90, 0x7c, 0x01, 0x09, 0x02, 0x28, 0xa5, 0x5b, 0x9c, 0x92, 0x0d, 0xf5, 0x82, 0x4b, - 0x6a, 0x72, 0x90, 0x60, 0x6e, 0x66, 0x9e, 0x33, 0xdc, 0xa4, 0xa0, 0xc4, 0x92, 0x54, 0xa1, 0x44, - 0x2e, 0x5e, 0xb8, 0x8b, 0xc0, 0x26, 0xb3, 0x82, 0x4d, 0xb6, 0x21, 0xcd, 0xe4, 0x4b, 0x5b, 0x74, - 0xb9, 0xa0, 0x61, 0x0f, 0xb2, 0x87, 0x07, 0x66, 0x24, 0xc8, 0x0a, 0x2b, 0x96, 0x19, 0x0b, 0xe4, - 0x19, 0x9c, 0x7c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, - 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x8a, 0x60, 0x1c, - 0x54, 0x20, 0xa7, 0x16, 0xb0, 0x9d, 0x49, 0x6c, 0xe0, 0x28, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, - 0xff, 0x67, 0xdc, 0x2d, 0x7f, 0x50, 0x02, 0x00, 0x00, + // 432 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x4f, 0x6f, 0xd3, 0x30, + 0x18, 0xc6, 0x63, 0x3a, 0x2a, 0x61, 0x36, 0x01, 0xe1, 0x5f, 0xb6, 0x43, 0x5a, 0xed, 0x80, 0xca, + 0x61, 0x09, 0x63, 0x13, 0x12, 0x88, 0xcb, 0x02, 0x42, 0x42, 0x70, 0xa8, 0x32, 0x04, 0x12, 0x07, + 0xac, 0x37, 0xae, 0x49, 0xad, 0xd6, 0x76, 0x14, 0x7b, 0x51, 0xfa, 0x2d, 0x38, 0x72, 0xe4, 0x43, + 0xf0, 0x19, 0xd0, 0x8e, 0x13, 0x27, 0xb4, 0x43, 0x85, 0xda, 0x2f, 0x82, 0xe2, 0xfc, 0xa1, 0x37, + 0xc4, 0x29, 0xf1, 0xfb, 0x3c, 0xfa, 0x3d, 0x7e, 0x5f, 0xbf, 0x78, 0x3f, 0x81, 0x64, 0x31, 0x57, + 0x32, 0x4c, 0x0c, 0xd5, 0x06, 0x66, 0x5c, 0xa6, 0x61, 0x71, 0x18, 0x66, 0x90, 0x83, 0xd0, 0x41, + 0x96, 0x2b, 0xa3, 0xdc, 0xbb, 0x8d, 0x27, 0xf8, 0xeb, 0x09, 0x8a, 0xc3, 0xbd, 0x3b, 0xa9, 0x4a, + 0x95, 0x75, 0x84, 0xd5, 0x5f, 0x6d, 0xde, 0xdb, 0xa5, 0x4a, 0x0b, 0xa5, 0x49, 0x2d, 0xd4, 0x87, + 0x5a, 0xda, 0xff, 0xd1, 0xc3, 0xfd, 0xb1, 0x05, 0xbb, 0x1f, 0xf0, 0x75, 0xaa, 0x0a, 0x26, 0x41, + 0x1a, 0x92, 0xcd, 0x3c, 0x34, 0x44, 0xa3, 0xed, 0xe8, 0xc9, 0xe5, 0x72, 0xf0, 0x38, 0xe5, 0x66, + 0x7a, 0x96, 0x04, 0x54, 0x89, 0xb0, 0x89, 0xa5, 0x53, 0xe0, 0xb2, 0x3d, 0x84, 0x66, 0x91, 0x31, + 0x1d, 0x44, 0xaf, 0xc7, 0x47, 0xc7, 0x8f, 0xc6, 0x67, 0xc9, 0x1b, 0xb6, 0x88, 0x71, 0x8b, 0x1a, + 0xcf, 0xdc, 0x87, 0xf8, 0xa6, 0x9e, 0x83, 0x9e, 0x72, 0x99, 0x12, 0x98, 0x4c, 0x72, 0xa6, 0xb5, + 0x77, 0x65, 0x88, 0x46, 0xd7, 0xe2, 0x1b, 0x6d, 0xfd, 0xa4, 0x2e, 0xbb, 0xc7, 0xf8, 0xbe, 0xe0, + 0x92, 0x74, 0x76, 0x53, 0x92, 0xcf, 0x8c, 0x11, 0x0d, 0xc6, 0xeb, 0x0d, 0xd1, 0xa8, 0x17, 0xdf, + 0x16, 0x5c, 0x9e, 0x36, 0xea, 0xbb, 0xf2, 0x15, 0x63, 0xa7, 0x60, 0xdc, 0x4f, 0xb8, 0x2a, 0x13, + 0xaa, 0x84, 0xe0, 0x5a, 0x73, 0x25, 0x49, 0x0e, 0x86, 0x79, 0x5b, 0x55, 0x46, 0x14, 0x9c, 0x2f, + 0x07, 0xce, 0xe5, 0x72, 0xf0, 0x60, 0xa3, 0x8b, 0x7a, 0x04, 0xcd, 0xe7, 0x40, 0x4f, 0x66, 0x4d, + 0x0b, 0x2f, 0x19, 0x8d, 0x6f, 0x09, 0x2e, 0x5f, 0x74, 0xa4, 0x18, 0x0c, 0x73, 0x01, 0xef, 0x74, + 0x37, 0xb2, 0xe4, 0xab, 0x96, 0xfc, 0xfc, 0xff, 0xc8, 0x3f, 0xbf, 0x1f, 0xe0, 0x66, 0xf6, 0x55, + 0xce, 0x76, 0x8b, 0xb4, 0x11, 0x4f, 0xf1, 0xae, 0x80, 0x92, 0x00, 0x35, 0xbc, 0x60, 0x24, 0x31, + 0x94, 0x14, 0x30, 0xe7, 0x13, 0x30, 0x2a, 0xd7, 0x5e, 0x7f, 0x88, 0x46, 0x3b, 0xf1, 0x3d, 0x01, + 0xe5, 0x89, 0xd5, 0x23, 0x43, 0xdf, 0x77, 0xea, 0xb3, 0xad, 0xaf, 0xdf, 0x06, 0x4e, 0xf4, 0xf6, + 0x7c, 0xe5, 0xa3, 0x8b, 0x95, 0x8f, 0x7e, 0xaf, 0x7c, 0xf4, 0x65, 0xed, 0x3b, 0x17, 0x6b, 0xdf, + 0xf9, 0xb5, 0xf6, 0x9d, 0x8f, 0xff, 0x7c, 0xbe, 0x72, 0x73, 0xd1, 0xec, 0x75, 0x93, 0xbe, 0xdd, + 0x8e, 0xa3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x85, 0x4c, 0x22, 0xe4, 0x8b, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -148,6 +159,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxActiveBtcValidators != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveBtcValidators)) + i-- + dAtA[i] = 0x30 + } { size := m.SlashingRate.Size() i -= size @@ -227,6 +243,9 @@ func (m *Params) Size() (n int) { n += 1 + l + sovParams(uint64(l)) l = m.SlashingRate.Size() n += 1 + l + sovParams(uint64(l)) + if m.MaxActiveBtcValidators != 0 { + n += 1 + sovParams(uint64(m.MaxActiveBtcValidators)) + } return n } @@ -419,6 +438,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveBtcValidators", wireType) + } + m.MaxActiveBtcValidators = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxActiveBtcValidators |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) From 30ebf3e04e9ae19b116e1b723d28c7afa95b31dd Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 15 Nov 2023 17:39:58 +1100 Subject: [PATCH 100/202] bump wasmd to v0.44 (#105) --- app/app.go | 59 ++++++++++-------------------------- app/app_test.go | 2 +- app/test_helpers.go | 9 +++--- cmd/babylond/cmd/root.go | 7 ++--- go.mod | 10 +++--- go.sum | 20 ++++++------ simapp/sim_bench_test.go | 2 -- simapp/sim_test.go | 12 ++++---- wasmbinding/wasm.go | 3 +- x/btcstaking/types/params.go | 4 +-- 10 files changed, 49 insertions(+), 79 deletions(-) diff --git a/app/app.go b/app/app.go index ab83fe029..3f60d0107 100644 --- a/app/app.go +++ b/app/app.go @@ -6,14 +6,14 @@ import ( "io" "os" "path/filepath" - "strings" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" wasmapp "github.com/CosmWasm/wasmd/app" - "github.com/CosmWasm/wasmd/x/wasm" + wasm "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/babylonchain/babylon/client/docs" bbn "github.com/babylonchain/babylon/types" owasm "github.com/babylonchain/babylon/wasmbinding" @@ -254,31 +254,9 @@ var ( EnableSpecificWasmProposals = "" // EmptyWasmOpts defines a type alias for a list of wasm options. - EmptyWasmOpts []wasm.Option + EmptyWasmOpts []wasmkeeper.Option ) -// GetWasmEnabledProposals parses the WasmProposalsEnabled and -// EnableSpecificWasmProposals values to produce a list of enabled proposals to -// pass into the application. -func GetWasmEnabledProposals() []wasm.ProposalType { - if EnableSpecificWasmProposals == "" { - if WasmProposalsEnabled == "true" { - return wasm.EnableAllProposals - } - - return wasm.DisableAllProposals - } - - chunks := strings.Split(EnableSpecificWasmProposals, ",") - - proposals, err := wasm.ConvertToProposals(chunks) - if err != nil { - panic(err) - } - - return proposals -} - var ( _ App = (*BabylonApp)(nil) _ servertypes.Application = (*BabylonApp)(nil) @@ -337,7 +315,7 @@ type BabylonApp struct { FinalityKeeper finalitykeeper.Keeper // wasm smart contract module - WasmKeeper wasm.Keeper + WasmKeeper wasmkeeper.Keeper // tokenomics-related modules IncentiveKeeper incentivekeeper.Keeper @@ -373,8 +351,7 @@ func NewBabylonApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool, homePath string, invCheckPeriod uint, encodingConfig appparams.EncodingConfig, privSigner *PrivSigner, appOpts servertypes.AppOptions, - wasmEnabledProposals []wasm.ProposalType, - wasmOpts []wasm.Option, + wasmOpts []wasmkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), ) *BabylonApp { // we could also take it from global object which should be initilised in rootCmd @@ -414,7 +391,7 @@ func NewBabylonApp( btcstakingtypes.StoreKey, finalitytypes.StoreKey, // WASM - wasm.StoreKey, + wasmtypes.StoreKey, // tokenomics-related modules incentivetypes.StoreKey, ) @@ -448,7 +425,7 @@ func NewBabylonApp( scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) scopedZoneConciergeKeeper := app.CapabilityKeeper.ScopeToModule(zctypes.ModuleName) - scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasm.ModuleName) + scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasmtypes.ModuleName) // Applications that wish to enforce statically created ScopedKeepers should call `Seal` after creating // their scoped modules in `NewApp` with `ScopeToModule` @@ -665,9 +642,9 @@ func NewBabylonApp( wasmOpts = append(owasm.RegisterCustomPlugins(&app.EpochingKeeper, &app.ZoneConciergeKeeper, &app.BTCLightClientKeeper), wasmOpts...) - app.WasmKeeper = wasm.NewKeeper( + app.WasmKeeper = wasmkeeper.NewKeeper( appCodec, - keys[wasm.StoreKey], + keys[wasmtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.StakingKeeper, @@ -703,17 +680,12 @@ func NewBabylonApp( ibcRouter := porttypes.NewRouter(). AddRoute(ibctransfertypes.ModuleName, transferStack). AddRoute(zctypes.ModuleName, zoneConciergeStack). - AddRoute(wasm.ModuleName, wasmStack) + AddRoute(wasmtypes.ModuleName, wasmStack) // Setting Router will finalize all routes by sealing router // No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) - // The gov proposal types can be individually enabled - if len(wasmEnabledProposals) != 0 { - govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, wasmEnabledProposals)) - } - // Set legacy router for backwards compatibility with gov v1beta1 app.GovKeeper.SetLegacyRouter(govRouter) @@ -746,7 +718,7 @@ func NewBabylonApp( params.NewAppModule(app.ParamsKeeper), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasm.ModuleName)), + wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmtypes.ModuleName)), // Babylon modules epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), btclightclient.NewAppModule(appCodec, app.BTCLightClientKeeper, app.AccountKeeper, app.BankKeeper), @@ -791,7 +763,7 @@ func NewBabylonApp( ibctransfertypes.ModuleName, zctypes.ModuleName, ibcfeetypes.ModuleName, - wasm.ModuleName, + wasmtypes.ModuleName, // BTC staking related modules btcstakingtypes.ModuleName, finalitytypes.ModuleName, @@ -819,7 +791,7 @@ func NewBabylonApp( ibctransfertypes.ModuleName, zctypes.ModuleName, ibcfeetypes.ModuleName, - wasm.ModuleName, + wasmtypes.ModuleName, // BTC staking related modules btcstakingtypes.ModuleName, finalitytypes.ModuleName, @@ -851,7 +823,7 @@ func NewBabylonApp( ibctransfertypes.ModuleName, zctypes.ModuleName, ibcfeetypes.ModuleName, - wasm.ModuleName, + wasmtypes.ModuleName, // BTC staking related modules btcstakingtypes.ModuleName, finalitytypes.ModuleName, @@ -915,7 +887,8 @@ func NewBabylonApp( }, IBCKeeper: app.IBCKeeper, WasmConfig: &wasmConfig, - TXCounterStoreKey: keys[wasm.StoreKey], + TXCounterStoreKey: keys[wasmtypes.StoreKey], + WasmKeeper: &app.WasmKeeper, }, ) diff --git a/app/app_test.go b/app/app_test.go index 75a406c64..3328e3c89 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -47,7 +47,7 @@ func TestBabylonBlockedAddrs(t *testing.T) { logger2 := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) // Making a new app object with the db, so that initchain hasn't been called - app2 := NewBabylonApp(logger2, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, signer, EmptyAppOptions{}, GetWasmEnabledProposals(), EmptyWasmOpts) + app2 := NewBabylonApp(logger2, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, signer, EmptyAppOptions{}, EmptyWasmOpts) _, err := app2.ExportAppStateAndValidators(false, []string{}, []string{}) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } diff --git a/app/test_helpers.go b/app/test_helpers.go index f83e2a392..d69efe98c 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -18,7 +18,6 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" errorsmod "cosmossdk.io/errors" - "github.com/babylonchain/babylon/app/params" appparams "github.com/babylonchain/babylon/app/params" bbn "github.com/babylonchain/babylon/types" dbm "github.com/cometbft/cometbft-db" @@ -53,7 +52,7 @@ type SetupOptions struct { InvCheckPeriod uint HomePath string SkipUpgradeHeights map[int64]bool - EncConfig params.EncodingConfig + EncConfig appparams.EncodingConfig AppOpts types.AppOptions } @@ -64,7 +63,7 @@ func setup(withGenesis bool, invCheckPeriod uint) (*BabylonApp, GenesisState) { if err != nil { panic(err) } - app := NewBabylonApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, invCheckPeriod, encCdc, privSigner, EmptyAppOptions{}, GetWasmEnabledProposals(), EmptyWasmOpts) + app := NewBabylonApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, invCheckPeriod, encCdc, privSigner, EmptyAppOptions{}, EmptyWasmOpts) if withGenesis { return app, NewDefaultGenesisState(encCdc.Marshaler) } @@ -103,7 +102,9 @@ func NewBabyblonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *P options.InvCheckPeriod, options.EncConfig, privSigner, - options.AppOpts, GetWasmEnabledProposals(), EmptyWasmOpts) + options.AppOpts, + EmptyWasmOpts, + ) genesisState := NewDefaultGenesisState(app.appCodec) genesisState = genesisStateWithValSet(t, app, genesisState, valSet, []authtypes.GenesisAccount{acc}, balance) diff --git a/cmd/babylond/cmd/root.go b/cmd/babylond/cmd/root.go index 709402c37..0c8fe80ba 100644 --- a/cmd/babylond/cmd/root.go +++ b/cmd/babylond/cmd/root.go @@ -307,7 +307,7 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent)), ) - var wasmOpts []wasm.Option + var wasmOpts []wasmkeeper.Option if cast.ToBool(appOpts.Get("telemetry.enabled")) { wasmOpts = append(wasmOpts, wasmkeeper.WithVMCacheMetrics(prometheus.DefaultRegisterer)) } @@ -319,7 +319,6 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a a.encCfg, privSigner, appOpts, - app.GetWasmEnabledProposals(), wasmOpts, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), @@ -380,13 +379,13 @@ func (a appCreator) appExport( panic(err) } if height != -1 { - babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.GetWasmEnabledProposals(), app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.EmptyWasmOpts) if err = babylonApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { - babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.GetWasmEnabledProposals(), app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.EmptyWasmOpts) } return babylonApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) diff --git a/go.mod b/go.mod index d5fa83621..b104cdc70 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ go 1.20 module github.com/babylonchain/babylon require ( - github.com/CosmWasm/wasmd v0.40.0 + github.com/CosmWasm/wasmd v0.44.0 github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 @@ -28,7 +28,7 @@ require ( cosmossdk.io/errors v1.0.0 cosmossdk.io/math v1.1.2 cosmossdk.io/tools/rosetta v0.2.1 - github.com/CosmWasm/wasmvm v1.2.3 + github.com/CosmWasm/wasmvm v1.5.0 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.3 @@ -58,7 +58,7 @@ require ( github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/go-bip39 v1.0.0 - github.com/cosmos/iavl v0.20.0 // indirect + github.com/cosmos/iavl v0.20.1 // indirect github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect @@ -101,10 +101,10 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.15.0 + github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rs/cors v1.8.3 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect diff --git a/go.sum b/go.sum index b77f2a223..df76612ea 100644 --- a/go.sum +++ b/go.sum @@ -210,10 +210,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmd v0.40.0 h1:3Vvq1m8dPQdvZR+QJc86VIx6QoWAEVnuHxqBPshJvyo= -github.com/CosmWasm/wasmd v0.40.0/go.mod h1:SuxskRBB7+bpwXGhUXaEfdpjg5WKpdxBy7Tm36VRMUU= -github.com/CosmWasm/wasmvm v1.2.3 h1:OKYlobwmVGbl0eSn0mXoAAjE5hIuXnQCLPjbNd91sVY= -github.com/CosmWasm/wasmvm v1.2.3/go.mod h1:vW/E3h8j9xBQs9bCoijDuawKo9kCtxOaS8N8J7KFtkc= +github.com/CosmWasm/wasmd v0.44.0 h1:2sbcoCAvfjCs1O0SWt53xULKjkV06dbSFthEViIC6Zg= +github.com/CosmWasm/wasmd v0.44.0/go.mod h1:tDyYN050qUcdd7LOxGeo2e185sEShyO3nJGl2Cf59+k= +github.com/CosmWasm/wasmvm v1.5.0 h1:3hKeT9SfwfLhxTGKH3vXaKFzBz1yuvP8SlfwfQXbQfw= +github.com/CosmWasm/wasmvm v1.5.0/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= @@ -368,8 +368,8 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= -github.com/cosmos/iavl v0.20.0 h1:fTVznVlepH0KK8NyKq8w+U7c2L6jofa27aFX6YGlm38= -github.com/cosmos/iavl v0.20.0/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= +github.com/cosmos/iavl v0.20.1 h1:rM1kqeG3/HBT85vsZdoSNsehciqUQPWrR4BYmqE2+zg= +github.com/cosmos/iavl v0.20.1/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/ibc-go/v7 v7.3.1 h1:bil1IjnHdyWDASFYKfwdRiNtFP6WK3osW7QFEAgU4I8= github.com/cosmos/ibc-go/v7 v7.3.1/go.mod h1:wvx4pPBofe5ZdMNV3OFRxSI4auEP5Qfqf8JXLLNV04g= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= @@ -906,8 +906,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= -github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -930,8 +930,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= diff --git a/simapp/sim_bench_test.go b/simapp/sim_bench_test.go index 4efb8f4c7..aeae76ddd 100644 --- a/simapp/sim_bench_test.go +++ b/simapp/sim_bench_test.go @@ -49,7 +49,6 @@ func BenchmarkFullAppSimulation(b *testing.B) { app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, - app.GetWasmEnabledProposals(), app.EmptyWasmOpts, interBlockCacheOpt()) @@ -116,7 +115,6 @@ func BenchmarkInvariants(b *testing.B) { app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, - app.GetWasmEnabledProposals(), app.EmptyWasmOpts, interBlockCacheOpt()) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index e95e0ac66..e62c11726 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -79,7 +79,7 @@ func TestFullAppSimulation(t *testing.T) { privSigner, err := app.SetupPrivSigner() require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, fauxMerkleModeOpt) + babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) require.Equal(t, "BabylonApp", babylon.Name()) // run randomized simulation @@ -119,7 +119,7 @@ func TestAppImportExport(t *testing.T) { privSigner, err := app.SetupPrivSigner() require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, fauxMerkleModeOpt) + babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) require.Equal(t, "BabylonApp", babylon.Name()) // Run randomized simulation @@ -159,7 +159,7 @@ func TestAppImportExport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, fauxMerkleModeOpt) + newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) require.Equal(t, "BabylonApp", newBabylon.Name()) var genesisState app.GenesisState @@ -223,7 +223,7 @@ func TestAppSimulationAfterImport(t *testing.T) { privSigner, err := app.SetupPrivSigner() require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, fauxMerkleModeOpt) + babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) require.Equal(t, "BabylonApp", babylon.Name()) // Run randomized simulation @@ -268,7 +268,7 @@ func TestAppSimulationAfterImport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, fauxMerkleModeOpt) + newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) require.Equal(t, "BabylonApp", newBabylon.Name()) newBabylon.InitChain(abci.RequestInitChain{ @@ -321,7 +321,7 @@ func TestAppStateDeterminism(t *testing.T) { db := dbm.NewMemDB() privSigner, err := app.SetupPrivSigner() require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.GetWasmEnabledProposals(), app.EmptyWasmOpts, interBlockCacheOpt()) + babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, interBlockCacheOpt()) fmt.Printf( "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index 41e78cff5..c96ecd884 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -5,7 +5,6 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" - "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmvmtypes "github.com/CosmWasm/wasmvm/types" bbn "github.com/babylonchain/babylon/types" @@ -168,7 +167,7 @@ func RegisterCustomPlugins( Custom: CustomQuerier(wasmQueryPlugin), }) - return []wasm.Option{ + return []wasmkeeper.Option{ queryPluginOpt, } } diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index e9f914d0d..66c812514 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -92,8 +92,8 @@ func validateSlashingRate(slashingRate sdk.Dec) error { // validateMaxActiveBTCValidators checks if the maximum number of // active BTC validators is at least the default value func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { - if maxActiveBtcValidators < defaultMaxActiveBtcValidators { - return fmt.Errorf("maximum number of BTC validators is at least %d", defaultMaxActiveBtcValidators) + if maxActiveBtcValidators == 0 { + return fmt.Errorf("max validators must be positive") } return nil } From bb0ad9c6deae8172f20dc324e32cfff7de623bea Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 17 Nov 2023 15:26:41 +1100 Subject: [PATCH 101/202] migrate adaptor signature to Babylon repo (#106) --- crypto/schnorr-adaptor-signature/README.md | 5 + crypto/schnorr-adaptor-signature/keys.go | 113 +++++++++++ crypto/schnorr-adaptor-signature/keys_test.go | 68 +++++++ crypto/schnorr-adaptor-signature/sig.go | 179 ++++++++++++++++++ crypto/schnorr-adaptor-signature/sig_test.go | 142 ++++++++++++++ .../schnorr-adaptor-signature/sign_utils.go | 165 ++++++++++++++++ 6 files changed, 672 insertions(+) create mode 100644 crypto/schnorr-adaptor-signature/README.md create mode 100644 crypto/schnorr-adaptor-signature/keys.go create mode 100644 crypto/schnorr-adaptor-signature/keys_test.go create mode 100644 crypto/schnorr-adaptor-signature/sig.go create mode 100644 crypto/schnorr-adaptor-signature/sig_test.go create mode 100644 crypto/schnorr-adaptor-signature/sign_utils.go diff --git a/crypto/schnorr-adaptor-signature/README.md b/crypto/schnorr-adaptor-signature/README.md new file mode 100644 index 000000000..8d6b043fd --- /dev/null +++ b/crypto/schnorr-adaptor-signature/README.md @@ -0,0 +1,5 @@ +# schnorr-adaptor-signature + +This package provides an implementation of the Schnorr adaptor signature in Golang. +It follows the construction in paper [One-Time Verifiably Encrypted Signatures A.K.A. Adaptor Signatures](https://github.com/LLFourn/one-time-VES/tree/master). +The implementation strictly ports the Rust implementation in [secp256kfun](https://github.com/LLFourn/secp256kfun/blob/master/schnorr_fun/src/adaptor/mod.rs). diff --git a/crypto/schnorr-adaptor-signature/keys.go b/crypto/schnorr-adaptor-signature/keys.go new file mode 100644 index 000000000..795fa7f84 --- /dev/null +++ b/crypto/schnorr-adaptor-signature/keys.go @@ -0,0 +1,113 @@ +package schnorr_adaptor_signature + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +// DecryptionKey is the decryption key in the adaptor +// signature scheme, noted by t in the paper +type DecryptionKey struct { + btcec.ModNScalar +} + +func NewDecyptionKeyFromModNScalar(scalar *btcec.ModNScalar) (*DecryptionKey, error) { + if scalar.IsZero() { + return nil, fmt.Errorf("the given scalar is zero") + } + + return &DecryptionKey{*scalar}, nil +} + +func NewDecyptionKeyFromBTCSK(btcSK *btcec.PrivateKey) (*DecryptionKey, error) { + return NewDecyptionKeyFromModNScalar(&btcSK.Key) +} + +func NewDecyptionKeyFromBytes(decKeyBytes []byte) (*DecryptionKey, error) { + if len(decKeyBytes) != ModNScalarSize { + return nil, fmt.Errorf( + "the length of the given bytes for decryption key is incorrect (expected: %d, actual: %d)", + ModNScalarSize, + len(decKeyBytes), + ) + } + + var decKeyScalar btcec.ModNScalar + decKeyScalar.SetByteSlice(decKeyBytes) //nolint:errcheck + + return NewDecyptionKeyFromModNScalar(&decKeyScalar) +} + +func (dk *DecryptionKey) GetEncKey() *EncryptionKey { + var ekPoint btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(&dk.ModNScalar, &ekPoint) + // NOTE: we convert ekPoint to affine coordinates for consistency + ekPoint.ToAffine() + return &EncryptionKey{ekPoint} +} + +func (dk *DecryptionKey) ToBTCSK() *btcec.PrivateKey { + return &btcec.PrivateKey{Key: dk.ModNScalar} +} + +func (dk *DecryptionKey) ToBytes() []byte { + scalarBytes := dk.ModNScalar.Bytes() + return scalarBytes[:] +} + +type EncryptionKey struct { + btcec.JacobianPoint +} + +func NewEncryptionKeyFromJacobianPoint(point *btcec.JacobianPoint) (*EncryptionKey, error) { + // ensure the point is not at infinity + if (point.X.IsZero() && point.Y.IsZero()) || point.Z.IsZero() { + return nil, fmt.Errorf("the given Jacobian point is at infinity") + } + + // convert point to affine coordinates if necessary + affinePoint := *point + if !affinePoint.Z.IsOne() { + affinePoint.ToAffine() + } + + return &EncryptionKey{affinePoint}, nil +} + +func NewEncryptionKeyFromBTCPK(btcPK *btcec.PublicKey) (*EncryptionKey, error) { + var btcPKPoint btcec.JacobianPoint + btcPK.AsJacobian(&btcPKPoint) + return NewEncryptionKeyFromJacobianPoint(&btcPKPoint) +} + +func NewEncryptionKeyFromBytes(encKeyBytes []byte) (*EncryptionKey, error) { + point, err := btcec.ParseJacobian(encKeyBytes) + if err != nil { + return nil, err + } + return NewEncryptionKeyFromJacobianPoint(&point) +} + +func (ek *EncryptionKey) ToBTCPK() *btcec.PublicKey { + affineEK := *ek + return secp256k1.NewPublicKey(&affineEK.X, &affineEK.Y) +} + +func (ek *EncryptionKey) ToBytes() []byte { + return btcec.JacobianToByteSlice(ek.JacobianPoint) +} + +func GenKeyPair() (*EncryptionKey, *DecryptionKey, error) { + sk, err := btcec.NewPrivateKey() + if err != nil { + return nil, nil, err + } + dk, err := NewDecyptionKeyFromBTCSK(sk) + if err != nil { + return nil, nil, err + } + ek := dk.GetEncKey() + return ek, dk, nil +} diff --git a/crypto/schnorr-adaptor-signature/keys_test.go b/crypto/schnorr-adaptor-signature/keys_test.go new file mode 100644 index 000000000..dcc1bcdf8 --- /dev/null +++ b/crypto/schnorr-adaptor-signature/keys_test.go @@ -0,0 +1,68 @@ +package schnorr_adaptor_signature_test + +import ( + "testing" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/stretchr/testify/require" +) + +func FuzzKeyGen(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, seed []byte) { + encKey, decKey, err := asig.GenKeyPair() + require.NoError(t, err) + + // ensure that decKey.GetEncKey() is same as encKey + actualEncKey := decKey.GetEncKey() + require.Equal(t, encKey, actualEncKey) + + // ensure that the corresponding btcPK and btcSK + // constitute a key pair + btcPK := encKey.ToBTCPK() + btcSK := decKey.ToBTCSK() + actualBTCPK := btcSK.PubKey() + require.Equal(t, btcPK, actualBTCPK) + + // ensure that one can convert btcPK and btcSK back to + // encKey and decKey + actualEncKey, err = asig.NewEncryptionKeyFromBTCPK(btcPK) + require.NoError(t, err) + require.Equal(t, encKey, actualEncKey) + actualDecKey, err := asig.NewDecyptionKeyFromBTCSK(btcSK) + require.NoError(t, err) + require.Equal(t, decKey, actualDecKey) + }) +} + +func FuzzKeySerialization(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, seed []byte) { + encKey, decKey, err := asig.GenKeyPair() + require.NoError(t, err) + + // roundtrip of serialising/deserialising encKey + encKeyBytes := encKey.ToBytes() + actualEncKey, err := asig.NewEncryptionKeyFromBytes(encKeyBytes) + require.NoError(t, err) + require.Equal(t, encKey, actualEncKey) + + // roundtrip of serialising/deserialising decKey + decKeyBytes := decKey.ToBytes() + actualDecKey, err := asig.NewDecyptionKeyFromBytes(decKeyBytes) + require.NoError(t, err) + require.Equal(t, decKey, actualDecKey) + }) +} diff --git a/crypto/schnorr-adaptor-signature/sig.go b/crypto/schnorr-adaptor-signature/sig.go new file mode 100644 index 000000000..3ea28483f --- /dev/null +++ b/crypto/schnorr-adaptor-signature/sig.go @@ -0,0 +1,179 @@ +package schnorr_adaptor_signature + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + schnorr "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // rfc6979ExtraDataV0 is the extra data to feed to RFC6979 when + // generating the deterministic nonce for the BIP-340 scheme. This + // ensures the same nonce is not generated for the same message and key + // as for other signing algorithms such as ECDSA. + // + // It is equal to SHA-256([]byte("BIP-340")). + rfc6979ExtraDataV0 = [chainhash.HashSize]uint8{ + 0xa3, 0xeb, 0x4c, 0x18, 0x2f, 0xae, 0x7e, 0xf4, + 0xe8, 0x10, 0xc6, 0xee, 0x13, 0xb0, 0xe9, 0x26, + 0x68, 0x6d, 0x71, 0xe8, 0x7f, 0x39, 0x4f, 0x79, + 0x9c, 0x00, 0xa5, 0x21, 0x03, 0xcb, 0x4e, 0x17, + } +) + +// AdaptorSignature is the structure for an adaptor signature +// the adaptor signature is a triple (R, s', need_negation) where +// - `R` is the tweaked public randomness, which is derived from +// offsetting public randomness R' sampled by the signer by +// using encryption key T +// - `sHat` is the secret s' in the adaptor signature +// - `needNegation` is a bool value indicating whether decryption +// key needs to be negated when decrypting a Schnorr signature +// It is needed since (R, s') does not tell whether R'+T has odd +// or even y index, thus does not tell whether decryption key needs +// to be negated upon decryption. +type AdaptorSignature struct { + r btcec.JacobianPoint + sHat btcec.ModNScalar + needNegation bool +} + +func newAdaptorSignature(r *btcec.JacobianPoint, sHat *btcec.ModNScalar, needNegation bool) *AdaptorSignature { + var sig AdaptorSignature + sig.r.Set(r) + sig.sHat.Set(sHat) + sig.needNegation = needNegation + return &sig +} + +// EncVerify verifies that the adaptor signature is valid w.r.t. the given +// public key, encryption key and message hash +func (sig *AdaptorSignature) EncVerify(pk *btcec.PublicKey, encKey *EncryptionKey, msgHash []byte) error { + pkBytes := schnorr.SerializePubKey(pk) + return encVerify(sig, msgHash, pkBytes, &encKey.JacobianPoint) +} + +// Decrypt decrypts the adaptor signature to a Schnorr signature by +// using the decryption key `decKey`, noted by `t` in the paper +func (sig *AdaptorSignature) Decrypt(decKey *DecryptionKey) *schnorr.Signature { + R := sig.r + + t := decKey.ModNScalar + if sig.needNegation { + t.Negate() + } + // s = s' + t (or s'-t if negation is needed) + s := sig.sHat + s.Add(&t) + + return schnorr.NewSignature(&R.X, &s) +} + +// Recover recovers the decryption key by using the adaptor signature +// and the Schnorr signature decrypted from it +func (sig *AdaptorSignature) Recover(decryptedSchnorrSig *schnorr.Signature) *DecryptionKey { + // unpack s and R from Schnorr signature + _, s := unpackSchnorrSig(decryptedSchnorrSig) + sHat := sig.sHat + + // extract encryption key t = s - s' + sHat.Negate() + t := s.Add(&sHat) + + if sig.needNegation { + t.Negate() + } + + return &DecryptionKey{*t} +} + +func (sig *AdaptorSignature) ToBytes() []byte { + asigBytes := []byte{} + // append r + rBytes := btcec.JacobianToByteSlice(sig.r) + asigBytes = append(asigBytes, rBytes...) + // append sHat + sHatBytes := sig.sHat.Bytes() + asigBytes = append(asigBytes, sHatBytes[:]...) + // append needNegation + if sig.needNegation { + asigBytes = append(asigBytes, 0x01) + } else { + asigBytes = append(asigBytes, 0x00) + } + return asigBytes +} + +// EncSign generates an adaptor signature by using the given secret key, +// encryption key (noted by `T` in the paper) and message hash +func EncSign(sk *btcec.PrivateKey, encKey *EncryptionKey, msgHash []byte) (*AdaptorSignature, error) { + // d' = int(d) + var skScalar btcec.ModNScalar + skScalar.Set(&sk.Key) + + // Fail if msgHash is not 32 bytes + if len(msgHash) != chainhash.HashSize { + return nil, fmt.Errorf("wrong size for message hash (got %v, want %v)", len(msgHash), chainhash.HashSize) + } + + // Fail if d = 0 or d >= n + if skScalar.IsZero() { + return nil, fmt.Errorf("private key is zero") + } + + // P = 'd*G + pk := sk.PubKey() + + // Negate d if P.y is odd. + pubKeyBytes := pk.SerializeCompressed() + if pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd { + skScalar.Negate() + } + + var privKeyBytes [chainhash.HashSize]byte + skScalar.PutBytes(&privKeyBytes) + for iteration := uint32(0); ; iteration++ { + // Use RFC6979 to generate a deterministic nonce in [1, n-1] + // parameterized by the private key, message being signed, extra data + // that identifies the scheme, and an iteration count + nonce := btcec.NonceRFC6979( + privKeyBytes[:], msgHash, rfc6979ExtraDataV0[:], nil, iteration, + ) + + // try to generate adaptor signature + sig, err := encSign(&skScalar, nonce, pk, msgHash, &encKey.JacobianPoint) + if err != nil { + // Try again with a new nonce. + continue + } + + return sig, nil + } +} + +// NewAdaptorSignatureFromBytes parses the given byte array to an adaptor signature +func NewAdaptorSignatureFromBytes(asigBytes []byte) (*AdaptorSignature, error) { + if len(asigBytes) != AdaptorSignatureSize { + return nil, fmt.Errorf( + "the length of the given bytes for adaptor signature is incorrect (expected: %d, actual: %d)", + AdaptorSignatureSize, + len(asigBytes), + ) + } + + // extract r + r, err := btcec.ParseJacobian(asigBytes[0:JacobianPointSize]) + if err != nil { + return nil, err + } + // extract sHat + var sHat btcec.ModNScalar + sHat.SetByteSlice(asigBytes[JacobianPointSize : JacobianPointSize+ModNScalarSize]) //nolint:errcheck + // extract needNegation + needNegation := asigBytes[AdaptorSignatureSize-1] != 0x00 + + return newAdaptorSignature(&r, &sHat, needNegation), nil +} diff --git a/crypto/schnorr-adaptor-signature/sig_test.go b/crypto/schnorr-adaptor-signature/sig_test.go new file mode 100644 index 000000000..d5a443fe7 --- /dev/null +++ b/crypto/schnorr-adaptor-signature/sig_test.go @@ -0,0 +1,142 @@ +package schnorr_adaptor_signature_test + +import ( + "testing" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/stretchr/testify/require" +) + +func FuzzEncSignAndEncVerify(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, msg []byte) { + // key pair + sk, err := btcec.NewPrivateKey() + require.NoError(t, err) + pk := sk.PubKey() + + // encryption/decryption pair + encKey, _, err := asig.GenKeyPair() + require.NoError(t, err) + + // message hash + msgHash := chainhash.HashB(msg) + + // encSign message + adaptorSig, err := asig.EncSign(sk, encKey, msgHash) + require.NoError(t, err) + + // encVerify message + err = adaptorSig.EncVerify(pk, encKey, msgHash) + require.NoError(t, err) + }) +} + +func FuzzDecrypt(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, msg []byte) { + // key pair + sk, err := btcec.NewPrivateKey() + require.NoError(t, err) + pk := sk.PubKey() + + // encryption/decryption key pair + encKey, decKey, err := asig.GenKeyPair() + require.NoError(t, err) + + // message hash + msgHash := chainhash.HashB(msg) + + // encSign message + adaptorSig, err := asig.EncSign(sk, encKey, msgHash) + require.NoError(t, err) + + // decrypt message + schnorrSig := adaptorSig.Decrypt(decKey) + + // decrypted Schnorr signature should be valid + resVerify := schnorrSig.Verify(msgHash, pk) + require.True(t, resVerify) + }) +} + +func FuzzRecover(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, msg []byte) { + // key pair + sk, err := btcec.NewPrivateKey() + require.NoError(t, err) + + // encryption/decryption key pair + encKey, decKey, err := asig.GenKeyPair() + require.NoError(t, err) + + // message hash + msgHash := chainhash.HashB(msg) + + // encSign message + adaptorSig, err := asig.EncSign(sk, encKey, msgHash) + require.NoError(t, err) + + // decrypt message + schnorrSig := adaptorSig.Decrypt(decKey) + + // recover + expectedDecKey := adaptorSig.Recover(schnorrSig) + + // assert the recovered decryption key is the expected one + require.True(t, expectedDecKey.Equals(&decKey.ModNScalar)) + }) +} + +func FuzzSerializeAdaptorSig(f *testing.F) { + // random seeds + f.Add([]byte("hello")) + f.Add([]byte("1234567890!@#$%^&*()")) + f.Add([]byte("1234567891!@#$%^&*()")) + f.Add([]byte("1234567892!@#$%^&*()")) + f.Add([]byte("1234567893!@#$%^&*()")) + + f.Fuzz(func(t *testing.T, msg []byte) { + // key pair + sk, err := btcec.NewPrivateKey() + require.NoError(t, err) + + // encryption/decryption key pair + encKey, _, err := asig.GenKeyPair() + require.NoError(t, err) + + // message hash + msgHash := chainhash.HashB(msg) + + // encSign message + adaptorSig, err := asig.EncSign(sk, encKey, msgHash) + require.NoError(t, err) + + // roundtrip for serialising/deserialising adaptor signature + adaptorSigBytes := adaptorSig.ToBytes() + actualAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(adaptorSigBytes) + require.NoError(t, err) + require.Equal(t, adaptorSig, actualAdaptorSig) + }) +} diff --git a/crypto/schnorr-adaptor-signature/sign_utils.go b/crypto/schnorr-adaptor-signature/sign_utils.go new file mode 100644 index 000000000..ee61e9e29 --- /dev/null +++ b/crypto/schnorr-adaptor-signature/sign_utils.go @@ -0,0 +1,165 @@ +package schnorr_adaptor_signature + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + schnorr "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +const ( + ModNScalarSize = 32 + FieldValSize = 32 + JacobianPointSize = 33 + AdaptorSignatureSize = JacobianPointSize + ModNScalarSize + 1 +) + +func encSign(privKey, nonce *btcec.ModNScalar, pubKey *btcec.PublicKey, m []byte, T *btcec.JacobianPoint) (*AdaptorSignature, error) { + // R' = kG + var RHat btcec.JacobianPoint + k := *nonce + btcec.ScalarBaseMultNonConst(&k, &RHat) + + // get R = R'+T + var R btcec.JacobianPoint + btcec.AddNonConst(&RHat, T, &R) + // negate k and R if R.y is odd + affineRWithEvenY, needNegation := intoPointWithEvenY(&R) + R = *affineRWithEvenY + if needNegation { + k.Negate() + } + + // e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n + var rBytes [chainhash.HashSize]byte + r := &R.X + r.PutBytesUnchecked(rBytes[:]) + pBytes := schnorr.SerializePubKey(pubKey) + commitment := chainhash.TaggedHash( + chainhash.TagBIP0340Challenge, rBytes[:], pBytes, m, + ) + var e btcec.ModNScalar + e.SetBytes((*[ModNScalarSize]byte)(commitment)) + + // s' = k + e*d mod n + sHat := new(btcec.ModNScalar).Mul2(&e, privKey).Add(&k) + + // compose signature + sig := newAdaptorSignature(&R, sHat, needNegation) + + // perform verification here. Failing to verify the generated signature + // can only be because of bad nonces. The caller function `EncSign` will + // keep trying `encSign` until finding a nonce that generates correct + // signature + if err := encVerify(sig, m, pBytes, T); err != nil { + return nil, fmt.Errorf("the provided nonce does not work: %w", err) + } + + // Return signature + return sig, nil +} + +func encVerify(sig *AdaptorSignature, m []byte, pubKeyBytes []byte, T *btcec.JacobianPoint) error { + // Fail if m is not 32 bytes + if len(m) != chainhash.HashSize { + return fmt.Errorf("wrong size for message (got %v, want %v)", + len(m), chainhash.HashSize) + } + + // R' = R-T (or R+T if it needs negation) + R := &sig.r // NOTE: R is an affine point + var RHat btcec.JacobianPoint + if sig.needNegation { + btcec.AddNonConst(R, T, &RHat) + } else { + btcec.AddNonConst(R, negatePoint(T), &RHat) + } + + RHat.ToAffine() + + // P = lift_x(int(pk)) + pubKey, err := schnorr.ParsePubKey(pubKeyBytes) + if err != nil { + return err + } + // Fail if P is not a point on the curve + if !pubKey.IsOnCurve() { + return fmt.Errorf("pubkey point is not on curve") + } + + // e = int(tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || M)) mod n. + var rBytes [chainhash.HashSize]byte + R.X.PutBytesUnchecked(rBytes[:]) + pBytes := schnorr.SerializePubKey(pubKey) + commitment := chainhash.TaggedHash( + chainhash.TagBIP0340Challenge, rBytes[:], pBytes, m, + ) + var e btcec.ModNScalar + e.SetBytes((*[ModNScalarSize]byte)(commitment)) + + // Negate e here so we can use AddNonConst below to subtract the s'*G + // point from e*P. + e.Negate() + + // expected R' = s'*G - e*P + var P, expRHat, sHatG, eP btcec.JacobianPoint + pubKey.AsJacobian(&P) + btcec.ScalarBaseMultNonConst(&sig.sHat, &sHatG) // s'*G + btcec.ScalarMultNonConst(&e, &P, &eP) // -e*P + btcec.AddNonConst(&sHatG, &eP, &expRHat) // R' = s'*G-e*P + + // Fail if expected R' is the point at infinity + if (expRHat.X.IsZero() && expRHat.Y.IsZero()) || expRHat.Z.IsZero() { + return fmt.Errorf("expected R' point is at infinity") + } + + expRHat.ToAffine() + + // fail if expected R'.y is odd + if expRHat.Y.IsOdd() { + return fmt.Errorf("expected R'.y is odd") + } + + // ensure R' is same as the expected R' = s'*G - e*P + if !expRHat.X.Equals(&RHat.X) { + return fmt.Errorf("expected R' = s'*G - e*P is different from the actual R'") + } + + return nil +} + +// intoPointWithEvenY converts the given Jacobian point to an affine +// point with even y value, and returns a bool value on whether the +// negation is performed. +// The bool value will be used for decrypting an adaptor signature +// to a Schnorr signature. +func intoPointWithEvenY(point *btcec.JacobianPoint) (*btcec.JacobianPoint, bool) { + affinePoint := point + affinePoint.ToAffine() + + needNegation := affinePoint.Y.IsOdd() + + if needNegation { + affinePoint = negatePoint(affinePoint) + } + + return affinePoint, needNegation +} + +// negatePoint negates a point (either Jacobian or affine) +func negatePoint(point *btcec.JacobianPoint) *btcec.JacobianPoint { + nPoint := *point + nPoint.Y.Negate(1).Normalize() + return &nPoint +} + +// unpackSchnorrSig +func unpackSchnorrSig(sig *schnorr.Signature) (*btcec.FieldVal, *btcec.ModNScalar) { + sigBytes := sig.Serialize() + var r btcec.FieldVal + r.SetByteSlice(sigBytes[0:32]) + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[32:64]) + return &r, &s +} From 8d94d8405160eb1f30f372b9e75f533e0a548f76 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 17 Nov 2023 10:20:45 +0100 Subject: [PATCH 102/202] Add new staking script utilities (#103) * Add new staking script utilities --- btcstaking/btc_staking.go | 336 +++++++++++++++++++ btcstaking/btcstaking_test.go | 609 ++++++++++++++++++++++++++++++++++ btcstaking/scripts.go | 121 +++++++ btcstaking/staking.go | 36 +- btcstaking/staking_utils.go | 219 ++++++++++++ 5 files changed, 1311 insertions(+), 10 deletions(-) create mode 100644 btcstaking/btc_staking.go create mode 100644 btcstaking/btcstaking_test.go create mode 100644 btcstaking/scripts.go create mode 100644 btcstaking/staking_utils.go diff --git a/btcstaking/btc_staking.go b/btcstaking/btc_staking.go new file mode 100644 index 000000000..017afc29f --- /dev/null +++ b/btcstaking/btc_staking.go @@ -0,0 +1,336 @@ +package btcstaking + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +type taprootScriptHolder struct { + internalPubKey *btcec.PublicKey + scriptTree *txscript.IndexedTapScriptTree +} + +func newTaprootScriptHolder( + internalPubKey *btcec.PublicKey, + scripts [][]byte, +) (*taprootScriptHolder, error) { + if internalPubKey == nil { + return nil, fmt.Errorf("internal public key is nil") + } + + if len(scripts) == 0 { + return &taprootScriptHolder{ + scriptTree: txscript.NewIndexedTapScriptTree(0), + }, nil + } + + createdLeafs := make(map[chainhash.Hash]bool) + tapLeafs := make([]txscript.TapLeaf, len(scripts)) + + for i, s := range scripts { + script := s + if len(script) == 0 { + return nil, fmt.Errorf("cannot build tree with empty script") + } + + tapLeaf := txscript.NewBaseTapLeaf(script) + leafHash := tapLeaf.TapHash() + + if _, ok := createdLeafs[leafHash]; ok { + return nil, fmt.Errorf("duplicate script in provided scripts") + } + + createdLeafs[leafHash] = true + tapLeafs[i] = tapLeaf + } + + scriptTree := txscript.AssembleTaprootScriptTree(tapLeafs...) + + return &taprootScriptHolder{ + internalPubKey: internalPubKey, + scriptTree: scriptTree, + }, nil +} + +func (t *taprootScriptHolder) scriptSpendInfoByName( + leafHash chainhash.Hash, +) (*SpendInfo, error) { + scriptIdx, ok := t.scriptTree.LeafProofIndex[leafHash] + + if !ok { + return nil, fmt.Errorf("script not found in script tree") + } + + merkleProof := t.scriptTree.LeafMerkleProofs[scriptIdx] + + return &SpendInfo{ + ControlBlock: merkleProof.ToControlBlock(t.internalPubKey), + RevealedLeaf: merkleProof.TapLeaf, + }, nil +} + +func (t *taprootScriptHolder) taprootPkScript(net *chaincfg.Params) ([]byte, error) { + return DeriveTaprootPkScript( + t.scriptTree, + t.internalPubKey, + net, + ) +} + +// Package responsible for different kinds of btc scripts used by babylon +// Staking script has 3 spending paths: +// 1. Staker can spend after relative time lock - staking +// 2. Staker can spend with covenat cooperation any time +// 3. Staker can spend with validator and covenant cooperation any time. +type StakingInfo struct { + StakingOutput *wire.TxOut + scriptHolder *taprootScriptHolder + timeLockPathLeafHash chainhash.Hash + unbondingPathLeafHash chainhash.Hash + slashingPathLeafHash chainhash.Hash +} + +// SpendInfo contains information necessary to create witness for given script +type SpendInfo struct { + // Control block contains merkle proof of inclusion of revealed script path + ControlBlock txscript.ControlBlock + // RevealedLeaf is the leaf of the script tree which is revealed i.e scriptpath + // which is being executed + RevealedLeaf txscript.TapLeaf +} + +func aggregateScripts(scripts ...[]byte) []byte { + if len(scripts) == 0 { + return []byte{} + } + + var finalScript []byte + + for _, script := range scripts { + finalScript = append(finalScript, script...) + } + return finalScript +} + +// babylonScriptPaths is and aggregate of all possible babylon script paths +// not every babylon output will contain all of those paths +type babylonScriptPaths struct { + timeLockPathScript []byte + unbondingPathScript []byte + slashingPathScript []byte +} + +func newBabylonScriptPaths( + stakerKey *btcec.PublicKey, + validatorKeys []*btcec.PublicKey, + covenantKeys []*btcec.PublicKey, + covenantThreshold uint32, + lockTime uint16, +) (*babylonScriptPaths, error) { + if stakerKey == nil { + return nil, fmt.Errorf("staker key is nil") + } + + timeLockPathScript, err := BuildTimeLockScript(stakerKey, lockTime) + + if err != nil { + return nil, err + } + + covenantMultisigScript, err := BuildMultiSigScript( + covenantKeys, + covenantThreshold, + // covenant multisig is always last in script so we do not run verify and leave + // last value on the stack. If we do not leave at least one element on the stack + // script will always error + false, + ) + + if err != nil { + return nil, err + } + + stakerSigScript, err := BuildSingleKeySigScript(stakerKey, true) + + if err != nil { + return nil, err + } + + validatorSigScript, err := BuildMultiSigScript( + validatorKeys, + // we always require only one validator to sign + 1, + // we need to run verify to clear the stack, as validator multisig is in the middle of the script + true, + ) + + if err != nil { + return nil, err + } + + unbondingPathScript := aggregateScripts( + stakerSigScript, + covenantMultisigScript, + ) + + slashingPathScript := aggregateScripts( + stakerSigScript, + validatorSigScript, + covenantMultisigScript, + ) + + return &babylonScriptPaths{ + timeLockPathScript: timeLockPathScript, + unbondingPathScript: unbondingPathScript, + slashingPathScript: slashingPathScript, + }, nil +} + +func BuildStakingInfo( + stakerKey *btcec.PublicKey, + validatorKeys []*btcec.PublicKey, + covenantKeys []*btcec.PublicKey, + covenantThreshold uint32, + stakingTime uint16, + stakingAmount btcutil.Amount, + net *chaincfg.Params, +) (*StakingInfo, error) { + unspendableKeyPathKey := UnspendableKeyPathInternalPubKey() + + babylonScripts, err := newBabylonScriptPaths( + stakerKey, + validatorKeys, + covenantKeys, + covenantThreshold, + stakingTime, + ) + + if err != nil { + return nil, err + } + + var unbodningPaths [][]byte + unbodningPaths = append(unbodningPaths, babylonScripts.timeLockPathScript) + unbodningPaths = append(unbodningPaths, babylonScripts.unbondingPathScript) + unbodningPaths = append(unbodningPaths, babylonScripts.slashingPathScript) + + timeLockLeafHash := txscript.NewBaseTapLeaf(babylonScripts.timeLockPathScript).TapHash() + unbondingPathLeafHash := txscript.NewBaseTapLeaf(babylonScripts.unbondingPathScript).TapHash() + slashingLeafHash := txscript.NewBaseTapLeaf(babylonScripts.slashingPathScript).TapHash() + + sh, err := newTaprootScriptHolder( + &unspendableKeyPathKey, + unbodningPaths, + ) + + if err != nil { + return nil, err + } + + taprootPkScript, err := sh.taprootPkScript(net) + + if err != nil { + return nil, err + } + + stakingOutput := wire.NewTxOut(int64(stakingAmount), taprootPkScript) + + return &StakingInfo{ + StakingOutput: stakingOutput, + scriptHolder: sh, + timeLockPathLeafHash: timeLockLeafHash, + unbondingPathLeafHash: unbondingPathLeafHash, + slashingPathLeafHash: slashingLeafHash, + }, nil +} + +func (i *StakingInfo) TimeLockPathSpendInfo() (*SpendInfo, error) { + return i.scriptHolder.scriptSpendInfoByName(i.timeLockPathLeafHash) +} + +func (i *StakingInfo) UnbondingPathSpendInfo() (*SpendInfo, error) { + return i.scriptHolder.scriptSpendInfoByName(i.unbondingPathLeafHash) +} + +func (i *StakingInfo) SlashingPathSpendInfo() (*SpendInfo, error) { + return i.scriptHolder.scriptSpendInfoByName(i.slashingPathLeafHash) +} + +// Unbonding script has 2 spending paths: +// 1. Staker can spend after relative time lock - staking +// 2. Staker can spend with validator and covenant cooperation any time. +type UnbondingInfo struct { + UnbondingOutput *wire.TxOut + scriptHolder *taprootScriptHolder + timeLockPathLeafHash chainhash.Hash + slashingPathLeafHash chainhash.Hash +} + +func BuildUnbondingInfo( + stakerKey *btcec.PublicKey, + validatorKeys []*btcec.PublicKey, + covenantKeys []*btcec.PublicKey, + covenantThreshold uint32, + unbondingTime uint16, + unbondingAmount btcutil.Amount, + net *chaincfg.Params, +) (*UnbondingInfo, error) { + unspendableKeyPathKey := UnspendableKeyPathInternalPubKey() + + babylonScripts, err := newBabylonScriptPaths( + stakerKey, + validatorKeys, + covenantKeys, + covenantThreshold, + unbondingTime, + ) + + if err != nil { + return nil, err + } + + var unbodningPaths [][]byte + unbodningPaths = append(unbodningPaths, babylonScripts.timeLockPathScript) + unbodningPaths = append(unbodningPaths, babylonScripts.slashingPathScript) + + timeLockLeafHash := txscript.NewBaseTapLeaf(babylonScripts.timeLockPathScript).TapHash() + slashingLeafHash := txscript.NewBaseTapLeaf(babylonScripts.slashingPathScript).TapHash() + + sh, err := newTaprootScriptHolder( + &unspendableKeyPathKey, + unbodningPaths, + ) + + if err != nil { + return nil, err + } + + taprootPkScript, err := sh.taprootPkScript(net) + + if err != nil { + return nil, err + } + + unbondingOutput := wire.NewTxOut(int64(unbondingAmount), taprootPkScript) + + return &UnbondingInfo{ + UnbondingOutput: unbondingOutput, + scriptHolder: sh, + timeLockPathLeafHash: timeLockLeafHash, + slashingPathLeafHash: slashingLeafHash, + }, nil +} + +func (i *UnbondingInfo) TimeLockPathSpendInfo() (*SpendInfo, error) { + return i.scriptHolder.scriptSpendInfoByName(i.timeLockPathLeafHash) +} + +func (i *UnbondingInfo) SlashingPathSpendInfo() (*SpendInfo, error) { + return i.scriptHolder.scriptSpendInfoByName(i.slashingPathLeafHash) +} diff --git a/btcstaking/btcstaking_test.go b/btcstaking/btcstaking_test.go new file mode 100644 index 000000000..993c8b1b9 --- /dev/null +++ b/btcstaking/btcstaking_test.go @@ -0,0 +1,609 @@ +package btcstaking_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/babylonchain/babylon/btcstaking" + btctest "github.com/babylonchain/babylon/testutil/bitcoin" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +type TestScenario struct { + StakerKey *btcec.PrivateKey + ValidatorKeys []*btcec.PrivateKey + CovenantKeys []*btcec.PrivateKey + RequiredCovenantSigs uint32 + StakingAmount btcutil.Amount + StakingTime uint16 +} + +func GenerateTestScenario( + r *rand.Rand, + t *testing.T, + numValidatorKeys uint32, + numCovenantKeys uint32, + requiredCovenantSigs uint32, + stakingAmount btcutil.Amount, + stakingTime uint16, +) *TestScenario { + stakerPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + validatorKeys := make([]*btcec.PrivateKey, numValidatorKeys) + for i := uint32(0); i < numValidatorKeys; i++ { + covenantPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + validatorKeys[i] = covenantPrivKey + } + + covenantKeys := make([]*btcec.PrivateKey, numCovenantKeys) + + for i := uint32(0); i < numCovenantKeys; i++ { + covenantPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + covenantKeys[i] = covenantPrivKey + } + + return &TestScenario{ + StakerKey: stakerPrivKey, + ValidatorKeys: validatorKeys, + CovenantKeys: covenantKeys, + RequiredCovenantSigs: requiredCovenantSigs, + StakingAmount: stakingAmount, + StakingTime: stakingTime, + } +} + +func (t *TestScenario) CovenantPublicKeys() []*btcec.PublicKey { + covenantPubKeys := make([]*btcec.PublicKey, len(t.CovenantKeys)) + + for i, covenantKey := range t.CovenantKeys { + covenantPubKeys[i] = covenantKey.PubKey() + } + + return covenantPubKeys +} + +func (t *TestScenario) ValidatorPublicKeys() []*btcec.PublicKey { + validatorPubKeys := make([]*btcec.PublicKey, len(t.ValidatorKeys)) + + for i, validatorKey := range t.ValidatorKeys { + validatorPubKeys[i] = validatorKey.PubKey() + } + + return validatorPubKeys +} + +func TestSpendingTimeLockPath(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + scenario := GenerateTestScenario( + r, + t, + 1, + 5, + 3, + btcutil.Amount(2*10e8), + 5, + ) + + stakingInfo, err := btcstaking.BuildStakingInfo( + scenario.StakerKey.PubKey(), + scenario.ValidatorPublicKeys(), + scenario.CovenantPublicKeys(), + scenario.RequiredCovenantSigs, + scenario.StakingTime, + scenario.StakingAmount, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(scenario.StakingAmount.MulF64(0.5)), + }, + ) + + // to spend tx as staker, we need to set the sequence number to be >= stakingTimeBlocks + spendStakeTx.TxIn[0].Sequence = uint32(scenario.StakingTime) + + si, err := stakingInfo.TimeLockPathSpendInfo() + require.NoError(t, err) + + sig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.StakerKey, + si.RevealedLeaf, + ) + + require.NoError(t, err) + + witness, err := btcstaking.CreateBabylonWitness( + [][]byte{sig.Serialize()}, + si, + ) + + require.NoError(t, err) + + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.StakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } + btctest.AssertEngineExecution(t, 0, true, newEngine) +} + +// generate list of signatures in valid order +func GenerateSignatures( + t *testing.T, + keys []*btcec.PrivateKey, + tx *wire.MsgTx, + stakingOutput *wire.TxOut, + leaf txscript.TapLeaf, +) [][]byte { + + var si []*btcstaking.SignatureInfo + + for _, key := range keys { + pubKey := key.PubKey() + sig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + tx, + stakingOutput, + key, + leaf, + ) + require.NoError(t, err) + info := btcstaking.NewSignatureInfo( + pubKey, + sig.Serialize(), + ) + si = append(si, info) + } + + // sort signatures by public key + sortedSigInfo := btcstaking.SortSignatureInfo(si) + + var sigs [][]byte = make([][]byte, len(sortedSigInfo)) + + for i, sigInfo := range sortedSigInfo { + sig := sigInfo + sigs[i] = sig.Signature + } + + return sigs +} + +func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + + // we are having here 3/5 covenant threshold sig + scenario := GenerateTestScenario( + r, + t, + 1, + 5, + 3, + btcutil.Amount(2*10e8), + 5, + ) + + stakingInfo, err := btcstaking.BuildStakingInfo( + scenario.StakerKey.PubKey(), + scenario.ValidatorPublicKeys(), + scenario.CovenantPublicKeys(), + scenario.RequiredCovenantSigs, + scenario.StakingTime, + scenario.StakingAmount, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(scenario.StakingAmount.MulF64(0.5)), + }, + ) + + si, err := stakingInfo.UnbondingPathSpendInfo() + require.NoError(t, err) + + stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.StakerKey, + si.RevealedLeaf, + ) + + require.NoError(t, err) + + // scenario where all keys are available + covenantSigantures := GenerateSignatures( + t, + scenario.CovenantKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + var witnessSignatures [][]byte + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err := btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.StakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } + btctest.AssertEngineExecution(t, 0, true, newEngine) + + numOfCovenantMembers := len(scenario.CovenantKeys) + // with each loop iteration we remove one key from the list of signatures + for i := 0; i < numOfCovenantMembers; i++ { + // reset signatures + witnessSignatures = [][]byte{} + + numOfRemovedSignatures := i + 1 + + covenantSigantures := GenerateSignatures( + t, + scenario.CovenantKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + + for j := 0; j <= i; j++ { + // NOTE: Number provides signatures must match number of public keys in the script, + // if we are missing some signatures those must be set to empty signature in witness + covenantSigantures[j] = []byte{} + } + + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err := btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + if numOfCovenantMembers-numOfRemovedSignatures >= int(scenario.RequiredCovenantSigs) { + // if we are above threshold execution should be successful + btctest.AssertEngineExecution(t, 0, true, newEngine) + } else { + // we are below threshold execution should be unsuccessful + btctest.AssertEngineExecution(t, 0, false, newEngine) + } + } +} + +func TestSpendingUnbondingPathSingleKeyCovenant(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + + // generate single key covenant + scenario := GenerateTestScenario( + r, + t, + 1, + 1, + 1, + btcutil.Amount(2*10e8), + 5, + ) + + stakingInfo, err := btcstaking.BuildStakingInfo( + scenario.StakerKey.PubKey(), + scenario.ValidatorPublicKeys(), + scenario.CovenantPublicKeys(), + scenario.RequiredCovenantSigs, + scenario.StakingTime, + scenario.StakingAmount, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(scenario.StakingAmount.MulF64(0.5)), + }, + ) + + si, err := stakingInfo.UnbondingPathSpendInfo() + require.NoError(t, err) + + stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.StakerKey, + si.RevealedLeaf, + ) + require.NoError(t, err) + + // scenario where all keys are available + covenantSigantures := GenerateSignatures( + t, + scenario.CovenantKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + var witnessSignatures [][]byte + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err := btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.StakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } + btctest.AssertEngineExecution(t, 0, true, newEngine) +} + +func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + + // we are having here 3/5 covenant threshold sig + scenario := GenerateTestScenario( + r, + t, + 1, + 5, + 3, + btcutil.Amount(2*10e8), + 5, + ) + + stakingInfo, err := btcstaking.BuildStakingInfo( + scenario.StakerKey.PubKey(), + scenario.ValidatorPublicKeys(), + scenario.CovenantPublicKeys(), + scenario.RequiredCovenantSigs, + scenario.StakingTime, + scenario.StakingAmount, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(scenario.StakingAmount.MulF64(0.5)), + }, + ) + + si, err := stakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.StakerKey, + si.RevealedLeaf, + ) + require.NoError(t, err) + + // Case without validator signature + covenantSigantures := GenerateSignatures( + t, + scenario.CovenantKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + var witnessSignatures [][]byte + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err := btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, + ) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.StakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } + // we expect it will fail because we are missing validator signature + btctest.AssertEngineExecution(t, 0, false, newEngine) + + // Retry with the same values but now with validator signature present + witnessSignatures = [][]byte{} + validatorSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.ValidatorKeys[0], + si.RevealedLeaf, + ) + require.NoError(t, err) + + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, validatorSig.Serialize()) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err = btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + // now as we have validator signature execution should succeed + btctest.AssertEngineExecution(t, 0, true, newEngine) +} + +func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + + // we are having here 3/5 covenant threshold sig, and we are restaking to 2 validators + scenario := GenerateTestScenario( + r, + t, + 2, + 5, + 3, + btcutil.Amount(2*10e8), + 5, + ) + + stakingInfo, err := btcstaking.BuildStakingInfo( + scenario.StakerKey.PubKey(), + scenario.ValidatorPublicKeys(), + scenario.CovenantPublicKeys(), + scenario.RequiredCovenantSigs, + scenario.StakingTime, + scenario.StakingAmount, + &chaincfg.MainNetParams, + ) + + require.NoError(t, err) + + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(scenario.StakingAmount.MulF64(0.5)), + }, + ) + + si, err := stakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + stakingInfo.StakingOutput, + scenario.StakerKey, + si.RevealedLeaf, + ) + require.NoError(t, err) + + // Case without validator signature + covenantSigantures := GenerateSignatures( + t, + scenario.CovenantKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + var witnessSignatures [][]byte + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err := btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, + ) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.StakingOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } + // we expect it will fail because we are missing validators signature + btctest.AssertEngineExecution(t, 0, false, newEngine) + + // Retry with the same values but now with validator signature present + witnessSignatures = [][]byte{} + + validatorsSignatures := GenerateSignatures( + t, + scenario.ValidatorKeys, + spendStakeTx, + stakingInfo.StakingOutput, + si.RevealedLeaf, + ) + + // make one signature empty, script still should be valid as we require only one validator signature + // to be present + validatorsSignatures[0] = []byte{} + + witnessSignatures = append(witnessSignatures, covenantSigantures...) + witnessSignatures = append(witnessSignatures, validatorsSignatures...) + witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) + witness, err = btcstaking.CreateBabylonWitness( + witnessSignatures, + si, + ) + require.NoError(t, err) + spendStakeTx.TxIn[0].Witness = witness + + // now as we have validator signature execution should succeed + btctest.AssertEngineExecution(t, 0, true, newEngine) +} diff --git a/btcstaking/scripts.go b/btcstaking/scripts.go new file mode 100644 index 000000000..41f7eac7c --- /dev/null +++ b/btcstaking/scripts.go @@ -0,0 +1,121 @@ +package btcstaking + +import ( + "bytes" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/txscript" +) + +// private helper to build multisig script +// SCRIPT: OP_CHEKCSIG OP_CHECKSIGADD OP_CHECKSIGADD ... OP_CHECKSIGADD OP_GREATERTHANOREQUAL OP_VERIFY +func buildMultiSigScript( + pubkeys []*btcec.PublicKey, + threshold uint32, + withVerify bool, +) ([]byte, error) { + builder := txscript.NewScriptBuilder() + + for i, key := range pubkeys { + builder.AddData(schnorr.SerializePubKey(key)) + if i == 0 { + builder.AddOp(txscript.OP_CHECKSIG) + } else { + builder.AddOp(txscript.OP_CHECKSIGADD) + } + } + + builder.AddInt64(int64(threshold)) + builder.AddOp(txscript.OP_GREATERTHANOREQUAL) + if withVerify { + builder.AddOp(txscript.OP_VERIFY) + } + + return builder.Script() +} + +// prepareKeys prepares keys to be used in multisig script +// Validates: +// - whether there are at lest 2 keys +// - whether there are no duplicate keys +// returns copy of the slice of keys sorted lexicographically +func prepareKeysForMultisigScript(keys []*btcec.PublicKey) ([]*btcec.PublicKey, error) { + if len(keys) < 2 { + return nil, fmt.Errorf("cannot create multisig script with less than 2 keys") + } + + sortedKeys := sortKeys(keys) + + for i := 0; i < len(sortedKeys)-1; i++ { + if bytes.Equal(schnorr.SerializePubKey(sortedKeys[i]), schnorr.SerializePubKey(sortedKeys[i+1])) { + return nil, fmt.Errorf("duplicate key in list of keys") + } + } + + return sortedKeys, nil +} + +// BuildMultiSigScript creates multisig script with given keys and signer threshold to +// successfully execute script +// it validates whether provided keys are unique and the threshold is not greater than number of keys +// If there is only one key provided it will return single key sig script +func BuildMultiSigScript( + keys []*btcec.PublicKey, + threshold uint32, + withVerify bool, +) ([]byte, error) { + if len(keys) == 0 { + return nil, fmt.Errorf("no keys provided") + } + + if threshold > uint32(len(keys)) { + return nil, fmt.Errorf("required number of valid signers is greater than number of provided keys") + } + + if len(keys) == 1 { + // if we have only one key we can use single key sig script + return BuildSingleKeySigScript(keys[0], withVerify) + } + + sortedKeys, err := prepareKeysForMultisigScript(keys) + + if err != nil { + return nil, err + } + + return buildMultiSigScript(sortedKeys, threshold, withVerify) +} + +// Only holder of private key for given pubKey can spend after relative lock time +// SCRIPT: OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY +func BuildTimeLockScript( + pubKey *btcec.PublicKey, + lockTime uint16, +) ([]byte, error) { + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(pubKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddInt64(int64(lockTime)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + return builder.Script() +} + +// Only holder of private key for given pubKey can spend +// SCRIPT: OP_CHECKSIGVERIFY +func BuildSingleKeySigScript( + pubKey *btcec.PublicKey, + withVerify bool, +) ([]byte, error) { + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(pubKey)) + + if withVerify { + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + } else { + builder.AddOp(txscript.OP_CHECKSIG) + } + + return builder.Script() +} diff --git a/btcstaking/staking.go b/btcstaking/staking.go index a658415e2..719dd906d 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -23,6 +23,26 @@ const ( unspendableKeyPath = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" ) +var ( + unspendableKeyPathKey = unspendableKeyPathInternalPubKeyInternal(unspendableKeyPath) +) + +func unspendableKeyPathInternalPubKeyInternal(keyHex string) btcec.PublicKey { + keyBytes, err := hex.DecodeString(keyHex) + + if err != nil { + panic(fmt.Sprintf("unexpected error: %v", err)) + } + + // We are using btcec here, as key is 33 byte compressed format. + pubKey, err := btcec.ParsePubKey(keyBytes) + + if err != nil { + panic(fmt.Sprintf("unexpected error: %v", err)) + } + return *pubKey +} + // Following methods are copied from btcd. In most recent they are not exported. // TODO: on btcd master those are already exported. Remove this copies // when this will be released. @@ -215,6 +235,7 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { var templateOffset int tokenizer := txscript.MakeScriptTokenizer(0, script) + for tokenizer.Next() { // Not an staking script if it has more opcodes than expected in the // template. @@ -316,13 +337,8 @@ func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { return scriptData, nil } -func UnspendableKeyPathInternalPubKey() *btcec.PublicKey { - // TODO: We could cache it in some cached private package variable if performance - // is necessary, as this returns always the same value. - keyBytes, _ := hex.DecodeString(unspendableKeyPath) - // We are using btcec here, as key is 33 byte compressed format. - pubKey, _ := btcec.ParsePubKey(keyBytes) - return pubKey +func UnspendableKeyPathInternalPubKey() btcec.PublicKey { + return unspendableKeyPathKey } // TaprootAddressForScript returns a Taproot address commiting to the given script, built taproot tree @@ -357,7 +373,7 @@ func TaprootAddressForScript( func BuildUnspendableTaprootPkScript(rawScript []byte, net *chaincfg.Params) ([]byte, error) { internalPubKey := UnspendableKeyPathInternalPubKey() - address, err := TaprootAddressForScript(rawScript, internalPubKey, net) + address, err := TaprootAddressForScript(rawScript, &internalPubKey, net) if err != nil { return nil, err @@ -413,7 +429,7 @@ func NewWitnessFromStakingScriptAndSignature( internalPubKey := UnspendableKeyPathInternalPubKey() tapLeaf := txscript.NewBaseTapLeaf(stakingScript) tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) - ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(internalPubKey) + ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(&internalPubKey) ctrlBlockBytes, err := ctrlBlock.ToBytes() if err != nil { return nil, err @@ -786,7 +802,7 @@ func signTxWithOneScriptSpendInputFromTapLeafInternal( return nil, err } - return parsedSig, err + return parsedSig, nil } // SignTxWithOneScriptSpendInputFromTapLeaf signs transaction with one input coming diff --git a/btcstaking/staking_utils.go b/btcstaking/staking_utils.go new file mode 100644 index 000000000..4c7ea0e12 --- /dev/null +++ b/btcstaking/staking_utils.go @@ -0,0 +1,219 @@ +package btcstaking + +import ( + "bytes" + "fmt" + "sort" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// key sorting code copied from musig2 impl in btcd: https://github.com/btcsuite/btcd/blob/master/btcec/schnorr/musig2/keys.go +type sortableKeys []*btcec.PublicKey + +// Less reports whether the element with index i must sort before the element +// with index j. +func (s sortableKeys) Less(i, j int) bool { + // TODO(roasbeef): more efficient way to compare... + keyIBytes := schnorr.SerializePubKey(s[i]) + keyJBytes := schnorr.SerializePubKey(s[j]) + + return bytes.Compare(keyIBytes, keyJBytes) == -1 +} + +// Swap swaps the elements with indexes i and j. +func (s sortableKeys) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Len is the number of elements in the collection. +func (s sortableKeys) Len() int { + return len(s) +} + +// sortKeys takes a set of schnorr public keys and returns a new slice that is +// a copy of the keys sorted in lexicographical order bytes on the x-only +// pubkey serialization. +func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey { + keySet := sortableKeys(keys) + if sort.IsSorted(keySet) { + return keys + } + + sort.Sort(keySet) + return keySet +} + +type SignatureInfo struct { + SignerPubKey *btcec.PublicKey + Signature []byte +} + +func NewSignatureInfo( + signerPubKey *btcec.PublicKey, + signature []byte, +) *SignatureInfo { + return &SignatureInfo{ + SignerPubKey: signerPubKey, + Signature: signature, + } +} + +type sortableSigInfo []*SignatureInfo + +// Less reports whether the element with index i must sort before the element +// with index j. +func (s sortableSigInfo) Less(i, j int) bool { + // TODO(roasbeef): more efficient way to compare... + keyIBytes := schnorr.SerializePubKey(s[i].SignerPubKey) + keyJBytes := schnorr.SerializePubKey(s[j].SignerPubKey) + + return bytes.Compare(keyIBytes, keyJBytes) == 1 +} + +// Swap swaps the elements with indexes i and j. +func (s sortableSigInfo) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Len is the number of elements in the collection. +func (s sortableSigInfo) Len() int { + return len(s) +} + +// Helper function to sort all signatures in reverse lexicographical order of signing public keys +// this way signatures are ready to be used in multisig witness with corresponding public keys +func SortSignatureInfo(infos []*SignatureInfo) []*SignatureInfo { + keySet := sortableSigInfo(infos) + if sort.IsSorted(keySet) { + return infos + } + + sort.Sort(keySet) + return keySet +} + +func SpendInfoFromRevealedScript( + revealedScript []byte, + internalKey *btcec.PublicKey, + tree *txscript.IndexedTapScriptTree) (*SpendInfo, error) { + + revealedLeaf := txscript.NewBaseTapLeaf(revealedScript) + leafHash := revealedLeaf.TapHash() + + scriptIdx, ok := tree.LeafProofIndex[leafHash] + + if !ok { + return nil, fmt.Errorf("script not found in script tree") + } + + merkleProof := tree.LeafMerkleProofs[scriptIdx] + + return &SpendInfo{ + ControlBlock: merkleProof.ToControlBlock(internalKey), + RevealedLeaf: revealedLeaf, + }, nil +} + +func NewTaprootTreeFromScripts( + scripts [][]byte, +) *txscript.IndexedTapScriptTree { + var tapLeafs []txscript.TapLeaf + for _, script := range scripts { + scr := script + tapLeafs = append(tapLeafs, txscript.NewBaseTapLeaf(scr)) + } + return txscript.AssembleTaprootScriptTree(tapLeafs...) +} + +func DeriveTaprootAddress( + tapScriptTree *txscript.IndexedTapScriptTree, + internalPubKey *btcec.PublicKey, + net *chaincfg.Params) (*btcutil.AddressTaproot, error) { + + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + + outputKey := txscript.ComputeTaprootOutputKey( + internalPubKey, tapScriptRootHash[:], + ) + + address, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(outputKey), net) + + if err != nil { + return nil, fmt.Errorf("error encoding Taproot address: %v", err) + } + + return address, nil +} + +func DeriveTaprootPkScript( + tapScriptTree *txscript.IndexedTapScriptTree, + internalPubKey *btcec.PublicKey, + net *chaincfg.Params, +) ([]byte, error) { + taprootAddress, err := DeriveTaprootAddress( + tapScriptTree, + &unspendableKeyPathKey, + net, + ) + + if err != nil { + return nil, err + } + + taprootPkScript, err := txscript.PayToAddrScript(taprootAddress) + + if err != nil { + return nil, err + } + + return taprootPkScript, nil +} + +// CreateBabylonWitness creates babylon compatible witness, as babylon scripts +// has witness with the same shape +// - first come signatures +// - then whole revealed script +// - then control block +func CreateBabylonWitness( + signatures [][]byte, + si *SpendInfo, +) (wire.TxWitness, error) { + numSignatures := len(signatures) + + if numSignatures == 0 { + return nil, fmt.Errorf("cannot build witness without signatures") + } + + if si == nil { + return nil, fmt.Errorf("cannot build witness without spend info") + } + + controlBlockBytes, err := si.ControlBlock.ToBytes() + + if err != nil { + return nil, err + } + + // witness stack has: + // all signatures + // whole revealed script + // control block + witnessStack := wire.TxWitness(make([][]byte, numSignatures+2)) + + for i, sig := range signatures { + sc := sig + witnessStack[i] = sc + } + + witnessStack[numSignatures] = si.RevealedLeaf.Script + witnessStack[numSignatures+1] = controlBlockBytes + + return witnessStack, nil +} From 7316f109439efe5cf9cf38013ada711850d41d13 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:02:17 -0500 Subject: [PATCH 103/202] feat: Add Validation for Fractional Slashing (#96) * slashing tx validation * fix * fix * fix * fix * fix check * fix tests and checks * fix * fix checks * fix data types * fix * update e2e tests * fix comments * fix comments * fix comments * fix comments * change to slashing rate * fix comments and checks * fix checks * hardcode slashing rate to 100% to pass tests * fix docstring * more comments * add slashing rate check in checktransactions * change staker addr to change addr * pass change addr in msgcreatedelegation * change address in msgbtcundelegate * error handling for change addr * verbose errors * fix fee checks and validation for slashing tx on rounded slashing rate * more comments * fix docstring * more comments in fee checks * remove staking output info in msgundelegate * remove receiver func Validate * fix tests and include change address in MsgCreateBTCDelegation request * fix tests and pass change address in MsgBTCUndelegate * fix naming and remove dead code * fix e2e tests - pass change addr in types.MsgCreateBTCDelegation * fix imports * fix import issue * fix imports * fix imports * fix * fix args in tx * fix imports * change address in undelegation and delegation creation * keep Validate func * add more comments * fix proto comments * fix slashing rate validation * fix validation * more comments * fix checks in validation and don't depend on change address * remove change address proto * remove change address usages * fix receiver func * remove todos * fix args * fix func sigs * fix imports * fix defs * fix imports * fix formatting * change to ceil(round up) * lock time 0 * fix checks * move fee checks * fix doc string * convert to btcutil.amount * dust relay tx fee * fix dust fn * fix mod * add todo * fix docstring --- btcstaking/staking.go | 146 ++++++++++++++++++------- btcstaking/staking_test.go | 35 ++++-- go.mod | 2 + go.sum | 2 + types/utils.go | 9 ++ x/btcstaking/keeper/msg_server.go | 6 +- x/btcstaking/keeper/msg_server_test.go | 4 +- x/btcstaking/types/btc_slashing_tx.go | 20 +++- x/btcstaking/types/params.go | 14 +-- 9 files changed, 173 insertions(+), 65 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 719dd906d..4639db75e 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -6,12 +6,15 @@ import ( "fmt" "math" + "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -642,26 +645,95 @@ func IsSimpleTransfer(tx *wire.MsgTx) error { return nil } -// IsSlashingTx perform basic checks on slashing transaction: -// - slashing transaction is not nil -// - slashing transaction has exactly one input -// - slashing transaction is not replacable -// - slashing transaction has exactly one output -// - slashing transaction locktime is 0 -// - slashing transaction output is simple pay to address script paying to provided slashing address -func IsSlashingTx(slashingTx *wire.MsgTx, slashingAddress btcutil.Address) error { - if err := IsSimpleTransfer(slashingTx); err != nil { - return fmt.Errorf("invalid slashing tx: %w", err) +// ValidateSlashingTx performs basic checks on a slashing transaction: +// - the slashing transaction is not nil. +// - the slashing transaction has exactly one input. +// - the slashing transaction is non-replaceable. +// - the lock time of the slashing transaction is 0. +// - the slashing transaction has exactly two outputs, and: +// - the first output must pay to the provided slashing address. +// - the first output must pay at least (staking output value * slashing rate) to the slashing address. +// - neither of the outputs are considered dust. +// - the min fee for slashing tx is preserved +func ValidateSlashingTx( + slashingTx *wire.MsgTx, + slashingAddress btcutil.Address, + slashingRate sdk.Dec, + slashingTxMinFee, stakingOutputValue int64, +) error { + // Verify that the slashing transaction is not nil. + if slashingTx == nil { + return fmt.Errorf("slashing transaction must not be nil") + } + + // Verify that the slashing transaction has exactly one input. + if len(slashingTx.TxIn) != 1 { + return fmt.Errorf("slashing transaction must have exactly one input") } - pkScript, err := txscript.PayToAddrScript(slashingAddress) + // Verify that the slashing transaction is non-replaceable. + if slashingTx.TxIn[0].Sequence != wire.MaxTxInSequenceNum { + return fmt.Errorf("slashing transaction must not be replaceable") + } + // Verify that lock time of the slashing transaction is 0. + if slashingTx.LockTime != 0 { + return fmt.Errorf("slashing tx must not have locktime") + } + + // Verify that the slashing transaction has exactly two outputs. + if len(slashingTx.TxOut) != 2 { + return fmt.Errorf("slashing transaction must have exactly 2 outputs") + } + + // Verify that at least staking output value * slashing rate is slashed. + slashingRateFloat64, err := slashingRate.Float64() if err != nil { - return err + return fmt.Errorf("error converting slashing rate to float64: %w", err) + } + minSlashingAmount := btcutil.Amount(stakingOutputValue).MulF64(slashingRateFloat64) + if btcutil.Amount(slashingTx.TxOut[0].Value) < minSlashingAmount { + return fmt.Errorf("slashing transaction must slash at least staking output value * slashing rate") } - if !bytes.Equal(slashingTx.TxOut[0].PkScript, pkScript) { - return fmt.Errorf("slashing transaction must pay to provided slashing address") + // Verify that the first output pays to the provided slashing address. + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + if err != nil { + return fmt.Errorf("error creating slashing pk script: %w", err) + } + if !bytes.Equal(slashingTx.TxOut[0].PkScript, slashingPkScript) { + return fmt.Errorf("slashing transaction must pay to the provided slashing address") + } + + // Verify that the none of the outputs is a dust output. + for _, out := range slashingTx.TxOut { + if mempool.IsDust(out, mempool.DefaultMinRelayTxFee) { + return fmt.Errorf("slashing transaction must not have a dust output") + } + } + + /* + Check Fees + */ + // Check that values of slashing and staking transaction are larger than 0 + if slashingTx.TxOut[0].Value <= 0 || stakingOutputValue <= 0 { + return fmt.Errorf("values of slashing and staking transaction must be larger than 0") + } + + // Calculate the sum of output values in the slashing transaction. + slashingTxOutSum := int64(0) + for _, out := range slashingTx.TxOut { + slashingTxOutSum += out.Value + } + + // Ensure that the staking transaction value is larger than the sum of slashing transaction output values. + if stakingOutputValue <= slashingTxOutSum { + return fmt.Errorf("slashing transaction must not spend more than staking transaction") + } + + // Ensure that the slashing transaction fee is larger than the specified minimum fee. + if stakingOutputValue-slashingTxOutSum < slashingTxMinFee { + return fmt.Errorf("slashing transaction fee must be larger than %d", slashingTxMinFee) } return nil @@ -703,43 +775,52 @@ func GetIdxOutputCommitingToScript( } // CheckTransactions validates all relevant data of slashing and funding transaction. -// - slashing transaction is valid // - funding transaction script is valid // - funding transaction has output committing to the provided script -// - slashing transaction input is pointing to funding transaction output commiting to the script -// - that min fee for slashing tx is preserved -// In case of success, it returns data extracted from valid staking script and staking amount. +// - slashing transaction is valid +// - slashing transaction input hash is pointing to funding transaction hash +// - slashing transaction input index is pointing to funding transaction output commiting to the script func CheckTransactions( slashingTx *wire.MsgTx, fundingTransaction *wire.MsgTx, slashingTxMinFee int64, + slashingRate sdk.Dec, slashingAddress btcutil.Address, script []byte, net *chaincfg.Params, ) (*StakingOutputInfo, error) { + // Check if slashing tx min fee is valid if slashingTxMinFee <= 0 { return nil, fmt.Errorf("slashing transaction min fee must be larger than 0") } - // 1. Check slashing tx - if err := IsSlashingTx(slashingTx, slashingAddress); err != nil { - return nil, err + // Check if slashing rate is in the valid range (0,1) + if !types.IsValidSlashingRate(slashingRate) { + return nil, fmt.Errorf("slashing rate must be in the range (0, 1)") } - // 2. Check staking script. + // 1. Check if the staking script is valid and extract data from it scriptData, err := ParseStakingTransactionScript(script) - if err != nil { return nil, err } - // 3. Check that staking transaction has output committing to the provided script + // 2. Check that staking transaction has output committing to the provided script stakingOutputIdx, err := GetIdxOutputCommitingToScript(fundingTransaction, script, net) - if err != nil { return nil, err } + stakingOutput := fundingTransaction.TxOut[stakingOutputIdx] + // 3. Check if slashing transaction is valid + if err := ValidateSlashingTx( + slashingTx, + slashingAddress, + slashingRate, + slashingTxMinFee, stakingOutput.Value); err != nil { + return nil, err + } + // 4. Check that slashing transaction input is pointing to staking transaction stakingTxHash := fundingTransaction.TxHash() if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { @@ -751,21 +832,6 @@ func CheckTransactions( return nil, fmt.Errorf("slashing transaction input must spend staking output") } - stakingOutput := fundingTransaction.TxOut[stakingOutputIdx] - - // 6. Check fees - if slashingTx.TxOut[0].Value <= 0 || stakingOutput.Value <= 0 { - return nil, fmt.Errorf("values of slashing and staking transaction must be larger than 0") - } - - if stakingOutput.Value <= slashingTx.TxOut[0].Value { - return nil, fmt.Errorf("slashing transaction must not spend more than staking transaction") - } - - if stakingOutput.Value-slashingTx.TxOut[0].Value < slashingTxMinFee { - return nil, fmt.Errorf("slashing transaction fee must be larger than %d", slashingTxMinFee) - } - return &StakingOutputInfo{ StakingScriptData: scriptData, StakingAmount: btcutil.Amount(stakingOutput.Value), diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index c66537d84..c575a9032 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" @@ -16,6 +17,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -67,7 +69,8 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { require.NoError(t, err) minStakingValue := 5000 minFee := 2000 - + // generate a random slashing rate in range [0.01, 0.99] + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2) slashingAddress, err := btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) require.NoError(t, err) @@ -102,18 +105,16 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { script, &chaincfg.MainNetParams, ) - require.NoError(t, err) - _, err = btcstaking.CheckTransactions( slashingTx, stakingTx, int64(minFee), + slashingRate, slashingAddress, script, &chaincfg.MainNetParams, ) - require.NoError(t, err) // Check case with some random fee @@ -126,19 +127,39 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { script, &chaincfg.MainNetParams, ) - require.NoError(t, err) - _, err = btcstaking.CheckTransactions( slashingTx, stakingTx, int64(minFee), + slashingRate, slashingAddress, script, &chaincfg.MainNetParams, ) - require.NoError(t, err) + + // Check case when slashing rate is invalid + _, err = btcstaking.CheckTransactions( + slashingTx, + stakingTx, + int64(minFee), + sdkmath.LegacyMustNewDecFromStr("1.5"), // invalid slashing rate + slashingAddress, + script, + &chaincfg.MainNetParams, + ) + require.Error(t, err) + _, err = btcstaking.CheckTransactions( + slashingTx, + stakingTx, + int64(minFee), + sdkmath.LegacyZeroDec(), // invalid slashing rate + slashingAddress, + script, + &chaincfg.MainNetParams, + ) + require.Error(t, err) }) } diff --git a/go.mod b/go.mod index b104cdc70..17fb9daf6 100644 --- a/go.mod +++ b/go.mod @@ -136,6 +136,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/aead/siphash v1.0.1 // indirect github.com/aws/aws-sdk-go v1.44.203 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect @@ -177,6 +178,7 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/linxGnu/grocksdb v1.7.16 // indirect diff --git a/go.sum b/go.sum index df76612ea..50b74f818 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -746,6 +747,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= diff --git a/types/utils.go b/types/utils.go index 59d9eb82c..6df984e14 100644 --- a/types/utils.go +++ b/types/utils.go @@ -3,6 +3,8 @@ package types import ( "fmt" "reflect" + + sdk "github.com/cosmos/cosmos-sdk/types" ) func Reverse(s interface{}) { @@ -29,3 +31,10 @@ func CheckForDuplicatesAndEmptyStrings(input []string) error { return nil } + +// IsValidSlashingRate checks if the given slashing rate is b/w the valid range i.e., (0,1) +func IsValidSlashingRate(slashingRate sdk.Dec) bool { + // TODO: add check to confirm precision is max 2 decimal places + + return slashingRate.GT(sdk.ZeroDec()) && slashingRate.LT(sdk.OneDec()) +} diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 9e12603ca..ed91a75f2 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -189,14 +189,18 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } + + // decode slashing address slashingAddr, err := btcutil.DecodeAddress(params.SlashingAddress, ms.btcNet) if err != nil { panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) } + if _, err := btcstaking.CheckTransactions( slashingMsgTx, stakingMsgTx, params.MinSlashingTxFeeSat, + params.SlashingRate, slashingAddr, req.StakingTx.Script, ms.btcNet, @@ -279,11 +283,11 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele slashingMsgTx, unbondingMsgTx, params.MinSlashingTxFeeSat, + params.SlashingRate, slashingAddress, req.UnbondingTx.Script, ms.btcNet, ) - if err != nil { return nil, types.ErrInvalidUnbodningTx.Wrapf("invalid unbonding tx: %v", err) } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 399348602..b65cae5dd 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -100,7 +100,7 @@ func getCovenantInfo(t *testing.T, SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.MustNewDecFromStr("0.1"), + SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) @@ -408,7 +408,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { SlashingAddress: slashingAddr.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.MustNewDecFromStr("0.1"), + SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 3a636f830..9b4289986 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -5,13 +5,13 @@ import ( "encoding/hex" "fmt" + "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - - "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) type BTCSlashingTx []byte @@ -89,7 +89,12 @@ func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { return &msgTx, nil } -func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) error { +func (tx *BTCSlashingTx) Validate( + net *chaincfg.Params, + slashingAddress string, + slashingRate sdk.Dec, + slashingTxMinFee, stakingOutputValue int64, +) error { msgTx, err := tx.ToMsgTx() if err != nil { return err @@ -98,7 +103,12 @@ func (tx *BTCSlashingTx) Validate(net *chaincfg.Params, slashingAddress string) if err != nil { return err } - return btcstaking.IsSlashingTx(msgTx, decodedAddr) + return btcstaking.ValidateSlashingTx( + msgTx, + decodedAddr, + slashingRate, + slashingTxMinFee, stakingOutputValue, + ) } // Sign generates a signature on the slashing tx signed by staker, validator or covenant diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 66c812514..cb55e0b89 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -81,14 +81,6 @@ func validateMinCommissionRate(rate sdk.Dec) error { return nil } -// validateSlashingRate checks if the slashing rate is within the valid range (0, 1]. -func validateSlashingRate(slashingRate sdk.Dec) error { - if slashingRate.LTE(math.LegacyZeroDec()) || slashingRate.GT(math.LegacyOneDec()) { - return fmt.Errorf("slashing rate must be in the range (0, 1] i.e., 0 exclusive and 1 inclusive") - } - return nil -} - // validateMaxActiveBTCValidators checks if the maximum number of // active BTC validators is at least the default value func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { @@ -106,9 +98,11 @@ func (p Params) Validate() error { if err := validateMinCommissionRate(p.MinCommissionRate); err != nil { return err } - if err := validateSlashingRate(p.SlashingRate); err != nil { - return err + + if !bbn.IsValidSlashingRate(p.SlashingRate) { + return fmt.Errorf("slashing rate must be in the range (0, 1)") } + if err := validateMaxActiveBTCValidators(p.MaxActiveBtcValidators); err != nil { return err } From 5d4322558fb185636c8b13b5bc332b3947bbe8d2 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:16:37 -0500 Subject: [PATCH 104/202] feat: Update slashing tx format (#104) * slashing tx validation * fix * fix * fix * fix * fix check * fix tests and checks * fix * fix checks * fix data types * fix * update e2e tests * fix comments * fix comments * fix comments * fix comments * change to slashing rate * fix comments and checks * fix checks * hardcode slashing rate to 100% to pass tests * fix docstring * more comments * add slashing rate check in checktransactions * change staker addr to change addr * pass change addr in msgcreatedelegation * change address in msgbtcundelegate * error handling for change addr * verbose errors * fix fee checks and validation for slashing tx on rounded slashing rate * more comments * fix docstring * more comments in fee checks * remove staking output info in msgundelegate * remove receiver func Validate * fix tests and include change address in MsgCreateBTCDelegation request * fix tests and pass change address in MsgBTCUndelegate * fix naming and remove dead code * fix e2e tests - pass change addr in types.MsgCreateBTCDelegation * fix imports * fix import issue * fix imports * fix imports * fix * fix args in tx * fix imports * change address in undelegation and delegation creation * keep Validate func * add more comments * fix proto comments * fix slashing rate validation * fix validation * more comments * fix checks in validation and don't depend on change address * remove change address proto * remove change address usages * fix receiver func * remove todos * fix args * fix func sigs * fix imports * fix defs * fix imports * fix formatting * change to ceil(round up) * lock time 0 * fix checks * move fee checks * fix doc string * convert to btcutil.amount * dust relay tx fee * fix dust fn * fix mod * add todo * fix docstring * changes in build logic and fix tests * rename vars * fix tests * fix slashing rate in tests to avoid change amount being too low * improve slashing rate validity and fix tests * improvise tests * fix comments * fix spacing * fix slashing rate in tests * docstrings * docstring * more comments * docstring * move btcstaking errors to btcstaking/ dir * move validation fn inside btcstaking/ dir * enforce checks if slashing/change address are same * imrpove precision check * fix slashing rate * fix tests --- btcstaking/errors.go | 11 + btcstaking/staking.go | 191 +++++++++++++----- btcstaking/staking_test.go | 136 +++++++------ test/e2e/btc_staking_e2e_test.go | 8 +- testutil/datagen/btcstaking.go | 71 ++++++- types/utils.go | 9 - x/btcstaking/keeper/grpc_query_test.go | 82 +++++++- x/btcstaking/keeper/incentive_test.go | 22 +- x/btcstaking/keeper/msg_server_test.go | 68 +++++-- .../keeper/voting_power_table_test.go | 72 ++++++- x/btcstaking/types/btc_slashing_tx_test.go | 23 ++- x/btcstaking/types/btcstaking_test.go | 23 ++- x/btcstaking/types/params.go | 6 +- 13 files changed, 545 insertions(+), 177 deletions(-) create mode 100644 btcstaking/errors.go diff --git a/btcstaking/errors.go b/btcstaking/errors.go new file mode 100644 index 000000000..85899c5a4 --- /dev/null +++ b/btcstaking/errors.go @@ -0,0 +1,11 @@ +package btcstaking + +import "errors" + +var ( + ErrInvalidSlashingRate = errors.New("invalid slashing rate") + ErrDustOutputFound = errors.New("transaction contains a dust output") + ErrInsufficientSlashingAmount = errors.New("insufficient slashing amount") + ErrInsufficientChangeAmount = errors.New("insufficient change amount") + ErrSameAddress = errors.New("slashing and change addresses cannot be the same") +) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 4639db75e..936729e59 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -6,7 +6,6 @@ import ( "fmt" "math" - "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" @@ -489,33 +488,84 @@ func ValidateStakingOutputPkScript( return ParseStakingTransactionScript(script) } -// BuildSlashingTxFromOutpoint builds valid slashing transaction, using provided: -// - stakingOutput - staking output -// - slashingAddress - address to which slashed funds will go -// - fee - fee for the transaction -// It does not attach script sig to the transaction nor the witness. -// It only validates that provided address is standard btc address and slashing value is larger than 0 +// BuildSlashingTxFromOutpoint builds a valid slashing transaction by creating a new Bitcoin transaction that slashes a portion +// of staked funds and directs them to a specified slashing address. The transaction also includes a change output sent back to +// the specified change address. The slashing rate determines the proportion of staked funds to be slashed. +// +// Parameters: +// - stakingOutput: The staking output to be spent in the transaction. +// - stakingAmount: The amount of staked funds in the staking output. +// - fee: The transaction fee to be paid. +// - slashingAddress: The Bitcoin address to which the slashed funds will be sent. +// - changeAddress: The Bitcoin address to receive the change from the transaction. +// - slashingRate: The rate at which the staked funds will be slashed, expressed as a decimal. +// +// Returns: +// - *wire.MsgTx: The constructed slashing transaction without a script signature or witness. +// - error: An error if any validation or construction step fails. func BuildSlashingTxFromOutpoint( stakingOutput wire.OutPoint, - slashingAddress btcutil.Address, - slashingValue int64) (*wire.MsgTx, error) { + stakingAmount, fee int64, + slashingAddress, changeAddress btcutil.Address, + slashingRate sdk.Dec, +) (*wire.MsgTx, error) { + // Validate staking amount + if stakingAmount <= 0 { + return nil, fmt.Errorf("staking amount must be larger than 0") + } - addrScript, err := txscript.PayToAddrScript(slashingAddress) + // Validate slashing rate + if !IsSlashingRateValid(slashingRate) { + return nil, ErrInvalidSlashingRate + } + + // Check if slashing address and change address are the same + if slashingAddress.EncodeAddress() == changeAddress.EncodeAddress() { + return nil, ErrSameAddress + } + // Calculate the amount to be slashed + slashingRateFloat64, err := slashingRate.Float64() + if err != nil { + return nil, fmt.Errorf("error converting slashing rate to float64: %w", err) + } + slashingAmount := btcutil.Amount(stakingAmount).MulF64(slashingRateFloat64) + if slashingAmount <= 0 { + return nil, ErrInsufficientSlashingAmount + } + // Generate script for slashing address + slashingAddrScript, err := txscript.PayToAddrScript(slashingAddress) if err != nil { return nil, err } - if slashingValue <= 0 { - return nil, fmt.Errorf("slashing value cannot be smaller or equal 0") + // Calculate the change amount + changeAmount := btcutil.Amount(stakingAmount) - slashingAmount - btcutil.Amount(fee) + if changeAmount <= 0 { + return nil, ErrInsufficientChangeAmount + } + // Generate script for change address + changeAddrScript, err := txscript.PayToAddrScript(changeAddress) + if err != nil { + return nil, err } + // Create a new btc transaction tx := wire.NewMsgTx(wire.TxVersion) // TODO: this builds input with sequence number equal to MaxTxInSequenceNum, which // means this tx is not replacable. input := wire.NewTxIn(&stakingOutput, nil, nil) tx.AddTxIn(input) - tx.AddTxOut(wire.NewTxOut(slashingValue, addrScript)) + tx.AddTxOut(wire.NewTxOut(int64(slashingAmount), slashingAddrScript)) + tx.AddTxOut(wire.NewTxOut(int64(changeAmount), changeAddrScript)) + + // Verify that the none of the outputs is a dust output. + for _, out := range tx.TxOut { + if mempool.IsDust(out, mempool.DefaultMinRelayTxFee) { + return nil, ErrDustOutputFound + } + } + return tx, nil } @@ -540,70 +590,100 @@ func getPossibleStakingOutput( return stakingOutput, nil } -// BuildSlashingTxFromOutpoint builds valid slashing transaction, using provided: -// - stakingTx - staking trasaction -// - stakingOutputIdx - index of the output committing to staking script -// - slashingAddress - address to which slashed funds will go -// - fee - fee for the transaction -// It does not attach script sig to the transaction nor the witness. -// It validates: -// - stakingTx is not nil -// - staking tx has output at stakingOutputIdx -// - staking output at stakingOutputIdx is valid staking output i.e p2tr output +// BuildSlashingTxFromStakingTx constructs a valid slashing transaction using information from a staking transaction, +// a specified staking output index, and other parameters such as slashing and change addresses, slashing rate, and transaction fee. +// +// Parameters: +// - stakingTx: The staking transaction from which the staking output is to be used for slashing. +// - stakingOutputIdx: The index of the staking output in the staking transaction. +// - slashingAddress: The Bitcoin address to which the slashed funds will be sent. +// - changeAddress: The Bitcoin address to receive the change from the transaction. +// - slashingRate: The rate at which the staked funds will be slashed, expressed as a decimal. +// - fee: The transaction fee to be paid. +// +// Returns: +// - *wire.MsgTx: The constructed slashing transaction without a script signature or witness. +// - error: An error if any validation or construction step fails. +// +// This function validates that the staking transaction is not nil, has an output at the specified index, +// and that the staking output at the specified index is a valid staking output. +// It then calls BuildSlashingTxFromOutpoint to build the slashing transaction using the validated staking output. func BuildSlashingTxFromStakingTx( stakingTx *wire.MsgTx, stakingOutputIdx uint32, - slashingAddress btcutil.Address, + slashingAddress, changeAddress btcutil.Address, + slashingRate sdk.Dec, fee int64, ) (*wire.MsgTx, error) { + // Get the staking output at the specified index from the staking transaction stakingOutput, err := getPossibleStakingOutput(stakingTx, stakingOutputIdx) - if err != nil { return nil, err } + // Create an OutPoint for the staking output stakingTxHash := stakingTx.TxHash() - stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) - return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee) + // Build the slashing transaction using the staking output + return BuildSlashingTxFromOutpoint( + *stakingOutpoint, + stakingOutput.Value, fee, + slashingAddress, changeAddress, + slashingRate) } -// BuildSlashingTxFromStakingTxStrict builds valid slashing transaction, using provided: -// - stakingTx - staking trasaction -// - stakingOutputIdx - index of the output committing to staking script -// - slashingAddress - address to which slashed funds will go -// - fee - fee for the transaction -// - script - staking script to which staking output should commit -// - scriptVersion - version of the script -// - net - network on wchich transactions should take place -// It validates: -// - the same stuff as BuildSlashingTxFromStakingTx -// - wheter staking output commits to the provided script -// - whether provided script is valid staking script +// BuildSlashingTxFromStakingTxStrict constructs a valid slashing transaction using information from a staking transaction, +// a specified staking output index, and additional parameters such as slashing and change addresses, transaction fee, +// staking script, script version, and network. This function performs stricter validation compared to BuildSlashingTxFromStakingTx. +// +// Parameters: +// - stakingTx: The staking transaction from which the staking output is to be used for slashing. +// - stakingOutputIdx: The index of the staking output in the staking transaction. +// - slashingAddress: The Bitcoin address to which the slashed funds will be sent. +// - changeAddress: The Bitcoin address to receive the change from the transaction. +// - fee: The transaction fee to be paid. +// - slashingRate: The rate at which the staked funds will be slashed, expressed as a decimal. +// - script: The staking script to which the staking output should commit. +// - net: The network on which transactions should take place (e.g., mainnet, testnet). +// +// Returns: +// - *wire.MsgTx: The constructed slashing transaction without script signature or witness. +// - error: An error if any validation or construction step fails. +// +// This function validates the same conditions as BuildSlashingTxFromStakingTx and additionally checks whether the +// staking output at the specified index commits to the provided script and whether the provided script is a valid +// staking script for the given network. If any of these additional validations fail, an error is returned. func BuildSlashingTxFromStakingTxStrict( stakingTx *wire.MsgTx, stakingOutputIdx uint32, - slashingAddress btcutil.Address, + slashingAddress, changeAddress btcutil.Address, fee int64, + slashingRate sdk.Dec, script []byte, net *chaincfg.Params, ) (*wire.MsgTx, error) { + // Get the staking output at the specified index from the staking transaction stakingOutput, err := getPossibleStakingOutput(stakingTx, stakingOutputIdx) - if err != nil { return nil, err } - if _, err := ValidateStakingOutputPkScript(stakingOutput, script, net); err != nil { + // Validate that the staking output commits to the provided script and the script is valid + if _, err = ValidateStakingOutputPkScript(stakingOutput, script, net); err != nil { return nil, err } + // Create an OutPoint for the staking output stakingTxHash := stakingTx.TxHash() - stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) - return BuildSlashingTxFromOutpoint(*stakingOutpoint, slashingAddress, stakingOutput.Value-fee) + // Build slashing tx with the staking output information + return BuildSlashingTxFromOutpoint( + *stakingOutpoint, + stakingOutput.Value, fee, + slashingAddress, changeAddress, + slashingRate) } // Transfer transaction is a transaction which: @@ -708,7 +788,7 @@ func ValidateSlashingTx( // Verify that the none of the outputs is a dust output. for _, out := range slashingTx.TxOut { if mempool.IsDust(out, mempool.DefaultMinRelayTxFee) { - return fmt.Errorf("slashing transaction must not have a dust output") + return ErrDustOutputFound } } @@ -795,8 +875,8 @@ func CheckTransactions( } // Check if slashing rate is in the valid range (0,1) - if !types.IsValidSlashingRate(slashingRate) { - return nil, fmt.Errorf("slashing rate must be in the range (0, 1)") + if !IsSlashingRateValid(slashingRate) { + return nil, ErrInvalidSlashingRate } // 1. Check if the staking script is valid and extract data from it @@ -1042,3 +1122,20 @@ func VerifyTransactionSigWithOutputData( return nil } + +// IsSlashingRateValid checks if the given slashing rate is between the valid range i.e., (0,1) with a precision of at most 2 decimal places. +func IsSlashingRateValid(slashingRate sdk.Dec) bool { + // Check if the slashing rate is between 0 and 1 + if slashingRate.LTE(sdk.ZeroDec()) || slashingRate.GTE(sdk.OneDec()) { + return false + } + + // Multiply by 100 to move the decimal places and check if precision is at most 2 decimal places + multipliedRate := slashingRate.Mul(sdk.NewDec(100)) + + // Truncate the rate to remove decimal places + truncatedRate := multipliedRate.TruncateDec() + + // Check if the truncated rate is equal to the original rate + return multipliedRate.Equal(truncatedRate) +} diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index c575a9032..574f5304b 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" @@ -69,10 +68,10 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { require.NoError(t, err) minStakingValue := 5000 minFee := 2000 - // generate a random slashing rate in range [0.01, 0.99] - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2) - slashingAddress, err := btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) - require.NoError(t, err) + // generate a random slashing rate with random precision, + // this will include both valid and invalid ranges, so we can test both cases + randomPrecision := r.Int63n(4) // [0,3] + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 1001)), randomPrecision) // [0,1000] / 10^{randomPrecision} for i := 0; i < stakingTxNumOutputs; i++ { if i == stakingOutputIdx { @@ -97,72 +96,81 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { } // Always check case with min fee - slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( - stakingTx, - uint32(stakingOutputIdx), - slashingAddress, - int64(minFee), - script, - &chaincfg.MainNetParams, - ) - require.NoError(t, err) - _, err = btcstaking.CheckTransactions( - slashingTx, - stakingTx, - int64(minFee), - slashingRate, - slashingAddress, - script, - &chaincfg.MainNetParams, - ) - require.NoError(t, err) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, script, int64(minFee)) // Check case with some random fee fee := int64(r.Intn(1000) + minFee) - slashingTx, err = btcstaking.BuildSlashingTxFromStakingTxStrict( - stakingTx, - uint32(stakingOutputIdx), - slashingAddress, - fee, - script, - &chaincfg.MainNetParams, - ) - require.NoError(t, err) - _, err = btcstaking.CheckTransactions( - slashingTx, - stakingTx, - int64(minFee), - slashingRate, - slashingAddress, - script, - &chaincfg.MainNetParams, - ) - require.NoError(t, err) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, script, fee) - // Check case when slashing rate is invalid - _, err = btcstaking.CheckTransactions( - slashingTx, - stakingTx, - int64(minFee), - sdkmath.LegacyMustNewDecFromStr("1.5"), // invalid slashing rate - slashingAddress, - script, - &chaincfg.MainNetParams, - ) - require.Error(t, err) - _, err = btcstaking.CheckTransactions( - slashingTx, - stakingTx, - int64(minFee), - sdkmath.LegacyZeroDec(), // invalid slashing rate - slashingAddress, - script, - &chaincfg.MainNetParams, - ) - require.Error(t, err) }) } +func genRandomBTCAddress(r *rand.Rand) (*btcutil.AddressPubKeyHash, error) { + return btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) +} + +func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdk.Dec, + script []byte, fee int64) { + dustThreshold := 546 // in satoshis + + // Generate random slashing and change addresses + slashingAddress, err := genRandomBTCAddress(r) + require.NoError(t, err) + + changeAddress, err := genRandomBTCAddress(r) + require.NoError(t, err) + + // Construct slashing transaction using the provided parameters + slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( + stakingTx, + uint32(stakingOutputIdx), + slashingAddress, changeAddress, + fee, + slashingRate, + script, + &chaincfg.MainNetParams, + ) + + if btcstaking.IsSlashingRateValid(slashingRate) { + // If the slashing rate is valid i.e., in the range (0,1) with at most 2 decimal places, + // it is still possible that the slashing transaction is invalid. The following checks will confirm that + // slashing tx is not constructed if + // - the change output has insufficient funds. + // - the change output is less than the dust threshold. + // - The slashing output is less than the dust threshold. + + slashingRateFloat64, err2 := slashingRate.Float64() + require.NoError(t, err2) + + stakingAmount := btcutil.Amount(stakingTx.TxOut[stakingOutputIdx].Value) + slashingAmount := stakingAmount.MulF64(slashingRateFloat64) + changeAmount := stakingAmount - slashingAmount - btcutil.Amount(fee) + + if changeAmount <= 0 { + require.Error(t, err) + require.ErrorIs(t, err, btcstaking.ErrInsufficientChangeAmount) + } else if changeAmount <= btcutil.Amount(dustThreshold) || slashingAmount <= btcutil.Amount(dustThreshold) { + require.Error(t, err) + require.ErrorIs(t, err, btcstaking.ErrDustOutputFound) + } else { + require.NoError(t, err) + _, err = btcstaking.CheckTransactions( + slashingTx, + stakingTx, + fee, + slashingRate, + slashingAddress, + script, + &chaincfg.MainNetParams, + ) + require.NoError(t, err) + } + } else { + require.Error(t, err) + require.ErrorIs(t, err, btcstaking.ErrInvalidSlashingRate) + } +} + func FuzzGeneratingSignatureValidation(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index f65962ccc..e97ade831 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -40,6 +40,8 @@ var ( ) stakingValue = int64(2 * 10e8) + + changeAddress, _ = datagen.GenRandomBTCAddress(r, net) ) type BTCStakingTestSuite struct { @@ -112,7 +114,8 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { params.CovenantPk.MustToBTCPK(), stakingTimeBlocks, stakingValue, - params.SlashingAddress, + params.SlashingAddress, changeAddress.String(), + params.SlashingRate, ) s.NoError(err) stakingMsgTx, err := stakingTx.ToMsgTx() @@ -406,7 +409,8 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { wire.NewOutPoint(stakingTxChainHash, uint32(stakingOutputIdx)), initialization.BabylonBtcFinalizationPeriod+1, stakingValue-fee, - params.SlashingAddress, + params.SlashingAddress, changeAddress.String(), + params.SlashingRate, ) s.NoError(err) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index fafe8f32a..3f05da94f 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -13,7 +13,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -63,7 +63,15 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe }, nil } -func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btcec.PrivateKey, covenantSK *btcec.PrivateKey, slashingAddr string, startHeight uint64, endHeight uint64, totalSat uint64) (*bstypes.BTCDelegation, error) { +func GenRandomBTCDelegation( + r *rand.Rand, + valBTCPK *bbn.BIP340PubKey, + delSK *btcec.PrivateKey, + covenantSK *btcec.PrivateKey, + slashingAddress, changeAddress string, + startHeight, endHeight, totalSat uint64, + slashingRate sdk.Dec, +) (*bstypes.BTCDelegation, error) { net := &chaincfg.SimNetParams delPK := delSK.PubKey() delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) @@ -88,7 +96,16 @@ func GenRandomBTCDelegation(r *rand.Rand, valBTCPK *bbn.BIP340PubKey, delSK *btc return nil, err } // staking/slashing tx - stakingTx, slashingTx, err := GenBTCStakingSlashingTx(r, net, delSK, valPK, covenantBTCPK, uint16(endHeight-startHeight), int64(totalSat), slashingAddr) + stakingTx, slashingTx, err := GenBTCStakingSlashingTx( + r, + net, + delSK, + valPK, + covenantBTCPK, + uint16(endHeight-startHeight), + int64(totalSat), + slashingAddress, changeAddress, + slashingRate) if err != nil { return nil, err } @@ -131,7 +148,8 @@ func GenBTCStakingSlashingTxWithOutPoint( covenantPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingAddress, changeAddress string, + slashingRate sdk.Dec, withChange bool, ) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( @@ -181,7 +199,18 @@ func GenBTCStakingSlashingTxWithOutPoint( if err != nil { return nil, nil, err } - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict(tx, 0, slashingAddrBtc, 2000, stakingScript, btcNet) + changeAddrBtc, err := btcutil.DecodeAddress(changeAddress, btcNet) + if err != nil { + return nil, nil, err + } + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( + tx, + 0, + slashingAddrBtc, changeAddrBtc, + 2000, + slashingRate, + stakingScript, + btcNet) if err != nil { return nil, nil, err } @@ -201,12 +230,24 @@ func GenBTCStakingSlashingTx( covenantPK *btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingAddress, changeAddress string, + slashingRate sdk.Dec, ) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) outPoint := &spend.prevOut - return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, outPoint, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddress, true) + return GenBTCStakingSlashingTxWithOutPoint( + r, + btcNet, + outPoint, + stakerSK, + validatorPK, + covenantPK, + stakingTimeBlocks, + stakingValue, + slashingAddress, changeAddress, + slashingRate, + true) } func GenBTCUnbondingSlashingTx( @@ -218,7 +259,19 @@ func GenBTCUnbondingSlashingTx( stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingAddress, changeAddress string, + slashingRate sdk.Dec, ) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { - return GenBTCStakingSlashingTxWithOutPoint(r, btcNet, stakingTransactionOutpoint, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddress, false) + return GenBTCStakingSlashingTxWithOutPoint( + r, + btcNet, + stakingTransactionOutpoint, + stakerSK, + validatorPK, + covenantPK, + stakingTimeBlocks, + stakingValue, + slashingAddress, changeAddress, + slashingRate, + false) } diff --git a/types/utils.go b/types/utils.go index 6df984e14..59d9eb82c 100644 --- a/types/utils.go +++ b/types/utils.go @@ -3,8 +3,6 @@ package types import ( "fmt" "reflect" - - sdk "github.com/cosmos/cosmos-sdk/types" ) func Reverse(s interface{}) { @@ -31,10 +29,3 @@ func CheckForDuplicatesAndEmptyStrings(input []string) error { return nil } - -// IsValidSlashingRate checks if the given slashing rate is b/w the valid range i.e., (0,1) -func IsValidSlashingRate(slashingRate sdk.Dec) bool { - // TODO: add check to confirm precision is max 2 decimal places - - return slashingRate.GT(sdk.ZeroDec()) && slashingRate.LT(sdk.OneDec()) -} diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 442fd6c30..76d42082e 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -174,8 +174,16 @@ func FuzzPendingBTCDelegations(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random number of BTC validators numBTCVals := datagen.RandomInt(r, 5) + 1 @@ -196,7 +204,15 @@ func FuzzPendingBTCDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation( + r, + btcVal.BtcPk, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + startHeight, endHeight, 10000, + slashingRate, + ) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { // remove covenant sig in random BTC delegations to make them inactive @@ -262,8 +278,16 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random number of BTC validators numBTCVals := datagen.RandomInt(r, 5) + 1 @@ -284,7 +308,15 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation( + r, + btcVal.BtcPk, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + startHeight, endHeight, 10000, + slashingRate, + ) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { @@ -423,8 +455,16 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random batch of validators var btcVals []*types.BTCValidator @@ -449,7 +489,15 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, 10000) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + valBTCPK, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, 10000, + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -506,7 +554,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { } func FuzzBTCValidatorDelegations(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) + datagen.AddRandomSeedsToFuzzer(f, 100) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) ctrl := gomock.NewController(t) @@ -521,8 +569,16 @@ func FuzzBTCValidatorDelegations(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a btc validator btcVal, err := datagen.GenRandomBTCValidator(r) @@ -537,7 +593,15 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, btcVal.BtcPk, delSK, covenantSK, slashingAddr.String(), startHeight, endHeight, 10000) + btcDel, err := datagen.GenRandomBTCDelegation( + r, + btcVal.BtcPk, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + startHeight, endHeight, 10000, + slashingRate, + ) require.NoError(t, err) expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel err = keeper.AddBTCDelegation(ctx, btcDel) diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 5e312c6c3..37f76eff9 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -10,6 +10,7 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -31,8 +32,16 @@ func FuzzRecordRewardDistCache(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 @@ -52,11 +61,18 @@ func FuzzRecordRewardDistCache(f *testing.F) { numBTCDels := datagen.RandomInt(r, 10) + 1 stakingValue := datagen.RandomInt(r, 100000) + 100000 for _, btcVal := range btcValsWithVotingPowerMap { - valBTCPK := btcVal.BtcPk for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + btcVal.BtcPk, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, stakingValue, + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index b65cae5dd..11224333b 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -93,18 +93,18 @@ func getCovenantInfo(t *testing.T, sdkCtx sdk.Context) (*btcec.PrivateKey, *btcec.PublicKey, btcutil.Address) { covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, net) + slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(sdkCtx, types.Params{ CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - SlashingAddress: slashingAddr.String(), + SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2), + SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) - return covenantSK, covenantPK, slashingAddr + return covenantSK, covenantPK, slashingAddress } @@ -141,7 +141,8 @@ func createDelegation( net *chaincfg.Params, validatorPK *btcec.PublicKey, covenantPK *btcec.PublicKey, - slashingAddr string, + slashingAddress, changeAddress string, + slashingRate sdk.Dec, stakingTime uint16, ) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) @@ -156,7 +157,8 @@ func createDelegation( covenantPK, stakingTimeBlocks, stakingValue, - slashingAddr, + slashingAddress, changeAddress, + slashingRate, ) require.NoError(t, err) // get msgTx @@ -286,7 +288,8 @@ func createUndelegation( delSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, covenantPK *btcec.PublicKey, - slashingAddr string, + slashingAddress, changeAddress string, + slashingRate sdk.Dec, ) *types.MsgBTCUndelegate { stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) require.NoError(t, err) @@ -302,7 +305,8 @@ func createUndelegation( wire.NewOutPoint(stkTxHash, stkOutputIdx), uint16(defaultParams.CheckpointFinalizationTimeout)+1, int64(actualDel.TotalSat)-1000, - slashingAddr, + slashingAddress, changeAddress, + slashingRate, ) require.NoError(t, err) // random signer @@ -347,7 +351,10 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { goCtx := sdk.WrapSDKContext(ctx) // set covenant PK to params - covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) /* generate and insert new BTC validator @@ -367,7 +374,8 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { net, validatorPK, covenantPK, - slashingAddr.String(), + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, 1000, ) @@ -401,14 +409,16 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // set covenant PK to params _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, net) + slashingAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - SlashingAddress: slashingAddr.String(), + SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 99)+1), 2), + SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) @@ -425,7 +435,17 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + r, + net, + delSK, + validatorPK, + covenantPK, + stakingTimeBlocks, + stakingValue, + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, + ) require.NoError(t, err) // get msgTx stakingMsgTx, err := stakingTx.ToMsgTx() @@ -485,7 +505,9 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) _, validatorPK, _ := createValidator(t, r, goCtx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, @@ -497,7 +519,8 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { net, validatorPK, covenantPK, - slashingAddr.String(), + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) @@ -515,7 +538,8 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { delSK, validatorPK, covenantPK, - slashingAddr.String(), + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, ) actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) @@ -547,7 +571,9 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { ms := keeper.NewMsgServerImpl(*bsKeeper) goCtx := sdk.WrapSDKContext(ctx) - covenantSK, covenantPK, slashingAddr := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) valSk, validatorPK, _ := createValidator(t, r, goCtx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, @@ -559,7 +585,8 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { net, validatorPK, covenantPK, - slashingAddr.String(), + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) @@ -577,7 +604,8 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { delSK, validatorPK, covenantPK, - slashingAddr.String(), + slashingAddress.String(), changeAddress.String(), + bsKeeper.GetParams(ctx).SlashingRate, ) del, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 1debc8e3e..c1bd544fe 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -11,6 +11,7 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -32,8 +33,16 @@ func FuzzVotingPowerTable(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators btcVals := []*types.BTCValidator{} @@ -50,11 +59,18 @@ func FuzzVotingPowerTable(f *testing.F) { numBTCDels := datagen.RandomInt(r, 10) + 1 stakingValue := datagen.RandomInt(r, 100000) + 100000 for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - valBTCPK := btcVals[i].BtcPk for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + btcVals[i].BtcPk, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, stakingValue, + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -187,8 +203,16 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators, each with a BTC delegation with random power btcValsWithMeta := []*types.BTCValidatorWithMeta{} @@ -204,7 +228,15 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { valBTCPK := btcVal.BtcPk delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + valBTCPK, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, stakingValue, // timelock period: 1-1000 + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -273,8 +305,16 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { // covenant and slashing addr covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators, each with a BTC delegation with random power btcValsWithMeta := []*types.BTCValidatorWithMeta{} @@ -290,7 +330,15 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { valBTCPK := btcVal.BtcPk delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, valBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + valBTCPK, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, stakingValue, // timelock period: 1-1000 + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) @@ -328,7 +376,15 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { activatedValBTCPK, _ = bbn.NewBIP340PubKeyFromHex(valBTCPKHex) delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation(r, activatedValBTCPK, delSK, covenantSK, slashingAddr.String(), 1, 1000, stakingValue) // timelock period: 1-1000 + btcDel, err := datagen.GenRandomBTCDelegation( + r, + activatedValBTCPK, + delSK, + covenantSK, + slashingAddress.String(), changeAddress.String(), + 1, 1000, stakingValue, // timelock period: 1-1000 + slashingRate, + ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 16ce9ee4f..51ad987e0 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -8,6 +8,7 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -23,8 +24,16 @@ func FuzzSlashingTxWithWitness(f *testing.F) { ) // slashing address and key paris - slashingAddr, err := datagen.GenRandomBTCAddress(r, net) + slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) @@ -33,7 +42,17 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) // generate staking/slashing tx - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx(r, net, delSK, valPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + r, + net, + delSK, + valPK, + covenantPK, + stakingTimeBlocks, + stakingValue, + slashingAddress.String(), changeAddress.String(), + slashingRate, + ) require.NoError(t, err) stakingOutInfo, err := stakingTx.GetBabylonOutputInfo(net) require.NoError(t, err) diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 273afaa0b..8b43650e7 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -8,6 +8,7 @@ import ( bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -27,9 +28,27 @@ func FuzzStakingTx(f *testing.F) { stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - slashingAddr, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - stakingTx, _, err := datagen.GenBTCStakingSlashingTx(r, net, stakerSK, validatorPK, covenantPK, stakingTimeBlocks, stakingValue, slashingAddr.String()) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + stakingTx, _, err := datagen.GenBTCStakingSlashingTx( + r, + net, + stakerSK, + validatorPK, + covenantPK, + stakingTimeBlocks, + stakingValue, + slashingAddress.String(), changeAddress.String(), + slashingRate, + ) require.NoError(t, err) err = stakingTx.ValidateBasic() diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index cb55e0b89..a78931fb1 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -4,6 +4,7 @@ import ( "fmt" "cosmossdk.io/math" + "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" @@ -95,12 +96,13 @@ func (p Params) Validate() error { if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { return err } + if err := validateMinCommissionRate(p.MinCommissionRate); err != nil { return err } - if !bbn.IsValidSlashingRate(p.SlashingRate) { - return fmt.Errorf("slashing rate must be in the range (0, 1)") + if !btcstaking.IsSlashingRateValid(p.SlashingRate) { + return btcstaking.ErrInvalidSlashingRate } if err := validateMaxActiveBTCValidators(p.MaxActiveBtcValidators); err != nil { From 421a3be04817f95907943c3fa2d785e7b03cb4b0 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 20 Nov 2023 18:38:19 +1100 Subject: [PATCH 105/202] BTCStaking: generalise KVStore schema and parameters for restaking and multisig covenant (#108) --- cmd/babylond/cmd/flags.go | 4 +- cmd/babylond/cmd/genesis.go | 4 +- proto/babylon/btcstaking/v1/btcstaking.proto | 18 +- proto/babylon/btcstaking/v1/events.proto | 14 +- proto/babylon/btcstaking/v1/params.proto | 19 +- proto/babylon/btcstaking/v1/query.proto | 5 +- test/e2e/btc_staking_e2e_test.go | 18 +- testutil/datagen/btcstaking.go | 55 ++- x/btcstaking/keeper/btc_delegators.go | 59 ++- x/btcstaking/keeper/grpc_query.go | 2 +- x/btcstaking/keeper/grpc_query_test.go | 20 +- x/btcstaking/keeper/incentive_test.go | 6 +- x/btcstaking/keeper/msg_server.go | 39 +- x/btcstaking/keeper/msg_server_test.go | 39 +- .../keeper/voting_power_table_test.go | 17 +- x/btcstaking/types/btc_slashing_tx_test.go | 5 +- x/btcstaking/types/btcstaking.go | 22 +- x/btcstaking/types/btcstaking.pb.go | 405 ++++++++++++++---- x/btcstaking/types/btcstaking_test.go | 5 +- x/btcstaking/types/events.pb.go | 139 +++--- x/btcstaking/types/genesis_test.go | 6 +- x/btcstaking/types/params.go | 16 +- x/btcstaking/types/params.pb.go | 165 ++++--- x/btcstaking/types/query.pb.go | 199 ++++----- 24 files changed, 829 insertions(+), 452 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 8e63aa2cb..e467d58e3 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -28,7 +28,7 @@ const ( flagBlocksPerYear = "blocks-per-year" flagGenesisTime = "genesis-time" flagBlockGasLimit = "block-gas-limit" - flagCovenantPk = "covenant-pk" + flagCovenantPk = "covenant-pk" // TODO: multisig covenant flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" flagSlashingRate = "slashing-rate" @@ -75,7 +75,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") // btcstaking args - cmd.Flags().String(flagCovenantPk, btcstypes.DefaultParams().CovenantPk.MarshalHex(), "Bitcoin staking covenant public key") + cmd.Flags().String(flagCovenantPk, btcstypes.DefaultParams().CovenantPks[0].MarshalHex(), "Bitcoin staking covenant public key") cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index b1a0b08df..929049c26 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -305,10 +305,12 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtclightclientBaseBtcHeader = *baseBtcHeaderInfo genParams.BtcstakingParams = btcstakingtypes.DefaultParams() - genParams.BtcstakingParams.CovenantPk, err = bbn.NewBIP340PubKeyFromHex(covenantPk) + covenantPK, err := bbn.NewBIP340PubKeyFromHex(covenantPk) if err != nil { panic(err) } + genParams.BtcstakingParams.CovenantPks = []bbn.BIP340PubKey{*covenantPK} + genParams.BtcstakingParams.CovenantQuorum = 1 // TODO: multisig covenant genParams.BtcstakingParams.SlashingAddress = slashingAddress genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee genParams.BtcstakingParams.MinCommissionRate = minCommissionRate diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 3bb7e7103..81208a591 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -63,10 +63,11 @@ message BTCDelegation { bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - bytes val_btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + repeated bytes val_btc_pk_list = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock uint64 start_height = 5; @@ -89,6 +90,7 @@ message BTCDelegation { // covenant_sig is the signature signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. + // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples bytes covenant_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // if this object is present it menans that staker requested undelegation, and whole @@ -115,15 +117,18 @@ message BTCUndelegation { // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon + // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples bytes covenant_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator + // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples bytes covenant_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // validator_unbonding_sig is the signature on the unbonding tx // by the validator (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon + // TODO: this is no longer needed bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } @@ -139,11 +144,13 @@ message BTCUndelegationInfo { // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) // It must be provided after processing undelagate message by Babylon // It will be nil if validator didn't sign it yet + // TODO: this is no longer needed bytes validator_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // it will be nil if covenant didn't sign it yet + // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples bytes covenant_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } @@ -190,3 +197,8 @@ enum BTCDelegationStatus { ANY = 4; } +// SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK +message SignatureInfo { + bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + bytes sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto index ef1117d46..b3b5513ba 100644 --- a/proto/babylon/btcstaking/v1/events.proto +++ b/proto/babylon/btcstaking/v1/events.proto @@ -24,10 +24,11 @@ message EventUnbondingBTCDelegation { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 3; @@ -41,10 +42,11 @@ message EventUnbondedBTCDelegation { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 3; diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 9a3e56fb2..eaa266c1a 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -10,28 +10,31 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; message Params { option (gogoproto.goproto_stringer) = false; - // covenant_pk is the public key of covenant - // the PK follows encoding in BIP-340 spec on Bitcoin - bytes covenant_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // covenant_pks is the list of public keys held by the covenant committee + // each PK follows encoding in BIP-340 spec on Bitcoin + repeated bytes covenant_pks = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // covenant_quorum is the minimum number of signatures needed for the covenant + // multisignature + uint32 covenant_quorum = 2; // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin - string slashing_address = 2; + string slashing_address = 3; // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? - int64 min_slashing_tx_fee_sat = 3; + int64 min_slashing_tx_fee_sat = 4; // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators - string min_commission_rate = 4 [ + string min_commission_rate = 5 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). - string slashing_rate = 5 [ + string slashing_rate = 6 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol - uint32 max_active_btc_validators = 6; + uint32 max_active_btc_validators = 7; } diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index ae84a4e71..f24c669b7 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -222,10 +222,9 @@ message QueryBTCDelegationResponse { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock uint64 start_height = 3; diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index e97ade831..eaff8c122 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -100,6 +100,11 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { */ // BTC staking params, BTC delegation key pairs and PoP params := nonValidatorNode.QueryBTCStakingParams() + // get covenant BTC PKs + covenantBTCPKs := []*btcec.PublicKey{} + for _, covenantPK := range params.CovenantPks { + covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) + } // NOTE: we use the node's secret key as Babylon secret key for the BTC delegation delBabylonSK := nonValidatorNode.SecretKey pop, err := bstypes.NewPoP(delBabylonSK, delBTCSK) @@ -110,8 +115,8 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { r, net, delBTCSK, - btcVal.BtcPk.MustToBTCPK(), - params.CovenantPk.MustToBTCPK(), + []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + covenantBTCPKs, stakingTimeBlocks, stakingValue, params.SlashingAddress, changeAddress.String(), @@ -386,6 +391,11 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { // params for covenantPk and slashing address params := nonValidatorNode.QueryBTCStakingParams() + // get covenant BTC PKs + covenantBTCPKs := []*btcec.PublicKey{} + for _, covenantPK := range params.CovenantPks { + covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) + } stakingTx := activeDel.StakingTx stakingMsgTx, err := stakingTx.ToMsgTx() @@ -404,8 +414,8 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { r, net, delBTCSK, - btcVal.BtcPk.MustToBTCPK(), - params.CovenantPk.MustToBTCPK(), + []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + covenantBTCPKs, wire.NewOutPoint(stakingTxChainHash, uint32(stakingOutputIdx)), initialization.BabylonBtcFinalizationPeriod+1, stakingValue-fee, diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 3f05da94f..3a0ab1a8d 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -65,9 +65,9 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe func GenRandomBTCDelegation( r *rand.Rand, - valBTCPK *bbn.BIP340PubKey, + valBTCPKs []bbn.BIP340PubKey, delSK *btcec.PrivateKey, - covenantSK *btcec.PrivateKey, + covenantSKs []*btcec.PrivateKey, slashingAddress, changeAddress string, startHeight, endHeight, totalSat uint64, slashingRate sdk.Dec, @@ -75,10 +75,19 @@ func GenRandomBTCDelegation( net := &chaincfg.SimNetParams delPK := delSK.PubKey() delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) - covenantBTCPK := covenantSK.PubKey() - valPK, err := valBTCPK.ToBTCPK() - if err != nil { - return nil, err + // list of covenant PKs + covenantBTCPKs := []*btcec.PublicKey{} + for _, covenantSK := range covenantSKs { + covenantBTCPKs = append(covenantBTCPKs, covenantSK.PubKey()) + } + // list of validator PKs + valPKs := []*btcec.PublicKey{} + for _, valBTCPK := range valBTCPKs { + valPK, err := valBTCPK.ToBTCPK() + if err != nil { + return nil, err + } + valPKs = append(valPKs, valPK) } // BTC delegation Babylon key pairs @@ -100,8 +109,8 @@ func GenRandomBTCDelegation( r, net, delSK, - valPK, - covenantBTCPK, + valPKs, + covenantBTCPKs, uint16(endHeight-startHeight), int64(totalSat), slashingAddress, changeAddress, @@ -115,7 +124,8 @@ func GenRandomBTCDelegation( if err != nil { return nil, err } - covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSK, net) + // TODO: covenant multisig + covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSKs[0], net) if err != nil { return nil, err } @@ -128,7 +138,7 @@ func GenRandomBTCDelegation( BabylonPk: secp256k1PK, BtcPk: delBTCPK, Pop: pop, - ValBtcPk: valBTCPK, + ValBtcPkList: valBTCPKs, StartHeight: startHeight, EndHeight: endHeight, TotalSat: totalSat, @@ -144,18 +154,19 @@ func GenBTCStakingSlashingTxWithOutPoint( btcNet *chaincfg.Params, outPoint *wire.OutPoint, stakerSK *btcec.PrivateKey, - validatorPK *btcec.PublicKey, - covenantPK *btcec.PublicKey, + validatorPKs []*btcec.PublicKey, + covenantPKs []*btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, slashingRate sdk.Dec, withChange bool, ) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { + // TODO: covenant multisig stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( stakerSK.PubKey(), - validatorPK, - covenantPK, + validatorPKs[0], + covenantPKs[0], stakingTimeBlocks, btcutil.Amount(stakingValue), btcNet, @@ -226,8 +237,8 @@ func GenBTCStakingSlashingTx( r *rand.Rand, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, - validatorPK *btcec.PublicKey, - covenantPK *btcec.PublicKey, + validatorPKs []*btcec.PublicKey, + covenantPKs []*btcec.PublicKey, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, @@ -241,8 +252,8 @@ func GenBTCStakingSlashingTx( btcNet, outPoint, stakerSK, - validatorPK, - covenantPK, + validatorPKs, + covenantPKs, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, @@ -254,8 +265,8 @@ func GenBTCUnbondingSlashingTx( r *rand.Rand, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, - validatorPK *btcec.PublicKey, - covenantPK *btcec.PublicKey, + validatorPKs []*btcec.PublicKey, + covenantPKs []*btcec.PublicKey, stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, @@ -267,8 +278,8 @@ func GenBTCUnbondingSlashingTx( btcNet, stakingTransactionOutpoint, stakerSK, - validatorPK, - covenantPK, + validatorPKs, + covenantPKs, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index bb5eab287..cffc91144 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -13,30 +13,37 @@ import ( // AddBTCDelegation indexes the given BTC delegation in the BTC delegator store, and saves // it under BTC delegation store func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { - var ( - btcDelIndex = types.NewBTCDelegatorDelegationIndex() - err error - ) - if k.hasBTCDelegatorDelegations(ctx, btcDel.ValBtcPk, btcDel.BtcPk) { - btcDelIndex, err = k.getBTCDelegatorDelegationIndex(ctx, btcDel.ValBtcPk, btcDel.BtcPk) - if err != nil { - // this can only be a programming error - panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegatorDelegations returns true")) - } + if err := btcDel.ValidateBasic(); err != nil { + return err } - // index staking tx hash of this BTC delegation + + // get staking tx hash stakingTxHash, err := btcDel.GetStakingTxHash() if err != nil { return err } - if err := btcDelIndex.Add(stakingTxHash); err != nil { - return types.ErrInvalidStakingTx.Wrapf(err.Error()) + + // for each BTC validator the delegation restakes to, update its index + for _, valBTCPK := range btcDel.ValBtcPkList { + var btcDelIndex = types.NewBTCDelegatorDelegationIndex() + if k.hasBTCDelegatorDelegations(ctx, &valBTCPK, btcDel.BtcPk) { + btcDelIndex, err = k.getBTCDelegatorDelegationIndex(ctx, &valBTCPK, btcDel.BtcPk) + if err != nil { + // this can only be a programming error + panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegatorDelegations returns true")) + } + } + + // index staking tx hash of this BTC delegation + if err := btcDelIndex.Add(stakingTxHash); err != nil { + return types.ErrInvalidStakingTx.Wrapf(err.Error()) + } + // save the index + store := k.btcDelegatorStore(ctx, &valBTCPK) + delBTCPKBytes := btcDel.BtcPk.MustMarshal() + btcDelIndexBytes := k.cdc.MustMarshal(btcDelIndex) + store.Set(delBTCPKBytes, btcDelIndexBytes) } - // save the index - store := k.btcDelegatorStore(ctx, btcDel.ValBtcPk) - delBTCPKBytes := btcDel.BtcPk.MustMarshal() - btcDelIndexBytes := k.cdc.MustMarshal(btcDelIndex) - store.Set(delBTCPKBytes, btcDelIndexBytes) // save this BTC delegation k.setBTCDelegation(ctx, btcDel) @@ -54,7 +61,7 @@ func (k Keeper) updateBTCDelegation( modifyFn func(*types.BTCDelegation) error, ) error { // get the BTC delegation - btcDel, err := k.GetBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHashStr) + btcDel, err := k.GetBTCDelegation(ctx, stakingTxHashStr) if err != nil { return err } @@ -204,23 +211,13 @@ func (k Keeper) getBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340 return &types.BTCDelegatorDelegations{Dels: btcDels}, nil } -// GetBTCDelegation gets the BTC delegation with a given BTC PK and staking tx hash under a given BTC validator -// TODO: only take stakingTxHash as input could be enough? -func (k Keeper) GetBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHashStr string) (*types.BTCDelegation, error) { - // find the BTC delegation index - btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) - if err != nil { - return nil, err - } +// GetBTCDelegation gets the BTC delegation with a given staking tx hash +func (k Keeper) GetBTCDelegation(ctx sdk.Context, stakingTxHashStr string) (*types.BTCDelegation, error) { // decode staking tx hash string stakingTxHash, err := chainhash.NewHashFromStr(stakingTxHashStr) if err != nil { return nil, err } - // ensure the BTC delegation index has this staking tx hash - if !btcDelIndex.Has(*stakingTxHash) { - return nil, types.ErrBTCDelegatorNotFound.Wrapf(err.Error()) - } return k.getBTCDelegation(ctx, *stakingTxHash), nil } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 262cda22a..c81f00465 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -290,7 +290,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation return &types.QueryBTCDelegationResponse{ BtcPk: btcDel.BtcPk, - ValBtcPk: btcDel.ValBtcPk, + ValBtcPkList: btcDel.ValBtcPkList, StartHeight: btcDel.StartHeight, EndHeight: btcDel.EndHeight, TotalSat: btcDel.TotalSat, diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 76d42082e..d11f2598c 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -7,9 +7,11 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" @@ -206,9 +208,9 @@ func FuzzPendingBTCDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - btcVal.BtcPk, + []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, @@ -310,9 +312,9 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - btcVal.BtcPk, + []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, @@ -491,9 +493,9 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - valBTCPK, + []bbn.BIP340PubKey{*valBTCPK}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, 10000, slashingRate, @@ -595,9 +597,9 @@ func FuzzBTCValidatorDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - btcVal.BtcPk, + []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, @@ -638,7 +640,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { for _, btcDels := range resp.BtcDelegatorDelegations { require.Len(t, btcDels.Dels, 1) btcDel := btcDels.Dels[0] - require.Equal(t, btcVal.BtcPk, btcDel.ValBtcPk) + require.Equal(t, btcVal.BtcPk, &btcDel.ValBtcPkList[0]) // Check if the pk exists in the map _, ok := expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] require.True(t, ok) diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 37f76eff9..de2db38bb 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -6,9 +6,11 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -66,9 +68,9 @@ func FuzzRecordRewardDistCache(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - btcVal.BtcPk, + []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, slashingRate, diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index ed91a75f2..000356831 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -119,6 +119,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout // extract staking script from staking tx + // TODO: use new algorithm that reconstructs staking tx output then asserts consistency stakingOutputInfo, err := req.StakingTx.GetBabylonOutputInfo(ms.btcNet) if err != nil { return nil, err @@ -155,7 +156,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } // ensure staking tx is using correct covenant PK - paramCovenantPK := params.CovenantPk + paramCovenantPK := ¶ms.CovenantPks[0] // TODO: verifying covenant committee PKs if !covenantPK.Equals(paramCovenantPK) { return nil, types.ErrInvalidCovenantPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramCovenantPK), hex.EncodeToString(*covenantPK)) } @@ -225,10 +226,11 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // have voting power only when 1) its corresponding staking tx is k-deep, // and 2) it receives a covenant signature newBTCDel := &types.BTCDelegation{ - BabylonPk: req.BabylonPk, - BtcPk: delBTCPK, - Pop: req.Pop, - ValBtcPk: valBTCPK, + BabylonPk: req.BabylonPk, + BtcPk: delBTCPK, + Pop: req.Pop, + // TODO: ValBtcPkList will be provided in the message + ValBtcPkList: []bbn.BIP340PubKey{*valBTCPK}, StartHeight: startHeight, EndHeight: endHeight, TotalSat: uint64(stakingOutputInfo.StakingAmount), @@ -311,10 +313,11 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // 5. Check Covenant Key from script is consistent with params publicKeyInfos := types.KeyDataFromScript(unbondingOutputInfo.StakingScriptData) - if !publicKeyInfos.CovenantKey.Equals(params.CovenantPk) { + paramCovenantPK := ¶ms.CovenantPks[0] // TODO: verifying covenant committee PKs + if !publicKeyInfos.CovenantKey.Equals(paramCovenantPK) { return nil, types.ErrInvalidCovenantPK.Wrapf( "expected: %s; actual: %s", - hex.EncodeToString(*params.CovenantPk), + hex.EncodeToString(*paramCovenantPK), hex.EncodeToString(*publicKeyInfos.CovenantKey), ) } @@ -322,7 +325,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // 6. Check delegation exists for the given validator and delegator and given staking tx hash // as all keys are taken from script, it effectively check that values in delegation staking script // matches the values in the unbonding tx staking script - del, err := ms.GetBTCDelegation(ctx, publicKeyInfos.ValidatorKey, publicKeyInfos.StakerKey, stakingTxHash) + del, err := ms.GetBTCDelegation(ctx, stakingTxHash) if err != nil { return nil, err @@ -381,7 +384,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // notify subscriber event := &types.EventUnbondingBTCDelegation{ BtcPk: del.BtcPk, - ValBtcPk: del.ValBtcPk, + ValBtcPkList: del.ValBtcPkList, StakingTxHash: stakingTxHash, UnbondingTxHash: unbondingMsgTx.TxHash().String(), } @@ -397,7 +400,7 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven ctx := sdk.UnwrapSDKContext(goCtx) // ensure BTC delegation exists - btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) + btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) if err != nil { return nil, err } @@ -411,7 +414,9 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) } - covenantPK, err := ms.GetParams(ctx).CovenantPk.ToBTCPK() + // TODO: covenant PK will be a field in the message. Then the verification will be + // that the given covenant PK has to be one of the covenant PKs in the parameter + covenantPK, err := ms.GetParams(ctx).CovenantPks[0].ToBTCPK() if err != nil { // failing to cast a verified covenant PK a programming error panic(fmt.Errorf("failed to cast a verified covenant public key")) @@ -449,7 +454,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout // 1. Check that delegation even exists for provided params - btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) + btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) if err != nil { return nil, err } @@ -485,7 +490,9 @@ func (ms msgServer) AddCovenantUnbondingSigs( panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) } - covenantPK, err := ms.GetParams(ctx).CovenantPk.ToBTCPK() + // TODO: covenant PK will be a field in the message. Then the verification will be + // that the given covenant PK has to be one of the covenant PKs in the parameter + covenantPK, err := ms.GetParams(ctx).CovenantPks[0].ToBTCPK() if err != nil { // failing to cast a verified covenant PK is a programming error panic(fmt.Errorf("failed to cast a verified covenant public key")) @@ -538,7 +545,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( if btcDel.BtcUndelegation.HasValidatorSig() { event := &types.EventUnbondedBTCDelegation{ BtcPk: btcDel.BtcPk, - ValBtcPk: btcDel.ValBtcPk, + ValBtcPkList: btcDel.ValBtcPkList, StakingTxHash: req.StakingTxHash, UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), FromState: types.BTCDelegationStatus_UNBONDING, @@ -558,7 +565,7 @@ func (ms msgServer) AddValidatorUnbondingSig( wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout // 1. Check that delegation even exists for provided params - btcDel, err := ms.GetBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash) + btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) if err != nil { return nil, err } @@ -612,7 +619,7 @@ func (ms msgServer) AddValidatorUnbondingSig( if btcDel.BtcUndelegation.HasCovenantSigs() { event := &types.EventUnbondedBTCDelegation{ BtcPk: btcDel.BtcPk, - ValBtcPk: btcDel.ValBtcPk, + ValBtcPkList: btcDel.ValBtcPkList, StakingTxHash: req.StakingTxHash, UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), FromState: types.BTCDelegationStatus_UNBONDING, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 11224333b..bb79bd7d6 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -96,7 +96,8 @@ func getCovenantInfo(t *testing.T, slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(sdkCtx, types.Params{ - CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, + CovenantQuorum: 1, SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), @@ -153,8 +154,8 @@ func createDelegation( r, net, delSK, - validatorPK, - covenantPK, + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, @@ -233,8 +234,9 @@ func createCovenantSig( ) require.NoError(t, err) msgAddCovenantSig := &types.MsgAddCovenantSig{ - Signer: msgCreateBTCDel.Signer, - ValPk: delegation.ValBtcPk, + Signer: msgCreateBTCDel.Signer, + // TODO: this will be removed after all + ValPk: &delegation.ValBtcPkList[0], DelPk: delegation.BtcPk, StakingTxHash: stakingTxHash, Sig: covenantSig, @@ -245,7 +247,7 @@ func createCovenantSig( /* ensure covenant sig is added successfully */ - actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, delegation.ValBtcPk, delegation.BtcPk, stakingTxHash) + actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) require.NoError(t, err) require.Equal(t, actualDelWithCovenantSig.CovenantSig.MustMarshal(), covenantSig.MustMarshal()) require.True(t, actualDelWithCovenantSig.HasCovenantSig()) @@ -262,7 +264,7 @@ func getDelegationAndCheckValues( delegatorPK *btcec.PublicKey, stakingTxHash string, ) *types.BTCDelegation { - actualDel, err := bsKeeper.GetBTCDelegation(sdkCtx, bbn.NewBIP340PubKeyFromBTCPK(validatorPK), bbn.NewBIP340PubKeyFromBTCPK(delegatorPK), stakingTxHash) + actualDel, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) require.NoError(t, err) require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) @@ -300,8 +302,8 @@ func createUndelegation( r, net, delSK, - validatorPK, - covenantPK, + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, wire.NewOutPoint(stkTxHash, stkOutputIdx), uint16(defaultParams.CheckpointFinalizationTimeout)+1, int64(actualDel.TotalSat)-1000, @@ -414,7 +416,8 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) err = bsKeeper.SetParams(ctx, types.Params{ - CovenantPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, + CovenantQuorum: 1, SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdk.MustNewDecFromStr("0.01"), @@ -439,8 +442,8 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { r, net, delSK, - validatorPK, - covenantPK, + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), @@ -542,7 +545,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { bsKeeper.GetParams(ctx).SlashingRate, ) - actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) @@ -608,7 +611,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { bsKeeper.GetParams(ctx).SlashingRate, ) - del, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + del, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, del.BtcUndelegation) @@ -624,7 +627,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { require.NoError(t, err) msg := types.MsgAddValidatorUnbondingSig{ Signer: datagen.GenRandomAccount().Address, - ValPk: del.ValBtcPk, + ValPk: &del.ValBtcPkList[0], DelPk: del.BtcPk, StakingTxHash: stakingTxHash, UnbondingTxSig: ubondingTxSignatureValidator, @@ -633,7 +636,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { _, err = ms.AddValidatorUnbondingSig(goCtx, &msg) require.NoError(t, err) - delWithValSig, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + delWithValSig, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithValSig.BtcUndelegation) require.NotNil(t, delWithValSig.BtcUndelegation.ValidatorUnbondingSig) @@ -662,7 +665,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ Signer: datagen.GenRandomAccount().Address, - ValPk: del.ValBtcPk, + ValPk: &del.ValBtcPkList[0], DelPk: del.BtcPk, StakingTxHash: stakingTxHash, UnbondingTxSig: unbondingTxSignatureCovenant, @@ -673,7 +676,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { _, err = ms.AddCovenantUnbondingSigs(goCtx, &covenantSigsMsg) require.NoError(t, err) - delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, actualDel.ValBtcPk, actualDel.BtcPk, stakingTxHash) + delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.ValidatorUnbondingSig) diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index c1bd544fe..9fe9734c2 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -10,6 +10,7 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -64,9 +65,9 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - btcVals[i].BtcPk, + []bbn.BIP340PubKey{*btcVals[i].BtcPk}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, slashingRate, @@ -230,9 +231,9 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - valBTCPK, + []bbn.BIP340PubKey{*valBTCPK}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -332,9 +333,9 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - valBTCPK, + []bbn.BIP340PubKey{*valBTCPK}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -378,9 +379,9 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, - activatedValBTCPK, + []bbn.BIP340PubKey{*activatedValBTCPK}, delSK, - covenantSK, + []*btcec.PrivateKey{covenantSK}, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 51ad987e0..e7edea0ad 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -6,6 +6,7 @@ import ( btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" sdk "github.com/cosmos/cosmos-sdk/types" @@ -46,8 +47,8 @@ func FuzzSlashingTxWithWitness(f *testing.F) { r, net, delSK, - valPK, - covenantPK, + []*btcec.PublicKey{valPK}, + []*btcec.PublicKey{covenantPK}, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index b28cffbb1..8c7bb0357 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -67,8 +67,11 @@ func (d *BTCDelegation) ValidateBasic() error { if d.Pop == nil { return fmt.Errorf("empty proof of possession") } - if d.ValBtcPk == nil { - return fmt.Errorf("empty Validator BTC public key") + if len(d.ValBtcPkList) == 0 { + return fmt.Errorf("empty list of BTC validator PKs") + } + if existsDup(d.ValBtcPkList) { + return fmt.Errorf("list of BTC validator PKs has duplication") } if d.StakingTx == nil { return fmt.Errorf("empty staking tx") @@ -351,3 +354,18 @@ func FilterTopNBTCValidators(validators []*BTCValidatorWithMeta, n uint32) []*BT // Return the top n elements return validators[:n] } + +func existsDup(btcPKs []bbn.BIP340PubKey) bool { + seen := make(map[string]struct{}) + + for _, btcPK := range btcPKs { + pkStr := string(btcPK) + if _, found := seen[pkStr]; found { + return true + } else { + seen[pkStr] = struct{}{} + } + } + + return false +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index c460f28b7..1df7af0b1 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -258,10 +258,11 @@ type BTCDelegation struct { BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock StartHeight uint64 `protobuf:"varint,5,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` @@ -284,6 +285,7 @@ type BTCDelegation struct { // covenant_sig is the signature signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. + // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. @@ -391,15 +393,18 @@ type BTCUndelegation struct { // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon + // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator + // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` // validator_unbonding_sig is the signature on the unbonding tx // by the validator (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon + // TODO: this is no longer needed ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` } @@ -453,10 +458,12 @@ type BTCUndelegationInfo struct { // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) // It must be provided after processing undelagate message by Babylon // It will be nil if validator didn't sign it yet + // TODO: this is no longer needed ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // it will be nil if covenant didn't sign it yet + // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` } @@ -646,6 +653,45 @@ func (m *BabylonBTCTaprootTx) GetScript() []byte { return nil } +// SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK +type SignatureInfo struct { + Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` + Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` +} + +func (m *SignatureInfo) Reset() { *m = SignatureInfo{} } +func (m *SignatureInfo) String() string { return proto.CompactTextString(m) } +func (*SignatureInfo) ProtoMessage() {} +func (*SignatureInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{8} +} +func (m *SignatureInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignatureInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignatureInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SignatureInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureInfo.Merge(m, src) +} +func (m *SignatureInfo) XXX_Size() int { + return m.Size() +} +func (m *SignatureInfo) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_SignatureInfo proto.InternalMessageInfo + func init() { proto.RegisterEnum("babylon.btcstaking.v1.BTCDelegationStatus", BTCDelegationStatus_name, BTCDelegationStatus_value) proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") @@ -656,6 +702,7 @@ func init() { proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*BTCDelegatorDelegationIndex)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegationIndex") proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") + proto.RegisterType((*SignatureInfo)(nil), "babylon.btcstaking.v1.SignatureInfo") } func init() { @@ -663,72 +710,74 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1033 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdb, 0x6e, 0xdb, 0x46, - 0x13, 0x36, 0x25, 0x59, 0xb6, 0x46, 0xf4, 0x1f, 0x65, 0x7d, 0x88, 0x7e, 0x07, 0x95, 0x1c, 0x35, - 0x30, 0x84, 0xa0, 0x91, 0x6a, 0xe7, 0x80, 0xb4, 0x40, 0x0b, 0x44, 0x96, 0xd1, 0x08, 0x8d, 0x1d, - 0x95, 0xa2, 0xd3, 0x03, 0x8a, 0x0a, 0x4b, 0x72, 0x2d, 0x11, 0x92, 0xb9, 0x2c, 0x77, 0xa5, 0xca, - 0xf7, 0x7d, 0x80, 0xbe, 0x41, 0x5f, 0xa2, 0x0f, 0x91, 0xcb, 0xa0, 0x57, 0x81, 0x2f, 0x8c, 0xc2, - 0x7e, 0x91, 0x82, 0xcb, 0xe5, 0xc1, 0x8e, 0xed, 0xd6, 0x95, 0x73, 0x25, 0xcd, 0xce, 0xcc, 0x37, - 0x33, 0xdf, 0x37, 0xe0, 0x2e, 0xac, 0x1b, 0xd8, 0x38, 0x1c, 0x52, 0xa7, 0x6e, 0x70, 0x93, 0x71, - 0x3c, 0xb0, 0x9d, 0x5e, 0x7d, 0xbc, 0x91, 0xb0, 0x6a, 0xae, 0x47, 0x39, 0x45, 0xcb, 0x32, 0xae, - 0x96, 0xf0, 0x8c, 0x37, 0x56, 0x97, 0x7a, 0xb4, 0x47, 0x45, 0x44, 0xdd, 0xff, 0x17, 0x04, 0xaf, - 0xfe, 0xdf, 0xa4, 0xec, 0x80, 0xb2, 0x6e, 0xe0, 0x08, 0x0c, 0xe9, 0xaa, 0x04, 0x56, 0xdd, 0xf4, - 0x0e, 0x5d, 0x4e, 0xeb, 0x8c, 0x98, 0xee, 0xe6, 0x93, 0xa7, 0x83, 0x8d, 0xfa, 0x80, 0x1c, 0x86, - 0x31, 0xf7, 0x65, 0x4c, 0xdc, 0x8f, 0x41, 0x38, 0xde, 0xa8, 0x9f, 0xe9, 0x68, 0xb5, 0x7c, 0x71, - 0xe7, 0x2e, 0x75, 0x83, 0x80, 0xca, 0x71, 0x1a, 0xd4, 0x86, 0xbe, 0xf5, 0x1a, 0x0f, 0x6d, 0x0b, - 0x73, 0xea, 0xa1, 0x6d, 0xc8, 0x5b, 0x84, 0x99, 0x9e, 0xed, 0x72, 0x9b, 0x3a, 0x45, 0x65, 0x4d, - 0xa9, 0xe6, 0x37, 0x3f, 0xae, 0xc9, 0xfe, 0xe2, 0xa9, 0x44, 0xb5, 0x5a, 0x33, 0x0e, 0xd5, 0x92, - 0x79, 0xe8, 0x3b, 0x00, 0x93, 0x1e, 0x1c, 0xd8, 0x8c, 0xf9, 0x28, 0xa9, 0x35, 0xa5, 0x9a, 0x6b, - 0x3c, 0x3b, 0x3a, 0x2e, 0xaf, 0xf7, 0x6c, 0xde, 0x1f, 0x19, 0x35, 0x93, 0x1e, 0xd4, 0xc3, 0x29, - 0xc5, 0xcf, 0x43, 0x66, 0x0d, 0xea, 0xfc, 0xd0, 0x25, 0xac, 0xd6, 0x24, 0xe6, 0x9f, 0x7f, 0x3c, - 0x04, 0x59, 0xb2, 0x49, 0x4c, 0x2d, 0x81, 0x85, 0xbe, 0x04, 0x90, 0x43, 0x75, 0xdd, 0x41, 0x31, - 0x2d, 0xfa, 0x2b, 0x87, 0xfd, 0x05, 0x8c, 0xd5, 0x22, 0xc6, 0x6a, 0xed, 0x91, 0xf1, 0x35, 0x39, - 0xd4, 0x72, 0x32, 0xa5, 0x3d, 0x40, 0x3b, 0x90, 0x35, 0xb8, 0xe9, 0xe7, 0x66, 0xd6, 0x94, 0xaa, - 0xda, 0x78, 0x7a, 0x74, 0x5c, 0xde, 0x4c, 0x74, 0x25, 0x23, 0xcd, 0x3e, 0xb6, 0x9d, 0xd0, 0x90, - 0x8d, 0x35, 0x5a, 0xed, 0x47, 0x8f, 0x3f, 0x95, 0x90, 0xb3, 0x06, 0x37, 0xdb, 0x03, 0xf4, 0x39, - 0xa4, 0x5d, 0xea, 0x16, 0x67, 0x45, 0x1f, 0xd5, 0xda, 0x85, 0x1b, 0x50, 0x6b, 0x7b, 0x94, 0xee, - 0xbf, 0xda, 0x6f, 0x53, 0xc6, 0x88, 0x98, 0x42, 0xf3, 0x93, 0xd0, 0x63, 0x58, 0x61, 0x43, 0xcc, - 0xfa, 0xc4, 0xea, 0x86, 0x23, 0xf5, 0x89, 0xdd, 0xeb, 0xf3, 0x62, 0x76, 0x4d, 0xa9, 0x66, 0xb4, - 0x25, 0xe9, 0x6d, 0x04, 0xce, 0x17, 0xc2, 0x87, 0x3e, 0x01, 0x14, 0x65, 0x71, 0x33, 0xcc, 0x98, - 0x13, 0x19, 0x85, 0x30, 0x83, 0x9b, 0x41, 0x74, 0xe5, 0xd7, 0x14, 0x2c, 0x25, 0x05, 0xfe, 0xd6, - 0xe6, 0xfd, 0x1d, 0xc2, 0x71, 0x82, 0x07, 0xe5, 0x26, 0x78, 0x58, 0x81, 0xac, 0xec, 0x24, 0x25, - 0x3a, 0x91, 0x16, 0xba, 0x07, 0xea, 0x98, 0x72, 0xdb, 0xe9, 0x75, 0x5d, 0xfa, 0x0b, 0xf1, 0x84, - 0x60, 0x19, 0x2d, 0x1f, 0x9c, 0xb5, 0xfd, 0xa3, 0x2b, 0x68, 0xc8, 0x5c, 0x9b, 0x86, 0xd9, 0x4b, - 0x68, 0xf8, 0x3d, 0x0b, 0x0b, 0x0d, 0x7d, 0xab, 0x49, 0x86, 0xa4, 0x87, 0xf9, 0xfb, 0x7b, 0xa4, - 0x4c, 0xb1, 0x47, 0xa9, 0x1b, 0xdc, 0xa3, 0xf4, 0x7f, 0xd9, 0x23, 0x1d, 0x60, 0x8c, 0x87, 0xdd, - 0x1b, 0x59, 0xeb, 0xf9, 0x31, 0x1e, 0x36, 0x44, 0x47, 0xf7, 0x40, 0x65, 0x1c, 0x7b, 0xfc, 0x2c, - 0xb5, 0x79, 0x71, 0x26, 0x35, 0xf8, 0x08, 0x80, 0x38, 0xd6, 0xd9, 0xa5, 0xcd, 0x11, 0xc7, 0x92, - 0xee, 0xbb, 0x90, 0xe3, 0x94, 0xe3, 0x61, 0x97, 0xe1, 0x70, 0x41, 0xe7, 0xc5, 0x41, 0x07, 0x73, - 0xd4, 0x02, 0x90, 0x93, 0x75, 0xf9, 0xa4, 0x38, 0x2f, 0xe6, 0x7e, 0x70, 0xc9, 0xdc, 0x52, 0xf9, - 0x86, 0xbe, 0xa5, 0x63, 0xd7, 0xa3, 0x94, 0xeb, 0x13, 0x2d, 0x27, 0xfd, 0xfa, 0x04, 0x6d, 0x42, - 0x5e, 0x08, 0x2e, 0xb1, 0x72, 0x82, 0x80, 0xdb, 0x47, 0xc7, 0x65, 0x5f, 0xf2, 0x8e, 0xf4, 0xe8, - 0x13, 0x0d, 0x58, 0xf4, 0x1f, 0xfd, 0x04, 0x0b, 0x56, 0xb0, 0x0c, 0xd4, 0xeb, 0x32, 0xbb, 0x57, - 0x04, 0x91, 0xf5, 0xd9, 0xd1, 0x71, 0xf9, 0xc9, 0x75, 0x68, 0xeb, 0xd8, 0x3d, 0x07, 0xf3, 0x91, - 0x47, 0x34, 0x35, 0xc2, 0xeb, 0xd8, 0x3d, 0xf4, 0x23, 0xa8, 0x26, 0x1d, 0x13, 0x07, 0x3b, 0x5c, - 0xc0, 0xe7, 0xa7, 0x85, 0xcf, 0x87, 0x70, 0x3e, 0xfa, 0x37, 0x50, 0xf0, 0xd5, 0x1e, 0x39, 0x56, - 0xb4, 0xd0, 0x45, 0x55, 0x50, 0xb8, 0x7e, 0x19, 0x85, 0xfa, 0xd6, 0x5e, 0x22, 0x5a, 0xbb, 0x65, - 0x70, 0x33, 0x79, 0x50, 0x79, 0x97, 0x81, 0x5b, 0xe7, 0x82, 0xd0, 0x0e, 0xa8, 0x23, 0xc7, 0xa0, - 0x8e, 0x25, 0x99, 0x55, 0xae, 0xad, 0x52, 0x3e, 0xca, 0x7f, 0x5f, 0xa7, 0xd4, 0xbf, 0xd1, 0x89, - 0xc2, 0x4a, 0x42, 0xa7, 0x30, 0xdb, 0x67, 0x34, 0x3d, 0x2d, 0xa3, 0x4b, 0xb1, 0x60, 0x12, 0xd7, - 0xa7, 0xf6, 0x00, 0x96, 0x63, 0xe1, 0x92, 0xf5, 0x32, 0xd3, 0xd6, 0x5b, 0x8c, 0x14, 0x4c, 0x94, - 0xa3, 0xb0, 0x12, 0x95, 0x8b, 0xb9, 0xf6, 0xeb, 0xcd, 0x4e, 0x3d, 0x5f, 0x08, 0xbc, 0x17, 0xe2, - 0xfa, 0x05, 0x7f, 0x86, 0x3b, 0xe3, 0xf0, 0x32, 0x38, 0x57, 0x31, 0x3b, 0x6d, 0xc5, 0xe5, 0x08, - 0x39, 0x59, 0xb2, 0xf2, 0x26, 0x05, 0x8b, 0xe7, 0x56, 0xab, 0xe5, 0xec, 0xd3, 0x9b, 0x5e, 0xaf, - 0x2b, 0x26, 0x4b, 0x7d, 0x98, 0xc9, 0xae, 0x50, 0x2f, 0xfd, 0x41, 0xd4, 0xab, 0x74, 0xe0, 0x4e, - 0x7c, 0x8d, 0x51, 0x2f, 0xbe, 0xcf, 0x18, 0x7a, 0x06, 0x19, 0x8b, 0x0c, 0x59, 0x51, 0x59, 0x4b, - 0x57, 0xf3, 0x9b, 0xf7, 0x2f, 0xff, 0x0e, 0xc4, 0x49, 0x9a, 0xc8, 0xa8, 0xec, 0xc2, 0xdd, 0x8b, - 0x41, 0x5b, 0x8e, 0x45, 0x26, 0xa8, 0x0e, 0x4b, 0xf1, 0x97, 0xba, 0xdb, 0xc7, 0xac, 0xdf, 0x1d, - 0xda, 0x8c, 0x8b, 0x42, 0xaa, 0x76, 0x3b, 0xfa, 0x0e, 0xbf, 0xc0, 0xac, 0xff, 0xd2, 0x66, 0xbc, - 0xf2, 0x05, 0x2c, 0x5e, 0x20, 0x16, 0xfa, 0x1f, 0xa4, 0xa4, 0xc8, 0xaa, 0x96, 0xe2, 0x13, 0xff, - 0xc9, 0x10, 0x3c, 0x18, 0x03, 0x79, 0x34, 0x69, 0x3d, 0xd0, 0xc5, 0xb6, 0xc4, 0x5d, 0x74, 0x38, - 0xe6, 0x23, 0x86, 0xf2, 0x30, 0xd7, 0xde, 0xde, 0x6d, 0xb6, 0x76, 0xbf, 0x2a, 0xcc, 0x20, 0x80, - 0xec, 0xf3, 0x2d, 0xbd, 0xf5, 0x7a, 0xbb, 0xa0, 0xa0, 0x05, 0xc8, 0xed, 0xed, 0x36, 0x5e, 0x05, - 0xae, 0x14, 0x52, 0x61, 0x3e, 0x30, 0xb7, 0x9b, 0x85, 0x34, 0x9a, 0x83, 0xf4, 0xf3, 0xdd, 0xef, - 0x0b, 0x99, 0xc6, 0xcb, 0x37, 0x27, 0x25, 0xe5, 0xed, 0x49, 0x49, 0xf9, 0xeb, 0xa4, 0xa4, 0xfc, - 0x76, 0x5a, 0x9a, 0x79, 0x7b, 0x5a, 0x9a, 0x79, 0x77, 0x5a, 0x9a, 0xf9, 0xe1, 0x1f, 0xaf, 0xc9, - 0x49, 0xf2, 0xf9, 0x2c, 0xd4, 0x32, 0xb2, 0xe2, 0xf9, 0xfc, 0xe8, 0xef, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x3a, 0x84, 0xbc, 0x0b, 0x1b, 0x0c, 0x00, 0x00, + // 1063 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdb, 0x6e, 0x1b, 0x45, + 0x18, 0xce, 0xee, 0x3a, 0x6e, 0xfc, 0x7b, 0x43, 0xdc, 0xc9, 0xa1, 0x26, 0x15, 0x76, 0x30, 0x55, + 0x64, 0x55, 0xd4, 0x26, 0xe9, 0x41, 0x05, 0x09, 0xa4, 0x3a, 0x0e, 0xd4, 0x6a, 0x93, 0x9a, 0xb5, + 0x53, 0x0e, 0x02, 0xac, 0x3d, 0x4c, 0xec, 0x95, 0x9d, 0x9d, 0x65, 0x67, 0x6c, 0x9c, 0x7b, 0x1e, + 0x80, 0x87, 0xe0, 0x82, 0x07, 0xe0, 0x21, 0x7a, 0x59, 0x71, 0x55, 0xe5, 0xc2, 0x42, 0xc9, 0x8b, + 0xa0, 0x9d, 0x9d, 0x3d, 0x24, 0x4d, 0x02, 0xc1, 0xe9, 0x95, 0xfd, 0xcf, 0x7f, 0xfe, 0xbe, 0x4f, + 0x3b, 0x03, 0xeb, 0x86, 0x6e, 0x1c, 0x0e, 0x88, 0x53, 0x35, 0x98, 0x49, 0x99, 0xde, 0xb7, 0x9d, + 0x6e, 0x75, 0xb4, 0x91, 0xb0, 0x2a, 0xae, 0x47, 0x18, 0x41, 0xcb, 0x22, 0xae, 0x92, 0xf0, 0x8c, + 0x36, 0x56, 0x97, 0xba, 0xa4, 0x4b, 0x78, 0x44, 0xd5, 0xff, 0x17, 0x04, 0xaf, 0xbe, 0x6f, 0x12, + 0x7a, 0x40, 0x68, 0x27, 0x70, 0x04, 0x86, 0x70, 0x95, 0x02, 0xab, 0x6a, 0x7a, 0x87, 0x2e, 0x23, + 0x55, 0x8a, 0x4d, 0x77, 0xf3, 0xe1, 0xa3, 0xfe, 0x46, 0xb5, 0x8f, 0x0f, 0xc3, 0x98, 0x3b, 0x22, + 0x26, 0x9e, 0xc7, 0xc0, 0x4c, 0xdf, 0xa8, 0x9e, 0x9a, 0x68, 0xb5, 0x78, 0xfe, 0xe4, 0x2e, 0x71, + 0x83, 0x80, 0xd2, 0x44, 0x01, 0xb5, 0xd6, 0xde, 0x7a, 0xa9, 0x0f, 0x6c, 0x4b, 0x67, 0xc4, 0x43, + 0xdb, 0x90, 0xb5, 0x30, 0x35, 0x3d, 0xdb, 0x65, 0x36, 0x71, 0xf2, 0xd2, 0x9a, 0x54, 0xce, 0x6e, + 0x7e, 0x54, 0x11, 0xf3, 0xc5, 0x5b, 0xf1, 0x6e, 0x95, 0x7a, 0x1c, 0xaa, 0x25, 0xf3, 0xd0, 0xb7, + 0x00, 0x26, 0x39, 0x38, 0xb0, 0x29, 0xf5, 0xab, 0xc8, 0x6b, 0x52, 0x39, 0x53, 0x7b, 0x7c, 0x34, + 0x29, 0xae, 0x77, 0x6d, 0xd6, 0x1b, 0x1a, 0x15, 0x93, 0x1c, 0x54, 0xc3, 0x2d, 0xf9, 0xcf, 0x3d, + 0x6a, 0xf5, 0xab, 0xec, 0xd0, 0xc5, 0xb4, 0x52, 0xc7, 0xe6, 0x5f, 0x7f, 0xde, 0x03, 0xd1, 0xb2, + 0x8e, 0x4d, 0x2d, 0x51, 0x0b, 0x7d, 0x01, 0x20, 0x96, 0xea, 0xb8, 0xfd, 0xbc, 0xc2, 0xe7, 0x2b, + 0x86, 0xf3, 0x05, 0x88, 0x55, 0x22, 0xc4, 0x2a, 0xcd, 0xa1, 0xf1, 0x0c, 0x1f, 0x6a, 0x19, 0x91, + 0xd2, 0xec, 0xa3, 0x1d, 0x48, 0x1b, 0xcc, 0xf4, 0x73, 0x53, 0x6b, 0x52, 0x59, 0xad, 0x3d, 0x3a, + 0x9a, 0x14, 0x37, 0x13, 0x53, 0x89, 0x48, 0xb3, 0xa7, 0xdb, 0x4e, 0x68, 0x88, 0xc1, 0x6a, 0x8d, + 0xe6, 0xfd, 0x07, 0x9f, 0x88, 0x92, 0xb3, 0x06, 0x33, 0x9b, 0x7d, 0xf4, 0x19, 0x28, 0x2e, 0x71, + 0xf3, 0xb3, 0x7c, 0x8e, 0x72, 0xe5, 0x5c, 0x05, 0x54, 0x9a, 0x1e, 0x21, 0xfb, 0x2f, 0xf6, 0x9b, + 0x84, 0x52, 0xcc, 0xb7, 0xd0, 0xfc, 0x24, 0xf4, 0x00, 0x56, 0xe8, 0x40, 0xa7, 0x3d, 0x6c, 0x75, + 0xc2, 0x95, 0x7a, 0xd8, 0xee, 0xf6, 0x58, 0x3e, 0xbd, 0x26, 0x95, 0x53, 0xda, 0x92, 0xf0, 0xd6, + 0x02, 0xe7, 0x53, 0xee, 0x43, 0x1f, 0x03, 0x8a, 0xb2, 0x98, 0x19, 0x66, 0xdc, 0xe0, 0x19, 0xb9, + 0x30, 0x83, 0x99, 0x41, 0x74, 0xe9, 0x57, 0x19, 0x96, 0x92, 0x04, 0x7f, 0x63, 0xb3, 0xde, 0x0e, + 0x66, 0x7a, 0x02, 0x07, 0xe9, 0x3a, 0x70, 0x58, 0x81, 0xb4, 0x98, 0x44, 0xe6, 0x93, 0x08, 0x0b, + 0x7d, 0x08, 0xea, 0x88, 0x30, 0xdb, 0xe9, 0x76, 0x5c, 0xf2, 0x0b, 0xf6, 0x38, 0x61, 0x29, 0x2d, + 0x1b, 0x9c, 0x35, 0xfd, 0xa3, 0x4b, 0x60, 0x48, 0x5d, 0x19, 0x86, 0xd9, 0x0b, 0x60, 0xf8, 0x23, + 0x0d, 0xf3, 0xb5, 0xf6, 0x56, 0x1d, 0x0f, 0x70, 0x57, 0x67, 0x6f, 0xeb, 0x48, 0x9a, 0x42, 0x47, + 0xf2, 0x35, 0xea, 0x48, 0xf9, 0x3f, 0x3a, 0xfa, 0x11, 0x16, 0x46, 0xfa, 0xa0, 0x13, 0x8c, 0xd3, + 0x19, 0xd8, 0xd4, 0x47, 0x4e, 0x99, 0x62, 0x26, 0x75, 0xa4, 0x0f, 0x6a, 0xfe, 0x58, 0xcf, 0x6d, + 0xca, 0x29, 0xa4, 0x4c, 0xf7, 0xd8, 0x69, 0x8c, 0xb3, 0xfc, 0x4c, 0x90, 0xf1, 0x01, 0x00, 0x76, + 0xac, 0xd3, 0xea, 0xcd, 0x60, 0xc7, 0x12, 0xee, 0xdb, 0x90, 0x61, 0x84, 0xe9, 0x83, 0x0e, 0xd5, + 0x43, 0xa5, 0xce, 0xf1, 0x83, 0x96, 0xce, 0x50, 0x03, 0x40, 0xac, 0xd8, 0x61, 0xe3, 0xfc, 0x1c, + 0x07, 0xe0, 0xee, 0x05, 0x00, 0x08, 0x09, 0xd4, 0xda, 0x5b, 0x6d, 0xdd, 0xf5, 0x08, 0x61, 0xed, + 0xb1, 0x96, 0x11, 0xfe, 0xf6, 0x18, 0x6d, 0x42, 0x96, 0x33, 0x2f, 0x6a, 0x65, 0x38, 0x31, 0x37, + 0x8f, 0x26, 0x45, 0x9f, 0xfb, 0x96, 0xf0, 0xb4, 0xc7, 0x1a, 0xd0, 0xe8, 0x3f, 0xfa, 0x09, 0xe6, + 0xad, 0x40, 0x15, 0xc4, 0xeb, 0x50, 0xbb, 0x9b, 0x07, 0x9e, 0xf5, 0xe9, 0xd1, 0xa4, 0xf8, 0xf0, + 0x2a, 0xd0, 0xb5, 0xec, 0xae, 0xa3, 0xb3, 0xa1, 0x87, 0x35, 0x35, 0xaa, 0xd7, 0xb2, 0xbb, 0xe8, + 0x07, 0x50, 0x4d, 0x32, 0xc2, 0x8e, 0xee, 0x30, 0x5e, 0x3e, 0x3b, 0x6d, 0xf9, 0x6c, 0x58, 0xce, + 0xaf, 0xfe, 0x35, 0xe4, 0x7c, 0xda, 0x87, 0x8e, 0x15, 0x29, 0x3b, 0xaf, 0x72, 0x08, 0xd7, 0x2f, + 0x82, 0xb0, 0xbd, 0xb5, 0x97, 0x88, 0xd6, 0x16, 0x0c, 0x66, 0x26, 0x0f, 0x4a, 0x6f, 0x52, 0xb0, + 0x70, 0x26, 0x08, 0xed, 0x80, 0x3a, 0x74, 0x0c, 0xe2, 0x58, 0x02, 0x59, 0xe9, 0xca, 0x2c, 0x65, + 0xa3, 0xfc, 0xb7, 0x79, 0x92, 0xff, 0x0b, 0x4f, 0x04, 0x56, 0x12, 0x3c, 0x85, 0xd9, 0x3e, 0xa2, + 0xca, 0xb4, 0x88, 0x2e, 0xc5, 0x84, 0x89, 0xba, 0x3e, 0xb4, 0x07, 0xb0, 0x1c, 0x13, 0x97, 0xec, + 0x97, 0x9a, 0xb6, 0xdf, 0x62, 0xc4, 0x60, 0xa2, 0x1d, 0x81, 0x95, 0xa8, 0x5d, 0x8c, 0xb5, 0xdf, + 0x6f, 0x76, 0xea, 0xfd, 0xc2, 0xc2, 0x7b, 0x61, 0x5d, 0xbf, 0xe1, 0xcf, 0x70, 0x6b, 0x14, 0xde, + 0x0a, 0x67, 0x3a, 0xa6, 0xa7, 0xed, 0xb8, 0x1c, 0x55, 0x4e, 0xb6, 0x2c, 0xbd, 0x92, 0x61, 0xf1, + 0x8c, 0xb4, 0x1a, 0xce, 0x3e, 0xb9, 0x6e, 0x79, 0x5d, 0xb2, 0x99, 0xfc, 0x6e, 0x36, 0xbb, 0x84, + 0x3d, 0xe5, 0x9d, 0xb0, 0x57, 0x6a, 0xc1, 0xad, 0xf8, 0x3e, 0x23, 0x5e, 0x7c, 0xb1, 0x51, 0xf4, + 0x18, 0x52, 0x16, 0x1e, 0xd0, 0xbc, 0xb4, 0xa6, 0x94, 0xb3, 0x9b, 0x77, 0x2e, 0xfe, 0x0e, 0xc4, + 0x49, 0x1a, 0xcf, 0x28, 0xed, 0xc2, 0xed, 0xf3, 0x8b, 0x36, 0x1c, 0x0b, 0x8f, 0x51, 0x15, 0x96, + 0xe2, 0x2f, 0x75, 0xa7, 0xa7, 0xd3, 0x5e, 0x70, 0xd9, 0xf8, 0x8d, 0x54, 0xed, 0x66, 0xf4, 0x1d, + 0x7e, 0xaa, 0xd3, 0x9e, 0x7f, 0x73, 0x94, 0x3e, 0x87, 0xc5, 0x73, 0xc8, 0x42, 0xef, 0x81, 0x2c, + 0x48, 0x56, 0x35, 0x99, 0x8d, 0xfd, 0xb7, 0x43, 0xf0, 0x72, 0x0c, 0xe8, 0xd1, 0x84, 0x55, 0xfa, + 0x5d, 0x82, 0xf9, 0x08, 0x07, 0x2e, 0x94, 0x2f, 0x41, 0x9e, 0xfa, 0xc1, 0x22, 0xbb, 0x7d, 0xf4, + 0x0c, 0x94, 0x6b, 0x51, 0x83, 0x5f, 0xe5, 0x6e, 0x9b, 0x8b, 0x3a, 0x06, 0xab, 0xc5, 0x74, 0x36, + 0xa4, 0x28, 0x0b, 0x37, 0x9a, 0xdb, 0xbb, 0xf5, 0xc6, 0xee, 0x57, 0xb9, 0x19, 0x04, 0x90, 0x7e, + 0xb2, 0xd5, 0x6e, 0xbc, 0xdc, 0xce, 0x49, 0x68, 0x1e, 0x32, 0x7b, 0xbb, 0xb5, 0x17, 0x81, 0x4b, + 0x46, 0x2a, 0xcc, 0x05, 0xe6, 0x76, 0x3d, 0xa7, 0xa0, 0x1b, 0xa0, 0x3c, 0xd9, 0xfd, 0x2e, 0x97, + 0xaa, 0x3d, 0x7f, 0x75, 0x5c, 0x90, 0x5e, 0x1f, 0x17, 0xa4, 0xbf, 0x8f, 0x0b, 0xd2, 0x6f, 0x27, + 0x85, 0x99, 0xd7, 0x27, 0x85, 0x99, 0x37, 0x27, 0x85, 0x99, 0xef, 0xff, 0x75, 0xe9, 0x71, 0xf2, + 0xb9, 0xcf, 0x07, 0x37, 0xd2, 0xfc, 0xb9, 0x7f, 0xff, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, + 0x08, 0xdb, 0x22, 0xcb, 0x0c, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -974,17 +1023,19 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.ValBtcPkList) > 0 { + for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.ValBtcPkList[iNdEx].Size() + i -= size + if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 } - i-- - dAtA[i] = 0x22 } if m.Pop != nil { { @@ -1285,6 +1336,53 @@ func (m *BabylonBTCTaprootTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *SignatureInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sig != nil { + { + size := m.Sig.Size() + i -= size + if _, err := m.Sig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Pk != nil { + { + size := m.Pk.Size() + i -= size + if _, err := m.Pk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { offset -= sovBtcstaking(v) base := offset @@ -1374,9 +1472,11 @@ func (m *BTCDelegation) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovBtcstaking(uint64(l)) + if len(m.ValBtcPkList) > 0 { + for _, e := range m.ValBtcPkList { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } if m.StartHeight != 0 { n += 1 + sovBtcstaking(uint64(m.StartHeight)) @@ -1511,6 +1611,23 @@ func (m *BabylonBTCTaprootTx) Size() (n int) { return n } +func (m *SignatureInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pk != nil { + l = m.Pk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.Sig != nil { + l = m.Sig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + func sovBtcstaking(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2083,7 +2200,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2111,8 +2228,8 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ValBtcPkList = append(m.ValBtcPkList, v) + if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3072,6 +3189,126 @@ func (m *BabylonBTCTaprootTx) Unmarshal(dAtA []byte) error { } return nil } +func (m *SignatureInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.Pk = &v + if err := m.Pk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.Sig = &v + if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBtcstaking(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 8b43650e7..3ad6d72ad 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -7,6 +7,7 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -42,8 +43,8 @@ func FuzzStakingTx(f *testing.F) { r, net, stakerSK, - validatorPK, - covenantPK, + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index 02edf82bb..4cb448a80 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -168,10 +168,11 @@ type EventUnbondingBTCDelegation struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` @@ -232,10 +233,11 @@ type EventUnbondedBTCDelegation struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // If there is more than 1 PKs, then this means the delegation is restaked + // to multiple BTC validators + ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` @@ -312,34 +314,35 @@ func init() { } var fileDescriptor_74118427820fff75 = []byte{ - // 431 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x93, 0xcd, 0xaa, 0xd3, 0x40, - 0x18, 0x86, 0x9b, 0xea, 0x39, 0x7a, 0xc6, 0x9f, 0x83, 0xe1, 0x14, 0x4a, 0x85, 0x58, 0xa2, 0x94, - 0xd2, 0x45, 0x62, 0x5b, 0x71, 0xa5, 0x0b, 0x63, 0x05, 0x8b, 0x3f, 0x94, 0x18, 0xbb, 0xd0, 0x45, - 0x98, 0x49, 0xc6, 0x24, 0x74, 0x3a, 0x53, 0x9a, 0x2f, 0xb1, 0xbd, 0x0b, 0x57, 0x5e, 0x93, 0xcb, - 0x2e, 0xc5, 0x85, 0x94, 0xf6, 0x46, 0x24, 0x93, 0xb4, 0x8d, 0x50, 0x51, 0xd0, 0xa5, 0xbb, 0x49, - 0xf2, 0xbc, 0xcf, 0xc7, 0xf7, 0x92, 0x41, 0x3a, 0xc1, 0x64, 0xc9, 0x04, 0x37, 0x09, 0x78, 0x31, - 0xe0, 0x49, 0xc4, 0x03, 0x33, 0xed, 0x9a, 0x34, 0xa5, 0x1c, 0x62, 0x63, 0x36, 0x17, 0x20, 0xd4, - 0x5a, 0xc1, 0x18, 0x07, 0xc6, 0x48, 0xbb, 0x8d, 0x8b, 0x40, 0x04, 0x42, 0x12, 0x66, 0x76, 0xca, - 0xe1, 0x46, 0xeb, 0xb8, 0xb0, 0x14, 0x95, 0x9c, 0xee, 0xa0, 0x8b, 0x67, 0xd9, 0x90, 0xd7, 0xf4, - 0xa3, 0xe5, 0x3c, 0x1d, 0x63, 0x16, 0xf9, 0x18, 0xc4, 0x5c, 0x7d, 0x84, 0xae, 0x10, 0xf0, 0xdc, - 0x14, 0xb3, 0xba, 0xd2, 0x54, 0xda, 0xd7, 0x7a, 0x77, 0x8d, 0xa3, 0xe3, 0x8d, 0x72, 0xca, 0x3e, - 0x25, 0xe0, 0x8d, 0x31, 0xd3, 0xc7, 0xa8, 0x56, 0xb2, 0x0e, 0x28, 0xa3, 0x01, 0x86, 0x48, 0x70, - 0xf5, 0x71, 0xae, 0xf5, 0xe9, 0x4e, 0x7b, 0xef, 0xd7, 0xda, 0x43, 0x4c, 0x7a, 0x07, 0x94, 0xe9, - 0xef, 0x51, 0x43, 0x7a, 0x9f, 0x78, 0x10, 0xa5, 0x18, 0xe8, 0x3f, 0x95, 0x7f, 0xae, 0xa2, 0xdb, - 0xd2, 0xfe, 0x96, 0x13, 0xc1, 0xfd, 0x88, 0x07, 0x3f, 0xeb, 0x5f, 0xa1, 0x8c, 0x74, 0x67, 0x13, - 0x69, 0xbf, 0x6e, 0x3d, 0xfc, 0xf6, 0xfd, 0x4e, 0x2f, 0x88, 0x20, 0x4c, 0x88, 0xe1, 0x89, 0xa9, - 0x59, 0xcc, 0xf2, 0x42, 0x1c, 0xf1, 0xdd, 0x83, 0x09, 0xcb, 0x19, 0x8d, 0x0d, 0x6b, 0x38, 0xea, - 0x3f, 0xb8, 0x3f, 0x4a, 0xc8, 0x0b, 0xba, 0xb4, 0x4f, 0x08, 0x78, 0xa3, 0x89, 0xea, 0x20, 0x94, - 0x62, 0xe6, 0x16, 0xca, 0xea, 0x5f, 0x29, 0xaf, 0xa6, 0x98, 0x59, 0xd2, 0xda, 0x42, 0xe7, 0xc5, - 0xa2, 0x2e, 0x2c, 0xdc, 0x10, 0xc7, 0x61, 0xfd, 0x52, 0x53, 0x69, 0x9f, 0xd9, 0x37, 0x8a, 0xd7, - 0xce, 0xe2, 0x39, 0x8e, 0x43, 0xb5, 0x83, 0x6e, 0x25, 0xbb, 0x35, 0xf7, 0xe4, 0x65, 0x49, 0x9e, - 0xef, 0x3f, 0xe4, 0xac, 0xbe, 0xae, 0x16, 0xb5, 0xe7, 0xc5, 0x50, 0xff, 0x7f, 0x2f, 0x92, 0x1d, - 0x22, 0xf4, 0x61, 0x2e, 0xa6, 0x6e, 0x0c, 0x18, 0x68, 0xfd, 0xa4, 0xa9, 0xb4, 0x6f, 0xf6, 0x3a, - 0x7f, 0xf2, 0xcb, 0xbd, 0x01, 0x0c, 0x49, 0x6c, 0x9f, 0x65, 0xe9, 0xec, 0x4c, 0xad, 0x97, 0x5f, - 0x36, 0x9a, 0xb2, 0xda, 0x68, 0xca, 0x7a, 0xa3, 0x29, 0x9f, 0xb6, 0x5a, 0x65, 0xb5, 0xd5, 0x2a, - 0x5f, 0xb7, 0x5a, 0xe5, 0xdd, 0x6f, 0xd7, 0x5e, 0x94, 0xaf, 0xb8, 0xec, 0x80, 0x9c, 0xca, 0xbb, - 0xdd, 0xff, 0x11, 0x00, 0x00, 0xff, 0xff, 0x66, 0x62, 0xb6, 0xdc, 0x56, 0x04, 0x00, 0x00, + // 447 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x93, 0xc1, 0x6a, 0xd4, 0x40, + 0x18, 0xc7, 0x37, 0x5b, 0x5b, 0xe9, 0x58, 0x5d, 0x0c, 0x2d, 0x84, 0x15, 0x62, 0x88, 0x52, 0x96, + 0x1e, 0x12, 0xbb, 0x15, 0x4f, 0x7a, 0x30, 0x56, 0xb0, 0x58, 0x65, 0x89, 0xeb, 0x1e, 0x14, 0x09, + 0x33, 0xc9, 0x98, 0x0c, 0x3b, 0x9d, 0x59, 0x76, 0xbe, 0xc4, 0xdd, 0xb7, 0xf0, 0x0d, 0x7c, 0x1d, + 0x8f, 0x3d, 0x8a, 0x07, 0x91, 0xdd, 0x93, 0x6f, 0x21, 0x99, 0x64, 0xdb, 0x14, 0x2a, 0x0a, 0x7a, + 0xf4, 0x36, 0x49, 0x7e, 0xff, 0xdf, 0xc7, 0xf7, 0x27, 0x83, 0x5c, 0x82, 0xc9, 0x9c, 0x4b, 0xe1, + 0x13, 0x88, 0x15, 0xe0, 0x31, 0x13, 0xa9, 0x5f, 0xec, 0xfb, 0xb4, 0xa0, 0x02, 0x94, 0x37, 0x99, + 0x4a, 0x90, 0xe6, 0x4e, 0xcd, 0x78, 0xe7, 0x8c, 0x57, 0xec, 0x77, 0xb7, 0x53, 0x99, 0x4a, 0x4d, + 0xf8, 0xe5, 0xa9, 0x82, 0xbb, 0xbb, 0x97, 0x0b, 0x1b, 0x51, 0xcd, 0xb9, 0x43, 0xb4, 0xfd, 0xb4, + 0x1c, 0xf2, 0x92, 0x7e, 0x08, 0x86, 0x4f, 0x46, 0x98, 0xb3, 0x04, 0x83, 0x9c, 0x9a, 0x0f, 0xd1, + 0x55, 0x02, 0x71, 0x54, 0x60, 0x6e, 0x19, 0x8e, 0xd1, 0xbb, 0xd6, 0xbf, 0xe3, 0x5d, 0x3a, 0xde, + 0x6b, 0xa6, 0xc2, 0x0d, 0x02, 0xf1, 0x08, 0x73, 0x77, 0x84, 0x76, 0x1a, 0xd6, 0x43, 0xca, 0x69, + 0x8a, 0x81, 0x49, 0x61, 0x3e, 0xaa, 0xb4, 0x09, 0x5d, 0x69, 0xef, 0xfe, 0x5a, 0x7b, 0x1e, 0xd3, + 0xde, 0x43, 0xca, 0xdd, 0xb7, 0xa8, 0xab, 0xbd, 0x8f, 0x63, 0x60, 0x05, 0x06, 0xfa, 0x4f, 0xe5, + 0x9f, 0xda, 0xe8, 0x96, 0xb6, 0xbf, 0x16, 0x44, 0x8a, 0x84, 0x89, 0xf4, 0xa2, 0xfe, 0x05, 0x2a, + 0xc9, 0x68, 0x32, 0xd6, 0xf6, 0xad, 0xe0, 0xc1, 0xd7, 0x6f, 0xb7, 0xfb, 0x29, 0x83, 0x2c, 0x27, + 0x5e, 0x2c, 0x4f, 0xfc, 0x7a, 0x56, 0x9c, 0x61, 0x26, 0x56, 0x0f, 0x3e, 0xcc, 0x27, 0x54, 0x79, + 0xc1, 0xd1, 0xe0, 0xe0, 0xfe, 0xbd, 0x41, 0x4e, 0x9e, 0xd3, 0x79, 0xb8, 0x4e, 0x20, 0x1e, 0x8c, + 0xcd, 0x77, 0xa8, 0x53, 0x60, 0x1e, 0x55, 0xca, 0x88, 0x33, 0x05, 0x56, 0xdb, 0x59, 0xfb, 0x0b, + 0xef, 0x56, 0x81, 0x79, 0x50, 0xaa, 0x8f, 0x99, 0x02, 0x73, 0x17, 0x75, 0xea, 0x8d, 0x23, 0x98, + 0x45, 0x19, 0x56, 0x99, 0xb5, 0xe6, 0x18, 0xbd, 0xcd, 0xf0, 0x7a, 0xfd, 0x7a, 0x38, 0x7b, 0x86, + 0x55, 0x66, 0xee, 0xa1, 0x9b, 0xf9, 0x6a, 0xdf, 0x33, 0xf2, 0x8a, 0x26, 0x3b, 0x67, 0x1f, 0x2a, + 0xd6, 0xfd, 0xd1, 0xae, 0xfb, 0xaf, 0x1a, 0xa2, 0xc9, 0xff, 0x82, 0x2e, 0x14, 0x64, 0x1e, 0x21, + 0xf4, 0x7e, 0x2a, 0x4f, 0x22, 0x05, 0x18, 0xa8, 0xb5, 0xee, 0x18, 0xbd, 0x1b, 0xfd, 0xbd, 0x3f, + 0xf9, 0x09, 0x5f, 0x01, 0x86, 0x5c, 0x85, 0x9b, 0x65, 0xba, 0x3c, 0xd3, 0xe0, 0xf8, 0xf3, 0xc2, + 0x36, 0x4e, 0x17, 0xb6, 0xf1, 0x7d, 0x61, 0x1b, 0x1f, 0x97, 0x76, 0xeb, 0x74, 0x69, 0xb7, 0xbe, + 0x2c, 0xed, 0xd6, 0x9b, 0xdf, 0xae, 0x3e, 0x6b, 0x5e, 0x7a, 0xdd, 0x03, 0xd9, 0xd0, 0xb7, 0xfd, + 0xe0, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xc7, 0xd4, 0x63, 0x68, 0x04, 0x00, 0x00, } func (m *EventNewBTCValidator) Marshal() (dAtA []byte, err error) { @@ -481,17 +484,19 @@ func (m *EventUnbondingBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, er i-- dAtA[i] = 0x1a } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.ValBtcPkList) > 0 { + for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.ValBtcPkList[iNdEx].Size() + i -= size + if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) } - i = encodeVarintEvents(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 } if m.BtcPk != nil { { @@ -547,17 +552,19 @@ func (m *EventUnbondedBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x1a } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.ValBtcPkList) > 0 { + for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.ValBtcPkList[iNdEx].Size() + i -= size + if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvents(dAtA, i, uint64(size)) } - i = encodeVarintEvents(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 } if m.BtcPk != nil { { @@ -634,9 +641,11 @@ func (m *EventUnbondingBTCDelegation) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovEvents(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovEvents(uint64(l)) + if len(m.ValBtcPkList) > 0 { + for _, e := range m.ValBtcPkList { + l = e.Size() + n += 1 + l + sovEvents(uint64(l)) + } } l = len(m.StakingTxHash) if l > 0 { @@ -659,9 +668,11 @@ func (m *EventUnbondedBTCDelegation) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovEvents(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovEvents(uint64(l)) + if len(m.ValBtcPkList) > 0 { + for _, e := range m.ValBtcPkList { + l = e.Size() + n += 1 + l + sovEvents(uint64(l)) + } } l = len(m.StakingTxHash) if l > 0 { @@ -1007,7 +1018,7 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1035,8 +1046,8 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ValBtcPkList = append(m.ValBtcPkList, v) + if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1191,7 +1202,7 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1219,8 +1230,8 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ValBtcPkList = append(m.ValBtcPkList, v) + if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index da62eedb7..f3cfae1f4 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -24,7 +24,8 @@ func TestGenesisState_Validate(t *testing.T) { desc: "valid genesis state", genState: &types.GenesisState{ Params: types.Params{ - CovenantPk: types.DefaultParams().CovenantPk, + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, SlashingAddress: types.DefaultParams().SlashingAddress, MinSlashingTxFeeSat: 500, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), @@ -38,7 +39,8 @@ func TestGenesisState_Validate(t *testing.T) { desc: "invalid slashing rate in genesis", genState: &types.GenesisState{ Params: types.Params{ - CovenantPk: types.DefaultParams().CovenantPk, + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, SlashingAddress: types.DefaultParams().SlashingAddress, MinSlashingTxFeeSat: 500, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index a78931fb1..8acb8e744 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -20,11 +20,12 @@ const ( var _ paramtypes.ParamSet = (*Params)(nil) -func defaultCovenantPk() *bbn.BIP340PubKey { +// TODO: default values for multisig covenant +func defaultCovenantPks() []bbn.BIP340PubKey { // 32 bytes skBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} _, defaultPK := btcec.PrivKeyFromBytes(skBytes) - return bbn.NewBIP340PubKeyFromBTCPK(defaultPK) + return []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(defaultPK)} } func defaultSlashingAddress() string { @@ -45,7 +46,8 @@ func ParamKeyTable() paramtypes.KeyTable { // DefaultParams returns a default set of parameters func DefaultParams() Params { return Params{ - CovenantPk: defaultCovenantPk(), + CovenantPks: defaultCovenantPks(), // TODO: default values for multisig covenant + CovenantQuorum: 1, // TODO: default values for multisig covenant SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, MinCommissionRate: math.LegacyZeroDec(), @@ -93,6 +95,14 @@ func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { // Validate validates the set of params func (p Params) Validate() error { + if p.CovenantQuorum == 0 { + return fmt.Errorf("covenant quorum size has to be positive") + } + if p.CovenantQuorum*3 <= uint32(len(p.CovenantPks))*2 { + // NOTE: we assume covenant member can be adversarial, including + // equivocation, so >2/3 quorum is needed + return fmt.Errorf("covenant quorum size has to be more than 2/3 of the covenant committee size") + } if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { return err } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 3b7c0ca17..af5a903b5 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -28,23 +28,26 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { - // covenant_pk is the public key of covenant - // the PK follows encoding in BIP-340 spec on Bitcoin - CovenantPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=covenant_pk,json=covenantPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"covenant_pk,omitempty"` + // covenant_pks is the list of public keys held by the covenant committee + // each PK follows encoding in BIP-340 spec on Bitcoin + CovenantPks []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,rep,name=covenant_pks,json=covenantPks,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"covenant_pks,omitempty"` + // covenant_quorum is the minimum number of signatures needed for the covenant + // multisignature + CovenantQuorum uint32 `protobuf:"varint,2,opt,name=covenant_quorum,json=covenantQuorum,proto3" json:"covenant_quorum,omitempty"` // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin - SlashingAddress string `protobuf:"bytes,2,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` + SlashingAddress string `protobuf:"bytes,3,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? - MinSlashingTxFeeSat int64 `protobuf:"varint,3,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` + MinSlashingTxFeeSat int64 `protobuf:"varint,4,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators - MinCommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_commission_rate"` + MinCommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_commission_rate"` // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). - SlashingRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=slashing_rate,json=slashingRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slashing_rate"` + SlashingRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slashing_rate"` // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol - MaxActiveBtcValidators uint32 `protobuf:"varint,6,opt,name=max_active_btc_validators,json=maxActiveBtcValidators,proto3" json:"max_active_btc_validators,omitempty"` + MaxActiveBtcValidators uint32 `protobuf:"varint,7,opt,name=max_active_btc_validators,json=maxActiveBtcValidators,proto3" json:"max_active_btc_validators,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -79,6 +82,13 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo +func (m *Params) GetCovenantQuorum() uint32 { + if m != nil { + return m.CovenantQuorum + } + return 0 +} + func (m *Params) GetSlashingAddress() string { if m != nil { return m.SlashingAddress @@ -109,34 +119,36 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 432 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0x63, 0x3a, 0x2a, 0x61, 0x36, 0x01, 0xe1, 0x5f, 0xb6, 0x43, 0x5a, 0xed, 0x80, 0xca, - 0x61, 0x09, 0x63, 0x13, 0x12, 0x88, 0xcb, 0x02, 0x42, 0x42, 0x70, 0xa8, 0x32, 0x04, 0x12, 0x07, - 0xac, 0x37, 0xae, 0x49, 0xad, 0xd6, 0x76, 0x14, 0x7b, 0x51, 0xfa, 0x2d, 0x38, 0x72, 0xe4, 0x43, - 0xf0, 0x19, 0xd0, 0x8e, 0x13, 0x27, 0xb4, 0x43, 0x85, 0xda, 0x2f, 0x82, 0xe2, 0xfc, 0xa1, 0x37, - 0xc4, 0x29, 0xf1, 0xfb, 0x3c, 0xfa, 0x3d, 0x7e, 0x5f, 0xbf, 0x78, 0x3f, 0x81, 0x64, 0x31, 0x57, - 0x32, 0x4c, 0x0c, 0xd5, 0x06, 0x66, 0x5c, 0xa6, 0x61, 0x71, 0x18, 0x66, 0x90, 0x83, 0xd0, 0x41, - 0x96, 0x2b, 0xa3, 0xdc, 0xbb, 0x8d, 0x27, 0xf8, 0xeb, 0x09, 0x8a, 0xc3, 0xbd, 0x3b, 0xa9, 0x4a, - 0x95, 0x75, 0x84, 0xd5, 0x5f, 0x6d, 0xde, 0xdb, 0xa5, 0x4a, 0x0b, 0xa5, 0x49, 0x2d, 0xd4, 0x87, - 0x5a, 0xda, 0xff, 0xd1, 0xc3, 0xfd, 0xb1, 0x05, 0xbb, 0x1f, 0xf0, 0x75, 0xaa, 0x0a, 0x26, 0x41, - 0x1a, 0x92, 0xcd, 0x3c, 0x34, 0x44, 0xa3, 0xed, 0xe8, 0xc9, 0xe5, 0x72, 0xf0, 0x38, 0xe5, 0x66, - 0x7a, 0x96, 0x04, 0x54, 0x89, 0xb0, 0x89, 0xa5, 0x53, 0xe0, 0xb2, 0x3d, 0x84, 0x66, 0x91, 0x31, - 0x1d, 0x44, 0xaf, 0xc7, 0x47, 0xc7, 0x8f, 0xc6, 0x67, 0xc9, 0x1b, 0xb6, 0x88, 0x71, 0x8b, 0x1a, - 0xcf, 0xdc, 0x87, 0xf8, 0xa6, 0x9e, 0x83, 0x9e, 0x72, 0x99, 0x12, 0x98, 0x4c, 0x72, 0xa6, 0xb5, - 0x77, 0x65, 0x88, 0x46, 0xd7, 0xe2, 0x1b, 0x6d, 0xfd, 0xa4, 0x2e, 0xbb, 0xc7, 0xf8, 0xbe, 0xe0, - 0x92, 0x74, 0x76, 0x53, 0x92, 0xcf, 0x8c, 0x11, 0x0d, 0xc6, 0xeb, 0x0d, 0xd1, 0xa8, 0x17, 0xdf, - 0x16, 0x5c, 0x9e, 0x36, 0xea, 0xbb, 0xf2, 0x15, 0x63, 0xa7, 0x60, 0xdc, 0x4f, 0xb8, 0x2a, 0x13, - 0xaa, 0x84, 0xe0, 0x5a, 0x73, 0x25, 0x49, 0x0e, 0x86, 0x79, 0x5b, 0x55, 0x46, 0x14, 0x9c, 0x2f, - 0x07, 0xce, 0xe5, 0x72, 0xf0, 0x60, 0xa3, 0x8b, 0x7a, 0x04, 0xcd, 0xe7, 0x40, 0x4f, 0x66, 0x4d, - 0x0b, 0x2f, 0x19, 0x8d, 0x6f, 0x09, 0x2e, 0x5f, 0x74, 0xa4, 0x18, 0x0c, 0x73, 0x01, 0xef, 0x74, - 0x37, 0xb2, 0xe4, 0xab, 0x96, 0xfc, 0xfc, 0xff, 0xc8, 0x3f, 0xbf, 0x1f, 0xe0, 0x66, 0xf6, 0x55, - 0xce, 0x76, 0x8b, 0xb4, 0x11, 0x4f, 0xf1, 0xae, 0x80, 0x92, 0x00, 0x35, 0xbc, 0x60, 0x24, 0x31, - 0x94, 0x14, 0x30, 0xe7, 0x13, 0x30, 0x2a, 0xd7, 0x5e, 0x7f, 0x88, 0x46, 0x3b, 0xf1, 0x3d, 0x01, - 0xe5, 0x89, 0xd5, 0x23, 0x43, 0xdf, 0x77, 0xea, 0xb3, 0xad, 0xaf, 0xdf, 0x06, 0x4e, 0xf4, 0xf6, - 0x7c, 0xe5, 0xa3, 0x8b, 0x95, 0x8f, 0x7e, 0xaf, 0x7c, 0xf4, 0x65, 0xed, 0x3b, 0x17, 0x6b, 0xdf, - 0xf9, 0xb5, 0xf6, 0x9d, 0x8f, 0xff, 0x7c, 0xbe, 0x72, 0x73, 0xd1, 0xec, 0x75, 0x93, 0xbe, 0xdd, - 0x8e, 0xa3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x85, 0x4c, 0x22, 0xe4, 0x8b, 0x02, 0x00, 0x00, + // 453 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcb, 0x6e, 0xd3, 0x40, + 0x18, 0x85, 0x6d, 0x52, 0x82, 0x18, 0x52, 0x0a, 0xe6, 0xe6, 0x76, 0xe1, 0x44, 0x5d, 0x40, 0x58, + 0xd4, 0xa6, 0xb4, 0x42, 0x02, 0xb1, 0xa9, 0x41, 0x48, 0x08, 0x16, 0xc1, 0x45, 0x48, 0xb0, 0x60, + 0xf4, 0x7b, 0x32, 0x38, 0xa3, 0x64, 0x66, 0x82, 0x67, 0x6c, 0x39, 0x6f, 0xc1, 0x92, 0x25, 0x0f, + 0xc1, 0x43, 0x74, 0x59, 0xb1, 0x42, 0x5d, 0x44, 0x28, 0x79, 0x02, 0xde, 0x00, 0x79, 0x7c, 0xa1, + 0x3b, 0xc4, 0xca, 0x9e, 0x73, 0x8e, 0xbe, 0x33, 0x97, 0x1f, 0xed, 0xc6, 0x10, 0x2f, 0x66, 0x52, + 0x04, 0xb1, 0x26, 0x4a, 0xc3, 0x94, 0x89, 0x24, 0xc8, 0xf7, 0x83, 0x39, 0xa4, 0xc0, 0x95, 0x3f, + 0x4f, 0xa5, 0x96, 0xce, 0xad, 0x3a, 0xe3, 0xff, 0xcd, 0xf8, 0xf9, 0xfe, 0xce, 0xcd, 0x44, 0x26, + 0xd2, 0x24, 0x82, 0xf2, 0xaf, 0x0a, 0xef, 0x6c, 0x13, 0xa9, 0xb8, 0x54, 0xb8, 0x32, 0xaa, 0x45, + 0x65, 0xed, 0xfe, 0xee, 0xa0, 0xee, 0xc8, 0x80, 0x9d, 0xf7, 0xa8, 0x47, 0x64, 0x4e, 0x05, 0x08, + 0x8d, 0xe7, 0x53, 0xe5, 0xda, 0x83, 0xce, 0xb0, 0x17, 0x3e, 0x3a, 0x5b, 0xf6, 0x1f, 0x26, 0x4c, + 0x4f, 0xb2, 0xd8, 0x27, 0x92, 0x07, 0x75, 0x2f, 0x99, 0x00, 0x13, 0xcd, 0x22, 0xd0, 0x8b, 0x39, + 0x55, 0x7e, 0xf8, 0x72, 0x74, 0x70, 0xf8, 0x60, 0x94, 0xc5, 0xaf, 0xe8, 0x22, 0xba, 0xd2, 0xb0, + 0x46, 0x53, 0xe5, 0xdc, 0x43, 0x5b, 0x2d, 0xfa, 0x73, 0x26, 0xd3, 0x8c, 0xbb, 0x17, 0x06, 0xf6, + 0x70, 0x33, 0xba, 0xda, 0xc8, 0x6f, 0x8c, 0xea, 0xdc, 0x47, 0xd7, 0xd4, 0x0c, 0xd4, 0x84, 0x89, + 0x04, 0xc3, 0x78, 0x9c, 0x52, 0xa5, 0xdc, 0xce, 0xc0, 0x1e, 0x5e, 0x8e, 0xb6, 0x1a, 0xfd, 0xa8, + 0x92, 0x9d, 0x43, 0x74, 0x87, 0x33, 0x81, 0xdb, 0xb8, 0x2e, 0xf0, 0x27, 0x4a, 0xb1, 0x02, 0xed, + 0x6e, 0x0c, 0xec, 0x61, 0x27, 0xba, 0xc1, 0x99, 0x38, 0xae, 0xdd, 0xb7, 0xc5, 0x0b, 0x4a, 0x8f, + 0x41, 0x3b, 0x1f, 0x51, 0x29, 0x63, 0x22, 0x39, 0x67, 0x4a, 0x31, 0x29, 0x70, 0x0a, 0x9a, 0xba, + 0x17, 0xcb, 0x8e, 0xd0, 0x3f, 0x59, 0xf6, 0xad, 0xb3, 0x65, 0xff, 0xee, 0xb9, 0xf3, 0x56, 0xb7, + 0x55, 0x7f, 0xf6, 0xd4, 0x78, 0x5a, 0x1f, 0xf6, 0x39, 0x25, 0xd1, 0x75, 0xce, 0xc4, 0xb3, 0x96, + 0x14, 0x81, 0xa6, 0x0e, 0xa0, 0xcd, 0x76, 0x47, 0x86, 0xdc, 0x35, 0xe4, 0xa7, 0xff, 0x47, 0xfe, + 0xf1, 0x7d, 0x0f, 0xd5, 0xcf, 0x54, 0xf6, 0xf4, 0x1a, 0xa4, 0xa9, 0x78, 0x8c, 0xb6, 0x39, 0x14, + 0x18, 0x88, 0x66, 0x39, 0xc5, 0xb1, 0x26, 0x38, 0x87, 0x19, 0x1b, 0x83, 0x96, 0xa9, 0x72, 0x2f, + 0x99, 0x6b, 0xbd, 0xcd, 0xa1, 0x38, 0x32, 0x7e, 0xa8, 0xc9, 0xbb, 0xd6, 0x7d, 0xb2, 0xf1, 0xf5, + 0x5b, 0xdf, 0x0a, 0x5f, 0x9f, 0xac, 0x3c, 0xfb, 0x74, 0xe5, 0xd9, 0xbf, 0x56, 0x9e, 0xfd, 0x65, + 0xed, 0x59, 0xa7, 0x6b, 0xcf, 0xfa, 0xb9, 0xf6, 0xac, 0x0f, 0xff, 0x7c, 0xe8, 0xe2, 0xfc, 0x4c, + 0x9a, 0xed, 0xc6, 0x5d, 0x33, 0x48, 0x07, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x89, 0x23, 0x96, + 0x04, 0xb6, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -162,7 +174,7 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { if m.MaxActiveBtcValidators != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveBtcValidators)) i-- - dAtA[i] = 0x30 + dAtA[i] = 0x38 } { size := m.SlashingRate.Size() @@ -173,7 +185,7 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintParams(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x32 { size := m.MinCommissionRate.Size() i -= size @@ -183,30 +195,37 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintParams(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a if m.MinSlashingTxFeeSat != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MinSlashingTxFeeSat)) i-- - dAtA[i] = 0x18 + dAtA[i] = 0x20 } if len(m.SlashingAddress) > 0 { i -= len(m.SlashingAddress) copy(dAtA[i:], m.SlashingAddress) i = encodeVarintParams(dAtA, i, uint64(len(m.SlashingAddress))) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a } - if m.CovenantPk != nil { - { - size := m.CovenantPk.Size() - i -= size - if _, err := m.CovenantPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintParams(dAtA, i, uint64(size)) - } + if m.CovenantQuorum != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.CovenantQuorum)) i-- - dAtA[i] = 0xa + dAtA[i] = 0x10 + } + if len(m.CovenantPks) > 0 { + for iNdEx := len(m.CovenantPks) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.CovenantPks[iNdEx].Size() + i -= size + if _, err := m.CovenantPks[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } } return len(dAtA) - i, nil } @@ -228,9 +247,14 @@ func (m *Params) Size() (n int) { } var l int _ = l - if m.CovenantPk != nil { - l = m.CovenantPk.Size() - n += 1 + l + sovParams(uint64(l)) + if len(m.CovenantPks) > 0 { + for _, e := range m.CovenantPks { + l = e.Size() + n += 1 + l + sovParams(uint64(l)) + } + } + if m.CovenantQuorum != 0 { + n += 1 + sovParams(uint64(m.CovenantQuorum)) } l = len(m.SlashingAddress) if l > 0 { @@ -286,7 +310,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantPks", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -314,12 +338,31 @@ func (m *Params) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.CovenantPk = &v - if err := m.CovenantPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantPks = append(m.CovenantPks, v) + if err := m.CovenantPks[len(m.CovenantPks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CovenantQuorum", wireType) + } + m.CovenantQuorum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CovenantQuorum |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingAddress", wireType) } @@ -351,7 +394,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { } m.SlashingAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field MinSlashingTxFeeSat", wireType) } @@ -370,7 +413,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MinCommissionRate", wireType) } @@ -404,7 +447,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingRate", wireType) } @@ -438,7 +481,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 6: + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveBtcValidators", wireType) } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index c73571379..a7a48e70f 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -998,10 +998,9 @@ type QueryBTCDelegationResponse struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk is the Bitcoin secp256k1 PK of the BTC validator that + // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that // this BTC delegation delegates to - // the PK follows encoding in BIP-340 spec - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock StartHeight uint64 `protobuf:"varint,3,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` @@ -1130,86 +1129,86 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1255 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0x4f, 0x6f, 0x1b, 0xc5, - 0x1b, 0xce, 0xa4, 0xa9, 0x7f, 0xc9, 0x9b, 0x7f, 0xfd, 0x4d, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, - 0x9a, 0x3f, 0x0d, 0xea, 0x6e, 0xec, 0xa0, 0x1c, 0x48, 0x68, 0xa9, 0x1b, 0x68, 0x9a, 0x10, 0xc9, - 0x6c, 0x02, 0x95, 0xb8, 0x58, 0xb3, 0xce, 0x74, 0xbd, 0xc4, 0xd9, 0x75, 0xbd, 0x63, 0x93, 0xa8, - 0xca, 0x85, 0x03, 0x12, 0xe2, 0x82, 0xe0, 0x23, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0xf0, 0x01, - 0xe8, 0xb1, 0x12, 0x12, 0x20, 0x2a, 0x45, 0x28, 0x41, 0xe2, 0x6b, 0xa0, 0x9d, 0x1d, 0x67, 0x77, - 0xe3, 0xb5, 0xbd, 0x36, 0xe1, 0x16, 0xcf, 0xcc, 0xfb, 0xbe, 0xcf, 0xf3, 0xcc, 0xb3, 0xef, 0xbc, - 0x0a, 0xdc, 0xd4, 0x89, 0x7e, 0x58, 0xb2, 0x2d, 0x55, 0x67, 0x05, 0x87, 0x91, 0x3d, 0xd3, 0x32, - 0xd4, 0x5a, 0x5a, 0x7d, 0x5a, 0xa5, 0x95, 0x43, 0xa5, 0x5c, 0xb1, 0x99, 0x8d, 0x5f, 0x11, 0x47, - 0x14, 0xff, 0x88, 0x52, 0x4b, 0x4b, 0xd7, 0x0c, 0xdb, 0xb0, 0xf9, 0x09, 0xd5, 0xfd, 0xcb, 0x3b, - 0x2c, 0xdd, 0x30, 0x6c, 0xdb, 0x28, 0x51, 0x95, 0x94, 0x4d, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, - 0x6d, 0xcb, 0x11, 0xbb, 0x0b, 0x05, 0xdb, 0xd9, 0xb7, 0x1d, 0x55, 0x27, 0x0e, 0xf5, 0x6a, 0xa8, - 0xb5, 0xb4, 0x4e, 0x19, 0x49, 0xab, 0x65, 0x62, 0x98, 0x16, 0x3f, 0x2c, 0xce, 0xca, 0xd1, 0xc8, - 0xca, 0xa4, 0x42, 0xf6, 0xeb, 0xf9, 0x66, 0xa3, 0xcf, 0x04, 0x80, 0xf2, 0x73, 0xf2, 0x35, 0xc0, - 0xef, 0xb9, 0xd5, 0x72, 0x3c, 0x58, 0xa3, 0x4f, 0xab, 0xd4, 0x61, 0xb2, 0x06, 0x57, 0x43, 0xab, - 0x4e, 0xd9, 0xb6, 0x1c, 0x8a, 0x57, 0x20, 0xe1, 0x15, 0x49, 0xa2, 0x29, 0x34, 0x3f, 0x98, 0x99, - 0x50, 0x22, 0x05, 0x50, 0xbc, 0xb0, 0x6c, 0xdf, 0xf3, 0xe3, 0xc9, 0x1e, 0x4d, 0x84, 0xc8, 0x05, - 0x18, 0xe3, 0x39, 0xb3, 0x3b, 0x0f, 0x3e, 0x20, 0x25, 0x73, 0x97, 0x30, 0xbb, 0x52, 0x2f, 0x88, - 0xdf, 0x01, 0xf0, 0x69, 0x8a, 0xec, 0xb3, 0x8a, 0xa7, 0x89, 0xe2, 0x6a, 0xa2, 0x78, 0xba, 0x0b, - 0x4d, 0x94, 0x1c, 0x31, 0xa8, 0x88, 0xd5, 0x02, 0x91, 0xf2, 0xf7, 0x08, 0xa4, 0xa8, 0x2a, 0x82, - 0xc0, 0x06, 0x8c, 0xe8, 0xac, 0x90, 0xaf, 0x9d, 0xed, 0x24, 0xd1, 0xd4, 0xa5, 0xf9, 0xc1, 0xcc, - 0x74, 0x13, 0x22, 0xc1, 0x2c, 0xda, 0xb0, 0xce, 0x0a, 0x7e, 0x4e, 0xfc, 0x30, 0x04, 0xb9, 0x97, - 0x43, 0x9e, 0x6b, 0x0b, 0xd9, 0x03, 0x12, 0xc2, 0x7c, 0x0f, 0x92, 0x0d, 0x90, 0xeb, 0xba, 0x4c, - 0xc3, 0x48, 0x8d, 0x94, 0xf2, 0x2e, 0xe8, 0xf2, 0x5e, 0xbe, 0x48, 0x0f, 0xb8, 0x36, 0x03, 0xda, - 0x60, 0x8d, 0x94, 0xb2, 0xac, 0x90, 0xdb, 0x5b, 0xa7, 0x07, 0x32, 0x8d, 0x50, 0xf6, 0x8c, 0xf2, - 0x3a, 0x0c, 0x87, 0x28, 0x0b, 0x71, 0x63, 0x31, 0x1e, 0x0a, 0x32, 0x96, 0xbf, 0x0d, 0x68, 0xbb, - 0x46, 0x4b, 0xd4, 0xf0, 0x0c, 0x5c, 0x87, 0x9a, 0x85, 0x84, 0xc3, 0x08, 0xab, 0x7a, 0xe6, 0x18, - 0xc9, 0x2c, 0x34, 0xaf, 0xe0, 0x47, 0x6f, 0xf3, 0x08, 0x4d, 0x44, 0x9e, 0xb3, 0x41, 0x6f, 0xd7, - 0x36, 0xf8, 0x01, 0xc1, 0x78, 0x24, 0x54, 0x21, 0xca, 0x16, 0x8c, 0xba, 0xa2, 0xec, 0xfa, 0x5b, - 0xc2, 0x08, 0xb7, 0xe2, 0x80, 0xd6, 0x5c, 0x13, 0x05, 0xd2, 0x5e, 0x9c, 0x15, 0x76, 0x61, 0xa6, - 0xe1, 0x26, 0x73, 0xf6, 0xc7, 0xb4, 0x72, 0x9f, 0xad, 0x53, 0xd3, 0x28, 0xb2, 0x4e, 0x7c, 0x81, - 0x5f, 0x85, 0x44, 0x91, 0x47, 0x71, 0x48, 0x7d, 0x9a, 0xf8, 0x25, 0x6f, 0xc2, 0x6c, 0xbb, 0x2a, - 0x42, 0xa7, 0x9b, 0x30, 0x54, 0xb3, 0x99, 0x69, 0x19, 0xf9, 0xb2, 0xbb, 0xcf, 0x8b, 0xf4, 0x69, - 0x83, 0xde, 0x1a, 0x0f, 0x91, 0x37, 0xe1, 0x56, 0x43, 0xb2, 0x07, 0xd5, 0x4a, 0x85, 0x5a, 0x8c, - 0x1f, 0xe8, 0xc8, 0xc9, 0x7a, 0x04, 0xff, 0x70, 0x32, 0x01, 0xcc, 0xa7, 0x86, 0x82, 0xd4, 0x1a, - 0x00, 0xf7, 0x36, 0x02, 0xfe, 0x0c, 0xc1, 0x1c, 0x2f, 0x72, 0xbf, 0xc0, 0xcc, 0x1a, 0x0d, 0x35, - 0x8a, 0xf3, 0x32, 0x37, 0x2b, 0x73, 0x51, 0x3e, 0xfd, 0x19, 0xc1, 0x7c, 0x7b, 0x2c, 0x82, 0xb3, - 0xd6, 0xa4, 0x79, 0xbd, 0x16, 0xe3, 0x53, 0x7e, 0x6c, 0xb2, 0xe2, 0x16, 0x65, 0xe4, 0x3f, 0x6b, - 0x62, 0x13, 0xe2, 0x83, 0xe3, 0x44, 0x08, 0xa3, 0xbb, 0x21, 0x21, 0xe5, 0x65, 0xb8, 0x11, 0xbd, - 0xdd, 0xfa, 0x3e, 0xe5, 0x2f, 0x11, 0x4c, 0x37, 0x38, 0x22, 0xa2, 0xf9, 0xc4, 0xfa, 0x1e, 0x2e, - 0xea, 0xd6, 0x5e, 0xa2, 0x08, 0xcf, 0x47, 0xb5, 0x99, 0x8f, 0x60, 0x2c, 0xd0, 0x66, 0xec, 0x4a, - 0x44, 0xc3, 0x51, 0xda, 0x36, 0x9c, 0x70, 0xea, 0xeb, 0x7e, 0xeb, 0x09, 0x6d, 0x5c, 0xdc, 0x4d, - 0x6e, 0xf8, 0xaf, 0x49, 0xa0, 0xe5, 0x09, 0x9d, 0xef, 0xc0, 0x55, 0x01, 0x32, 0xcf, 0x0e, 0xf2, - 0x45, 0xe2, 0x14, 0x03, 0x62, 0x5f, 0x11, 0x5b, 0x3b, 0x07, 0xeb, 0xc4, 0x29, 0xba, 0xdf, 0xf3, - 0x6f, 0x97, 0xa2, 0x9e, 0x8c, 0x40, 0x1b, 0x4e, 0x78, 0x37, 0xc6, 0x13, 0x0c, 0x65, 0x97, 0xff, - 0x38, 0x9e, 0xcc, 0x18, 0x26, 0x2b, 0x56, 0x75, 0xa5, 0x60, 0xef, 0xab, 0x42, 0x9a, 0x42, 0x91, - 0x98, 0x56, 0xfd, 0x87, 0xca, 0x0e, 0xcb, 0xd4, 0x51, 0xb2, 0x8f, 0x72, 0x4b, 0xaf, 0x2f, 0xe6, - 0xaa, 0xfa, 0x26, 0x3d, 0xd4, 0x2e, 0xeb, 0xee, 0x15, 0xe3, 0x1d, 0x00, 0xdf, 0x04, 0x5c, 0x82, - 0xee, 0x53, 0xf6, 0xd7, 0x8d, 0xe3, 0xb6, 0x14, 0x87, 0x91, 0x0a, 0xcb, 0x0b, 0x83, 0x5e, 0xf2, - 0x5a, 0x0a, 0x5f, 0xf3, 0x5c, 0x8c, 0x27, 0x00, 0xa8, 0xb5, 0x5b, 0x3f, 0xd0, 0xc7, 0x0f, 0x0c, - 0x50, 0x4b, 0x98, 0x1c, 0x8f, 0xc3, 0x00, 0xb3, 0x19, 0x29, 0xe5, 0x1d, 0xc2, 0x92, 0x97, 0xf9, - 0x6e, 0x3f, 0x5f, 0xd8, 0x26, 0x3c, 0xd6, 0x57, 0x34, 0x99, 0xe0, 0x42, 0x0e, 0x9c, 0x09, 0x89, - 0x67, 0x60, 0xa4, 0xbe, 0xed, 0x14, 0x2a, 0x66, 0x99, 0x25, 0xff, 0xc7, 0x8f, 0x0c, 0x8b, 0xd5, - 0x6d, 0xbe, 0xe8, 0x7e, 0x3f, 0x84, 0xb7, 0x90, 0x64, 0xff, 0x14, 0x9a, 0xef, 0xd7, 0xc4, 0x2f, - 0xfc, 0x18, 0xfe, 0x5f, 0xb5, 0x7c, 0xd7, 0xe5, 0x4d, 0xeb, 0x89, 0x9d, 0x1c, 0xe0, 0xe6, 0x68, - 0xf1, 0x3e, 0xbf, 0x1f, 0x08, 0x79, 0x64, 0x3d, 0xb1, 0xb5, 0x2b, 0xd5, 0x73, 0x2b, 0x99, 0xcf, - 0x47, 0xe1, 0x32, 0xbf, 0x59, 0xfc, 0x29, 0x82, 0x84, 0x37, 0xf0, 0xe1, 0xdb, 0x4d, 0x52, 0x36, - 0x4e, 0x98, 0xd2, 0x42, 0x9c, 0xa3, 0x9e, 0x4d, 0xe4, 0x99, 0x4f, 0x7e, 0xf9, 0xeb, 0xab, 0xde, - 0x49, 0x3c, 0xa1, 0xb6, 0x1a, 0x7c, 0xf1, 0xd7, 0x08, 0x86, 0x43, 0x1d, 0x14, 0x2f, 0xb6, 0x2a, - 0x12, 0x35, 0x87, 0x4a, 0xe9, 0x0e, 0x22, 0x04, 0xba, 0x3b, 0x1c, 0xdd, 0x1c, 0x9e, 0x51, 0x9b, - 0x8e, 0xdc, 0x81, 0x9e, 0x8d, 0x7f, 0x42, 0x30, 0x14, 0x4c, 0x84, 0xd5, 0xb8, 0x25, 0xeb, 0x18, - 0x17, 0xe3, 0x07, 0x08, 0x88, 0xeb, 0x1c, 0x62, 0x16, 0xbf, 0x15, 0x0b, 0xa2, 0xfa, 0x2c, 0xdc, - 0x4a, 0x8f, 0xd4, 0xb3, 0x3d, 0xfc, 0x0d, 0x82, 0x91, 0xf0, 0x4c, 0x85, 0xdb, 0x49, 0xd6, 0xd8, - 0xad, 0xa5, 0x4c, 0x27, 0x21, 0x82, 0x83, 0xc2, 0x39, 0xcc, 0xe3, 0xd9, 0x16, 0x1c, 0x02, 0xed, - 0x15, 0xff, 0x8a, 0x60, 0xbc, 0xc5, 0xab, 0x8a, 0xef, 0xb6, 0xc2, 0xd0, 0x7e, 0x34, 0x90, 0xee, - 0x75, 0x1d, 0x2f, 0x08, 0x2d, 0x73, 0x42, 0x8b, 0x58, 0x89, 0x79, 0x29, 0x5e, 0x77, 0x39, 0xc2, - 0x7f, 0x23, 0x18, 0x6b, 0x3a, 0xb9, 0xe1, 0xd5, 0xb8, 0xe6, 0x88, 0x1a, 0x2b, 0xa5, 0x37, 0xbb, - 0x8c, 0x16, 0x94, 0xb6, 0x38, 0xa5, 0x87, 0xf8, 0xed, 0x2e, 0x7d, 0xc6, 0x67, 0x36, 0x9f, 0xe9, - 0x4b, 0x04, 0xc9, 0x66, 0x93, 0x20, 0x5e, 0x89, 0x0b, 0x35, 0x62, 0x18, 0x95, 0x56, 0xbb, 0x0b, - 0x16, 0x34, 0xd7, 0x38, 0xcd, 0xbb, 0x78, 0xf5, 0xdf, 0xd0, 0xc4, 0xdf, 0x21, 0x18, 0x3d, 0x37, - 0x0e, 0xe1, 0x4c, 0x5b, 0x53, 0x35, 0x8c, 0x56, 0xd2, 0x52, 0x47, 0x31, 0x82, 0x82, 0xca, 0x29, - 0xdc, 0xc6, 0x73, 0x4d, 0x28, 0x90, 0x7a, 0x9c, 0x78, 0xd4, 0xf0, 0x31, 0x82, 0xeb, 0x4d, 0xc6, - 0x1d, 0xfc, 0x46, 0x5c, 0x35, 0x23, 0x5a, 0xc1, 0x4a, 0x57, 0xb1, 0x82, 0xc5, 0x06, 0x67, 0xb1, - 0x86, 0xb3, 0x5d, 0x5e, 0x44, 0xb0, 0x5f, 0xfc, 0xe8, 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, - 0xc3, 0x74, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0xc0, 0x4b, 0x01, 0x98, 0xea, 0xb3, 0x88, 0xf1, 0xeb, - 0x28, 0xfb, 0xee, 0xf3, 0x93, 0x14, 0x7a, 0x71, 0x92, 0x42, 0x7f, 0x9e, 0xa4, 0xd0, 0x17, 0xa7, - 0xa9, 0x9e, 0x17, 0xa7, 0xa9, 0x9e, 0xdf, 0x4f, 0x53, 0x3d, 0x1f, 0xb6, 0x9d, 0x7d, 0x0e, 0x82, - 0x05, 0xf9, 0x20, 0xa4, 0x27, 0xf8, 0xbf, 0x86, 0x96, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x4c, - 0x47, 0x4f, 0x01, 0x02, 0x13, 0x00, 0x00, + // 1262 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0x24, 0xa9, 0x69, 0x5e, 0x7e, 0x95, 0x69, 0xa1, 0xee, 0xa6, 0x71, 0xd2, 0x4d, 0xf3, + 0xa3, 0x41, 0xdd, 0x8d, 0x1d, 0x94, 0x03, 0x09, 0x2d, 0x75, 0x03, 0x4d, 0x93, 0x46, 0x32, 0x9b, + 0x42, 0x25, 0x24, 0x64, 0xcd, 0x3a, 0xd3, 0xf5, 0x12, 0x67, 0xd7, 0xf5, 0x8e, 0x4d, 0xa2, 0x2a, + 0x17, 0x0e, 0x48, 0x88, 0x0b, 0x82, 0x3f, 0x81, 0x03, 0x48, 0x9c, 0x38, 0x70, 0x81, 0x3f, 0x80, + 0x1e, 0x2b, 0x21, 0x21, 0x44, 0xa5, 0xa8, 0x4a, 0x90, 0xf8, 0x37, 0xd0, 0xce, 0x8e, 0xb3, 0xbb, + 0xf1, 0xda, 0x5e, 0xbb, 0xe1, 0x16, 0xcf, 0xcc, 0x7b, 0xef, 0xfb, 0xde, 0x7c, 0xfb, 0xcd, 0x53, + 0xe0, 0x9a, 0x4e, 0xf4, 0xfd, 0x92, 0x6d, 0xa9, 0x3a, 0x2b, 0x38, 0x8c, 0xec, 0x98, 0x96, 0xa1, + 0xd6, 0xd2, 0xea, 0x93, 0x2a, 0xad, 0xec, 0x2b, 0xe5, 0x8a, 0xcd, 0x6c, 0xfc, 0x86, 0x38, 0xa2, + 0xf8, 0x47, 0x94, 0x5a, 0x5a, 0xba, 0x64, 0xd8, 0x86, 0xcd, 0x4f, 0xa8, 0xee, 0x5f, 0xde, 0x61, + 0xe9, 0xaa, 0x61, 0xdb, 0x46, 0x89, 0xaa, 0xa4, 0x6c, 0xaa, 0xc4, 0xb2, 0x6c, 0x46, 0x98, 0x69, + 0x5b, 0x8e, 0xd8, 0x9d, 0x2f, 0xd8, 0xce, 0xae, 0xed, 0xa8, 0x3a, 0x71, 0xa8, 0x57, 0x43, 0xad, + 0xa5, 0x75, 0xca, 0x48, 0x5a, 0x2d, 0x13, 0xc3, 0xb4, 0xf8, 0x61, 0x71, 0x56, 0x8e, 0x46, 0x56, + 0x26, 0x15, 0xb2, 0x5b, 0xcf, 0x37, 0x13, 0x7d, 0x26, 0x00, 0x94, 0x9f, 0x93, 0x2f, 0x01, 0xfe, + 0xd0, 0xad, 0x96, 0xe3, 0xc1, 0x1a, 0x7d, 0x52, 0xa5, 0x0e, 0x93, 0x35, 0xb8, 0x18, 0x5a, 0x75, + 0xca, 0xb6, 0xe5, 0x50, 0xbc, 0x0c, 0x09, 0xaf, 0x48, 0x12, 0x4d, 0xa2, 0xb9, 0xc1, 0xcc, 0xb8, + 0x12, 0xd9, 0x00, 0xc5, 0x0b, 0xcb, 0xf6, 0x3f, 0x3b, 0x9c, 0xe8, 0xd1, 0x44, 0x88, 0x5c, 0x80, + 0x2b, 0x3c, 0x67, 0xf6, 0xe1, 0xdd, 0x8f, 0x49, 0xc9, 0xdc, 0x26, 0xcc, 0xae, 0xd4, 0x0b, 0xe2, + 0x0f, 0x00, 0x7c, 0x9a, 0x22, 0xfb, 0x8c, 0xe2, 0xf5, 0x44, 0x71, 0x7b, 0xa2, 0x78, 0x7d, 0x17, + 0x3d, 0x51, 0x72, 0xc4, 0xa0, 0x22, 0x56, 0x0b, 0x44, 0xca, 0x3f, 0x23, 0x90, 0xa2, 0xaa, 0x08, + 0x02, 0xeb, 0x30, 0xa2, 0xb3, 0x42, 0xbe, 0x76, 0xb2, 0x93, 0x44, 0x93, 0x7d, 0x73, 0x83, 0x99, + 0xa9, 0x26, 0x44, 0x82, 0x59, 0xb4, 0x61, 0x9d, 0x15, 0xfc, 0x9c, 0xf8, 0x5e, 0x08, 0x72, 0x2f, + 0x87, 0x3c, 0xdb, 0x16, 0xb2, 0x07, 0x24, 0x84, 0xf9, 0x36, 0x24, 0x1b, 0x20, 0xd7, 0xfb, 0x32, + 0x05, 0x23, 0x35, 0x52, 0xca, 0xbb, 0xa0, 0xcb, 0x3b, 0xf9, 0x22, 0xdd, 0xe3, 0xbd, 0x19, 0xd0, + 0x06, 0x6b, 0xa4, 0x94, 0x65, 0x85, 0xdc, 0xce, 0x1a, 0xdd, 0x93, 0x69, 0x44, 0x67, 0x4f, 0x28, + 0xaf, 0xc1, 0x70, 0x88, 0xb2, 0x68, 0x6e, 0x2c, 0xc6, 0x43, 0x41, 0xc6, 0xf2, 0x8f, 0x81, 0xde, + 0xae, 0xd2, 0x12, 0x35, 0x3c, 0x01, 0xd7, 0xa1, 0x66, 0x21, 0xe1, 0x30, 0xc2, 0xaa, 0x9e, 0x38, + 0x46, 0x32, 0xf3, 0xcd, 0x2b, 0xf8, 0xd1, 0x5b, 0x3c, 0x42, 0x13, 0x91, 0xa7, 0x64, 0xd0, 0xdb, + 0xb5, 0x0c, 0x7e, 0x41, 0x30, 0x16, 0x09, 0x55, 0x34, 0x65, 0x13, 0x46, 0xdd, 0xa6, 0x6c, 0xfb, + 0x5b, 0x42, 0x08, 0xd7, 0xe3, 0x80, 0xd6, 0x5c, 0x11, 0x05, 0xd2, 0x9e, 0x9d, 0x14, 0xb6, 0x61, + 0xba, 0xe1, 0x26, 0x73, 0xf6, 0xe7, 0xb4, 0x72, 0x87, 0xad, 0x51, 0xd3, 0x28, 0xb2, 0x4e, 0x74, + 0x81, 0xdf, 0x84, 0x44, 0x91, 0x47, 0x71, 0x48, 0xfd, 0x9a, 0xf8, 0x25, 0x6f, 0xc0, 0x4c, 0xbb, + 0x2a, 0xa2, 0x4f, 0xd7, 0x60, 0xa8, 0x66, 0x33, 0xd3, 0x32, 0xf2, 0x65, 0x77, 0x9f, 0x17, 0xe9, + 0xd7, 0x06, 0xbd, 0x35, 0x1e, 0x22, 0x6f, 0xc0, 0xf5, 0x86, 0x64, 0x77, 0xab, 0x95, 0x0a, 0xb5, + 0x18, 0x3f, 0xd0, 0x91, 0x92, 0xf5, 0x08, 0xfe, 0xe1, 0x64, 0x02, 0x98, 0x4f, 0x0d, 0x05, 0xa9, + 0x35, 0x00, 0xee, 0x6d, 0x04, 0xfc, 0x15, 0x82, 0x59, 0x5e, 0xe4, 0x4e, 0x81, 0x99, 0x35, 0x1a, + 0x32, 0x8a, 0xd3, 0x6d, 0x6e, 0x56, 0xe6, 0xac, 0x74, 0xfa, 0x3b, 0x82, 0xb9, 0xf6, 0x58, 0x04, + 0x67, 0xad, 0x89, 0x79, 0xbd, 0x15, 0xe3, 0x53, 0x7e, 0x64, 0xb2, 0xe2, 0x26, 0x65, 0xe4, 0x7f, + 0x33, 0xb1, 0x71, 0xf1, 0xc1, 0x71, 0x22, 0x84, 0xd1, 0xed, 0x50, 0x23, 0xe5, 0x25, 0xb8, 0x1a, + 0xbd, 0xdd, 0xfa, 0x3e, 0xe5, 0x6f, 0x11, 0x4c, 0x35, 0x28, 0x22, 0xc2, 0x7c, 0x62, 0x7d, 0x0f, + 0x67, 0x75, 0x6b, 0x2f, 0x50, 0x84, 0xe6, 0xa3, 0x6c, 0xe6, 0x33, 0xb8, 0x12, 0xb0, 0x19, 0xbb, + 0x12, 0x61, 0x38, 0x4a, 0x5b, 0xc3, 0x09, 0xa7, 0xbe, 0xec, 0x5b, 0x4f, 0x68, 0xe3, 0xec, 0x6e, + 0x72, 0xdd, 0x7f, 0x4d, 0x02, 0x96, 0x27, 0xfa, 0x7c, 0x13, 0x2e, 0x0a, 0x90, 0x79, 0xb6, 0x97, + 0x2f, 0x12, 0xa7, 0x18, 0x68, 0xf6, 0x05, 0xb1, 0xf5, 0x70, 0x6f, 0x8d, 0x38, 0x45, 0xf7, 0x7b, + 0x7e, 0xd9, 0x17, 0xf5, 0x64, 0x04, 0x6c, 0x38, 0xe1, 0xdd, 0x18, 0x4f, 0x30, 0x94, 0x5d, 0xfa, + 0xfb, 0x70, 0x22, 0x63, 0x98, 0xac, 0x58, 0xd5, 0x95, 0x82, 0xbd, 0xab, 0x8a, 0xd6, 0x14, 0x8a, + 0xc4, 0xb4, 0xea, 0x3f, 0x54, 0xb6, 0x5f, 0xa6, 0x8e, 0x92, 0xbd, 0x9f, 0x5b, 0x7c, 0x7b, 0x21, + 0x57, 0xd5, 0x37, 0xe8, 0xbe, 0x76, 0x4e, 0x77, 0xaf, 0x18, 0x7f, 0x0a, 0xa3, 0x01, 0x11, 0x94, + 0x4c, 0xc7, 0x35, 0xbe, 0xbe, 0x57, 0xc8, 0x3b, 0x54, 0x57, 0xcf, 0x03, 0xd3, 0xe1, 0xde, 0xe2, + 0x30, 0x52, 0x61, 0x79, 0xa1, 0xd4, 0x3e, 0xcf, 0x5b, 0xf8, 0x9a, 0x27, 0x67, 0x3c, 0x0e, 0x40, + 0xad, 0xed, 0xfa, 0x81, 0x7e, 0x7e, 0x60, 0x80, 0x5a, 0x42, 0xed, 0x78, 0x0c, 0x06, 0x98, 0xcd, + 0x48, 0x29, 0xef, 0x10, 0x96, 0x3c, 0xc7, 0x77, 0xcf, 0xf3, 0x85, 0x2d, 0xc2, 0x63, 0xfd, 0xd6, + 0x26, 0x13, 0xbc, 0xa3, 0x03, 0x27, 0x1d, 0xc5, 0xd3, 0x30, 0x52, 0xdf, 0x76, 0x0a, 0x15, 0xb3, + 0xcc, 0x92, 0xaf, 0xf1, 0x23, 0xc3, 0x62, 0x75, 0x8b, 0x2f, 0xba, 0x1f, 0x12, 0xe1, 0x5e, 0x92, + 0x3c, 0x3f, 0x89, 0xe6, 0xce, 0x6b, 0xe2, 0x17, 0x7e, 0x04, 0xaf, 0x57, 0x2d, 0x5f, 0x7e, 0x79, + 0xd3, 0x7a, 0x6c, 0x27, 0x07, 0xb8, 0x4a, 0x5a, 0x3c, 0xd4, 0x1f, 0x05, 0x42, 0xee, 0x5b, 0x8f, + 0x6d, 0xed, 0x42, 0xf5, 0xd4, 0x4a, 0xe6, 0xeb, 0x51, 0x38, 0xc7, 0xaf, 0x18, 0x7f, 0x89, 0x20, + 0xe1, 0x4d, 0x7e, 0xf8, 0x46, 0x93, 0x94, 0x8d, 0xa3, 0xa6, 0x34, 0x1f, 0xe7, 0xa8, 0xa7, 0x17, + 0x79, 0xfa, 0x8b, 0x3f, 0xfe, 0xf9, 0xae, 0x77, 0x02, 0x8f, 0xab, 0xad, 0x26, 0x60, 0xfc, 0x3d, + 0x82, 0xe1, 0x90, 0x95, 0xe2, 0x85, 0x56, 0x45, 0xa2, 0x06, 0x52, 0x29, 0xdd, 0x41, 0x84, 0x40, + 0x77, 0x93, 0xa3, 0x9b, 0xc5, 0xd3, 0x6a, 0xd3, 0xd9, 0x3b, 0x60, 0xde, 0xf8, 0x37, 0x04, 0x43, + 0xc1, 0x44, 0x58, 0x8d, 0x5b, 0xb2, 0x8e, 0x71, 0x21, 0x7e, 0x80, 0x80, 0xb8, 0xc6, 0x21, 0x66, + 0xf1, 0x7b, 0xb1, 0x20, 0xaa, 0x4f, 0xc3, 0x9e, 0x7a, 0xa0, 0x9e, 0xec, 0xe1, 0x1f, 0x10, 0x8c, + 0x84, 0x87, 0x2b, 0xdc, 0xae, 0x65, 0x8d, 0xb6, 0x2d, 0x65, 0x3a, 0x09, 0x11, 0x1c, 0x14, 0xce, + 0x61, 0x0e, 0xcf, 0xb4, 0xe0, 0x10, 0xf0, 0x59, 0xfc, 0x27, 0x82, 0xb1, 0x16, 0xcf, 0x2b, 0xbe, + 0xd5, 0x0a, 0x43, 0xfb, 0x19, 0x41, 0xba, 0xdd, 0x75, 0xbc, 0x20, 0xb4, 0xc4, 0x09, 0x2d, 0x60, + 0x25, 0xe6, 0xa5, 0x78, 0xee, 0x72, 0x80, 0xff, 0x45, 0x70, 0xa5, 0xe9, 0x08, 0x87, 0x57, 0xe2, + 0x8a, 0x23, 0x6a, 0xbe, 0x94, 0xde, 0xed, 0x32, 0x5a, 0x50, 0xda, 0xe4, 0x94, 0xee, 0xe1, 0xf7, + 0xbb, 0xd4, 0x19, 0x1f, 0xde, 0x7c, 0xa6, 0x2f, 0x10, 0x24, 0x9b, 0x8d, 0x84, 0x78, 0x39, 0x2e, + 0xd4, 0x88, 0xa9, 0x54, 0x5a, 0xe9, 0x2e, 0x58, 0xd0, 0x5c, 0xe5, 0x34, 0x6f, 0xe1, 0x95, 0x57, + 0xa1, 0x89, 0x7f, 0x42, 0x30, 0x7a, 0x6a, 0x2e, 0xc2, 0x99, 0xb6, 0xa2, 0x6a, 0x98, 0xb1, 0xa4, + 0xc5, 0x8e, 0x62, 0x04, 0x05, 0x95, 0x53, 0xb8, 0x81, 0x67, 0x9b, 0x50, 0x20, 0xf5, 0x38, 0xf1, + 0xa8, 0xe1, 0x43, 0x04, 0x97, 0x9b, 0xcc, 0x3d, 0xf8, 0x9d, 0xb8, 0xdd, 0x8c, 0xb0, 0x82, 0xe5, + 0xae, 0x62, 0x05, 0x8b, 0x75, 0xce, 0x62, 0x15, 0x67, 0xbb, 0xbc, 0x88, 0xa0, 0x5f, 0xfc, 0xea, + 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, 0xc3, 0x98, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0x40, 0x4b, + 0x01, 0x98, 0xea, 0xd3, 0x88, 0x39, 0xec, 0x20, 0xfb, 0xe0, 0xd9, 0x51, 0x0a, 0x3d, 0x3f, 0x4a, + 0xa1, 0x97, 0x47, 0x29, 0xf4, 0xcd, 0x71, 0xaa, 0xe7, 0xf9, 0x71, 0xaa, 0xe7, 0xaf, 0xe3, 0x54, + 0xcf, 0x27, 0x6d, 0xe7, 0x9f, 0xbd, 0x60, 0x41, 0x3e, 0x0c, 0xe9, 0x09, 0xfe, 0x3f, 0xa2, 0xc5, + 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x69, 0xcd, 0x35, 0xac, 0x0b, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2390,17 +2389,19 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x18 } - if m.ValBtcPk != nil { - { - size := m.ValBtcPk.Size() - i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.ValBtcPkList) > 0 { + for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.ValBtcPkList[iNdEx].Size() + i -= size + if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) } - i = encodeVarintQuery(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 } if m.BtcPk != nil { { @@ -2712,9 +2713,11 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovQuery(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() - n += 1 + l + sovQuery(uint64(l)) + if len(m.ValBtcPkList) > 0 { + for _, e := range m.ValBtcPkList { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } } if m.StartHeight != 0 { n += 1 + sovQuery(uint64(m.StartHeight)) @@ -4551,7 +4554,7 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -4579,8 +4582,8 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.ValBtcPkList = append(m.ValBtcPkList, v) + if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex From 7e54265d7975b3da0ef7c95595944531195e015f Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 22 Nov 2023 08:10:31 +0100 Subject: [PATCH 106/202] Update delegation/undelegation to new btc primitives (#109) - Update delegation and undelegation to new btc transactions format --- btcstaking/staking.go | 511 +-------- btcstaking/staking_test.go | 226 +--- proto/babylon/btcstaking/v1/btcstaking.proto | 51 +- proto/babylon/btcstaking/v1/query.proto | 10 +- proto/babylon/btcstaking/v1/tx.proto | 53 +- test/e2e/btc_staking_e2e_test.go | 197 +++- .../configurer/chain/commands_btcstaking.go | 48 +- testutil/datagen/btcstaking.go | 192 ++-- x/btcstaking/client/cli/tx.go | 172 +-- x/btcstaking/keeper/btc_delegators.go | 35 +- x/btcstaking/keeper/grpc_query.go | 8 +- x/btcstaking/keeper/grpc_query_test.go | 15 +- x/btcstaking/keeper/incentive_test.go | 2 + x/btcstaking/keeper/msg_server.go | 574 ++++++---- x/btcstaking/keeper/msg_server_test.go | 222 ++-- .../keeper/voting_power_table_test.go | 8 + x/btcstaking/types/btc_slashing_tx.go | 81 +- x/btcstaking/types/btc_slashing_tx_test.go | 48 +- x/btcstaking/types/btcstaking.go | 192 +--- x/btcstaking/types/btcstaking.pb.go | 688 ++++------- x/btcstaking/types/btcstaking_test.go | 20 +- x/btcstaking/types/codec.go | 2 - x/btcstaking/types/errors.go | 3 + x/btcstaking/types/msg.go | 42 +- x/btcstaking/types/query.pb.go | 241 ++-- x/btcstaking/types/tx.pb.go | 1006 ++++++----------- x/btcstaking/types/types.go | 60 + 27 files changed, 1715 insertions(+), 2992 deletions(-) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 936729e59..c6aae1387 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "fmt" - "math" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -17,8 +16,6 @@ import ( ) const ( - // we expect signatures from 3 signers - expectedMultiSigSigners = 3 // Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs // using it as internal public key efectively disables taproot key spends @@ -45,88 +42,6 @@ func unspendableKeyPathInternalPubKeyInternal(keyHex string) btcec.PublicKey { return *pubKey } -// Following methods are copied from btcd. In most recent they are not exported. -// TODO: on btcd master those are already exported. Remove this copies -// when this will be released. -func isSmallInt(op byte) bool { - return op == txscript.OP_0 || (op >= txscript.OP_1 && op <= txscript.OP_16) -} - -func asSmallInt(op byte) int { - if op == txscript.OP_0 { - return 0 - } - - return int(op - (txscript.OP_1 - 1)) -} - -func checkMinimalDataEncoding(v []byte) error { - if len(v) == 0 { - return nil - } - - // Check that the number is encoded with the minimum possible - // number of bytes. - // - // If the most-significant-byte - excluding the sign bit - is zero - // then we're not minimal. Note how this test also rejects the - // negative-zero encoding, [0x80]. - if v[len(v)-1]&0x7f == 0 { - // One exception: if there's more than one byte and the most - // significant bit of the second-most-significant-byte is set - // it would conflict with the sign bit. An example of this case - // is +-255, which encode to 0xff00 and 0xff80 respectively. - // (big-endian). - if len(v) == 1 || v[len(v)-2]&0x80 == 0 { - return fmt.Errorf("numeric value encoded as %x is "+ - "not minimally encoded", v) - } - } - - return nil -} - -func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (int64, error) { - // Interpreting data requires that it is not larger than - // the the passed scriptNumLen value. - if len(v) > scriptNumLen { - return 0, fmt.Errorf("numeric value encoded as %x is %d bytes "+ - "which exceeds the max allowed of %d", v, len(v), - scriptNumLen) - } - - // Enforce minimal encoded if requested. - if requireMinimal { - if err := checkMinimalDataEncoding(v); err != nil { - return 0, err - } - } - - // Zero is encoded as an empty byte slice. - if len(v) == 0 { - return 0, nil - } - - // Decode from little endian. - var result int64 - for i, val := range v { - result |= int64(val) << uint8(8*i) - } - - // When the most significant byte of the input bytes has the sign bit - // set, the result is negative. So, remove the sign bit from the result - // and make it negative. - if v[len(v)-1]&0x80 != 0 { - // The maximum length of v has already been determined to be 4 - // above, so uint8 is enough to cover the max possible shift - // value of 24. - result &= ^(int64(0x80) << uint8(8*(len(v)-1))) - return -result, nil - } - - return result, nil -} - // StakingScriptData is a struct that holds data parsed from staking script type StakingScriptData struct { StakerKey *btcec.PublicKey @@ -135,16 +50,6 @@ type StakingScriptData struct { StakingTime uint16 } -// StakingOutputInfo holds info about whole staking output: -// - data derived from the script -// - staking amount in staking output -// - staking pk script -type StakingOutputInfo struct { - StakingScriptData *StakingScriptData - StakingAmount btcutil.Amount - StakingPkScript []byte -} - func NewStakingScriptData( stakerKey, validatorKey, @@ -163,331 +68,10 @@ func NewStakingScriptData( }, nil } -// BuildStakingScript builds a staking script in the following format: -// OP_CHECKSIG -// OP_NOTIF -// -// OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL -// -// OP_ELSE -// -// OP_CHECKSEQUENCEVERIFY -// -// OP_ENDIF -func (sd *StakingScriptData) BuildStakingScript() ([]byte, error) { - builder := txscript.NewScriptBuilder() - builder.AddData(schnorr.SerializePubKey(sd.StakerKey)) - builder.AddOp(txscript.OP_CHECKSIG) - builder.AddOp(txscript.OP_NOTIF) - builder.AddData(schnorr.SerializePubKey(sd.StakerKey)) - builder.AddOp(txscript.OP_CHECKSIG) - builder.AddData(schnorr.SerializePubKey(sd.ValidatorKey)) - builder.AddOp(txscript.OP_CHECKSIGADD) - builder.AddData(schnorr.SerializePubKey(sd.CovenantKey)) - builder.AddOp(txscript.OP_CHECKSIGADD) - builder.AddInt64(expectedMultiSigSigners) - builder.AddOp(txscript.OP_NUMEQUAL) - builder.AddOp(txscript.OP_ELSE) - builder.AddInt64(int64(sd.StakingTime)) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) - builder.AddOp(txscript.OP_ENDIF) - return builder.Script() - -} - -// ParseStakingTransactionScript parses provided script. If script is not a valid staking script -// error is returned. If script is valid, StakingScriptData is returned, which contains all -// relevant data parsed from the script. -// Only stateless checks are performed. -func ParseStakingTransactionScript(script []byte) (*StakingScriptData, error) { - // OP_CHECKSIG - // OP_NOTIF - // - // OP_CHECKSIG OP_CHECKSIGADD OP_CHECKSIGADD 3 OP_NUMEQUAL - // - // OP_ELSE - // - // OP_CHECKSEQUENCEVERIFY - // - // OP_ENDIF - type templateMatch struct { - expectCanonicalInt bool - maxIntBytes int - opcode byte - extractedInt int64 - extractedData []byte - } - var template = [15]templateMatch{ - {opcode: txscript.OP_DATA_32}, - {opcode: txscript.OP_CHECKSIG}, - {opcode: txscript.OP_NOTIF}, - {opcode: txscript.OP_DATA_32}, - {opcode: txscript.OP_CHECKSIG}, - {opcode: txscript.OP_DATA_32}, - {opcode: txscript.OP_CHECKSIGADD}, - {opcode: txscript.OP_DATA_32}, - {opcode: txscript.OP_CHECKSIGADD}, - {expectCanonicalInt: true, maxIntBytes: 4}, - {opcode: txscript.OP_NUMEQUAL}, - {opcode: txscript.OP_ELSE}, - {expectCanonicalInt: true, maxIntBytes: 4}, - {opcode: txscript.OP_CHECKSEQUENCEVERIFY}, - {opcode: txscript.OP_ENDIF}, - } - - var templateOffset int - tokenizer := txscript.MakeScriptTokenizer(0, script) - - for tokenizer.Next() { - // Not an staking script if it has more opcodes than expected in the - // template. - if templateOffset >= len(template) { - return nil, nil - } - - op := tokenizer.Opcode() - data := tokenizer.Data() - tplEntry := &template[templateOffset] - if tplEntry.expectCanonicalInt { - switch { - case data != nil: - val, err := makeScriptNum(data, true, tplEntry.maxIntBytes) - if err != nil { - return nil, err - } - tplEntry.extractedInt = int64(val) - - case isSmallInt(op): - tplEntry.extractedInt = int64(asSmallInt(op)) - - // Not an staking script if this is not int - default: - return nil, nil - } - } else { - if op != tplEntry.opcode { - return nil, nil - } - - tplEntry.extractedData = data - } - - templateOffset++ - } - if err := tokenizer.Err(); err != nil { - return nil, err - } - if !tokenizer.Done() || templateOffset != len(template) { - return nil, nil - } - - // At this point, the script appears to be an valid staking script. Extract relevant data and perform - // some initial validations. - - // Staker public key from the path without multisig i.e path where sats are locked - // for staking duration - stakerPk1, err := schnorr.ParsePubKey(template[0].extractedData) - if err != nil { - return nil, err - } - - // Staker public key from the path with multisig - if _, err := schnorr.ParsePubKey(template[3].extractedData); err != nil { - return nil, err - } - - if !bytes.Equal(template[0].extractedData, template[3].extractedData) { - return nil, fmt.Errorf("staker public key on lock path and multisig path are different") - } - - // Delegator public key - validatorPk, err := schnorr.ParsePubKey(template[5].extractedData) - - if err != nil { - return nil, err - } - - // Covenant public key - covenantPk, err := schnorr.ParsePubKey(template[7].extractedData) - - if err != nil { - return nil, err - } - - // validate number of mulitsig signers - if template[9].extractedInt != expectedMultiSigSigners { - return nil, fmt.Errorf("expected %d multisig signers, got %d", expectedMultiSigSigners, template[9].extractedInt) - } - - // validate staking time - if template[12].extractedInt < 0 || template[12].extractedInt > math.MaxUint16 { - return nil, fmt.Errorf("invalid staking time %d", template[12].extractedInt) - } - - // we do not need to check error here, as we already validated that all public keys are not nil - scriptData, err := NewStakingScriptData( - stakerPk1, - validatorPk, - covenantPk, - uint16(template[12].extractedInt), - ) - - if err != nil { - panic(fmt.Sprintf("unexpected error: %v", err)) - } - - return scriptData, nil -} - func UnspendableKeyPathInternalPubKey() btcec.PublicKey { return unspendableKeyPathKey } -// TaprootAddressForScript returns a Taproot address commiting to the given script, built taproot tree -// has only one leaf node. -func TaprootAddressForScript( - script []byte, - internalPubKey *btcec.PublicKey, - net *chaincfg.Params) (*btcutil.AddressTaproot, error) { - - tapLeaf := txscript.NewBaseTapLeaf(script) - - tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) - - tapScriptRootHash := tapScriptTree.RootNode.TapHash() - - outputKey := txscript.ComputeTaprootOutputKey( - internalPubKey, tapScriptRootHash[:], - ) - - address, err := btcutil.NewAddressTaproot( - schnorr.SerializePubKey(outputKey), net) - - if err != nil { - return nil, fmt.Errorf("error encoding Taproot address: %v", err) - } - - return address, nil -} - -// BuildUnspendableTaprootPkScript builds taproot pkScript which commits to the provided script with -// unspendable spending key path. -func BuildUnspendableTaprootPkScript(rawScript []byte, net *chaincfg.Params) ([]byte, error) { - internalPubKey := UnspendableKeyPathInternalPubKey() - - address, err := TaprootAddressForScript(rawScript, &internalPubKey, net) - - if err != nil { - return nil, err - } - - pkScript, err := txscript.PayToAddrScript(address) - - if err != nil { - return nil, err - } - - return pkScript, nil -} - -// BuildStakingOutput builds out which is necessary for staking transaction to stake funds. -func BuildStakingOutput( - stakerKey, - validatorKey, - covenantKey *btcec.PublicKey, - stTime uint16, - stAmount btcutil.Amount, - net *chaincfg.Params) (*wire.TxOut, []byte, error) { - - sd, err := NewStakingScriptData(stakerKey, validatorKey, covenantKey, stTime) - - if err != nil { - return nil, nil, err - } - - script, err := sd.BuildStakingScript() - - if err != nil { - return nil, nil, err - } - - pkScript, err := BuildUnspendableTaprootPkScript(script, net) - - if err != nil { - return nil, nil, err - } - - return wire.NewTxOut(int64(stAmount), pkScript), script, nil -} - -// NewWitnessFromStakingScriptAndSignature creates witness for spending -// staking from the given staking script and the given signature of -// a single party in the multisig -func NewWitnessFromStakingScriptAndSignature( - stakingScript []byte, - sig *schnorr.Signature, -) (wire.TxWitness, error) { - // get ctrlBlockBytes - internalPubKey := UnspendableKeyPathInternalPubKey() - tapLeaf := txscript.NewBaseTapLeaf(stakingScript) - tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) - ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(&internalPubKey) - ctrlBlockBytes, err := ctrlBlock.ToBytes() - if err != nil { - return nil, err - } - - // compose witness stack - witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = sig.Serialize() - witnessStack[1] = stakingScript - witnessStack[2] = ctrlBlockBytes - return witnessStack, nil -} - -// BuildWitnessToSpendStakingOutput builds witness for spending staking as single staker -// Current assumptions: -// - staking output is the only input to the transaction -// - staking output is valid staking output -func BuildWitnessToSpendStakingOutput( - slashingMsgTx *wire.MsgTx, // slashing tx - stakingOutput *wire.TxOut, - stakingScript []byte, - privKey *btcec.PrivateKey, -) (wire.TxWitness, error) { - tapLeaf := txscript.NewBaseTapLeaf(stakingScript) - sig, err := SignTxWithOneScriptSpendInputFromTapLeaf(slashingMsgTx, stakingOutput, privKey, tapLeaf) - if err != nil { - return nil, err - } - - return NewWitnessFromStakingScriptAndSignature(stakingScript, sig) -} - -// ValidateStakingOutputPkScript validates that: -// - provided output commits to the given script with unspendable spending key path -// - provided script is valid staking script -func ValidateStakingOutputPkScript( - output *wire.TxOut, - script []byte, - net *chaincfg.Params) (*StakingScriptData, error) { - if output == nil { - return nil, fmt.Errorf("provided output cannot be nil") - } - - pkScript, err := BuildUnspendableTaprootPkScript(script, net) - - if err != nil { - return nil, err - } - - if !bytes.Equal(output.PkScript, pkScript) { - return nil, fmt.Errorf("output does not commit to the given script") - } - - return ParseStakingTransactionScript(script) -} - // BuildSlashingTxFromOutpoint builds a valid slashing transaction by creating a new Bitcoin transaction that slashes a portion // of staked funds and directs them to a specified slashing address. The transaction also includes a change output sent back to // the specified change address. The slashing rate determines the proportion of staked funds to be slashed. @@ -660,7 +244,6 @@ func BuildSlashingTxFromStakingTxStrict( slashingAddress, changeAddress btcutil.Address, fee int64, slashingRate sdk.Dec, - script []byte, net *chaincfg.Params, ) (*wire.MsgTx, error) { // Get the staking output at the specified index from the staking transaction @@ -669,11 +252,6 @@ func BuildSlashingTxFromStakingTxStrict( return nil, err } - // Validate that the staking output commits to the provided script and the script is valid - if _, err = ValidateStakingOutputPkScript(stakingOutput, script, net); err != nil { - return nil, err - } - // Create an OutPoint for the staking output stakingTxHash := stakingTx.TxHash() stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) @@ -734,6 +312,7 @@ func IsSimpleTransfer(tx *wire.MsgTx) error { // - the first output must pay to the provided slashing address. // - the first output must pay at least (staking output value * slashing rate) to the slashing address. // - neither of the outputs are considered dust. +// // - the min fee for slashing tx is preserved func ValidateSlashingTx( slashingTx *wire.MsgTx, @@ -819,43 +398,7 @@ func ValidateSlashingTx( return nil } -// GetIdxOutputCommitingToScript retrieves index of the output committing to the provided script. -// It returns error if: -// - tx is nil -// - tx does not have output committing to the provided script -// - tx has more than one output committing to the provided script -func GetIdxOutputCommitingToScript( - tx *wire.MsgTx, - script []byte, - net *chaincfg.Params) (int, error) { - - if tx == nil { - return -1, fmt.Errorf("provided staking transaction must not be nil") - } - - script, err := BuildUnspendableTaprootPkScript(script, net) - - if err != nil { - return -1, err - } - - var comittingOutputIdx int = -1 - for i, out := range tx.TxOut { - if bytes.Equal(out.PkScript, script) && comittingOutputIdx < 0 { - comittingOutputIdx = i - } else if bytes.Equal(out.PkScript, script) && comittingOutputIdx >= 0 { - return -1, fmt.Errorf("transaction has more than one output committing to the provided script") - } - } - - if comittingOutputIdx < 0 { - return -1, fmt.Errorf("transaction does not have output committing to the provided script") - } - return comittingOutputIdx, nil -} - // CheckTransactions validates all relevant data of slashing and funding transaction. -// - funding transaction script is valid // - funding transaction has output committing to the provided script // - slashing transaction is valid // - slashing transaction input hash is pointing to funding transaction hash @@ -863,60 +406,47 @@ func GetIdxOutputCommitingToScript( func CheckTransactions( slashingTx *wire.MsgTx, fundingTransaction *wire.MsgTx, + fundingOutputIdx uint32, slashingTxMinFee int64, slashingRate sdk.Dec, slashingAddress btcutil.Address, - script []byte, net *chaincfg.Params, -) (*StakingOutputInfo, error) { +) error { // Check if slashing tx min fee is valid if slashingTxMinFee <= 0 { - return nil, fmt.Errorf("slashing transaction min fee must be larger than 0") + return fmt.Errorf("slashing transaction min fee must be larger than 0") } // Check if slashing rate is in the valid range (0,1) if !IsSlashingRateValid(slashingRate) { - return nil, ErrInvalidSlashingRate + return ErrInvalidSlashingRate } - // 1. Check if the staking script is valid and extract data from it - scriptData, err := ParseStakingTransactionScript(script) - if err != nil { - return nil, err + if fundingOutputIdx >= uint32(len(fundingTransaction.TxOut)) { + return fmt.Errorf("invalid funding output index %d, tx has %d outputs", fundingOutputIdx, len(fundingTransaction.TxOut)) } - // 2. Check that staking transaction has output committing to the provided script - stakingOutputIdx, err := GetIdxOutputCommitingToScript(fundingTransaction, script, net) - if err != nil { - return nil, err - } - - stakingOutput := fundingTransaction.TxOut[stakingOutputIdx] + stakingOutput := fundingTransaction.TxOut[fundingOutputIdx] // 3. Check if slashing transaction is valid if err := ValidateSlashingTx( slashingTx, slashingAddress, slashingRate, slashingTxMinFee, stakingOutput.Value); err != nil { - return nil, err + return err } // 4. Check that slashing transaction input is pointing to staking transaction stakingTxHash := fundingTransaction.TxHash() if !slashingTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { - return nil, fmt.Errorf("slashing transaction must spend staking output") + return fmt.Errorf("slashing transaction must spend staking output") } // 5. Check that index of the fund output matches index of the input in slashing transaction - if slashingTx.TxIn[0].PreviousOutPoint.Index != uint32(stakingOutputIdx) { - return nil, fmt.Errorf("slashing transaction input must spend staking output") + if slashingTx.TxIn[0].PreviousOutPoint.Index != fundingOutputIdx { + return fmt.Errorf("slashing transaction input must spend staking output") } - - return &StakingOutputInfo{ - StakingScriptData: scriptData, - StakingAmount: btcutil.Amount(stakingOutput.Value), - StakingPkScript: stakingOutput.PkScript, - }, nil + return nil } func signTxWithOneScriptSpendInputFromTapLeafInternal( @@ -1008,8 +538,9 @@ func SignTxWithOneScriptSpendInputFromScript( func SignTxWithOneScriptSpendInputStrict( txToSign *wire.MsgTx, fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, privKey *btcec.PrivateKey, - script []byte, net *chaincfg.Params, ) (*schnorr.Signature, error) { @@ -1021,10 +552,8 @@ func SignTxWithOneScriptSpendInputStrict( return nil, fmt.Errorf("tx to sign must have exactly one input") } - scriptIdx, err := GetIdxOutputCommitingToScript(fundingTx, script, net) - - if err != nil { - return nil, err + if fundingOutputIdx >= uint32(len(fundingTx.TxOut)) { + return nil, fmt.Errorf("invalid funding output index %d, tx has %d outputs", fundingOutputIdx, len(fundingTx.TxOut)) } fundingTxHash := fundingTx.TxHash() @@ -1033,13 +562,13 @@ func SignTxWithOneScriptSpendInputStrict( return nil, fmt.Errorf("txToSign must input point to fundingTx") } - if txToSign.TxIn[0].PreviousOutPoint.Index != uint32(scriptIdx) { + if txToSign.TxIn[0].PreviousOutPoint.Index != uint32(fundingOutputIdx) { return nil, fmt.Errorf("txToSign inpunt index must point to output with provided script") } - fundingOutput := fundingTx.TxOut[scriptIdx] + fundingOutput := fundingTx.TxOut[fundingOutputIdx] - return SignTxWithOneScriptSpendInputFromScript(txToSign, fundingOutput, privKey, script) + return SignTxWithOneScriptSpendInputFromScript(txToSign, fundingOutput, privKey, signedScriptPath) } // VerifyTransactionSigWithOutput verifies that: diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 574f5304b..3ec701e21 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -4,17 +4,13 @@ import ( "math" "math/rand" "testing" - "time" "github.com/babylonchain/babylon/btcstaking" - btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -35,25 +31,6 @@ func genValidStakingScriptData(t *testing.T, r *rand.Rand) *btcstaking.StakingSc return sd } -func FuzzGeneratingParsingValidStakingScript(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - - sd := genValidStakingScriptData(t, r) - - script, err := sd.BuildStakingScript() - require.NoError(t, err) - parsedScript, err := btcstaking.ParseStakingTransactionScript(script) - require.NoError(t, err) - - require.Equal(t, parsedScript.StakingTime, sd.StakingTime) - require.Equal(t, schnorr.SerializePubKey(sd.StakerKey), schnorr.SerializePubKey(parsedScript.StakerKey)) - require.Equal(t, schnorr.SerializePubKey(sd.ValidatorKey), schnorr.SerializePubKey(parsedScript.ValidatorKey)) - require.Equal(t, schnorr.SerializePubKey(sd.CovenantKey), schnorr.SerializePubKey(parsedScript.CovenantKey)) - }) -} - func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -64,27 +41,28 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { // always more outputs than stakingOutputIdx stakingTxNumOutputs := r.Intn(5) + 10 sd := genValidStakingScriptData(t, r) - script, err := sd.BuildStakingScript() - require.NoError(t, err) + minStakingValue := 5000 minFee := 2000 // generate a random slashing rate with random precision, // this will include both valid and invalid ranges, so we can test both cases - randomPrecision := r.Int63n(4) // [0,3] + randomPrecision := r.Int63n(4) // [0,3] slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 1001)), randomPrecision) // [0,1000] / 10^{randomPrecision} for i := 0; i < stakingTxNumOutputs; i++ { if i == stakingOutputIdx { - stakingOutput, _, err := btcstaking.BuildStakingOutput( + info, err := btcstaking.BuildStakingInfo( sd.StakerKey, - sd.ValidatorKey, - sd.CovenantKey, + []*btcec.PublicKey{sd.ValidatorKey}, + []*btcec.PublicKey{sd.CovenantKey}, + 1, sd.StakingTime, btcutil.Amount(r.Intn(5000)+minStakingValue), &chaincfg.MainNetParams, ) + require.NoError(t, err) - stakingTx.AddTxOut(stakingOutput) + stakingTx.AddTxOut(info.StakingOutput) } else { stakingTx.AddTxOut( &wire.TxOut{ @@ -96,11 +74,11 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { } // Always check case with min fee - testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, script, int64(minFee)) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, int64(minFee)) // Check case with some random fee fee := int64(r.Intn(1000) + minFee) - testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, script, fee) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, fee) }) } @@ -109,8 +87,7 @@ func genRandomBTCAddress(r *rand.Rand) (*btcutil.AddressPubKeyHash, error) { return btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) } -func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdk.Dec, - script []byte, fee int64) { +func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdk.Dec, fee int64) { dustThreshold := 546 // in satoshis // Generate random slashing and change addresses @@ -127,7 +104,6 @@ func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOu slashingAddress, changeAddress, fee, slashingRate, - script, &chaincfg.MainNetParams, ) @@ -154,13 +130,13 @@ func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOu require.ErrorIs(t, err, btcstaking.ErrDustOutputFound) } else { require.NoError(t, err) - _, err = btcstaking.CheckTransactions( + err = btcstaking.CheckTransactions( slashingTx, stakingTx, + uint32(stakingOutputIdx), fee, slashingRate, slashingAddress, - script, &chaincfg.MainNetParams, ) require.NoError(t, err) @@ -210,179 +186,3 @@ func FuzzGeneratingSignatureValidation(f *testing.F) { require.NoError(t, err) }) } - -func TestStakingScriptExecutionSingleStaker(t *testing.T) { - const ( - stakingValue = btcutil.Amount(2 * 10e8) - stakingTimeBlocks = 5 - ) - - r := rand.New(rand.NewSource(time.Now().Unix())) - - stakerPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - validatorPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - covenantPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) - require.NoError(t, err) - - stakingOut := &wire.OutPoint{ - Hash: *txid, - Index: 0, - } - - stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( - stakerPrivKey.PubKey(), - validatorPrivKey.PubKey(), - covenantPrivKey.PubKey(), - stakingTimeBlocks, - stakingValue, - &chaincfg.MainNetParams, - ) - - require.NoError(t, err) - - spendStakeTx := wire.NewMsgTx(2) - - spendStakeTx.AddTxIn(wire.NewTxIn(stakingOut, nil, nil)) - - spendStakeTx.AddTxOut( - &wire.TxOut{ - PkScript: []byte("doesn't matter"), - Value: 1 * 10e8, - }, - ) - - // to spend tx as staker, we need to set the sequence number to be >= stakingTimeBlocks - spendStakeTx.TxIn[0].Sequence = stakingTimeBlocks - - witness, err := btcstaking.BuildWitnessToSpendStakingOutput( - spendStakeTx, - stakingOutput, - stakingScript, - stakerPrivKey, - ) - - require.NoError(t, err) - - spendStakeTx.TxIn[0].Witness = witness - - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingOutput.PkScript, stakingOutput.Value, - ) - - newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine( - stakingOutput.PkScript, - spendStakeTx, 0, txscript.StandardVerifyFlags, nil, - txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingOutput.Value, - prevOutputFetcher, - ) - } - btctest.AssertEngineExecution(t, 0, true, newEngine) -} - -func TestStakingScriptExecutionMulitSig(t *testing.T) { - const ( - stakingValue = btcutil.Amount(2 * 10e8) - stakingTimeBlocks = 5 - ) - - r := rand.New(rand.NewSource(time.Now().Unix())) - - stakerPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - validatorPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - covenantPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - - txid, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32)) - require.NoError(t, err) - - stakingOut := &wire.OutPoint{ - Hash: *txid, - Index: 0, - } - - stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( - stakerPrivKey.PubKey(), - validatorPrivKey.PubKey(), - covenantPrivKey.PubKey(), - stakingTimeBlocks, - stakingValue, - &chaincfg.MainNetParams, - ) - - require.NoError(t, err) - - spendStakeTx := wire.NewMsgTx(2) - - spendStakeTx.AddTxIn(wire.NewTxIn(stakingOut, nil, nil)) - - spendStakeTx.AddTxOut( - &wire.TxOut{ - PkScript: []byte("doesn't matter"), - Value: 1 * 10e8, - }, - ) - - witnessStaker, err := btcstaking.BuildWitnessToSpendStakingOutput( - spendStakeTx, - stakingOutput, - stakingScript, - stakerPrivKey, - ) - require.NoError(t, err) - - witnessValidator, err := btcstaking.BuildWitnessToSpendStakingOutput( - spendStakeTx, - stakingOutput, - stakingScript, - validatorPrivKey, - ) - - require.NoError(t, err) - - witnessCovenant, err := btcstaking.BuildWitnessToSpendStakingOutput( - spendStakeTx, - stakingOutput, - stakingScript, - covenantPrivKey, - ) - - require.NoError(t, err) - - // To Construct valid witness, for multisig case we need: - // - covenant signature - witnessCovenant[0] - // - validator signature - witnessValidator[0] - // - staker signature - witnessStaker[0] - // - empty signature - which is just an empty byte array which signals we are going to use multisig. - // This must be signagure on top of the stack. - // - whole script - witnessStaker[1] (any other wittness[1] will work as well) - // - control block - witnessStaker[2] (any other wittness[2] will work as well) - spendStakeTx.TxIn[0].Witness = [][]byte{ - witnessCovenant[0], witnessValidator[0], witnessStaker[0], []byte{}, witnessStaker[1], witnessStaker[2], - } - - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingOutput.PkScript, stakingOutput.Value, - ) - - newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine( - stakingOutput.PkScript, - spendStakeTx, 0, txscript.StandardVerifyFlags, nil, - txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingOutput.Value, - prevOutputFetcher, - ) - } - btctest.AssertEngineExecution(t, 0, true, newEngine) -} diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 81208a591..148b694f7 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -78,26 +78,28 @@ message BTCDelegation { // quantified in satoshi uint64 total_sat = 7; // staking_tx is the staking tx - BabylonBTCTaprootTx staking_tx = 8; + bytes staking_tx = 8; + // staking_output_idx is the index of the staking output in the staking tx + uint32 staking_output_idx = 9; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. - bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + bytes slashing_tx = 10 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_sig is the signature signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - bytes covenant_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_sig = 12 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object - BTCUndelegation btc_undelegation = 12; + BTCUndelegation btc_undelegation = 13; } // BTCUndelegation signalizes that the delegation is being undelegated @@ -105,31 +107,28 @@ message BTCUndelegation { // unbonding_tx is the transaction which will transfer the funds from staking // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. - BabylonBTCTaprootTx unbonding_tx = 1; + bytes unbonding_tx = 1; + // unbonding_time describes how long the funds will be locked in the unbonding output + uint32 unbonding_time = 2; // slashing_tx is the slashing tx for unbodning transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. - bytes slashing_tx = 2 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbodning tx output. - bytes delegator_slashing_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - bytes covenant_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_slashing_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - bytes covenant_unbonding_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon - // TODO: this is no longer needed - bytes validator_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } @@ -138,20 +137,13 @@ message BTCUndelegationInfo { // unbonding_tx is the transaction which will transfer the funds from staking // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. - BabylonBTCTaprootTx unbonding_tx = 1; - - // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) - // It must be provided after processing undelagate message by Babylon - // It will be nil if validator didn't sign it yet - // TODO: this is no longer needed - bytes validator_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes unbonding_tx = 1; // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // it will be nil if covenant didn't sign it yet // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - bytes covenant_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes covenant_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. @@ -164,15 +156,6 @@ message BTCDelegatorDelegationIndex { repeated bytes staking_tx_hash_list = 1; } -// BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and -// transaction which commits to the provided script. -message BabylonBTCTaprootTx { - // tx is the transaction bytes - bytes tx = 1; - // script is the script recognized by Babylon - bytes script = 2; -} - // BTCDelegationStatus is the status of a delegation. There are two possible state // transition paths: // 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path when timelock of staking @@ -201,4 +184,4 @@ enum BTCDelegationStatus { message SignatureInfo { bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; bytes sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; -} \ No newline at end of file +} diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index f24c669b7..978ae855f 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -234,13 +234,11 @@ message QueryBTCDelegationResponse { // total_sat is the total amount of BTC stakes in this delegation // quantified in satoshi uint64 total_sat = 5; - // staking_tx is the staking tx - string staking_tx = 6; - // staking_script is the script commited to in staking output - string staking_script = 7; + // staking_tx_hex is the hex string of staking tx + string staking_tx_hex = 6; // whether this delegation is active - bool active = 8; + bool active = 7; // undelegation_info is the undelegation info of this delegation. It is nil // if it is not undelegating - BTCUndelegationInfo undelegation_info = 9; + BTCUndelegationInfo undelegation_info = 8; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 035dbadb7..2ca0eea31 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -6,7 +6,6 @@ import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "cosmos/crypto/secp256k1/keys.proto"; import "babylon/btcstaking/v1/params.proto"; -import "babylon/btcstaking/v1/btcstaking.proto"; import "babylon/btccheckpoint/v1/btccheckpoint.proto"; import "cosmos/staking/v1beta1/staking.proto"; import "babylon/btcstaking/v1/pop.proto"; @@ -27,8 +26,6 @@ service Msg { // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker rpc AddCovenantUnbondingSigs(MsgAddCovenantUnbondingSigs) returns (MsgAddCovenantUnbondingSigsResponse); - // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker - rpc AddValidatorUnbondingSig(MsgAddValidatorUnbondingSig) returns (MsgAddValidatorUnbondingSigResponse); // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } @@ -62,34 +59,45 @@ message MsgCreateBTCDelegation { cosmos.crypto.secp256k1.PubKey babylon_pk = 2; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // staking_tx is the staking tx - BabylonBTCTaprootTx staking_tx = 4; - // staking_tx_info is the tx info of the staking tx, including the Merkle proof - babylon.btccheckpoint.v1.TransactionInfo staking_tx_info = 5; + // staker_btc_pk is the Bitcoin secp256k1 PK of the BTC staker + bytes staker_btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one + // validator pk it means that delegation is re-staked + repeated bytes val_btc_pk_list = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // staking_time is the time lock used in staking transaction + uint32 staking_time = 6; + // staking_value is amout of satoshis locked in staking output + int64 staking_value = 7; + // staking_tx is the staking tx along with the merekle proof of inclusion in btc block + babylon.btccheckpoint.v1.TransactionInfo staking_tx = 8; // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. - bytes slashing_tx = 6 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from covenant and validator in // order to be spendable. - bytes delegator_sig = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation message MsgCreateBTCDelegationResponse {} - // MsgBTCUndelegate is the message undelegating existing and active delegation message MsgBTCUndelegate { string signer = 1; // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output - BabylonBTCTaprootTx unbonding_tx = 2; + bytes unbonding_tx = 2; + // unbonding_time is the time lock used in unbonding transaction + uint32 unbonding_time = 3; + // unbonding_value is amount of satoshis locked in unbonding output. + // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding + int64 unbonding_value = 4; // slashing_tx is the slashing tx which slash unbonding contract // Note that the tx itself does not contain signatures, which are off-chain. - bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + bytes slashing_tx = 5 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). - bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_slashing_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgBtcUndelegateResponse is the response for MsgBtcUndelegate @@ -155,22 +163,3 @@ message MsgAddCovenantUnbondingSigs { } // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddCovenantUnbondingSigsResponse {} - -// MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker -message MsgAddValidatorUnbondingSig { - string signer = 1; - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - string staking_tx_hash = 4; - // unbonding_tx_sig is the signature of validator for unbodning tx submitted to babylon by staker - // the signature follows encoding in BIP-340 spec - bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; -} -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -message MsgAddValidatorUnbondingSigResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index eaff8c122..e7529b6ca 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -1,7 +1,6 @@ package e2e import ( - "encoding/hex" "math" "math/rand" "time" @@ -18,6 +17,7 @@ import ( ftypes "github.com/babylonchain/babylon/x/finality/types" itypes "github.com/babylonchain/babylon/x/incentive/types" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -111,24 +111,30 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.NoError(err) // generate staking tx and slashing tx stakingTimeBlocks := uint16(math.MaxUint16) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingTx( r, + s.T(), net, delBTCSK, []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, covenantBTCPKs, + 1, stakingTimeBlocks, stakingValue, params.SlashingAddress, changeAddress.String(), params.SlashingRate, ) + + stakingMsgTx := testStakingInfo.StakingTx + stakingTxHash := stakingMsgTx.TxHash().String() + stakingSlashingPathInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() s.NoError(err) - stakingMsgTx, err := stakingTx.ToMsgTx() - s.NoError(err) + // generate proper delegator sig - delegatorSig, err := slashingTx.Sign( + delegatorSig, err := testStakingInfo.SlashingTx.Sign( stakingMsgTx, - stakingTx.Script, + 0, + stakingSlashingPathInfo.RevealedLeaf.Script, delBTCSK, net, ) @@ -146,10 +152,21 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { stakingTxInfo := btcctypes.NewTransactionInfoFromSpvProof(blockWithStakingTx.SpvProof) // submit the message for creating BTC delegation - nonValidatorNode.CreateBTCDelegation(delBabylonSK.PubKey().(*secp256k1.PubKey), pop, stakingTx, stakingTxInfo, slashingTx, delegatorSig) + nonValidatorNode.CreateBTCDelegation( + delBabylonSK.PubKey().(*secp256k1.PubKey), + bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), + pop, + stakingTxInfo, + btcVal.BtcPk, + stakingTimeBlocks, + btcutil.Amount(stakingValue), + testStakingInfo.SlashingTx, + delegatorSig, + ) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() + nonValidatorNode.WaitForNextBlock() pendingDelSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) s.Len(pendingDelSet, 1) @@ -159,10 +176,8 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.Nil(pendingDels.Dels[0].CovenantSig) // check delegation - delegation := nonValidatorNode.QueryBtcDelegation(stakingTx.MustGetTxHashStr()) + delegation := nonValidatorNode.QueryBtcDelegation(stakingTxHash) s.NotNil(delegation) - expectedScript := hex.EncodeToString(stakingTx.Script) - s.Equal(expectedScript, delegation.StakingScript) } // Test2SubmitCovenantSignature is an end-to-end test for user @@ -183,16 +198,42 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { slashingTx := pendingDel.SlashingTx stakingTx := pendingDel.StakingTx - stakingMsgTx, err := stakingTx.ToMsgTx() + stakingMsgTx, err := bstypes.ParseBtcTx(stakingTx) + s.NoError(err) + stakingTxHash := stakingMsgTx.TxHash().String() + + params := nonValidatorNode.QueryBTCStakingParams() + covenantBTCPKs := []*btcec.PublicKey{} + for _, covenantPK := range params.CovenantPks { + covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) + } + + validatorBTCPKs := []*btcec.PublicKey{} + for _, valPK := range pendingDel.ValBtcPkList { + validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) + } + + stakingInfo, err := btcstaking.BuildStakingInfo( + pendingDel.BtcPk.MustToBTCPK(), + validatorBTCPKs, + covenantBTCPKs, + params.CovenantQuorum, + pendingDel.GetStakingTime(), + btcutil.Amount(pendingDel.TotalSat), + net, + ) + s.NoError(err) + + stakingSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() s.NoError(err) - stakingTxHash := stakingTx.MustGetTxHashStr() /* generate and insert new covenant signature, in order to activate the BTC delegation */ covenantSig, err := slashingTx.Sign( stakingMsgTx, - stakingTx.Script, + pendingDel.StakingOutputIdx, + stakingSlashingPathInfo.RevealedLeaf.Script, covenantSK, net, ) @@ -201,6 +242,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() + nonValidatorNode.WaitForNextBlock() // ensure the BTC delegation has covenant sig now activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) @@ -397,26 +439,27 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) } - stakingTx := activeDel.StakingTx - stakingMsgTx, err := stakingTx.ToMsgTx() + validatorBTCPKs := []*btcec.PublicKey{} + for _, valPK := range activeDel.ValBtcPkList { + validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) + } + + stakingMsgTx, err := bstypes.ParseBtcTx(activeDel.StakingTx) s.NoError(err) - stakingTxHash := stakingTx.MustGetTxHashStr() + stakingTxHash := stakingMsgTx.TxHash().String() stakingTxChainHash, err := chainhash.NewHashFromStr(stakingTxHash) s.NoError(err) - stakingOutputIdx, err := btcstaking.GetIdxOutputCommitingToScript( - stakingMsgTx, activeDel.StakingTx.Script, net, - ) - s.NoError(err) - fee := int64(1000) - unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + testUnbondingInfo := datagen.GenBTCUnbondingSlashingTx( r, + s.T(), net, delBTCSK, - []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + validatorBTCPKs, covenantBTCPKs, - wire.NewOutPoint(stakingTxChainHash, uint32(stakingOutputIdx)), + params.CovenantQuorum, + wire.NewOutPoint(stakingTxChainHash, uint32(activeDel.StakingOutputIdx)), initialization.BabylonBtcFinalizationPeriod+1, stakingValue-fee, params.SlashingAddress, changeAddress.String(), @@ -424,19 +467,28 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { ) s.NoError(err) - unbondingTxMsg, err := unbondingTx.ToMsgTx() + unbondingTxMsg := testUnbondingInfo.UnbondingTx + + unbondingTxSlashingPathInfo, err := testUnbondingInfo.UnbondingInfo.SlashingPathSpendInfo() s.NoError(err) - slashingTxSig, err := slashUnbondingTx.Sign( + slashingTxSig, err := testUnbondingInfo.SlashingTx.Sign( unbondingTxMsg, - unbondingTx.Script, + 0, + unbondingTxSlashingPathInfo.RevealedLeaf.Script, delBTCSK, net, ) s.NoError(err) // submit the message for creating BTC undelegation - nonValidatorNode.CreateBTCUndelegation(unbondingTx, slashUnbondingTx, slashingTxSig) + nonValidatorNode.CreateBTCUndelegation( + unbondingTxMsg, + testUnbondingInfo.SlashingTx, + initialization.BabylonBtcFinalizationPeriod+1, + btcutil.Amount(stakingValue-fee), + slashingTxSig, + ) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() @@ -464,61 +516,83 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegation := delegatorDelegations.Dels[0] s.NotNil(delegation.BtcUndelegation) - s.Nil(delegation.BtcUndelegation.ValidatorUnbondingSig) s.Nil(delegation.BtcUndelegation.CovenantUnbondingSig) s.Nil(delegation.BtcUndelegation.CovenantSlashingSig) - // First sent validator signature - stakingTxMsg, err := delegation.StakingTx.ToMsgTx() - s.NoError(err) - stakingTxHash := delegation.StakingTx.MustGetTxHashStr() + // params for covenantPk and slashing address + params := nonValidatorNode.QueryBTCStakingParams() + // get covenant BTC PKs + covenantBTCPKs := []*btcec.PublicKey{} + for _, covenantPK := range params.CovenantPks { + covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) + } + + validatorBTCPKs := []*btcec.PublicKey{} + for _, valPK := range delegation.ValBtcPkList { + validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) + } - validatorUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( - stakingTxMsg, - delegation.StakingTx.Script, - valBTCSK, + stakingInfo, err := btcstaking.BuildStakingInfo( + delegation.BtcPk.MustToBTCPK(), + validatorBTCPKs, + covenantBTCPKs, + params.CovenantQuorum, + delegation.GetStakingTime(), + btcutil.Amount(delegation.TotalSat), net, ) s.NoError(err) - nonValidatorNode.AddValidatorUnbondingSig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, validatorUnbondingSig) - nonValidatorNode.WaitForNextBlock() - - allDelegationsValSig := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) - s.Len(allDelegationsValSig, 1) - delegationWithValSig := allDelegationsValSig[0].Dels[0] - s.NotNil(delegationWithValSig.BtcUndelegation) - s.NotNil(delegationWithValSig.BtcUndelegation.ValidatorUnbondingSig) + unbondingTx, err := bstypes.ParseBtcTx(delegation.BtcUndelegation.UnbondingTx) + s.NoError(err) + stakingTx, err := bstypes.ParseBtcTx(delegation.StakingTx) + s.NoError(err) + stakingTxHash := stakingTx.TxHash().String() - unbodnindDelegations := nonValidatorNode.QueryUnbondingDelegations() - s.Len(unbodnindDelegations, 1) + unbondingInfo, err := btcstaking.BuildUnbondingInfo( + delegation.BtcPk.MustToBTCPK(), + validatorBTCPKs, + covenantBTCPKs, + params.CovenantQuorum, + uint16(delegation.BtcUndelegation.UnbondingTime), + btcutil.Amount(unbondingTx.TxOut[0].Value), + net, + ) + s.NoError(err) - btcTip, err := nonValidatorNode.QueryTip() + // Next send covenant signatures. + // First covenant signature on unbonding tx + stakingTxUnbondigPathInfo, err := stakingInfo.UnbondingPathSpendInfo() s.NoError(err) - s.Equal( - bstypes.BTCDelegationStatus_UNBONDING, - delegationWithValSig.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), - ) - // Next send covenant signatures - covenantUnbondingSig, err := delegation.BtcUndelegation.UnbondingTx.Sign( - stakingTxMsg, - delegation.StakingTx.Script, + covenantUnbondingSignature, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + delegation.StakingOutputIdx, + stakingTxUnbondigPathInfo.RevealedLeaf.Script, covenantSK, net, ) s.NoError(err) - unbondingTxMsg, err := delegation.BtcUndelegation.UnbondingTx.ToMsgTx() + covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingSignature) + + // Next covenant signature on unbonding slashing tx + unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() s.NoError(err) + covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.Sign( - unbondingTxMsg, - delegation.BtcUndelegation.UnbondingTx.Script, + unbondingTx, + 0, + unbondingTxSlashingPath.RevealedLeaf.Script, covenantSK, net, ) s.NoError(err) - nonValidatorNode.AddCovenantUnbondingSigs(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, covenantUnbondingSig, covenantSlashingSig) + nonValidatorNode.AddCovenantUnbondingSigs( + btcVal.BtcPk, + bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, &covenantUnbondingSig, covenantSlashingSig) + nonValidatorNode.WaitForNextBlock() nonValidatorNode.WaitForNextBlock() // Check all signatures are properly registered @@ -526,10 +600,9 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { s.Len(allDelegationsWithSigs, 1) delegationWithSigs := allDelegationsWithSigs[0].Dels[0] s.NotNil(delegationWithSigs.BtcUndelegation) - s.NotNil(delegationWithSigs.BtcUndelegation.ValidatorUnbondingSig) s.NotNil(delegationWithSigs.BtcUndelegation.CovenantUnbondingSig) s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSig) - btcTip, err = nonValidatorNode.QueryTip() + btcTip, err := nonValidatorNode.QueryTip() s.NoError(err) s.Equal( bstypes.BTCDelegationStatus_UNBONDED, diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 33d1de0b6..3ff50efbb 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -5,9 +5,12 @@ import ( "strconv" "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/stretchr/testify/require" ) @@ -33,28 +36,45 @@ func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn. n.LogActionF("successfully created BTC validator") } -func (n *NodeConfig) CreateBTCDelegation(babylonPK *secp256k1.PubKey, pop *bstypes.ProofOfPossession, stakingTx *bstypes.BabylonBTCTaprootTx, stakingTxInfo *btcctypes.TransactionInfo, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { +func (n *NodeConfig) CreateBTCDelegation( + babylonPK *secp256k1.PubKey, + btcPk *bbn.BIP340PubKey, + pop *bstypes.ProofOfPossession, + stakingTxInfo *btcctypes.TransactionInfo, + valPK *bbn.BIP340PubKey, + stakingTimeBlocks uint16, + stakingValue btcutil.Amount, + slashingTx *bstypes.BTCSlashingTx, + delegatorSig *bbn.BIP340Signature, +) { n.LogActionF("creating BTC delegation") // get babylon PK hex babylonPKBytes, err := babylonPK.Marshal() require.NoError(n.t, err) babylonPKHex := hex.EncodeToString(babylonPKBytes) + + btcPkHex := btcPk.MarshalHex() + // get pop hex popHex, err := pop.ToHexStr() require.NoError(n.t, err) - // get staking tx hex - stakingTxHex, err := stakingTx.ToHexStr() - require.NoError(n.t, err) + // get staking tx info hex stakingTxInfoHex, err := stakingTxInfo.ToHexStr() require.NoError(n.t, err) + + valPKHex := valPK.MarshalHex() + + stakingTimeString := sdkmath.NewUint(uint64(stakingTimeBlocks)).String() + stakingValueString := sdkmath.NewInt(int64(stakingValue)).String() + // get slashing tx hex slashingTxHex := slashingTx.ToHexStr() // get delegator sig hex delegatorSigHex := delegatorSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, popHex, stakingTxHex, stakingTxInfoHex, slashingTxHex, delegatorSigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, btcPkHex, popHex, stakingTxInfoHex, valPKHex, stakingTimeString, stakingValueString, slashingTxHex, delegatorSigHex, "--from=val"} _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully created BTC delegation") @@ -121,17 +141,27 @@ func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint n.LogActionF("successfully added finality signature") } -func (n *NodeConfig) CreateBTCUndelegation(unbondingTx *bstypes.BabylonBTCTaprootTx, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature) { +func (n *NodeConfig) CreateBTCUndelegation( + unbondingTx *wire.MsgTx, + slashingTx *bstypes.BTCSlashingTx, + unbondingTimeBlocks uint16, + unbondingValue btcutil.Amount, + delegatorSig *bbn.BIP340Signature) { n.LogActionF("creating BTC undelegation") - // get staking tx hex - unbondingTxHex, err := unbondingTx.ToHexStr() + + txBytes, err := bstypes.SerializeBtcTx(unbondingTx) require.NoError(n.t, err) + // get staking tx hex + unbondingTxHex := hex.EncodeToString(txBytes) // get slashing tx hex slashingTxHex := slashingTx.ToHexStr() // get delegator sig hex delegatorSigHex := delegatorSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-undelegation", unbondingTxHex, slashingTxHex, delegatorSigHex, "--from=val"} + unbondingTimeStr := sdkmath.NewUint(uint64(unbondingTimeBlocks)).String() + unbondingValueStr := sdkmath.NewInt(int64(unbondingValue)).String() + + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-undelegation", unbondingTxHex, slashingTxHex, unbondingTimeStr, unbondingValueStr, delegatorSigHex, "--from=val"} _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully created BTC delegation") diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 3a0ab1a8d..71db336fe 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -1,9 +1,9 @@ package datagen import ( - "bytes" "fmt" "math/rand" + "testing" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" @@ -17,6 +17,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" ) func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { @@ -65,9 +66,11 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe func GenRandomBTCDelegation( r *rand.Rand, + t *testing.T, valBTCPKs []bbn.BIP340PubKey, delSK *btcec.PrivateKey, covenantSKs []*btcec.PrivateKey, + covenantThreshold uint32, slashingAddress, changeAddress string, startHeight, endHeight, totalSat uint64, slashingRate sdk.Dec, @@ -105,184 +108,211 @@ func GenRandomBTCDelegation( return nil, err } // staking/slashing tx - stakingTx, slashingTx, err := GenBTCStakingSlashingTx( + testingInfo := GenBTCStakingSlashingTx( r, + t, net, delSK, valPKs, covenantBTCPKs, + covenantThreshold, uint16(endHeight-startHeight), int64(totalSat), slashingAddress, changeAddress, - slashingRate) - if err != nil { - return nil, err - } + slashingRate, + ) + + slashingPathSpendInfo, err := testingInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + script := slashingPathSpendInfo.RevealedLeaf.Script // covenant sig and delegator sig - stakingMsgTx, err := stakingTx.ToMsgTx() - if err != nil { - return nil, err - } + stakingMsgTx := testingInfo.StakingTx // TODO: covenant multisig - covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSKs[0], net) + covenantSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, covenantSKs[0], net) if err != nil { return nil, err } - delegatorSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, delSK, net) + delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, delSK, net) if err != nil { return nil, err } + serializedStaking, err := bstypes.SerializeBtcTx(testingInfo.StakingTx) + require.NoError(t, err) + return &bstypes.BTCDelegation{ - BabylonPk: secp256k1PK, - BtcPk: delBTCPK, - Pop: pop, - ValBtcPkList: valBTCPKs, - StartHeight: startHeight, - EndHeight: endHeight, - TotalSat: totalSat, - DelegatorSig: delegatorSig, - CovenantSig: covenantSig, - StakingTx: stakingTx, - SlashingTx: slashingTx, + BabylonPk: secp256k1PK, + BtcPk: delBTCPK, + Pop: pop, + ValBtcPkList: valBTCPKs, + StartHeight: startHeight, + EndHeight: endHeight, + TotalSat: totalSat, + StakingOutputIdx: 0, + DelegatorSig: delegatorSig, + CovenantSig: covenantSig, + StakingTx: serializedStaking, + SlashingTx: testingInfo.SlashingTx, }, nil } +type TestStakingSlashingInfo struct { + StakingTx *wire.MsgTx + SlashingTx *bstypes.BTCSlashingTx + StakingInfo *btcstaking.StakingInfo +} + +type TestUnbondingSlashingInfo struct { + UnbondingTx *wire.MsgTx + SlashingTx *bstypes.BTCSlashingTx + UnbondingInfo *btcstaking.UnbondingInfo +} + func GenBTCStakingSlashingTxWithOutPoint( r *rand.Rand, + t *testing.T, btcNet *chaincfg.Params, outPoint *wire.OutPoint, stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, + covenantThreshold uint32, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, slashingRate sdk.Dec, - withChange bool, -) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { - // TODO: covenant multisig - stakingOutput, stakingScript, err := btcstaking.BuildStakingOutput( +) *TestStakingSlashingInfo { + + stakingInfo, err := btcstaking.BuildStakingInfo( stakerSK.PubKey(), - validatorPKs[0], - covenantPKs[0], + validatorPKs, + covenantPKs, + covenantThreshold, stakingTimeBlocks, btcutil.Amount(stakingValue), btcNet, ) - if err != nil { - return nil, nil, err - } + require.NoError(t, err) tx := wire.NewMsgTx(2) // add the given tx input txIn := wire.NewTxIn(outPoint, nil, nil) tx.AddTxIn(txIn) + tx.AddTxOut(stakingInfo.StakingOutput) - tx.AddTxOut(stakingOutput) + // 2 outputs for changes and staking output + changeAddrScript, err := GenRandomPubKeyHashScript(r, btcNet) + require.NoError(t, err) + require.False(t, txscript.GetScriptClass(changeAddrScript) == txscript.NonStandardTy) - if withChange { - // 2 outputs for changes and staking output - changeAddrScript, err := GenRandomPubKeyHashScript(r, btcNet) - if err != nil { - return nil, nil, err - } - if txscript.GetScriptClass(changeAddrScript) == txscript.NonStandardTy { - return nil, nil, fmt.Errorf("change address script is non-standard") - } - tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change - } - - // construct staking tx - var buf bytes.Buffer - err = tx.Serialize(&buf) - if err != nil { - return nil, nil, err - } - stakingTx := &bstypes.BabylonBTCTaprootTx{ - Tx: buf.Bytes(), - Script: stakingScript, - } + tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change // construct slashing tx slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) - if err != nil { - return nil, nil, err - } + require.NoError(t, err) changeAddrBtc, err := btcutil.DecodeAddress(changeAddress, btcNet) - if err != nil { - return nil, nil, err - } + require.NoError(t, err) slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, 0, slashingAddrBtc, changeAddrBtc, 2000, slashingRate, - stakingScript, btcNet) - if err != nil { - return nil, nil, err - } + require.NoError(t, err) slashingTx, err := bstypes.NewBTCSlashingTxFromMsgTx(slashingMsgTx) - if err != nil { - return nil, nil, err - } + require.NoError(t, err) - return stakingTx, slashingTx, nil + return &TestStakingSlashingInfo{ + StakingTx: tx, + SlashingTx: slashingTx, + StakingInfo: stakingInfo, + } } func GenBTCStakingSlashingTx( r *rand.Rand, + t *testing.T, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, + covenantThreshold uint32, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, slashingRate sdk.Dec, -) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { +) *TestStakingSlashingInfo { // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) outPoint := &spend.prevOut return GenBTCStakingSlashingTxWithOutPoint( r, + t, btcNet, outPoint, stakerSK, validatorPKs, covenantPKs, + covenantThreshold, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, - slashingRate, - true) + slashingRate) } func GenBTCUnbondingSlashingTx( r *rand.Rand, + t *testing.T, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, + covenantThreshold uint32, stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, slashingRate sdk.Dec, -) (*bstypes.BabylonBTCTaprootTx, *bstypes.BTCSlashingTx, error) { - return GenBTCStakingSlashingTxWithOutPoint( - r, - btcNet, - stakingTransactionOutpoint, - stakerSK, +) *TestUnbondingSlashingInfo { + + unbondingInfo, err := btcstaking.BuildUnbondingInfo( + stakerSK.PubKey(), validatorPKs, covenantPKs, + covenantThreshold, stakingTimeBlocks, - stakingValue, - slashingAddress, changeAddress, + btcutil.Amount(stakingValue), + btcNet, + ) + + require.NoError(t, err) + tx := wire.NewMsgTx(2) + // add the given tx input + txIn := wire.NewTxIn(stakingTransactionOutpoint, nil, nil) + tx.AddTxIn(txIn) + tx.AddTxOut(unbondingInfo.UnbondingOutput) + + // construct slashing tx + slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) + require.NoError(t, err) + changeAddrBtc, err := btcutil.DecodeAddress(changeAddress, btcNet) + require.NoError(t, err) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( + tx, + 0, + slashingAddrBtc, changeAddrBtc, + 2000, slashingRate, - false) + btcNet) + require.NoError(t, err) + slashingTx, err := bstypes.NewBTCSlashingTxFromMsgTx(slashingMsgTx) + require.NoError(t, err) + + return &TestUnbondingSlashingInfo{ + UnbondingTx: tx, + SlashingTx: slashingTx, + UnbondingInfo: unbondingInfo, + } } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 09464e8bf..d3fcbb98f 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -3,11 +3,14 @@ package cli import ( "encoding/hex" "fmt" + "math" "strings" + sdkmath "cosmossdk.io/math" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcutil" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -42,7 +45,6 @@ func GetTxCmd() *cobra.Command { NewAddCovenantSigCmd(), NewCreateBTCUndelegationCmd(), NewAddCovenantUnbondingSigsCmd(), - NewAddValidatorUnbondingSigCmd(), ) return cmd @@ -132,10 +134,50 @@ func NewCreateBTCValidatorCmd() *cobra.Command { return cmd } +func parseLockTime(str string) (uint16, error) { + num, ok := sdkmath.NewIntFromString(str) + + if !ok { + return 0, fmt.Errorf("invalid staking time: %s", str) + } + + if !num.IsUint64() { + return 0, fmt.Errorf("staking time is not valid uint") + } + + asUint64 := num.Uint64() + + if asUint64 > math.MaxUint16 { + return 0, fmt.Errorf("staking time is too large. Max is %d", math.MaxUint16) + } + + return uint16(asUint64), nil +} + +func parseBtcAmount(str string) (btcutil.Amount, error) { + num, ok := sdkmath.NewIntFromString(str) + + if !ok { + return 0, fmt.Errorf("invalid staking value: %s", str) + } + + if num.IsNegative() { + return 0, fmt.Errorf("staking value is negative") + } + + if !num.IsInt64() { + return 0, fmt.Errorf("staking value is not valid uint") + } + + asInt64 := num.Int64() + + return btcutil.Amount(asInt64), nil +} + func NewCreateBTCDelegationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-delegation [babylon_pk] [pop] [staking_tx] [staking_tx_info] [slashing_tx] [delegator_sig]", - Args: cobra.ExactArgs(6), + Use: "create-btc-delegation [babylon_pk] [btc_pk] [pop] [staking_tx_info] [val_pk] [staking_time] [staking_value] [slashing_tx] [delegator_sig]", + Args: cobra.ExactArgs(9), Short: "Create a BTC delegation", Long: strings.TrimSpace( `Create a BTC delegation.`, // TODO: example @@ -156,14 +198,15 @@ func NewCreateBTCDelegationCmd() *cobra.Command { return err } - // get PoP - pop, err := types.NewPoPFromHex(args[1]) + // staker pk + btcPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + if err != nil { return err } - // get staking tx - stakingTx, err := types.NewBabylonTaprootTxFromHex(args[2]) + // get PoP + pop, err := types.NewPoPFromHex(args[2]) if err != nil { return err } @@ -174,26 +217,49 @@ func NewCreateBTCDelegationCmd() *cobra.Command { return err } + // TODO: Support multiple validators + // get validator PK + valPK, err := bbn.NewBIP340PubKeyFromHex(args[4]) + if err != nil { + return err + } + + // get staking time + stakingTime, err := parseLockTime(args[5]) + + if err != nil { + return err + } + + stakingValue, err := parseBtcAmount(args[6]) + + if err != nil { + return err + } + // get slashing tx - slashingTx, err := types.NewBTCSlashingTxFromHex(args[4]) + slashingTx, err := types.NewBTCSlashingTxFromHex(args[7]) if err != nil { return err } // get delegator sig - delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[5]) + delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[8]) if err != nil { return err } msg := types.MsgCreateBTCDelegation{ - Signer: clientCtx.FromAddress.String(), - BabylonPk: &babylonPK, - Pop: pop, - StakingTx: stakingTx, - StakingTxInfo: stakingTxInfo, - SlashingTx: slashingTx, - DelegatorSig: delegatorSig, + Signer: clientCtx.FromAddress.String(), + BabylonPk: &babylonPK, + StakerBtcPk: btcPK, + ValBtcPkList: []bbn.BIP340PubKey{*valPK}, + Pop: pop, + StakingTime: uint32(stakingTime), + StakingValue: int64(stakingValue), + StakingTx: stakingTxInfo, + SlashingTx: slashingTx, + DelegatorSig: delegatorSig, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -259,8 +325,8 @@ func NewAddCovenantSigCmd() *cobra.Command { func NewCreateBTCUndelegationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-undelegation [unbonding_tx] [slashing_tx] [delegator_sig]", - Args: cobra.ExactArgs(3), + Use: "create-btc-undelegation [unbonding_tx] [slashing_tx] [unbonding_time] [unbonding_value] [delegator_sig]", + Args: cobra.ExactArgs(5), Short: "Create a BTC undelegation", Long: strings.TrimSpace( `Create a BTC undelegation.`, // TODO: example @@ -272,7 +338,7 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { } // get staking tx - unbondingTx, err := types.NewBabylonTaprootTxFromHex(args[0]) + _, unbondingTxBytes, err := types.ParseBtcTxFromHex(args[0]) if err != nil { return err } @@ -283,15 +349,30 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { return err } + // get staking time + unbondingTime, err := parseLockTime(args[2]) + + if err != nil { + return err + } + + unbondingValue, err := parseBtcAmount(args[3]) + + if err != nil { + return err + } + // get delegator sig - delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[2]) + delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[4]) if err != nil { return err } msg := types.MsgBTCUndelegate{ Signer: clientCtx.FromAddress.String(), - UnbondingTx: unbondingTx, + UnbondingTx: unbondingTxBytes, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: int64(unbondingValue), SlashingTx: slashingTx, DelegatorSlashingSig: delegatorSig, } @@ -360,52 +441,3 @@ func NewAddCovenantUnbondingSigsCmd() *cobra.Command { return cmd } - -func NewAddValidatorUnbondingSigCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "add-validator-unbonding-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", - Args: cobra.ExactArgs(4), - Short: "Add a validator signature for unbonding tx", - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - // get validator PK - valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) - if err != nil { - return err - } - - // get delegator PK - delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) - if err != nil { - return err - } - - // get staking tx hash - stakingTxHash := args[2] - - // get validator sigature - sig, err := bbn.NewBIP340SignatureFromHex(args[3]) - if err != nil { - return err - } - - msg := types.MsgAddValidatorUnbondingSig{ - Signer: clientCtx.FromAddress.String(), - ValPk: valPK, - DelPk: delPK, - StakingTxHash: stakingTxHash, - UnbondingTxSig: sig, - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index cffc91144..e41d275fc 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -55,8 +55,6 @@ func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) e // and staking tx hash by using a given function func (k Keeper) updateBTCDelegation( ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, stakingTxHashStr string, modifyFn func(*types.BTCDelegation) error, ) error { @@ -88,13 +86,11 @@ func (k Keeper) AddCovenantSigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP return nil } - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addCovenantSig) + return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSig) } func (k Keeper) AddUndelegationToBTCDelegation( ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, stakingTxHash string, ud *types.BTCUndelegation, ) error { @@ -106,36 +102,11 @@ func (k Keeper) AddUndelegationToBTCDelegation( return nil } - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addUndelegation) -} - -func (k Keeper) AddValidatorSigToUndelegation( - ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, - stakingTxHash string, - sig *bbn.BIP340Signature, -) error { - addValidatorSig := func(btcDel *types.BTCDelegation) error { - if btcDel.BtcUndelegation == nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) - } - - if btcDel.BtcUndelegation.ValidatorUnbondingSig != nil { - return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid validator signature", stakingTxHash) - } - - btcDel.BtcUndelegation.ValidatorUnbondingSig = sig - return nil - } - - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addValidatorSig) + return k.updateBTCDelegation(ctx, stakingTxHash, addUndelegation) } func (k Keeper) AddCovenantSigsToUndelegation( ctx sdk.Context, - valBTCPK *bbn.BIP340PubKey, - delBTCPK *bbn.BIP340PubKey, stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, slashUnbondingTxSig *bbn.BIP340Signature, @@ -154,7 +125,7 @@ func (k Keeper) AddCovenantSigsToUndelegation( return nil } - return k.updateBTCDelegation(ctx, valBTCPK, delBTCPK, stakingTxHash, addCovenantSigs) + return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSigs) } // hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index c81f00465..e56466594 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -282,9 +282,8 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation var undelegationInfo *types.BTCUndelegationInfo if btcDel.BtcUndelegation != nil { undelegationInfo = &types.BTCUndelegationInfo{ - UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, - ValidatorUnbondingSig: btcDel.BtcUndelegation.ValidatorUnbondingSig, - CovenantUnbondingSig: btcDel.BtcUndelegation.CovenantUnbondingSig, + UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, + CovenantUnbondingSig: btcDel.BtcUndelegation.CovenantUnbondingSig, } } @@ -294,8 +293,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation StartHeight: btcDel.StartHeight, EndHeight: btcDel.EndHeight, TotalSat: btcDel.TotalSat, - StakingTx: hex.EncodeToString(btcDel.StakingTx.Tx), - StakingScript: hex.EncodeToString(btcDel.StakingTx.Script), + StakingTxHex: hex.EncodeToString(btcDel.StakingTx), Active: isActive, UndelegationInfo: undelegationInfo, }, nil diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index d11f2598c..da3a98c82 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -208,9 +208,11 @@ func FuzzPendingBTCDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, @@ -224,7 +226,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { err = keeper.AddBTCDelegation(ctx, btcDel) require.NoError(t, err) - txHash := btcDel.StakingTx.MustGetTxHashStr() + txHash := btcDel.MustGetStakingTxHash().String() btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) delView, err := keeper.BTCDelegation(ctx, &types.QueryBTCDelegationRequest{ StakingTxHashHex: txHash, @@ -312,9 +314,11 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, @@ -323,10 +327,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { if datagen.RandomInt(r, 2) == 1 { // add unbonding object in random BTC delegations to make them ready to receive covenant sig - btcDel.BtcUndelegation = &types.BTCUndelegation{ - // doesn't matter what we put here - ValidatorUnbondingSig: btcDel.CovenantSig, - } + btcDel.BtcUndelegation = &types.BTCUndelegation{} if datagen.RandomInt(r, 2) == 1 { // these BTC delegations are unbonded @@ -493,9 +494,11 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*valBTCPK}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, 10000, slashingRate, @@ -597,9 +600,11 @@ func FuzzBTCValidatorDelegations(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), startHeight, endHeight, 10000, slashingRate, diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index de2db38bb..7ea89f0e4 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -68,9 +68,11 @@ func FuzzRecordRewardDistCache(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, slashingRate, diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 000356831..d5e436f7d 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -2,13 +2,13 @@ package keeper import ( "context" - "encoding/hex" "fmt" + "math" errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -39,23 +39,91 @@ func mustGetSlashingAddress(params *types.Params, btcParams *chaincfg.Params) bt } func mustGetStakingTxInfo(del *types.BTCDelegation, params *chaincfg.Params) (*wire.MsgTx, uint32) { - stakingTxMsg, err := del.StakingTx.ToMsgTx() + stakingTxMsg, err := types.ParseBtcTx(del.StakingTx) if err != nil { // failing to get staking output info from a verified staking tx is a programming error panic(fmt.Errorf("failed deserialize staking tx from db")) } + return stakingTxMsg, del.StakingOutputIdx +} + +func (ms msgServer) stakingInfoFromDelegation(ctx sdk.Context, del *types.BTCDelegation) (*btcstaking.StakingInfo, error) { + params := ms.GetParams(ctx) + + var validatorKeys []*btcec.PublicKey + for _, valBTCPK := range del.ValBtcPkList { + validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) + } + + var covenantKeys []*btcec.PublicKey + for _, covenantPK := range params.CovenantPks { + covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) + } + + stakerPk := del.BtcPk.MustToBTCPK() + + si, err := btcstaking.BuildStakingInfo( + stakerPk, + validatorKeys, + covenantKeys, + params.CovenantQuorum, + del.GetStakingTime(), + btcutil.Amount(del.TotalSat), + ms.btcNet, + ) + + if err != nil { + return nil, err + } + + return si, nil +} + +func (ms msgServer) mustStakingInfoFromDelegation( + ctx sdk.Context, del *types.BTCDelegation) *btcstaking.StakingInfo { + si, err := ms.stakingInfoFromDelegation(ctx, del) + if err != nil { + panic(err) + } + return si +} + +func (ms msgServer) mustUnbondingInfo( + ctx sdk.Context, + del *types.BTCDelegation, + unbondingTime uint16, + unbondingValue btcutil.Amount, +) *btcstaking.UnbondingInfo { + params := ms.GetParams(ctx) + + var validatorKeys []*btcec.PublicKey + for _, valBTCPK := range del.ValBtcPkList { + validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) + } - stakingOutputIndex, err := btcstaking.GetIdxOutputCommitingToScript( - stakingTxMsg, - del.StakingTx.Script, - params, + var covenantKeys []*btcec.PublicKey + for _, covenantPK := range params.CovenantPks { + covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) + } + + stakerPk := del.BtcPk.MustToBTCPK() + + si, err := btcstaking.BuildUnbondingInfo( + stakerPk, + validatorKeys, + covenantKeys, + params.CovenantQuorum, + unbondingTime, + unbondingValue, + ms.btcNet, ) if err != nil { - panic(fmt.Errorf("script not matching staking tx in database")) + panic("failed to build unbonding info") } - return stakingTxMsg, uint32(stakingOutputIndex) + + return si } // UpdateParams updates the params @@ -118,56 +186,88 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre btccParams := ms.btccKeeper.GetParams(ctx) kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout - // extract staking script from staking tx - // TODO: use new algorithm that reconstructs staking tx output then asserts consistency - stakingOutputInfo, err := req.StakingTx.GetBabylonOutputInfo(ms.btcNet) - if err != nil { - return nil, err + // 1. verify proof of possession + if err := req.Pop.Verify(req.BabylonPk, req.StakerBtcPk, ms.btcNet); err != nil { + return nil, types.ErrInvalidProofOfPossession.Wrapf("error while validating proof of posession: %v", err) } - delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.StakerKey) - valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.ValidatorKey) - covenantPK := bbn.NewBIP340PubKeyFromBTCPK(stakingOutputInfo.StakingScriptData.CovenantKey) - // verify proof of possession - if err := req.Pop.Verify(req.BabylonPk, delBTCPK, ms.btcNet); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession: %v", err) + // 2. Ensure list of validator BTC PKs is not empty + if len(req.ValBtcPkList) == 0 { + return nil, types.ErrEmptyValidatorList + } + + // 3. Ensure list of validator BTC PKs is not duplicated + if types.ExistsDup(req.ValBtcPkList) { + return nil, types.ErrDuplicatedValidator } - // extract staking tx and its hash - stakingMsgTx, err := req.StakingTx.ToMsgTx() + // 4. Ensure all validators are known to Babylon + for _, valBTCPK := range req.ValBtcPkList { + if !ms.HasBTCValidator(ctx, valBTCPK) { + return nil, types.ErrBTCValNotFound.Wrapf("validator pk: %s", valBTCPK.MarshalHex()) + } + } + + // 5. Parse staking tx + stakingMsgTx, err := types.ParseBtcTx(req.StakingTx.Transaction) + if err != nil { - return nil, types.ErrInvalidStakingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + return nil, types.ErrInvalidStakingTx.Wrapf("cannot be parsed: %v", err) } + + // 6. Check staking tx is not duplicated stakingTxHash := stakingMsgTx.TxHash() - // ensure the validator exists - if !ms.HasBTCValidator(ctx, *valBTCPK) { - return nil, types.ErrBTCValNotFound + delgation := ms.getBTCDelegation(ctx, stakingTxHash) + + if delgation != nil { + return nil, types.ErrReusedStakingTx.Wrapf("duplicated tx hash: %s", stakingTxHash.String()) } - // ensure the staking tx is not duplicated - btcDelIndex, err := ms.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) - if err == nil { - // err is nil, meaning there exists a BTC delegation for this validator and delegator - // ensure the staking tx is not duplicated - if btcDelIndex.Has(stakingTxHash) { - return nil, types.ErrReusedStakingTx - } + // 7. Check staking time is at most uint16 + if req.StakingTime > math.MaxUint16 { + return nil, types.ErrInvalidStakingTx.Wrapf("invalid lock time: %d, max: %d", req.StakingTime, math.MaxUint16) + } + + // 8. Check if data provided in request, matches data to which staking tx is comitted + validatorKeys := make([]*btcec.PublicKey, 0, len(req.ValBtcPkList)) + for _, valBTCPK := range req.ValBtcPkList { + validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) + } + + covenantKeys := make([]*btcec.PublicKey, 0, len(params.CovenantPks)) + for _, covenantPK := range params.CovenantPks { + covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) + } + + si, err := btcstaking.BuildStakingInfo( + req.StakerBtcPk.MustToBTCPK(), + validatorKeys, + covenantKeys, + params.CovenantQuorum, + uint16(req.StakingTime), + btcutil.Amount(req.StakingValue), + ms.btcNet, + ) + + if err != nil { + return nil, types.ErrInvalidStakingTx.Wrapf("err: %v", err) } - // ensure staking tx is using correct covenant PK - paramCovenantPK := ¶ms.CovenantPks[0] // TODO: verifying covenant committee PKs - if !covenantPK.Equals(paramCovenantPK) { - return nil, types.ErrInvalidCovenantPK.Wrapf("expected: %s; actual: %s", hex.EncodeToString(*paramCovenantPK), hex.EncodeToString(*covenantPK)) + stakingOutputIdx, err := types.GetOutputIdx(stakingMsgTx, si.StakingOutput) + + if err != nil { + return nil, types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output") } + // 9. Check staking tx timelock has correct values // get startheight and endheight of the timelock - stakingTxHeader := ms.btclcKeeper.GetHeaderByHash(ctx, req.StakingTxInfo.Key.Hash) + stakingTxHeader := ms.btclcKeeper.GetHeaderByHash(ctx, req.StakingTx.Key.Hash) if stakingTxHeader == nil { return nil, fmt.Errorf("header that includes the staking tx is not found") } startHeight := stakingTxHeader.Height - endHeight := stakingTxHeader.Height + uint64(stakingOutputInfo.StakingScriptData.StakingTime) + endHeight := stakingTxHeader.Height + uint64(req.StakingTime) // ensure staking tx is k-deep btcTip := ms.btclcKeeper.GetTipInfo(ctx) @@ -180,43 +280,54 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidStakingTx.Wrapf("staking tx's timelock has no more than w(=%d) blocks left", wValue) } - // verify staking tx info, i.e., inclusion proof - if err := req.StakingTxInfo.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { + // 10. verify staking tx info, i.e., inclusion proof + if err := req.StakingTx.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain: %v", err) } - // check slashing tx and its consistency with staking tx + // 11. check slashing tx and its consistency with staking tx slashingMsgTx, err := req.SlashingTx.ToMsgTx() if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } // decode slashing address + // TODO: Decode slashing address only once, as it is the same for all BTC delegations slashingAddr, err := btcutil.DecodeAddress(params.SlashingAddress, ms.btcNet) if err != nil { panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) } - if _, err := btcstaking.CheckTransactions( + // 12. Check slashing tx and staking tx are valid and consistent + if err := btcstaking.CheckTransactions( slashingMsgTx, stakingMsgTx, + stakingOutputIdx, params.MinSlashingTxFeeSat, params.SlashingRate, slashingAddr, - req.StakingTx.Script, ms.btcNet, ); err != nil { return nil, types.ErrInvalidStakingTx.Wrap(err.Error()) } - // verify delegator_sig + stakingOutput := stakingMsgTx.TxOut[stakingOutputIdx] + + // 13. verify delegator sig against slashing path of the script + slashingPathInfo, err := si.SlashingPathSpendInfo() + + if err != nil { + panic(fmt.Errorf("failed to construct slashing path from the staking tx: %w", err)) + } + err = req.SlashingTx.VerifySignature( - stakingOutputInfo.StakingPkScript, - int64(stakingOutputInfo.StakingAmount), - req.StakingTx.Script, - stakingOutputInfo.StakingScriptData.StakerKey, + stakingOutput.PkScript, + stakingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, + req.StakerBtcPk.MustToBTCPK(), req.DelegatorSig, ) + if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) } @@ -226,19 +337,19 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // have voting power only when 1) its corresponding staking tx is k-deep, // and 2) it receives a covenant signature newBTCDel := &types.BTCDelegation{ - BabylonPk: req.BabylonPk, - BtcPk: delBTCPK, - Pop: req.Pop, - // TODO: ValBtcPkList will be provided in the message - ValBtcPkList: []bbn.BIP340PubKey{*valBTCPK}, - StartHeight: startHeight, - EndHeight: endHeight, - TotalSat: uint64(stakingOutputInfo.StakingAmount), - StakingTx: req.StakingTx, - SlashingTx: req.SlashingTx, - DelegatorSig: req.DelegatorSig, - CovenantSig: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant - BtcUndelegation: nil, + BabylonPk: req.BabylonPk, + BtcPk: req.StakerBtcPk, + Pop: req.Pop, + ValBtcPkList: req.ValBtcPkList, + StartHeight: startHeight, + EndHeight: endHeight, + TotalSat: uint64(stakingOutput.Value), + StakingTx: req.StakingTx.Transaction, + StakingOutputIdx: stakingOutputIdx, + SlashingTx: req.SlashingTx, + DelegatorSig: req.DelegatorSig, + CovenantSig: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant + BtcUndelegation: nil, } if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { panic("failed to set BTC delegation that has passed verification") @@ -265,14 +376,25 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - unbondingMsgTx, err := req.UnbondingTx.ToMsgTx() + unbondingMsgTx, err := types.ParseBtcTx(req.UnbondingTx) if err != nil { return nil, types.ErrInvalidUnbodningTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - // 2. basic stateless checks for unbodning tx + // 2. basic stateless checks for unbonding tx if err := btcstaking.IsSimpleTransfer(unbondingMsgTx); err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("invalid unbonding tx: %v", err) + return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) + } + + // 3. Check unbonding time (staking time from unbonding tx) is larger than finalization time + // Unbodning time must be strictly larger that babylon finalization time. + if uint64(req.UnbondingTime) <= wValue { + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time %d must be larger than finalization time %d", req.UnbondingTime, wValue) + } + + // 4. Check unbonding time is lower than max uint16 + if uint64(req.UnbondingTime) > math.MaxUint16 { + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time %d must be lower than %d", req.UnbondingTime, math.MaxUint16) } // retrieve staking tx hash from unbonding tx, at this point we know that unbonding tx is a simple transfer with @@ -280,63 +402,89 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele unbondingTxFundingOutpoint := unbondingMsgTx.TxIn[0].PreviousOutPoint stakingTxHash := unbondingTxFundingOutpoint.Hash.String() - // 3. Check that slashing tx and unbonding tx are valid and consistent - unbondingOutputInfo, err := btcstaking.CheckTransactions( - slashingMsgTx, - unbondingMsgTx, - params.MinSlashingTxFeeSat, - params.SlashingRate, - slashingAddress, - req.UnbondingTx.Script, - ms.btcNet, - ) + // 5. Check delegation wchich should be undelegeated exists and it is in correct state + del, err := ms.GetBTCDelegation(ctx, stakingTxHash) + if err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("invalid unbonding tx: %v", err) + return nil, types.ErrInvalidDelegationState.Wrapf("couldn't retrieve delegation for staking tx hash %s, err: %v", stakingTxHash, err) } - err = req.SlashingTx.VerifySignature( - unbondingOutputInfo.StakingPkScript, - int64(unbondingOutputInfo.StakingAmount), - req.UnbondingTx.Script, - unbondingOutputInfo.StakingScriptData.StakerKey, - req.DelegatorSlashingSig, + // 6. Check delegation state. Only active delegations can be unbonded. + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + status := del.GetStatus(btcTip.Height, wValue) + + if status != types.BTCDelegationStatus_ACTIVE { + return nil, types.ErrInvalidDelegationState.Wrapf("current status: %v, want: %s", status.String(), types.BTCDelegationStatus_ACTIVE.String()) + } + + // 7. Check unbonding tx commits to valid scripts + validatorKeys := make([]*btcec.PublicKey, 0, len(del.ValBtcPkList)) + // We retrieve validator keys from the delegation, as we want to check that unbonding tx commits to the same + // validator keys as staking tx. + for _, valBTCPK := range del.ValBtcPkList { + validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) + } + + covenantKeys := make([]*btcec.PublicKey, 0, len(params.CovenantPks)) + // as we do not rotate covenant keys, we can retrieve them from params + for _, covenantPK := range params.CovenantPks { + covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) + } + + si, err := btcstaking.BuildUnbondingInfo( + del.BtcPk.MustToBTCPK(), + validatorKeys, + covenantKeys, + params.CovenantQuorum, + uint16(req.UnbondingTime), + btcutil.Amount(req.UnbondingValue), + ms.btcNet, ) + if err != nil { - return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) + return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) } - // 4. Check unbonding time (staking time from unbonding tx) is larger than finalization time - // Unbodning time must be strictly larger that babylon finalization time. - if uint64(unbondingOutputInfo.StakingScriptData.StakingTime) <= wValue { - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time must be larger than finalization time") + unbondingOutputIdx, err := types.GetOutputIdx(unbondingMsgTx, si.UnbondingOutput) + + if err != nil { + return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx does not contain expected unbonding output") } - // 5. Check Covenant Key from script is consistent with params - publicKeyInfos := types.KeyDataFromScript(unbondingOutputInfo.StakingScriptData) - paramCovenantPK := ¶ms.CovenantPks[0] // TODO: verifying covenant committee PKs - if !publicKeyInfos.CovenantKey.Equals(paramCovenantPK) { - return nil, types.ErrInvalidCovenantPK.Wrapf( - "expected: %s; actual: %s", - hex.EncodeToString(*paramCovenantPK), - hex.EncodeToString(*publicKeyInfos.CovenantKey), - ) + // 8. Check that slashing tx and unbonding tx are valid and consistent + err = btcstaking.CheckTransactions( + slashingMsgTx, + unbondingMsgTx, + unbondingOutputIdx, + params.MinSlashingTxFeeSat, + params.SlashingRate, + slashingAddress, + ms.btcNet, + ) + if err != nil { + return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) } - // 6. Check delegation exists for the given validator and delegator and given staking tx hash - // as all keys are taken from script, it effectively check that values in delegation staking script - // matches the values in the unbonding tx staking script - del, err := ms.GetBTCDelegation(ctx, stakingTxHash) + // 9. Check staker signature against slashing path of the unbonding tx + unbondingOutput := unbondingMsgTx.TxOut[unbondingOutputIdx] + + slashingPathInfo, err := si.SlashingPathSpendInfo() if err != nil { - return nil, err + // our staking info was constructed by using BuildStakingInfo constructor, so if + // this fails, it is a programming error + panic(err) } - // 7. Check delegation state. Only active delegations can be unbonded. - btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := del.GetStatus(btcTip.Height, wValue) - - if status != types.BTCDelegationStatus_ACTIVE { - return nil, types.ErrInvalidDelegationState + err = req.SlashingTx.VerifySignature( + unbondingOutput.PkScript, + unbondingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, + del.BtcPk.MustToBTCPK(), + req.DelegatorSlashingSig, + ) + if err != nil { + return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) } // 8. Check unbonding tx against staking tx. @@ -367,15 +515,12 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // Jurry needs to provide two sigs: // - one for unbonding tx // - one for slashing tx of unbonding tx - CovenantSlashingSig: nil, - CovenantUnbondingSig: nil, - ValidatorUnbondingSig: nil, + CovenantSlashingSig: nil, + CovenantUnbondingSig: nil, } if err := ms.AddUndelegationToBTCDelegation( ctx, - publicKeyInfos.ValidatorKey, - publicKeyInfos.StakerKey, stakingTxHash, &ud); err != nil { panic(fmt.Errorf("failed to set BTC delegation that has passed verification: %w", err)) @@ -401,6 +546,7 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven // ensure BTC delegation exists btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) + if err != nil { return nil, err } @@ -408,11 +554,8 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven return nil, types.ErrDuplicatedCovenantSig } - stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) - if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) - } + stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) + stakingOutput := stakingTx.TxOut[stakingOutputIdx] // TODO: covenant PK will be a field in the message. Then the verification will be // that the given covenant PK has to be one of the covenant PKs in the parameter @@ -422,14 +565,29 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven panic(fmt.Errorf("failed to cast a verified covenant public key")) } + spendInfo, err := ms.stakingInfoFromDelegation(ctx, btcDel) + + if err != nil { + panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) + } + + slashingPathInfo, err := spendInfo.SlashingPathSpendInfo() + + if err != nil { + // our staking info was constructed by using BuildStakingInfo constructor, so if + // this fails, it is a programming error + panic(err) + } + // verify signature w.r.t. covenant PK and signature err = btcDel.SlashingTx.VerifySignature( - stakingOutputInfo.StakingPkScript, - int64(stakingOutputInfo.StakingAmount), - btcDel.StakingTx.Script, + stakingOutput.PkScript, + stakingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, covenantPK, req.Sig, ) + if err != nil { return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) } @@ -469,26 +627,12 @@ func (ms msgServer) AddCovenantUnbondingSigs( // 3. Check that we did not recevie covenant signature yet if btcDel.BtcUndelegation.HasCovenantSigs() { - return nil, types.ErrDuplicatedCovenantSig.Wrap("Covenant signature for undelegation already received") - } - - // 4. Check that we already received validator signature - if !btcDel.BtcUndelegation.HasValidatorSig() { - // Covenant should provide signature only after validator to avoid validator and staker - // collusion i.e sending unbonding tx to btc without leaving validator signature on babylon chain. - // TODO: Maybe it is worth accepting signatures and just emmiting some kind of warning event ? as if this msg - // processing fails, it will still be included on babylon chain, so anybody could still retrieve - // all covenant signatures included in msg. And with warning we will at least have some kind of - // indication that something is wrong. - return nil, types.ErrUnbondingUnexpectedValidatorSig + return nil, types.ErrDuplicatedCovenantSig } // 4. Verify signature of unbodning tx against staking tx output - stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) - if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) - } + stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) + stakingOutput := stakingTx.TxOut[stakingOutputIdx] // TODO: covenant PK will be a field in the message. Then the verification will be // that the given covenant PK has to be one of the covenant PKs in the parameter @@ -498,30 +642,57 @@ func (ms msgServer) AddCovenantUnbondingSigs( panic(fmt.Errorf("failed to cast a verified covenant public key")) } - // UnbondingTx has exactly one input and one output so we may re-use the same - // machinery as for slashing tx to verify signature - err = btcDel.BtcUndelegation.UnbondingTx.VerifySignature( - stakingOutputInfo.StakingPkScript, - int64(stakingOutputInfo.StakingAmount), - btcDel.StakingTx.Script, - covenantPK, - req.UnbondingTxSig, - ) + unbondingTxMsg, err := types.ParseBtcTx(btcDel.BtcUndelegation.UnbondingTx) + if err != nil { + panic(fmt.Errorf("failed to parse unbonding tx from existing delegation with hash %s : %v", req.StakingTxHash, err)) + } + + unbondingTxHash := unbondingTxMsg.TxHash().String() + + stakingOutputSpendInfo := ms.mustStakingInfoFromDelegation(ctx, btcDel) + + unbondingPathInfo, err := stakingOutputSpendInfo.UnbondingPathSpendInfo() + + if err != nil { + // our staking info was constructed by using BuildStakingInfo constructor, so if + // this fails, it is a programming error + panic(err) + } + + if err := btcstaking.VerifyTransactionSigWithOutputData( + unbondingTxMsg, + stakingOutput.PkScript, + stakingOutput.Value, + unbondingPathInfo.RevealedLeaf.Script, + covenantPK, + *req.UnbondingTxSig, + ); err != nil { return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) } // 5. Verify signature of slashing tx against unbonding tx output - unbondingOutputInfo, err := btcDel.BtcUndelegation.UnbondingTx.GetBabylonOutputInfo(ms.btcNet) + // unbonding tx always have only one output + unbondingOutput := unbondingTxMsg.TxOut[0] + unbondingOutputSpendInfo := ms.mustUnbondingInfo( + ctx, + btcDel, + uint16(btcDel.BtcUndelegation.UnbondingTime), + btcutil.Amount(unbondingTxMsg.TxOut[0].Value), + ) + + slashingPathInfo, err := unbondingOutputSpendInfo.SlashingPathSpendInfo() + if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed to get unbonding output info from a verified staking tx")) + // our unbonding info was constructed by using BuildStakingInfo constructor, so if + // this fails, it is a programming error + panic(err) } err = btcDel.BtcUndelegation.SlashingTx.VerifySignature( - unbondingOutputInfo.StakingPkScript, - int64(unbondingOutputInfo.StakingAmount), - btcDel.BtcUndelegation.UnbondingTx.Script, + unbondingOutput.PkScript, + unbondingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, covenantPK, req.SlashingUnbondingTxSig, ) @@ -529,104 +700,25 @@ func (ms msgServer) AddCovenantUnbondingSigs( return nil, types.ErrUnbodningInvalidValidatorSig.Wrap(err.Error()) } - // all good, add signature to BTC delegation and set it back to KVStore + // all good, add signature to BTC undelegation and set it back to KVStore if err := ms.AddCovenantSigsToUndelegation( ctx, - req.ValPk, - req.DelPk, req.StakingTxHash, req.UnbondingTxSig, req.SlashingUnbondingTxSig); err != nil { panic("failed to set BTC delegation that has passed verification") } - // if the BTC undelegation has validator sig, then after above operations the - // BTC delegation will become unbonded - if btcDel.BtcUndelegation.HasValidatorSig() { - event := &types.EventUnbondedBTCDelegation{ - BtcPk: btcDel.BtcPk, - ValBtcPkList: btcDel.ValBtcPkList, - StakingTxHash: req.StakingTxHash, - UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), - FromState: types.BTCDelegationStatus_UNBONDING, - } - if err := ctx.EventManager().EmitTypedEvent(event); err != nil { - panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) - } - } - - return nil, nil -} - -func (ms msgServer) AddValidatorUnbondingSig( - goCtx context.Context, - req *types.MsgAddValidatorUnbondingSig) (*types.MsgAddValidatorUnbondingSigResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - - // 1. Check that delegation even exists for provided params - btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) - if err != nil { - return nil, err - } - - btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := btcDel.GetStatus(btcTip.Height, wValue) - - // 2. Check that we are in proper status - if status != types.BTCDelegationStatus_UNBONDING { - return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) + event := &types.EventUnbondedBTCDelegation{ + BtcPk: btcDel.BtcPk, + ValBtcPkList: btcDel.ValBtcPkList, + StakingTxHash: req.StakingTxHash, + UnbondingTxHash: unbondingTxHash, + FromState: types.BTCDelegationStatus_UNBONDING, } - // 3. Check that we did not recevie validator signature yet - if btcDel.BtcUndelegation.HasValidatorSig() { - return nil, types.ErrUnbondingDuplicatedValidatorSig - } - - // 4. Verify signature of unbonding tx against staking tx output - stakingOutputInfo, err := btcDel.StakingTx.GetBabylonOutputInfo(ms.btcNet) - if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed to get staking output info from a verified staking tx")) - } - - validatorPK, err := req.ValPk.ToBTCPK() - - if err != nil { - panic(fmt.Errorf("failed to cast a verified validator public key")) - } - - // UnbondingTx has exactly one input and one output so we may re-use the same - // machinery as for slashing tx to verify signature - err = btcDel.BtcUndelegation.UnbondingTx.VerifySignature( - stakingOutputInfo.StakingPkScript, - int64(stakingOutputInfo.StakingAmount), - btcDel.StakingTx.Script, - validatorPK, - req.UnbondingTxSig, - ) - if err != nil { - return nil, types.ErrUnbodningInvalidValidatorSig.Wrap(err.Error()) - } - - // all good, add signature to BTC delegation and set it back to KVStore - if err := ms.AddValidatorSigToUndelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.UnbondingTxSig); err != nil { - panic("failed to set BTC delegation that has passed verification") - } - - // if the BTC undelegation has covenant sigs, then after above operations the - // BTC delegation will become unbonded - if btcDel.BtcUndelegation.HasCovenantSigs() { - event := &types.EventUnbondedBTCDelegation{ - BtcPk: btcDel.BtcPk, - ValBtcPkList: btcDel.ValBtcPkList, - StakingTxHash: req.StakingTxHash, - UnbondingTxHash: btcDel.BtcUndelegation.UnbondingTx.MustGetTxHashStr(), - FromState: types.BTCDelegationStatus_UNBONDING, - } - if err := ctx.EventManager().EmitTypedEvent(event); err != nil { - panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) - } + if err := ctx.EventManager().EmitTypedEvent(event); err != nil { + panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) } return nil, nil diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index bb79bd7d6..de2bd86d5 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -150,22 +151,22 @@ func createDelegation( require.NoError(t, err) stakingTimeBlocks := stakingTime stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + + testStakingInfo := datagen.GenBTCStakingSlashingTx( r, + t, net, delSK, []*btcec.PublicKey{validatorPK}, []*btcec.PublicKey{covenantPK}, + 1, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, slashingRate, ) require.NoError(t, err) - // get msgTx - stakingMsgTx, err := stakingTx.ToMsgTx() - require.NoError(t, err) - stakingTxHash := stakingTx.MustGetTxHashStr() + stakingTxHash := testStakingInfo.StakingTx.TxHash().String() // random signer signer := datagen.GenRandomAccount().Address @@ -177,9 +178,12 @@ func createDelegation( require.NoError(t, err) // generate staking tx info prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) - btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, testStakingInfo.StakingTx) btcHeader := btcHeaderWithProof.HeaderBytes - txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) + serializedStakingTx, err := types.SerializeBtcTx(testStakingInfo.StakingTx) + require.NoError(t, err) + + txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, btcHeaderWithProof.SpvProof.MerkleNodes) // mock for testing k-deep stuff btccKeeper.EXPECT().GetPowLimit().Return(net.PowLimit).AnyTimes() @@ -187,24 +191,34 @@ func createDelegation( btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) + slashignSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + // generate proper delegator sig - delegatorSig, err := slashingTx.Sign( - stakingMsgTx, - stakingTx.Script, + delegatorSig, err := testStakingInfo.SlashingTx.Sign( + testStakingInfo.StakingTx, + 0, + slashignSpendInfo.RevealedLeaf.Script, delSK, net, ) require.NoError(t, err) + stakerPk := delSK.PubKey() + stPk := bbn.NewBIP340PubKeyFromBTCPK(stakerPk) + // all good, construct and send MsgCreateBTCDelegation message msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - Pop: pop, - StakingTx: stakingTx, - StakingTxInfo: txInfo, - SlashingTx: slashingTx, - DelegatorSig: delegatorSig, + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + StakerBtcPk: stPk, + ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSig: delegatorSig, } _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) require.NoError(t, err) @@ -223,12 +237,32 @@ func createCovenantSig( msgCreateBTCDel *types.MsgCreateBTCDelegation, delegation *types.BTCDelegation, ) { - stakingMsgTx, err := msgCreateBTCDel.StakingTx.ToMsgTx() + stakingTx, err := types.ParseBtcTx(delegation.StakingTx) + require.NoError(t, err) + stakingTxHash := stakingTx.TxHash().String() + + cPk := covenantSK.PubKey() + + vPK := delegation.ValBtcPkList[0].MustToBTCPK() + + info, err := btcstaking.BuildStakingInfo( + delegation.BtcPk.MustToBTCPK(), + []*btcec.PublicKey{vPK}, + []*btcec.PublicKey{cPk}, + 1, + delegation.GetStakingTime(), + btcutil.Amount(delegation.TotalSat), + net, + ) + + require.NoError(t, err) + slashingPathInfo, err := info.SlashingPathSpendInfo() require.NoError(t, err) - stakingTxHash := msgCreateBTCDel.StakingTx.MustGetTxHashStr() + covenantSig, err := msgCreateBTCDel.SlashingTx.Sign( - stakingMsgTx, - msgCreateBTCDel.StakingTx.Script, + stakingTx, + 0, + slashingPathInfo.RevealedLeaf.Script, covenantSK, net, ) @@ -268,7 +302,7 @@ func getDelegationAndCheckValues( require.NoError(t, err) require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) - require.Equal(t, msgCreateBTCDel.StakingTx, actualDel.StakingTx) + require.Equal(t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx) require.Equal(t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) // ensure the BTC delegation in DB is correctly formatted err = actualDel.ValidateBasic() @@ -298,36 +332,49 @@ func createUndelegation( stkOutputIdx := uint32(0) defaultParams := btcctypes.DefaultParams() - unbondingTx, slashUnbondingTx, err := datagen.GenBTCUnbondingSlashingTx( + unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 + unbondingValue := int64(actualDel.TotalSat) - 1000 + + testUnbondingInfo := datagen.GenBTCUnbondingSlashingTx( r, + t, net, delSK, []*btcec.PublicKey{validatorPK}, []*btcec.PublicKey{covenantPK}, + 1, wire.NewOutPoint(stkTxHash, stkOutputIdx), - uint16(defaultParams.CheckpointFinalizationTimeout)+1, - int64(actualDel.TotalSat)-1000, + unbondingTime, + unbondingValue, slashingAddress, changeAddress, slashingRate, ) require.NoError(t, err) // random signer signer := datagen.GenRandomAccount().Address - unbondingTxMsg, err := unbondingTx.ToMsgTx() + unbondingTxMsg := testUnbondingInfo.UnbondingTx + + unbondingSlashingPathInfo, err := testUnbondingInfo.UnbondingInfo.SlashingPathSpendInfo() require.NoError(t, err) - sig, err := slashUnbondingTx.Sign( + sig, err := testUnbondingInfo.SlashingTx.Sign( unbondingTxMsg, - unbondingTx.Script, + 0, + unbondingSlashingPathInfo.RevealedLeaf.Script, delSK, net, ) require.NoError(t, err) + serializedUnbondingTx, err := types.SerializeBtcTx(testUnbondingInfo.UnbondingTx) + require.NoError(t, err) + msg := &types.MsgBTCUndelegate{ Signer: signer, - UnbondingTx: unbondingTx, - SlashingTx: slashUnbondingTx, + UnbondingTx: serializedUnbondingTx, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: unbondingValue, + SlashingTx: testUnbondingInfo.SlashingTx, DelegatorSlashingSig: sig, } btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) @@ -438,22 +485,23 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingTx( r, + t, net, delSK, []*btcec.PublicKey{validatorPK}, []*btcec.PublicKey{covenantPK}, + 1, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), bsKeeper.GetParams(ctx).SlashingRate, ) - require.NoError(t, err) // get msgTx - stakingMsgTx, err := stakingTx.ToMsgTx() + stakingMsgTx := testStakingInfo.StakingTx + serializedStakingTx, err := types.SerializeBtcTx(stakingMsgTx) require.NoError(t, err) - // random signer signer := datagen.GenRandomAccount().Address // random Babylon SK @@ -466,12 +514,20 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx) btcHeader := btcHeaderWithProof.HeaderBytes - txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, stakingTx.Tx, btcHeaderWithProof.SpvProof.MerkleNodes) + txInfo := btcctypes.NewTransactionInfo( + &btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, + serializedStakingTx, + btcHeaderWithProof.SpvProof.MerkleNodes, + ) + + slashingPathInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) // generate proper delegator sig - delegatorSig, err := slashingTx.Sign( + delegatorSig, err := testStakingInfo.SlashingTx.Sign( stakingMsgTx, - stakingTx.Script, + 0, + slashingPathInfo.RevealedLeaf.Script, delSK, net, ) @@ -479,13 +535,16 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // all good, construct and send MsgCreateBTCDelegation message msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - Pop: pop, - StakingTx: stakingTx, - StakingTxInfo: txInfo, - SlashingTx: slashingTx, - DelegatorSig: delegatorSig, + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + StakerBtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSig: delegatorSig, } _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) require.Error(t, err) @@ -554,11 +613,10 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.ValidatorUnbondingSig) }) } -func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { +func FuzzAddCovenantSigToUnbonding(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -577,7 +635,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - valSk, validatorPK, _ := createValidator(t, r, goCtx, ms) + _, validatorPK, _ := createValidator(t, r, goCtx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, r, @@ -615,49 +673,59 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { require.NoError(t, err) require.NotNil(t, del.BtcUndelegation) - // Check sending validator signature - stakingTxMsg, err := del.StakingTx.ToMsgTx() + stakingTx, err := types.ParseBtcTx(del.StakingTx) require.NoError(t, err) - ubondingTxSignatureValidator, err := undelegateMsg.UnbondingTx.Sign( - stakingTxMsg, - del.StakingTx.Script, - valSk, + + unbondingTx, err := types.ParseBtcTx(del.BtcUndelegation.UnbondingTx) + require.NoError(t, err) + + // Check sending covenant signatures + // unbonding tx spends staking tx + stakingInfo, err := btcstaking.BuildStakingInfo( + del.BtcPk.MustToBTCPK(), + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, + 1, + uint16(del.GetStakingTime()), + btcutil.Amount(del.TotalSat), net, ) require.NoError(t, err) - msg := types.MsgAddValidatorUnbondingSig{ - Signer: datagen.GenRandomAccount().Address, - ValPk: &del.ValBtcPkList[0], - DelPk: del.BtcPk, - StakingTxHash: stakingTxHash, - UnbondingTxSig: ubondingTxSignatureValidator, - } - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.AddValidatorUnbondingSig(goCtx, &msg) - require.NoError(t, err) - delWithValSig, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) + stakingUnbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() require.NoError(t, err) - require.NotNil(t, delWithValSig.BtcUndelegation) - require.NotNil(t, delWithValSig.BtcUndelegation.ValidatorUnbondingSig) - // Check sending covenant signatures - // unbonding tx spends staking tx - unbondingTxSignatureCovenant, err := undelegateMsg.UnbondingTx.Sign( - stakingTxMsg, - del.StakingTx.Script, + unbondingTxSignatureCovenant, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + del.StakingOutputIdx, + stakingUnbondingPathInfo.RevealedLeaf.Script, covenantSK, net, ) - require.NoError(t, err) - unbondingTxMsg, err := undelegateMsg.UnbondingTx.ToMsgTx() + covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(unbondingTxSignatureCovenant) require.NoError(t, err) // slash unbodning tx spends unbonding tx + unbondingInfo, err := btcstaking.BuildUnbondingInfo( + del.BtcPk.MustToBTCPK(), + []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{covenantPK}, + 1, + uint16(del.BtcUndelegation.GetUnbondingTime()), + btcutil.Amount(unbondingTx.TxOut[0].Value), + net, + ) + require.NoError(t, err) + + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.Sign( - unbondingTxMsg, - undelegateMsg.UnbondingTx.Script, + unbondingTx, + 0, + unbondingSlashingPathInfo.RevealedLeaf.Script, covenantSK, net, ) @@ -668,7 +736,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { ValPk: &del.ValBtcPkList[0], DelPk: del.BtcPk, StakingTxHash: stakingTxHash, - UnbondingTxSig: unbondingTxSignatureCovenant, + UnbondingTxSig: &covenantUnbondingSig, SlashingUnbondingTxSig: slashUnbondingTxSignatureCovenant, } @@ -679,9 +747,7 @@ func FuzzAddCovenantAndValidatorSignaturesToUnbondind(f *testing.F) { delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.ValidatorUnbondingSig) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSig) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSig) - }) } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 9fe9734c2..1f768361c 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -65,9 +65,11 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*btcVals[i].BtcPk}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, slashingRate, @@ -231,9 +233,11 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*valBTCPK}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -333,9 +337,11 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*valBTCPK}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -379,9 +385,11 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, + t, []bbn.BIP340PubKey{*activatedValBTCPK}, delSK, []*btcec.PrivateKey{covenantSK}, + 1, slashingAddress.String(), changeAddress.String(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 9b4289986..d8b90c4e8 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -3,7 +3,6 @@ package types import ( "bytes" "encoding/hex" - "fmt" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" @@ -81,12 +80,7 @@ func (tx *BTCSlashingTx) ToHexStr() string { } func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { - var msgTx wire.MsgTx - rbuf := bytes.NewReader(*tx) - if err := msgTx.Deserialize(rbuf); err != nil { - return nil, err - } - return &msgTx, nil + return ParseBtcTx(*tx) } func (tx *BTCSlashingTx) Validate( @@ -112,7 +106,12 @@ func (tx *BTCSlashingTx) Validate( } // Sign generates a signature on the slashing tx signed by staker, validator or covenant -func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk *btcec.PrivateKey, net *chaincfg.Params) (*bbn.BIP340Signature, error) { +func (tx *BTCSlashingTx) Sign( + stakingMsgTx *wire.MsgTx, + spendOutputIndex uint32, + scriptPath []byte, + sk *btcec.PrivateKey, + net *chaincfg.Params) (*bbn.BIP340Signature, error) { msgTx, err := tx.ToMsgTx() if err != nil { return nil, err @@ -120,8 +119,9 @@ func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk schnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( msgTx, stakingMsgTx, + spendOutputIndex, + scriptPath, sk, - stakingScript, net, ) if err != nil { @@ -132,7 +132,12 @@ func (tx *BTCSlashingTx) Sign(stakingMsgTx *wire.MsgTx, stakingScript []byte, sk } // VerifySignature verifies a signature on the slashing tx signed by staker, validator or covenant -func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { +func (tx *BTCSlashingTx) VerifySignature( + stakingPkScript []byte, + stakingAmount int64, + stakingScript []byte, + pk *btcec.PublicKey, + sig *bbn.BIP340Signature) error { msgTx, err := tx.ToMsgTx() if err != nil { return err @@ -146,59 +151,3 @@ func (tx *BTCSlashingTx) VerifySignature(stakingPkScript []byte, stakingAmount i *sig, ) } - -// ToMsgTxWithWitness generates a BTC slashing tx with witness from -// - the staking tx -// - validator signature -// - delegator signature -// - covenant signature -func (tx *BTCSlashingTx) ToMsgTxWithWitness(stakingTx *BabylonBTCTaprootTx, valSig, delSig, covenantSig *bbn.BIP340Signature) (*wire.MsgTx, error) { - // get staking script - stakingScript := stakingTx.Script - - // get Schnorr signatures - valSchnorrSig, err := valSig.ToBTCSig() - if err != nil { - return nil, fmt.Errorf("failed to convert BTC validator signature to Schnorr signature format: %w", err) - } - delSchnorrSig, err := delSig.ToBTCSig() - if err != nil { - return nil, fmt.Errorf("failed to convert BTC delegator signature to Schnorr signature format: %w", err) - } - covenantSchnorrSig, err := covenantSig.ToBTCSig() - if err != nil { - return nil, fmt.Errorf("failed to convert covenant signature to Schnorr signature format: %w", err) - } - - // build witness from each signature - valWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, valSchnorrSig) - if err != nil { - return nil, fmt.Errorf("failed to build witness for BTC validator: %w", err) - } - delWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, delSchnorrSig) - if err != nil { - return nil, fmt.Errorf("failed to build witness for BTC delegator: %w", err) - } - covenantWitness, err := btcstaking.NewWitnessFromStakingScriptAndSignature(stakingScript, covenantSchnorrSig) - if err != nil { - return nil, fmt.Errorf("failed to build witness for covenant: %w", err) - } - - // To Construct valid witness, for multisig case we need: - // - covenant signature - witnessCovenant[0] - // - validator signature - witnessValidator[0] - // - staker signature - witnessStaker[0] - // - empty signature - which is just an empty byte array which signals we are going to use multisig. - // This must be signature on top of the stack. - // - whole script - witnessStaker[1] (any other witness[1] will work as well) - // - control block - witnessStaker[2] (any other witness[2] will work as well) - slashingMsgTx, err := tx.ToMsgTx() - if err != nil { - return nil, fmt.Errorf("failed to convert slashing tx to Bitcoin format: %w", err) - } - slashingMsgTx.TxIn[0].Witness = [][]byte{ - covenantWitness[0], valWitness[0], delWitness[0], []byte{}, delWitness[1], delWitness[2], - } - - return slashingMsgTx, nil -} diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index e7edea0ad..c26ce5b61 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/btcstaking" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" "github.com/btcsuite/btcd/btcec/v2" @@ -43,44 +44,63 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) // generate staking/slashing tx - stakingTx, slashingTx, err := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingTx( r, + t, net, delSK, []*btcec.PublicKey{valPK}, []*btcec.PublicKey{covenantPK}, + 1, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), slashingRate, ) + + slashingTx := testStakingInfo.SlashingTx + stakingMsgTx := testStakingInfo.StakingTx + stakingPkScript := testStakingInfo.StakingInfo.StakingOutput.PkScript + + slashingScriptInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - stakingOutInfo, err := stakingTx.GetBabylonOutputInfo(net) - require.NoError(t, err) - stakingPkScript := stakingOutInfo.StakingPkScript - stakingMsgTx, err := stakingTx.ToMsgTx() - require.NoError(t, err) + slashingScript := slashingScriptInfo.RevealedLeaf.Script // sign slashing tx - valSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, valSK, net) + valSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, valSK, net) require.NoError(t, err) - delSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, delSK, net) + delSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, delSK, net) require.NoError(t, err) - covenantSig, err := slashingTx.Sign(stakingMsgTx, stakingTx.Script, covenantSK, net) + covenantSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, covenantSK, net) require.NoError(t, err) // verify signatures first - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, valPK, valSig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, valPK, valSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, delPK, delSig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, delPK, delSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, stakingTx.Script, covenantPK, covenantSig) + err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, covenantPK, covenantSig) + require.NoError(t, err) + + stakerSigBytes := delSig.MustMarshal() + validatorSigBytes := valSig.MustMarshal() + covSigBytes := covenantSig.MustMarshal() + + // TODO: use comittee + witness, err := btcstaking.CreateBabylonWitness( + [][]byte{ + covSigBytes, + validatorSigBytes, + stakerSigBytes, + }, + slashingScriptInfo, + ) require.NoError(t, err) - // build slashing tx with witness - slashingMsgTxWithWitness, err := slashingTx.ToMsgTxWithWitness(stakingTx, valSig, delSig, covenantSig) + slashingMsgTxWithWitness, err := slashingTx.ToMsgTx() require.NoError(t, err) + slashingMsgTxWithWitness.TxIn[0].Witness = witness // verify slashing tx with witness prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( stakingPkScript, stakingValue, diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 8c7bb0357..5b2fc5380 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -1,18 +1,11 @@ package types import ( - "bytes" - "encoding/hex" "fmt" "sort" - "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, error) { @@ -70,7 +63,7 @@ func (d *BTCDelegation) ValidateBasic() error { if len(d.ValBtcPkList) == 0 { return fmt.Errorf("empty list of BTC validator PKs") } - if existsDup(d.ValBtcPkList) { + if ExistsDup(d.ValBtcPkList) { return fmt.Errorf("list of BTC validator PKs has duplication") } if d.StakingTx == nil { @@ -84,7 +77,7 @@ func (d *BTCDelegation) ValidateBasic() error { } // ensure staking tx is correctly formatted - if err := d.StakingTx.ValidateBasic(); err != nil { + if _, err := ParseBtcTx(d.StakingTx); err != nil { return err } if err := d.Pop.ValidateBasic(); err != nil { @@ -103,12 +96,8 @@ func (ud *BTCUndelegation) HasCovenantSigs() bool { return ud.CovenantSlashingSig != nil && ud.CovenantUnbondingSig != nil } -func (ud *BTCUndelegation) HasValidatorSig() bool { - return ud.ValidatorUnbondingSig != nil -} - func (ud *BTCUndelegation) HasAllSignatures() bool { - return ud.HasCovenantSigs() && ud.HasValidatorSig() + return ud.HasCovenantSigs() } // GetStatus returns the status of the BTC Delegation based on a BTC height and a w value @@ -155,186 +144,23 @@ func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { } func (d *BTCDelegation) GetStakingTxHash() (chainhash.Hash, error) { - return d.StakingTx.GetTxHash() -} - -func (d *BTCDelegation) MustGetStakingTxHash() chainhash.Hash { - return d.StakingTx.MustGetTxHash() -} - -// GetStakingTxHashStr returns the staking tx hash of the BTC delegation in hex string -// it can be used for uniquely identifying a BTC delegation -func (d *BTCDelegation) GetStakingTxHashStr() (string, error) { - return d.StakingTx.GetTxHashStr() -} - -func (d *BTCDelegation) MustGetStakingTxHashStr() string { - return d.StakingTx.MustGetTxHashStr() -} + parsed, err := ParseBtcTx(d.StakingTx) -func NewBabylonTaprootTxFromHex(txHex string) (*BabylonBTCTaprootTx, error) { - txBytes, err := hex.DecodeString(txHex) - if err != nil { - return nil, err - } - var tx BabylonBTCTaprootTx - if err := tx.Unmarshal(txBytes); err != nil { - return nil, err - } - return &tx, nil -} - -func (tx *BabylonBTCTaprootTx) ToHexStr() (string, error) { - txBytes, err := tx.Marshal() - if err != nil { - return "", err - } - return hex.EncodeToString(txBytes), nil -} - -func (tx *BabylonBTCTaprootTx) Equals(tx2 *BabylonBTCTaprootTx) bool { - return bytes.Equal(tx.Tx, tx2.Tx) && bytes.Equal(tx.Script, tx2.Script) -} - -func (tx *BabylonBTCTaprootTx) ValidateBasic() error { - // unmarshal tx bytes to MsgTx - var msgTx wire.MsgTx - rbuf := bytes.NewReader(tx.Tx) - if err := msgTx.Deserialize(rbuf); err != nil { - return err - } - - // parse staking script - if _, err := btcstaking.ParseStakingTransactionScript(tx.Script); err != nil { - return err - } - - return nil -} - -func (tx *BabylonBTCTaprootTx) ToMsgTx() (*wire.MsgTx, error) { - var msgTx wire.MsgTx - rbuf := bytes.NewReader(tx.Tx) - if err := msgTx.Deserialize(rbuf); err != nil { - return nil, err - } - return &msgTx, nil -} - -func (tx *BabylonBTCTaprootTx) GetTxHash() (chainhash.Hash, error) { - msgTx, err := tx.ToMsgTx() if err != nil { return chainhash.Hash{}, err } - return msgTx.TxHash(), nil -} -func (tx *BabylonBTCTaprootTx) MustGetTxHash() chainhash.Hash { - txHash, err := tx.GetTxHash() - if err != nil { - panic(err) - } - return txHash + return parsed.TxHash(), nil } -func (tx *BabylonBTCTaprootTx) GetTxHashStr() (string, error) { - txHash, err := tx.GetTxHash() - if err != nil { - return "", err - } - return txHash.String(), nil -} +func (d *BTCDelegation) MustGetStakingTxHash() chainhash.Hash { + txHash, err := d.GetStakingTxHash() -func (tx *BabylonBTCTaprootTx) MustGetTxHashStr() string { - txHashStr, err := tx.GetTxHashStr() if err != nil { panic(err) } - return txHashStr -} - -func (tx *BabylonBTCTaprootTx) GetScriptData() (*btcstaking.StakingScriptData, error) { - return btcstaking.ParseStakingTransactionScript(tx.Script) -} - -func (tx *BabylonBTCTaprootTx) GetBabylonOutputInfo(net *chaincfg.Params) (*btcstaking.StakingOutputInfo, error) { - var ( - scriptData *btcstaking.StakingScriptData - outValue int64 - err error - ) - - // unmarshal tx bytes to MsgTx - var msgTx wire.MsgTx - rbuf := bytes.NewReader(tx.Tx) - if err := msgTx.Deserialize(rbuf); err != nil { - return nil, err - } - - // parse staking script - scriptData, err = btcstaking.ParseStakingTransactionScript(tx.Script) - if err != nil { - return nil, err - } - expectedPkScript, err := btcstaking.BuildUnspendableTaprootPkScript(tx.Script, net) - if err != nil { - return nil, err - } - - // find the output that corresponds to the staking script - for _, txOut := range msgTx.TxOut { - if bytes.Equal(expectedPkScript, txOut.PkScript) { - outValue = txOut.Value - } - } - if outValue == 0 { - // not found - return nil, fmt.Errorf("the tx contains no StakingTransactionScript") - } - return &btcstaking.StakingOutputInfo{ - StakingScriptData: scriptData, - StakingPkScript: expectedPkScript, - StakingAmount: btcutil.Amount(outValue), - }, nil -} - -func (tx *BabylonBTCTaprootTx) Sign( - fundingTx *wire.MsgTx, - fundingTxScript []byte, - sk *btcec.PrivateKey, - net *chaincfg.Params) (*bbn.BIP340Signature, error) { - msgTx, err := tx.ToMsgTx() - if err != nil { - return nil, err - } - schnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - msgTx, - fundingTx, - sk, - fundingTxScript, - net, - ) - if err != nil { - return nil, err - } - sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) - return &sig, nil -} - -func (tx *BabylonBTCTaprootTx) VerifySignature(stakingPkScript []byte, stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { - msgTx, err := tx.ToMsgTx() - if err != nil { - return err - } - return btcstaking.VerifyTransactionSigWithOutputData( - msgTx, - stakingPkScript, - stakingAmount, - stakingScript, - pk, - *sig, - ) + return txHash } // FilterTopNBTCValidators returns the top n validators based on VotingPower. @@ -355,7 +181,7 @@ func FilterTopNBTCValidators(validators []*BTCValidatorWithMeta, n uint32) []*BT return validators[:n] } -func existsDup(btcPKs []bbn.BIP340PubKey) bool { +func ExistsDup(btcPKs []bbn.BIP340PubKey) bool { seen := make(map[string]struct{}) for _, btcPK := range btcPKs { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 1df7af0b1..1e229d75e 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -273,25 +273,27 @@ type BTCDelegation struct { // quantified in satoshi TotalSat uint64 `protobuf:"varint,7,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` // staking_tx is the staking tx - StakingTx *BabylonBTCTaprootTx `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + StakingTx []byte `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` + // staking_output_idx is the index of the staking output in the staking tx + StakingOutputIdx uint32 `protobuf:"varint,9,opt,name=staking_output_idx,json=stakingOutputIdx,proto3" json:"staking_output_idx,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. - SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` + DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` // covenant_sig is the signature signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` + CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,12,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object - BtcUndelegation *BTCUndelegation `protobuf:"bytes,12,opt,name=btc_undelegation,json=btcUndelegation,proto3" json:"btc_undelegation,omitempty"` + BtcUndelegation *BTCUndelegation `protobuf:"bytes,13,opt,name=btc_undelegation,json=btcUndelegation,proto3" json:"btc_undelegation,omitempty"` } func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } @@ -362,13 +364,20 @@ func (m *BTCDelegation) GetTotalSat() uint64 { return 0 } -func (m *BTCDelegation) GetStakingTx() *BabylonBTCTaprootTx { +func (m *BTCDelegation) GetStakingTx() []byte { if m != nil { return m.StakingTx } return nil } +func (m *BTCDelegation) GetStakingOutputIdx() uint32 { + if m != nil { + return m.StakingOutputIdx + } + return 0 +} + func (m *BTCDelegation) GetBtcUndelegation() *BTCUndelegation { if m != nil { return m.BtcUndelegation @@ -381,31 +390,28 @@ type BTCUndelegation struct { // unbonding_tx is the transaction which will transfer the funds from staking // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. - UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // unbonding_time describes how long the funds will be locked in the unbonding output + UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // slashing_tx is the slashing tx for unbodning transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. - SlashingTx *BTCSlashingTx `protobuf:"bytes,2,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbodning tx output. - DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` + CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon and after // validator sig will be provided by validator // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` - // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon - // TODO: this is no longer needed - ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` + CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` } func (m *BTCUndelegation) Reset() { *m = BTCUndelegation{} } @@ -441,30 +447,31 @@ func (m *BTCUndelegation) XXX_DiscardUnknown() { var xxx_messageInfo_BTCUndelegation proto.InternalMessageInfo -func (m *BTCUndelegation) GetUnbondingTx() *BabylonBTCTaprootTx { +func (m *BTCUndelegation) GetUnbondingTx() []byte { if m != nil { return m.UnbondingTx } return nil } +func (m *BTCUndelegation) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + // BTCUndelegationInfo provides all necessary info about the undeleagation type BTCUndelegationInfo struct { // unbonding_tx is the transaction which will transfer the funds from staking // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. - UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // validator_unbonding_sig is the signature on the unbonding tx - // by the validator (i.e., SK corresponding to the pk of the validator that the staker delegates to) - // It must be provided after processing undelagate message by Babylon - // It will be nil if validator didn't sign it yet - // TODO: this is no longer needed - ValidatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=validator_unbonding_sig,json=validatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"validator_unbonding_sig,omitempty"` + UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // covenant_unbonding_sig is the signature on the unbonding tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // it will be nil if covenant didn't sign it yet // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` + CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -500,7 +507,7 @@ func (m *BTCUndelegationInfo) XXX_DiscardUnknown() { var xxx_messageInfo_BTCUndelegationInfo proto.InternalMessageInfo -func (m *BTCUndelegationInfo) GetUnbondingTx() *BabylonBTCTaprootTx { +func (m *BTCUndelegationInfo) GetUnbondingTx() []byte { if m != nil { return m.UnbondingTx } @@ -597,62 +604,6 @@ func (m *BTCDelegatorDelegationIndex) GetStakingTxHashList() [][]byte { return nil } -// BabylonBtcTaprootTx is the bitcoin transaction which contains script recognized by Babylon and -// transaction which commits to the provided script. -type BabylonBTCTaprootTx struct { - // tx is the transaction bytes - Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` - // script is the script recognized by Babylon - Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` -} - -func (m *BabylonBTCTaprootTx) Reset() { *m = BabylonBTCTaprootTx{} } -func (m *BabylonBTCTaprootTx) String() string { return proto.CompactTextString(m) } -func (*BabylonBTCTaprootTx) ProtoMessage() {} -func (*BabylonBTCTaprootTx) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{7} -} -func (m *BabylonBTCTaprootTx) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *BabylonBTCTaprootTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_BabylonBTCTaprootTx.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *BabylonBTCTaprootTx) XXX_Merge(src proto.Message) { - xxx_messageInfo_BabylonBTCTaprootTx.Merge(m, src) -} -func (m *BabylonBTCTaprootTx) XXX_Size() int { - return m.Size() -} -func (m *BabylonBTCTaprootTx) XXX_DiscardUnknown() { - xxx_messageInfo_BabylonBTCTaprootTx.DiscardUnknown(m) -} - -var xxx_messageInfo_BabylonBTCTaprootTx proto.InternalMessageInfo - -func (m *BabylonBTCTaprootTx) GetTx() []byte { - if m != nil { - return m.Tx - } - return nil -} - -func (m *BabylonBTCTaprootTx) GetScript() []byte { - if m != nil { - return m.Script - } - return nil -} - // SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK type SignatureInfo struct { Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` @@ -663,7 +614,7 @@ func (m *SignatureInfo) Reset() { *m = SignatureInfo{} } func (m *SignatureInfo) String() string { return proto.CompactTextString(m) } func (*SignatureInfo) ProtoMessage() {} func (*SignatureInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_3851ae95ccfaf7db, []int{8} + return fileDescriptor_3851ae95ccfaf7db, []int{7} } func (m *SignatureInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -701,7 +652,6 @@ func init() { proto.RegisterType((*BTCUndelegationInfo)(nil), "babylon.btcstaking.v1.BTCUndelegationInfo") proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*BTCDelegatorDelegationIndex)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegationIndex") - proto.RegisterType((*BabylonBTCTaprootTx)(nil), "babylon.btcstaking.v1.BabylonBTCTaprootTx") proto.RegisterType((*SignatureInfo)(nil), "babylon.btcstaking.v1.SignatureInfo") } @@ -710,74 +660,73 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1063 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdb, 0x6e, 0x1b, 0x45, - 0x18, 0xce, 0xee, 0x3a, 0x6e, 0xfc, 0x7b, 0x43, 0xdc, 0xc9, 0xa1, 0x26, 0x15, 0x76, 0x30, 0x55, - 0x64, 0x55, 0xd4, 0x26, 0xe9, 0x41, 0x05, 0x09, 0xa4, 0x3a, 0x0e, 0xd4, 0x6a, 0x93, 0x9a, 0xb5, - 0x53, 0x0e, 0x02, 0xac, 0x3d, 0x4c, 0xec, 0x95, 0x9d, 0x9d, 0x65, 0x67, 0x6c, 0x9c, 0x7b, 0x1e, - 0x80, 0x87, 0xe0, 0x82, 0x07, 0xe0, 0x21, 0x7a, 0x59, 0x71, 0x55, 0xe5, 0xc2, 0x42, 0xc9, 0x8b, - 0xa0, 0x9d, 0x9d, 0x3d, 0x24, 0x4d, 0x02, 0xc1, 0xe9, 0x95, 0xfd, 0xcf, 0x7f, 0xfe, 0xbe, 0x4f, - 0x3b, 0x03, 0xeb, 0x86, 0x6e, 0x1c, 0x0e, 0x88, 0x53, 0x35, 0x98, 0x49, 0x99, 0xde, 0xb7, 0x9d, - 0x6e, 0x75, 0xb4, 0x91, 0xb0, 0x2a, 0xae, 0x47, 0x18, 0x41, 0xcb, 0x22, 0xae, 0x92, 0xf0, 0x8c, - 0x36, 0x56, 0x97, 0xba, 0xa4, 0x4b, 0x78, 0x44, 0xd5, 0xff, 0x17, 0x04, 0xaf, 0xbe, 0x6f, 0x12, - 0x7a, 0x40, 0x68, 0x27, 0x70, 0x04, 0x86, 0x70, 0x95, 0x02, 0xab, 0x6a, 0x7a, 0x87, 0x2e, 0x23, - 0x55, 0x8a, 0x4d, 0x77, 0xf3, 0xe1, 0xa3, 0xfe, 0x46, 0xb5, 0x8f, 0x0f, 0xc3, 0x98, 0x3b, 0x22, - 0x26, 0x9e, 0xc7, 0xc0, 0x4c, 0xdf, 0xa8, 0x9e, 0x9a, 0x68, 0xb5, 0x78, 0xfe, 0xe4, 0x2e, 0x71, - 0x83, 0x80, 0xd2, 0x44, 0x01, 0xb5, 0xd6, 0xde, 0x7a, 0xa9, 0x0f, 0x6c, 0x4b, 0x67, 0xc4, 0x43, - 0xdb, 0x90, 0xb5, 0x30, 0x35, 0x3d, 0xdb, 0x65, 0x36, 0x71, 0xf2, 0xd2, 0x9a, 0x54, 0xce, 0x6e, - 0x7e, 0x54, 0x11, 0xf3, 0xc5, 0x5b, 0xf1, 0x6e, 0x95, 0x7a, 0x1c, 0xaa, 0x25, 0xf3, 0xd0, 0xb7, - 0x00, 0x26, 0x39, 0x38, 0xb0, 0x29, 0xf5, 0xab, 0xc8, 0x6b, 0x52, 0x39, 0x53, 0x7b, 0x7c, 0x34, - 0x29, 0xae, 0x77, 0x6d, 0xd6, 0x1b, 0x1a, 0x15, 0x93, 0x1c, 0x54, 0xc3, 0x2d, 0xf9, 0xcf, 0x3d, - 0x6a, 0xf5, 0xab, 0xec, 0xd0, 0xc5, 0xb4, 0x52, 0xc7, 0xe6, 0x5f, 0x7f, 0xde, 0x03, 0xd1, 0xb2, - 0x8e, 0x4d, 0x2d, 0x51, 0x0b, 0x7d, 0x01, 0x20, 0x96, 0xea, 0xb8, 0xfd, 0xbc, 0xc2, 0xe7, 0x2b, - 0x86, 0xf3, 0x05, 0x88, 0x55, 0x22, 0xc4, 0x2a, 0xcd, 0xa1, 0xf1, 0x0c, 0x1f, 0x6a, 0x19, 0x91, - 0xd2, 0xec, 0xa3, 0x1d, 0x48, 0x1b, 0xcc, 0xf4, 0x73, 0x53, 0x6b, 0x52, 0x59, 0xad, 0x3d, 0x3a, - 0x9a, 0x14, 0x37, 0x13, 0x53, 0x89, 0x48, 0xb3, 0xa7, 0xdb, 0x4e, 0x68, 0x88, 0xc1, 0x6a, 0x8d, - 0xe6, 0xfd, 0x07, 0x9f, 0x88, 0x92, 0xb3, 0x06, 0x33, 0x9b, 0x7d, 0xf4, 0x19, 0x28, 0x2e, 0x71, - 0xf3, 0xb3, 0x7c, 0x8e, 0x72, 0xe5, 0x5c, 0x05, 0x54, 0x9a, 0x1e, 0x21, 0xfb, 0x2f, 0xf6, 0x9b, - 0x84, 0x52, 0xcc, 0xb7, 0xd0, 0xfc, 0x24, 0xf4, 0x00, 0x56, 0xe8, 0x40, 0xa7, 0x3d, 0x6c, 0x75, - 0xc2, 0x95, 0x7a, 0xd8, 0xee, 0xf6, 0x58, 0x3e, 0xbd, 0x26, 0x95, 0x53, 0xda, 0x92, 0xf0, 0xd6, - 0x02, 0xe7, 0x53, 0xee, 0x43, 0x1f, 0x03, 0x8a, 0xb2, 0x98, 0x19, 0x66, 0xdc, 0xe0, 0x19, 0xb9, - 0x30, 0x83, 0x99, 0x41, 0x74, 0xe9, 0x57, 0x19, 0x96, 0x92, 0x04, 0x7f, 0x63, 0xb3, 0xde, 0x0e, - 0x66, 0x7a, 0x02, 0x07, 0xe9, 0x3a, 0x70, 0x58, 0x81, 0xb4, 0x98, 0x44, 0xe6, 0x93, 0x08, 0x0b, - 0x7d, 0x08, 0xea, 0x88, 0x30, 0xdb, 0xe9, 0x76, 0x5c, 0xf2, 0x0b, 0xf6, 0x38, 0x61, 0x29, 0x2d, - 0x1b, 0x9c, 0x35, 0xfd, 0xa3, 0x4b, 0x60, 0x48, 0x5d, 0x19, 0x86, 0xd9, 0x0b, 0x60, 0xf8, 0x23, - 0x0d, 0xf3, 0xb5, 0xf6, 0x56, 0x1d, 0x0f, 0x70, 0x57, 0x67, 0x6f, 0xeb, 0x48, 0x9a, 0x42, 0x47, - 0xf2, 0x35, 0xea, 0x48, 0xf9, 0x3f, 0x3a, 0xfa, 0x11, 0x16, 0x46, 0xfa, 0xa0, 0x13, 0x8c, 0xd3, - 0x19, 0xd8, 0xd4, 0x47, 0x4e, 0x99, 0x62, 0x26, 0x75, 0xa4, 0x0f, 0x6a, 0xfe, 0x58, 0xcf, 0x6d, - 0xca, 0x29, 0xa4, 0x4c, 0xf7, 0xd8, 0x69, 0x8c, 0xb3, 0xfc, 0x4c, 0x90, 0xf1, 0x01, 0x00, 0x76, - 0xac, 0xd3, 0xea, 0xcd, 0x60, 0xc7, 0x12, 0xee, 0xdb, 0x90, 0x61, 0x84, 0xe9, 0x83, 0x0e, 0xd5, - 0x43, 0xa5, 0xce, 0xf1, 0x83, 0x96, 0xce, 0x50, 0x03, 0x40, 0xac, 0xd8, 0x61, 0xe3, 0xfc, 0x1c, - 0x07, 0xe0, 0xee, 0x05, 0x00, 0x08, 0x09, 0xd4, 0xda, 0x5b, 0x6d, 0xdd, 0xf5, 0x08, 0x61, 0xed, - 0xb1, 0x96, 0x11, 0xfe, 0xf6, 0x18, 0x6d, 0x42, 0x96, 0x33, 0x2f, 0x6a, 0x65, 0x38, 0x31, 0x37, - 0x8f, 0x26, 0x45, 0x9f, 0xfb, 0x96, 0xf0, 0xb4, 0xc7, 0x1a, 0xd0, 0xe8, 0x3f, 0xfa, 0x09, 0xe6, - 0xad, 0x40, 0x15, 0xc4, 0xeb, 0x50, 0xbb, 0x9b, 0x07, 0x9e, 0xf5, 0xe9, 0xd1, 0xa4, 0xf8, 0xf0, - 0x2a, 0xd0, 0xb5, 0xec, 0xae, 0xa3, 0xb3, 0xa1, 0x87, 0x35, 0x35, 0xaa, 0xd7, 0xb2, 0xbb, 0xe8, - 0x07, 0x50, 0x4d, 0x32, 0xc2, 0x8e, 0xee, 0x30, 0x5e, 0x3e, 0x3b, 0x6d, 0xf9, 0x6c, 0x58, 0xce, - 0xaf, 0xfe, 0x35, 0xe4, 0x7c, 0xda, 0x87, 0x8e, 0x15, 0x29, 0x3b, 0xaf, 0x72, 0x08, 0xd7, 0x2f, - 0x82, 0xb0, 0xbd, 0xb5, 0x97, 0x88, 0xd6, 0x16, 0x0c, 0x66, 0x26, 0x0f, 0x4a, 0x6f, 0x52, 0xb0, - 0x70, 0x26, 0x08, 0xed, 0x80, 0x3a, 0x74, 0x0c, 0xe2, 0x58, 0x02, 0x59, 0xe9, 0xca, 0x2c, 0x65, - 0xa3, 0xfc, 0xb7, 0x79, 0x92, 0xff, 0x0b, 0x4f, 0x04, 0x56, 0x12, 0x3c, 0x85, 0xd9, 0x3e, 0xa2, - 0xca, 0xb4, 0x88, 0x2e, 0xc5, 0x84, 0x89, 0xba, 0x3e, 0xb4, 0x07, 0xb0, 0x1c, 0x13, 0x97, 0xec, - 0x97, 0x9a, 0xb6, 0xdf, 0x62, 0xc4, 0x60, 0xa2, 0x1d, 0x81, 0x95, 0xa8, 0x5d, 0x8c, 0xb5, 0xdf, - 0x6f, 0x76, 0xea, 0xfd, 0xc2, 0xc2, 0x7b, 0x61, 0x5d, 0xbf, 0xe1, 0xcf, 0x70, 0x6b, 0x14, 0xde, - 0x0a, 0x67, 0x3a, 0xa6, 0xa7, 0xed, 0xb8, 0x1c, 0x55, 0x4e, 0xb6, 0x2c, 0xbd, 0x92, 0x61, 0xf1, - 0x8c, 0xb4, 0x1a, 0xce, 0x3e, 0xb9, 0x6e, 0x79, 0x5d, 0xb2, 0x99, 0xfc, 0x6e, 0x36, 0xbb, 0x84, - 0x3d, 0xe5, 0x9d, 0xb0, 0x57, 0x6a, 0xc1, 0xad, 0xf8, 0x3e, 0x23, 0x5e, 0x7c, 0xb1, 0x51, 0xf4, - 0x18, 0x52, 0x16, 0x1e, 0xd0, 0xbc, 0xb4, 0xa6, 0x94, 0xb3, 0x9b, 0x77, 0x2e, 0xfe, 0x0e, 0xc4, - 0x49, 0x1a, 0xcf, 0x28, 0xed, 0xc2, 0xed, 0xf3, 0x8b, 0x36, 0x1c, 0x0b, 0x8f, 0x51, 0x15, 0x96, - 0xe2, 0x2f, 0x75, 0xa7, 0xa7, 0xd3, 0x5e, 0x70, 0xd9, 0xf8, 0x8d, 0x54, 0xed, 0x66, 0xf4, 0x1d, - 0x7e, 0xaa, 0xd3, 0x9e, 0x7f, 0x73, 0x94, 0x3e, 0x87, 0xc5, 0x73, 0xc8, 0x42, 0xef, 0x81, 0x2c, - 0x48, 0x56, 0x35, 0x99, 0x8d, 0xfd, 0xb7, 0x43, 0xf0, 0x72, 0x0c, 0xe8, 0xd1, 0x84, 0x55, 0xfa, - 0x5d, 0x82, 0xf9, 0x08, 0x07, 0x2e, 0x94, 0x2f, 0x41, 0x9e, 0xfa, 0xc1, 0x22, 0xbb, 0x7d, 0xf4, - 0x0c, 0x94, 0x6b, 0x51, 0x83, 0x5f, 0xe5, 0x6e, 0x9b, 0x8b, 0x3a, 0x06, 0xab, 0xc5, 0x74, 0x36, - 0xa4, 0x28, 0x0b, 0x37, 0x9a, 0xdb, 0xbb, 0xf5, 0xc6, 0xee, 0x57, 0xb9, 0x19, 0x04, 0x90, 0x7e, - 0xb2, 0xd5, 0x6e, 0xbc, 0xdc, 0xce, 0x49, 0x68, 0x1e, 0x32, 0x7b, 0xbb, 0xb5, 0x17, 0x81, 0x4b, - 0x46, 0x2a, 0xcc, 0x05, 0xe6, 0x76, 0x3d, 0xa7, 0xa0, 0x1b, 0xa0, 0x3c, 0xd9, 0xfd, 0x2e, 0x97, - 0xaa, 0x3d, 0x7f, 0x75, 0x5c, 0x90, 0x5e, 0x1f, 0x17, 0xa4, 0xbf, 0x8f, 0x0b, 0xd2, 0x6f, 0x27, - 0x85, 0x99, 0xd7, 0x27, 0x85, 0x99, 0x37, 0x27, 0x85, 0x99, 0xef, 0xff, 0x75, 0xe9, 0x71, 0xf2, - 0xb9, 0xcf, 0x07, 0x37, 0xd2, 0xfc, 0xb9, 0x7f, 0xff, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, - 0x08, 0xdb, 0x22, 0xcb, 0x0c, 0x00, 0x00, + // 1043 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdf, 0x6e, 0x1a, 0xc7, + 0x17, 0xf6, 0xb2, 0x18, 0x9b, 0x03, 0xfc, 0x4c, 0x26, 0x8e, 0x7f, 0xd4, 0x51, 0x81, 0xd2, 0xd4, + 0x42, 0x55, 0x03, 0xb5, 0xf3, 0x47, 0x69, 0x2f, 0x2a, 0x05, 0xe3, 0x36, 0x28, 0x89, 0x4d, 0x17, + 0x9c, 0xfe, 0x51, 0xdb, 0xd5, 0xec, 0xee, 0x78, 0x59, 0x01, 0x3b, 0x2b, 0x66, 0xa0, 0xf8, 0xbe, + 0x0f, 0xd0, 0x87, 0xe8, 0x4d, 0x7b, 0xdd, 0x87, 0xe8, 0x65, 0xd4, 0xab, 0xca, 0x17, 0x28, 0xb2, + 0x5f, 0xa4, 0xda, 0xd9, 0x59, 0x76, 0xed, 0xda, 0x6d, 0x52, 0xdc, 0x2b, 0x33, 0x73, 0xce, 0xf9, + 0xce, 0x77, 0xbe, 0xef, 0x78, 0x00, 0xb6, 0x0c, 0x6c, 0x1c, 0x0f, 0xa8, 0x5b, 0x37, 0xb8, 0xc9, + 0x38, 0xee, 0x3b, 0xae, 0x5d, 0x9f, 0x6c, 0xc7, 0x4e, 0x35, 0x6f, 0x44, 0x39, 0x45, 0xb7, 0x64, + 0x5e, 0x2d, 0x16, 0x99, 0x6c, 0x6f, 0xae, 0xdb, 0xd4, 0xa6, 0x22, 0xa3, 0xee, 0x7f, 0x0a, 0x92, + 0x37, 0xdf, 0x32, 0x29, 0x1b, 0x52, 0xa6, 0x07, 0x81, 0xe0, 0x20, 0x43, 0x95, 0xe0, 0x54, 0x37, + 0x47, 0xc7, 0x1e, 0xa7, 0x75, 0x46, 0x4c, 0x6f, 0xe7, 0xc1, 0xc3, 0xfe, 0x76, 0xbd, 0x4f, 0x8e, + 0xc3, 0x9c, 0x3b, 0x32, 0x27, 0xe2, 0x63, 0x10, 0x8e, 0xb7, 0xeb, 0xe7, 0x18, 0x6d, 0x96, 0x2e, + 0x67, 0xee, 0x51, 0x2f, 0x48, 0xa8, 0xcc, 0x54, 0xc8, 0x36, 0xba, 0xbb, 0x2f, 0xf0, 0xc0, 0xb1, + 0x30, 0xa7, 0x23, 0xb4, 0x07, 0x19, 0x8b, 0x30, 0x73, 0xe4, 0x78, 0xdc, 0xa1, 0x6e, 0x41, 0x29, + 0x2b, 0xd5, 0xcc, 0xce, 0xbb, 0x35, 0xc9, 0x2f, 0x9a, 0x4a, 0x74, 0xab, 0x35, 0xa3, 0x54, 0x2d, + 0x5e, 0x87, 0xbe, 0x04, 0x30, 0xe9, 0x70, 0xe8, 0x30, 0xe6, 0xa3, 0x24, 0xca, 0x4a, 0x35, 0xdd, + 0x78, 0x74, 0x32, 0x2b, 0x6d, 0xd9, 0x0e, 0xef, 0x8d, 0x8d, 0x9a, 0x49, 0x87, 0xf5, 0x70, 0x4a, + 0xf1, 0xe7, 0x2e, 0xb3, 0xfa, 0x75, 0x7e, 0xec, 0x11, 0x56, 0x6b, 0x12, 0xf3, 0xf7, 0x5f, 0xef, + 0x82, 0x6c, 0xd9, 0x24, 0xa6, 0x16, 0xc3, 0x42, 0x9f, 0x00, 0xc8, 0xa1, 0x74, 0xaf, 0x5f, 0x50, + 0x05, 0xbf, 0x52, 0xc8, 0x2f, 0x50, 0xac, 0x36, 0x57, 0xac, 0xd6, 0x1e, 0x1b, 0x4f, 0xc9, 0xb1, + 0x96, 0x96, 0x25, 0xed, 0x3e, 0x7a, 0x0e, 0x29, 0x83, 0x9b, 0x7e, 0x6d, 0xb2, 0xac, 0x54, 0xb3, + 0x8d, 0x87, 0x27, 0xb3, 0xd2, 0x4e, 0x8c, 0x95, 0xcc, 0x34, 0x7b, 0xd8, 0x71, 0xc3, 0x83, 0x24, + 0xd6, 0x68, 0xb5, 0xef, 0xdd, 0xff, 0x50, 0x42, 0x2e, 0x1b, 0xdc, 0x6c, 0xf7, 0xd1, 0xc7, 0xa0, + 0x7a, 0xd4, 0x2b, 0x2c, 0x0b, 0x1e, 0xd5, 0xda, 0xa5, 0x1b, 0x50, 0x6b, 0x8f, 0x28, 0x3d, 0x3a, + 0x38, 0x6a, 0x53, 0xc6, 0x88, 0x98, 0x42, 0xf3, 0x8b, 0xd0, 0x7d, 0xd8, 0x60, 0x03, 0xcc, 0x7a, + 0xc4, 0xd2, 0xc3, 0x91, 0x7a, 0xc4, 0xb1, 0x7b, 0xbc, 0x90, 0x2a, 0x2b, 0xd5, 0xa4, 0xb6, 0x2e, + 0xa3, 0x8d, 0x20, 0xf8, 0x44, 0xc4, 0xd0, 0x07, 0x80, 0xe6, 0x55, 0xdc, 0x0c, 0x2b, 0x56, 0x44, + 0x45, 0x3e, 0xac, 0xe0, 0x66, 0x90, 0x5d, 0xf9, 0x21, 0x01, 0xeb, 0x71, 0x83, 0xbf, 0x70, 0x78, + 0xef, 0x39, 0xe1, 0x38, 0xa6, 0x83, 0x72, 0x1d, 0x3a, 0x6c, 0x40, 0x4a, 0x32, 0x49, 0x08, 0x26, + 0xf2, 0x84, 0xde, 0x81, 0xec, 0x84, 0x72, 0xc7, 0xb5, 0x75, 0x8f, 0x7e, 0x4f, 0x46, 0xc2, 0xb0, + 0xa4, 0x96, 0x09, 0xee, 0xda, 0xfe, 0xd5, 0xdf, 0xc8, 0x90, 0x7c, 0x63, 0x19, 0x96, 0xaf, 0x90, + 0xe1, 0x97, 0x14, 0xe4, 0x1a, 0xdd, 0xdd, 0x26, 0x19, 0x10, 0x1b, 0xf3, 0xbf, 0xee, 0x91, 0xb2, + 0xc0, 0x1e, 0x25, 0xae, 0x71, 0x8f, 0xd4, 0x7f, 0xb3, 0x47, 0xdf, 0xc2, 0xda, 0x04, 0x0f, 0xf4, + 0x80, 0x8e, 0x3e, 0x70, 0x98, 0xaf, 0x9c, 0xba, 0x00, 0xa7, 0xec, 0x04, 0x0f, 0x1a, 0x3e, 0xad, + 0x67, 0x0e, 0x13, 0x16, 0x32, 0x8e, 0x47, 0xfc, 0xbc, 0xc6, 0x19, 0x71, 0x27, 0xcd, 0x78, 0x1b, + 0x80, 0xb8, 0xd6, 0xf9, 0xed, 0x4d, 0x13, 0xd7, 0x92, 0xe1, 0xdb, 0x90, 0xe6, 0x94, 0xe3, 0x81, + 0xce, 0x70, 0xb8, 0xa9, 0xab, 0xe2, 0xa2, 0x83, 0x45, 0xad, 0x1c, 0x51, 0xe7, 0xd3, 0xc2, 0xaa, + 0x2f, 0xa6, 0x96, 0x96, 0x37, 0xdd, 0xa9, 0xf0, 0x59, 0x86, 0xe9, 0x98, 0x7b, 0x63, 0xae, 0x3b, + 0xd6, 0xb4, 0x90, 0x2e, 0x2b, 0xd5, 0x9c, 0x96, 0x97, 0x91, 0x03, 0x11, 0x68, 0x59, 0x53, 0xb4, + 0x03, 0x19, 0xe1, 0xbd, 0x44, 0x03, 0x61, 0xcd, 0x8d, 0x93, 0x59, 0xc9, 0x77, 0xbf, 0x23, 0x23, + 0xdd, 0xa9, 0x06, 0x6c, 0xfe, 0x19, 0x7d, 0x07, 0x39, 0x2b, 0xd8, 0x0b, 0x3a, 0xd2, 0x99, 0x63, + 0x17, 0x32, 0xa2, 0xea, 0xa3, 0x93, 0x59, 0xe9, 0xc1, 0x9b, 0x88, 0xd7, 0x71, 0x6c, 0x17, 0xf3, + 0xf1, 0x88, 0x68, 0xd9, 0x39, 0x5e, 0xc7, 0xb1, 0xd1, 0x37, 0x90, 0x35, 0xe9, 0x84, 0xb8, 0xd8, + 0xe5, 0x02, 0x3e, 0xbb, 0x28, 0x7c, 0x26, 0x84, 0xf3, 0xd1, 0x3f, 0x87, 0xbc, 0x6f, 0xfc, 0xd8, + 0xb5, 0xe6, 0xbb, 0x5d, 0xc8, 0x89, 0x2d, 0xda, 0xba, 0x62, 0x8b, 0x1a, 0xdd, 0xdd, 0xc3, 0x58, + 0xb6, 0xb6, 0x66, 0x70, 0x33, 0x7e, 0x51, 0x79, 0xa5, 0xc2, 0xda, 0x85, 0x24, 0x7f, 0x09, 0xc6, + 0xae, 0x41, 0x5d, 0x4b, 0x2a, 0x2b, 0x1e, 0x0d, 0x2d, 0x33, 0xbf, 0xeb, 0x4e, 0xd1, 0x7b, 0xf0, + 0xbf, 0x58, 0x8a, 0x33, 0x24, 0xe2, 0x3f, 0x23, 0xa7, 0xe5, 0xa2, 0x24, 0x67, 0x48, 0x2e, 0x5a, + 0xa4, 0xbe, 0x8e, 0x45, 0x14, 0x36, 0x62, 0x16, 0x85, 0xd5, 0xbe, 0x98, 0xc9, 0x45, 0xc5, 0x5c, + 0x8f, 0xbc, 0x92, 0xb8, 0xbe, 0xaa, 0x43, 0xb8, 0x15, 0x79, 0x16, 0xef, 0xb7, 0xbc, 0x68, 0xbf, + 0x9b, 0x73, 0xf3, 0x62, 0xed, 0x28, 0x6c, 0xcc, 0xdb, 0x45, 0x1a, 0xfa, 0xfd, 0x52, 0x0b, 0xcf, + 0x17, 0x02, 0x1f, 0x86, 0xb8, 0x1d, 0xc7, 0xae, 0xfc, 0xac, 0xc0, 0xcd, 0x0b, 0x16, 0xb7, 0xdc, + 0x23, 0xfa, 0x3a, 0x36, 0x5f, 0xcd, 0x35, 0xf1, 0xdf, 0x70, 0xed, 0xc0, 0xff, 0xa3, 0xa7, 0x9b, + 0x8e, 0xa2, 0x37, 0x9c, 0xa1, 0x47, 0x90, 0xb4, 0xc8, 0x80, 0x15, 0x94, 0xb2, 0x5a, 0xcd, 0xec, + 0xdc, 0xb9, 0x7a, 0xe1, 0xa3, 0x22, 0x4d, 0x54, 0x54, 0xf6, 0xe1, 0xf6, 0xe5, 0xa0, 0x2d, 0xd7, + 0x22, 0x53, 0x54, 0x87, 0xf5, 0xe8, 0x51, 0xd2, 0x7b, 0x98, 0xf5, 0x82, 0x77, 0xd5, 0x6f, 0x94, + 0xd5, 0x6e, 0xcc, 0x9f, 0xa7, 0x27, 0x98, 0xf5, 0xfc, 0x47, 0xb2, 0xf2, 0x93, 0x02, 0xb9, 0xf9, + 0x20, 0x42, 0xca, 0x4f, 0x21, 0xb1, 0xf0, 0x97, 0x6b, 0xc2, 0xeb, 0xa3, 0xa7, 0xa0, 0x5e, 0x8b, + 0xb8, 0x3e, 0xca, 0xfb, 0x5d, 0x61, 0x7b, 0x34, 0x6d, 0x87, 0x63, 0x3e, 0x66, 0x28, 0x03, 0x2b, + 0xed, 0xbd, 0xfd, 0x66, 0x6b, 0xff, 0xb3, 0xfc, 0x12, 0x02, 0x48, 0x3d, 0xde, 0xed, 0xb6, 0x5e, + 0xec, 0xe5, 0x15, 0x94, 0x83, 0xf4, 0xe1, 0x7e, 0xe3, 0x20, 0x08, 0x25, 0x50, 0x16, 0x56, 0x83, + 0xe3, 0x5e, 0x33, 0xaf, 0xa2, 0x15, 0x50, 0x1f, 0xef, 0x7f, 0x95, 0x4f, 0x36, 0x9e, 0xfd, 0x76, + 0x5a, 0x54, 0x5e, 0x9e, 0x16, 0x95, 0x57, 0xa7, 0x45, 0xe5, 0xc7, 0xb3, 0xe2, 0xd2, 0xcb, 0xb3, + 0xe2, 0xd2, 0x1f, 0x67, 0xc5, 0xa5, 0xaf, 0xff, 0x71, 0xe8, 0x69, 0xfc, 0xa7, 0xa9, 0x20, 0x6e, + 0xa4, 0xc4, 0x4f, 0xd3, 0x7b, 0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0x01, 0xc5, 0x51, 0x3c, 0x77, + 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -958,7 +907,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x62 + dAtA[i] = 0x6a } if m.CovenantSig != nil { { @@ -970,7 +919,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x5a + dAtA[i] = 0x62 } if m.DelegatorSig != nil { { @@ -982,7 +931,7 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x52 + dAtA[i] = 0x5a } if m.SlashingTx != nil { { @@ -994,17 +943,17 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x4a + dAtA[i] = 0x52 } - if m.StakingTx != nil { - { - size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } + if m.StakingOutputIdx != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.StakingOutputIdx)) + i-- + dAtA[i] = 0x48 + } + if len(m.StakingTx) > 0 { + i -= len(m.StakingTx) + copy(dAtA[i:], m.StakingTx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.StakingTx))) i-- dAtA[i] = 0x42 } @@ -1096,18 +1045,6 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.ValidatorUnbondingSig != nil { - { - size := m.ValidatorUnbondingSig.Size() - i -= size - if _, err := m.ValidatorUnbondingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x32 - } if m.CovenantUnbondingSig != nil { { size := m.CovenantUnbondingSig.Size() @@ -1118,7 +1055,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x32 } if m.CovenantSlashingSig != nil { { @@ -1130,7 +1067,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a } if m.DelegatorSlashingSig != nil { { @@ -1142,7 +1079,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x22 } if m.SlashingTx != nil { { @@ -1154,17 +1091,17 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a } - if m.UnbondingTx != nil { - { - size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } + if m.UnbondingTime != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x10 + } + if len(m.UnbondingTx) > 0 { + i -= len(m.UnbondingTx) + copy(dAtA[i:], m.UnbondingTx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.UnbondingTx))) i-- dAtA[i] = 0xa } @@ -1201,29 +1138,12 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - if m.ValidatorUnbondingSig != nil { - { - size := m.ValidatorUnbondingSig.Size() - i -= size - if _, err := m.ValidatorUnbondingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0x12 } - if m.UnbondingTx != nil { - { - size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) - } + if len(m.UnbondingTx) > 0 { + i -= len(m.UnbondingTx) + copy(dAtA[i:], m.UnbondingTx) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.UnbondingTx))) i-- dAtA[i] = 0xa } @@ -1299,43 +1219,6 @@ func (m *BTCDelegatorDelegationIndex) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } -func (m *BabylonBTCTaprootTx) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *BabylonBTCTaprootTx) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *BabylonBTCTaprootTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Script) > 0 { - i -= len(m.Script) - copy(dAtA[i:], m.Script) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Script))) - i-- - dAtA[i] = 0x12 - } - if len(m.Tx) > 0 { - i -= len(m.Tx) - copy(dAtA[i:], m.Tx) - i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.Tx))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *SignatureInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1487,10 +1370,13 @@ func (m *BTCDelegation) Size() (n int) { if m.TotalSat != 0 { n += 1 + sovBtcstaking(uint64(m.TotalSat)) } - if m.StakingTx != nil { - l = m.StakingTx.Size() + l = len(m.StakingTx) + if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } + if m.StakingOutputIdx != 0 { + n += 1 + sovBtcstaking(uint64(m.StakingOutputIdx)) + } if m.SlashingTx != nil { l = m.SlashingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -1516,10 +1402,13 @@ func (m *BTCUndelegation) Size() (n int) { } var l int _ = l - if m.UnbondingTx != nil { - l = m.UnbondingTx.Size() + l = len(m.UnbondingTx) + if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } + if m.UnbondingTime != 0 { + n += 1 + sovBtcstaking(uint64(m.UnbondingTime)) + } if m.SlashingTx != nil { l = m.SlashingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -1536,10 +1425,6 @@ func (m *BTCUndelegation) Size() (n int) { l = m.CovenantUnbondingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.ValidatorUnbondingSig != nil { - l = m.ValidatorUnbondingSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) - } return n } @@ -1549,12 +1434,8 @@ func (m *BTCUndelegationInfo) Size() (n int) { } var l int _ = l - if m.UnbondingTx != nil { - l = m.UnbondingTx.Size() - n += 1 + l + sovBtcstaking(uint64(l)) - } - if m.ValidatorUnbondingSig != nil { - l = m.ValidatorUnbondingSig.Size() + l = len(m.UnbondingTx) + if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } if m.CovenantUnbondingSig != nil { @@ -1594,23 +1475,6 @@ func (m *BTCDelegatorDelegationIndex) Size() (n int) { return n } -func (m *BabylonBTCTaprootTx) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Tx) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - l = len(m.Script) - if l > 0 { - n += 1 + l + sovBtcstaking(uint64(l)) - } - return n -} - func (m *SignatureInfo) Size() (n int) { if m == nil { return 0 @@ -2294,7 +2158,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2304,29 +2168,46 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } + m.StakingTx = append(m.StakingTx[:0], dAtA[iNdEx:postIndex]...) if m.StakingTx == nil { - m.StakingTx = &BabylonBTCTaprootTx{} - } - if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.StakingTx = []byte{} } iNdEx = postIndex case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingOutputIdx", wireType) + } + m.StakingOutputIdx = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StakingOutputIdx |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } @@ -2361,7 +2242,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 10: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) } @@ -2396,7 +2277,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 11: + case 12: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantSig", wireType) } @@ -2431,7 +2312,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 12: + case 13: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BtcUndelegation", wireType) } @@ -2521,7 +2402,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2531,33 +2412,31 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } + m.UnbondingTx = append(m.UnbondingTx[:0], dAtA[iNdEx:postIndex]...) if m.UnbondingTx == nil { - m.UnbondingTx = &BabylonBTCTaprootTx{} - } - if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.UnbondingTx = []byte{} } iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) } - var byteLen int + m.UnbondingTime = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2567,30 +2446,14 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + m.UnbondingTime |= uint32(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v BTCSlashingTx - m.SlashingTx = &v - if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2617,15 +2480,15 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.DelegatorSlashingSig = &v - if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v BTCSlashingTx + m.SlashingTx = &v + if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2653,14 +2516,14 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantSlashingSig = &v - if err := m.CovenantSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.DelegatorSlashingSig = &v + if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2688,14 +2551,14 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantUnbondingSig = &v - if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantSlashingSig = &v + if err := m.CovenantSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValidatorUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2723,8 +2586,8 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.ValidatorUnbondingSig = &v - if err := m.ValidatorUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantUnbondingSig = &v + if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2782,42 +2645,6 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.UnbondingTx == nil { - m.UnbondingTx = &BabylonBTCTaprootTx{} - } - if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValidatorUnbondingSig", wireType) - } var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { @@ -2843,13 +2670,12 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.ValidatorUnbondingSig = &v - if err := m.ValidatorUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.UnbondingTx = append(m.UnbondingTx[:0], dAtA[iNdEx:postIndex]...) + if m.UnbondingTx == nil { + m.UnbondingTx = []byte{} } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) } @@ -3071,124 +2897,6 @@ func (m *BTCDelegatorDelegationIndex) Unmarshal(dAtA []byte) error { } return nil } -func (m *BabylonBTCTaprootTx) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BabylonBTCTaprootTx: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BabylonBTCTaprootTx: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Tx", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Tx = append(m.Tx[:0], dAtA[iNdEx:postIndex]...) - if m.Tx == nil { - m.Tx = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Script", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBtcstaking - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtcstaking - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Script = append(m.Script[:0], dAtA[iNdEx:postIndex]...) - if m.Script == nil { - m.Script = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBtcstaking(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthBtcstaking - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *SignatureInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 3ad6d72ad..2ba17e1b1 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -20,7 +20,7 @@ func FuzzStakingTx(f *testing.F) { r := rand.New(rand.NewSource(seed)) net := &chaincfg.SimNetParams - stakerSK, stakerPK, err := datagen.GenRandomBTCKeyPair(r) + stakerSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) @@ -39,31 +39,21 @@ func FuzzStakingTx(f *testing.F) { // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - stakingTx, _, err := datagen.GenBTCStakingSlashingTx( + testInfo := datagen.GenBTCStakingSlashingTx( r, + t, net, stakerSK, []*btcec.PublicKey{validatorPK}, []*btcec.PublicKey{covenantPK}, + 1, stakingTimeBlocks, stakingValue, slashingAddress.String(), changeAddress.String(), slashingRate, ) require.NoError(t, err) - - err = stakingTx.ValidateBasic() - require.NoError(t, err) - - // extract staking script and staked value - stakingOutputInfo, err := stakingTx.GetBabylonOutputInfo(&chaincfg.SimNetParams) - require.NoError(t, err) - // NOTE: given that PK derived from SK has 2 possibilities on a curve, we can only compare x value but not y value - require.Equal(t, stakingOutputInfo.StakingScriptData.StakerKey.SerializeCompressed()[1:], stakerPK.SerializeCompressed()[1:]) - require.Equal(t, stakingOutputInfo.StakingScriptData.ValidatorKey.SerializeCompressed()[1:], validatorPK.SerializeCompressed()[1:]) - require.Equal(t, stakingOutputInfo.StakingScriptData.CovenantKey.SerializeCompressed()[1:], covenantPK.SerializeCompressed()[1:]) - require.Equal(t, stakingOutputInfo.StakingScriptData.StakingTime, stakingTimeBlocks) - require.Equal(t, int64(stakingOutputInfo.StakingAmount), stakingValue) + require.Equal(t, testInfo.StakingInfo.StakingOutput.Value, stakingValue) }) } diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index fe6704e44..afc7b1955 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -14,7 +14,6 @@ func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) cdc.RegisterConcrete(&MsgBTCUndelegate{}, "btcstaking/MsgBtcUndelegate", nil) cdc.RegisterConcrete(&MsgAddCovenantUnbondingSigs{}, "btcstaking/MsgAddCovenantUnbondingSigs", nil) - cdc.RegisterConcrete(&MsgAddValidatorUnbondingSig{}, "btcstaking/MsgAddValidatorUnbondingSig", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -27,7 +26,6 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { &MsgUpdateParams{}, &MsgBTCUndelegate{}, &MsgAddCovenantUnbondingSigs{}, - &MsgAddValidatorUnbondingSig{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index ed5640e49..3a1f641fa 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -26,4 +26,7 @@ var ( ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1117, "the validator signature is not valid") ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1118, "the BTC undelegation did not receive validator signature yet") ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1119, "the reward distribution cache is not found") + ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1120, "the validator list is empty") + ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1121, "the proof of possession is not valid") + ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1122, "the staking request contains duplicated validator public key") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 5d8cd5962..217a9c559 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/btcstaking" + "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -17,7 +18,6 @@ var ( _ sdk.Msg = &MsgAddCovenantSig{} _ sdk.Msg = &MsgBTCUndelegate{} _ sdk.Msg = &MsgAddCovenantUnbondingSigs{} - _ sdk.Msg = &MsgAddValidatorUnbondingSig{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -91,10 +91,10 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.Pop == nil { return fmt.Errorf("empty proof of possession") } - if m.StakingTx == nil { - return fmt.Errorf("empty staking tx") + if m.StakerBtcPk == nil { + return fmt.Errorf("empty staker BTC public key") } - if m.StakingTxInfo == nil { + if m.StakingTx == nil { return fmt.Errorf("empty staking tx info") } if m.SlashingTx == nil { @@ -153,11 +153,11 @@ func (m *MsgBTCUndelegate) ValidateBasic() error { if m.DelegatorSlashingSig == nil { return fmt.Errorf("empty delegator signature") } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } - - unbondingTxMsg, err := m.UnbondingTx.ToMsgTx() + unbondingTxMsg, err := ParseBtcTx(m.UnbondingTx) if err != nil { return err @@ -167,11 +167,6 @@ func (m *MsgBTCUndelegate) ValidateBasic() error { return err } - // unbodning tx should be correctly formatted - valid transaction and valid script - if err := m.UnbondingTx.ValidateBasic(); err != nil { - return err - } - return nil } @@ -209,28 +204,3 @@ func (m *MsgAddCovenantUnbondingSigs) ValidateBasic() error { } return nil } - -func (m *MsgAddValidatorUnbondingSig) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - -func (m *MsgAddValidatorUnbondingSig) ValidateBasic() error { - if m.ValPk == nil { - return fmt.Errorf("empty BTC validator public key") - } - if m.DelPk == nil { - return fmt.Errorf("empty BTC delegation public key") - } - if m.UnbondingTxSig == nil { - return fmt.Errorf("empty covenant signature") - } - if len(m.StakingTxHash) != chainhash.MaxHashStringSize { - return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) - } - - return nil -} diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index a7a48e70f..59fc7761c 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -1010,15 +1010,13 @@ type QueryBTCDelegationResponse struct { // total_sat is the total amount of BTC stakes in this delegation // quantified in satoshi TotalSat uint64 `protobuf:"varint,5,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` - // staking_tx is the staking tx - StakingTx string `protobuf:"bytes,6,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` - // staking_script is the script commited to in staking output - StakingScript string `protobuf:"bytes,7,opt,name=staking_script,json=stakingScript,proto3" json:"staking_script,omitempty"` + // staking_tx_hex is the hex string of staking tx + StakingTxHex string `protobuf:"bytes,6,opt,name=staking_tx_hex,json=stakingTxHex,proto3" json:"staking_tx_hex,omitempty"` // whether this delegation is active - Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` + Active bool `protobuf:"varint,7,opt,name=active,proto3" json:"active,omitempty"` // undelegation_info is the undelegation info of this delegation. It is nil // if it is not undelegating - UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,9,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` + UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,8,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` } func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationResponse{} } @@ -1075,16 +1073,9 @@ func (m *QueryBTCDelegationResponse) GetTotalSat() uint64 { return 0 } -func (m *QueryBTCDelegationResponse) GetStakingTx() string { +func (m *QueryBTCDelegationResponse) GetStakingTxHex() string { if m != nil { - return m.StakingTx - } - return "" -} - -func (m *QueryBTCDelegationResponse) GetStakingScript() string { - if m != nil { - return m.StakingScript + return m.StakingTxHex } return "" } @@ -1129,86 +1120,85 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1262 bytes of a gzipped FileDescriptorProto + // 1246 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xce, 0x24, 0xa9, 0x69, 0x5e, 0x7e, 0x95, 0x69, 0xa1, 0xee, 0xa6, 0x71, 0xd2, 0x4d, 0xf3, - 0xa3, 0x41, 0xdd, 0x8d, 0x1d, 0x94, 0x03, 0x09, 0x2d, 0x75, 0x03, 0x4d, 0x93, 0x46, 0x32, 0x9b, - 0x42, 0x25, 0x24, 0x64, 0xcd, 0x3a, 0xd3, 0xf5, 0x12, 0x67, 0xd7, 0xf5, 0x8e, 0x4d, 0xa2, 0x2a, - 0x17, 0x0e, 0x48, 0x88, 0x0b, 0x82, 0x3f, 0x81, 0x03, 0x48, 0x9c, 0x38, 0x70, 0x81, 0x3f, 0x80, - 0x1e, 0x2b, 0x21, 0x21, 0x44, 0xa5, 0xa8, 0x4a, 0x90, 0xf8, 0x37, 0xd0, 0xce, 0x8e, 0xb3, 0xbb, - 0xf1, 0xda, 0x5e, 0xbb, 0xe1, 0x16, 0xcf, 0xcc, 0x7b, 0xef, 0xfb, 0xde, 0x7c, 0xfb, 0xcd, 0x53, - 0xe0, 0x9a, 0x4e, 0xf4, 0xfd, 0x92, 0x6d, 0xa9, 0x3a, 0x2b, 0x38, 0x8c, 0xec, 0x98, 0x96, 0xa1, - 0xd6, 0xd2, 0xea, 0x93, 0x2a, 0xad, 0xec, 0x2b, 0xe5, 0x8a, 0xcd, 0x6c, 0xfc, 0x86, 0x38, 0xa2, - 0xf8, 0x47, 0x94, 0x5a, 0x5a, 0xba, 0x64, 0xd8, 0x86, 0xcd, 0x4f, 0xa8, 0xee, 0x5f, 0xde, 0x61, - 0xe9, 0xaa, 0x61, 0xdb, 0x46, 0x89, 0xaa, 0xa4, 0x6c, 0xaa, 0xc4, 0xb2, 0x6c, 0x46, 0x98, 0x69, - 0x5b, 0x8e, 0xd8, 0x9d, 0x2f, 0xd8, 0xce, 0xae, 0xed, 0xa8, 0x3a, 0x71, 0xa8, 0x57, 0x43, 0xad, - 0xa5, 0x75, 0xca, 0x48, 0x5a, 0x2d, 0x13, 0xc3, 0xb4, 0xf8, 0x61, 0x71, 0x56, 0x8e, 0x46, 0x56, - 0x26, 0x15, 0xb2, 0x5b, 0xcf, 0x37, 0x13, 0x7d, 0x26, 0x00, 0x94, 0x9f, 0x93, 0x2f, 0x01, 0xfe, - 0xd0, 0xad, 0x96, 0xe3, 0xc1, 0x1a, 0x7d, 0x52, 0xa5, 0x0e, 0x93, 0x35, 0xb8, 0x18, 0x5a, 0x75, - 0xca, 0xb6, 0xe5, 0x50, 0xbc, 0x0c, 0x09, 0xaf, 0x48, 0x12, 0x4d, 0xa2, 0xb9, 0xc1, 0xcc, 0xb8, - 0x12, 0xd9, 0x00, 0xc5, 0x0b, 0xcb, 0xf6, 0x3f, 0x3b, 0x9c, 0xe8, 0xd1, 0x44, 0x88, 0x5c, 0x80, - 0x2b, 0x3c, 0x67, 0xf6, 0xe1, 0xdd, 0x8f, 0x49, 0xc9, 0xdc, 0x26, 0xcc, 0xae, 0xd4, 0x0b, 0xe2, - 0x0f, 0x00, 0x7c, 0x9a, 0x22, 0xfb, 0x8c, 0xe2, 0xf5, 0x44, 0x71, 0x7b, 0xa2, 0x78, 0x7d, 0x17, - 0x3d, 0x51, 0x72, 0xc4, 0xa0, 0x22, 0x56, 0x0b, 0x44, 0xca, 0x3f, 0x23, 0x90, 0xa2, 0xaa, 0x08, - 0x02, 0xeb, 0x30, 0xa2, 0xb3, 0x42, 0xbe, 0x76, 0xb2, 0x93, 0x44, 0x93, 0x7d, 0x73, 0x83, 0x99, - 0xa9, 0x26, 0x44, 0x82, 0x59, 0xb4, 0x61, 0x9d, 0x15, 0xfc, 0x9c, 0xf8, 0x5e, 0x08, 0x72, 0x2f, - 0x87, 0x3c, 0xdb, 0x16, 0xb2, 0x07, 0x24, 0x84, 0xf9, 0x36, 0x24, 0x1b, 0x20, 0xd7, 0xfb, 0x32, - 0x05, 0x23, 0x35, 0x52, 0xca, 0xbb, 0xa0, 0xcb, 0x3b, 0xf9, 0x22, 0xdd, 0xe3, 0xbd, 0x19, 0xd0, - 0x06, 0x6b, 0xa4, 0x94, 0x65, 0x85, 0xdc, 0xce, 0x1a, 0xdd, 0x93, 0x69, 0x44, 0x67, 0x4f, 0x28, - 0xaf, 0xc1, 0x70, 0x88, 0xb2, 0x68, 0x6e, 0x2c, 0xc6, 0x43, 0x41, 0xc6, 0xf2, 0x8f, 0x81, 0xde, - 0xae, 0xd2, 0x12, 0x35, 0x3c, 0x01, 0xd7, 0xa1, 0x66, 0x21, 0xe1, 0x30, 0xc2, 0xaa, 0x9e, 0x38, - 0x46, 0x32, 0xf3, 0xcd, 0x2b, 0xf8, 0xd1, 0x5b, 0x3c, 0x42, 0x13, 0x91, 0xa7, 0x64, 0xd0, 0xdb, - 0xb5, 0x0c, 0x7e, 0x41, 0x30, 0x16, 0x09, 0x55, 0x34, 0x65, 0x13, 0x46, 0xdd, 0xa6, 0x6c, 0xfb, - 0x5b, 0x42, 0x08, 0xd7, 0xe3, 0x80, 0xd6, 0x5c, 0x11, 0x05, 0xd2, 0x9e, 0x9d, 0x14, 0xb6, 0x61, - 0xba, 0xe1, 0x26, 0x73, 0xf6, 0xe7, 0xb4, 0x72, 0x87, 0xad, 0x51, 0xd3, 0x28, 0xb2, 0x4e, 0x74, - 0x81, 0xdf, 0x84, 0x44, 0x91, 0x47, 0x71, 0x48, 0xfd, 0x9a, 0xf8, 0x25, 0x6f, 0xc0, 0x4c, 0xbb, - 0x2a, 0xa2, 0x4f, 0xd7, 0x60, 0xa8, 0x66, 0x33, 0xd3, 0x32, 0xf2, 0x65, 0x77, 0x9f, 0x17, 0xe9, - 0xd7, 0x06, 0xbd, 0x35, 0x1e, 0x22, 0x6f, 0xc0, 0xf5, 0x86, 0x64, 0x77, 0xab, 0x95, 0x0a, 0xb5, - 0x18, 0x3f, 0xd0, 0x91, 0x92, 0xf5, 0x08, 0xfe, 0xe1, 0x64, 0x02, 0x98, 0x4f, 0x0d, 0x05, 0xa9, - 0x35, 0x00, 0xee, 0x6d, 0x04, 0xfc, 0x15, 0x82, 0x59, 0x5e, 0xe4, 0x4e, 0x81, 0x99, 0x35, 0x1a, - 0x32, 0x8a, 0xd3, 0x6d, 0x6e, 0x56, 0xe6, 0xac, 0x74, 0xfa, 0x3b, 0x82, 0xb9, 0xf6, 0x58, 0x04, - 0x67, 0xad, 0x89, 0x79, 0xbd, 0x15, 0xe3, 0x53, 0x7e, 0x64, 0xb2, 0xe2, 0x26, 0x65, 0xe4, 0x7f, - 0x33, 0xb1, 0x71, 0xf1, 0xc1, 0x71, 0x22, 0x84, 0xd1, 0xed, 0x50, 0x23, 0xe5, 0x25, 0xb8, 0x1a, - 0xbd, 0xdd, 0xfa, 0x3e, 0xe5, 0x6f, 0x11, 0x4c, 0x35, 0x28, 0x22, 0xc2, 0x7c, 0x62, 0x7d, 0x0f, - 0x67, 0x75, 0x6b, 0x2f, 0x50, 0x84, 0xe6, 0xa3, 0x6c, 0xe6, 0x33, 0xb8, 0x12, 0xb0, 0x19, 0xbb, - 0x12, 0x61, 0x38, 0x4a, 0x5b, 0xc3, 0x09, 0xa7, 0xbe, 0xec, 0x5b, 0x4f, 0x68, 0xe3, 0xec, 0x6e, - 0x72, 0xdd, 0x7f, 0x4d, 0x02, 0x96, 0x27, 0xfa, 0x7c, 0x13, 0x2e, 0x0a, 0x90, 0x79, 0xb6, 0x97, - 0x2f, 0x12, 0xa7, 0x18, 0x68, 0xf6, 0x05, 0xb1, 0xf5, 0x70, 0x6f, 0x8d, 0x38, 0x45, 0xf7, 0x7b, - 0x7e, 0xd9, 0x17, 0xf5, 0x64, 0x04, 0x6c, 0x38, 0xe1, 0xdd, 0x18, 0x4f, 0x30, 0x94, 0x5d, 0xfa, - 0xfb, 0x70, 0x22, 0x63, 0x98, 0xac, 0x58, 0xd5, 0x95, 0x82, 0xbd, 0xab, 0x8a, 0xd6, 0x14, 0x8a, - 0xc4, 0xb4, 0xea, 0x3f, 0x54, 0xb6, 0x5f, 0xa6, 0x8e, 0x92, 0xbd, 0x9f, 0x5b, 0x7c, 0x7b, 0x21, - 0x57, 0xd5, 0x37, 0xe8, 0xbe, 0x76, 0x4e, 0x77, 0xaf, 0x18, 0x7f, 0x0a, 0xa3, 0x01, 0x11, 0x94, - 0x4c, 0xc7, 0x35, 0xbe, 0xbe, 0x57, 0xc8, 0x3b, 0x54, 0x57, 0xcf, 0x03, 0xd3, 0xe1, 0xde, 0xe2, - 0x30, 0x52, 0x61, 0x79, 0xa1, 0xd4, 0x3e, 0xcf, 0x5b, 0xf8, 0x9a, 0x27, 0x67, 0x3c, 0x0e, 0x40, - 0xad, 0xed, 0xfa, 0x81, 0x7e, 0x7e, 0x60, 0x80, 0x5a, 0x42, 0xed, 0x78, 0x0c, 0x06, 0x98, 0xcd, - 0x48, 0x29, 0xef, 0x10, 0x96, 0x3c, 0xc7, 0x77, 0xcf, 0xf3, 0x85, 0x2d, 0xc2, 0x63, 0xfd, 0xd6, - 0x26, 0x13, 0xbc, 0xa3, 0x03, 0x27, 0x1d, 0xc5, 0xd3, 0x30, 0x52, 0xdf, 0x76, 0x0a, 0x15, 0xb3, - 0xcc, 0x92, 0xaf, 0xf1, 0x23, 0xc3, 0x62, 0x75, 0x8b, 0x2f, 0xba, 0x1f, 0x12, 0xe1, 0x5e, 0x92, - 0x3c, 0x3f, 0x89, 0xe6, 0xce, 0x6b, 0xe2, 0x17, 0x7e, 0x04, 0xaf, 0x57, 0x2d, 0x5f, 0x7e, 0x79, - 0xd3, 0x7a, 0x6c, 0x27, 0x07, 0xb8, 0x4a, 0x5a, 0x3c, 0xd4, 0x1f, 0x05, 0x42, 0xee, 0x5b, 0x8f, - 0x6d, 0xed, 0x42, 0xf5, 0xd4, 0x4a, 0xe6, 0xeb, 0x51, 0x38, 0xc7, 0xaf, 0x18, 0x7f, 0x89, 0x20, - 0xe1, 0x4d, 0x7e, 0xf8, 0x46, 0x93, 0x94, 0x8d, 0xa3, 0xa6, 0x34, 0x1f, 0xe7, 0xa8, 0xa7, 0x17, - 0x79, 0xfa, 0x8b, 0x3f, 0xfe, 0xf9, 0xae, 0x77, 0x02, 0x8f, 0xab, 0xad, 0x26, 0x60, 0xfc, 0x3d, - 0x82, 0xe1, 0x90, 0x95, 0xe2, 0x85, 0x56, 0x45, 0xa2, 0x06, 0x52, 0x29, 0xdd, 0x41, 0x84, 0x40, - 0x77, 0x93, 0xa3, 0x9b, 0xc5, 0xd3, 0x6a, 0xd3, 0xd9, 0x3b, 0x60, 0xde, 0xf8, 0x37, 0x04, 0x43, - 0xc1, 0x44, 0x58, 0x8d, 0x5b, 0xb2, 0x8e, 0x71, 0x21, 0x7e, 0x80, 0x80, 0xb8, 0xc6, 0x21, 0x66, - 0xf1, 0x7b, 0xb1, 0x20, 0xaa, 0x4f, 0xc3, 0x9e, 0x7a, 0xa0, 0x9e, 0xec, 0xe1, 0x1f, 0x10, 0x8c, - 0x84, 0x87, 0x2b, 0xdc, 0xae, 0x65, 0x8d, 0xb6, 0x2d, 0x65, 0x3a, 0x09, 0x11, 0x1c, 0x14, 0xce, - 0x61, 0x0e, 0xcf, 0xb4, 0xe0, 0x10, 0xf0, 0x59, 0xfc, 0x27, 0x82, 0xb1, 0x16, 0xcf, 0x2b, 0xbe, - 0xd5, 0x0a, 0x43, 0xfb, 0x19, 0x41, 0xba, 0xdd, 0x75, 0xbc, 0x20, 0xb4, 0xc4, 0x09, 0x2d, 0x60, - 0x25, 0xe6, 0xa5, 0x78, 0xee, 0x72, 0x80, 0xff, 0x45, 0x70, 0xa5, 0xe9, 0x08, 0x87, 0x57, 0xe2, - 0x8a, 0x23, 0x6a, 0xbe, 0x94, 0xde, 0xed, 0x32, 0x5a, 0x50, 0xda, 0xe4, 0x94, 0xee, 0xe1, 0xf7, - 0xbb, 0xd4, 0x19, 0x1f, 0xde, 0x7c, 0xa6, 0x2f, 0x10, 0x24, 0x9b, 0x8d, 0x84, 0x78, 0x39, 0x2e, - 0xd4, 0x88, 0xa9, 0x54, 0x5a, 0xe9, 0x2e, 0x58, 0xd0, 0x5c, 0xe5, 0x34, 0x6f, 0xe1, 0x95, 0x57, - 0xa1, 0x89, 0x7f, 0x42, 0x30, 0x7a, 0x6a, 0x2e, 0xc2, 0x99, 0xb6, 0xa2, 0x6a, 0x98, 0xb1, 0xa4, - 0xc5, 0x8e, 0x62, 0x04, 0x05, 0x95, 0x53, 0xb8, 0x81, 0x67, 0x9b, 0x50, 0x20, 0xf5, 0x38, 0xf1, - 0xa8, 0xe1, 0x43, 0x04, 0x97, 0x9b, 0xcc, 0x3d, 0xf8, 0x9d, 0xb8, 0xdd, 0x8c, 0xb0, 0x82, 0xe5, - 0xae, 0x62, 0x05, 0x8b, 0x75, 0xce, 0x62, 0x15, 0x67, 0xbb, 0xbc, 0x88, 0xa0, 0x5f, 0xfc, 0xea, - 0xbd, 0x1e, 0x7e, 0x99, 0xb6, 0xaf, 0x47, 0xc3, 0x98, 0x24, 0xa5, 0x3b, 0x88, 0xe8, 0x40, 0x4b, - 0x01, 0x98, 0xea, 0xd3, 0x88, 0x39, 0xec, 0x20, 0xfb, 0xe0, 0xd9, 0x51, 0x0a, 0x3d, 0x3f, 0x4a, - 0xa1, 0x97, 0x47, 0x29, 0xf4, 0xcd, 0x71, 0xaa, 0xe7, 0xf9, 0x71, 0xaa, 0xe7, 0xaf, 0xe3, 0x54, - 0xcf, 0x27, 0x6d, 0xe7, 0x9f, 0xbd, 0x60, 0x41, 0x3e, 0x0c, 0xe9, 0x09, 0xfe, 0x3f, 0xa2, 0xc5, - 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x69, 0xcd, 0x35, 0xac, 0x0b, 0x13, 0x00, 0x00, + 0x14, 0xce, 0x24, 0xa9, 0x69, 0x5f, 0x9c, 0xa4, 0x4c, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, 0x7e, + 0x36, 0xa8, 0xbb, 0xb1, 0x83, 0x72, 0x20, 0xa1, 0xa5, 0x6e, 0xa0, 0x69, 0xd2, 0x48, 0x66, 0x5b, + 0xa8, 0x84, 0x84, 0xac, 0x59, 0x67, 0xba, 0x5e, 0xe2, 0xec, 0xba, 0xde, 0xb1, 0x71, 0x54, 0xe5, + 0xc2, 0x01, 0x09, 0x71, 0x41, 0xf0, 0x27, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0x70, 0xe1, 0x46, + 0x8f, 0x95, 0x90, 0x10, 0xa2, 0x52, 0x84, 0x12, 0x24, 0xfe, 0x0d, 0xe4, 0xd9, 0x71, 0x76, 0x37, + 0x5e, 0xdb, 0x6b, 0x37, 0xdc, 0xe2, 0x9d, 0xf7, 0xe3, 0xfb, 0xde, 0x7c, 0xfb, 0xed, 0x53, 0xe0, + 0x9a, 0x4e, 0xf4, 0xfd, 0xa2, 0x6d, 0xa9, 0x3a, 0xcb, 0x3b, 0x8c, 0xec, 0x9a, 0x96, 0xa1, 0x56, + 0x53, 0xea, 0x93, 0x0a, 0x2d, 0xef, 0x2b, 0xa5, 0xb2, 0xcd, 0x6c, 0xfc, 0x9a, 0x08, 0x51, 0xbc, + 0x10, 0xa5, 0x9a, 0x92, 0x2e, 0x1b, 0xb6, 0x61, 0xf3, 0x08, 0xb5, 0xfe, 0x97, 0x1b, 0x2c, 0x5d, + 0x35, 0x6c, 0xdb, 0x28, 0x52, 0x95, 0x94, 0x4c, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, 0x6d, 0xcb, + 0x11, 0xa7, 0x8b, 0x79, 0xdb, 0xd9, 0xb3, 0x1d, 0x55, 0x27, 0x0e, 0x75, 0x7b, 0xa8, 0xd5, 0x94, + 0x4e, 0x19, 0x49, 0xa9, 0x25, 0x62, 0x98, 0x16, 0x0f, 0x16, 0xb1, 0x72, 0x38, 0xb2, 0x12, 0x29, + 0x93, 0xbd, 0x46, 0xbd, 0xb9, 0xf0, 0x18, 0x1f, 0x50, 0x1e, 0x27, 0x5f, 0x06, 0xfc, 0x7e, 0xbd, + 0x5b, 0x96, 0x27, 0x6b, 0xf4, 0x49, 0x85, 0x3a, 0x4c, 0xd6, 0xe0, 0x52, 0xe0, 0xa9, 0x53, 0xb2, + 0x2d, 0x87, 0xe2, 0x55, 0x88, 0xb9, 0x4d, 0x12, 0x68, 0x0a, 0x2d, 0x0c, 0xa5, 0x27, 0x94, 0xd0, + 0x01, 0x28, 0x6e, 0x5a, 0x66, 0xf0, 0xd9, 0xe1, 0x64, 0x9f, 0x26, 0x52, 0xe4, 0x3c, 0x8c, 0xf1, + 0x9a, 0x99, 0x87, 0x77, 0x3e, 0x24, 0x45, 0x73, 0x87, 0x30, 0xbb, 0xdc, 0x68, 0x88, 0xdf, 0x03, + 0xf0, 0x68, 0x8a, 0xea, 0x73, 0x8a, 0x3b, 0x13, 0xa5, 0x3e, 0x13, 0xc5, 0x9d, 0xbb, 0x98, 0x89, + 0x92, 0x25, 0x06, 0x15, 0xb9, 0x9a, 0x2f, 0x53, 0xfe, 0x11, 0x81, 0x14, 0xd6, 0x45, 0x10, 0xd8, + 0x84, 0x11, 0x9d, 0xe5, 0x73, 0xd5, 0x93, 0x93, 0x04, 0x9a, 0x1a, 0x58, 0x18, 0x4a, 0x4f, 0xb7, + 0x20, 0xe2, 0xaf, 0xa2, 0x0d, 0xeb, 0x2c, 0xef, 0xd5, 0xc4, 0x77, 0x03, 0x90, 0xfb, 0x39, 0xe4, + 0xf9, 0x8e, 0x90, 0x5d, 0x20, 0x01, 0xcc, 0xb7, 0x20, 0xd1, 0x04, 0xb9, 0x31, 0x97, 0x69, 0x18, + 0xa9, 0x92, 0x62, 0xae, 0x0e, 0xba, 0xb4, 0x9b, 0x2b, 0xd0, 0x1a, 0x9f, 0xcd, 0x05, 0x6d, 0xa8, + 0x4a, 0x8a, 0x19, 0x96, 0xcf, 0xee, 0x6e, 0xd0, 0x9a, 0x4c, 0x43, 0x26, 0x7b, 0x42, 0x79, 0x03, + 0x86, 0x03, 0x94, 0xc5, 0x70, 0x23, 0x31, 0x8e, 0xfb, 0x19, 0xcb, 0xdf, 0xfb, 0x66, 0xbb, 0x4e, + 0x8b, 0xd4, 0x70, 0x05, 0xdc, 0x80, 0x9a, 0x81, 0x98, 0xc3, 0x08, 0xab, 0xb8, 0xe2, 0x18, 0x49, + 0x2f, 0xb6, 0xee, 0xe0, 0x65, 0x3f, 0xe0, 0x19, 0x9a, 0xc8, 0x3c, 0x25, 0x83, 0xfe, 0x9e, 0x65, + 0xf0, 0x13, 0x82, 0xf1, 0x50, 0xa8, 0x62, 0x28, 0xdb, 0x30, 0x5a, 0x1f, 0xca, 0x8e, 0x77, 0x24, + 0x84, 0x30, 0x13, 0x05, 0xb4, 0x56, 0x17, 0x91, 0xaf, 0xec, 0xd9, 0x49, 0x61, 0x07, 0x66, 0x9b, + 0x6e, 0x32, 0x6b, 0x7f, 0x4a, 0xcb, 0xb7, 0xd9, 0x06, 0x35, 0x8d, 0x02, 0xeb, 0x46, 0x17, 0xf8, + 0x75, 0x88, 0x15, 0x78, 0x16, 0x87, 0x34, 0xa8, 0x89, 0x5f, 0xf2, 0x16, 0xcc, 0x75, 0xea, 0x22, + 0xe6, 0x74, 0x0d, 0xe2, 0x55, 0x9b, 0x99, 0x96, 0x91, 0x2b, 0xd5, 0xcf, 0x79, 0x93, 0x41, 0x6d, + 0xc8, 0x7d, 0xc6, 0x53, 0xe4, 0x2d, 0x98, 0x69, 0x2a, 0x76, 0xa7, 0x52, 0x2e, 0x53, 0x8b, 0xf1, + 0x80, 0xae, 0x94, 0xac, 0x87, 0xf0, 0x0f, 0x16, 0x13, 0xc0, 0x3c, 0x6a, 0xc8, 0x4f, 0xad, 0x09, + 0x70, 0x7f, 0x33, 0xe0, 0x2f, 0x10, 0xcc, 0xf3, 0x26, 0xb7, 0xf3, 0xcc, 0xac, 0xd2, 0x80, 0x51, + 0x9c, 0x1e, 0x73, 0xab, 0x36, 0x67, 0xa5, 0xd3, 0xdf, 0x10, 0x2c, 0x74, 0xc6, 0x22, 0x38, 0x6b, + 0x2d, 0xcc, 0xeb, 0x8d, 0x08, 0xaf, 0xf2, 0x23, 0x93, 0x15, 0xb6, 0x29, 0x23, 0xff, 0x9b, 0x89, + 0x4d, 0x88, 0x17, 0x8e, 0x13, 0x21, 0x8c, 0xee, 0x04, 0x06, 0x29, 0xaf, 0xc0, 0xd5, 0xf0, 0xe3, + 0xf6, 0xf7, 0x29, 0x7f, 0x8d, 0x60, 0xba, 0x49, 0x11, 0x21, 0xe6, 0x13, 0xe9, 0x7d, 0x38, 0xab, + 0x5b, 0x7b, 0x81, 0x42, 0x34, 0x1f, 0x66, 0x33, 0x9f, 0xc0, 0x98, 0xcf, 0x66, 0xec, 0x72, 0x88, + 0xe1, 0x28, 0x1d, 0x0d, 0x27, 0x58, 0xfa, 0x8a, 0x67, 0x3d, 0x81, 0x83, 0xb3, 0xbb, 0xc9, 0x4d, + 0xef, 0x6b, 0xe2, 0xb3, 0x3c, 0x31, 0xe7, 0x1b, 0x70, 0x49, 0x80, 0xcc, 0xb1, 0x5a, 0xae, 0x40, + 0x9c, 0x82, 0x6f, 0xd8, 0x17, 0xc5, 0xd1, 0xc3, 0xda, 0x06, 0x71, 0x0a, 0xf5, 0xf7, 0xf9, 0xd7, + 0x81, 0xb0, 0x4f, 0x86, 0xcf, 0x86, 0x63, 0xee, 0x8d, 0xf1, 0x02, 0xf1, 0xcc, 0xca, 0x5f, 0x87, + 0x93, 0x69, 0xc3, 0x64, 0x85, 0x8a, 0xae, 0xe4, 0xed, 0x3d, 0x55, 0x8c, 0x26, 0x5f, 0x20, 0xa6, + 0xd5, 0xf8, 0xa1, 0xb2, 0xfd, 0x12, 0x75, 0x94, 0xcc, 0xbd, 0xec, 0xf2, 0x9b, 0x4b, 0xd9, 0x8a, + 0xbe, 0x45, 0xf7, 0xb5, 0x73, 0x7a, 0xfd, 0x8a, 0xf1, 0xc7, 0x30, 0xea, 0x13, 0x41, 0xd1, 0x74, + 0xea, 0xc6, 0x37, 0xf0, 0x12, 0x75, 0xe3, 0x0d, 0xf5, 0xdc, 0x37, 0x1d, 0xee, 0x2d, 0x0e, 0x23, + 0x65, 0x96, 0x13, 0x4a, 0x1d, 0x70, 0xbd, 0x85, 0x3f, 0x73, 0xe5, 0x8c, 0x27, 0x00, 0xa8, 0xb5, + 0xd3, 0x08, 0x18, 0xe4, 0x01, 0x17, 0xa8, 0x25, 0xd4, 0x8e, 0xc7, 0xe1, 0x02, 0xb3, 0x19, 0x29, + 0xe6, 0x1c, 0xc2, 0x12, 0xe7, 0xf8, 0xe9, 0x79, 0xfe, 0xe0, 0x01, 0x61, 0x78, 0x06, 0x46, 0xfc, + 0xa3, 0xa5, 0xb5, 0x44, 0x8c, 0x4f, 0x35, 0xee, 0x4d, 0xd5, 0xf5, 0x74, 0xc2, 0xbd, 0x22, 0xf1, + 0xca, 0x14, 0x5a, 0x38, 0xaf, 0x89, 0x5f, 0xf8, 0x11, 0xbc, 0x5a, 0xb1, 0x3c, 0x79, 0xe5, 0x4c, + 0xeb, 0xb1, 0x9d, 0x38, 0xcf, 0x55, 0xd0, 0xe6, 0x43, 0xfc, 0x81, 0x2f, 0xe5, 0x9e, 0xf5, 0xd8, + 0xd6, 0x2e, 0x56, 0x4e, 0x3d, 0x49, 0x7f, 0x39, 0x0a, 0xe7, 0xf8, 0x15, 0xe2, 0xcf, 0x11, 0xc4, + 0xdc, 0xcd, 0x0e, 0x5f, 0x6f, 0x51, 0xb2, 0x79, 0x95, 0x94, 0x16, 0xa3, 0x84, 0xba, 0x7a, 0x90, + 0x67, 0x3f, 0xfb, 0xfd, 0x9f, 0x6f, 0xfa, 0x27, 0xf1, 0x84, 0xda, 0x6e, 0xc3, 0xc5, 0xdf, 0x22, + 0x18, 0x0e, 0x58, 0x25, 0x5e, 0x6a, 0xd7, 0x24, 0x6c, 0xe1, 0x94, 0x52, 0x5d, 0x64, 0x08, 0x74, + 0x37, 0x38, 0xba, 0x79, 0x3c, 0xab, 0xb6, 0xdc, 0xad, 0x7d, 0xe6, 0x8c, 0x7f, 0x41, 0x10, 0xf7, + 0x17, 0xc2, 0x6a, 0xd4, 0x96, 0x0d, 0x8c, 0x4b, 0xd1, 0x13, 0x04, 0xc4, 0x0d, 0x0e, 0x31, 0x83, + 0xdf, 0x89, 0x04, 0x51, 0x7d, 0x1a, 0xf4, 0xcc, 0x03, 0xf5, 0xe4, 0x0c, 0x7f, 0x87, 0x60, 0x24, + 0xb8, 0x3c, 0xe1, 0x4e, 0x23, 0x6b, 0xb6, 0x65, 0x29, 0xdd, 0x4d, 0x8a, 0xe0, 0xa0, 0x70, 0x0e, + 0x0b, 0x78, 0xae, 0x0d, 0x07, 0x9f, 0x8f, 0xe2, 0x3f, 0x10, 0x8c, 0xb7, 0xf9, 0x7c, 0xe2, 0x9b, + 0xed, 0x30, 0x74, 0xde, 0x01, 0xa4, 0x5b, 0x3d, 0xe7, 0x0b, 0x42, 0x2b, 0x9c, 0xd0, 0x12, 0x56, + 0x22, 0x5e, 0x8a, 0xeb, 0x1e, 0x07, 0xf8, 0x5f, 0x04, 0x63, 0x2d, 0x57, 0x34, 0xbc, 0x16, 0x55, + 0x1c, 0x61, 0xfb, 0xa3, 0xf4, 0x76, 0x8f, 0xd9, 0x82, 0xd2, 0x36, 0xa7, 0x74, 0x17, 0xbf, 0xdb, + 0xa3, 0xce, 0xf8, 0x72, 0xe6, 0x31, 0x7d, 0x81, 0x20, 0xd1, 0x6a, 0xe5, 0xc3, 0xab, 0x51, 0xa1, + 0x86, 0x6c, 0x9d, 0xd2, 0x5a, 0x6f, 0xc9, 0x82, 0xe6, 0x3a, 0xa7, 0x79, 0x13, 0xaf, 0xbd, 0x0c, + 0x4d, 0xfc, 0x03, 0x82, 0xd1, 0x53, 0x7b, 0x0f, 0x4e, 0x77, 0x14, 0x55, 0xd3, 0x0e, 0x25, 0x2d, + 0x77, 0x95, 0x23, 0x28, 0xa8, 0x9c, 0xc2, 0x75, 0x3c, 0xdf, 0x82, 0x02, 0x69, 0xe4, 0x89, 0x8f, + 0x16, 0x3e, 0x44, 0x70, 0xa5, 0xc5, 0x5e, 0x83, 0xdf, 0x8a, 0x3a, 0xcd, 0x10, 0x2b, 0x58, 0xed, + 0x29, 0x57, 0xb0, 0xd8, 0xe4, 0x2c, 0xd6, 0x71, 0xa6, 0xc7, 0x8b, 0xf0, 0xfb, 0xc5, 0xcf, 0xee, + 0xd7, 0xc3, 0x6b, 0xd3, 0xf1, 0xeb, 0xd1, 0xb4, 0x06, 0x49, 0xa9, 0x2e, 0x32, 0xba, 0xd0, 0x92, + 0x0f, 0xa6, 0xfa, 0x34, 0x64, 0xcf, 0x3a, 0xc8, 0xdc, 0x7f, 0x76, 0x94, 0x44, 0xcf, 0x8f, 0x92, + 0xe8, 0xef, 0xa3, 0x24, 0xfa, 0xea, 0x38, 0xd9, 0xf7, 0xfc, 0x38, 0xd9, 0xf7, 0xe7, 0x71, 0xb2, + 0xef, 0xa3, 0x8e, 0xfb, 0x4d, 0xcd, 0xdf, 0x90, 0x2f, 0x3b, 0x7a, 0x8c, 0xff, 0x0f, 0x68, 0xf9, + 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x99, 0x87, 0xd2, 0xeb, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2348,7 +2338,7 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x4a + dAtA[i] = 0x42 } if m.Active { i-- @@ -2358,19 +2348,12 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err dAtA[i] = 0 } i-- - dAtA[i] = 0x40 + dAtA[i] = 0x38 } - if len(m.StakingScript) > 0 { - i -= len(m.StakingScript) - copy(dAtA[i:], m.StakingScript) - i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingScript))) - i-- - dAtA[i] = 0x3a - } - if len(m.StakingTx) > 0 { - i -= len(m.StakingTx) - copy(dAtA[i:], m.StakingTx) - i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingTx))) + if len(m.StakingTxHex) > 0 { + i -= len(m.StakingTxHex) + copy(dAtA[i:], m.StakingTxHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.StakingTxHex))) i-- dAtA[i] = 0x32 } @@ -2728,11 +2711,7 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { if m.TotalSat != 0 { n += 1 + sovQuery(uint64(m.TotalSat)) } - l = len(m.StakingTx) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - l = len(m.StakingScript) + l = len(m.StakingTxHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -4646,7 +4625,7 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { } case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4674,41 +4653,9 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.StakingTx = string(dAtA[iNdEx:postIndex]) + m.StakingTxHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingScript", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakingScript = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 8: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Active", wireType) } @@ -4728,7 +4675,7 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { } } m.Active = bool(v != 0) - case 9: + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UndelegationInfo", wireType) } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index f5c12dc93..66046b25c 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -156,18 +156,25 @@ type MsgCreateBTCDelegation struct { BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` - // staking_tx is the staking tx - StakingTx *BabylonBTCTaprootTx `protobuf:"bytes,4,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` - // staking_tx_info is the tx info of the staking tx, including the Merkle proof - StakingTxInfo *types1.TransactionInfo `protobuf:"bytes,5,opt,name=staking_tx_info,json=stakingTxInfo,proto3" json:"staking_tx_info,omitempty"` + // staker_btc_pk is the Bitcoin secp256k1 PK of the BTC staker + StakerBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=staker_btc_pk,json=stakerBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"staker_btc_pk,omitempty"` + // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one + // validator pk it means that delegation is re-staked + ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + // staking_time is the time lock used in staking transaction + StakingTime uint32 `protobuf:"varint,6,opt,name=staking_time,json=stakingTime,proto3" json:"staking_time,omitempty"` + // staking_value is amout of satoshis locked in staking output + StakingValue int64 `protobuf:"varint,7,opt,name=staking_value,json=stakingValue,proto3" json:"staking_value,omitempty"` + // staking_tx is the staking tx along with the merekle proof of inclusion in btc block + StakingTx *types1.TransactionInfo `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. - SlashingTx *BTCSlashingTx `protobuf:"bytes,6,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from covenant and validator in // order to be spendable. - DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,7,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` + DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` } func (m *MsgCreateBTCDelegation) Reset() { *m = MsgCreateBTCDelegation{} } @@ -224,16 +231,23 @@ func (m *MsgCreateBTCDelegation) GetPop() *ProofOfPossession { return nil } -func (m *MsgCreateBTCDelegation) GetStakingTx() *BabylonBTCTaprootTx { +func (m *MsgCreateBTCDelegation) GetStakingTime() uint32 { if m != nil { - return m.StakingTx + return m.StakingTime } - return nil + return 0 +} + +func (m *MsgCreateBTCDelegation) GetStakingValue() int64 { + if m != nil { + return m.StakingValue + } + return 0 } -func (m *MsgCreateBTCDelegation) GetStakingTxInfo() *types1.TransactionInfo { +func (m *MsgCreateBTCDelegation) GetStakingTx() *types1.TransactionInfo { if m != nil { - return m.StakingTxInfo + return m.StakingTx } return nil } @@ -280,12 +294,17 @@ type MsgBTCUndelegate struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output - UnbondingTx *BabylonBTCTaprootTx `protobuf:"bytes,2,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + UnbondingTx []byte `protobuf:"bytes,2,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // unbonding_time is the time lock used in unbonding transaction + UnbondingTime uint32 `protobuf:"varint,3,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` + // unbonding_value is amount of satoshis locked in unbonding output. + // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding + UnbondingValue int64 `protobuf:"varint,4,opt,name=unbonding_value,json=unbondingValue,proto3" json:"unbonding_value,omitempty"` // slashing_tx is the slashing tx which slash unbonding contract // Note that the tx itself does not contain signatures, which are off-chain. - SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,5,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). - DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` } func (m *MsgBTCUndelegate) Reset() { *m = MsgBTCUndelegate{} } @@ -328,13 +347,27 @@ func (m *MsgBTCUndelegate) GetSigner() string { return "" } -func (m *MsgBTCUndelegate) GetUnbondingTx() *BabylonBTCTaprootTx { +func (m *MsgBTCUndelegate) GetUnbondingTx() []byte { if m != nil { return m.UnbondingTx } return nil } +func (m *MsgBTCUndelegate) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + +func (m *MsgBTCUndelegate) GetUnbondingValue() int64 { + if m != nil { + return m.UnbondingValue + } + return 0 +} + // MsgBtcUndelegateResponse is the response for MsgBtcUndelegate type MsgBTCUndelegateResponse struct { } @@ -674,107 +707,6 @@ func (m *MsgAddCovenantUnbondingSigsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse proto.InternalMessageInfo -// MsgAddValidatorUnbondingSig is the message for unbodning tx submitted to babylon by staker -type MsgAddValidatorUnbondingSig struct { - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` - // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // unbonding_tx_sig is the signature of validator for unbodning tx submitted to babylon by staker - // the signature follows encoding in BIP-340 spec - UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` -} - -func (m *MsgAddValidatorUnbondingSig) Reset() { *m = MsgAddValidatorUnbondingSig{} } -func (m *MsgAddValidatorUnbondingSig) String() string { return proto.CompactTextString(m) } -func (*MsgAddValidatorUnbondingSig) ProtoMessage() {} -func (*MsgAddValidatorUnbondingSig) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{12} -} -func (m *MsgAddValidatorUnbondingSig) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddValidatorUnbondingSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddValidatorUnbondingSig.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddValidatorUnbondingSig) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddValidatorUnbondingSig.Merge(m, src) -} -func (m *MsgAddValidatorUnbondingSig) XXX_Size() int { - return m.Size() -} -func (m *MsgAddValidatorUnbondingSig) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddValidatorUnbondingSig.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddValidatorUnbondingSig proto.InternalMessageInfo - -func (m *MsgAddValidatorUnbondingSig) GetSigner() string { - if m != nil { - return m.Signer - } - return "" -} - -func (m *MsgAddValidatorUnbondingSig) GetStakingTxHash() string { - if m != nil { - return m.StakingTxHash - } - return "" -} - -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -type MsgAddValidatorUnbondingSigResponse struct { -} - -func (m *MsgAddValidatorUnbondingSigResponse) Reset() { *m = MsgAddValidatorUnbondingSigResponse{} } -func (m *MsgAddValidatorUnbondingSigResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddValidatorUnbondingSigResponse) ProtoMessage() {} -func (*MsgAddValidatorUnbondingSigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{13} -} -func (m *MsgAddValidatorUnbondingSigResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddValidatorUnbondingSigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddValidatorUnbondingSigResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.Merge(m, src) -} -func (m *MsgAddValidatorUnbondingSigResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgAddValidatorUnbondingSigResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddValidatorUnbondingSigResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddValidatorUnbondingSigResponse proto.InternalMessageInfo - func init() { proto.RegisterType((*MsgCreateBTCValidator)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidator") proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") @@ -788,84 +720,84 @@ func init() { proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") proto.RegisterType((*MsgAddCovenantUnbondingSigs)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigs") proto.RegisterType((*MsgAddCovenantUnbondingSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigsResponse") - proto.RegisterType((*MsgAddValidatorUnbondingSig)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSig") - proto.RegisterType((*MsgAddValidatorUnbondingSigResponse)(nil), "babylon.btcstaking.v1.MsgAddValidatorUnbondingSigResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1107 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x3f, 0x6f, 0xdb, 0xc6, - 0x1b, 0xb6, 0x24, 0x5b, 0x3f, 0xf8, 0xf5, 0x9f, 0x24, 0xfc, 0x39, 0x8e, 0xac, 0x20, 0x92, 0xa1, - 0xb4, 0xae, 0x1b, 0xc4, 0x64, 0xac, 0xc4, 0x46, 0xeb, 0x02, 0x05, 0x42, 0xb9, 0x40, 0x8d, 0x54, - 0xa8, 0x4a, 0xc9, 0x45, 0xd1, 0xa1, 0xc2, 0x89, 0x3c, 0x53, 0x84, 0x24, 0x1e, 0xc1, 0x3b, 0x0b, - 0x12, 0xba, 0x75, 0xec, 0xd4, 0xa9, 0x4b, 0x81, 0x7e, 0x86, 0x0e, 0x99, 0x3b, 0x67, 0x0c, 0x32, - 0xb4, 0x85, 0x07, 0x21, 0xb0, 0x87, 0x7e, 0x8d, 0x82, 0xe4, 0xf1, 0x8f, 0x14, 0xd2, 0x91, 0xaa, - 0x4e, 0x45, 0x27, 0xe9, 0x78, 0xcf, 0xfb, 0xbc, 0xef, 0x3d, 0xcf, 0x7b, 0x77, 0x24, 0x14, 0x5a, - 0xa8, 0x35, 0xec, 0x12, 0x53, 0x6a, 0x31, 0x95, 0x32, 0xd4, 0x31, 0x4c, 0x5d, 0xea, 0xef, 0x4b, - 0x6c, 0x20, 0x5a, 0x36, 0x61, 0x44, 0xb8, 0xcd, 0xe7, 0xc5, 0x70, 0x5e, 0xec, 0xef, 0xe7, 0x37, - 0x74, 0xa2, 0x13, 0x17, 0x21, 0x39, 0xff, 0x3c, 0x70, 0x7e, 0x4b, 0x25, 0xb4, 0x47, 0x68, 0xd3, - 0x9b, 0xf0, 0x06, 0x7c, 0xea, 0x8e, 0x37, 0x92, 0x7a, 0xd4, 0xe5, 0xef, 0x51, 0x9d, 0x4f, 0x94, - 0xf8, 0x84, 0x6a, 0x0f, 0x2d, 0x46, 0x24, 0x8a, 0x55, 0xab, 0x7c, 0x70, 0xd8, 0xd9, 0x97, 0x3a, - 0x78, 0xe8, 0x07, 0x97, 0xe2, 0x8b, 0xb4, 0x90, 0x8d, 0x7a, 0x3e, 0x66, 0x27, 0x1e, 0x13, 0x29, - 0xdb, 0xc3, 0x3d, 0x8c, 0xe0, 0xd4, 0x36, 0x56, 0x3b, 0x16, 0x31, 0x4c, 0xc6, 0xa1, 0xe1, 0x03, - 0x8e, 0x7e, 0x87, 0x57, 0x17, 0x32, 0xb6, 0x30, 0x43, 0xfb, 0xd2, 0x38, 0x67, 0x31, 0xa1, 0x3e, - 0x62, 0x79, 0x80, 0xd2, 0x4f, 0x19, 0xb8, 0x5d, 0xa5, 0x7a, 0xc5, 0xc6, 0x88, 0x61, 0xb9, 0x51, - 0xf9, 0x12, 0x75, 0x0d, 0x0d, 0x31, 0x62, 0x0b, 0x9b, 0x90, 0xa5, 0x86, 0x6e, 0x62, 0x3b, 0x97, - 0xda, 0x4e, 0xed, 0x2e, 0x2b, 0x7c, 0x24, 0x7c, 0x02, 0x2b, 0x1a, 0xa6, 0xaa, 0x6d, 0x58, 0xcc, - 0x20, 0x66, 0x2e, 0xbd, 0x9d, 0xda, 0x5d, 0x29, 0xdf, 0x17, 0xb9, 0xa6, 0xa1, 0x13, 0x6e, 0x39, - 0xe2, 0x71, 0x08, 0x55, 0xa2, 0x71, 0xc2, 0x57, 0x00, 0x2a, 0xe9, 0xf5, 0x0c, 0x4a, 0x1d, 0x96, - 0x8c, 0x93, 0x42, 0xfe, 0xe0, 0x62, 0x54, 0xdc, 0xd1, 0x0d, 0xd6, 0x3e, 0x6f, 0x89, 0x2a, 0xe9, - 0x49, 0xbe, 0x01, 0xee, 0xcf, 0x1e, 0xd5, 0x3a, 0x12, 0x1b, 0x5a, 0x98, 0x8a, 0xc7, 0x58, 0x7d, - 0xf5, 0x7c, 0x0f, 0x78, 0xca, 0x63, 0xac, 0x2a, 0x11, 0x2e, 0xe1, 0x63, 0x00, 0xbe, 0xea, 0xa6, - 0xd5, 0xc9, 0x2d, 0xba, 0xf5, 0x15, 0xfd, 0xfa, 0x3c, 0x33, 0xc5, 0xc0, 0x4c, 0xb1, 0x76, 0xde, - 0x7a, 0x86, 0x87, 0xca, 0x32, 0x0f, 0xa9, 0x75, 0x84, 0x2a, 0x64, 0x5b, 0x4c, 0x75, 0x62, 0x97, - 0xb6, 0x53, 0xbb, 0xab, 0xf2, 0xe1, 0xc5, 0xa8, 0x58, 0x8e, 0x54, 0xc5, 0x91, 0x6a, 0x1b, 0x19, - 0xa6, 0x3f, 0xe0, 0x85, 0xc9, 0x27, 0xb5, 0xc7, 0x4f, 0x1e, 0x71, 0xca, 0xa5, 0x16, 0x53, 0x6b, - 0x1d, 0xe1, 0x08, 0x32, 0x16, 0xb1, 0x72, 0x59, 0xb7, 0x8e, 0x5d, 0x31, 0xb6, 0x6b, 0xc5, 0x9a, - 0x4d, 0xc8, 0xd9, 0xe7, 0x67, 0x35, 0x42, 0x29, 0x76, 0x57, 0xa1, 0x38, 0x41, 0xa5, 0x22, 0xdc, - 0x8b, 0x35, 0x47, 0xc1, 0xd4, 0x22, 0x26, 0xc5, 0xa5, 0x51, 0x06, 0x36, 0xa3, 0x88, 0x63, 0xdc, - 0xc5, 0x3a, 0x72, 0x05, 0x4e, 0xf2, 0x6f, 0x5c, 0x9e, 0xf4, 0xcc, 0xf2, 0xf0, 0xf5, 0x64, 0xfe, - 0xc6, 0x7a, 0x84, 0x13, 0x00, 0x0e, 0x6a, 0xb2, 0x01, 0xb7, 0xe6, 0x41, 0x02, 0x85, 0xec, 0x3d, - 0x95, 0x1b, 0x95, 0x06, 0xb2, 0x6c, 0x42, 0x58, 0x63, 0xa0, 0x2c, 0xf3, 0xf9, 0xc6, 0x40, 0xf8, - 0x02, 0x6e, 0x84, 0x54, 0x4d, 0xc3, 0x3c, 0x23, 0xae, 0x5d, 0x2b, 0xe5, 0xf7, 0xa3, 0x7c, 0x91, - 0x6d, 0xd3, 0xdf, 0x17, 0x1b, 0x36, 0x32, 0x29, 0x52, 0x1d, 0x79, 0x4e, 0xcc, 0x33, 0xa2, 0xac, - 0x05, 0x74, 0xce, 0x50, 0x28, 0xc3, 0x0a, 0xed, 0x22, 0xda, 0xe6, 0xe5, 0x65, 0x5d, 0xf7, 0x6f, - 0x5d, 0x8c, 0x8a, 0x6b, 0x72, 0xa3, 0x52, 0xe7, 0x33, 0x8d, 0x81, 0x02, 0x34, 0xf8, 0x2f, 0x7c, - 0x03, 0x6b, 0x9a, 0xa7, 0x39, 0xb1, 0x9b, 0xd4, 0xd0, 0x73, 0xff, 0x73, 0xa3, 0x3e, 0xbc, 0x18, - 0x15, 0x0f, 0x66, 0xe9, 0x99, 0xba, 0xa1, 0x9b, 0x88, 0x9d, 0xdb, 0x58, 0x59, 0x0d, 0xf8, 0xea, - 0x86, 0x5e, 0xda, 0x86, 0x42, 0xbc, 0xbf, 0x41, 0x0b, 0xfc, 0x9c, 0x86, 0x9b, 0x55, 0xaa, 0xcb, - 0x8d, 0xca, 0xa9, 0xc9, 0x43, 0x71, 0xa2, 0xf9, 0x55, 0x58, 0x3d, 0x37, 0x5b, 0xc4, 0xd4, 0xf8, - 0x1a, 0xd3, 0x33, 0x5b, 0xb0, 0x12, 0xc4, 0x37, 0x06, 0x93, 0x8a, 0x65, 0xa6, 0x51, 0x8c, 0xc0, - 0x66, 0x44, 0x31, 0x3f, 0xda, 0x91, 0x6e, 0x71, 0x5e, 0xe9, 0x36, 0x42, 0xe9, 0x38, 0xaf, 0x23, - 0x61, 0x1e, 0x72, 0x93, 0xfa, 0x04, 0xe2, 0xfd, 0x9a, 0x86, 0x5b, 0x55, 0xaa, 0x3f, 0xd5, 0xb4, - 0x0a, 0xe9, 0x63, 0x13, 0x99, 0xac, 0x6e, 0xe8, 0xd7, 0xa8, 0x97, 0xed, 0xa3, 0xae, 0xbf, 0x6d, - 0xe6, 0x38, 0x19, 0xfa, 0xa8, 0xeb, 0x1d, 0x34, 0x1a, 0x76, 0xe9, 0x32, 0xf3, 0xd1, 0x69, 0xd8, - 0xa1, 0xdb, 0x19, 0xdb, 0x11, 0x6d, 0x44, 0xdb, 0xae, 0xa2, 0xcb, 0x91, 0x36, 0xff, 0x14, 0xd1, - 0xb6, 0xf0, 0x0c, 0x32, 0x8e, 0xda, 0x4b, 0xf3, 0xaa, 0xed, 0xb0, 0x94, 0xee, 0xc2, 0xd6, 0x1b, - 0xfa, 0x05, 0xea, 0xfe, 0x98, 0x82, 0x1b, 0x55, 0xaa, 0x9f, 0x5a, 0x1a, 0x62, 0xb8, 0xe6, 0xde, - 0x89, 0xc2, 0x21, 0x2c, 0xa3, 0x73, 0xd6, 0x26, 0xb6, 0xc1, 0x86, 0x9e, 0xbc, 0x72, 0xee, 0xd5, - 0xf3, 0xbd, 0x0d, 0x7e, 0x00, 0x3d, 0xd5, 0x34, 0x1b, 0x53, 0x5a, 0x67, 0xb6, 0x61, 0xea, 0x4a, - 0x08, 0x15, 0x3e, 0x82, 0xac, 0x77, 0xab, 0xf2, 0x9e, 0xbd, 0x97, 0x74, 0xf2, 0xb8, 0x20, 0x79, - 0xf1, 0xc5, 0xa8, 0xb8, 0xa0, 0xf0, 0x90, 0xa3, 0xf5, 0xef, 0xfe, 0xfc, 0xe5, 0x41, 0x48, 0x56, - 0xda, 0x82, 0x3b, 0x13, 0x75, 0x05, 0x35, 0xff, 0x96, 0x81, 0xbb, 0xe3, 0x2b, 0x3a, 0xf5, 0x1b, - 0xbe, 0x6e, 0xe8, 0xf4, 0x5f, 0xde, 0x1b, 0x2a, 0xdc, 0x8c, 0x9e, 0x0f, 0xcd, 0x7f, 0xa4, 0x51, - 0xd6, 0x23, 0x47, 0x86, 0xb3, 0xbd, 0x18, 0x6c, 0x05, 0xfb, 0xfe, 0x8d, 0x6c, 0xd9, 0x79, 0xb3, - 0x6d, 0xfa, 0xdc, 0xa7, 0x63, 0x59, 0x4b, 0xef, 0xc2, 0xfd, 0x6b, 0x7c, 0x0d, 0xfc, 0x7f, 0x9d, - 0xf6, 0xfd, 0x0f, 0x6e, 0xdb, 0x28, 0xf0, 0x3f, 0xff, 0xe7, 0xf6, 0x3f, 0x74, 0x22, 0x56, 0x61, - 0xdf, 0x89, 0xf2, 0xef, 0x59, 0xc8, 0x54, 0xa9, 0x2e, 0x0c, 0x40, 0x88, 0x79, 0x3d, 0x7d, 0x98, - 0xb0, 0xff, 0x63, 0xdf, 0x97, 0xf2, 0x4f, 0x66, 0x41, 0xfb, 0x15, 0x08, 0xdf, 0xc2, 0xff, 0xe3, - 0xde, 0xac, 0xf6, 0xa6, 0x20, 0x0b, 0xe1, 0xf9, 0x83, 0x99, 0xe0, 0x41, 0x72, 0x03, 0xd6, 0xc6, - 0xef, 0xf4, 0xf7, 0x92, 0x79, 0xc6, 0x80, 0x79, 0x69, 0x4a, 0x60, 0x90, 0xaa, 0x0b, 0xeb, 0x13, - 0x37, 0xe0, 0x6e, 0x32, 0xc5, 0x38, 0x32, 0xff, 0x68, 0x5a, 0x64, 0x90, 0xed, 0xfb, 0x14, 0xe4, - 0x12, 0x8f, 0xd7, 0xf2, 0x54, 0x74, 0x63, 0x31, 0xf9, 0xa3, 0xd9, 0x63, 0x26, 0x8b, 0x89, 0xdf, - 0xeb, 0xd7, 0x17, 0x13, 0x1b, 0xf3, 0x96, 0x62, 0xae, 0xed, 0x78, 0xe1, 0x0c, 0x56, 0xc7, 0xee, - 0xca, 0x9d, 0x64, 0xae, 0x28, 0x2e, 0x2f, 0x4e, 0x87, 0xf3, 0xf3, 0xc8, 0x9f, 0xbd, 0xb8, 0x2c, - 0xa4, 0x5e, 0x5e, 0x16, 0x52, 0xaf, 0x2f, 0x0b, 0xa9, 0x1f, 0xae, 0x0a, 0x0b, 0x2f, 0xaf, 0x0a, - 0x0b, 0x7f, 0x5c, 0x15, 0x16, 0xbe, 0x7e, 0xeb, 0x11, 0x33, 0x88, 0x7e, 0x49, 0xba, 0xdb, 0xbd, - 0x95, 0x75, 0xbf, 0x24, 0x1f, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xda, 0x28, 0xdf, 0xb1, - 0x0f, 0x00, 0x00, + // 1149 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0xcf, 0x6f, 0x1a, 0x47, + 0x14, 0x36, 0xc6, 0xd0, 0xf2, 0x0c, 0x76, 0xb2, 0x4d, 0x9c, 0x35, 0x51, 0x80, 0x90, 0x26, 0xa1, + 0x55, 0xbc, 0xc4, 0x24, 0xb6, 0x5a, 0x57, 0xaa, 0x14, 0xec, 0x4a, 0x89, 0x12, 0x54, 0xb4, 0xe0, + 0xa8, 0x8a, 0xd4, 0xa2, 0x61, 0x77, 0xbc, 0xac, 0x80, 0x9d, 0xd5, 0xce, 0x80, 0x40, 0xbd, 0xf5, + 0x58, 0xa9, 0x52, 0x4f, 0xbd, 0xf4, 0x9f, 0xe8, 0x21, 0xe7, 0x1e, 0x7a, 0x8a, 0x7a, 0x8a, 0x72, + 0xa8, 0x2a, 0x1f, 0xac, 0xca, 0x3e, 0xf4, 0xdf, 0xa8, 0x76, 0x76, 0xf6, 0x07, 0x0e, 0xb4, 0xb8, + 0xf4, 0xd4, 0x13, 0xcc, 0xce, 0xf7, 0xbe, 0xf7, 0xde, 0xf7, 0x7e, 0xb0, 0x40, 0xae, 0x8d, 0xda, + 0xe3, 0x1e, 0xb1, 0xca, 0x6d, 0xa6, 0x51, 0x86, 0xba, 0xa6, 0x65, 0x94, 0x87, 0xdb, 0x65, 0x36, + 0x52, 0x6c, 0x87, 0x30, 0x22, 0x5d, 0x15, 0xf7, 0x4a, 0x78, 0xaf, 0x0c, 0xb7, 0xb3, 0x57, 0x0c, + 0x62, 0x10, 0x8e, 0x28, 0xbb, 0xdf, 0x3c, 0x70, 0x76, 0x53, 0x23, 0xb4, 0x4f, 0x68, 0xcb, 0xbb, + 0xf0, 0x0e, 0xe2, 0xea, 0x9a, 0x77, 0x2a, 0xf7, 0x29, 0xe7, 0xef, 0x53, 0x43, 0x5c, 0x14, 0xc5, + 0x85, 0xe6, 0x8c, 0x6d, 0x46, 0xca, 0x14, 0x6b, 0x76, 0x65, 0x67, 0xb7, 0xbb, 0x5d, 0xee, 0xe2, + 0xb1, 0x6f, 0x5c, 0x9c, 0x1e, 0xa4, 0x8d, 0x1c, 0xd4, 0xf7, 0x31, 0xf7, 0x22, 0x18, 0xad, 0x83, + 0xb5, 0xae, 0x4d, 0x4c, 0x8b, 0xb9, 0xb0, 0x89, 0x07, 0x02, 0xfd, 0xbe, 0xf0, 0x1a, 0xb2, 0xb5, + 0x31, 0x43, 0xdb, 0xfe, 0x59, 0xa0, 0xf2, 0x33, 0xfc, 0x12, 0xdb, 0x03, 0x14, 0x7f, 0x8c, 0xc3, + 0xd5, 0x1a, 0x35, 0xf6, 0x1d, 0x8c, 0x18, 0xae, 0x36, 0xf7, 0x9f, 0xa3, 0x9e, 0xa9, 0x23, 0x46, + 0x1c, 0x69, 0x03, 0x92, 0xd4, 0x34, 0x2c, 0xec, 0xc8, 0xb1, 0x42, 0xac, 0x94, 0x52, 0xc5, 0x49, + 0xfa, 0x0c, 0x56, 0x75, 0x4c, 0x35, 0xc7, 0xb4, 0x99, 0x49, 0x2c, 0x79, 0xb9, 0x10, 0x2b, 0xad, + 0x56, 0x6e, 0x29, 0x42, 0xab, 0x50, 0x61, 0x1e, 0x8e, 0x72, 0x10, 0x42, 0xd5, 0xa8, 0x9d, 0xf4, + 0x05, 0x80, 0x46, 0xfa, 0x7d, 0x93, 0x52, 0x97, 0x25, 0xee, 0xba, 0xa8, 0x7e, 0x74, 0x7c, 0x92, + 0xbf, 0x63, 0x98, 0xac, 0x33, 0x68, 0x2b, 0x1a, 0xe9, 0x97, 0x7d, 0x61, 0xf9, 0xc7, 0x16, 0xd5, + 0xbb, 0x65, 0x36, 0xb6, 0x31, 0x55, 0x0e, 0xb0, 0xf6, 0xe6, 0xe5, 0x16, 0x08, 0x97, 0x07, 0x58, + 0x53, 0x23, 0x5c, 0xd2, 0xa7, 0x00, 0x22, 0xeb, 0x96, 0xdd, 0x95, 0x57, 0x78, 0x7c, 0x79, 0x3f, + 0x3e, 0xaf, 0x48, 0x4a, 0x50, 0x24, 0xa5, 0x3e, 0x68, 0x3f, 0xc5, 0x63, 0x35, 0x25, 0x4c, 0xea, + 0x5d, 0xa9, 0x06, 0xc9, 0x36, 0xd3, 0x5c, 0xdb, 0x44, 0x21, 0x56, 0x4a, 0x57, 0x77, 0x8f, 0x4f, + 0xf2, 0x95, 0x48, 0x54, 0x02, 0xa9, 0x75, 0x90, 0x69, 0xf9, 0x07, 0x11, 0x58, 0xf5, 0x49, 0xfd, + 0xc1, 0xc3, 0xfb, 0x82, 0x32, 0xd1, 0x66, 0x5a, 0xbd, 0x2b, 0xed, 0x41, 0xdc, 0x26, 0xb6, 0x9c, + 0xe4, 0x71, 0x94, 0x94, 0xa9, 0xdd, 0xa8, 0xd4, 0x1d, 0x42, 0x8e, 0x3e, 0x3f, 0xaa, 0x13, 0x4a, + 0x31, 0xcf, 0x42, 0x75, 0x8d, 0x8a, 0x79, 0xb8, 0x31, 0xb5, 0x38, 0x2a, 0xa6, 0x36, 0xb1, 0x28, + 0x2e, 0x7e, 0x97, 0x80, 0x8d, 0x28, 0xe2, 0x00, 0xf7, 0xb0, 0x81, 0xb8, 0xc0, 0xb3, 0xea, 0x37, + 0x29, 0xcf, 0xf2, 0x85, 0xe5, 0x11, 0xf9, 0xc4, 0xff, 0x45, 0x3e, 0xd2, 0x0b, 0xc8, 0xb8, 0x20, + 0xec, 0xb4, 0x84, 0xc2, 0x2b, 0x0b, 0x29, 0xbc, 0xea, 0x91, 0x55, 0xb9, 0xce, 0x5f, 0xc2, 0xfa, + 0x10, 0xf5, 0x04, 0x71, 0xab, 0x67, 0x52, 0x26, 0x27, 0x0a, 0xf1, 0x05, 0xd8, 0xd3, 0x43, 0xd4, + 0xe3, 0xd4, 0xcf, 0x4c, 0xca, 0xa4, 0x9b, 0x90, 0x16, 0xf9, 0xb5, 0x98, 0xd9, 0xc7, 0xbc, 0x9e, + 0x19, 0x2f, 0x02, 0xd3, 0x32, 0x9a, 0x66, 0x1f, 0x4b, 0xb7, 0xbc, 0xec, 0x5c, 0xc8, 0x10, 0xf5, + 0x06, 0x58, 0x7e, 0xa7, 0x10, 0x2b, 0xc5, 0x55, 0xdf, 0xee, 0xb9, 0xfb, 0x4c, 0x7a, 0x0c, 0x10, + 0xf0, 0x8c, 0xe4, 0x77, 0xb9, 0x8a, 0x1f, 0x44, 0x55, 0x8c, 0x4c, 0xfa, 0x70, 0x5b, 0x69, 0x3a, + 0xc8, 0xa2, 0x48, 0x73, 0x2b, 0xfa, 0xc4, 0x3a, 0x22, 0x6a, 0xca, 0x77, 0x38, 0x92, 0x2a, 0xb0, + 0x4a, 0x7b, 0x88, 0x76, 0x04, 0x55, 0x8a, 0x4b, 0x79, 0xf9, 0xf8, 0x24, 0x9f, 0xa9, 0x36, 0xf7, + 0x1b, 0xe2, 0xa6, 0x39, 0x52, 0x81, 0x06, 0xdf, 0xa5, 0xaf, 0x20, 0xa3, 0x7b, 0x2d, 0x42, 0x9c, + 0x16, 0x35, 0x0d, 0x19, 0xb8, 0xd5, 0xc7, 0xc7, 0x27, 0xf9, 0x9d, 0x8b, 0x48, 0xd4, 0x30, 0x0d, + 0x0b, 0xb1, 0x81, 0x83, 0xd5, 0x74, 0xc0, 0xd7, 0x30, 0x8d, 0x62, 0x01, 0x72, 0xd3, 0xdb, 0x31, + 0xe8, 0xd8, 0x5f, 0x96, 0xe1, 0x52, 0x8d, 0x1a, 0xd5, 0xe6, 0xfe, 0xa1, 0x25, 0x4c, 0xf1, 0xcc, + 0x5e, 0xbd, 0x09, 0xe9, 0x81, 0xd5, 0x26, 0x96, 0x2e, 0x72, 0x74, 0xbb, 0x35, 0xad, 0xae, 0x06, + 0xcf, 0x9a, 0x23, 0xe9, 0x36, 0xac, 0x45, 0x20, 0x6e, 0x65, 0xe2, 0xbc, 0x32, 0x99, 0x10, 0xe4, + 0xd6, 0xe6, 0x2e, 0xac, 0x87, 0x30, 0xaf, 0x3a, 0x2b, 0xbc, 0x3a, 0xa1, 0xb5, 0x57, 0x9f, 0x73, + 0xaa, 0x26, 0xe6, 0x51, 0x95, 0xc0, 0x46, 0x44, 0x55, 0xdf, 0xda, 0x95, 0x37, 0xb9, 0xa8, 0xbc, + 0x57, 0x42, 0x79, 0x05, 0xaf, 0x2b, 0x73, 0x16, 0xe4, 0xf3, 0x1a, 0x06, 0x02, 0xff, 0xbc, 0x0c, + 0x97, 0x6b, 0xd4, 0x78, 0xa4, 0xeb, 0xfb, 0x64, 0x88, 0x2d, 0x64, 0xb1, 0x86, 0x69, 0xcc, 0x54, + 0xb8, 0x06, 0x49, 0x77, 0x6a, 0xc4, 0x26, 0x58, 0x60, 0xd9, 0x0d, 0x51, 0xcf, 0xdb, 0x9d, 0x3a, + 0xe6, 0x74, 0xf1, 0xc5, 0xe8, 0x74, 0xec, 0xd2, 0xdd, 0x81, 0xf5, 0x70, 0x58, 0x5a, 0x1d, 0x44, + 0x3b, 0xbc, 0x6a, 0x29, 0x35, 0x13, 0x8c, 0xc1, 0x63, 0x44, 0x3b, 0xd2, 0x53, 0x88, 0xbb, 0x6a, + 0x27, 0x16, 0x55, 0xdb, 0x65, 0x29, 0x5e, 0x87, 0xcd, 0xb7, 0xf4, 0x0b, 0xd4, 0xfd, 0x21, 0x06, + 0xeb, 0x35, 0x6a, 0x1c, 0xda, 0x3a, 0x62, 0xb8, 0xce, 0x7f, 0xbe, 0xa5, 0x5d, 0x48, 0xa1, 0x01, + 0xeb, 0x10, 0xc7, 0x64, 0x63, 0x4f, 0xde, 0xaa, 0xfc, 0xe6, 0xe5, 0xd6, 0x15, 0xb1, 0x53, 0x1f, + 0xe9, 0xba, 0x83, 0x29, 0x6d, 0x30, 0xc7, 0xb4, 0x0c, 0x35, 0x84, 0x4a, 0x9f, 0x40, 0xd2, 0x7b, + 0x01, 0x10, 0x5b, 0xf8, 0xc6, 0xac, 0x65, 0xca, 0x41, 0xd5, 0x95, 0x57, 0x27, 0xf9, 0x25, 0x55, + 0x98, 0xec, 0xad, 0x7d, 0xf3, 0xe7, 0x4f, 0x1f, 0x86, 0x64, 0xc5, 0x4d, 0xb8, 0x76, 0x2e, 0xae, + 0x20, 0xe6, 0xdf, 0xe2, 0x70, 0x7d, 0x32, 0xa3, 0x43, 0xbf, 0xe7, 0x1b, 0xa6, 0x41, 0xff, 0xe7, + 0xbd, 0xa1, 0xc1, 0xa5, 0xe8, 0x0e, 0x69, 0xfd, 0x27, 0x8d, 0xb2, 0x16, 0x59, 0x41, 0xee, 0x78, + 0x31, 0xd8, 0x0c, 0xe6, 0xfe, 0x2d, 0x6f, 0x0b, 0x2f, 0x81, 0x0d, 0x9f, 0xfb, 0x70, 0xc2, 0x6b, + 0xf1, 0x36, 0xdc, 0xfa, 0x9b, 0xba, 0xfa, 0xf5, 0xaf, 0xfc, 0x9a, 0x80, 0x78, 0x8d, 0x1a, 0xd2, + 0x08, 0xa4, 0x29, 0xef, 0x79, 0xf7, 0x66, 0x74, 0xdd, 0xd4, 0x17, 0x8f, 0xec, 0xc3, 0x8b, 0xa0, + 0xfd, 0x08, 0xa4, 0xaf, 0xe1, 0xbd, 0x69, 0xaf, 0x28, 0x5b, 0x73, 0x90, 0x85, 0xf0, 0xec, 0xce, + 0x85, 0xe0, 0x81, 0x73, 0x13, 0x32, 0x93, 0xbf, 0x36, 0x77, 0x67, 0xf3, 0x4c, 0x00, 0xb3, 0xe5, + 0x39, 0x81, 0x81, 0xab, 0x1e, 0xac, 0x9d, 0xdb, 0xbb, 0xa5, 0xd9, 0x14, 0x93, 0xc8, 0xec, 0xfd, + 0x79, 0x91, 0x81, 0xb7, 0x6f, 0x63, 0x20, 0xcf, 0x1c, 0xea, 0xca, 0x5c, 0x74, 0x13, 0x36, 0xd9, + 0xbd, 0x8b, 0xdb, 0x04, 0xc1, 0x1c, 0x41, 0x7a, 0x62, 0x29, 0xde, 0x99, 0xcd, 0x15, 0xc5, 0x65, + 0x95, 0xf9, 0x70, 0xbe, 0x9f, 0xea, 0xb3, 0x57, 0xa7, 0xb9, 0xd8, 0xeb, 0xd3, 0x5c, 0xec, 0x8f, + 0xd3, 0x5c, 0xec, 0xfb, 0xb3, 0xdc, 0xd2, 0xeb, 0xb3, 0xdc, 0xd2, 0xef, 0x67, 0xb9, 0xa5, 0x17, + 0xff, 0xb8, 0x4b, 0x46, 0xd1, 0x7f, 0x41, 0x7c, 0xd2, 0xda, 0x49, 0xfe, 0x2f, 0xe8, 0xc1, 0x5f, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x92, 0xce, 0x72, 0x31, 0x45, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -892,8 +824,6 @@ type MsgClient interface { // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) - // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker - AddValidatorUnbondingSig(ctx context.Context, in *MsgAddValidatorUnbondingSig, opts ...grpc.CallOption) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -951,15 +881,6 @@ func (c *msgClient) AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCove return out, nil } -func (c *msgClient) AddValidatorUnbondingSig(ctx context.Context, in *MsgAddValidatorUnbondingSig, opts ...grpc.CallOption) (*MsgAddValidatorUnbondingSigResponse, error) { - out := new(MsgAddValidatorUnbondingSigResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddValidatorUnbondingSig", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) @@ -983,8 +904,6 @@ type MsgServer interface { // - unbonding tx submitted to babylon by staker // - slashing tx corresponding to unbodning tx submitted to babylon by staker AddCovenantUnbondingSigs(context.Context, *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) - // AddValidatorUnbondingSig handles a signature from validator for unbonding tx submitted to babylon by staker - AddValidatorUnbondingSig(context.Context, *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } @@ -1008,9 +927,6 @@ func (*UnimplementedMsgServer) AddCovenantSig(ctx context.Context, req *MsgAddCo func (*UnimplementedMsgServer) AddCovenantUnbondingSigs(ctx context.Context, req *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddCovenantUnbondingSigs not implemented") } -func (*UnimplementedMsgServer) AddValidatorUnbondingSig(ctx context.Context, req *MsgAddValidatorUnbondingSig) (*MsgAddValidatorUnbondingSigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddValidatorUnbondingSig not implemented") -} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -1109,24 +1025,6 @@ func _Msg_AddCovenantUnbondingSigs_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _Msg_AddValidatorUnbondingSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddValidatorUnbondingSig) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).AddValidatorUnbondingSig(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/AddValidatorUnbondingSig", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddValidatorUnbondingSig(ctx, req.(*MsgAddValidatorUnbondingSig)) - } - return interceptor(ctx, in, info, handler) -} - func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -1169,10 +1067,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AddCovenantUnbondingSigs", Handler: _Msg_AddCovenantUnbondingSigs_Handler, }, - { - MethodName: "AddValidatorUnbondingSig", - Handler: _Msg_AddValidatorUnbondingSig_Handler, - }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -1325,7 +1219,7 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x3a + dAtA[i] = 0x52 } if m.SlashingTx != nil { { @@ -1337,11 +1231,11 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 + dAtA[i] = 0x4a } - if m.StakingTxInfo != nil { + if m.StakingTx != nil { { - size, err := m.StakingTxInfo.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1349,15 +1243,39 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x42 } - if m.StakingTx != nil { + if m.StakingValue != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.StakingValue)) + i-- + dAtA[i] = 0x38 + } + if m.StakingTime != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.StakingTime)) + i-- + dAtA[i] = 0x30 + } + if len(m.ValBtcPkList) > 0 { + for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.ValBtcPkList[iNdEx].Size() + i -= size + if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.StakerBtcPk != nil { { - size, err := m.StakingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { + size := m.StakerBtcPk.Size() + i -= size + if _, err := m.StakerBtcPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } - i -= size i = encodeVarintTx(dAtA, i, uint64(size)) } i-- @@ -1450,7 +1368,7 @@ func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x32 } if m.SlashingTx != nil { { @@ -1462,17 +1380,22 @@ func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x2a } - if m.UnbondingTx != nil { - { - size, err := m.UnbondingTx.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } + if m.UnbondingValue != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.UnbondingValue)) + i-- + dAtA[i] = 0x20 + } + if m.UnbondingTime != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x18 + } + if len(m.UnbondingTx) > 0 { + i -= len(m.UnbondingTx) + copy(dAtA[i:], m.UnbondingTx) + i = encodeVarintTx(dAtA, i, uint64(len(m.UnbondingTx))) i-- dAtA[i] = 0x12 } @@ -1776,102 +1699,6 @@ func (m *MsgAddCovenantUnbondingSigsResponse) MarshalToSizedBuffer(dAtA []byte) return len(dAtA) - i, nil } -func (m *MsgAddValidatorUnbondingSig) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgAddValidatorUnbondingSig) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddValidatorUnbondingSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.UnbondingTxSig != nil { - { - size := m.UnbondingTxSig.Size() - i -= size - if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } - if len(m.StakingTxHash) > 0 { - i -= len(m.StakingTxHash) - copy(dAtA[i:], m.StakingTxHash) - i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) - i-- - dAtA[i] = 0x22 - } - if m.DelPk != nil { - { - size := m.DelPk.Size() - i -= size - if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if m.ValPk != nil { - { - size := m.ValPk.Size() - i -= size - if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgAddValidatorUnbondingSigResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgAddValidatorUnbondingSigResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddValidatorUnbondingSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -1943,12 +1770,24 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovTx(uint64(l)) } - if m.StakingTx != nil { - l = m.StakingTx.Size() + if m.StakerBtcPk != nil { + l = m.StakerBtcPk.Size() n += 1 + l + sovTx(uint64(l)) } - if m.StakingTxInfo != nil { - l = m.StakingTxInfo.Size() + if len(m.ValBtcPkList) > 0 { + for _, e := range m.ValBtcPkList { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + if m.StakingTime != 0 { + n += 1 + sovTx(uint64(m.StakingTime)) + } + if m.StakingValue != 0 { + n += 1 + sovTx(uint64(m.StakingValue)) + } + if m.StakingTx != nil { + l = m.StakingTx.Size() n += 1 + l + sovTx(uint64(l)) } if m.SlashingTx != nil { @@ -1981,10 +1820,16 @@ func (m *MsgBTCUndelegate) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.UnbondingTx != nil { - l = m.UnbondingTx.Size() + l = len(m.UnbondingTx) + if l > 0 { n += 1 + l + sovTx(uint64(l)) } + if m.UnbondingTime != 0 { + n += 1 + sovTx(uint64(m.UnbondingTime)) + } + if m.UnbondingValue != 0 { + n += 1 + sovTx(uint64(m.UnbondingValue)) + } if m.SlashingTx != nil { l = m.SlashingTx.Size() n += 1 + l + sovTx(uint64(l)) @@ -2109,44 +1954,6 @@ func (m *MsgAddCovenantUnbondingSigsResponse) Size() (n int) { return n } -func (m *MsgAddValidatorUnbondingSig) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.ValPk != nil { - l = m.ValPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.DelPk != nil { - l = m.DelPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.StakingTxHash) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.UnbondingTxSig != nil { - l = m.UnbondingTxSig.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgAddValidatorUnbondingSigResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2599,9 +2406,9 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StakerBtcPk", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -2611,31 +2418,103 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if m.StakingTx == nil { - m.StakingTx = &BabylonBTCTaprootTx{} - } - if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.StakerBtcPk = &v + if err := m.StakerBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxInfo", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.ValBtcPkList = append(m.ValBtcPkList, v) + if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTime", wireType) + } + m.StakingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StakingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingValue", wireType) + } + m.StakingValue = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StakingValue |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTx", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2662,14 +2541,14 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.StakingTxInfo == nil { - m.StakingTxInfo = &types1.TransactionInfo{} + if m.StakingTx == nil { + m.StakingTx = &types1.TransactionInfo{} } - if err := m.StakingTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.StakingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 6: + case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } @@ -2704,7 +2583,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) } @@ -2875,7 +2754,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -2885,29 +2764,65 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } + m.UnbondingTx = append(m.UnbondingTx[:0], dAtA[iNdEx:postIndex]...) if m.UnbondingTx == nil { - m.UnbondingTx = &BabylonBTCTaprootTx{} - } - if err := m.UnbondingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.UnbondingTx = []byte{} } iNdEx = postIndex case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) + } + m.UnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingValue", wireType) + } + m.UnbondingValue = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingValue |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } @@ -2942,7 +2857,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } @@ -3786,275 +3701,6 @@ func (m *MsgAddCovenantUnbondingSigsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddValidatorUnbondingSig) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgAddValidatorUnbondingSig: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddValidatorUnbondingSig: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signer = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValPk = &v - if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.DelPk = &v - if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakingTxHash = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.UnbondingTxSig = &v - if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgAddValidatorUnbondingSigResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgAddValidatorUnbondingSigResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddValidatorUnbondingSigResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go index b8cb5f3b1..fac4aab24 100644 --- a/x/btcstaking/types/types.go +++ b/x/btcstaking/types/types.go @@ -1,8 +1,14 @@ package types import ( + "bytes" + "encoding/hex" + "fmt" + "math" + "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/wire" ) type PublicKeyInfo struct { @@ -18,3 +24,57 @@ func KeyDataFromScript(scriptData *btcstaking.StakingScriptData) *PublicKeyInfo CovenantKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.CovenantKey), } } + +func ParseBtcTx(txBytes []byte) (*wire.MsgTx, error) { + var msgTx wire.MsgTx + rbuf := bytes.NewReader(txBytes) + if err := msgTx.Deserialize(rbuf); err != nil { + return nil, err + } + + return &msgTx, nil +} + +func SerializeBtcTx(tx *wire.MsgTx) ([]byte, error) { + var txBuf bytes.Buffer + if err := tx.Serialize(&txBuf); err != nil { + return nil, err + } + return txBuf.Bytes(), nil +} + +func ParseBtcTxFromHex(txHex string) (*wire.MsgTx, []byte, error) { + txBytes, err := hex.DecodeString(txHex) + if err != nil { + return nil, nil, err + } + + parsed, err := ParseBtcTx(txBytes) + + if err != nil { + return nil, nil, err + } + + return parsed, txBytes, nil +} + +func GetOutputIdx(tx *wire.MsgTx, output *wire.TxOut) (uint32, error) { + for i, txOut := range tx.TxOut { + if bytes.Equal(txOut.PkScript, output.PkScript) && txOut.Value == output.Value { + return uint32(i), nil + } + } + + return 0, fmt.Errorf("output not found") +} + +func (del *BTCDelegation) GetStakingTime() uint16 { + diff := del.EndHeight - del.StartHeight + + if diff > math.MaxUint16 { + // In valid delegation, EndHeight is always greater than StartHeight and it is always uint16 value + panic("invalid delegation in database") + } + + return uint16(diff) +} From 00bf5307f8aa783aff16ecae4a0bae2bdfbcdff6 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 22 Nov 2023 16:02:25 +0400 Subject: [PATCH 107/202] Upgrade to Cosmos SDK 0.50.1 and Go 1.21.4 (#107) --------- Co-authored-by: Runchao Han --- .circleci/config.yml | 13 +- Makefile | 2 +- app/app.go | 607 +++++++++++------- app/app_test.go | 52 +- app/encoding.go | 39 +- app/export.go | 116 +++- app/genesis.go | 23 +- app/modules.go | 8 +- app/params/amino.go | 26 - app/params/config.go | 5 +- app/params/doc.go | 19 - app/params/encoding.go | 7 +- app/params/params.go | 8 - app/params/proto.go | 28 +- app/params/weights.go | 28 - app/test_helpers.go | 544 ++++------------ app/types.go | 44 -- app/utils.go | 8 +- btcstaking/staking.go | 22 +- btcstaking/staking_test.go | 10 +- btctxformatter/formatter.go | 28 +- btctxformatter/formatter_test.go | 22 +- .../docs/diagrams/create_raw_checkpoint.puml | 10 +- client/docs/swagger-ui/swagger.yaml | 200 +++--- client/tx/tx.go | 21 +- cmd/babylond/cmd/add_gen_bls_test.go | 93 ++- cmd/babylond/cmd/cmd_test.go | 2 +- cmd/babylond/cmd/create_bls_key.go | 4 +- cmd/babylond/cmd/flags.go | 11 +- cmd/babylond/cmd/genaccounts.go | 5 +- cmd/babylond/cmd/genaccounts_test.go | 8 +- cmd/babylond/cmd/genbls.go | 4 +- cmd/babylond/cmd/genbls_test.go | 26 +- cmd/babylond/cmd/genesis.go | 73 +-- cmd/babylond/cmd/root.go | 186 ++---- cmd/babylond/cmd/testnet.go | 70 +- cmd/babylond/cmd/testnet_test.go | 29 +- cmd/babylond/cmd/validate_genesis_test.go | 95 +-- cmd/babylond/main.go | 13 +- contrib/images/babylond/Dockerfile | 2 +- go.mod | 193 +++--- go.sum | 428 +++++++----- privval/file.go | 2 +- proto/babylon/btccheckpoint/v1/tx.proto | 4 + .../btclightclient/v1/btclightclient.proto | 2 +- proto/babylon/btclightclient/v1/tx.proto | 5 + proto/babylon/btcstaking/v1/btcstaking.proto | 2 +- proto/babylon/btcstaking/v1/incentive.proto | 4 +- proto/babylon/btcstaking/v1/params.proto | 4 +- proto/babylon/btcstaking/v1/tx.proto | 14 +- .../babylon/checkpointing/v1/checkpoint.proto | 8 +- proto/babylon/checkpointing/v1/tx.proto | 5 + proto/babylon/epoching/v1/tx.proto | 5 + proto/babylon/finality/v1/tx.proto | 6 + proto/babylon/incentive/params.proto | 8 +- proto/babylon/incentive/tx.proto | 3 + proto/babylon/zoneconcierge/v1/tx.proto | 3 +- proto/scripts/protocgen.sh | 2 +- simapp/config.go | 75 --- simapp/sim_bench_test.go | 164 ----- simapp/sim_test.go | 359 ----------- simapp/state.go | 245 ------- simapp/utils.go | 87 --- test/e2e/btc_staking_e2e_test.go | 11 +- test/e2e/configurer/base.go | 4 +- test/e2e/configurer/chain/chain.go | 2 +- .../configurer/chain/commands_btcstaking.go | 6 +- test/e2e/configurer/chain/node.go | 3 +- test/e2e/configurer/chain/queries.go | 6 +- test/e2e/configurer/config/constants.go | 12 +- test/e2e/configurer/factory.go | 4 +- test/e2e/containers/config.go | 4 +- test/e2e/initialization/config.go | 9 +- test/e2e/initialization/export.go | 2 +- test/e2e/initialization/node.go | 134 ++-- test/e2e/scripts/hermes_bootstrap.sh | 4 +- test/e2e/util/codec.go | 8 +- testutil/datagen/account_balance.go | 4 +- testutil/datagen/btc_header_info.go | 3 +- testutil/datagen/btc_transaction.go | 6 +- testutil/datagen/btcstaking.go | 12 +- testutil/datagen/finality.go | 22 +- testutil/datagen/genesiskey.go | 2 +- testutil/datagen/incentive.go | 5 +- testutil/datagen/priv_validator.go | 2 +- testutil/datagen/raw_checkpoint.go | 26 +- testutil/datagen/tendermint.go | 8 +- testutil/keeper/btccheckpoint.go | 23 +- testutil/keeper/btclightclient.go | 21 +- testutil/keeper/btcstaking.go | 21 +- testutil/keeper/checkpointing.go | 21 +- testutil/keeper/epoching.go | 21 +- testutil/keeper/finality.go | 23 +- testutil/keeper/incentive.go | 21 +- testutil/keeper/zoneconcierge.go | 41 +- .../mocks/checkpointing_expected_keepers.go | 98 +-- types/retry/log.go | 4 +- wasmbinding/bindings/utils.go | 2 +- wasmbinding/test/custom_query_test.go | 9 +- x/btccheckpoint/abci.go | 5 +- x/btccheckpoint/client/cli/tx.go | 12 - x/btccheckpoint/genesis.go | 6 +- x/btccheckpoint/genesis_test.go | 5 +- x/btccheckpoint/keeper/grpc_query.go | 10 +- .../keeper/grpc_query_params_test.go | 4 +- x/btccheckpoint/keeper/hooks.go | 17 +- x/btccheckpoint/keeper/incentive.go | 4 +- x/btccheckpoint/keeper/keeper.go | 74 ++- x/btccheckpoint/keeper/msg_server.go | 14 +- x/btccheckpoint/keeper/msg_server_test.go | 47 +- x/btccheckpoint/keeper/params.go | 18 +- x/btccheckpoint/keeper/submissions.go | 79 ++- x/btccheckpoint/module.go | 43 +- x/btccheckpoint/module_simulation.go | 12 +- x/btccheckpoint/types/btcutils.go | 2 +- x/btccheckpoint/types/btcutils_test.go | 10 - x/btccheckpoint/types/errors.go | 7 +- x/btccheckpoint/types/expected_keepers.go | 32 +- x/btccheckpoint/types/genesis.go | 3 - x/btccheckpoint/types/incentive.go | 2 +- x/btccheckpoint/types/mock_keepers.go | 18 +- x/btccheckpoint/types/msgs.go | 2 +- x/btccheckpoint/types/params.go | 2 +- x/btccheckpoint/types/tx.pb.go | 57 +- x/btccheckpoint/types/types.go | 14 +- x/btclightclient/client/cli/query.go | 2 +- x/btclightclient/genesis.go | 6 +- x/btclightclient/keeper/base_btc_header.go | 6 +- .../keeper/export_headers_state_test.go | 6 +- x/btclightclient/keeper/grpc_query_test.go | 34 +- x/btclightclient/keeper/hooks.go | 8 +- x/btclightclient/keeper/keeper.go | 54 +- x/btclightclient/keeper/msg_server_test.go | 4 +- x/btclightclient/keeper/state.go | 21 +- x/btclightclient/keeper/triggers.go | 13 +- x/btclightclient/keeper/utils_test.go | 12 +- x/btclightclient/module.go | 35 +- x/btclightclient/module_simulation.go | 12 +- .../types/btc_header_info_test.go | 2 +- x/btclightclient/types/btc_light_client.go | 5 +- x/btclightclient/types/btclightclient.pb.go | 36 +- x/btclightclient/types/codec.go | 2 +- x/btclightclient/types/expected_keepers.go | 21 +- x/btclightclient/types/hooks.go | 8 +- x/btclightclient/types/msgs_test.go | 7 +- x/btclightclient/types/tx.pb.go | 31 +- x/btclightclient/types/work_test.go | 8 +- x/btcstaking/abci.go | 10 +- x/btcstaking/client/cli/tx.go | 5 +- x/btcstaking/genesis.go | 6 +- x/btcstaking/keeper/btc_delegations.go | 15 +- x/btcstaking/keeper/btc_delegators.go | 29 +- x/btcstaking/keeper/btc_height_index.go | 20 +- x/btcstaking/keeper/btc_validators.go | 22 +- x/btcstaking/keeper/grpc_query_test.go | 9 +- x/btcstaking/keeper/incentive.go | 20 +- x/btcstaking/keeper/incentive_test.go | 4 +- x/btcstaking/keeper/keeper.go | 17 +- x/btcstaking/keeper/msg_server_test.go | 51 +- x/btcstaking/keeper/params.go | 20 +- x/btcstaking/keeper/query_params_test.go | 4 +- x/btcstaking/keeper/voting_power_table.go | 34 +- .../keeper/voting_power_table_test.go | 12 +- x/btcstaking/module.go | 33 +- x/btcstaking/module_simulation.go | 8 +- x/btcstaking/types/btc_slashing_tx.go | 4 +- x/btcstaking/types/btc_slashing_tx_test.go | 4 +- x/btcstaking/types/btcstaking.pb.go | 140 ++-- x/btcstaking/types/btcstaking_test.go | 4 +- x/btcstaking/types/expected_keepers.go | 21 +- x/btcstaking/types/genesis.go | 3 - x/btcstaking/types/incentive.go | 12 +- x/btcstaking/types/incentive.pb.go | 66 +- x/btcstaking/types/mocked_keepers.go | 83 +-- x/btcstaking/types/params.go | 11 +- x/btcstaking/types/params.pb.go | 66 +- x/btcstaking/types/pop.go | 6 +- x/btcstaking/types/tx.pb.go | 154 ++--- x/checkpointing/abci.go | 17 +- x/checkpointing/client/cli/tx.go | 30 +- x/checkpointing/client/cli/tx_test.go | 219 ++++--- x/checkpointing/client/cli/utils.go | 155 +++-- x/checkpointing/client/testutil/cli_test.go | 1 - x/checkpointing/client/testutil/suite.go | 1 - x/checkpointing/exported/exported.go | 1 - x/checkpointing/genesis.go | 6 +- x/checkpointing/genesis_test.go | 5 +- x/checkpointing/keeper/bls_signer.go | 18 +- x/checkpointing/keeper/ckpt_state.go | 13 +- x/checkpointing/keeper/genesis_bls.go | 3 +- x/checkpointing/keeper/grpc_query_bls_test.go | 17 +- .../keeper/grpc_query_checkpoint.go | 2 +- .../keeper/grpc_query_checkpoint_test.go | 12 +- x/checkpointing/keeper/hooks.go | 11 +- x/checkpointing/keeper/keeper.go | 106 +-- x/checkpointing/keeper/keeper_test.go | 18 +- x/checkpointing/keeper/msg_server_test.go | 163 ++--- x/checkpointing/keeper/registration_state.go | 17 +- x/checkpointing/keeper/val_bls_set.go | 24 +- x/checkpointing/keeper/val_bls_set_test.go | 18 +- x/checkpointing/module.go | 35 +- x/checkpointing/module_simulation.go | 10 +- x/checkpointing/simulation/genesis.go | 1 - x/checkpointing/simulation/operations.go | 1 - x/checkpointing/testckpt/helper.go | 60 +- x/checkpointing/types/checkpoint.pb.go | 150 ++--- x/checkpointing/types/errors.go | 2 +- x/checkpointing/types/expected_keepers.go | 34 +- x/checkpointing/types/genesis.go | 3 - x/checkpointing/types/hooks.go | 11 +- x/checkpointing/types/msgs.go | 23 +- x/checkpointing/types/msgs_test.go | 10 +- x/checkpointing/types/pop_test.go | 2 +- x/checkpointing/types/tx.pb.go | 59 +- x/checkpointing/types/types.go | 74 ++- x/checkpointing/types/types_test.go | 4 +- x/checkpointing/types/utils.go | 22 +- x/checkpointing/types/val_bls_set.go | 2 +- x/epoching/abci.go | 30 +- x/epoching/client/cli/tx.go | 14 +- x/epoching/genesis.go | 6 +- x/epoching/genesis_test.go | 5 +- x/epoching/keeper/apphash_chain.go | 47 +- x/epoching/keeper/apphash_chain_test.go | 28 +- x/epoching/keeper/epoch_msg_queue.go | 58 +- x/epoching/keeper/epoch_msg_queue_test.go | 44 +- x/epoching/keeper/epoch_slashed_val_set.go | 38 +- .../keeper/epoch_slashed_val_set_test.go | 24 +- x/epoching/keeper/epoch_val_set.go | 61 +- x/epoching/keeper/epoch_val_set_test.go | 6 +- x/epoching/keeper/epochs.go | 61 +- x/epoching/keeper/epochs_test.go | 25 +- x/epoching/keeper/grpc_query_test.go | 44 +- x/epoching/keeper/hooks.go | 61 +- x/epoching/keeper/keeper.go | 38 +- x/epoching/keeper/keeper_test.go | 4 +- x/epoching/keeper/lifecycle_delegation.go | 23 +- x/epoching/keeper/lifecycle_validator.go | 18 +- x/epoching/keeper/modified_staking.go | 73 ++- x/epoching/keeper/msg_server.go | 48 +- x/epoching/keeper/msg_server_test.go | 19 +- x/epoching/keeper/params.go | 21 +- x/epoching/keeper/staking_functions.go | 27 +- x/epoching/module.go | 25 +- x/epoching/module_simulation.go | 5 +- x/epoching/simulation/decoder_test.go | 14 +- x/epoching/simulation/genesis.go | 4 +- x/epoching/simulation/genesis_test.go | 2 +- x/epoching/simulation/operations.go | 118 ++-- x/epoching/testepoching/helper.go | 177 +++-- x/epoching/testepoching/tm.go | 59 -- x/epoching/testepoching/utils.go | 8 - x/epoching/testepoching/validator.go | 20 +- x/epoching/types/epoching.go | 29 +- x/epoching/types/expected_keepers.go | 54 +- x/epoching/types/genesis_test.go | 3 +- x/epoching/types/hooks.go | 8 +- x/epoching/types/msg.go | 45 +- x/epoching/types/msg_test.go | 32 +- x/epoching/types/params.go | 6 +- x/epoching/types/tx.pb.go | 68 +- x/epoching/types/validator.go | 4 +- x/finality/abci.go | 9 +- x/finality/client/cli/tx.go | 2 +- x/finality/genesis.go | 6 +- x/finality/keeper/evidence.go | 20 +- x/finality/keeper/grpc_query.go | 7 +- x/finality/keeper/grpc_query_test.go | 22 +- x/finality/keeper/indexed_blocks.go | 26 +- x/finality/keeper/keeper.go | 23 +- x/finality/keeper/msg_server.go | 10 +- x/finality/keeper/msg_server_test.go | 7 +- x/finality/keeper/params.go | 18 +- x/finality/keeper/public_randomness.go | 22 +- x/finality/keeper/query_params_test.go | 4 +- x/finality/keeper/tallying.go | 27 +- x/finality/keeper/tallying_test.go | 6 +- x/finality/keeper/votes.go | 22 +- x/finality/module.go | 33 +- x/finality/module_simulation.go | 5 +- x/finality/types/errors.go | 21 +- x/finality/types/expected_keepers.go | 35 +- x/finality/types/finality.go | 16 +- x/finality/types/finality.pb.go | 72 +-- x/finality/types/genesis.go | 5 - x/finality/types/mocked_keepers.go | 97 +-- x/finality/types/msg.go | 6 +- x/finality/types/params.go | 2 +- x/finality/types/tx.pb.go | 107 +-- x/incentive/abci.go | 10 +- x/incentive/client/cli/tx.go | 6 - x/incentive/genesis.go | 6 +- x/incentive/keeper/btc_staking_gauge.go | 20 +- x/incentive/keeper/btc_timestamping_gauge.go | 18 +- x/incentive/keeper/grpc_query_test.go | 13 +- x/incentive/keeper/intercept_fee_collector.go | 4 +- .../keeper/intercept_fee_collector_test.go | 3 +- x/incentive/keeper/keeper.go | 19 +- x/incentive/keeper/msg_server_test.go | 3 +- x/incentive/keeper/params.go | 18 +- x/incentive/keeper/query_params_test.go | 4 +- x/incentive/keeper/reward_gauge.go | 18 +- x/incentive/module.go | 25 +- x/incentive/module_simulation.go | 3 +- x/incentive/types/errors.go | 2 - x/incentive/types/expected_keepers.go | 16 +- x/incentive/types/genesis.go | 3 - x/incentive/types/incentive.go | 6 +- x/incentive/types/mocked_keepers.go | 20 +- x/incentive/types/params.pb.go | 37 +- x/incentive/types/tx.pb.go | 61 +- x/monitor/genesis.go | 6 +- x/monitor/genesis_test.go | 3 +- x/monitor/keeper/grpc_query_test.go | 51 +- x/monitor/keeper/hooks.go | 21 +- x/monitor/keeper/keeper.go | 65 +- x/monitor/module.go | 36 +- x/monitor/module_simulation.go | 5 +- x/monitor/types/errors.go | 2 - x/monitor/types/expected_keepers.go | 19 +- x/monitor/types/genesis.go | 3 - x/zoneconcierge/abci.go | 10 +- x/zoneconcierge/client/cli/tx.go | 12 - x/zoneconcierge/genesis.go | 10 +- .../keeper/canonical_chain_indexer.go | 16 +- .../keeper/canonical_chain_indexer_test.go | 41 +- x/zoneconcierge/keeper/chain_info_indexer.go | 26 +- .../keeper/epoch_chain_info_indexer.go | 20 +- .../keeper/epoch_chain_info_indexer_test.go | 34 +- x/zoneconcierge/keeper/epochs.go | 49 +- x/zoneconcierge/keeper/fork_indexer.go | 14 +- x/zoneconcierge/keeper/fork_indexer_test.go | 21 +- x/zoneconcierge/keeper/grpc_query_test.go | 71 +- x/zoneconcierge/keeper/header_handler.go | 5 +- x/zoneconcierge/keeper/hooks.go | 17 +- x/zoneconcierge/keeper/ibc_channels.go | 14 +- .../keeper/ibc_header_decorator.go | 14 +- x/zoneconcierge/keeper/ibc_packet.go | 28 +- .../keeper/ibc_packet_btc_timestamp.go | 41 +- x/zoneconcierge/keeper/keeper.go | 51 +- x/zoneconcierge/keeper/keeper_test.go | 33 +- x/zoneconcierge/keeper/msg_server_test.go | 16 - x/zoneconcierge/keeper/params.go | 20 +- .../keeper/proof_block_in_epoch.go | 9 +- x/zoneconcierge/keeper/proof_epoch_sealed.go | 15 +- .../keeper/proof_epoch_sealed_test.go | 10 +- .../keeper/proof_epoch_submitted.go | 4 +- .../keeper/proof_epoch_submitted_test.go | 3 +- x/zoneconcierge/keeper/proof_tx_in_block.go | 6 +- .../keeper/proof_tx_in_block_test.go | 91 --- x/zoneconcierge/keeper/query_kvstore.go | 9 +- x/zoneconcierge/keeper/query_kvstore_test.go | 33 - x/zoneconcierge/module.go | 29 +- x/zoneconcierge/module_ibc.go | 10 +- x/zoneconcierge/module_ibc_packet_test.go | 103 --- x/zoneconcierge/module_simulation.go | 5 +- x/zoneconcierge/module_test.go | 429 ------------- x/zoneconcierge/types/codec.go | 4 +- x/zoneconcierge/types/errors.go | 30 +- x/zoneconcierge/types/events_ibc.go | 4 +- x/zoneconcierge/types/expected_keepers.go | 57 +- x/zoneconcierge/types/genesis.go | 5 +- x/zoneconcierge/types/keys.go | 2 +- x/zoneconcierge/types/mocked_keepers.go | 137 ++-- x/zoneconcierge/types/packet.pb.go | 2 +- x/zoneconcierge/types/tx.pb.go | 16 +- x/zoneconcierge/types/zoneconcierge.go | 2 +- 367 files changed, 5145 insertions(+), 6921 deletions(-) delete mode 100644 app/params/amino.go delete mode 100644 app/params/doc.go delete mode 100644 app/params/params.go delete mode 100644 app/params/weights.go delete mode 100644 app/types.go delete mode 100644 simapp/config.go delete mode 100644 simapp/sim_bench_test.go delete mode 100644 simapp/sim_test.go delete mode 100644 simapp/state.go delete mode 100644 simapp/utils.go delete mode 100644 x/checkpointing/client/testutil/cli_test.go delete mode 100644 x/checkpointing/client/testutil/suite.go delete mode 100644 x/checkpointing/exported/exported.go delete mode 100644 x/checkpointing/simulation/genesis.go delete mode 100644 x/checkpointing/simulation/operations.go delete mode 100644 x/epoching/testepoching/tm.go delete mode 100644 x/epoching/testepoching/utils.go delete mode 100644 x/zoneconcierge/keeper/proof_tx_in_block_test.go delete mode 100644 x/zoneconcierge/keeper/query_kvstore_test.go delete mode 100644 x/zoneconcierge/module_ibc_packet_test.go delete mode 100644 x/zoneconcierge/module_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 56b2cf7ea..538d46c07 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ version: 2.1 orbs: aws-ecr: circleci/aws-ecr@8.2.1 - go: circleci/go@1.7.3 + go: circleci/go@1.9.0 jobs: @@ -14,7 +14,7 @@ jobs: resource_class: large steps: - go/install: - version: "1.20" + version: "1.21.4" - checkout - run: name: Print Go environment @@ -39,7 +39,7 @@ jobs: resource_class: large steps: - go/install: - version: "1.20" + version: "1.21.4" - checkout - run: name: Print Go environment @@ -48,14 +48,15 @@ jobs: - run: name: Run e2e tests command: | - make test-e2e + sudo ln -s /usr/local/go/bin/go /usr/bin/go + sudo make test-e2e lint: machine: image: ubuntu-2204:2022.10.1 resource_class: large steps: - go/install: - version: "1.20" + version: "1.21.4" - checkout - add_ssh_keys - run: @@ -64,7 +65,7 @@ jobs: - run: name: Lint command: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.52.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2 ./bin/golangci-lint run build_docker: diff --git a/Makefile b/Makefile index 55309e208..3c775fd5b 100644 --- a/Makefile +++ b/Makefile @@ -362,7 +362,7 @@ devdoc-update: ### Protobuf ### ############################################################################### -protoVer=0.12.0 +protoVer=0.14.0 protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) diff --git a/app/app.go b/app/app.go index 3f60d0107..e043db380 100644 --- a/app/app.go +++ b/app/app.go @@ -1,17 +1,26 @@ package app import ( + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/x/circuit" + circuitkeeper "cosmossdk.io/x/circuit/keeper" "encoding/json" "fmt" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/gogoproto/proto" "io" "os" "path/filepath" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" - wasmapp "github.com/CosmWasm/wasmd/app" - wasm "github.com/CosmWasm/wasmd/x/wasm" + "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/babylonchain/babylon/client/docs" @@ -22,21 +31,27 @@ import ( "github.com/cosmos/cosmos-sdk/x/consensus" consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" - ibcclient "github.com/cosmos/ibc-go/v7/modules/core/02-client" - ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - dbm "github.com/cometbft/cometbft-db" + "cosmossdk.io/log" abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - ibcclientclient "github.com/cosmos/ibc-go/v7/modules/core/02-client/client" + dbm "github.com/cosmos/cosmos-db" "github.com/spf13/cast" errorsmod "cosmossdk.io/errors" + circuittypes "cosmossdk.io/x/circuit/types" + "cosmossdk.io/x/evidence" + evidencekeeper "cosmossdk.io/x/evidence/keeper" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + feegrantmodule "cosmossdk.io/x/feegrant/module" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/server/api" @@ -49,6 +64,7 @@ import ( "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -61,21 +77,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/x/capability" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/cosmos/cosmos-sdk/x/crisis" crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" distr "github.com/cosmos/cosmos-sdk/x/distribution" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - "github.com/cosmos/cosmos-sdk/x/evidence" - evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/cosmos-sdk/x/feegrant" - feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" - feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/gov" @@ -95,13 +102,13 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/cosmos-sdk/x/upgrade" - upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" - upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/cosmos/ibc-go/modules/capability" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" appparams "github.com/babylonchain/babylon/app/params" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/btccheckpoint" btccheckpointkeeper "github.com/babylonchain/babylon/x/btccheckpoint/keeper" btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" @@ -126,26 +133,25 @@ import ( "github.com/babylonchain/babylon/x/monitor" monitorkeeper "github.com/babylonchain/babylon/x/monitor/keeper" monitortypes "github.com/babylonchain/babylon/x/monitor/types" - storetypes "github.com/cosmos/cosmos-sdk/store/types" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - ibcfee "github.com/cosmos/ibc-go/v7/modules/apps/29-fee" - ibcfeekeeper "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/keeper" - ibcfeetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer" - ibctransferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" - ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - ibc "github.com/cosmos/ibc-go/v7/modules/core" - porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" // ibc module puts types under `ibchost` rather than `ibctypes` - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" + ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" + ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" + ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" + "github.com/cosmos/ibc-go/v8/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v8/modules/core" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" // ibc module puts types under `ibchost` rather than `ibctypes` + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" // IBC-related "github.com/babylonchain/babylon/x/zoneconcierge" zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" - ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) const ( @@ -172,59 +178,6 @@ const ( var ( // DefaultNodeHome default home directories for the application daemon DefaultNodeHome string - // ModuleBasics defines the module BasicManager is in charge of setting up basic, - // non-dependant module elements, such as codec registration - // and genesis verification. - ModuleBasics = module.NewBasicManager( - auth.AppModuleBasic{}, - genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), - bank.AppModuleBasic{}, - capability.AppModuleBasic{}, - staking.AppModuleBasic{}, - mint.AppModuleBasic{}, - distr.AppModuleBasic{}, - gov.NewAppModuleBasic( - []govclient.ProposalHandler{ - paramsclient.ProposalHandler, - upgradeclient.LegacyProposalHandler, - upgradeclient.LegacyCancelProposalHandler, - ibcclientclient.UpdateClientProposalHandler, - ibcclientclient.UpgradeProposalHandler, - }, - ), - params.AppModuleBasic{}, - consensus.AppModuleBasic{}, - crisis.AppModuleBasic{}, - slashing.AppModuleBasic{}, - feegrantmodule.AppModuleBasic{}, - upgrade.AppModuleBasic{}, - evidence.AppModuleBasic{}, - authzmodule.AppModuleBasic{}, - vesting.AppModuleBasic{}, - wasm.AppModuleBasic{}, - - // Babylon modules - epoching.AppModuleBasic{}, - btclightclient.AppModuleBasic{}, - btccheckpoint.AppModuleBasic{}, - checkpointing.AppModuleBasic{}, - monitor.AppModuleBasic{}, - - // IBC-related - ibc.AppModuleBasic{}, - ibctm.AppModuleBasic{}, - transfer.AppModuleBasic{}, - zoneconcierge.AppModuleBasic{}, - ibcfee.AppModuleBasic{}, - - // BTC staking related - btcstaking.AppModuleBasic{}, - finality.AppModuleBasic{}, - - // tokenomics-related - incentive.AppModuleBasic{}, - ) - // fee collector account, module accounts and their permissions maccPerms = map[string][]string{ authtypes.FeeCollectorName: nil, // fee collector account @@ -241,24 +194,12 @@ var ( // Wasm related variables var ( - // WasmProposalsEnabled enables all x/wasm proposals when its value is "true" - // and EnableSpecificWasmProposals is empty. Otherwise, all x/wasm proposals - // are disabled. - WasmProposalsEnabled = "true" - - // EnableSpecificWasmProposals, if set, must be comma-separated list of values - // that are all a subset of "EnableAllProposals", which takes precedence over - // WasmProposalsEnabled. - // - // See: https://github.com/CosmWasm/wasmd/blob/02a54d33ff2c064f3539ae12d75d027d9c665f05/x/wasm/internal/types/proposal.go#L28-L34 - EnableSpecificWasmProposals = "" - // EmptyWasmOpts defines a type alias for a list of wasm options. EmptyWasmOpts []wasmkeeper.Option ) var ( - _ App = (*BabylonApp)(nil) + _ runtime.AppI = (*BabylonApp)(nil) _ servertypes.Application = (*BabylonApp)(nil) ) @@ -296,6 +237,7 @@ type BabylonApp struct { EvidenceKeeper evidencekeeper.Keeper FeeGrantKeeper feegrantkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper + CircuitKeeper circuitkeeper.Keeper // Babylon modules EpochingKeeper epochingkeeper.Keeper @@ -327,7 +269,8 @@ type BabylonApp struct { ScopedWasmKeeper capabilitykeeper.ScopedKeeper // the module manager - mm *module.Manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager // simulation manager sm *module.SimulationManager @@ -349,7 +292,7 @@ func init() { // NewBabylonApp returns a reference to an initialized BabylonApp. func NewBabylonApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool, - homePath string, invCheckPeriod uint, encodingConfig appparams.EncodingConfig, privSigner *PrivSigner, + invCheckPeriod uint, privSigner *PrivSigner, appOpts servertypes.AppOptions, wasmOpts []wasmkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), @@ -359,22 +302,30 @@ func NewBabylonApp( btcConfig := bbn.ParseBtcOptionsFromConfig(appOpts) powLimit := btcConfig.PowLimit() btcNetParams := btcConfig.NetParams() + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + if homePath == "" { + homePath = DefaultNodeHome + } + + encCfg := appparams.DefaultEncodingConfig() + interfaceRegistry := encCfg.InterfaceRegistry + appCodec := encCfg.Codec + legacyAmino := encCfg.Amino + txConfig := encCfg.TxConfig + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) - appCodec := encodingConfig.Marshaler - legacyAmino := encodingConfig.Amino - interfaceRegistry := encodingConfig.InterfaceRegistry - txConfig := encodingConfig.TxConfig bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) bApp.SetTxEncoder(txConfig.TxEncoder()) - keys := sdk.NewKVStoreKeys( + keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, consensusparamtypes.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, - evidencetypes.StoreKey, capabilitytypes.StoreKey, + evidencetypes.StoreKey, circuittypes.StoreKey, capabilitytypes.StoreKey, authzkeeper.StoreKey, // Babylon modules epochingtypes.StoreKey, @@ -396,11 +347,16 @@ func NewBabylonApp( incentivetypes.StoreKey, ) - tkeys := sdk.NewTransientStoreKeys( + tkeys := storetypes.NewTransientStoreKeys( paramstypes.TStoreKey, btccheckpointtypes.TStoreKey) // NOTE: The testingkey is just mounted for testing purposes. Actual applications should // not include this key. - memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, "testingkey") + memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, "testingkey") + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } app := &BabylonApp{ BaseApp: bApp, @@ -414,12 +370,26 @@ func NewBabylonApp( memKeys: memKeys, } - app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + app.ParamsKeeper = initParamsKeeper( + appCodec, + legacyAmino, + keys[paramstypes.StoreKey], + tkeys[paramstypes.TStoreKey], + ) - app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, keys[consensusparamtypes.StoreKey], authtypes.NewModuleAddress(govtypes.ModuleName).String()) - bApp.SetParamStore(&app.ConsensusParamsKeeper) + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + runtime.EventService{}, + ) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) - app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + app.CapabilityKeeper = capabilitykeeper.NewKeeper( + appCodec, + keys[capabilitytypes.StoreKey], + memKeys[capabilitytypes.MemStoreKey], + ) // grant capabilities for the ibc and ibc-transfer modules scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) @@ -432,53 +402,129 @@ func NewBabylonApp( app.CapabilityKeeper.Seal() // add keepers - app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, keys[authtypes.StoreKey], authtypes.ProtoBaseAccount, maccPerms, appparams.Bech32PrefixAccAddr, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + app.AccountKeeper = authkeeper.NewAccountKeeper( + appCodec, + runtime.NewKVStoreService(keys[authtypes.StoreKey]), + authtypes.ProtoBaseAccount, + maccPerms, + authcodec.NewBech32Codec(appparams.Bech32PrefixAccAddr), + appparams.Bech32PrefixAccAddr, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) app.BankKeeper = bankkeeper.NewBaseKeeper( appCodec, - keys[banktypes.StoreKey], + runtime.NewKVStoreService(keys[banktypes.StoreKey]), app.AccountKeeper, BlockedAddresses(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, ) app.StakingKeeper = stakingkeeper.NewKeeper( - appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, + runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), + app.AccountKeeper, + app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), + authcodec.NewBech32Codec(appparams.Bech32PrefixConsAddr), ) - app.MintKeeper = mintkeeper.NewKeeper(appCodec, keys[minttypes.StoreKey], app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + app.CircuitKeeper = circuitkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[circuittypes.StoreKey]), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.AccountKeeper.AddressCodec(), + ) + app.BaseApp.SetCircuitBreaker(&app.CircuitKeeper) - app.DistrKeeper = distrkeeper.NewKeeper(appCodec, keys[distrtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + app.MintKeeper = mintkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[minttypes.StoreKey]), + app.StakingKeeper, + app.AccountKeeper, + app.BankKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.DistrKeeper = distrkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[distrtypes.StoreKey]), + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) // set up epoching keeper // NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module epochingKeeper := epochingkeeper.NewKeeper( - appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.BankKeeper, app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, + runtime.NewKVStoreService(keys[epochingtypes.StoreKey]), + app.BankKeeper, + app.StakingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // set up incentive keeper app.IncentiveKeeper = incentivekeeper.NewKeeper( - appCodec, keys[incentivetypes.StoreKey], keys[incentivetypes.StoreKey], app.BankKeeper, app.AccountKeeper, &epochingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authtypes.FeeCollectorName, + appCodec, + runtime.NewKVStoreService(keys[incentivetypes.StoreKey]), + app.BankKeeper, + app.AccountKeeper, + &epochingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authtypes.FeeCollectorName, ) app.SlashingKeeper = slashingkeeper.NewKeeper( - appCodec, legacyAmino, keys[slashingtypes.StoreKey], app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, + legacyAmino, + runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), + app.StakingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - app.CrisisKeeper = crisiskeeper.NewKeeper(appCodec, keys[crisistypes.StoreKey], invCheckPeriod, - app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) - - app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper) - // set the governance module account as the authority for conducting upgrades - app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + app.CrisisKeeper = crisiskeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[crisistypes.StoreKey]), + invCheckPeriod, + app.BankKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.AccountKeeper.AddressCodec(), + ) + app.FeeGrantKeeper = feegrantkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[feegrant.StoreKey]), + app.AccountKeeper, + ) // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks app.StakingKeeper.SetHooks( stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks(), epochingKeeper.Hooks()), ) - app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper( + skipUpgradeHeights, + runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), + appCodec, + homePath, + app.BaseApp, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.AuthzKeeper = authzkeeper.NewKeeper( + runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), + appCodec, + app.MsgServiceRouter(), + app.AccountKeeper, + ) app.IBCKeeper = ibckeeper.NewKeeper( appCodec, @@ -487,10 +533,13 @@ func NewBabylonApp( app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + // From 8.0.0 the IBC keeper requires an authority for the messages + // `MsgIBCSoftwareUpgrade` and `MsgRecoverClient` + // https://github.com/cosmos/ibc-go/releases/tag/v8.0.0 + // Gov is the proper authority for those types of messages + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - // ... other modules keepers - // register the proposal types // Deprecated: Avoid adding new handlers, instead use the new proposal flow // by granting the governance module the right to execute the message. @@ -498,20 +547,24 @@ func NewBabylonApp( // TODO: investigate how to migrate to new proposal flow govRouter := govv1beta1.NewRouter() govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). - AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). - AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). - AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)) + // TODO: this should be a function parameter govConfig := govtypes.DefaultConfig() - /* Example of setting gov params: govConfig.MaxMetadataLen = 10000 */ govKeeper := govkeeper.NewKeeper( - appCodec, keys[govtypes.StoreKey], app.AccountKeeper, app.BankKeeper, - app.StakingKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) + appCodec, + runtime.NewKVStoreService(keys[govtypes.StoreKey]), + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + app.DistrKeeper, + app.MsgServiceRouter(), + govConfig, + authtypes.NewModuleAddress(govtypes.ModuleName).String()) app.GovKeeper = *govKeeper.SetHooks( govtypes.NewMultiGovHooks( @@ -521,23 +574,20 @@ func NewBabylonApp( btclightclientKeeper := btclightclientkeeper.NewKeeper( appCodec, - keys[btclightclienttypes.StoreKey], - keys[btclightclienttypes.MemStoreKey], + runtime.NewKVStoreService(keys[btclightclienttypes.StoreKey]), btcConfig, ) checkpointingKeeper := checkpointingkeeper.NewKeeper( appCodec, - keys[checkpointingtypes.StoreKey], - keys[checkpointingtypes.MemStoreKey], + runtime.NewKVStoreService(keys[checkpointingtypes.StoreKey]), privSigner.WrappedPV, - &epochingKeeper, + epochingKeeper, privSigner.ClientCtx, ) btcCheckpointKeeper := btccheckpointkeeper.NewKeeper( appCodec, - keys[btccheckpointtypes.StoreKey], + runtime.NewKVStoreService(keys[btccheckpointtypes.StoreKey]), tkeys[btccheckpointtypes.TStoreKey], - keys[btccheckpointtypes.MemStoreKey], &btclightclientKeeper, &checkpointingKeeper, &app.IncentiveKeeper, @@ -546,12 +596,12 @@ func NewBabylonApp( ) // create Tendermint client - tmClient, err := client.NewClientFromNode(privSigner.ClientCtx.NodeURI) // create a Tendermint client for ZoneConcierge + cmtClient, err := client.NewClientFromNode(privSigner.ClientCtx.NodeURI) // create a Tendermint client for ZoneConcierge if err != nil { panic(fmt.Errorf("couldn't get client from nodeURI %s: %w", privSigner.ClientCtx.NodeURI, err)) } // create querier for KVStore - storeQuerier, ok := app.CommitMultiStore().(sdk.Queryable) + storeQuerier, ok := app.CommitMultiStore().(storetypes.Queryable) if !ok { panic(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "multistore doesn't support queries")) } @@ -560,24 +610,23 @@ func NewBabylonApp( appCodec, keys[ibcfeetypes.StoreKey], app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware app.IBCKeeper.ChannelKeeper, - &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, + app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, ) zcKeeper := zckeeper.NewKeeper( appCodec, - keys[zctypes.StoreKey], - keys[zctypes.MemStoreKey], + runtime.NewKVStoreService(keys[zctypes.StoreKey]), app.IBCFeeKeeper, app.IBCKeeper.ClientKeeper, app.IBCKeeper.ChannelKeeper, - &app.IBCKeeper.PortKeeper, + app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, &btclightclientKeeper, &checkpointingKeeper, &btcCheckpointKeeper, epochingKeeper, - tmClient, + cmtClient, storeQuerier, scopedZoneConciergeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), @@ -586,15 +635,21 @@ func NewBabylonApp( // Create Transfer Keepers app.TransferKeeper = ibctransferkeeper.NewKeeper( - appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.IBCFeeKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, - app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + appCodec, + keys[ibctransfertypes.StoreKey], + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, + app.AccountKeeper, + app.BankKeeper, + scopedTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.MonitorKeeper = monitorkeeper.NewKeeper( appCodec, - keys[monitortypes.StoreKey], - keys[monitortypes.StoreKey], + runtime.NewKVStoreService(keys[monitortypes.StoreKey]), &btclightclientKeeper, ) @@ -616,25 +671,34 @@ func NewBabylonApp( // set up BTC staking keeper app.BTCStakingKeeper = btcstakingkeeper.NewKeeper( - appCodec, keys[btcstakingtypes.StoreKey], keys[btcstakingtypes.StoreKey], - &btclightclientKeeper, &btcCheckpointKeeper, + appCodec, + runtime.NewKVStoreService(keys[btcstakingtypes.StoreKey]), + &btclightclientKeeper, + &btcCheckpointKeeper, btcNetParams, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // set up finality keeper app.FinalityKeeper = finalitykeeper.NewKeeper( - appCodec, keys[finalitytypes.StoreKey], keys[finalitytypes.StoreKey], app.AccountKeeper, app.BankKeeper, - app.BTCStakingKeeper, app.IncentiveKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, + runtime.NewKVStoreService(keys[finalitytypes.StoreKey]), + app.BTCStakingKeeper, + app.IncentiveKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // create evidence keeper with router evidenceKeeper := evidencekeeper.NewKeeper( - appCodec, keys[evidencetypes.StoreKey], app.StakingKeeper, app.SlashingKeeper, + appCodec, + runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), + app.StakingKeeper, + app.SlashingKeeper, + app.AccountKeeper.AddressCodec(), + runtime.ProvideCometInfoService(), ) // If evidence needs to be handled for the app, set routes in router here and seal app.EvidenceKeeper = *evidenceKeeper - wasmDir := filepath.Join(homePath, "wasm") wasmConfig, err := wasm.ReadWasmConfig(appOpts) if err != nil { panic(fmt.Sprintf("error while reading wasm config: %s", err)) @@ -644,25 +708,28 @@ func NewBabylonApp( app.WasmKeeper = wasmkeeper.NewKeeper( appCodec, - keys[wasmtypes.StoreKey], + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, distrkeeper.NewQuerier(app.DistrKeeper), app.IBCFeeKeeper, app.IBCKeeper.ChannelKeeper, - &app.IBCKeeper.PortKeeper, + app.IBCKeeper.PortKeeper, scopedWasmKeeper, app.TransferKeeper, app.MsgServiceRouter(), app.GRPCQueryRouter(), - wasmDir, + homePath, wasmConfig, wasmCapabilities, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmOpts..., ) + // Set legacy router for backwards compatibility with gov v1beta1 + app.GovKeeper.SetLegacyRouter(govRouter) + // Create all supported IBC routes var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) @@ -686,9 +753,6 @@ func NewBabylonApp( // No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) - // Set legacy router for backwards compatibility with gov v1beta1 - app.GovKeeper.SetLegacyRouter(govRouter) - /**** Module Options ****/ // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment @@ -697,52 +761,78 @@ func NewBabylonApp( // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. - app.mm = module.NewManager( + app.ModuleManager = module.NewManager( genutil.NewAppModule( - app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx, - encodingConfig.TxConfig, + app.AccountKeeper, + app.StakingKeeper, + app, + txConfig, ), auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), - capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), crisis.NewAppModule(app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), - slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), - upgrade.NewAppModule(app.UpgradeKeeper), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + circuit.NewAppModule(appCodec, app.CircuitKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + // non sdk modules + capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmtypes.ModuleName)), - // Babylon modules - epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), - btclightclient.NewAppModule(appCodec, app.BTCLightClientKeeper, app.AccountKeeper, app.BankKeeper), - btccheckpoint.NewAppModule(appCodec, app.BtcCheckpointKeeper, app.AccountKeeper, app.BankKeeper), - checkpointing.NewAppModule(appCodec, app.CheckpointingKeeper, app.AccountKeeper, app.BankKeeper), - monitor.NewAppModule(appCodec, app.MonitorKeeper, app.AccountKeeper, app.BankKeeper), - // IBC-related modules ibc.NewAppModule(app.IBCKeeper), transfer.NewAppModule(app.TransferKeeper), - zoneconcierge.NewAppModule(appCodec, app.ZoneConciergeKeeper, app.AccountKeeper, app.BankKeeper), ibcfee.NewAppModule(app.IBCFeeKeeper), - // BTC staking related modules - btcstaking.NewAppModule(appCodec, app.BTCStakingKeeper, app.AccountKeeper, app.BankKeeper), - finality.NewAppModule(appCodec, app.FinalityKeeper, app.AccountKeeper, app.BankKeeper), - // tokenomics related modules + ibctm.AppModule{}, + // Babylon modules - btc timestamping + epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + btclightclient.NewAppModule(appCodec, app.BTCLightClientKeeper), + btccheckpoint.NewAppModule(appCodec, app.BtcCheckpointKeeper), + checkpointing.NewAppModule(appCodec, app.CheckpointingKeeper), + monitor.NewAppModule(appCodec, app.MonitorKeeper), + zoneconcierge.NewAppModule(appCodec, app.ZoneConciergeKeeper, app.AccountKeeper, app.BankKeeper), + // Babylon modules - btc staking + btcstaking.NewAppModule(appCodec, app.BTCStakingKeeper), + finality.NewAppModule(appCodec, app.FinalityKeeper), + // Babylon modules - tokenomics incentive.NewAppModule(appCodec, app.IncentiveKeeper, app.AccountKeeper, app.BankKeeper), ) + // BasicModuleManager defines the module BasicManager which is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default, it is composed of all the modules from the module manager. + // Additionally, app module basics can be overwritten by passing them as an argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + ) + // During begin block slashing happens after distr.BeginBlocker so that // there is nothing left over in the validator fee pool, so as to keep the // CanWithdrawInvariant invariant. // NOTE: staking module is required if HistoricalEntries param > 0 // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) - app.mm.SetOrderBeginBlockers( + app.ModuleManager.SetOrderBeginBlockers( upgradetypes.ModuleName, capabilitytypes.ModuleName, // NOTE: incentive module's BeginBlock has to be after mint but before distribution // so that it can intercept a part of new inflation to reward BTC staking/timestamping stakeholders @@ -751,7 +841,7 @@ func NewBabylonApp( evidencetypes.ModuleName, stakingtypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, govtypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, feegrant.ModuleName, - paramstypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, + paramstypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, // Babylon modules epochingtypes.ModuleName, btclightclienttypes.ModuleName, @@ -774,7 +864,7 @@ func NewBabylonApp( // app.mm.OrderBeginBlockers = append(app.mm.OrderBeginBlockers[:4], app.mm.OrderBeginBlockers[4+1:]...) // remove slashingtypes.ModuleName // app.mm.OrderBeginBlockers = append(app.mm.OrderBeginBlockers[:4], app.mm.OrderBeginBlockers[4+1:]...) // remove evidencetypes.ModuleName - app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, + app.ModuleManager.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, minttypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, @@ -799,19 +889,19 @@ func NewBabylonApp( incentivetypes.ModuleName, // EndBlock of incentive module does not matter ) // Babylon does not want EndBlock processing in staking - app.mm.OrderEndBlockers = append(app.mm.OrderEndBlockers[:2], app.mm.OrderEndBlockers[2+1:]...) // remove stakingtypes.ModuleName + app.ModuleManager.OrderEndBlockers = append(app.ModuleManager.OrderEndBlockers[:2], app.ModuleManager.OrderEndBlockers[2+1:]...) // remove stakingtypes.ModuleName // NOTE: The genutils module must occur after staking so that pools are // properly initialized with tokens from genesis accounts. // NOTE: Capability module must occur first so that it can initialize any capabilities // so that other modules that want to create or claim capabilities afterwards in InitChain // can do so safely. - app.mm.SetOrderInitGenesis( + genesisModuleOrder := []string{ capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, feegrant.ModuleName, - paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, + paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, // Babylon modules epochingtypes.ModuleName, btclightclienttypes.ModuleName, @@ -829,16 +919,21 @@ func NewBabylonApp( finalitytypes.ModuleName, // tokenomics-related modules incentivetypes.ModuleName, - ) + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) // Uncomment if you want to set a custom migration order here. // app.mm.SetOrderMigrations(custom order) - app.mm.RegisterInvariants(app.CrisisKeeper) + app.ModuleManager.RegisterInvariants(app.CrisisKeeper) app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) - app.mm.RegisterServices(app.configurator) + err = app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } - autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.mm.Modules)) + autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.ModuleManager.Modules)) reflectionSvc, err := runtimeservices.NewReflectionService() if err != nil { @@ -856,7 +951,7 @@ func NewBabylonApp( overrideModules := map[string]module.AppModuleSimulation{ authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), } - app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager().Modules, overrideModules) + app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) app.sm.RegisterStoreDecoders() @@ -865,10 +960,6 @@ func NewBabylonApp( app.MountTransientStores(tkeys) app.MountMemoryStores(memKeys) - // initialize BaseApp - app.SetInitChainer(app.InitChainer) - app.SetBeginBlocker(app.BeginBlocker) - // initialize AnteHandler, which includes // - authAnteHandler // - custom wasm ante handler NewLimitSimulationGasDecorator and NewCountTXDecorator @@ -881,14 +972,15 @@ func NewBabylonApp( HandlerOptions: ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + SignModeHandler: txConfig.SignModeHandler(), FeegrantKeeper: app.FeeGrantKeeper, SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, - IBCKeeper: app.IBCKeeper, - WasmConfig: &wasmConfig, - TXCounterStoreKey: keys[wasmtypes.StoreKey], - WasmKeeper: &app.WasmKeeper, + IBCKeeper: app.IBCKeeper, + WasmConfig: &wasmConfig, + TXCounterStoreService: runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), + WasmKeeper: &app.WasmKeeper, + CircuitKeeper: &app.CircuitKeeper, }, ) @@ -901,6 +993,12 @@ func NewBabylonApp( epochingkeeper.NewDropValidatorMsgDecorator(app.EpochingKeeper), NewBtcValidationDecorator(btcConfig, &app.BtcCheckpointKeeper), ) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(anteHandler) // set postHandler @@ -921,8 +1019,23 @@ func NewBabylonApp( } } - // initialize EndBlocker - app.SetEndBlocker(app.EndBlocker) + app.ScopedIBCKeeper = scopedIBCKeeper + app.ScopedZoneConciergeKeeper = scopedZoneConciergeKeeper + app.ScopedTransferKeeper = scopedTransferKeeper + app.ScopedWasmKeeper = scopedWasmKeeper + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + } if loadLatest { if err := app.LoadLatestVersion(); err != nil { @@ -937,11 +1050,6 @@ func NewBabylonApp( } } - app.ScopedIBCKeeper = scopedIBCKeeper - app.ScopedZoneConciergeKeeper = scopedZoneConciergeKeeper - app.ScopedTransferKeeper = scopedTransferKeeper - app.ScopedWasmKeeper = scopedWasmKeeper - return app } @@ -954,24 +1062,37 @@ func (app *BabylonApp) GetBaseApp() *baseapp.BaseApp { // Name returns the name of the App func (app *BabylonApp) Name() string { return app.BaseApp.Name() } +// PreBlocker application updates every pre block +func (app *BabylonApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + // BeginBlocker application updates every begin block -func (app *BabylonApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - return app.mm.BeginBlock(ctx, req) +func (app *BabylonApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) } // EndBlocker application updates every end block -func (app *BabylonApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return app.mm.EndBlock(ctx, req) +func (app *BabylonApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) } // InitChainer application update at chain initialization -func (app *BabylonApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { +func (app *BabylonApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { var genesisState GenesisState if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) } - app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap()) - return app.mm.InitGenesis(ctx, app.appCodec, genesisState) + err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()) + if err != nil { + panic(err) + } + + if _, ok := app.ModuleManager.Modules[epochingtypes.ModuleName].(module.HasGenesis); !ok { + panic("FAULTY") + } + + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) } // LoadHeight loads a particular height @@ -1010,6 +1131,15 @@ func (app *BabylonApp) InterfaceRegistry() types.InterfaceRegistry { return app.interfaceRegistry } +func (app *BabylonApp) EncodingConfig() *appparams.EncodingConfig { + return &appparams.EncodingConfig{ + InterfaceRegistry: app.InterfaceRegistry(), + Codec: app.AppCodec(), + TxConfig: app.TxConfig(), + Amino: app.LegacyAmino(), + } +} + // GetKey returns the KVStoreKey for the provided store key. // // NOTE: This is solely to be used for testing purposes. @@ -1052,13 +1182,13 @@ func (app *BabylonApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.AP authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register new tendermint queries routes from grpc-gateway. - tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register node gRPC service for grpc-gateway. nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register grpc-gateway routes for all modules. - ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // register swagger API from root so that other applications can override easily if apiConfig.Swagger { @@ -1073,7 +1203,7 @@ func (app *BabylonApp) RegisterTxService(clientCtx client.Context) { // RegisterTendermintService implements the Application.RegisterTendermintService method. func (app *BabylonApp) RegisterTendermintService(clientCtx client.Context) { - tmservice.RegisterTendermintService( + cmtservice.RegisterTendermintService( clientCtx, app.BaseApp.GRPCQueryRouter(), app.interfaceRegistry, @@ -1081,23 +1211,40 @@ func (app *BabylonApp) RegisterTendermintService(clientCtx client.Context) { ) } -func (app *BabylonApp) RegisterNodeService(clientCtx client.Context) { - nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter()) -} - -func (app *BabylonApp) ModuleManager() *module.Manager { - return app.mm +func (app *BabylonApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) } // DefaultGenesis returns a default genesis from the registered AppModuleBasic's. func (a *BabylonApp) DefaultGenesis() map[string]json.RawMessage { - return ModuleBasics.DefaultGenesis(a.appCodec) + return a.BasicModuleManager.DefaultGenesis(a.appCodec) } func (app *BabylonApp) TxConfig() client.TxConfig { return app.txConfig } +// AutoCliOpts returns the autocli options for the app. +func (app *BabylonApp) AutoCliOpts() autocli.AppOptions { + modules := make(map[string]appmodule.AppModule, 0) + for _, m := range app.ModuleManager.Modules { + if moduleWithName, ok := m.(module.HasName); ok { + moduleName := moduleWithName.Name() + if appModule, ok := moduleWithName.(appmodule.AppModule); ok { + modules[moduleName] = appModule + } + } + } + + return autocli.AppOptions{ + Modules: modules, + ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), + AddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), + ValidatorAddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), + ConsensusAddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixConsAddr), + } +} + // GetMaccPerms returns a copy of the module account permissions func GetMaccPerms() map[string][]string { dupMaccPerms := make(map[string][]string) diff --git a/app/app_test.go b/app/app_test.go index 3328e3c89..f98b7acb4 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -2,28 +2,25 @@ package app import ( "fmt" - "os" + abci "github.com/cometbft/cometbft/abci/types" "testing" - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "cosmossdk.io/log" + dbm "github.com/cosmos/cosmos-db" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/stretchr/testify/require" ) func TestBabylonBlockedAddrs(t *testing.T) { - encCfg := GetEncodingConfig() db := dbm.NewMemDB() signer, _ := SetupPrivSigner() - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - app := NewBabyblonAppWithCustomOptions(t, false, signer, SetupOptions{ + logger := log.NewTestLogger(t) + + app := NewBabylonAppWithCustomOptions(t, false, signer, SetupOptions{ Logger: logger, DB: db, InvCheckPeriod: 0, - EncConfig: encCfg, - HomePath: DefaultNodeHome, SkipUpgradeHeights: map[int64]bool{}, AppOpts: EmptyAppOptions{}, }) @@ -43,12 +40,27 @@ func TestBabylonBlockedAddrs(t *testing.T) { ) } - app.Commit() + _, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: 1, + }) + require.NoError(t, err) + _, err = app.Commit() + require.NoError(t, err) - logger2 := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger2 := log.NewTestLogger(t) // Making a new app object with the db, so that initchain hasn't been called - app2 := NewBabylonApp(logger2, db, nil, true, map[int64]bool{}, DefaultNodeHome, 0, encCfg, signer, EmptyAppOptions{}, EmptyWasmOpts) - _, err := app2.ExportAppStateAndValidators(false, []string{}, []string{}) + app2 := NewBabylonApp( + logger2, + db, + nil, + true, + map[int64]bool{}, + 0, + signer, + EmptyAppOptions{}, + EmptyWasmOpts, + ) + _, err = app2.ExportAppStateAndValidators(false, []string{}, []string{}) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } @@ -58,26 +70,24 @@ func TestGetMaccPerms(t *testing.T) { } func TestUpgradeStateOnGenesis(t *testing.T) { - encCfg := GetEncodingConfig() db := dbm.NewMemDB() privSigner, err := SetupPrivSigner() require.NoError(t, err) - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger := log.NewTestLogger(t) - app := NewBabyblonAppWithCustomOptions(t, false, privSigner, SetupOptions{ + app := NewBabylonAppWithCustomOptions(t, false, privSigner, SetupOptions{ Logger: logger, DB: db, InvCheckPeriod: 0, - EncConfig: encCfg, - HomePath: DefaultNodeHome, SkipUpgradeHeights: map[int64]bool{}, AppOpts: EmptyAppOptions{}, }) // make sure the upgrade keeper has version map in state - ctx := app.NewContext(false, tmproto.Header{}) - vm := app.UpgradeKeeper.GetModuleVersionMap(ctx) - for v, i := range app.mm.Modules { + ctx := app.NewContext(false) + vm, err := app.UpgradeKeeper.GetModuleVersionMap(ctx) + require.NoError(t, err) + for v, i := range app.ModuleManager.Modules { if i, ok := i.(module.HasConsensusVersion); ok { require.Equal(t, vm[v], i.ConsensusVersion()) } diff --git a/app/encoding.go b/app/encoding.go index 800f0e302..5c9fb7110 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -1,23 +1,34 @@ package app import ( - "github.com/cosmos/cosmos-sdk/std" - + "cosmossdk.io/log" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" appparams "github.com/babylonchain/babylon/app/params" + dbm "github.com/cosmos/cosmos-db" ) -var encodingConfig = makeEncodingConfig() - -func GetEncodingConfig() appparams.EncodingConfig { - return encodingConfig +func NewTmpBabylonApp() *BabylonApp { + signer, _ := SetupPrivSigner() + return NewBabylonApp( + log.NewNopLogger(), + dbm.NewMemDB(), + nil, + true, + map[int64]bool{}, + 0, + signer, + EmptyAppOptions{}, + []wasmkeeper.Option{}) } -// makeEncodingConfig creates an EncodingConfig. -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.GetEncodingConfig() - std.RegisterLegacyAminoCodec(encodingConfig.Amino) - std.RegisterInterfaces(encodingConfig.InterfaceRegistry) - ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) - ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) - return encodingConfig +// GetEncodingConfig returns a *registered* encoding config +// Note that the only way to register configuration is through the app creation +func GetEncodingConfig() *appparams.EncodingConfig { + tmpApp := NewTmpBabylonApp() + return &appparams.EncodingConfig{ + InterfaceRegistry: tmpApp.InterfaceRegistry(), + Codec: tmpApp.AppCodec(), + TxConfig: tmpApp.TxConfig(), + Amino: tmpApp.LegacyAmino(), + } } diff --git a/app/export.go b/app/export.go index ada8411d7..5c2e90a3b 100644 --- a/app/export.go +++ b/app/export.go @@ -1,11 +1,10 @@ package app import ( + storetypes "cosmossdk.io/store/types" "encoding/json" "log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - servertypes "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" @@ -20,17 +19,20 @@ func (app *BabylonApp) ExportAppStateAndValidators( jailAllowedAddrs []string, modulesToExport []string) (servertypes.ExportedApp, error) { // as if they could withdraw from the start of the next block - ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + ctx := app.NewContext(true) // We export at last height + 1, because that's the height at which - // Tendermint will start InitChain. + // Comet will start InitChain. height := app.LastBlockHeight() + 1 if forZeroHeight { height = 0 app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - genState := app.mm.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + if err != nil { + return servertypes.ExportedApp{}, err + } appState, err := json.MarshalIndent(genState, "", " ") if err != nil { @@ -73,13 +75,25 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr /* Handle fee distribution state. */ // withdraw all validator commission - app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { - _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } + if _, err = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz); err != nil { + panic(err) + } return false }) + if err != nil { + panic(err) + } // withdraw all delegator rewards - dels := app.StakingKeeper.GetAllDelegations(ctx) + dels, err := app.StakingKeeper.GetAllDelegations(ctx) + if err != nil { + panic(err) + } for _, delegation := range dels { valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) if err != nil { @@ -90,7 +104,9 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr if err != nil { panic(err) } - _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + if _, err = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr); err != nil { + panic(err) + } } // clear validator slash events @@ -104,16 +120,33 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr ctx = ctx.WithBlockHeight(0) // reinitialize all validators - app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } // donate any unwithdrawn outstanding reward fraction tokens to the community pool - scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator()) - feePool := app.DistrKeeper.GetFeePool(ctx) + scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) + if err != nil { + panic(err) + } + feePool, err := app.DistrKeeper.FeePool.Get(ctx) + if err != nil { + panic(err) + } feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) - app.DistrKeeper.SetFeePool(ctx, feePool) + if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { + panic(err) + } - app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) //nolint:errcheck // either we ignore the error here or propogate up the stack + if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, sdk.ValAddress(val.GetOperator())); err != nil { + panic(err) + } return false }) + if err != nil { + panic(err) + } // reinitialize all delegations for _, del := range dels { @@ -121,12 +154,13 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr if err != nil { panic(err) } - delAddr, err := sdk.AccAddressFromBech32(del.DelegatorAddress) - if err != nil { + delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress) + if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil { + panic(err) + } + if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil { panic(err) } - app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propogate up the stack - app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propogate up the stack } // reset context height @@ -135,33 +169,42 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr /* Handle staking state. */ // iterate through redelegations, reset creation height - app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { for i := range red.Entries { red.Entries[i].CreationHeight = 0 } - app.StakingKeeper.SetRedelegation(ctx, red) + if err := app.StakingKeeper.SetRedelegation(ctx, red); err != nil { + panic(err) + } return false }) + if err != nil { + panic(err) + } // iterate through unbonding delegations, reset creation height - app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { for i := range ubd.Entries { ubd.Entries[i].CreationHeight = 0 } - app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + if err := app.StakingKeeper.SetUnbondingDelegation(ctx, ubd); err != nil { + panic(err) + } return false }) + if err != nil { + panic(err) + } // Iterate through validators by power descending, reset bond heights, and // update bond intra-tx counters. store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) - iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) - counter := int16(0) + iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) for ; iter.Valid(); iter.Next() { addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) - validator, found := app.StakingKeeper.GetValidator(ctx, addr) - if !found { + validator, err := app.StakingKeeper.GetValidator(ctx, addr) + if err != nil { panic("expected validator, not found") } @@ -170,13 +213,17 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr validator.Jailed = true } - app.StakingKeeper.SetValidator(ctx, validator) - counter++ + if err := app.StakingKeeper.SetValidator(ctx, validator); err != nil { + panic(err) + } } - iter.Close() + if err := iter.Close(); err != nil { + app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err) + return + } - _, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) if err != nil { log.Fatal(err) } @@ -184,12 +231,17 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr /* Handle slashing state. */ // reset start height on signing infos - app.SlashingKeeper.IterateValidatorSigningInfos( + err = app.SlashingKeeper.IterateValidatorSigningInfos( ctx, func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { info.StartHeight = 0 - app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + if err := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); err != nil { + panic(err) + } return false }, ) + if err != nil { + panic(err) + } } diff --git a/app/genesis.go b/app/genesis.go index 2900679c1..ae974c8e6 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -1,9 +1,11 @@ package app import ( + "cosmossdk.io/log" "encoding/json" - - "github.com/cosmos/cosmos-sdk/codec" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + dbm "github.com/cosmos/cosmos-db" + "testing" ) // GenesisState of the blockchain is represented here as a map of raw json @@ -16,6 +18,19 @@ import ( type GenesisState map[string]json.RawMessage // NewDefaultGenesisState generates the default state for the application. -func NewDefaultGenesisState(cdc codec.JSONCodec) GenesisState { - return ModuleBasics.DefaultGenesis(cdc) +func NewDefaultGenesisState(t *testing.T) GenesisState { + t.Helper() + // we "pre"-instantiate the application for getting the injected/configured encoding configuration + // note, this is not necessary when using app wiring, as depinject can be directly used (see root_v2.go) + tempApp := NewBabylonApp( + log.NewNopLogger(), + dbm.NewMemDB(), + nil, + true, + map[int64]bool{}, + 0, + nil, + EmptyAppOptions{}, + []wasmkeeper.Option{}) + return tempApp.DefaultGenesis() } diff --git a/app/modules.go b/app/modules.go index 424a5179a..b6482870c 100644 --- a/app/modules.go +++ b/app/modules.go @@ -2,9 +2,9 @@ package app import ( "github.com/cosmos/cosmos-sdk/client" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" - ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" - ibctestingtypes "github.com/cosmos/ibc-go/v7/testing/types" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + ibctestingtypes "github.com/cosmos/ibc-go/v8/testing/types" ) // The following functions are required by ibctesting @@ -23,5 +23,5 @@ func (app *BabylonApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { } func (app *BabylonApp) GetTxConfig() client.TxConfig { - return GetEncodingConfig().TxConfig + return app.TxConfig() } diff --git a/app/params/amino.go b/app/params/amino.go deleted file mode 100644 index 9be768342..000000000 --- a/app/params/amino.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build test_amino -// +build test_amino - -package params - -import ( - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/types" -) - -// MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. -// This function should be used only internally (in the SDK). -// App user should'nt create new codecs - use the app.AppCodec instead. -// [DEPRECATED] -func MakeTestEncodingConfig() EncodingConfig { - cdc := codec.NewLegacyAmino() - interfaceRegistry := types.NewInterfaceRegistry() - marshaler := codec.NewAminoCodec(cdc) - - return EncodingConfig{ - InterfaceRegistry: interfaceRegistry, - Marshaler: marshaler, - TxConfig: legacytx.StdTxConfig{Cdc: cdc}, - Amino: cdc, - } -} diff --git a/app/params/config.go b/app/params/config.go index 7649b7066..3d4322677 100644 --- a/app/params/config.go +++ b/app/params/config.go @@ -1,6 +1,7 @@ package params import ( + "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/types/address" errorsmod "cosmossdk.io/errors" @@ -38,11 +39,11 @@ func init() { } func RegisterDenoms() { - err := sdk.RegisterDenom(HumanCoinUnit, sdk.OneDec()) + err := sdk.RegisterDenom(HumanCoinUnit, math.LegacyOneDec()) if err != nil { panic(err) } - err = sdk.RegisterDenom(BaseCoinUnit, sdk.NewDecWithPrec(1, BbnExponent)) + err = sdk.RegisterDenom(BaseCoinUnit, math.LegacyNewDecWithPrec(1, BbnExponent)) if err != nil { panic(err) } diff --git a/app/params/doc.go b/app/params/doc.go deleted file mode 100644 index bc9d1630a..000000000 --- a/app/params/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Package params defines the simulation parameters in the babylon app. - -It contains the default weights used for each transaction used on the module's -simulation. These weights define the chance for a transaction to be simulated at -any given operation. - -You can replace the default values for the weights by providing a params.json -file with the weights defined for each of the transaction operations: - - { - "op_weight_msg_send": 60, - "op_weight_msg_delegate": 100, - } - -In the example above, the `MsgSend` has 60% chance to be simulated, while the -`MsgDelegate` will always be simulated. -*/ -package params diff --git a/app/params/encoding.go b/app/params/encoding.go index 2cd16263a..8ff9ea04b 100644 --- a/app/params/encoding.go +++ b/app/params/encoding.go @@ -10,8 +10,7 @@ import ( // This is provided for compatibility between protobuf and amino implementations. type EncodingConfig struct { InterfaceRegistry types.InterfaceRegistry - // NOTE: this field will be renamed to Codec - Marshaler codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino } diff --git a/app/params/params.go b/app/params/params.go deleted file mode 100644 index 85d61e3c1..000000000 --- a/app/params/params.go +++ /dev/null @@ -1,8 +0,0 @@ -package params - -// Simulation parameter constants -const ( - StakePerAccount = "stake_per_account" - InitiallyBondedValidators = "initially_bonded_validators" - SimAppChainID = "simulation-app" -) diff --git a/app/params/proto.go b/app/params/proto.go index f2f36aae3..7ae024c72 100644 --- a/app/params/proto.go +++ b/app/params/proto.go @@ -4,21 +4,39 @@ package params import ( + "github.com/cosmos/gogoproto/proto" + + "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/x/auth/tx" ) -// GetEncodingConfig creates an EncodingConfig for an amino based test configuration. -func GetEncodingConfig() EncodingConfig { +// DefaultEncodingConfig returns the default encoding config +func DefaultEncodingConfig() *EncodingConfig { amino := codec.NewLegacyAmino() - interfaceRegistry := types.NewInterfaceRegistry() + interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: Bech32PrefixAccAddr, + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: Bech32PrefixValAddr, + }, + }, + }) + if err != nil { + panic(err) + } marshaler := codec.NewProtoCodec(interfaceRegistry) txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) - return EncodingConfig{ + return &EncodingConfig{ InterfaceRegistry: interfaceRegistry, - Marshaler: marshaler, + Codec: marshaler, TxConfig: txCfg, Amino: amino, } diff --git a/app/params/weights.go b/app/params/weights.go deleted file mode 100644 index 746e304de..000000000 --- a/app/params/weights.go +++ /dev/null @@ -1,28 +0,0 @@ -package params - -// Default simulation operation weights for messages and gov proposals -const ( - DefaultWeightMsgSend int = 100 - DefaultWeightMsgMultiSend int = 10 - DefaultWeightMsgSetWithdrawAddress int = 50 - DefaultWeightMsgWithdrawDelegationReward int = 50 - DefaultWeightMsgWithdrawValidatorCommission int = 50 - DefaultWeightMsgFundCommunityPool int = 50 - DefaultWeightMsgDeposit int = 100 - DefaultWeightMsgVote int = 67 - DefaultWeightMsgVoteWeighted int = 33 - DefaultWeightMsgUnjail int = 100 - DefaultWeightMsgCreateValidator int = 100 - DefaultWeightMsgEditValidator int = 5 - DefaultWeightMsgDelegate int = 100 - DefaultWeightMsgUndelegate int = 100 - DefaultWeightMsgBeginRedelegate int = 100 - - DefaultWeightCommunitySpendProposal int = 5 - DefaultWeightTextProposal int = 5 - DefaultWeightParamChangeProposal int = 5 - - // feegrant - DefaultWeightGrantAllowance int = 100 - DefaultWeightRevokeAllowance int = 100 -) diff --git a/app/test_helpers.go b/app/test_helpers.go index d69efe98c..2e65ab15a 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -1,44 +1,38 @@ package app import ( - "bytes" - "encoding/hex" "encoding/json" - "fmt" + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/privval" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/cosmos/cosmos-sdk/client/flags" + cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/server" "math/rand" - "strconv" "testing" "time" "cosmossdk.io/math" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmjson "github.com/cometbft/cometbft/libs/json" - "github.com/babylonchain/babylon/testutil/datagen" - - errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" appparams "github.com/babylonchain/babylon/app/params" bbn "github.com/babylonchain/babylon/types" - dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmtypes "github.com/cometbft/cometbft/types" + "github.com/cometbft/cometbft/crypto/ed25519" + dbm "github.com/cosmos/cosmos-db" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - sec256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/server/types" simsutils "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -50,46 +44,63 @@ type SetupOptions struct { Logger log.Logger DB *dbm.MemDB InvCheckPeriod uint - HomePath string SkipUpgradeHeights map[int64]bool - EncConfig appparams.EncodingConfig AppOpts types.AppOptions } -func setup(withGenesis bool, invCheckPeriod uint) (*BabylonApp, GenesisState) { +func setup(t *testing.T, withGenesis bool, invCheckPeriod uint) (*BabylonApp, GenesisState) { db := dbm.NewMemDB() - encCdc := GetEncodingConfig() + nodeHome := t.TempDir() + + appOptions := make(simsutils.AppOptionsMap, 0) + appOptions[flags.FlagHome] = nodeHome // ensure unique folder + appOptions[server.FlagInvCheckPeriod] = invCheckPeriod + appOptions["btc-config.network"] = string(bbn.BtcSimnet) privSigner, err := SetupPrivSigner() if err != nil { panic(err) } - app := NewBabylonApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, invCheckPeriod, encCdc, privSigner, EmptyAppOptions{}, EmptyWasmOpts) + app := NewBabylonApp( + log.NewNopLogger(), + db, + nil, + true, + map[int64]bool{}, + invCheckPeriod, + privSigner, + EmptyAppOptions{}, + EmptyWasmOpts, + ) if withGenesis { - return app, NewDefaultGenesisState(encCdc.Marshaler) + return app, app.DefaultGenesis() } return app, GenesisState{} } -// NewBabyblonAppWithCustomOptions initializes a new BabylonApp with custom options. -// Created Babylon application will have one validator with hardoced amout of tokens. +// NewBabylonAppWithCustomOptions initializes a new BabylonApp with custom options. +// Created Babylon application will have one validator with hardcoed amout of tokens. // This is necessary as from cosmos-sdk 0.46 it is required that there is at least // one validator in validator set during InitGenesis abci call - https://github.com/cosmos/cosmos-sdk/pull/9697 -func NewBabyblonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *PrivSigner, options SetupOptions) *BabylonApp { +func NewBabylonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *PrivSigner, options SetupOptions) *BabylonApp { t.Helper() - privVal := datagen.NewPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) // create validator set with single validator - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - - // generate genesis account - senderPrivKey := sec256k1.GenPrivKey() + valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) + require.NoError(t, err) + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) + require.NoError(t, err) + genesisKey, err := checkpointingtypes.NewGenesisKey( + sdk.ValAddress(valKeys.ValPubkey.Address()), + &valKeys.BlsPubkey, + valKeys.PoP, + &cosmosed.PubKey{Key: valPubkey.Bytes()}, + ) + require.NoError(t, err) + genesisValSet := []*checkpointingtypes.GenesisKey{genesisKey} - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + acc := authtypes.NewBaseAccount(valPubkey.Address().Bytes(), valPubkey, 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), + Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), } app := NewBabylonApp( @@ -98,15 +109,13 @@ func NewBabyblonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *P nil, true, options.SkipUpgradeHeights, - options.HomePath, options.InvCheckPeriod, - options.EncConfig, privSigner, options.AppOpts, EmptyWasmOpts, ) - genesisState := NewDefaultGenesisState(app.appCodec) - genesisState = genesisStateWithValSet(t, app, genesisState, valSet, []authtypes.GenesisAccount{acc}, balance) + genesisState := app.DefaultGenesis() + genesisState = genesisStateWithValSet(t, app, genesisState, genesisValSet, []authtypes.GenesisAccount{acc}, balance) if !isCheckTx { // init chain must be called to stop deliverState from being nil @@ -114,13 +123,14 @@ func NewBabyblonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *P require.NoError(t, err) // Initialize the chain - app.InitChain( - abci.RequestInitChain{ + _, err = app.InitChain( + &abci.RequestInitChain{ Validators: []abci.ValidatorUpdate{}, ConsensusParams: simsutils.DefaultConsensusParams, AppStateBytes: stateBytes, }, ) + require.NoError(t, err) } return app @@ -128,59 +138,67 @@ func NewBabyblonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *P func genesisStateWithValSet(t *testing.T, app *BabylonApp, genesisState GenesisState, - valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, + valSet []*checkpointingtypes.GenesisKey, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance, ) GenesisState { // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) - validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) - delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + validators := make([]stakingtypes.Validator, 0, len(valSet)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet)) - bondAmt := sdk.DefaultPowerReduction + bondAmt := sdk.DefaultPowerReduction.MulRaw(1000) - for _, val := range valSet.Validators { - pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) - require.NoError(t, err) - pkAny, err := codectypes.NewAnyWithValue(pk) + for _, valGenKey := range valSet { + pkAny, err := codectypes.NewAnyWithValue(valGenKey.ValPubkey) require.NoError(t, err) validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), + OperatorAddress: valGenKey.ValidatorAddress, ConsensusPubkey: pkAny, Jailed: false, Status: stakingtypes.Bonded, Tokens: bondAmt, - DelegatorShares: sdk.OneDec(), + DelegatorShares: math.LegacyOneDec(), Description: stakingtypes.Description{}, UnbondingHeight: int64(0), UnbondingTime: time.Unix(0, 0).UTC(), - Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - MinSelfDelegation: sdk.ZeroInt(), + Commission: stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), + MinSelfDelegation: math.ZeroInt(), } - validators = append(validators, validator) - delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), valGenKey.ValidatorAddress, math.LegacyOneDec())) + // blsKeys = append(blsKeys, checkpointingtypes.NewGenesisKey(sdk.ValAddress(val.Address), genesisBLSPubkey)) } + // total bond amount = bond amount * number of validators + require.Equal(t, len(validators), len(delegations)) + totalBondAmt := bondAmt.MulRaw(int64(len(validators))) + // set validators and delegations stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) + stakingGenesis.Params.BondDenom = appparams.DefaultBondDenom genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) + checkpointingGenesis := &checkpointingtypes.GenesisState{ + GenesisKeys: valSet, + } + genesisState[checkpointingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(checkpointingGenesis) + totalSupply := sdk.NewCoins() for _, b := range balances { // add genesis acc tokens to total supply totalSupply = totalSupply.Add(b.Coins...) } - for range delegations { // add delegated tokens to total supply - totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)) + totalSupply = totalSupply.Add(sdk.NewCoin(appparams.DefaultBondDenom, bondAmt)) } // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), - Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)}, + Coins: sdk.Coins{sdk.NewCoin(appparams.DefaultBondDenom, totalBondAmt)}, }) // update total supply @@ -203,23 +221,28 @@ func genesisStateWithValSet(t *testing.T, func Setup(t *testing.T, isCheckTx bool) *BabylonApp { t.Helper() - privVal := datagen.NewPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) - // create validator set with single validator - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) + require.NoError(t, err) + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) + require.NoError(t, err) + genesisKey, err := checkpointingtypes.NewGenesisKey( + sdk.ValAddress(valKeys.ValPubkey.Address()), + &valKeys.BlsPubkey, + valKeys.PoP, + &cosmosed.PubKey{Key: valPubkey.Bytes()}, + ) + require.NoError(t, err) + genesisValSet := []*checkpointingtypes.GenesisKey{genesisKey} // generate genesis account - senderPrivKey := sec256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + acc := authtypes.NewBaseAccount(valPubkey.Address().Bytes(), valPubkey, 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), + Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), } - app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance) + app := SetupWithGenesisValSet(t, genesisValSet, []authtypes.GenesisAccount{acc}, balance) return app } @@ -230,8 +253,8 @@ func SetupPrivSigner() (*PrivSigner, error) { if err != nil { return nil, err } - nodeCfg := tmconfig.DefaultConfig() - encodingCfg := appparams.GetEncodingConfig() + nodeCfg := cmtconfig.DefaultConfig() + encodingCfg := appparams.DefaultEncodingConfig() privSigner, _ := InitPrivSigner(client.Context{}, ".", kr, "", encodingCfg) privSigner.WrappedPV.Clean(nodeCfg.PrivValidatorKeyFile(), nodeCfg.PrivValidatorStateFile()) return privSigner, nil @@ -241,128 +264,36 @@ func SetupPrivSigner() (*PrivSigner, error) { // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit (10^6) in the default token of the babylon app from first genesis // account. A Nop logger is set in BabylonApp. -func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { - app, genesisState := setup(true, 5) - // set genesis accounts - authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) - genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) - - validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) - delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) - - // sdk.DefaultPowerReduction is 1 unit of voting power, which by default needs 1000000 tokens - bondAmt := sdk.DefaultPowerReduction.MulRaw(1000) - - for _, val := range valSet.Validators { - pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) - require.NoError(t, err) - pkAny, err := codectypes.NewAnyWithValue(pk) - require.NoError(t, err) - validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: stakingtypes.Bonded, - Tokens: bondAmt, - DelegatorShares: sdk.OneDec(), - Description: stakingtypes.Description{}, - UnbondingHeight: int64(0), - UnbondingTime: time.Unix(0, 0).UTC(), - Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), - MinSelfDelegation: sdk.ZeroInt(), - } - validators = append(validators, validator) - delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) - } - - // total bond amount = bond amount * number of validators - require.Equal(t, len(validators), len(delegations)) - totalBondAmt := bondAmt.MulRaw(int64(len(validators))) - - // set validators and delegations - stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) - stakingGenesis.Params.BondDenom = appparams.DefaultBondDenom - genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) - - totalSupply := sdk.NewCoins() - for _, b := range balances { - // add genesis acc tokens and delegated tokens to total supply - totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(appparams.DefaultBondDenom, totalBondAmt))...) - } - - // add bonded amount to bonded pool module account - balances = append(balances, banktypes.Balance{ - Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), - Coins: sdk.Coins{sdk.NewCoin(appparams.DefaultBondDenom, totalBondAmt)}, - }) - - // update total supply - bankGenesis := banktypes.NewGenesisState( - banktypes.DefaultGenesisState().Params, - balances, - totalSupply, - []banktypes.Metadata{}, - []banktypes.SendEnabled{}, - ) - genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) +func SetupWithGenesisValSet(t *testing.T, valSet []*checkpointingtypes.GenesisKey, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { + t.Helper() + app, genesisState := setup(t, true, 5) + genesisState = genesisStateWithValSet(t, app, genesisState, valSet, genAccs, balances...) stateBytes, err := json.MarshalIndent(genesisState, "", " ") require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts - app.InitChain( - abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, - ConsensusParams: simsutils.DefaultConsensusParams, - AppStateBytes: stateBytes, - }, - ) - - return app -} - -// SetupWithGenesisAccounts initializes a new BabylonApp with the provided genesis -// accounts and possible balances. -func SetupWithGenesisAccounts(genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { - app, genesisState := setup(true, 0) - authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) - genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) - - totalSupply := sdk.NewCoins() - for _, b := range balances { - totalSupply = totalSupply.Add(b.Coins...) - } - - bankGenesis := banktypes.NewGenesisState( - banktypes.DefaultGenesisState().Params, - balances, - totalSupply, - []banktypes.Metadata{}, - []banktypes.SendEnabled{}, - ) - genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) - - stateBytes, err := json.MarshalIndent(genesisState, "", " ") - if err != nil { - panic(err) - } - - app.InitChain( - abci.RequestInitChain{ - Validators: []abci.ValidatorUpdate{}, - ConsensusParams: simsutils.DefaultConsensusParams, - AppStateBytes: stateBytes, - }, - ) + consensusParams := simsutils.DefaultConsensusParams + consensusParams.Block.MaxGas = 100 * simsutils.DefaultGenTxGas + _, err = app.InitChain(&abci.RequestInitChain{ + ChainId: app.ChainID(), + Time: time.Now().UTC(), + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: consensusParams, + InitialHeight: app.LastBlockHeight() + 1, + AppStateBytes: stateBytes, + }) + require.NoError(t, err) - app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1}}) + _, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Hash: app.LastCommitID().Hash, + }) + require.NoError(t, err) return app } -type GenerateAccountStrategy func(int) []sdk.AccAddress - // createRandomAccounts is a strategy used by addTestAddrs() in order to generated addresses in random order. func createRandomAccounts(accNum int) []sdk.AccAddress { testAddrs := make([]sdk.AccAddress, accNum) @@ -374,59 +305,22 @@ func createRandomAccounts(accNum int) []sdk.AccAddress { return testAddrs } -// createIncrementalAccounts is a strategy used by addTestAddrs() in order to generated addresses in ascending order. -func createIncrementalAccounts(accNum int) []sdk.AccAddress { - var addresses []sdk.AccAddress - var buffer bytes.Buffer - - // start at 100 so we can make up to 999 test addresses with valid test addresses - for i := 100; i < (accNum + 100); i++ { - numString := strconv.Itoa(i) - buffer.WriteString("A58856F0FD53BF058B4909A21AEC019107BA6") // base address string - - buffer.WriteString(numString) // adding on final two digits to make addresses unique - res, _ := sdk.AccAddressFromHexUnsafe(buffer.String()) - bech := res.String() - addr, _ := TestAddr(buffer.String(), bech) - - addresses = append(addresses, addr) - buffer.Reset() - } - - return addresses -} - -// AddTestAddrsFromPubKeys adds the addresses into the BabylonApp providing only the public keys. -func AddTestAddrsFromPubKeys(app *BabylonApp, ctx sdk.Context, pubKeys []cryptotypes.PubKey, accAmt math.Int) { - initCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), accAmt)) - - for _, pk := range pubKeys { - initAccountWithCoins(app, ctx, sdk.AccAddress(pk.Address()), initCoins) - } -} - // AddTestAddrs constructs and returns accNum amount of accounts with an // initial balance of accAmt in random order -func AddTestAddrs(app *BabylonApp, ctx sdk.Context, accNum int, accAmt math.Int) []sdk.AccAddress { - return addTestAddrs(app, ctx, accNum, accAmt, createRandomAccounts) -} +func AddTestAddrs(app *BabylonApp, ctx sdk.Context, accNum int, accAmt math.Int) ([]sdk.AccAddress, error) { + testAddrs := createRandomAccounts(accNum) -// AddTestAddrs constructs and returns accNum amount of accounts with an -// initial balance of accAmt in random order -func AddTestAddrsIncremental(app *BabylonApp, ctx sdk.Context, accNum int, accAmt math.Int) []sdk.AccAddress { - return addTestAddrs(app, ctx, accNum, accAmt, createIncrementalAccounts) -} - -func addTestAddrs(app *BabylonApp, ctx sdk.Context, accNum int, accAmt math.Int, strategy GenerateAccountStrategy) []sdk.AccAddress { - testAddrs := strategy(accNum) - - initCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), accAmt)) + bondDenom, err := app.StakingKeeper.BondDenom(ctx) + if err != nil { + return nil, err + } + initCoins := sdk.NewCoins(sdk.NewCoin(bondDenom, accAmt)) for _, addr := range testAddrs { initAccountWithCoins(app, ctx, addr, initCoins) } - return testAddrs + return testAddrs, nil } func initAccountWithCoins(app *BabylonApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) { @@ -441,58 +335,27 @@ func initAccountWithCoins(app *BabylonApp, ctx sdk.Context, addr sdk.AccAddress, } } -// ConvertAddrsToValAddrs converts the provided addresses to ValAddress. -func ConvertAddrsToValAddrs(addrs []sdk.AccAddress) []sdk.ValAddress { - valAddrs := make([]sdk.ValAddress, len(addrs)) - - for i, addr := range addrs { - valAddrs[i] = sdk.ValAddress(addr) - } - - return valAddrs -} +// EmptyAppOptions is a stub implementing AppOptions +type EmptyAppOptions struct{} -func TestAddr(addr string, bech string) (sdk.AccAddress, error) { - res, err := sdk.AccAddressFromHexUnsafe(addr) - if err != nil { - return nil, err - } - bechexpected := res.String() - if bech != bechexpected { - return nil, fmt.Errorf("bech encoding doesn't match reference") - } +// Get implements AppOptions +func (ao EmptyAppOptions) Get(o string) interface{} { + // some defaults required for app.toml config - bechres, err := sdk.AccAddressFromBech32(bech) - if err != nil { - return nil, err - } - if !bytes.Equal(bechres, res) { - return nil, err + if o == "btc-config.network" { + return string(bbn.BtcSimnet) } - return res, nil -} - -// CheckBalance checks the balance of an account. -func CheckBalance(t *testing.T, app *BabylonApp, addr sdk.AccAddress, balances sdk.Coins) { - ctxCheck := app.BaseApp.NewContext(true, tmproto.Header{}) - require.True(t, balances.IsEqual(app.BankKeeper.GetAllBalances(ctxCheck, addr))) + return nil } -// SignCheckDeliver checks a generated signed transaction and simulates a -// block commitment with the given transaction. A test assertion is made using -// the parameter 'expPass' against the result. A corresponding result is -// returned. -func SignCheckDeliver( - t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, header tmproto.Header, msgs []sdk.Msg, - chainID string, accNums, accSeqs []uint64, expSimPass, expPass bool, priv ...cryptotypes.PrivKey, -) (sdk.GasInfo, *sdk.Result, error) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) +// SignAndDeliverWithoutCommit signs and delivers a transaction. No commit +func SignAndDeliverWithoutCommit(t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, msgs []sdk.Msg, fees sdk.Coins, chainID string, accNums, accSeqs []uint64, blockTime time.Time, priv ...cryptotypes.PrivKey) (*abci.ResponseFinalizeBlock, error) { tx, err := simsutils.GenSignedMockTx( - r, + rand.New(rand.NewSource(time.Now().UnixNano())), txCfg, msgs, - sdk.Coins{sdk.NewInt64Coin(appparams.DefaultBondDenom, 0)}, + fees, simsutils.DefaultGenTxGas, chainID, accNums, @@ -500,139 +363,14 @@ func SignCheckDeliver( priv..., ) require.NoError(t, err) - txBytes, err := txCfg.TxEncoder()(tx) - require.Nil(t, err) - - // Must simulate now as CheckTx doesn't run Msgs anymore - _, res, err := app.Simulate(txBytes) - - if expSimPass { - require.NoError(t, err) - require.NotNil(t, res) - } else { - require.Error(t, err) - require.Nil(t, res) - } - - // Simulate a sending a transaction and committing a block - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - gInfo, res, err := app.SimDeliver(txCfg.TxEncoder(), tx) - if expPass { - require.NoError(t, err) - require.NotNil(t, res) - } else { - require.Error(t, err) - require.Nil(t, res) - } - - app.EndBlock(abci.RequestEndBlock{}) - app.Commit() - - return gInfo, res, err -} - -// GenSequenceOfTxs generates a set of signed transactions of messages, such -// that they differ only by having the sequence numbers incremented between -// every transaction. -func GenSequenceOfTxs(txGen client.TxConfig, msgs []sdk.Msg, accNums []uint64, initSeqNums []uint64, numToGenerate int, priv ...cryptotypes.PrivKey) ([]sdk.Tx, error) { - txs := make([]sdk.Tx, numToGenerate) - var err error - for i := 0; i < numToGenerate; i++ { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - txs[i], err = simsutils.GenSignedMockTx( - r, - txGen, - msgs, - sdk.Coins{sdk.NewInt64Coin(appparams.DefaultBondDenom, 0)}, - simsutils.DefaultGenTxGas, - "", - accNums, - initSeqNums, - priv..., - ) - if err != nil { - break - } - incrementAllSequenceNumbers(initSeqNums) - } - - return txs, err -} - -func incrementAllSequenceNumbers(initSeqNums []uint64) { - for i := 0; i < len(initSeqNums); i++ { - initSeqNums[i]++ - } -} - -// CreateTestPubKeys returns a total of numPubKeys public keys in ascending order. -func CreateTestPubKeys(numPubKeys int) []cryptotypes.PubKey { - var publicKeys []cryptotypes.PubKey - var buffer bytes.Buffer - - // start at 10 to avoid changing 1 to 01, 2 to 02, etc - for i := 100; i < (numPubKeys + 100); i++ { - numString := strconv.Itoa(i) - buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") // base pubkey string - buffer.WriteString(numString) // adding on final two digits to make pubkeys unique - publicKeys = append(publicKeys, NewPubKeyFromHex(buffer.String())) - buffer.Reset() - } - - return publicKeys -} - -// NewPubKeyFromHex returns a PubKey from a hex string. -func NewPubKeyFromHex(pk string) (res cryptotypes.PubKey) { - pkBytes, err := hex.DecodeString(pk) - if err != nil { - panic(err) - } - if len(pkBytes) != ed25519.PubKeySize { - panic(errorsmod.Wrap(errors.ErrInvalidPubKey, "invalid pubkey size")) - } - return &ed25519.PubKey{Key: pkBytes} -} - -// EmptyAppOptions is a stub implementing AppOptions -type EmptyAppOptions struct{} - -// Get implements AppOptions -func (ao EmptyAppOptions) Get(o string) interface{} { - // some defaults required for app.toml config - - if o == "btc-config.network" { - return string(bbn.BtcSimnet) - } - - return nil -} - -// FundAccount is a utility function that funds an account by minting and -// sending the coins to the address. This should be used for testing purposes -// only! -// -// TODO: Instead of using the mint module account, which has the -// permission of minting, create a "faucet" account. (@fdymylja) -func FundAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, amounts sdk.Coins) error { - if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { - return err - } - - return bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, amounts) -} - -// FundModuleAccount is a utility function that funds a module account by -// minting and sending the coins to the address. This should be used for testing -// purposes only! -// -// TODO: Instead of using the mint module account, which has the -// permission of minting, create a "faucet" account. (@fdymylja) -func FundModuleAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, recipientMod string, amounts sdk.Coins) error { - if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { - return err - } + bz, err := txCfg.TxEncoder()(tx) + require.NoError(t, err) - return bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, recipientMod, amounts) + return app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Hash: app.LastCommitID().Hash, + Time: blockTime, + Txs: [][]byte{bz}, + }) } diff --git a/app/types.go b/app/types.go deleted file mode 100644 index fc631ccdc..000000000 --- a/app/types.go +++ /dev/null @@ -1,44 +0,0 @@ -package app - -import ( - abci "github.com/cometbft/cometbft/abci/types" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" -) - -// App implements the common methods for a Cosmos SDK-based application -// specific blockchain. -type App interface { - // The assigned name of the app. - Name() string - - // The application types codec. - // NOTE: This shoult be sealed before being returned. - LegacyAmino() *codec.LegacyAmino - - // Application updates every begin block. - BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock - - // Application updates every end block. - EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock - - // Application update at chain (i.e app) initialization. - InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain - - // Loads the app at a given height. - LoadHeight(height int64) error - - // Exports the state of the application for a genesis file. - ExportAppStateAndValidators( - forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, - ) (types.ExportedApp, error) - - // All the registered module account addreses. - ModuleAccountAddrs() map[string]bool - - // Helper for the simulation framework. - SimulationManager() *module.SimulationManager -} diff --git a/app/utils.go b/app/utils.go index 4bbd2fd41..de2d49c81 100644 --- a/app/utils.go +++ b/app/utils.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" @@ -43,9 +43,9 @@ type PrivSigner struct { ClientCtx client.Context } -func InitPrivSigner(clientCtx client.Context, nodeDir string, kr keyring.Keyring, feePayer string, encodingCfg appparams.EncodingConfig) (*PrivSigner, error) { +func InitPrivSigner(clientCtx client.Context, nodeDir string, kr keyring.Keyring, feePayer string, encodingCfg *appparams.EncodingConfig) (*PrivSigner, error) { // setup private validator - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() pvKeyFile := filepath.Join(nodeDir, nodeCfg.PrivValidatorKeyFile()) err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0777) if err != nil { @@ -60,7 +60,7 @@ func InitPrivSigner(clientCtx client.Context, nodeDir string, kr keyring.Keyring clientCtx = clientCtx. WithInterfaceRegistry(encodingCfg.InterfaceRegistry). - WithCodec(encodingCfg.Marshaler). + WithCodec(encodingCfg.Codec). WithLegacyAmino(encodingCfg.Amino). WithTxConfig(encodingCfg.TxConfig). WithAccountRetriever(types.AccountRetriever{}). diff --git a/btcstaking/staking.go b/btcstaking/staking.go index c6aae1387..55a6921a3 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" + sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" @@ -12,7 +13,6 @@ import ( "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -91,7 +91,7 @@ func BuildSlashingTxFromOutpoint( stakingOutput wire.OutPoint, stakingAmount, fee int64, slashingAddress, changeAddress btcutil.Address, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) (*wire.MsgTx, error) { // Validate staking amount if stakingAmount <= 0 { @@ -196,7 +196,7 @@ func BuildSlashingTxFromStakingTx( stakingTx *wire.MsgTx, stakingOutputIdx uint32, slashingAddress, changeAddress btcutil.Address, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, fee int64, ) (*wire.MsgTx, error) { // Get the staking output at the specified index from the staking transaction @@ -243,7 +243,7 @@ func BuildSlashingTxFromStakingTxStrict( stakingOutputIdx uint32, slashingAddress, changeAddress btcutil.Address, fee int64, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, net *chaincfg.Params, ) (*wire.MsgTx, error) { // Get the staking output at the specified index from the staking transaction @@ -264,7 +264,7 @@ func BuildSlashingTxFromStakingTxStrict( slashingRate) } -// Transfer transaction is a transaction which: +// IsTransferTx Transfer transaction is a transaction which: // - has exactly one input // - has exactly one output func IsTransferTx(tx *wire.MsgTx) error { @@ -283,7 +283,7 @@ func IsTransferTx(tx *wire.MsgTx) error { return nil } -// Simple transfer transaction is a transaction which: +// IsSimpleTransfer Simple transfer transaction is a transaction which: // - has exactly one input // - has exactly one output // - is not replacable @@ -317,7 +317,7 @@ func IsSimpleTransfer(tx *wire.MsgTx) error { func ValidateSlashingTx( slashingTx *wire.MsgTx, slashingAddress btcutil.Address, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, slashingTxMinFee, stakingOutputValue int64, ) error { // Verify that the slashing transaction is not nil. @@ -408,7 +408,7 @@ func CheckTransactions( fundingTransaction *wire.MsgTx, fundingOutputIdx uint32, slashingTxMinFee int64, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, slashingAddress btcutil.Address, net *chaincfg.Params, ) error { @@ -653,14 +653,14 @@ func VerifyTransactionSigWithOutputData( } // IsSlashingRateValid checks if the given slashing rate is between the valid range i.e., (0,1) with a precision of at most 2 decimal places. -func IsSlashingRateValid(slashingRate sdk.Dec) bool { +func IsSlashingRateValid(slashingRate sdkmath.LegacyDec) bool { // Check if the slashing rate is between 0 and 1 - if slashingRate.LTE(sdk.ZeroDec()) || slashingRate.GTE(sdk.OneDec()) { + if slashingRate.LTE(sdkmath.LegacyZeroDec()) || slashingRate.GTE(sdkmath.LegacyOneDec()) { return false } // Multiply by 100 to move the decimal places and check if precision is at most 2 decimal places - multipliedRate := slashingRate.Mul(sdk.NewDec(100)) + multipliedRate := slashingRate.Mul(sdkmath.LegacyNewDec(100)) // Truncate the rate to remove decimal places truncatedRate := multipliedRate.TruncateDec() diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 3ec701e21..c3eacb655 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -1,6 +1,7 @@ package btcstaking_test import ( + sdkmath "cosmossdk.io/math" "math" "math/rand" "testing" @@ -12,11 +13,10 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) -func genValidStakingScriptData(t *testing.T, r *rand.Rand) *btcstaking.StakingScriptData { +func genValidStakingScriptData(_ *testing.T, r *rand.Rand) *btcstaking.StakingScriptData { stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) covenantPrivKeyBytes := datagen.GenRandomByteArray(r, 32) @@ -46,8 +46,8 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { minFee := 2000 // generate a random slashing rate with random precision, // this will include both valid and invalid ranges, so we can test both cases - randomPrecision := r.Int63n(4) // [0,3] - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 1001)), randomPrecision) // [0,1000] / 10^{randomPrecision} + randomPrecision := r.Int63n(4) // [0,3] + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 1001)), randomPrecision) // [0,1000] / 10^{randomPrecision} for i := 0; i < stakingTxNumOutputs; i++ { if i == stakingOutputIdx { @@ -87,7 +87,7 @@ func genRandomBTCAddress(r *rand.Rand) (*btcutil.AddressPubKeyHash, error) { return btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) } -func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdk.Dec, fee int64) { +func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdkmath.LegacyDec, fee int64) { dustThreshold := 546 // in satoshis // Generate random slashing and change addresses diff --git a/btctxformatter/formatter.go b/btctxformatter/formatter.go index 527e37a4a..6629b7ed4 100644 --- a/btctxformatter/formatter.go +++ b/btctxformatter/formatter.go @@ -25,7 +25,7 @@ type BabylonData struct { type RawBtcCheckpoint struct { Epoch uint64 - LastCommitHash []byte + AppHash []byte BitMap []byte SubmitterAddress []byte BlsSig []byte @@ -43,7 +43,7 @@ const ( // 4bytes tag + 4 bits version + 4 bits part index headerLength = TagLength + 1 - LastCommitHashLength = 32 + AppHashLength = 32 BitMapLength = 13 @@ -61,11 +61,11 @@ const ( // 8 bytes are for 64bit unsigned epoch number EpochLength = 8 - firstPartLength = headerLength + LastCommitHashLength + AddressLength + EpochLength + BitMapLength + firstPartLength = headerLength + AppHashLength + AddressLength + EpochLength + BitMapLength secondPartLength = headerLength + BlsSigLength + firstPartHashLength - RawBTCCheckpointLength = EpochLength + LastCommitHashLength + BitMapLength + BlsSigLength + AddressLength + RawBTCCheckpointLength = EpochLength + AppHashLength + BitMapLength + BlsSigLength + AddressLength ) func getVerHalf(version FormatVersion, halfNumber uint8) uint8 { @@ -96,7 +96,7 @@ func encodeFirstOpRetrun( tag BabylonTag, version FormatVersion, epoch uint64, - lastCommitHash []byte, + appHash []byte, bitMap []byte, submitterAddress []byte, ) []byte { @@ -107,7 +107,7 @@ func encodeFirstOpRetrun( serializedBytes = append(serializedBytes, U64ToBEBytes(epoch)...) - serializedBytes = append(serializedBytes, lastCommitHash...) + serializedBytes = append(serializedBytes, appHash...) serializedBytes = append(serializedBytes, bitMap...) @@ -154,8 +154,8 @@ func EncodeCheckpointData( return nil, nil, errors.New("invalid format version") } - if len(rawBTCCheckpoint.LastCommitHash) != LastCommitHashLength { - return nil, nil, errors.New("lastCommitHash should have 32 bytes") + if len(rawBTCCheckpoint.AppHash) != AppHashLength { + return nil, nil, errors.New("appHash should have 32 bytes") } if len(rawBTCCheckpoint.BitMap) != BitMapLength { @@ -174,7 +174,7 @@ func EncodeCheckpointData( tag, version, rawBTCCheckpoint.Epoch, - rawBTCCheckpoint.LastCommitHash, + rawBTCCheckpoint.AppHash, rawBTCCheckpoint.BitMap, rawBTCCheckpoint.SubmitterAddress, ) @@ -211,7 +211,7 @@ func parseHeader( header := formatHeader{ tag: BabylonTag(tagBytes), - version: FormatVersion((verHalf & 0xf)), + version: FormatVersion(verHalf & 0xf), part: verHalf >> 4, } @@ -220,7 +220,7 @@ func parseHeader( func (header *formatHeader) validateHeader( expectedTag BabylonTag, - supportedVersion FormatVersion, + _ FormatVersion, expectedPart uint8, ) error { if !bytes.Equal(header.tag, expectedTag) { @@ -304,7 +304,7 @@ func IsBabylonCheckpointData( return nil, errors.New("not valid babylon data") } -// DecodeRawCheckpoint extracts epoch, lastCommitHash, bitmap, and blsSig from a +// DecodeRawCheckpoint extracts epoch, appHash, bitmap, and blsSig from a // flat byte array and compose them into a RawCheckpoint struct func DecodeRawCheckpoint(version FormatVersion, btcCkptBytes []byte) (*RawBtcCheckpoint, error) { if version > CurrentVersion { @@ -318,14 +318,14 @@ func DecodeRawCheckpoint(version FormatVersion, btcCkptBytes []byte) (*RawBtcChe var b bytes.Buffer b.Write(btcCkptBytes) epochBytes := b.Next(EpochLength) - lchBytes := b.Next(LastCommitHashLength) + appHashBytes := b.Next(AppHashLength) bitmapBytes := b.Next(BitMapLength) addressBytes := b.Next(AddressLength) blsSigBytes := b.Next(BlsSigLength) rawCheckpoint := &RawBtcCheckpoint{ Epoch: binary.BigEndian.Uint64(epochBytes), - LastCommitHash: lchBytes, + AppHash: appHashBytes, BitMap: bitmapBytes, SubmitterAddress: addressBytes, BlsSig: blsSigBytes, diff --git a/btctxformatter/formatter_test.go b/btctxformatter/formatter_test.go index 06b9ea10f..d90127451 100644 --- a/btctxformatter/formatter_test.go +++ b/btctxformatter/formatter_test.go @@ -8,17 +8,17 @@ import ( ) func randNBytes(n int) []byte { - bytes := make([]byte, n) - cprand.Read(bytes) //nolint:errcheck // This is a test. - return bytes + bz := make([]byte, n) + cprand.Read(bz) //nolint:errcheck // This is a test. + return bz } func FuzzEncodingDecoding(f *testing.F) { - f.Add(uint64(5), randNBytes(TagLength), randNBytes(LastCommitHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) - f.Add(uint64(20), randNBytes(TagLength), randNBytes(LastCommitHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) - f.Add(uint64(2000), randNBytes(TagLength), randNBytes(LastCommitHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(5), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(20), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(2000), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) - f.Fuzz(func(t *testing.T, epoch uint64, tag []byte, lastCommitHash []byte, bitMap []byte, blsSig []byte, address []byte) { + f.Fuzz(func(t *testing.T, epoch uint64, tag []byte, appHash []byte, bitMap []byte, blsSig []byte, address []byte) { if len(tag) < TagLength { t.Skip("Tag should have 4 bytes") @@ -28,7 +28,7 @@ func FuzzEncodingDecoding(f *testing.F) { rawBTCCkpt := &RawBtcCheckpoint{ Epoch: epoch, - LastCommitHash: lastCommitHash, + AppHash: appHash, BitMap: bitMap, SubmitterAddress: blsSig, BlsSig: address, @@ -78,8 +78,8 @@ func FuzzEncodingDecoding(f *testing.F) { t.Errorf("Epoch should match. Expected: %v. Got: %v", epoch, ckpt.Epoch) } - if !bytes.Equal(lastCommitHash, ckpt.LastCommitHash) { - t.Errorf("LastCommitHash should match. Expected: %v. Got: %v", lastCommitHash, ckpt.LastCommitHash) + if !bytes.Equal(appHash, ckpt.AppHash) { + t.Errorf("AppHash should match. Expected: %v. Got: %v", appHash, ckpt.AppHash) } if !bytes.Equal(bitMap, ckpt.BitMap) { @@ -99,7 +99,7 @@ func FuzzDecodingWontPanic(f *testing.F) { f.Fuzz(func(t *testing.T, bytes []byte, tagIdx uint8) { tag := []byte{0, 1, 2, 3} - decoded, err := IsBabylonCheckpointData(BabylonTag(tag), CurrentVersion, bytes) + decoded, err := IsBabylonCheckpointData(tag, CurrentVersion, bytes) if err == nil { if decoded.Index != 0 && decoded.Index != 1 { diff --git a/client/docs/diagrams/create_raw_checkpoint.puml b/client/docs/diagrams/create_raw_checkpoint.puml index 17dfff17b..de1990280 100644 --- a/client/docs/diagrams/create_raw_checkpoint.puml +++ b/client/docs/diagrams/create_raw_checkpoint.puml @@ -36,21 +36,21 @@ end note == Block N+2: The block that contains the Quorum Certificate of the old validators over Block N+1 == Tendermint -> checkpointing : BeginBlock -checkpointing -> rawckpts : Store header.LastCommitHash +checkpointing -> rawckpts : Store header.AppHash note left LastCommitInfo in BeginBlock does not contain signatures, - but we are signing the LastCommitHash, the Merkle root of + but we are signing the AppHash, the Merkle root of signatures, and that is available in the Header. end note loop on each old validator node - Tendermint --> signer ++ : Observe block.header.LastCommitHash + Tendermint --> signer ++ : Observe block.header.AppHash note right The full block has the signatures, i.e. the Quorum Certificate (Q.C.), but all we need is the hash. end note - signer -> signer : Sign LastCommitHash + signer -> signer : Sign AppHash signer -> Tendermint -- : AddBlsSig Tendermint -> checkpointing ++: CheckTx note right @@ -73,7 +73,7 @@ loop for each AddBlsSig tx checkpointing -> checkpointing : Check BLS signature over full Q.C. checkpointing -> staking : Get validator power at \n beginning of previous epoch checkpointing -> checkpointing : Check eligibility to sign BLS - checkpointing -> rawckpts : Get LastCommitHash for the uncheckpointed epoch + checkpointing -> rawckpts : Get AppHash for the uncheckpointed epoch checkpointing -> checkpointing : Check that Q.C. hash in ledger matches the tx alt valid transaction diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index bd6f813c4..6da7695d5 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -1775,7 +1775,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -1818,7 +1818,7 @@ paths: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -1866,7 +1866,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -2262,7 +2262,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -2305,7 +2305,7 @@ paths: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -2353,7 +2353,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -5044,11 +5044,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -5127,11 +5127,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -5293,11 +5293,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -5531,11 +5531,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -5843,7 +5843,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -5975,7 +5975,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -6663,7 +6663,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -6799,7 +6799,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -7204,7 +7204,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -7340,7 +7340,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -7753,7 +7753,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -7888,7 +7888,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -8060,7 +8060,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -8103,7 +8103,7 @@ paths: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -8151,7 +8151,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -8192,11 +8192,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -8325,7 +8325,7 @@ paths: validator_set is the validator set of the sealed epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header proof_epoch_info: @@ -8781,7 +8781,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -8920,7 +8920,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -9096,7 +9096,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -9139,7 +9139,7 @@ paths: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -9187,7 +9187,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -9228,11 +9228,11 @@ paths: title: >- epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -9364,7 +9364,7 @@ paths: epoch This validator set has generated a BLS multisig - on `last_commit_hash` of + on `app_hash` of the sealer header proof_epoch_info: @@ -9803,7 +9803,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -10234,7 +10234,7 @@ paths: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -12141,7 +12141,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -12181,7 +12181,7 @@ definitions: title: >- sealer_header is the 2nd header of the next epoch - This validator set has generated a BLS multisig on `last_commit_hash` + This validator set has generated a BLS multisig on `app_hash` of the sealer header @@ -12230,7 +12230,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -12409,7 +12409,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -12450,7 +12450,7 @@ definitions: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -12498,7 +12498,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -13054,7 +13054,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -13095,7 +13095,7 @@ definitions: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -13143,7 +13143,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -14889,7 +14889,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -15078,11 +15078,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual BLS + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15119,11 +15119,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15259,11 +15259,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15381,11 +15381,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15534,11 +15534,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual BLS sigs + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15567,11 +15567,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual BLS + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -15757,7 +15757,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -15887,7 +15887,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16067,7 +16067,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16198,7 +16198,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16367,7 +16367,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16408,7 +16408,7 @@ definitions: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -16456,7 +16456,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16495,11 +16495,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual BLS + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -16626,7 +16626,7 @@ definitions: validator_set is the validator set of the sealed epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header proof_epoch_info: @@ -16834,7 +16834,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -16983,7 +16983,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -17065,7 +17065,7 @@ definitions: title: >- validator_set is the validator set of the sealed epoch - This validator set has generated a BLS multisig on `last_commit_hash` + This validator set has generated a BLS multisig on `app_hash` of the sealer header @@ -17129,11 +17129,11 @@ definitions: The verifier can perform the following verification rules: - - The raw checkpoint's `last_commit_hash` is same as in the sealer header + - The raw checkpoint's `app_hash` is same as in the sealer header - More than 1/3 (in voting power) validators in the validator set of this - epoch have signed `last_commit_hash` of the sealer header + epoch have signed `app_hash` of the sealer header - The epoch medatata is committed to the `app_hash` of the sealer header @@ -17223,7 +17223,7 @@ definitions: validator_set is the validator set of the sealed epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header proof_epoch_info: @@ -17476,7 +17476,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -17608,7 +17608,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -17798,7 +17798,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -17930,7 +17930,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -18121,7 +18121,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -18252,7 +18252,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -18421,7 +18421,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -18462,7 +18462,7 @@ definitions: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -18510,7 +18510,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -18549,11 +18549,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that individual BLS + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -18680,7 +18680,7 @@ definitions: validator_set is the validator set of the sealed epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header proof_epoch_info: @@ -18905,7 +18905,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19039,7 +19039,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19209,7 +19209,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19252,7 +19252,7 @@ definitions: sealer_header is the 2nd header of the next epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header type: object @@ -19300,7 +19300,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19339,11 +19339,11 @@ definitions: type: string format: uint64 title: epoch_num defines the epoch number the raw checkpoint is for - last_commit_hash: + app_hash: type: string format: byte title: >- - last_commit_hash defines the 'LastCommitHash' that + app_hash defines the 'AppHash' that individual BLS sigs are signed on @@ -19472,7 +19472,7 @@ definitions: validator_set is the validator set of the sealed epoch This validator set has generated a BLS multisig on - `last_commit_hash` of + `app_hash` of the sealer header proof_epoch_info: @@ -19681,7 +19681,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19811,7 +19811,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -19970,7 +19970,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data @@ -20103,7 +20103,7 @@ definitions: type: string format: byte title: PartsetHeader - last_commit_hash: + app_hash: type: string format: byte title: hashes of block data diff --git a/client/tx/tx.go b/client/tx/tx.go index cc7b9b4e3..97128e375 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -1,6 +1,7 @@ package tx import ( + "context" "errors" "fmt" @@ -11,17 +12,11 @@ import ( "github.com/babylonchain/babylon/types" ) -func SendMsgToTendermint(clientCtx client.Context, msg sdk.Msg) (*sdk.TxResponse, error) { - return SendMsgsToTendermint(clientCtx, []sdk.Msg{msg}) +func SendMsgToComet(ctx context.Context, clientCtx client.Context, msg sdk.Msg) (*sdk.TxResponse, error) { + return SendMsgsToComet(ctx, clientCtx, []sdk.Msg{msg}) } -func SendMsgsToTendermint(clientCtx client.Context, msgs []sdk.Msg) (*sdk.TxResponse, error) { - for _, msg := range msgs { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - } - +func SendMsgsToComet(ctx context.Context, clientCtx client.Context, msgs []sdk.Msg) (*sdk.TxResponse, error) { gasPrice, gasAdjustment := types.MustGetGasSettings(clientCtx.HomeDir, clientCtx.Viper) txf := sdktx.Factory{}. WithTxConfig(clientCtx.TxConfig). @@ -32,7 +27,7 @@ func SendMsgsToTendermint(clientCtx client.Context, msgs []sdk.Msg) (*sdk.TxResp WithGasPrices(gasPrice). WithGasAdjustment(gasAdjustment) - return BroadcastTx(clientCtx, txf, msgs...) + return BroadcastTx(ctx, clientCtx, txf, msgs...) } // BroadcastTx attempts to generate, sign and broadcast a transaction with the @@ -40,7 +35,7 @@ func SendMsgsToTendermint(clientCtx client.Context, msgs []sdk.Msg) (*sdk.TxResp // It will return an error upon failure. // The code is based on cosmos-sdk: https://github.com/cosmos/cosmos-sdk/blob/7781cdb3d20bc7ebac017452897ce1e6ab3903ef/client/tx/tx.go#L65 // it treats non-zero response code as errors -func BroadcastTx(clientCtx client.Context, txf sdktx.Factory, msgs ...sdk.Msg) (*sdk.TxResponse, error) { +func BroadcastTx(ctx context.Context, clientCtx client.Context, txf sdktx.Factory, msgs ...sdk.Msg) (*sdk.TxResponse, error) { txf, err := prepareFactory(clientCtx, txf) if err != nil { return nil, err @@ -64,7 +59,7 @@ func BroadcastTx(clientCtx client.Context, txf sdktx.Factory, msgs ...sdk.Msg) ( tx.SetFeeGranter(clientCtx.GetFeeGranterAddress()) - err = sdktx.Sign(txf, clientCtx.GetFromName(), tx, true) + err = sdktx.Sign(ctx, txf, clientCtx.GetFromName(), tx, true) if err != nil { return nil, err } @@ -74,7 +69,7 @@ func BroadcastTx(clientCtx client.Context, txf sdktx.Factory, msgs ...sdk.Msg) ( return nil, err } - // broadcast to a Tendermint node + // broadcast to a comet node res, err := clientCtx.BroadcastTx(txBytes) if err != nil { return nil, err diff --git a/cmd/babylond/cmd/add_gen_bls_test.go b/cmd/babylond/cmd/add_gen_bls_test.go index 0bb59cfb7..459a2935d 100644 --- a/cmd/babylond/cmd/add_gen_bls_test.go +++ b/cmd/babylond/cmd/add_gen_bls_test.go @@ -6,45 +6,89 @@ import ( "path/filepath" "testing" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/x/genutil" - tmconfig "github.com/cometbft/cometbft/config" + appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" + authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1" + "cosmossdk.io/core/appconfig" + "cosmossdk.io/depinject" + "cosmossdk.io/log" + "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/cmd/babylond/cmd" + "github.com/babylonchain/babylon/privval" + "github.com/babylonchain/babylon/testutil/cli" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/x/checkpointing/types" + cmtconfig "github.com/cometbft/cometbft/config" tmjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/libs/tempfile" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/testutil/configurator" "github.com/cosmos/cosmos-sdk/testutil/network" genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/spf13/viper" "github.com/stretchr/testify/require" - - "github.com/babylonchain/babylon/app" - "github.com/babylonchain/babylon/cmd/babylond/cmd" - "github.com/babylonchain/babylon/privval" - "github.com/babylonchain/babylon/testutil/cli" - "github.com/babylonchain/babylon/testutil/datagen" - "github.com/babylonchain/babylon/x/checkpointing/types" ) +func newConfig() depinject.Config { + return configurator.NewAppConfig( + func(config *configurator.Config) { + config.ModuleConfigs["auth"] = &appv1alpha1.ModuleConfig{ + Name: "auth", + Config: appconfig.WrapAny(&authmodulev1.Module{ + Bech32Prefix: "bbn", // overwrite prefix here + ModuleAccountPermissions: []*authmodulev1.ModuleAccountPermission{ + {Account: "fee_collector"}, + {Account: "distribution"}, + {Account: "mint", Permissions: []string{"minter"}}, + {Account: "bonded_tokens_pool", Permissions: []string{"burner", "staking"}}, + {Account: "not_bonded_tokens_pool", Permissions: []string{"burner", "staking"}}, + {Account: "gov", Permissions: []string{"burner"}}, + {Account: "nft"}, + }, + }), + } + }, + configurator.ParamsModule(), + configurator.BankModule(), + configurator.GenutilModule(), + configurator.StakingModule(), + configurator.ConsensusModule(), + configurator.TxModule(), + ) +} + // test adding genesis BLS keys without gentx // error is expected func Test_AddGenBlsCmdWithoutGentx(t *testing.T) { home := t.TempDir() logger := log.NewNopLogger() - tmcfg, err := genutiltest.CreateDefaultTendermintConfig(home) + cmtcfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - appCodec := app.GetEncodingConfig().Marshaler - gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) + db := dbm.NewMemDB() + signer, err := app.SetupPrivSigner() + require.NoError(t, err) + bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + Logger: logger, + DB: db, + InvCheckPeriod: 0, + SkipUpgradeHeights: map[int64]bool{}, + AppOpts: app.EmptyAppOptions{}, + }) + gentxModule := bbn.BasicModuleManager[genutiltypes.ModuleName].(genutil.AppModuleBasic) + appCodec := bbn.AppCodec() err = genutiltest.ExecInitCmd(testMbm, home, appCodec) require.NoError(t, err) - serverCtx := server.NewContext(viper.New(), tmcfg, logger) + serverCtx := server.NewContext(viper.New(), cmtcfg, logger) clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) cfg := serverCtx.Config cfg.SetRoot(clientCtx.HomeDir) @@ -70,20 +114,29 @@ func Test_AddGenBlsCmdWithoutGentx(t *testing.T) { // test adding genesis BLS keys with gentx // error is expected if adding duplicate func Test_AddGenBlsCmdWithGentx(t *testing.T) { - min := network.MinimumAppConfig() - cfg, _ := network.DefaultConfigWithAppConfig(min) + db := dbm.NewMemDB() + signer, err := app.SetupPrivSigner() + require.NoError(t, err) + bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + Logger: log.NewNopLogger(), + DB: db, + InvCheckPeriod: 0, + SkipUpgradeHeights: map[int64]bool{}, + AppOpts: app.EmptyAppOptions{}, + }) + + gentxModule := bbn.BasicModuleManager[genutiltypes.ModuleName].(genutil.AppModuleBasic) config.SetConfigTemplate(config.DefaultConfigTemplate) + cfg, _ := network.DefaultConfigWithAppConfig(newConfig()) cfg.NumValidators = 1 - testNetwork, err := network.New(t, t.TempDir(), cfg) require.NoError(t, err) defer testNetwork.Cleanup() _, err = testNetwork.WaitForHeight(1) require.NoError(t, err) - gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) - targetCfg := tmconfig.DefaultConfig() + targetCfg := cmtconfig.DefaultConfig() targetCfg.SetRoot(filepath.Join(testNetwork.Validators[0].Dir, "simd")) targetGenesisFile := targetCfg.GenesisFile() targetCtx := testNetwork.Validators[0].ClientCtx @@ -91,13 +144,12 @@ func Test_AddGenBlsCmdWithGentx(t *testing.T) { v := testNetwork.Validators[i] // build and create genesis BLS key genBlsCmd := cmd.GenBlsCmd() - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() homeDir := filepath.Join(v.Dir, "simd") nodeCfg.SetRoot(homeDir) keyPath := nodeCfg.PrivValidatorKeyFile() statePath := nodeCfg.PrivValidatorStateFile() filePV := privval.GenWrappedFilePV(keyPath, statePath) - defer filePV.Clean(keyPath, statePath) filePV.SetAccAddress(v.Address) _, err = cli.ExecTestCLICmd(v.ClientCtx, genBlsCmd, []string{fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir)}) require.NoError(t, err) @@ -120,5 +172,6 @@ func Test_AddGenBlsCmdWithGentx(t *testing.T) { require.NotEmpty(t, checkpointingGenState.GenesisKeys) gks := checkpointingGenState.GetGenesisKeys() require.Equal(t, genKey, gks[i]) + filePV.Clean(keyPath, statePath) } } diff --git a/cmd/babylond/cmd/cmd_test.go b/cmd/babylond/cmd/cmd_test.go index b91efe3f2..f00b10994 100644 --- a/cmd/babylond/cmd/cmd_test.go +++ b/cmd/babylond/cmd/cmd_test.go @@ -13,7 +13,7 @@ import ( ) func TestInitCmd(t *testing.T) { - rootCmd, _ := cmd.NewRootCmd() + rootCmd := cmd.NewRootCmd() rootCmd.SetArgs([]string{ "init", // Test the init cmd "app-test", // Moniker diff --git a/cmd/babylond/cmd/create_bls_key.go b/cmd/babylond/cmd/create_bls_key.go index 2d2430e2c..8ddd41f00 100644 --- a/cmd/babylond/cmd/create_bls_key.go +++ b/cmd/babylond/cmd/create_bls_key.go @@ -7,7 +7,7 @@ import ( appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/privval" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" @@ -55,7 +55,7 @@ $ babylond create-bls-key %s1f5tnl46mk4dfp4nx3n2vnrvyw2h2ydz6ykhk3r --home ./ } func CreateBlsKey(home string, addr sdk.AccAddress) error { - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() keyPath := filepath.Join(home, nodeCfg.PrivValidatorKeyFile()) statePath := filepath.Join(home, nodeCfg.PrivValidatorStateFile()) if !tmos.FileExists(keyPath) { diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index e467d58e3..00a35a91f 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -1,10 +1,9 @@ package cmd import ( + "cosmossdk.io/math" "time" - sdk "github.com/cosmos/cosmos-sdk/types" - babylonApp "github.com/babylonchain/babylon/app" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -55,9 +54,9 @@ type GenesisCLIArgs struct { CovenantPK string SlashingAddress string MinSlashingTransactionFeeSat int64 - SlashingRate sdk.Dec + SlashingRate math.LegacyDec MinPubRand uint64 - MinCommissionRate sdk.Dec + MinCommissionRate math.LegacyDec } func addGenesisFlags(cmd *cobra.Command) { @@ -135,8 +134,8 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { CovenantPK: covenantPk, SlashingAddress: slashingAddress, MinSlashingTransactionFeeSat: minSlashingFee, - MinCommissionRate: sdk.MustNewDecFromStr(minCommissionRate), - SlashingRate: sdk.MustNewDecFromStr(slashingRate), + MinCommissionRate: math.LegacyMustNewDecFromStr(minCommissionRate), + SlashingRate: math.LegacyMustNewDecFromStr(slashingRate), MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genaccounts.go b/cmd/babylond/cmd/genaccounts.go index 65d9d547f..b30395cbb 100644 --- a/cmd/babylond/cmd/genaccounts.go +++ b/cmd/babylond/cmd/genaccounts.go @@ -90,7 +90,10 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa baseAccount := authtypes.NewBaseAccount(addr, nil, 0, 0) if !vestingAmt.IsZero() { - baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + if err != nil { + return err + } if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { diff --git a/cmd/babylond/cmd/genaccounts_test.go b/cmd/babylond/cmd/genaccounts_test.go index 3f18a76be..0142009a1 100644 --- a/cmd/babylond/cmd/genaccounts_test.go +++ b/cmd/babylond/cmd/genaccounts_test.go @@ -3,13 +3,14 @@ package cmd_test import ( "context" "fmt" + "github.com/babylonchain/babylon/app" "testing" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -21,7 +22,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/genutil" genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" - "github.com/babylonchain/babylon/app" bbncmd "github.com/babylonchain/babylon/cmd/babylond/cmd" ) @@ -66,15 +66,15 @@ func TestAddGenesisAccountCmd(t *testing.T) { }, } + appCodec := app.GetEncodingConfig().Codec for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { home := t.TempDir() logger := log.NewNopLogger() - cfg, err := genutiltest.CreateDefaultTendermintConfig(home) + cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - appCodec := app.GetEncodingConfig().Marshaler err = genutiltest.ExecInitCmd(testMbm, home, appCodec) require.NoError(t, err) diff --git a/cmd/babylond/cmd/genbls.go b/cmd/babylond/cmd/genbls.go index 5534cd60b..0742a2bf3 100644 --- a/cmd/babylond/cmd/genbls.go +++ b/cmd/babylond/cmd/genbls.go @@ -7,7 +7,7 @@ import ( "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/privval" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" @@ -32,7 +32,7 @@ $ babylond genbls --home ./ RunE: func(cmd *cobra.Command, args []string) error { homeDir, _ := cmd.Flags().GetString(flags.FlagHome) - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() keyPath := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) statePath := filepath.Join(homeDir, nodeCfg.PrivValidatorStateFile()) if !tmos.FileExists(keyPath) { diff --git a/cmd/babylond/cmd/genbls_test.go b/cmd/babylond/cmd/genbls_test.go index bf9abc7c4..93037f760 100644 --- a/cmd/babylond/cmd/genbls_test.go +++ b/cmd/babylond/cmd/genbls_test.go @@ -7,8 +7,10 @@ import ( "path/filepath" "testing" - tmconfig "github.com/cometbft/cometbft/config" - "github.com/cometbft/cometbft/libs/log" + dbm "github.com/cosmos/cosmos-db" + + "cosmossdk.io/log" + cmtconfig "github.com/cometbft/cometbft/config" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/hd" @@ -28,19 +30,27 @@ import ( func Test_GenBlsCmd(t *testing.T) { home := t.TempDir() - encodingConfig := app.GetEncodingConfig() logger := log.NewNopLogger() - cfg, err := genutiltest.CreateDefaultTendermintConfig(home) + cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - err = genutiltest.ExecInitCmd(app.ModuleBasics, home, encodingConfig.Marshaler) + signer, err := app.SetupPrivSigner() + require.NoError(t, err) + bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + Logger: logger, + DB: dbm.NewMemDB(), + InvCheckPeriod: 0, + SkipUpgradeHeights: map[int64]bool{}, + AppOpts: app.EmptyAppOptions{}, + }) + err = genutiltest.ExecInitCmd(bbn.BasicModuleManager, home, bbn.AppCodec()) require.NoError(t, err) serverCtx := server.NewContext(viper.New(), cfg, logger) clientCtx := client.Context{}. - WithCodec(encodingConfig.Marshaler). + WithCodec(bbn.AppCodec()). WithHomeDir(home). - WithTxConfig(encodingConfig.TxConfig) + WithTxConfig(bbn.TxConfig()) ctx := context.Background() ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) @@ -58,7 +68,7 @@ func Test_GenBlsCmd(t *testing.T) { require.NoError(t, err) // create BLS keys - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() keyPath := filepath.Join(home, nodeCfg.PrivValidatorKeyFile()) statePath := filepath.Join(home, nodeCfg.PrivValidatorStateFile()) filePV := privval.GenWrappedFilePV(keyPath, statePath) diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 929049c26..b4177c1b0 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -1,6 +1,7 @@ package cmd import ( + sdkmath "cosmossdk.io/math" "encoding/json" "fmt" "time" @@ -55,7 +56,7 @@ Example: genFile := config.GenesisFile() - appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + genesisState, genesis, err := genutiltypes.GenesisStateFromGenFile(genFile) if err != nil { return fmt.Errorf("failed to unmarshal genesis state: %s", err) } @@ -81,16 +82,16 @@ Example: return fmt.Errorf("please choose testnet or mainnet") } - err = PrepareGenesis(clientCtx, appState, genDoc, genesisParams, chainID) + err = PrepareGenesis(clientCtx, genesisState, genesis, genesisParams, chainID) if err != nil { return fmt.Errorf("failed to prepare genesis: %w", err) } - if err = mbm.ValidateGenesis(clientCtx.Codec, clientCtx.TxConfig, appState); err != nil { + if err = mbm.ValidateGenesis(clientCtx.Codec, clientCtx.TxConfig, genesisState); err != nil { return fmt.Errorf("error validating genesis file: %s", err) } - return genutil.ExportGenesisFile(genDoc, genFile) + return genutil.ExportGenesisFile(genesis, genFile) }, } @@ -102,12 +103,12 @@ Example: func PrepareGenesis( clientCtx client.Context, - appState map[string]json.RawMessage, - genDoc *comettypes.GenesisDoc, + genesisState map[string]json.RawMessage, + genesis *genutiltypes.AppGenesis, genesisParams GenesisParams, chainID string, ) error { - if genDoc == nil { + if genesis == nil { return fmt.Errorf("provided genesis must not be nil") } @@ -115,76 +116,76 @@ func PrepareGenesis( cdc := depCdc // Add ChainID - genDoc.ChainID = chainID - genDoc.GenesisTime = genesisParams.GenesisTime + genesis.ChainID = chainID + genesis.GenesisTime = genesisParams.GenesisTime - if genDoc.ConsensusParams == nil { - genDoc.ConsensusParams = comettypes.DefaultConsensusParams() + if genesis.Consensus == nil { + genesis.Consensus = genutiltypes.NewConsensusGenesis(comettypes.DefaultConsensusParams().ToProto(), nil) } // Set gas limit - genDoc.ConsensusParams.Block.MaxGas = genesisParams.BlockGasLimit + genesis.Consensus.Params.Block.MaxGas = genesisParams.BlockGasLimit // Set the confirmation and finalization parameters btccheckpointGenState := btccheckpointtypes.DefaultGenesis() btccheckpointGenState.Params = genesisParams.BtccheckpointParams - appState[btccheckpointtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btccheckpointGenState) + genesisState[btccheckpointtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btccheckpointGenState) // btclightclient genesis btclightclientGenState := btclightclienttypes.DefaultGenesis() btclightclientGenState.BaseBtcHeader = genesisParams.BtclightclientBaseBtcHeader - appState[btclightclienttypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btclightclientGenState) + genesisState[btclightclienttypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btclightclientGenState) // epoching module genesis epochingGenState := epochingtypes.DefaultGenesis() epochingGenState.Params = genesisParams.EpochingParams - appState[epochingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(epochingGenState) + genesisState[epochingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(epochingGenState) // checkpointing module genesis checkpointingGenState := checkpointingtypes.DefaultGenesis() checkpointingGenState.GenesisKeys = genesisParams.CheckpointingGenKeys - appState[checkpointingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(checkpointingGenState) + genesisState[checkpointingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(checkpointingGenState) // btcstaking module genesis btcstakingGenState := btcstakingtypes.DefaultGenesis() btcstakingGenState.Params = genesisParams.BtcstakingParams - appState[btcstakingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btcstakingGenState) + genesisState[btcstakingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(btcstakingGenState) // finality module genesis finalityGenState := finalitytypes.DefaultGenesis() finalityGenState.Params = genesisParams.FinalityParams - appState[finalitytypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(finalityGenState) + genesisState[finalitytypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(finalityGenState) // staking module genesis - stakingGenState := stakingtypes.GetGenesisStateFromAppState(depCdc, appState) - clientCtx.Codec.MustUnmarshalJSON(appState[stakingtypes.ModuleName], stakingGenState) + stakingGenState := stakingtypes.GetGenesisStateFromAppState(depCdc, genesisState) + clientCtx.Codec.MustUnmarshalJSON(genesisState[stakingtypes.ModuleName], stakingGenState) stakingGenState.Params = genesisParams.StakingParams - appState[stakingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(stakingGenState) + genesisState[stakingtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(stakingGenState) // mint module genesis mintGenState := minttypes.DefaultGenesisState() mintGenState.Params = genesisParams.MintParams - appState[minttypes.ModuleName] = cdc.MustMarshalJSON(mintGenState) + genesisState[minttypes.ModuleName] = cdc.MustMarshalJSON(mintGenState) // distribution module genesis distributionGenState := distributiontypes.DefaultGenesisState() distributionGenState.Params = genesisParams.DistributionParams - appState[distributiontypes.ModuleName] = cdc.MustMarshalJSON(distributionGenState) + genesisState[distributiontypes.ModuleName] = cdc.MustMarshalJSON(distributionGenState) // gov module genesis govGenState := govv1.DefaultGenesisState() govGenState.Params = &genesisParams.GovParams - appState[govtypes.ModuleName] = cdc.MustMarshalJSON(govGenState) + genesisState[govtypes.ModuleName] = cdc.MustMarshalJSON(govGenState) // crisis module genesis crisisGenState := crisistypes.DefaultGenesisState() crisisGenState.ConstantFee = genesisParams.CrisisConstantFee - appState[crisistypes.ModuleName] = cdc.MustMarshalJSON(crisisGenState) + genesisState[crisistypes.ModuleName] = cdc.MustMarshalJSON(crisisGenState) // auth module genesis authGenState := authtypes.DefaultGenesisState() authGenState.Accounts = genesisParams.AuthAccounts - appState[authtypes.ModuleName] = cdc.MustMarshalJSON(authGenState) + genesisState[authtypes.ModuleName] = cdc.MustMarshalJSON(authGenState) // bank module genesis bankGenState := banktypes.DefaultGenesisState() @@ -192,15 +193,15 @@ func PrepareGenesis( for _, bal := range bankGenState.Balances { bankGenState.Supply = bankGenState.Supply.Add(bal.Coins...) } - appState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenState) + genesisState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenState) - appGenStateJSON, err := json.MarshalIndent(appState, "", " ") + appGenStateJSON, err := json.MarshalIndent(genesisState, "", " ") if err != nil { return err } - genDoc.AppState = appGenStateJSON + genesis.AppState = appGenStateJSON return nil } @@ -231,7 +232,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, covenantPk string, slashingAddress string, minSlashingFee int64, - minCommissionRate sdk.Dec, slashingRate sdk.Dec, minPubRand uint64, inflationRateChange float64, + minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -270,20 +271,20 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.MintParams.MintDenom = genParams.NativeCoinMetadatas[0].Base genParams.MintParams.BlocksPerYear = blocksPerYear // This should always work as inflation rate is already a float64 - genParams.MintParams.InflationRateChange = sdk.MustNewDecFromStr(fmt.Sprintf("%f", inflationRateChange)) - genParams.MintParams.InflationMin = sdk.MustNewDecFromStr(fmt.Sprintf("%f", inflationMin)) - genParams.MintParams.InflationMax = sdk.MustNewDecFromStr(fmt.Sprintf("%f", inflationMax)) - genParams.MintParams.GoalBonded = sdk.MustNewDecFromStr(fmt.Sprintf("%f", goalBonded)) + genParams.MintParams.InflationRateChange = sdkmath.LegacyMustNewDecFromStr(fmt.Sprintf("%f", inflationRateChange)) + genParams.MintParams.InflationMin = sdkmath.LegacyMustNewDecFromStr(fmt.Sprintf("%f", inflationMin)) + genParams.MintParams.InflationMax = sdkmath.LegacyMustNewDecFromStr(fmt.Sprintf("%f", inflationMax)) + genParams.MintParams.GoalBonded = sdkmath.LegacyMustNewDecFromStr(fmt.Sprintf("%f", goalBonded)) genParams.GovParams = govv1.DefaultParams() genParams.GovParams.MinDeposit = sdk.NewCoins(sdk.NewCoin( genParams.NativeCoinMetadatas[0].Base, - sdk.NewInt(2_500_000_000), + sdkmath.NewInt(2_500_000_000), )) genParams.CrisisConstantFee = sdk.NewCoin( genParams.NativeCoinMetadatas[0].Base, - sdk.NewInt(500_000_000_000), + sdkmath.NewInt(500_000_000_000), ) genParams.BtccheckpointParams = btccheckpointtypes.DefaultParams() diff --git a/cmd/babylond/cmd/root.go b/cmd/babylond/cmd/root.go index 0c8fe80ba..a87067300 100644 --- a/cmd/babylond/cmd/root.go +++ b/cmd/babylond/cmd/root.go @@ -1,23 +1,23 @@ package cmd import ( + confixcmd "cosmossdk.io/tools/confix/cmd" "errors" - "io" - "os" - "path/filepath" - - rosettaCmd "cosmossdk.io/tools/rosetta/cmd" "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - dbm "github.com/cometbft/cometbft-db" - tmcfg "github.com/cometbft/cometbft/config" - tmcli "github.com/cometbft/cometbft/libs/cli" - tmtypes "github.com/cometbft/cometbft/types" + cmtcfg "github.com/cometbft/cometbft/config" + cmtcli "github.com/cometbft/cometbft/libs/cli" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/types/module" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "io" + "os" - "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/baseapp" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" @@ -25,17 +25,12 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" - "github.com/cosmos/cosmos-sdk/snapshots" - snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/crisis" - "github.com/cosmos/cosmos-sdk/x/genutil" genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" - genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cast" "github.com/spf13/cobra" @@ -47,13 +42,16 @@ import ( // NewRootCmd creates a new root command for babylond. It is called once in the // main function. -func NewRootCmd() (*cobra.Command, params.EncodingConfig) { - encodingConfig := app.GetEncodingConfig() +func NewRootCmd() *cobra.Command { + // we "pre"-instantiate the application for getting the injected/configured encoding configuration + // note, this is not necessary when using app wiring, as depinject can be directly used + tempApp := app.NewTmpBabylonApp() + initClientCtx := client.Context{}. - WithCodec(encodingConfig.Marshaler). - WithInterfaceRegistry(encodingConfig.InterfaceRegistry). - WithTxConfig(encodingConfig.TxConfig). - WithLegacyAmino(encodingConfig.Amino). + WithCodec(tempApp.AppCodec()). + WithInterfaceRegistry(tempApp.InterfaceRegistry()). + WithTxConfig(tempApp.TxConfig()). + WithLegacyAmino(tempApp.LegacyAmino()). WithInput(os.Stdin). WithAccountRetriever(types.AccountRetriever{}). WithHomeDir(app.DefaultNodeHome). @@ -67,6 +65,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { cmd.SetOut(cmd.OutOrStdout()) cmd.SetErr(cmd.ErrOrStderr()) + initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) if err != nil { return err @@ -82,9 +81,9 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { } customAppTemplate, customAppConfig := initAppConfig() - customTMConfig := initTendermintConfig() + customCometConfig := initCometConfig() - err = server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig) + err = server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCometConfig) if err != nil { return err @@ -94,15 +93,25 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { }, } - initRootCmd(rootCmd, encodingConfig) + initRootCmd(rootCmd, tempApp.TxConfig(), tempApp.BasicModuleManager) + + // add keyring to autocli opts + autoCliOpts := tempApp.AutoCliOpts() + initClientCtx, _ = config.ReadFromClientConfig(initClientCtx) + autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) + autoCliOpts.ClientCtx = initClientCtx + + if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { + panic(err) + } - return rootCmd, encodingConfig + return rootCmd } -// initTendermintConfig helps to override default Tendermint Config values. +// initCometConfig helps to override default Comet Config values. // return tmcfg.DefaultConfig if no custom configuration is required for the application. -func initTendermintConfig() *tmcfg.Config { - cfg := tmcfg.DefaultConfig() +func initCometConfig() *cmtcfg.Config { + cfg := cmtcfg.DefaultConfig() // these values put a higher strain on node memory // cfg.P2P.MaxNumInboundPeers = 100 @@ -137,42 +146,38 @@ func initAppConfig() (string, interface{}) { return babylonTemplate, babylonConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, txConfig client.TxEncodingConfig, basicManager module.BasicManager) { cfg := sdk.GetConfig() cfg.Seal() - gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) + gentxModule := basicManager[genutiltypes.ModuleName].(genutil.AppModuleBasic) rootCmd.AddCommand( - genutilcli.InitCmd(app.ModuleBasics, app.DefaultNodeHome), - genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome, gentxModule.GenTxValidator), - genutilcli.MigrateGenesisCmd(), - genutilcli.GenTxCmd(app.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome), - ValidateGenesisCmd(app.ModuleBasics, gentxModule.GenTxValidator), - PrepareGenesisCmd(app.DefaultNodeHome, app.ModuleBasics), + genutilcli.InitCmd(basicManager, app.DefaultNodeHome), + genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome, gentxModule.GenTxValidator, authcodec.NewBech32Codec(params.Bech32PrefixValAddr)), + genutilcli.MigrateGenesisCmd(genutilcli.MigrationMap), + genutilcli.GenTxCmd(basicManager, txConfig, banktypes.GenesisBalancesIterator{}, app.DefaultNodeHome, authcodec.NewBech32Codec(params.Bech32PrefixValAddr)), + ValidateGenesisCmd(basicManager, gentxModule.GenTxValidator), + PrepareGenesisCmd(app.DefaultNodeHome, basicManager), AddGenesisAccountCmd(app.DefaultNodeHome), - tmcli.NewCompletionCmd(rootCmd, true), - TestnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}), + cmtcli.NewCompletionCmd(rootCmd, true), + TestnetCmd(basicManager, banktypes.GenesisBalancesIterator{}), CreateBlsKeyCmd(), GenBlsCmd(), AddGenBlsCmd(gentxModule.GenTxValidator), debug.Cmd(), - config.Cmd(), + confixcmd.ConfigCommand(), ) - a := appCreator{encodingConfig} - server.AddCommands(rootCmd, app.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) + server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, addModuleInitFlags) // add keybase, auxiliary RPC, query, and tx child commands rootCmd.AddCommand( - rpc.StatusCommand(), + server.StatusCommand(), queryCommand(), txCommand(), - keys.Commands(app.DefaultNodeHome), + keys.Commands(), ) - - // add rosetta - rootCmd.AddCommand(rosettaCmd.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) } func addModuleInitFlags(startCmd *cobra.Command) { @@ -194,14 +199,15 @@ func queryCommand() *cobra.Command { } cmd.AddCommand( - authcmd.GetAccountCmd(), + rpc.QueryEventForTxCmd(), rpc.ValidatorCommand(), - rpc.BlockCommand(), + server.QueryBlockCmd(), authcmd.QueryTxsByEventsCmd(), + server.QueryBlocksCmd(), authcmd.QueryTxCmd(), + server.QueryBlockResultsCmd(), ) - app.ModuleBasics.AddQueryCommands(cmd) cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") return cmd @@ -225,60 +231,25 @@ func txCommand() *cobra.Command { authcmd.GetBroadcastCommand(), authcmd.GetEncodeCommand(), authcmd.GetDecodeCommand(), + authcmd.GetSimulateCmd(), ) - app.ModuleBasics.AddTxCommands(cmd) cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID") return cmd } -type appCreator struct { - encCfg params.EncodingConfig -} - // newApp is an appCreator -func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { - var cache sdk.MultiStorePersistentCache - - // TODO migrate following option to using server.DefaultBaseappOptions - if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) { - cache = store.NewCommitKVStoreCacheManager() - } +func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + baseappOptions := server.DefaultBaseappOptions(appOpts) skipUpgradeHeights := make(map[int64]bool) for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { skipUpgradeHeights[int64(h)] = true } - pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts) - if err != nil { - panic(err) - } - homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) - - chainID := cast.ToString(appOpts.Get(flags.FlagChainID)) - if chainID == "" { - // fallback to genesis chain-id - appGenesis, err := tmtypes.GenesisDocFromFile(filepath.Join(homeDir, "config", "genesis.json")) - if err != nil { - panic(err) - } - chainID = appGenesis.ChainID - } - - snapshotDir := filepath.Join(homeDir, "data", "snapshots") - snapshotDB, err := dbm.NewDB("metadata", server.GetAppDBBackend(appOpts), snapshotDir) - - if err != nil { - panic(err) - } - snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) - if err != nil { - panic(err) - } - + encCfg := app.GetEncodingConfig() clientCtx, err := config.ReadFromClientConfig( client.Context{}. WithHomeDir(homeDir). @@ -289,7 +260,7 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a // with already initialized codec. It creates keyring inside, and from cosmos // 0.46.0 keyring requires codec. Without codec, operations performed by // keyring ends with nil pointer exception (`panic`) - WithCodec(a.encCfg.Marshaler), + WithCodec(encCfg.Codec), ) if err != nil { panic(err) @@ -297,16 +268,11 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a // parse the key name that will be used for signing BLS-sig txs from app.toml keyName := bbntypes.ParseKeyNameFromConfig(appOpts) - privSigner, err := app.InitPrivSigner(clientCtx, homeDir, clientCtx.Keyring, keyName, a.encCfg) + privSigner, err := app.InitPrivSigner(clientCtx, homeDir, clientCtx.Keyring, keyName, encCfg) if err != nil { panic(err) } - snapshotOptions := snapshottypes.NewSnapshotOptions( - cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval)), - cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent)), - ) - var wasmOpts []wasmkeeper.Option if cast.ToBool(appOpts.Get("telemetry.enabled")) { wasmOpts = append(wasmOpts, wasmkeeper.WithVMCacheMetrics(prometheus.DefaultRegisterer)) @@ -314,30 +280,17 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a return app.NewBabylonApp( logger, db, traceStore, true, skipUpgradeHeights, - cast.ToString(appOpts.Get(flags.FlagHome)), cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), - a.encCfg, privSigner, appOpts, wasmOpts, - baseapp.SetPruning(pruningOpts), - baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), - baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))), - baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))), - baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), - baseapp.SetInterBlockCache(cache), - baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))), - baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))), - baseapp.SetSnapshot(snapshotStore, snapshotOptions), - baseapp.SetIAVLCacheSize(cast.ToInt(appOpts.Get(server.FlagIAVLCacheSize))), - baseapp.SetIAVLDisableFastNode(cast.ToBool(appOpts.Get(server.FlagDisableIAVLFastNode))), - baseapp.SetChainID(chainID), + baseappOptions..., ) } // appExport creates a new app (optionally at a given height) // and exports state. -func (a appCreator) appExport( +func appExport( logger log.Logger, db dbm.DB, traceStore io.Writer, @@ -354,6 +307,7 @@ func (a appCreator) appExport( return servertypes.ExportedApp{}, errors.New("application home not set") } + encCfg := app.GetEncodingConfig() clientCtx, err := config.ReadFromClientConfig( client.Context{}. WithHomeDir(homePath). @@ -364,7 +318,7 @@ func (a appCreator) appExport( // with already initialized codec. It creates keyring inside, and from cosmos // 0.46.0 keyring requires codec. Without codec, operations performed by // keyring ends with nil pointer exception (`panic`) - WithCodec(a.encCfg.Marshaler), + WithCodec(encCfg.Codec), ) if err != nil { panic(err) @@ -374,18 +328,18 @@ func (a appCreator) appExport( panic(err) } - privSigner, err := app.InitPrivSigner(clientCtx, homePath, kr, "", a.encCfg) + privSigner, err := app.InitPrivSigner(clientCtx, homePath, kr, "", encCfg) if err != nil { panic(err) } if height != -1 { - babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, false, map[int64]bool{}, uint(1), privSigner, appOpts, app.EmptyWasmOpts) if err = babylonApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { - babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, homePath, uint(1), a.encCfg, privSigner, appOpts, app.EmptyWasmOpts) + babylonApp = app.NewBabylonApp(logger, db, traceStore, true, map[int64]bool{}, uint(1), privSigner, appOpts, app.EmptyWasmOpts) } return babylonApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index b172c13c1..b4707b458 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -1,12 +1,13 @@ package cmd -// DONTCOVER - import ( "bufio" + "cosmossdk.io/math" "encoding/json" "errors" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "net" "os" "path/filepath" @@ -24,9 +25,8 @@ import ( checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" - "github.com/cometbft/cometbft/types" tmtime "github.com/cometbft/cometbft/types/time" "github.com/spf13/cobra" @@ -107,7 +107,8 @@ Example: return InitTestnet( clientCtx, cmd, config, mbm, genBalIterator, outputDir, genesisCliArgs.ChainID, minGasPrices, nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators, - btcNetwork, additionalAccount, timeBetweenBlocks, genesisParams, + btcNetwork, additionalAccount, timeBetweenBlocks, + clientCtx.TxConfig.SigningContext().ValidatorAddressCodec(), genesisParams, ) }, } @@ -130,11 +131,11 @@ Example: const nodeDirPerm = 0755 -// Initialize the testnet +// InitTestnet initialize the testnet func InitTestnet( clientCtx client.Context, cmd *cobra.Command, - nodeConfig *tmconfig.Config, + nodeConfig *cmtconfig.Config, mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator, outputDir, @@ -149,6 +150,7 @@ func InitTestnet( btcNetwork string, additionalAccount bool, timeBetweenBlocks uint64, + valAddrCodec runtime.ValidatorAddressCodec, genesisParams GenesisParams, ) error { @@ -167,8 +169,8 @@ func InitTestnet( babylonConfig.BtcConfig.Network = btcNetwork // Explorer related config. Allow CORS connections. babylonConfig.API.EnableUnsafeCORS = true + babylonConfig.GRPC.Enable = true babylonConfig.GRPC.Address = "0.0.0.0:9090" - babylonConfig.GRPCWeb.Address = "0.0.0.0:9091" var ( genAccounts []authtypes.GenesisAccount @@ -261,7 +263,7 @@ func InitTestnet( genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) valTokens := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction) - valPubkey, err := cryptocodec.FromTmPubKeyInterface(valKeys[i].ValPubkey) + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys[i].ValPubkey) if err != nil { return err } @@ -275,13 +277,17 @@ func InitTestnet( ValPubkey: valPubkey.(*ed25519.PubKey), } genKeys = append(genKeys, genKey) + valStr, err := valAddrCodec.BytesToString(sdk.ValAddress(addr)) + if err != nil { + return err + } createValMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), + valStr, valPubkey, sdk.NewCoin(genesisParams.NativeCoinMetadatas[0].Base, valTokens), stakingtypes.NewDescription(nodeDirName, "", "", "", ""), - stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()), - sdk.OneInt(), + stakingtypes.NewCommissionRates(math.LegacyOneDec(), math.LegacyOneDec(), math.LegacyOneDec()), + math.OneInt(), ) if err != nil { return err @@ -301,7 +307,7 @@ func InitTestnet( WithKeybase(kb). WithTxConfig(clientCtx.TxConfig) - if err = tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil { + if err = tx.Sign(cmd.Context(), txFactory, nodeDirName, txBuilder, true); err != nil { return err } @@ -357,8 +363,8 @@ func InitTestnet( } coins := sdk.Coins{ - sdk.NewCoin("testtoken", sdk.NewInt(1000000000)), - sdk.NewCoin(genesisParams.NativeCoinMetadatas[0].Base, sdk.NewInt(1000000000000)), + sdk.NewCoin("testtoken", math.NewInt(1000000000)), + sdk.NewCoin(genesisParams.NativeCoinMetadatas[0].Base, math.NewInt(1000000000000)), } genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()}) @@ -405,20 +411,20 @@ func initGenFiles( // set the bls keys for the checkpointing module genesisParams.CheckpointingGenKeys = genKeys - genDoc := &types.GenesisDoc{} + genesis := &genutiltypes.AppGenesis{} - err = PrepareGenesis(clientCtx, appGenState, genDoc, genesisParams, chainID) + err = PrepareGenesis(clientCtx, appGenState, genesis, genesisParams, chainID) if err != nil { return err } // set initial validators to nil, they will be added by collectGenFiles based on // genesis tranascations - genDoc.Validators = nil + genesis.Consensus.Validators = nil // generate empty genesis files for each validator and save for i := 0; i < numValidators; i++ { - if err := genDoc.SaveAs(genFiles[i]); err != nil { + if err := genesis.SaveAs(genFiles[i]); err != nil { return err } } @@ -426,7 +432,7 @@ func initGenFiles( } func collectGenFiles( - clientCtx client.Context, nodeConfig *tmconfig.Config, chainID string, + clientCtx client.Context, nodeConfig *cmtconfig.Config, chainID string, nodeIDs []string, genKeys []*checkpointingtypes.GenesisKey, numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string, genBalIterator banktypes.GenesisBalancesIterator, ) error { @@ -445,7 +451,7 @@ func collectGenFiles( nodeID, valPubKey := nodeIDs[i], genKeys[i].ValPubkey initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey) - genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile()) + _, genesis, err := genutiltypes.GenesisStateFromGenFile(nodeConfig.GenesisFile()) if err != nil { return err } @@ -453,10 +459,12 @@ func collectGenFiles( nodeAppState, err := genutil.GenAppStateFromConfig( clientCtx.Codec, clientCtx.TxConfig, - nodeConfig, initCfg, - *genDoc, + nodeConfig, + initCfg, + genesis, genBalIterator, genutiltypes.DefaultMessageValidator, + authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), ) if err != nil { return err @@ -470,14 +478,18 @@ func collectGenFiles( // overwrite each validator's genesis file to have a canonical genesis time genFile := nodeConfig.GenesisFile() - newGendoc := types.GenesisDoc{ - GenesisTime: genTime, - AppState: appState, - ConsensusParams: genDoc.ConsensusParams, - ChainID: genDoc.ChainID, + newGenesis := genutiltypes.AppGenesis{ + AppName: genesis.AppName, + AppVersion: genesis.AppVersion, + GenesisTime: genTime, + ChainID: genesis.ChainID, + InitialHeight: genesis.InitialHeight, + AppHash: genesis.AppHash, + AppState: appState, + Consensus: genesis.Consensus, } - if err := genutil.ExportGenesisFile(&newGendoc, genFile); err != nil { + if err := genutil.ExportGenesisFile(&newGenesis, genFile); err != nil { return err } } diff --git a/cmd/babylond/cmd/testnet_test.go b/cmd/babylond/cmd/testnet_test.go index 82215080b..b0e352eaf 100644 --- a/cmd/babylond/cmd/testnet_test.go +++ b/cmd/babylond/cmd/testnet_test.go @@ -3,9 +3,10 @@ package cmd import ( "context" "fmt" + dbm "github.com/cosmos/cosmos-db" "testing" - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" @@ -20,24 +21,34 @@ import ( func Test_TestnetCmd(t *testing.T) { home := t.TempDir() - encodingConfig := app.GetEncodingConfig() logger := log.NewNopLogger() - cfg, err := genutiltest.CreateDefaultTendermintConfig(home) + cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - err = genutiltest.ExecInitCmd(app.ModuleBasics, home, encodingConfig.Marshaler) + signer, err := app.SetupPrivSigner() + require.NoError(t, err) + bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + Logger: logger, + DB: dbm.NewMemDB(), + InvCheckPeriod: 0, + SkipUpgradeHeights: map[int64]bool{}, + AppOpts: app.EmptyAppOptions{}, + }) + err = genutiltest.ExecInitCmd(bbn.BasicModuleManager, home, bbn.AppCodec()) require.NoError(t, err) serverCtx := server.NewContext(viper.New(), cfg, logger) clientCtx := client.Context{}. - WithCodec(encodingConfig.Marshaler). - WithHomeDir(home). - WithTxConfig(encodingConfig.TxConfig) + WithCodec(bbn.AppCodec()). + WithInterfaceRegistry(bbn.InterfaceRegistry()). + WithLegacyAmino(bbn.LegacyAmino()). + WithTxConfig(bbn.TxConfig()). + WithHomeDir(home) ctx := context.Background() ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) - cmd := TestnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}) + cmd := TestnetCmd(bbn.BasicModuleManager, banktypes.GenesisBalancesIterator{}) cmd.SetArgs([]string{fmt.Sprintf("--%s=test", flags.FlagKeyringBackend), fmt.Sprintf("--output-dir=%s", home)}) err = cmd.ExecuteContext(ctx) require.NoError(t, err) @@ -46,6 +57,6 @@ func Test_TestnetCmd(t *testing.T) { appState, _, err := genutiltypes.GenesisStateFromGenFile(genFile) require.NoError(t, err) - bankGenState := banktypes.GetGenesisStateFromAppState(encodingConfig.Marshaler, appState) + bankGenState := banktypes.GetGenesisStateFromAppState(bbn.AppCodec(), appState) require.NotEmpty(t, bankGenState.Supply.String()) } diff --git a/cmd/babylond/cmd/validate_genesis_test.go b/cmd/babylond/cmd/validate_genesis_test.go index 5406591d9..75236771c 100644 --- a/cmd/babylond/cmd/validate_genesis_test.go +++ b/cmd/babylond/cmd/validate_genesis_test.go @@ -6,17 +6,16 @@ import ( "fmt" "testing" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/spf13/viper" "github.com/stretchr/testify/require" + "cosmossdk.io/log" "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/cmd/babylond/cmd" - "github.com/babylonchain/babylon/x/checkpointing/types" - - tmjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/libs/log" - tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" @@ -27,58 +26,55 @@ import ( func TestCheckCorrespondence(t *testing.T) { homePath := t.TempDir() - encodingCft := app.GetEncodingConfig() - clientCtx := client.Context{}.WithCodec(encodingCft.Marshaler).WithTxConfig(encodingCft.TxConfig) - // generate valid genesis doc - validGenState, genDoc := generateTestGenesisState(homePath, 2) - validGenDocJSON, err := tmjson.MarshalIndent(genDoc, "", " ") - require.NoError(t, err) + bbn, appState := generateTestGenesisState(t, homePath, 2) + clientCtx := client.Context{}.WithCodec(bbn.AppCodec()).WithTxConfig(bbn.TxConfig()) - // generate mismatched genesis doc by deleting one item from gentx and genKeys in different positions - gentxs := genutiltypes.GetGenesisStateFromAppState(clientCtx.Codec, validGenState) - genKeys := types.GetGenesisStateFromAppState(clientCtx.Codec, validGenState) - gentxs.GenTxs = gentxs.GenTxs[:1] - genKeys.GenesisKeys = genKeys.GenesisKeys[1:] - genTxsBz, err := clientCtx.Codec.MarshalJSON(gentxs) + // Copy the appState into a new struct + bz, err := json.Marshal(appState) require.NoError(t, err) - genKeysBz, err := clientCtx.Codec.MarshalJSON(&genKeys) + var mismatchedAppState map[string]json.RawMessage + err = json.Unmarshal(bz, &mismatchedAppState) require.NoError(t, err) - validGenState[genutiltypes.ModuleName] = genTxsBz - validGenState[types.ModuleName] = genKeysBz - misMatchedGenStateBz, err := json.Marshal(validGenState) + + genutilGenesisState := genutiltypes.GetGenesisStateFromAppState(clientCtx.Codec, mismatchedAppState) + checkpointingGenesisState := checkpointingtypes.GetGenesisStateFromAppState(clientCtx.Codec, mismatchedAppState) + + // generate mismatched genesis doc by deleting one item from gentx and genKeys in different positions + genutilGenesisState.GenTxs = genutilGenesisState.GenTxs[:1] + checkpointingGenesisState.GenesisKeys = checkpointingGenesisState.GenesisKeys[1:] + + // Update the for the genutil module with the invalid data + genTxsBz, err := clientCtx.Codec.MarshalJSON(genutilGenesisState) require.NoError(t, err) - genDoc.AppState = misMatchedGenStateBz - misMatchedGenDocJSON, err := tmjson.MarshalIndent(genDoc, "", " ") + mismatchedAppState[genutiltypes.ModuleName] = genTxsBz + + // Update the for the checkpointing module with the invalid data + genKeysBz, err := clientCtx.Codec.MarshalJSON(&checkpointingGenesisState) require.NoError(t, err) + mismatchedAppState[checkpointingtypes.ModuleName] = genKeysBz testCases := []struct { - name string - genesis []byte - expErr bool + name string + appState map[string]json.RawMessage + expErr bool }{ { "valid genesis gentx and BLS key pair", - validGenDocJSON, + appState, false, }, { "mismatched genesis state", - misMatchedGenDocJSON, + mismatchedAppState, true, }, } - gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) + gentxModule := bbn.BasicModuleManager[genutiltypes.ModuleName].(genutil.AppModuleBasic) for _, tc := range testCases { - genDoc, err := tmtypes.GenesisDocFromJSON(tc.genesis) - require.NoError(t, err) - require.NotEmpty(t, genDoc) - genesisState, err := genutiltypes.GenesisStateFromGenDoc(*genDoc) - require.NoError(t, err) - require.NotEmpty(t, genesisState) - err = cmd.CheckCorrespondence(clientCtx, genesisState, gentxModule.GenTxValidator) + err = cmd.CheckCorrespondence(clientCtx, tc.appState, gentxModule.GenTxValidator) if tc.expErr { require.Error(t, err) } else { @@ -87,23 +83,32 @@ func TestCheckCorrespondence(t *testing.T) { } } -func generateTestGenesisState(home string, n int) (map[string]json.RawMessage, *tmtypes.GenesisDoc) { - encodingConfig := app.GetEncodingConfig() +func generateTestGenesisState(t *testing.T, home string, n int) (*app.BabylonApp, map[string]json.RawMessage) { logger := log.NewNopLogger() - cfg, _ := genutiltest.CreateDefaultTendermintConfig(home) + cfg, _ := genutiltest.CreateDefaultCometConfig(home) + + signer, err := app.SetupPrivSigner() + require.NoError(t, err) + bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ + Logger: logger, + DB: dbm.NewMemDB(), + InvCheckPeriod: 0, + SkipUpgradeHeights: map[int64]bool{}, + AppOpts: app.EmptyAppOptions{}, + }) - _ = genutiltest.ExecInitCmd(app.ModuleBasics, home, encodingConfig.Marshaler) + _ = genutiltest.ExecInitCmd(bbn.BasicModuleManager, home, bbn.AppCodec()) serverCtx := server.NewContext(viper.New(), cfg, logger) clientCtx := client.Context{}. - WithCodec(encodingConfig.Marshaler). + WithCodec(bbn.AppCodec()). WithHomeDir(home). - WithTxConfig(encodingConfig.TxConfig) + WithTxConfig(bbn.TxConfig()) ctx := context.Background() ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) - testnetCmd := cmd.TestnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}) + testnetCmd := cmd.TestnetCmd(bbn.BasicModuleManager, banktypes.GenesisBalancesIterator{}) testnetCmd.SetArgs([]string{ fmt.Sprintf("--%s=test", flags.FlagKeyringBackend), fmt.Sprintf("--v=%v", n), @@ -112,6 +117,6 @@ func generateTestGenesisState(home string, n int) (map[string]json.RawMessage, * _ = testnetCmd.ExecuteContext(ctx) genFile := cfg.GenesisFile() - appState, gendoc, _ := genutiltypes.GenesisStateFromGenFile(genFile) - return appState, gendoc + genState, _, _ := genutiltypes.GenesisStateFromGenFile(genFile) + return bbn, genState } diff --git a/cmd/babylond/main.go b/cmd/babylond/main.go index e86aed55f..338f0cb45 100644 --- a/cmd/babylond/main.go +++ b/cmd/babylond/main.go @@ -1,11 +1,11 @@ package main import ( + "cosmossdk.io/log" "os" "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/cmd/babylond/cmd" - "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" "github.com/babylonchain/babylon/app/params" @@ -13,15 +13,10 @@ import ( func main() { params.SetAddressPrefixes() - rootCmd, _ := cmd.NewRootCmd() + rootCmd := cmd.NewRootCmd() if err := svrcmd.Execute(rootCmd, app.BabylonAppEnvPrefix, app.DefaultNodeHome); err != nil { - switch e := err.(type) { - case server.ErrorCode: - os.Exit(e.Code) - - default: - os.Exit(1) - } + log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) + os.Exit(1) } } diff --git a/contrib/images/babylond/Dockerfile b/contrib/images/babylond/Dockerfile index 71a148639..ef590d207 100644 --- a/contrib/images/babylond/Dockerfile +++ b/contrib/images/babylond/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine AS build-env +FROM golang:1.21-alpine AS build-env # Customize to your build env diff --git a/go.mod b/go.mod index 17fb9daf6..63d5dbaef 100644 --- a/go.mod +++ b/go.mod @@ -1,66 +1,74 @@ -go 1.20 +go 1.21 module github.com/babylonchain/babylon require ( - github.com/CosmWasm/wasmd v0.44.0 + github.com/CosmWasm/wasmd v0.50.0-rc.2 github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 - github.com/cometbft/cometbft v0.37.2 - github.com/cometbft/cometbft-db v0.8.0 - github.com/cosmos/cosmos-sdk v0.47.5 - github.com/golang/protobuf v1.5.3 - github.com/gorilla/mux v1.8.0 + github.com/cometbft/cometbft v0.38.0 + github.com/cometbft/cometbft-db v0.8.0 // indirect + github.com/cosmos/cosmos-sdk v0.50.1 + github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/pkg/errors v0.9.1 - github.com/rakyll/statik v0.1.7 // indirect github.com/spf13/cast v1.5.1 - github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.16.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 github.com/supranational/blst v0.3.8 - google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect - google.golang.org/grpc v1.56.2 + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - cosmossdk.io/api v0.3.1 + cosmossdk.io/api v0.7.2 + cosmossdk.io/client/v2 v2.0.0-beta.1 + cosmossdk.io/core v0.11.0 cosmossdk.io/errors v1.0.0 - cosmossdk.io/math v1.1.2 - cosmossdk.io/tools/rosetta v0.2.1 + cosmossdk.io/log v1.2.1 + cosmossdk.io/math v1.2.0 + cosmossdk.io/store v1.0.0 + cosmossdk.io/tools/confix v0.1.0 + cosmossdk.io/x/circuit v0.1.0 + cosmossdk.io/x/evidence v0.1.0 + cosmossdk.io/x/feegrant v0.1.0 + cosmossdk.io/x/tx v0.12.0 + cosmossdk.io/x/upgrade v0.1.0 github.com/CosmWasm/wasmvm v1.5.0 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.3 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 - github.com/cosmos/cosmos-proto v1.0.0-beta.2 - github.com/cosmos/gogoproto v1.4.10 - github.com/cosmos/ibc-go/v7 v7.3.1 + github.com/cosmos/cosmos-db v1.0.0 + github.com/cosmos/cosmos-proto v1.0.0-beta.3 + github.com/cosmos/gogoproto v1.4.11 + github.com/cosmos/ibc-go/modules/capability v1.0.0 + github.com/cosmos/ibc-go/v8 v8.0.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.5.3 + github.com/hashicorp/go-metrics v0.5.1 github.com/jinzhu/copier v0.3.5 github.com/ory/dockertest/v3 v3.9.1 github.com/vulpine-io/io-test v1.0.0 - google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 - google.golang.org/protobuf v1.31.0 + google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a ) require ( filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect - github.com/armon/go-metrics v0.4.1 github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/go-bip39 v1.0.0 - github.com/cosmos/iavl v0.20.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cosmos/iavl v1.0.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect @@ -75,144 +83,157 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/gtank/merlin v0.1.1 // indirect - github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect - github.com/klauspost/compress v1.16.3 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rs/cors v1.8.3 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/pflag v1.0.5 - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/zondax/hid v0.9.1 // indirect + github.com/zondax/hid v0.9.2 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect nhooyr.io/websocket v1.8.6 // indirect ) require ( - cloud.google.com/go v0.110.4 // indirect - cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.0 // indirect + cloud.google.com/go/iam v1.1.3 // indirect cloud.google.com/go/storage v1.30.1 // indirect - cosmossdk.io/core v0.5.1 // indirect + cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/log v1.2.1 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + cosmossdk.io/x/nft v0.1.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aead/siphash v1.0.1 // indirect - github.com/aws/aws-sdk-go v1.44.203 // indirect + github.com/aws/aws-sdk-go v1.44.224 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bits-and-blooms/bitset v1.8.0 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/cockroachdb/errors v1.10.0 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb // indirect github.com/cockroachdb/redact v1.1.5 // indirect - github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect - github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect - github.com/creachadair/taskgroup v0.4.2 // indirect + github.com/creachadair/atomicfile v0.3.1 // indirect + github.com/creachadair/tomledit v0.0.24 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/docker/cli v20.10.21+incompatible // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v20.10.24+incompatible // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/cli v23.0.1+incompatible // indirect + github.com/docker/docker v23.0.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.2 // indirect - github.com/getsentry/sentry-go v0.23.0 // indirect + github.com/emicklei/dot v1.6.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/getsentry/sentry-go v0.25.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.1 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/skiplist v1.2.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/linxGnu/grocksdb v1.7.16 // indirect + github.com/linxGnu/grocksdb v1.8.4 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/runc v1.1.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/rs/zerolog v1.30.0 // indirect + github.com/rs/zerolog v1.31.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/tidwall/btree v1.6.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/tidwall/btree v1.7.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/zondax/ledger-go v0.14.1 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/tools v0.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/tools v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.143.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - pgregory.net/rapid v0.5.5 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + gotest.tools/v3 v3.5.1 // indirect + pgregory.net/rapid v1.1.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) replace ( diff --git a/go.sum b/go.sum index 50b74f818..bab2f3afa 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -70,8 +70,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -111,8 +111,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= -cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -187,34 +187,52 @@ cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xX cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cosmossdk.io/api v0.3.1 h1:NNiOclKRR0AOlO4KIqeaG6PS6kswOMhHD0ir0SscNXE= -cosmossdk.io/api v0.3.1/go.mod h1:DfHfMkiNA2Uhy8fj0JJlOCYOBp4eWUUJ1te5zBGNyIw= -cosmossdk.io/core v0.5.1 h1:vQVtFrIYOQJDV3f7rw4pjjVqc1id4+mE0L9hHP66pyI= -cosmossdk.io/core v0.5.1/go.mod h1:KZtwHCLjcFuo0nmDc24Xy6CRNEL9Vl/MeimQ2aC7NLE= +cosmossdk.io/api v0.7.2 h1:BO3i5fvKMKvfaUiMkCznxViuBEfyWA/k6w2eAF6q1C4= +cosmossdk.io/api v0.7.2/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38= +cosmossdk.io/client/v2 v2.0.0-beta.1 h1:XkHh1lhrLYIT9zKl7cIOXUXg2hdhtjTPBUfqERNA1/Q= +cosmossdk.io/client/v2 v2.0.0-beta.1/go.mod h1:JEUSu9moNZQ4kU3ir1DKD5eU4bllmAexrGWjmb9k8qU= +cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s= +cosmossdk.io/collections v0.4.0/go.mod h1:oa5lUING2dP+gdDquow+QjlF45eL1t4TJDypgGd+tv0= +cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo= +cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w= cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc= cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU= cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4= -cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= -cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= -cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= -cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= +cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= +cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/store v1.0.0 h1:6tnPgTpTSIskaTmw/4s5C9FARdgFflycIc9OX8i1tOI= +cosmossdk.io/store v1.0.0/go.mod h1:ABMprwjvx6IpMp8l06TwuMrj6694/QP5NIW+X6jaTYc= +cosmossdk.io/tools/confix v0.1.0 h1:2OOZTtQsDT5e7P3FM5xqM0bPfluAxZlAwxqaDmYBE+E= +cosmossdk.io/tools/confix v0.1.0/go.mod h1:TdXKVYs4gEayav5wM+JHT+kTU2J7fozFNqoVaN+8CdY= +cosmossdk.io/x/circuit v0.1.0 h1:IAej8aRYeuOMritczqTlljbUVHq1E85CpBqaCTwYgXs= +cosmossdk.io/x/circuit v0.1.0/go.mod h1:YDzblVE8+E+urPYQq5kq5foRY/IzhXovSYXb4nwd39w= +cosmossdk.io/x/evidence v0.1.0 h1:J6OEyDl1rbykksdGynzPKG5R/zm6TacwW2fbLTW4nCk= +cosmossdk.io/x/evidence v0.1.0/go.mod h1:hTaiiXsoiJ3InMz1uptgF0BnGqROllAN8mwisOMMsfw= +cosmossdk.io/x/feegrant v0.1.0 h1:c7s3oAq/8/UO0EiN1H5BIjwVntujVTkYs35YPvvrdQk= +cosmossdk.io/x/feegrant v0.1.0/go.mod h1:4r+FsViJRpcZif/yhTn+E0E6OFfg4n0Lx+6cCtnZElU= +cosmossdk.io/x/nft v0.1.0 h1:VhcsFiEK33ODN27kxKLa0r/CeFd8laBfbDBwYqCyYCM= +cosmossdk.io/x/nft v0.1.0/go.mod h1:ec4j4QAO4mJZ+45jeYRnW7awLHby1JZANqe1hNZ4S3g= +cosmossdk.io/x/tx v0.12.0 h1:Ry2btjQdrfrje9qZ3iZeZSmDArjgxUJMMcLMrX4wj5U= +cosmossdk.io/x/tx v0.12.0/go.mod h1:qTth2coAGkwCwOCjqQ8EAQg+9udXNRzcnSbMgGKGEI0= +cosmossdk.io/x/upgrade v0.1.0 h1:z1ZZG4UL9ICTNbJDYZ6jOnF9GdEK9wyoEFi4BUScHXE= +cosmossdk.io/x/upgrade v0.1.0/go.mod h1:/6jjNGbiPCNtmA1N+rBtP601sr0g4ZXuj3yC6ClPCGY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmd v0.44.0 h1:2sbcoCAvfjCs1O0SWt53xULKjkV06dbSFthEViIC6Zg= -github.com/CosmWasm/wasmd v0.44.0/go.mod h1:tDyYN050qUcdd7LOxGeo2e185sEShyO3nJGl2Cf59+k= +github.com/CosmWasm/wasmd v0.50.0-rc.2 h1:CK7bRrAxYo8xooGhAwCRWyJqog+/M/7G1/p1BQVDNNU= +github.com/CosmWasm/wasmd v0.50.0-rc.2/go.mod h1:KtrZmXmh/V1ZmQ2/3dU6VRuZ09moRDpRqTL3L/mnPIE= github.com/CosmWasm/wasmvm v1.5.0 h1:3hKeT9SfwfLhxTGKH3vXaKFzBz1yuvP8SlfwfQXbQfw= github.com/CosmWasm/wasmvm v1.5.0/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= @@ -227,6 +245,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -241,16 +260,15 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.203 h1:pcsP805b9acL3wUqa4JR2vg1k2wnItkDYNvfmcy6F+U= -github.com/aws/aws-sdk-go v1.44.203/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ= +github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +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= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -260,6 +278,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= +github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI= github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= @@ -289,7 +309,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= +github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -330,17 +351,21 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/errors v1.10.0 h1:lfxS8zZz1+OjtV4MtNWgboi/W5tyLEB6VQZBXN+0VUU= -github.com/cockroachdb/errors v1.10.0/go.mod h1:lknhIsEVQ9Ss/qKDBQS/UqFSvPQjOwNq2qyKAxtHRqE= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb h1:6Po+YYKT5B5ZXN0wd2rwFBaebM0LufPf8p4zxOd48Kg= +github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb/go.mod h1:acMRUGd/BK8AUmQNK3spUCCGzFLZU2bSST3NMXSq2Kc= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA= -github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c= -github.com/cometbft/cometbft v0.37.2 h1:XB0yyHGT0lwmJlFmM4+rsRnczPlHoAKFX6K8Zgc2/Jc= -github.com/cometbft/cometbft v0.37.2/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs= +github.com/cometbft/cometbft v0.38.0 h1:ogKnpiPX7gxCvqTEF4ly25/wAxUqf181t30P3vqdpdc= +github.com/cometbft/cometbft v0.38.0/go.mod h1:5Jz0Z8YsHSf0ZaAqGvi/ifioSdVFPtEGrm8Y9T/993k= github.com/cometbft/cometbft-db v0.8.0 h1:vUMDaH3ApkX8m0KZvOFFy9b5DZHBAjsnEuo9AKVZpjo= github.com/cometbft/cometbft-db v0.8.0/go.mod h1:6ASCP4pfhmrCBpfk01/9E1SI29nD3HfVHrY4PG8x5c0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -355,48 +380,51 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= -github.com/cosmos/cosmos-proto v1.0.0-beta.2 h1:X3OKvWgK9Gsejo0F1qs5l8Qn6xJV/AzgIWR2wZ8Nua8= -github.com/cosmos/cosmos-proto v1.0.0-beta.2/go.mod h1:+XRCLJ14pr5HFEHIUcn51IKXD1Fy3rkEQqt4WqmN4V0= -github.com/cosmos/cosmos-sdk v0.47.5 h1:n1+WjP/VM/gAEOx3TqU2/Ny734rj/MX1kpUnn7zVJP8= -github.com/cosmos/cosmos-sdk v0.47.5/go.mod h1:EHwCeN9IXonsjKcjpS12MqeStdZvIdxt3VYXhus3G3c= -github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k= -github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30= -github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/cosmos-db v1.0.0 h1:EVcQZ+qYag7W6uorBKFPvX6gRjw6Uq2hIh4hCWjuQ0E= +github.com/cosmos/cosmos-db v1.0.0/go.mod h1:iBvi1TtqaedwLdcrZVYRSSCb6eSy61NLj4UNmdIgs0U= +github.com/cosmos/cosmos-proto v1.0.0-beta.3 h1:VitvZ1lPORTVxkmF2fAp3IiA61xVwArQYKXTdEcpW6o= +github.com/cosmos/cosmos-proto v1.0.0-beta.3/go.mod h1:t8IASdLaAq+bbHbjq4p960BvcTqtwuAxid3b/2rOD6I= +github.com/cosmos/cosmos-sdk v0.50.1 h1:2SYwAYqd7ZwtrWxu/J8PwbQV/cDcu90bCr/a78g3lVw= +github.com/cosmos/cosmos-sdk v0.50.1/go.mod h1:fsLSPGstCwn6MMsFDMAQWGJj8E4sYsN9Gnu1bGE5imA= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= -github.com/cosmos/iavl v0.20.1 h1:rM1kqeG3/HBT85vsZdoSNsehciqUQPWrR4BYmqE2+zg= -github.com/cosmos/iavl v0.20.1/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= -github.com/cosmos/ibc-go/v7 v7.3.1 h1:bil1IjnHdyWDASFYKfwdRiNtFP6WK3osW7QFEAgU4I8= -github.com/cosmos/ibc-go/v7 v7.3.1/go.mod h1:wvx4pPBofe5ZdMNV3OFRxSI4auEP5Qfqf8JXLLNV04g= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= +github.com/cosmos/iavl v1.0.0 h1:bw6t0Mv/mVCJvlMTOPHWLs5uUE3BRBfVWCRelOzl+so= +github.com/cosmos/iavl v1.0.0/go.mod h1:CmTGqMnRnucjxbjduneZXT+0vPgNElYvdefjX2q9tYc= +github.com/cosmos/ibc-go/modules/capability v1.0.0 h1:r/l++byFtn7jHYa09zlAdSeevo8ci1mVZNO9+V0xsLE= +github.com/cosmos/ibc-go/modules/capability v1.0.0/go.mod h1:D81ZxzjZAe0ZO5ambnvn1qedsFQ8lOwtqicG6liLBco= +github.com/cosmos/ibc-go/v8 v8.0.0 h1:QKipnr/NGwc+9L7NZipURvmSIu+nw9jOIWTJuDBqOhg= +github.com/cosmos/ibc-go/v8 v8.0.0/go.mod h1:C6IiJom0F3cIQCD5fKwVPDrDK9j/xTu563AWuOmXois= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8= -github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= -github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= -github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM= -github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFgWl/ENIznEoYQI4= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= -github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q= +github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU= +github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ= +github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= @@ -415,12 +443,12 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= -github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= +github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -437,6 +465,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.0 h1:vUzuoVE8ipzS7QkES4UfxdpCwdU2U97m2Pb2tQCoYRY= +github.com/emicklei/dot v1.6.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -449,26 +479,31 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= -github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= -github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -477,6 +512,7 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -487,17 +523,23 @@ github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -515,8 +557,8 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -575,8 +617,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -587,6 +630,7 @@ github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -605,19 +649,20 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -627,38 +672,33 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= -github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= -github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -668,11 +708,17 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.1 h1:rfPwUqFU6uZXNvGl4hzjY8LEBsqFVU4si1H9/Hqck/U= +github.com/hashicorp/go-metrics v0.5.1/go.mod h1:KEjodfebIOuBYSAe/bHTm+HChmKSxAOXPBieMLYozDE= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= +github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= @@ -680,22 +726,25 @@ github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoD github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -704,6 +753,8 @@ github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0Jr github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -716,7 +767,8 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= +github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -735,6 +787,7 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -753,8 +806,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= -github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -769,14 +822,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linxGnu/grocksdb v1.7.16 h1:Q2co1xrpdkr5Hx3Fp+f+f7fRGhQFQhvi/+226dtLmA8= -github.com/linxGnu/grocksdb v1.7.16/go.mod h1:JkS7pl5qWpGpuVb3bPqTz8nC12X3YtPZT+Xq7+QfQo4= +github.com/linxGnu/grocksdb v1.8.4 h1:ZMsBpPpJNtRLHiKKp0mI7gW+NT4s7UgfD5xHxx1jVRo= +github.com/linxGnu/grocksdb v1.8.4/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -784,6 +838,7 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -792,17 +847,15 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= -github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -819,14 +872,15 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/term v0.0.0-20221120202655-abb19827d345 h1:J9c53/kxIH+2nTKBEfZYFMlhghtHpIHSXpm5VRGHSnU= -github.com/moby/term v0.0.0-20221120202655-abb19827d345/go.mod h1:15ce4BGCFxt7I5NQKT+HV0yEDxmf6fSysfEDiVo3zFM= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= @@ -844,19 +898,26 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -875,6 +936,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -883,15 +945,16 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= -github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -899,8 +962,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -908,34 +972,32 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= -github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -949,12 +1011,16 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= -github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -974,29 +1040,29 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1013,13 +1079,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.8 h1:glwLF4oBRSJOTr05lRBgNwGQST0ndP2wg29fSeTRKCY= github.com/supranational/blst v0.3.8/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1027,15 +1093,16 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= -github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -1060,10 +1127,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= -github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= -github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= @@ -1083,11 +1150,17 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1097,15 +1170,13 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1117,8 +1188,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= -golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1145,8 +1216,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1209,8 +1280,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1236,8 +1307,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1252,7 +1323,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1359,13 +1431,14 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1375,10 +1448,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1404,6 +1476,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1445,8 +1518,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1505,8 +1578,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA= +google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1624,12 +1697,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= -google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= -google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk= -google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1671,8 +1744,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1697,6 +1770,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1723,8 +1797,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1735,12 +1809,12 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= -pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/privval/file.go b/privval/file.go index 05183c218..7a0eeb981 100644 --- a/privval/file.go +++ b/privval/file.go @@ -187,7 +187,7 @@ func (pv *WrappedFilePV) ExportGenBls(filePath string) (outputFileName string, e return outputFileName, err } - pubkey, err := codec.FromTmPubKeyInterface(validatorKey.ValPubkey) + pubkey, err := codec.FromCmtPubKeyInterface(validatorKey.ValPubkey) if err != nil { return outputFileName, err } diff --git a/proto/babylon/btccheckpoint/v1/tx.proto b/proto/babylon/btccheckpoint/v1/tx.proto index d0d12b363..38fb5a173 100644 --- a/proto/babylon/btccheckpoint/v1/tx.proto +++ b/proto/babylon/btccheckpoint/v1/tx.proto @@ -11,6 +11,8 @@ option go_package = "github.com/babylonchain/babylon/x/btccheckpoint/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // InsertBTCSpvProof tries to insert a new checkpoint into the store. rpc InsertBTCSpvProof(MsgInsertBTCSpvProof) returns (MsgInsertBTCSpvProofResponse); @@ -22,6 +24,8 @@ service Msg { // MsgInsertBTCSpvProof defines resquest to insert a new checkpoint into the // store message MsgInsertBTCSpvProof { + option (cosmos.msg.v1.signer) = "submitter"; + string submitter = 1; repeated babylon.btccheckpoint.v1.BTCSpvProof proofs = 2; } diff --git a/proto/babylon/btclightclient/v1/btclightclient.proto b/proto/babylon/btclightclient/v1/btclightclient.proto index 6c1c400fb..f5300706c 100644 --- a/proto/babylon/btclightclient/v1/btclightclient.proto +++ b/proto/babylon/btclightclient/v1/btclightclient.proto @@ -22,5 +22,5 @@ message BTCHeaderInfo { "github.com/babylonchain/babylon/types.BTCHeaderHashBytes" ]; uint64 height = 3; bytes work = 4 - [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint" ]; + [ (gogoproto.customtype) = "cosmossdk.io/math.Uint" ]; } diff --git a/proto/babylon/btclightclient/v1/tx.proto b/proto/babylon/btclightclient/v1/tx.proto index 52014be2b..6a10a7c64 100644 --- a/proto/babylon/btclightclient/v1/tx.proto +++ b/proto/babylon/btclightclient/v1/tx.proto @@ -2,17 +2,22 @@ syntax = "proto3"; package babylon.btclightclient.v1; import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // InsertHeaders adds a batch of headers to the BTC light client chain rpc InsertHeaders(MsgInsertHeaders) returns (MsgInsertHeadersResponse) {}; } // MsgInsertHeaders defines the message for multiple incoming header bytes message MsgInsertHeaders { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; repeated bytes headers = 2 [ (gogoproto.customtype) = diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 148b694f7..7853a808b 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -16,7 +16,7 @@ message BTCValidator { // commission defines the commission rate of BTC validator. string commission = 2 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; // babylon_pk is the Babylon secp256k1 PK of this BTC validator cosmos.crypto.secp256k1.PubKey babylon_pk = 3; diff --git a/proto/babylon/btcstaking/v1/incentive.proto b/proto/babylon/btcstaking/v1/incentive.proto index cc9053b85..e6c78c7d8 100644 --- a/proto/babylon/btcstaking/v1/incentive.proto +++ b/proto/babylon/btcstaking/v1/incentive.proto @@ -24,7 +24,7 @@ message BTCValDistInfo { // commission defines the commission rate of BTC validator string commission = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; // total_voting_power is the total voting power of the BTC validator uint64 total_voting_power = 4; @@ -38,4 +38,4 @@ message BTCDelDistInfo { cosmos.crypto.secp256k1.PubKey babylon_pk = 1; // voting_power is the voting power of the BTC delegation uint64 voting_power = 2; -} \ No newline at end of file +} diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index eaa266c1a..9445294ae 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -25,14 +25,14 @@ message Params { int64 min_slashing_tx_fee_sat = 4; // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators string min_commission_rate = 5 [ - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). string slashing_rate = 6 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 2ca0eea31..66a028e2f 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -14,6 +14,8 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // CreateBTCValidator creates a new BTC validator rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); // CreateBTCDelegation creates a new BTC delegation @@ -32,6 +34,8 @@ service Msg { // MsgCreateBTCValidator is the message for creating a BTC validator message MsgCreateBTCValidator { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // description defines the description terms for the BTC validator. @@ -39,7 +43,7 @@ message MsgCreateBTCValidator { // commission defines the commission rate of BTC validator. string commission = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; // babylon_pk is the Babylon secp256k1 PK of this BTC validator cosmos.crypto.secp256k1.PubKey babylon_pk = 4; @@ -54,6 +58,8 @@ message MsgCreateBTCValidatorResponse {} // MsgCreateBTCDelegation is the message for creating a BTC delegation message MsgCreateBTCDelegation { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // babylon_pk is the Babylon secp256k1 PK of this BTC delegation cosmos.crypto.secp256k1.PubKey babylon_pk = 2; @@ -84,6 +90,8 @@ message MsgCreateBTCDelegationResponse {} // MsgBTCUndelegate is the message undelegating existing and active delegation message MsgBTCUndelegate { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output @@ -105,6 +113,8 @@ message MsgBTCUndelegateResponse {} // MsgAddCovenantSig is the message for handling a signature from covenant message MsgAddCovenantSig { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec @@ -143,6 +153,8 @@ message MsgUpdateParamsResponse {} // MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant message MsgAddCovenantUnbondingSigs { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // val_pk is the Bitcoin secp256k1 PK of the BTC validator // the PK follows encoding in BIP-340 spec diff --git a/proto/babylon/checkpointing/v1/checkpoint.proto b/proto/babylon/checkpointing/v1/checkpoint.proto index 909450708..019904a88 100644 --- a/proto/babylon/checkpointing/v1/checkpoint.proto +++ b/proto/babylon/checkpointing/v1/checkpoint.proto @@ -12,9 +12,9 @@ message RawCheckpoint { // epoch_num defines the epoch number the raw checkpoint is for uint64 epoch_num = 1; - // last_commit_hash defines the 'LastCommitHash' that individual BLS sigs are + // app_hash defines the 'AppHash' that individual BLS sigs are // signed on - bytes last_commit_hash = 2 [ (gogoproto.customtype) = "LastCommitHash" ]; + bytes app_hash = 2 [ (gogoproto.customtype) = "AppHash" ]; // bitmap defines the bitmap that indicates the signers of the BLS multi sig bytes bitmap = 3; // bls_multi_sig defines the multi sig that is aggregated from individual BLS @@ -80,8 +80,8 @@ message BlsSig { // epoch_num defines the epoch number that the BLS sig is signed on uint64 epoch_num = 1; - // last_commit_hash defines the 'LastCommitHash' that the BLS sig is signed on - bytes last_commit_hash = 2 [ (gogoproto.customtype) = "LastCommitHash" ]; + // last_commit_hash defines the 'AppHash' that the BLS sig is signed on + bytes app_hash = 2 [ (gogoproto.customtype) = "AppHash" ]; bytes bls_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/crypto/bls12381.Signature" ]; diff --git a/proto/babylon/checkpointing/v1/tx.proto b/proto/babylon/checkpointing/v1/tx.proto index 78a990491..fc8f96fff 100644 --- a/proto/babylon/checkpointing/v1/tx.proto +++ b/proto/babylon/checkpointing/v1/tx.proto @@ -6,11 +6,14 @@ import "babylon/checkpointing/v1/checkpoint.proto"; import "babylon/checkpointing/v1/bls_key.proto"; import "cosmos/staking/v1beta1/tx.proto"; import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; option go_package = "github.com/babylonchain/babylon/x/checkpointing/types"; // Msg defines the checkpointing Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // AddBlsSig defines a method for accumulating BLS signatures rpc AddBlsSig(MsgAddBlsSig) returns (MsgAddBlsSigResponse); @@ -24,6 +27,7 @@ service Msg { message MsgAddBlsSig { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "signer"; // signer corresponds to the submitter of the transaction // This might be a different entity compared to the one that created the BLS signature @@ -40,6 +44,7 @@ message MsgAddBlsSigResponse {} message MsgWrappedCreateValidator { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "msg_create_validator"; BlsKey key = 1; cosmos.staking.v1beta1.MsgCreateValidator msg_create_validator = 2; diff --git a/proto/babylon/epoching/v1/tx.proto b/proto/babylon/epoching/v1/tx.proto index 7a25e34a4..0f1c83f3c 100644 --- a/proto/babylon/epoching/v1/tx.proto +++ b/proto/babylon/epoching/v1/tx.proto @@ -12,6 +12,8 @@ option go_package = "github.com/babylonchain/babylon/x/epoching/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // WrappedDelegate defines a method for performing a delegation of coins from // a delegator to a validator. rpc WrappedDelegate(MsgWrappedDelegate) returns (MsgWrappedDelegateResponse); @@ -34,6 +36,7 @@ service Msg { message MsgWrappedDelegate { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "msg"; cosmos.staking.v1beta1.MsgDelegate msg = 1; } @@ -45,6 +48,7 @@ message MsgWrappedDelegateResponse {} message MsgWrappedUndelegate { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "msg"; cosmos.staking.v1beta1.MsgUndelegate msg = 1; } @@ -58,6 +62,7 @@ message MsgWrappedUndelegateResponse {} message MsgWrappedBeginRedelegate { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "msg"; cosmos.staking.v1beta1.MsgBeginRedelegate msg = 1; } diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index a7a1e3c64..d81e7ed45 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -10,6 +10,8 @@ import "babylon/finality/v1/params.proto"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // AddFinalitySig adds a finality signature to a given block rpc AddFinalitySig(MsgAddFinalitySig) returns (MsgAddFinalitySigResponse); // CommitPubRandList commits a list of public randomness for EOTS @@ -21,6 +23,8 @@ service Msg { // MsgAddFinalitySig defines a message for adding a vote message MsgAddFinalitySig { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // val_btc_pk is the BTC Pk of the validator that casts this vote bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; @@ -39,6 +43,8 @@ message MsgAddFinalitySigResponse{} // MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS message MsgCommitPubRandList { + option (cosmos.msg.v1.signer) = "signer"; + string signer = 1; // val_btc_pk is the BTC Pk of the validator that commits the public randomness bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; diff --git a/proto/babylon/incentive/params.proto b/proto/babylon/incentive/params.proto index 99e619e77..7ac4deed7 100644 --- a/proto/babylon/incentive/params.proto +++ b/proto/babylon/incentive/params.proto @@ -8,7 +8,7 @@ option go_package = "github.com/babylonchain/babylon/x/incentive/types"; // Params defines the parameters for the module, including portions of rewards // distributed to each type of stakeholder. Note that sum of the portions should -// be strictly less than 1 so that the rest will go to Tendermint validators/delegations +// be strictly less than 1 so that the rest will go to Comet validators/delegations // adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto message Params { option (gogoproto.goproto_stringer) = false; @@ -16,13 +16,13 @@ message Params { // submitter_portion is the portion of rewards that goes to submitter string submitter_portion = 1 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; // reporter_portion is the portion of rewards that goes to reporter string reporter_portion = 2 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations @@ -30,7 +30,7 @@ message Params { // power and BTC validator's commission string btc_staking_portion = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; } diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto index a491d5437..fb784eb7e 100644 --- a/proto/babylon/incentive/tx.proto +++ b/proto/babylon/incentive/tx.proto @@ -11,6 +11,8 @@ option go_package = "github.com/babylonchain/babylon/x/incentive/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // WithdrawReward defines a method to withdraw rewards of a stakeholder rpc WithdrawReward(MsgWithdrawReward) returns (MsgWithdrawRewardResponse); // UpdateParams updates the incentive module parameters. @@ -20,6 +22,7 @@ service Msg { // MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. message MsgWithdrawReward { + option (cosmos.msg.v1.signer) = "address"; // {submitter, reporter, btc_validator, btc_delegation} string type = 1; // address is the address of the stakeholder in bech32 string diff --git a/proto/babylon/zoneconcierge/v1/tx.proto b/proto/babylon/zoneconcierge/v1/tx.proto index 922128e11..105a29107 100644 --- a/proto/babylon/zoneconcierge/v1/tx.proto +++ b/proto/babylon/zoneconcierge/v1/tx.proto @@ -11,6 +11,8 @@ option go_package = "github.com/babylonchain/babylon/x/zoneconcierge/types"; // Msg defines the Msg service. service Msg { + option (cosmos.msg.v1.service) = true; + // UpdateParams updates the zoneconcierge module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } @@ -33,4 +35,3 @@ message MsgUpdateParams { // MsgUpdateParamsResponse is the response to the MsgUpdateParams message. message MsgUpdateParamsResponse {} - \ No newline at end of file diff --git a/proto/scripts/protocgen.sh b/proto/scripts/protocgen.sh index 70312bb3d..9cbce2e51 100755 --- a/proto/scripts/protocgen.sh +++ b/proto/scripts/protocgen.sh @@ -19,4 +19,4 @@ cd .. cp -r github.com/babylonchain/babylon/* ./ rm -rf github.com -go mod tidy -compat=1.20 +go mod tidy -compat=1.21 diff --git a/simapp/config.go b/simapp/config.go deleted file mode 100644 index 98df982bd..000000000 --- a/simapp/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package simapp - -import ( - "flag" - - "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// List of available flags for the simulator -var ( - FlagGenesisFileValue string - FlagParamsFileValue string - FlagExportParamsPathValue string - FlagExportParamsHeightValue int - FlagExportStatePathValue string - FlagExportStatsPathValue string - FlagSeedValue int64 - FlagInitialBlockHeightValue int - FlagNumBlocksValue int - FlagBlockSizeValue int - FlagLeanValue bool - FlagCommitValue bool - FlagOnOperationValue bool // TODO: Remove in favor of binary search for invariant violation - FlagAllInvariantsValue bool - - FlagEnabledValue bool - FlagVerboseValue bool - FlagPeriodValue uint - FlagGenesisTimeValue int64 -) - -// GetSimulatorFlags gets the values of all the available simulation flags -func GetSimulatorFlags() { - // config fields - flag.StringVar(&FlagGenesisFileValue, "Genesis", "", "custom simulation genesis file; cannot be used with params file") - flag.StringVar(&FlagParamsFileValue, "Params", "", "custom simulation params file which overrides any random params; cannot be used with genesis") - flag.StringVar(&FlagExportParamsPathValue, "ExportParamsPath", "", "custom file path to save the exported params JSON") - flag.IntVar(&FlagExportParamsHeightValue, "ExportParamsHeight", 0, "height to which export the randomly generated params") - flag.StringVar(&FlagExportStatePathValue, "ExportStatePath", "", "custom file path to save the exported app state JSON") - flag.StringVar(&FlagExportStatsPathValue, "ExportStatsPath", "", "custom file path to save the exported simulation statistics JSON") - flag.Int64Var(&FlagSeedValue, "Seed", 42, "simulation random seed") - flag.IntVar(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation") - flag.IntVar(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height") - flag.IntVar(&FlagBlockSizeValue, "BlockSize", 200, "operations per block") - flag.BoolVar(&FlagLeanValue, "Lean", false, "lean simulation log output") - flag.BoolVar(&FlagCommitValue, "Commit", false, "have the simulation commit") - flag.BoolVar(&FlagOnOperationValue, "SimulateEveryOperation", false, "run slow invariants every operation") - flag.BoolVar(&FlagAllInvariantsValue, "PrintAllInvariants", false, "print all invariants if a broken invariant is found") - - // simulation flags - flag.BoolVar(&FlagEnabledValue, "Enabled", false, "enable the simulation") - flag.BoolVar(&FlagVerboseValue, "Verbose", false, "verbose log output") - flag.UintVar(&FlagPeriodValue, "Period", 0, "run slow invariants only once every period assertions") - flag.Int64Var(&FlagGenesisTimeValue, "GenesisTime", 0, "override genesis UNIX time instead of using a random UNIX time") -} - -// NewConfigFromFlags creates a simulation from the retrieved values of the flags. -func NewConfigFromFlags() simulation.Config { - return simulation.Config{ - GenesisFile: FlagGenesisFileValue, - ParamsFile: FlagParamsFileValue, - ExportParamsPath: FlagExportParamsPathValue, - ExportParamsHeight: FlagExportParamsHeightValue, - ExportStatePath: FlagExportStatePathValue, - ExportStatsPath: FlagExportStatsPathValue, - Seed: FlagSeedValue, - InitialBlockHeight: FlagInitialBlockHeightValue, - NumBlocks: FlagNumBlocksValue, - BlockSize: FlagBlockSizeValue, - Lean: FlagLeanValue, - Commit: FlagCommitValue, - OnOperation: FlagOnOperationValue, - AllInvariants: FlagAllInvariantsValue, - } -} diff --git a/simapp/sim_bench_test.go b/simapp/sim_bench_test.go deleted file mode 100644 index aeae76ddd..000000000 --- a/simapp/sim_bench_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package simapp - -import ( - "fmt" - "os" - "testing" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/testutil/sims" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - - "github.com/babylonchain/babylon/app" -) - -// Profile with: -// /usr/local/go/bin/go test -benchmem -run=^$ github.com/babylonchain/babylon/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out -func BenchmarkFullAppSimulation(b *testing.B) { - b.ReportAllocs() - config, db, dir, logger, skip, err := SetupSimulation("goleveldb-app-sim", "Simulation") - if err != nil { - b.Fatalf("simulation setup failed: %s", err.Error()) - } - - if skip { - b.Skip("skipping benchmark application simulation") - } - - defer func() { - db.Close() - err = os.RemoveAll(dir) - if err != nil { - b.Fatal(err) - } - }() - - privSigner, err := app.SetupPrivSigner() - if err != nil { - b.Fatal(err) - } - babylon := app.NewBabylonApp( - logger, - db, - nil, - true, - map[int64]bool{}, - app.DefaultNodeHome, - FlagPeriodValue, - app.GetEncodingConfig(), - privSigner, - sims.EmptyAppOptions{}, - app.EmptyWasmOpts, - interBlockCacheOpt()) - - // run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - b, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - if err = sims.CheckExportSimulation(babylon, config, simParams); err != nil { - b.Fatal(err) - } - - if simErr != nil { - b.Fatal(simErr) - } - - if config.Commit { - sims.PrintStats(db) - } -} - -func BenchmarkInvariants(b *testing.B) { - b.ReportAllocs() - config, db, dir, logger, skip, err := SetupSimulation("leveldb-app-invariant-bench", "Simulation") - if err != nil { - b.Fatalf("simulation setup failed: %s", err.Error()) - } - - if skip { - b.Skip("skipping benchmark application simulation") - } - - config.AllInvariants = false - - defer func() { - db.Close() - err = os.RemoveAll(dir) - if err != nil { - b.Fatal(err) - } - }() - - privSigner, err := app.SetupPrivSigner() - if err != nil { - b.Fatal(err) - } - babylon := app.NewBabylonApp( - logger, - db, - nil, - true, - map[int64]bool{}, - app.DefaultNodeHome, - FlagPeriodValue, - app.GetEncodingConfig(), - privSigner, - sims.EmptyAppOptions{}, - app.EmptyWasmOpts, - interBlockCacheOpt()) - - // run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - b, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - if err = sims.CheckExportSimulation(babylon, config, simParams); err != nil { - b.Fatal(err) - } - - if simErr != nil { - b.Fatal(simErr) - } - - if config.Commit { - sims.PrintStats(db) - } - - ctx := babylon.NewContext(true, tmproto.Header{Height: babylon.LastBlockHeight() + 1}) - - // 3. Benchmark each invariant separately - // - // NOTE: We use the crisis keeper as it has all the invariants registered with - // their respective metadata which makes it useful for testing/benchmarking. - for _, cr := range babylon.CrisisKeeper.Routes() { - cr := cr - b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) { - if res, stop := cr.Invar(ctx); stop { - b.Fatalf( - "broken invariant at block %d of %d\n%s", - ctx.BlockHeight()-1, config.NumBlocks, res, - ) - } - }) - } -} diff --git a/simapp/sim_test.go b/simapp/sim_test.go deleted file mode 100644 index e62c11726..000000000 --- a/simapp/sim_test.go +++ /dev/null @@ -1,359 +0,0 @@ -package simapp - -import ( - "encoding/json" - "fmt" - "math/rand" - "os" - "testing" - - simappparams "github.com/babylonchain/babylon/app/params" - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/stretchr/testify/require" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/cosmos/cosmos-sdk/testutil/sims" - - "github.com/babylonchain/babylon/app" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - "github.com/cosmos/cosmos-sdk/x/simulation" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" -) - -// Get flags every time the simulator is run -func init() { - GetSimulatorFlags() -} - -type StoreKeysPrefixes struct { - A storetypes.StoreKey - B storetypes.StoreKey - Prefixes [][]byte -} - -// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of -// an IAVLStore for faster simulation speed. -func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { - bapp.SetFauxMerkleMode() -} - -// interBlockCacheOpt returns a BaseApp option function that sets the persistent -// inter-block write-through cache. -func interBlockCacheOpt() func(*baseapp.BaseApp) { - return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) -} - -func TestFullAppSimulation(t *testing.T) { - config, db, dir, logger, skip, err := SetupSimulation("leveldb-app-sim", "Simulation") - if skip { - t.Skip("skipping application simulation") - } - require.NoError(t, err, "simulation setup failed") - - defer func() { - db.Close() - require.NoError(t, os.RemoveAll(dir)) - }() - - privSigner, err := app.SetupPrivSigner() - require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) - require.Equal(t, "BabylonApp", babylon.Name()) - - // run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - t, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - err = sims.CheckExportSimulation(babylon, config, simParams) - require.NoError(t, err) - require.NoError(t, simErr) - - if config.Commit { - sims.PrintStats(db) - } -} - -func TestAppImportExport(t *testing.T) { - config, db, dir, logger, skip, err := SetupSimulation("leveldb-app-sim", "Simulation") - if skip { - t.Skip("skipping application import/export simulation") - } - require.NoError(t, err, "simulation setup failed") - - defer func() { - db.Close() - require.NoError(t, os.RemoveAll(dir)) - }() - - privSigner, err := app.SetupPrivSigner() - require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) - require.Equal(t, "BabylonApp", babylon.Name()) - - // Run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - t, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - err = sims.CheckExportSimulation(babylon, config, simParams) - require.NoError(t, err) - require.NoError(t, simErr) - - if config.Commit { - sims.PrintStats(db) - } - - fmt.Printf("exporting genesis...\n") - - exported, err := babylon.ExportAppStateAndValidators(false, []string{}, []string{}) - require.NoError(t, err) - - fmt.Printf("importing genesis...\n") - - _, newDB, newDir, _, _, err := SetupSimulation("leveldb-app-sim-2", "Simulation-2") - require.NoError(t, err, "simulation setup failed") - - defer func() { - newDB.Close() - require.NoError(t, os.RemoveAll(newDir)) - }() - - newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) - require.Equal(t, "BabylonApp", newBabylon.Name()) - - var genesisState app.GenesisState - err = json.Unmarshal(exported.AppState, &genesisState) - require.NoError(t, err) - - ctxA := babylon.NewContext(true, tmproto.Header{Height: babylon.LastBlockHeight()}) - ctxB := newBabylon.NewContext(true, tmproto.Header{Height: babylon.LastBlockHeight()}) - newBabylon.ModuleManager().InitGenesis(ctxB, babylon.AppCodec(), genesisState) - newBabylon.StoreConsensusParams(ctxB, exported.ConsensusParams) - - fmt.Printf("comparing stores...\n") - - storeKeysPrefixes := []StoreKeysPrefixes{ - {babylon.GetKey(authtypes.StoreKey), newBabylon.GetKey(authtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(stakingtypes.StoreKey), newBabylon.GetKey(stakingtypes.StoreKey), - [][]byte{ - stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, - stakingtypes.HistoricalInfoKey, - }}, // ordering may change but it doesn't matter - {babylon.GetKey(slashingtypes.StoreKey), newBabylon.GetKey(slashingtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(minttypes.StoreKey), newBabylon.GetKey(minttypes.StoreKey), [][]byte{}}, - {babylon.GetKey(distrtypes.StoreKey), newBabylon.GetKey(distrtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(banktypes.StoreKey), newBabylon.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, - {babylon.GetKey(paramtypes.StoreKey), newBabylon.GetKey(paramtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(govtypes.StoreKey), newBabylon.GetKey(govtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(evidencetypes.StoreKey), newBabylon.GetKey(evidencetypes.StoreKey), [][]byte{}}, - {babylon.GetKey(capabilitytypes.StoreKey), newBabylon.GetKey(capabilitytypes.StoreKey), [][]byte{}}, - {babylon.GetKey(authzkeeper.StoreKey), newBabylon.GetKey(authzkeeper.StoreKey), [][]byte{}}, - // TODO: add Babylon module StoreKey and prefix here - {babylon.GetKey(btccheckpointtypes.StoreKey), newBabylon.GetKey(btccheckpointtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(btclightclienttypes.StoreKey), newBabylon.GetKey(btclightclienttypes.StoreKey), [][]byte{}}, - {babylon.GetKey(checkpointingtypes.StoreKey), newBabylon.GetKey(checkpointingtypes.StoreKey), [][]byte{}}, - {babylon.GetKey(epochingtypes.StoreKey), newBabylon.GetKey(epochingtypes.StoreKey), - [][]byte{epochingtypes.SlashedVotingPowerKey, epochingtypes.VotingPowerKey}}, - } - - for _, skp := range storeKeysPrefixes { - storeA := ctxA.KVStore(skp.A) - storeB := ctxB.KVStore(skp.B) - - failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) - require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") - - fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) - require.Equal(t, len(failedKVAs), 0, sims.GetSimulationLog(skp.A.Name(), babylon.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) - } -} - -func TestAppSimulationAfterImport(t *testing.T) { - config, db, dir, logger, skip, err := SetupSimulation("leveldb-app-sim", "Simulation") - if skip { - t.Skip("skipping application simulation after import") - } - require.NoError(t, err, "simulation setup failed") - - defer func() { - db.Close() - require.NoError(t, os.RemoveAll(dir)) - }() - - privSigner, err := app.SetupPrivSigner() - require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) - require.Equal(t, "BabylonApp", babylon.Name()) - - // Run randomized simulation - stopEarly, simParams, simErr := simulation.SimulateFromSeed( - t, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - - // export state and simParams before the simulation error is checked - err = sims.CheckExportSimulation(babylon, config, simParams) - require.NoError(t, err) - require.NoError(t, simErr) - - if config.Commit { - sims.PrintStats(db) - } - - if stopEarly { - fmt.Println("can't export or import a zero-validator genesis, exiting test...") - return - } - - fmt.Printf("exporting genesis...\n") - - exported, err := babylon.ExportAppStateAndValidators(true, []string{}, []string{}) - require.NoError(t, err) - - fmt.Printf("importing genesis...\n") - - _, newDB, newDir, _, _, err := SetupSimulation("leveldb-app-sim-2", "Simulation-2") - require.NoError(t, err, "simulation setup failed") - - defer func() { - newDB.Close() - require.NoError(t, os.RemoveAll(newDir)) - }() - - newBabylon := app.NewBabylonApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, fauxMerkleModeOpt) - require.Equal(t, "BabylonApp", newBabylon.Name()) - - newBabylon.InitChain(abci.RequestInitChain{ - AppStateBytes: exported.AppState, - }) - - _, _, err = simulation.SimulateFromSeed( - t, - os.Stdout, - newBabylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(newBabylon, newBabylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - require.NoError(t, err) -} - -// TODO: Make another test for the fuzzer itself, which just has noOp txs -// and doesn't depend on the application. -func TestAppStateDeterminism(t *testing.T) { - if !FlagEnabledValue { - t.Skip("skipping application simulation") - } - - config := NewConfigFromFlags() - config.InitialBlockHeight = 1 - config.ExportParamsPath = "" - config.OnOperation = false - config.AllInvariants = false - config.ChainID = simappparams.SimAppChainID - - numSeeds := 3 - numTimesToRunPerSeed := 5 - appHashList := make([]json.RawMessage, numTimesToRunPerSeed) - - for i := 0; i < numSeeds; i++ { - config.Seed = rand.Int63() - - for j := 0; j < numTimesToRunPerSeed; j++ { - var logger log.Logger - if FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - db := dbm.NewMemDB() - privSigner, err := app.SetupPrivSigner() - require.NoError(t, err) - babylon := app.NewBabylonApp(logger, db, nil, true, map[int64]bool{}, app.DefaultNodeHome, FlagPeriodValue, app.GetEncodingConfig(), privSigner, sims.EmptyAppOptions{}, app.EmptyWasmOpts, interBlockCacheOpt()) - - fmt.Printf( - "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", - config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, - ) - - _, _, err = simulation.SimulateFromSeed( - t, - os.Stdout, - babylon.BaseApp, - AppStateFn(babylon.AppCodec(), babylon.SimulationManager()), - simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 - SimulationOperations(babylon, babylon.AppCodec(), config), - babylon.ModuleAccountAddrs(), - config, - babylon.AppCodec(), - ) - require.NoError(t, err) - - if config.Commit { - sims.PrintStats(db) - } - - appHash := babylon.LastCommitID().Hash - appHashList[j] = appHash - - if j != 0 { - require.Equal( - t, string(appHashList[0]), string(appHashList[j]), - "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, - ) - } - } - } -} diff --git a/simapp/state.go b/simapp/state.go deleted file mode 100644 index dec564272..000000000 --- a/simapp/state.go +++ /dev/null @@ -1,245 +0,0 @@ -package simapp - -import ( - "encoding/json" - "fmt" - "io" - "math/rand" - "os" - "time" - - sdkmath "cosmossdk.io/math" - simappparams "github.com/babylonchain/babylon/app/params" - - tmjson "github.com/cometbft/cometbft/libs/json" - tmtypes "github.com/cometbft/cometbft/types" - - "github.com/babylonchain/babylon/app" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -// AppStateFn returns the initial application state using a genesis or the simulation parameters. -// It panics if the user provides files for both of them. -// If a file is not given for the genesis or the sim params, it creates a randomized one. -func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simtypes.AppStateFn { - return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config, - ) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) { - - if FlagGenesisTimeValue == 0 { - genesisTimestamp = simtypes.RandTimestamp(r) - } else { - genesisTimestamp = time.Unix(FlagGenesisTimeValue, 0) - } - - chainID = config.ChainID - switch { - case config.ParamsFile != "" && config.GenesisFile != "": - panic("cannot provide both a genesis file and a params file") - case config.GenesisFile != "": - // override the default chain-id from simapp to set it later to the config - genesisDoc, accounts := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile) - - if FlagGenesisTimeValue == 0 { - // use genesis timestamp if no custom timestamp is provided (i.e no random timestamp) - genesisTimestamp = genesisDoc.GenesisTime - } - - appState = genesisDoc.AppState - chainID = genesisDoc.ChainID - simAccs = accounts - - case config.ParamsFile != "": - appParams := make(simtypes.AppParams) - bz, err := os.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &appParams) - if err != nil { - panic(err) - } - appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams) - default: - appParams := make(simtypes.AppParams) - appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams) - } - - rawState := make(map[string]json.RawMessage) - err := json.Unmarshal(appState, &rawState) - if err != nil { - panic(err) - } - - stakingStateBz, ok := rawState[stakingtypes.ModuleName] - if !ok { - panic("staking genesis state is missing") - } - - stakingState := new(stakingtypes.GenesisState) - err = cdc.UnmarshalJSON(stakingStateBz, stakingState) - if err != nil { - panic(err) - } - // compute not bonded balance - notBondedTokens := sdk.ZeroInt() - for _, val := range stakingState.Validators { - if val.Status != stakingtypes.Unbonded { - continue - } - notBondedTokens = notBondedTokens.Add(val.GetTokens()) - } - notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) - // edit bank state to make it have the not bonded pool tokens - bankStateBz, ok := rawState[banktypes.ModuleName] - // TODO(fdymylja/jonathan): should we panic in this case - if !ok { - panic("bank genesis state is missing") - } - bankState := new(banktypes.GenesisState) - err = cdc.UnmarshalJSON(bankStateBz, bankState) - if err != nil { - panic(err) - } - - stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() - var found bool - for _, balance := range bankState.Balances { - if balance.Address == stakingAddr { - found = true - break - } - } - if !found { - bankState.Balances = append(bankState.Balances, banktypes.Balance{ - Address: stakingAddr, - Coins: sdk.NewCoins(notBondedCoins), - }) - } - - // change appState back - rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) - rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) - - // replace appstate - appState, err = json.Marshal(rawState) - if err != nil { - panic(err) - } - return appState, simAccs, chainID, genesisTimestamp - } -} - -// AppStateRandomizedFn creates calls each module's GenesisState generator function -// and creates the simulation params -func AppStateRandomizedFn( - simManager *module.SimulationManager, r *rand.Rand, cdc codec.JSONCodec, - accs []simtypes.Account, genesisTimestamp time.Time, appParams simtypes.AppParams, -) (json.RawMessage, []simtypes.Account) { - numAccs := int64(len(accs)) - genesisState := app.NewDefaultGenesisState(cdc) - - // generate a random amount of initial stake coins and a random initial - // number of bonded accounts - var initialStake, numInitiallyBonded int64 - appParams.GetOrGenerate( - cdc, simappparams.StakePerAccount, &initialStake, r, - func(r *rand.Rand) { initialStake = r.Int63n(1e12) }, - ) - appParams.GetOrGenerate( - cdc, simappparams.InitiallyBondedValidators, &numInitiallyBonded, r, - func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(300)) }, - ) - - if numInitiallyBonded > numAccs { - numInitiallyBonded = numAccs - } - - fmt.Printf( - `Selected randomly generated parameters for simulated genesis: -{ - stake_per_account: "%d", - initially_bonded_validators: "%d" -} -`, initialStake, numInitiallyBonded, - ) - - simState := &module.SimulationState{ - AppParams: appParams, - Cdc: cdc, - Rand: r, - GenState: genesisState, - Accounts: accs, - InitialStake: sdkmath.NewInt(initialStake), - NumBonded: numInitiallyBonded, - GenTimestamp: genesisTimestamp, - } - - simManager.GenerateGenesisStates(simState) - - appState, err := json.Marshal(genesisState) - if err != nil { - panic(err) - } - - return appState, accs -} - -// AppStateFromGenesisFileFn util function to generate the genesis AppState -// from a genesis.json file. -func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile string) (tmtypes.GenesisDoc, []simtypes.Account) { - bytes, err := os.ReadFile(genesisFile) - if err != nil { - panic(err) - } - - var genesis tmtypes.GenesisDoc - // NOTE: Tendermint uses a custom JSON decoder for GenesisDoc - err = tmjson.Unmarshal(bytes, &genesis) - if err != nil { - panic(err) - } - - var appState app.GenesisState - err = json.Unmarshal(genesis.AppState, &appState) - if err != nil { - panic(err) - } - - var authGenesis authtypes.GenesisState - if appState[authtypes.ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[authtypes.ModuleName], &authGenesis) - } - - newAccs := make([]simtypes.Account, len(authGenesis.Accounts)) - for i, acc := range authGenesis.Accounts { - // Pick a random private key, since we don't know the actual key - // This should be fine as it's only used for mock Tendermint validators - // and these keys are never actually used to sign by mock Tendermint. - privkeySeed := make([]byte, 15) - if _, err := r.Read(privkeySeed); err != nil { - panic(err) - } - - privKey := secp256k1.GenPrivKeyFromSecret(privkeySeed) - - a, ok := acc.GetCachedValue().(authtypes.AccountI) - if !ok { - panic("expected account") - } - - // create simulator accounts - simAcc := simtypes.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: a.GetAddress()} - newAccs[i] = simAcc - } - - return genesis, newAccs -} diff --git a/simapp/utils.go b/simapp/utils.go deleted file mode 100644 index 6efac1755..000000000 --- a/simapp/utils.go +++ /dev/null @@ -1,87 +0,0 @@ -package simapp - -import ( - "encoding/json" - "os" - - "github.com/babylonchain/babylon/app" - simappparams "github.com/babylonchain/babylon/app/params" - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/codec" - - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/staking" -) - -// SetupSimulation creates the config, db (levelDB), temporary directory and logger for the simulation tests. -// If `FlagEnabledValue` is false it skips the current test. -// Returns error on an invalid db intantiation or temp dir creation. -// NOTE: this function is identical to https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/utils.go. -// The reason of migrating it here is that it uses the modules' flags initialised in `init()`. Otherwise, -// if using `sdksimapp.SetupSimulation`, then `sdksimapp.FlagEnabledValue` and `sdksimapp.FlagVerboseValue` -// will be used, rather than those in `config.go` of our module. The other functions under -// https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/utils.go do not access flags and thus can be reused safely. -func SetupSimulation(dirPrefix, dbName string) (simtypes.Config, dbm.DB, string, log.Logger, bool, error) { - if !FlagEnabledValue { - return simtypes.Config{}, nil, "", nil, true, nil - } - - config := NewConfigFromFlags() - config.ChainID = simappparams.SimAppChainID - - var logger log.Logger - if FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - dir, err := os.MkdirTemp("", dirPrefix) - if err != nil { - return simtypes.Config{}, nil, "", nil, false, err - } - - db, err := dbm.NewDB(dbName, dbm.BackendType(config.DBBackend), dir) - if err != nil { - return simtypes.Config{}, nil, "", nil, false, err - } - - return config, db, dir, logger, false, nil -} - -// SimulationOperations retrieves the simulation params from the provided file path -// and returns all the modules weighted operations -// NOTE: the code is same as https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/utils.go#L50-L73, -// except that this function modifies the default weights given in Cosmos SDK. -// Specifically, Babylon does not want unwrapped message types in the staking module. -func SimulationOperations(app app.App, cdc codec.JSONCodec, config simtypes.Config) []simtypes.WeightedOperation { - simState := module.SimulationState{ - AppParams: make(simtypes.AppParams), - Cdc: cdc, - } - - if config.ParamsFile != "" { - bz, err := os.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &simState.AppParams) - if err != nil { - panic(err) - } - } - // get weighted operations from all modules, except for the staking module whose messages will be rejected by Babylon - sm := app.SimulationManager() - appWOps := make([]simtypes.WeightedOperation, 0, len(sm.Modules)) - for _, module := range sm.Modules { - if _, ok := module.(staking.AppModule); ok { - continue - } - appWOps = append(appWOps, module.WeightedOperations(simState)...) - } - - return appWOps -} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index e7529b6ca..3a7259f81 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -317,13 +317,15 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat // get block to vote blockToVote, err := nonValidatorNode.QueryBlock(int64(activatedHeight)) s.NoError(err) - msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), blockToVote.LastCommitHash...) + appHash := blockToVote.AppHash + + msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), appHash...) // generate EOTS signature sig, err := eots.Sign(valBTCSK, srList[0], msgToSign) s.NoError(err) eotsSig := bbn.NewSchnorrEOTSSigFromModNScalar(sig) // submit finality signature - nonValidatorNode.AddFinalitySig(btcVal.BtcPk, activatedHeight, blockToVote.LastCommitHash, eotsSig) + nonValidatorNode.AddFinalitySig(btcVal.BtcPk, activatedHeight, appHash, eotsSig) // ensure vote is eventually cast nonValidatorNode.WaitForNextBlock() @@ -332,14 +334,15 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat votes = nonValidatorNode.QueryVotesAtHeight(activatedHeight) return len(votes) > 0 }, time.Minute, time.Second*5) + s.Equal(1, len(votes)) s.Equal(votes[0].MarshalHex(), btcVal.BtcPk.MarshalHex()) // once the vote is cast, ensure block is finalised finalizedBlock := nonValidatorNode.QueryIndexedBlock(activatedHeight) s.NotEmpty(finalizedBlock) - s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlock.LastCommitHash) + s.Equal(appHash.Bytes(), finalizedBlock.AppHash) finalizedBlocks := nonValidatorNode.QueryListBlocks(ftypes.QueriedBlockStatus_FINALIZED) s.NotEmpty(finalizedBlocks) - s.Equal(blockToVote.LastCommitHash.Bytes(), finalizedBlocks[0].LastCommitHash) + s.Equal(appHash.Bytes(), finalizedBlocks[0].AppHash) // ensure BTC validator has received rewards after the block is finalised btcValRewardGauges, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) diff --git a/test/e2e/configurer/base.go b/test/e2e/configurer/base.go index f27ca0227..39828d4ee 100644 --- a/test/e2e/configurer/base.go +++ b/test/e2e/configurer/base.go @@ -43,7 +43,9 @@ func (bc *baseConfigurer) ClearResources() error { } for _, chainConfig := range bc.chainConfigs { - os.RemoveAll(chainConfig.DataDir) + if err := os.RemoveAll(chainConfig.DataDir); err != nil { + return err + } } return nil } diff --git a/test/e2e/configurer/chain/chain.go b/test/e2e/configurer/chain/chain.go index f08f8f575..e5931cd69 100644 --- a/test/e2e/configurer/chain/chain.go +++ b/test/e2e/configurer/chain/chain.go @@ -61,7 +61,7 @@ func New(t *testing.T, containerManager *containers.Manager, id string, initVali func (c *Config) CreateNode(initNode *initialization.Node) *NodeConfig { nodeConfig := &NodeConfig{ Node: *initNode, - chainId: c.Id, + chainId: c.ChainMeta.Id, containerManager: c.containerManager, t: c.t, } diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 3ff50efbb..68ebbc441 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -2,6 +2,8 @@ package chain import ( "encoding/hex" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" "strconv" "cosmossdk.io/math" @@ -9,9 +11,7 @@ import ( bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" - secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/stretchr/testify/require" ) diff --git a/test/e2e/configurer/chain/node.go b/test/e2e/configurer/chain/node.go index bb51f3a8c..efe0be7b9 100644 --- a/test/e2e/configurer/chain/node.go +++ b/test/e2e/configurer/chain/node.go @@ -2,6 +2,7 @@ package chain import ( "context" + "encoding/hex" "fmt" "regexp" "strings" @@ -153,7 +154,7 @@ func (n *NodeConfig) extractOperatorAddressIfValidator() error { return nil } - cmd := []string{"babylond", "debug", "addr", n.PublicKey} + cmd := []string{"babylond", "debug", "addr", hex.EncodeToString(n.PublicKey)} n.t.Logf("extracting validator operator addresses for validator: %s", n.Name) _, errBuf, err := n.containerManager.ExecCmd(n.t, n.Name, cmd, "") if err != nil { diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index 524e07451..e9a6afffd 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -68,7 +68,7 @@ func (n *NodeConfig) QueryGRPCGateway(path string, queryParams url.Values) ([]by return bz, nil } -// QueryModuleAccoint returns the address of a given module +// QueryModuleAddress returns the address of a given module func (n *NodeConfig) QueryModuleAddress(name string) (sdk.AccAddress, error) { path := fmt.Sprintf("/cosmos/auth/v1beta1/module_accounts/%s", name) bz, err := n.QueryGRPCGateway(path, url.Values{}) @@ -79,7 +79,7 @@ func (n *NodeConfig) QueryModuleAddress(name string) (sdk.AccAddress, error) { return sdk.AccAddress{}, err } // cast to account - var account authtypes.AccountI + var account sdk.AccountI if err := util.EncodingConfig.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { return sdk.AccAddress{}, err } @@ -107,7 +107,7 @@ func (n *NodeConfig) QuerySupplyOf(denom string) (sdkmath.Int, error) { var supplyResp banktypes.QuerySupplyOfResponse if err := util.Cdc.UnmarshalJSON(bz, &supplyResp); err != nil { - return sdk.NewInt(0), err + return sdkmath.NewInt(0), err } return supplyResp.Amount.Amount, nil } diff --git a/test/e2e/configurer/config/constants.go b/test/e2e/configurer/config/constants.go index f399c94c3..09c584507 100644 --- a/test/e2e/configurer/config/constants.go +++ b/test/e2e/configurer/config/constants.go @@ -1,16 +1,10 @@ package config const ( - // if not skipping upgrade, how many blocks we allow for fork to run pre upgrade state creation - ForkHeightPreUpgradeOffset int64 = 60 - // estimated number of blocks it takes to submit for a proposal - PropSubmitBlocks float32 = 10 - // estimated number of blocks it takes to deposit for a proposal + // PropDepositBlocks estimated number of blocks it takes to deposit for a proposal PropDepositBlocks float32 = 10 - // number of blocks it takes to vote for a single validator to vote for a proposal + // PropVoteBlocks number of blocks it takes to vote for a single validator to vote for a proposal PropVoteBlocks float32 = 1.2 - // number of blocks used as a calculation buffer + // PropBufferBlocks number of blocks used as a calculation buffer PropBufferBlocks float32 = 6 - // max retries for json unmarshalling - MaxRetries = 60 ) diff --git a/test/e2e/configurer/factory.go b/test/e2e/configurer/factory.go index 915440165..6f11174f3 100644 --- a/test/e2e/configurer/factory.go +++ b/test/e2e/configurer/factory.go @@ -91,7 +91,7 @@ var ( } ) -// New returns a new Configurer for BTC timestamping service. +// NewBTCTimestampingConfigurer returns a new Configurer for BTC timestamping service. // TODO currently only one configuration is available. Consider testing upgrades // when necessary func NewBTCTimestampingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { @@ -110,7 +110,7 @@ func NewBTCTimestampingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configu ), nil } -// New returns a new Configurer for BTC staking service +// NewBTCStakingConfigurer returns a new Configurer for BTC staking service func NewBTCStakingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { containerManager, err := containers.NewManager(isDebugLogEnabled) if err != nil { diff --git a/test/e2e/containers/config.go b/test/e2e/containers/config.go index 90b8416cc..fbd59b197 100644 --- a/test/e2e/containers/config.go +++ b/test/e2e/containers/config.go @@ -13,10 +13,10 @@ const ( BabylonContainerName = "babylonchain/babylond" relayerRepository = "informalsystems/hermes" - relayerTag = "1.4.0" + relayerTag = "v1.7.1" ) -// Returns ImageConfig needed for running e2e test. +// NewImageConfig returns ImageConfig needed for running e2e test. // If isUpgrade is true, returns images for running the upgrade // If isFork is true, utilizes provided fork height to initiate fork logic func NewImageConfig() ImageConfig { diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index ac8e5f923..c94ef9662 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -1,6 +1,7 @@ package initialization import ( + "cosmossdk.io/math" "encoding/json" "fmt" "path/filepath" @@ -65,9 +66,9 @@ const ( ) var ( - StakeAmountIntA = sdk.NewInt(StakeAmountA) + StakeAmountIntA = math.NewInt(StakeAmountA) StakeAmountCoinA = sdk.NewCoin(BabylonDenom, StakeAmountIntA) - StakeAmountIntB = sdk.NewInt(StakeAmountB) + StakeAmountIntB = math.NewInt(StakeAmountB) StakeAmountCoinB = sdk.NewCoin(BabylonDenom, StakeAmountIntB) InitBalanceStrA = fmt.Sprintf("%d%s", BabylonBalanceA, BabylonDenom) @@ -297,7 +298,7 @@ func updateStakeGenesis(stakeGenState *staketypes.GenesisState) { MaxEntries: 7, HistoricalEntries: 10000, UnbondingTime: 240000000000, - MinCommissionRate: sdk.ZeroDec(), + MinCommissionRate: math.LegacyZeroDec(), } } @@ -370,7 +371,7 @@ func updateCheckpointingGenesis(c *internalChain) func(*checkpointingtypes.Genes panic("It should be possible to build proof of possesion from validator private keys") } - valPubKey, err := cryptocodec.FromTmPubKeyInterface(node.consensusKey.PubKey) + valPubKey, err := cryptocodec.FromCmtPubKeyInterface(node.consensusKey.PubKey) if err != nil { panic("It should be possible to retrieve validator public key") diff --git a/test/e2e/initialization/export.go b/test/e2e/initialization/export.go index 570a0cd47..1da2629ba 100644 --- a/test/e2e/initialization/export.go +++ b/test/e2e/initialization/export.go @@ -17,7 +17,7 @@ type Node struct { Mnemonic string `json:"mnemonic"` PublicAddress string `json:"publicAddress"` SecretKey cryptotypes.PrivKey - PublicKey string `json:"publicKey"` + PublicKey []byte `json:"publicKey"` PeerId string `json:"peerId"` IsValidator bool `json:"isValidator"` } diff --git a/test/e2e/initialization/node.go b/test/e2e/initialization/node.go index 1f0872391..2acae3ba0 100644 --- a/test/e2e/initialization/node.go +++ b/test/e2e/initialization/node.go @@ -1,8 +1,17 @@ package initialization import ( + "cosmossdk.io/log" + "cosmossdk.io/math" "encoding/json" "fmt" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + cmtos "github.com/cometbft/cometbft/libs/os" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/client/flags" + simsutils "github.com/cosmos/cosmos-sdk/testutil/sims" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "os" "path" "path/filepath" @@ -11,11 +20,10 @@ import ( "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/privval" bbn "github.com/babylonchain/babylon/types" - tmconfig "github.com/cometbft/cometbft/config" - tmed25519 "github.com/cometbft/cometbft/crypto/ed25519" - tmos "github.com/cometbft/cometbft/libs/os" + cmtconfig "github.com/cometbft/cometbft/config" + cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/p2p" - tmtypes "github.com/cometbft/cometbft/types" + cmttypes "github.com/cometbft/cometbft/types" sdkcrypto "github.com/cosmos/cosmos-sdk/crypto" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/hd" @@ -25,8 +33,7 @@ import ( srvconfig "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" - txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + sdksigning "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/genutil" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/go-bip39" @@ -81,15 +88,15 @@ func (n *internalNode) configDir() string { func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin) (sdk.Msg, error) { description := stakingtypes.NewDescription(n.moniker, "", "", "", "") commissionRates := stakingtypes.CommissionRates{ - Rate: sdk.MustNewDecFromStr("0.1"), - MaxRate: sdk.MustNewDecFromStr("0.2"), - MaxChangeRate: sdk.MustNewDecFromStr("0.01"), + Rate: math.LegacyMustNewDecFromStr("0.1"), + MaxRate: math.LegacyMustNewDecFromStr("0.2"), + MaxChangeRate: math.LegacyMustNewDecFromStr("0.01"), } // get the initial validator min self delegation - minSelfDelegation, _ := sdk.NewIntFromString("1") + minSelfDelegation, _ := math.NewIntFromString("1") - valPubKey, err := cryptocodec.FromTmPubKeyInterface(n.consensusKey.PubKey) + valPubKey, err := cryptocodec.FromCmtPubKeyInterface(n.consensusKey.PubKey) if err != nil { return nil, err } @@ -101,7 +108,7 @@ func (n *internalNode) buildCreateValidatorMsg(amount sdk.Coin) (sdk.Msg, error) } return stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), + sdk.ValAddress(addr).String(), valPubKey, amount, description, @@ -164,17 +171,17 @@ func (n *internalNode) createConsensusKey() error { config.Moniker = n.moniker pvKeyFile := config.PrivValidatorKeyFile() - if err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { + if err := cmtos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { return err } pvStateFile := config.PrivValidatorStateFile() - if err := tmos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { + if err := cmtos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { return err } // privval.LoadOrGenWrappedFilePV() - privKey := tmed25519.GenPrivKeyFromSecret([]byte(n.mnemonic)) + privKey := cmted25519.GenPrivKeyFromSecret([]byte(n.mnemonic)) blsPrivKey := bls12381.GenPrivKeyFromSecret([]byte(n.mnemonic)) filePV := privval.NewWrappedFilePV(privKey, blsPrivKey, pvKeyFile, pvStateFile) @@ -249,7 +256,7 @@ func (n *internalNode) export() *Node { Mnemonic: n.mnemonic, PublicAddress: addr.String(), SecretKey: n.privateKey, - PublicKey: pub.String(), + PublicKey: pub.Bytes(), PeerId: n.peerId, IsValidator: n.isValidator, } @@ -259,13 +266,13 @@ func (n *internalNode) getNodeKey() *p2p.NodeKey { return &n.nodeKey } -func (n *internalNode) getGenesisDoc() (*tmtypes.GenesisDoc, error) { +func (n *internalNode) getAppGenesis() (*genutiltypes.AppGenesis, error) { serverCtx := server.NewDefaultContext() config := serverCtx.Config config.SetRoot(n.configDir()) genFile := config.GenesisFile() - doc := &tmtypes.GenesisDoc{} + appGenesis := &genutiltypes.AppGenesis{} if _, err := os.Stat(genFile); err != nil { if !os.IsNotExist(err) { @@ -274,13 +281,13 @@ func (n *internalNode) getGenesisDoc() (*tmtypes.GenesisDoc, error) { } else { var err error - doc, err = tmtypes.GenesisDocFromFile(genFile) + _, appGenesis, err = genutiltypes.GenesisStateFromGenFile(genFile) if err != nil { return nil, fmt.Errorf("failed to read genesis doc from file: %w", err) } } - return doc, nil + return appGenesis, nil } func (n *internalNode) init() error { @@ -294,27 +301,45 @@ func (n *internalNode) init() error { config.SetRoot(n.configDir()) config.Moniker = n.moniker - genDoc, err := n.getGenesisDoc() + appOptions := make(simsutils.AppOptionsMap, 0) + appOptions[flags.FlagHome] = n.configDir() + appOptions["btc-config.network"] = string(bbn.BtcSimnet) + + privSigner, _ := babylonApp.SetupPrivSigner() + // Create a temp app to get the default genesis state + tempApp := babylonApp.NewBabylonApp( + log.NewNopLogger(), + dbm.NewMemDB(), + nil, + true, + map[int64]bool{}, + 0, + privSigner, + appOptions, + []wasmkeeper.Option{}) + + appGenesis, err := n.getAppGenesis() if err != nil { return err } - appState, err := json.MarshalIndent(babylonApp.ModuleBasics.DefaultGenesis(util.Cdc), "", " ") + appState, err := json.MarshalIndent(tempApp.DefaultGenesis(), "", " ") if err != nil { return fmt.Errorf("failed to JSON encode app genesis state: %w", err) } - genDoc.ChainID = n.chain.chainMeta.Id - genDoc.Validators = nil - genDoc.AppState = appState - genDoc.ConsensusParams = tmtypes.DefaultConsensusParams() - genDoc.ConsensusParams.Block.MaxGas = babylonApp.DefaultGasLimit + appGenesis.ChainID = n.chain.chainMeta.Id + appGenesis.AppState = appState + appGenesis.Consensus = &genutiltypes.ConsensusGenesis{ + Params: cmttypes.DefaultConsensusParams(), + } + appGenesis.Consensus.Params.Block.MaxGas = babylonApp.DefaultGasLimit - if err = genutil.ExportGenesisFile(genDoc, config.GenesisFile()); err != nil { + if err = genutil.ExportGenesisFile(appGenesis, config.GenesisFile()); err != nil { return fmt.Errorf("failed to export app genesis state: %w", err) } - tmconfig.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + cmtconfig.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) return nil } @@ -333,15 +358,15 @@ func (n *internalNode) createMnemonic() (string, error) { } func (n *internalNode) initNodeConfigs(persistentPeers []string) error { - tmCfgPath := filepath.Join(n.configDir(), "config", "config.toml") + cmtCfgPath := filepath.Join(n.configDir(), "config", "config.toml") vpr := viper.New() - vpr.SetConfigFile(tmCfgPath) + vpr.SetConfigFile(cmtCfgPath) if err := vpr.ReadInConfig(); err != nil { return err } - valConfig := tmconfig.DefaultConfig() + valConfig := cmtconfig.DefaultConfig() if err := vpr.Unmarshal(valConfig); err != nil { return err } @@ -355,31 +380,31 @@ func (n *internalNode) initNodeConfigs(persistentPeers []string) error { valConfig.P2P.PersistentPeers = strings.Join(persistentPeers, ",") valConfig.Storage.DiscardABCIResponses = true - tmconfig.WriteConfigFile(tmCfgPath, valConfig) + cmtconfig.WriteConfigFile(cmtCfgPath, valConfig) return nil } func (n *internalNode) initStateSyncConfig(trustHeight int64, trustHash string, stateSyncRPCServers []string) error { - tmCfgPath := filepath.Join(n.configDir(), "config", "config.toml") + cmtCfgPath := filepath.Join(n.configDir(), "config", "config.toml") vpr := viper.New() - vpr.SetConfigFile(tmCfgPath) + vpr.SetConfigFile(cmtCfgPath) if err := vpr.ReadInConfig(); err != nil { return err } - valConfig := tmconfig.DefaultConfig() + valConfig := cmtconfig.DefaultConfig() if err := vpr.Unmarshal(valConfig); err != nil { return err } - valConfig.StateSync = tmconfig.DefaultStateSyncConfig() + valConfig.StateSync = cmtconfig.DefaultStateSyncConfig() valConfig.StateSync.Enable = true valConfig.StateSync.TrustHeight = trustHeight valConfig.StateSync.TrustHash = trustHash valConfig.StateSync.RPCServers = stateSyncRPCServers - tmconfig.WriteConfigFile(tmCfgPath, valConfig) + cmtconfig.WriteConfigFile(cmtCfgPath, valConfig) return nil } @@ -396,18 +421,23 @@ func (n *internalNode) signMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) { txBuilder.SetFeeAmount(sdk.NewCoins()) txBuilder.SetGasLimit(uint64(200000 * len(msgs))) + addr, err := n.keyInfo.GetAddress() + if err != nil { + return nil, err + } + pub, err := n.keyInfo.GetPubKey() + if err != nil { + return nil, err + } // TODO: Find a better way to sign this tx with less code. signerData := authsigning.SignerData{ ChainID: n.chain.chainMeta.Id, AccountNumber: 0, Sequence: 0, + Address: addr.String(), + PubKey: pub, } - pub, err := n.keyInfo.GetPubKey() - - if err != nil { - return nil, err - } // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on // TxBuilder under the hood, and SignerInfos is needed to generate the sign // bytes. This is the reason for setting SetSignatures here, with a nil @@ -416,10 +446,10 @@ func (n *internalNode) signMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) { // Note: This line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it // also doesn't affect its generated sign bytes, so for code's simplicity // sake, we put it here. - sig := txsigning.SignatureV2{ + sig := sdksigning.SignatureV2{ PubKey: pub, - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Data: &sdksigning.SingleSignatureData{ + SignMode: sdksigning.SignMode_SIGN_MODE_DIRECT, Signature: nil, }, Sequence: 0, @@ -429,8 +459,10 @@ func (n *internalNode) signMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) { return nil, err } - bytesToSign, err := util.EncodingConfig.TxConfig.SignModeHandler().GetSignBytes( - txsigning.SignMode_SIGN_MODE_DIRECT, + bytesToSign, err := authsigning.GetSignBytesAdapter( + sdk.Context{}, // TODO: this is an empty context + util.EncodingConfig.TxConfig.SignModeHandler(), + sdksigning.SignMode_SIGN_MODE_DIRECT, signerData, txBuilder.GetTx(), ) @@ -447,10 +479,10 @@ func (n *internalNode) signMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) { return nil, err } - sig = txsigning.SignatureV2{ + sig = sdksigning.SignatureV2{ PubKey: pub, - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Data: &sdksigning.SingleSignatureData{ + SignMode: sdksigning.SignMode_SIGN_MODE_DIRECT, Signature: sigBytes, }, Sequence: 0, diff --git a/test/e2e/scripts/hermes_bootstrap.sh b/test/e2e/scripts/hermes_bootstrap.sh index cfb3702bd..7203da8b5 100644 --- a/test/e2e/scripts/hermes_bootstrap.sh +++ b/test/e2e/scripts/hermes_bootstrap.sh @@ -38,7 +38,7 @@ port = 3001 id = '$BBN_A_E2E_CHAIN_ID' rpc_addr = 'http://$BBN_A_E2E_VAL_HOST:26657' grpc_addr = 'http://$BBN_A_E2E_VAL_HOST:9090' -websocket_addr = 'ws://$BBN_A_E2E_VAL_HOST:26657/websocket' +event_source = { mode = 'push', url = 'ws://$BBN_A_E2E_VAL_HOST:26657/websocket', batch_delay = '500ms' } rpc_timeout = '10s' account_prefix = 'bbn' key_name = 'val01-bbn-a' @@ -53,7 +53,7 @@ trust_threshold = { numerator = '1', denominator = '3' } id = '$BBN_B_E2E_CHAIN_ID' rpc_addr = 'http://$BBN_B_E2E_VAL_HOST:26657' grpc_addr = 'http://$BBN_B_E2E_VAL_HOST:9090' -websocket_addr = 'ws://$BBN_B_E2E_VAL_HOST:26657/websocket' +event_source = { mode = 'push', url = 'ws://$BBN_A_E2E_VAL_HOST:26657/websocket', batch_delay = '500ms' } rpc_timeout = '10s' account_prefix = 'bbn' key_name = 'val01-bbn-b' diff --git a/test/e2e/util/codec.go b/test/e2e/util/codec.go index 73e4bb911..fd633f711 100644 --- a/test/e2e/util/codec.go +++ b/test/e2e/util/codec.go @@ -1,7 +1,7 @@ package util import ( - babylonApp "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/app/params" "github.com/cosmos/cosmos-sdk/codec" @@ -22,7 +22,7 @@ func init() { } func initEncodingConfigAndCdc() (params.EncodingConfig, codec.Codec) { - encodingConfig := babylonApp.GetEncodingConfig() + encodingConfig := app.GetEncodingConfig() encodingConfig.InterfaceRegistry.RegisterImplementations( (*sdk.Msg)(nil), @@ -34,7 +34,7 @@ func initEncodingConfigAndCdc() (params.EncodingConfig, codec.Codec) { &ed25519.PubKey{}, ) - cdc := encodingConfig.Marshaler + cdc := encodingConfig.Codec - return encodingConfig, cdc + return *encodingConfig, cdc } diff --git a/testutil/datagen/account_balance.go b/testutil/datagen/account_balance.go index 5055c4896..5530ecea5 100644 --- a/testutil/datagen/account_balance.go +++ b/testutil/datagen/account_balance.go @@ -1,6 +1,8 @@ package datagen import ( + sdkmath "cosmossdk.io/math" + appparams "github.com/babylonchain/babylon/app/params" sec256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -22,7 +24,7 @@ func GenRandomAccWithBalance(n int) ([]authtypes.GenesisAccount, []banktypes.Bal accs[i] = acc balance := banktypes.Balance{ Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), + Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdkmath.NewInt(100000000000000))), } balances[i] = balance } diff --git a/testutil/datagen/btc_header_info.go b/testutil/datagen/btc_header_info.go index 25284ee19..7cd54ebf9 100644 --- a/testutil/datagen/btc_header_info.go +++ b/testutil/datagen/btc_header_info.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" ) type RetargetInfo struct { @@ -93,7 +92,7 @@ func GenRandomBTCHeaderBits(r *rand.Rand) uint32 { if difficulty == 0 { difficulty += 1 } - bigDifficulty := sdk.NewUint(difficulty) + bigDifficulty := sdkmath.NewUint(difficulty) workBits := blockchain.BigToCompact(bigDifficulty.BigInt()) return workBits diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 3e0dc8509..e8d1a8648 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -35,7 +35,7 @@ var ( type testCheckpointData struct { epoch uint64 - lastCommitHash []byte + appHash []byte bitmap []byte blsSig []byte submitterAddress []byte @@ -460,7 +460,7 @@ func GenerateMessageWithRandomSubmitter(blockResults []*BlockCreationResult) *bt func getRandomCheckpointDataForEpoch(r *rand.Rand, e uint64) testCheckpointData { return testCheckpointData{ epoch: e, - lastCommitHash: GenRandomByteArray(r, txformat.LastCommitHashLength), + appHash: GenRandomByteArray(r, txformat.AppHashLength), bitmap: GenRandomByteArray(r, txformat.BitMapLength), blsSig: GenRandomByteArray(r, txformat.BlsSigLength), submitterAddress: GenRandomByteArray(r, txformat.AddressLength), @@ -504,7 +504,7 @@ func RandomRawCheckpointDataForEpoch(r *rand.Rand, e uint64) (*TestRawCheckpoint checkpointData := getRandomCheckpointDataForEpoch(r, e) rawBTCCkpt := &txformat.RawBtcCheckpoint{ Epoch: checkpointData.epoch, - LastCommitHash: checkpointData.lastCommitHash, + AppHash: checkpointData.appHash, BitMap: checkpointData.bitmap, SubmitterAddress: checkpointData.submitterAddress, BlsSig: checkpointData.blsSig, diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 71db336fe..7a6f5a54b 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -1,6 +1,7 @@ package datagen import ( + sdkmath "cosmossdk.io/math" "fmt" "math/rand" "testing" @@ -15,7 +16,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" ) @@ -39,7 +39,7 @@ func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bst func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKey, bbnSK cryptotypes.PrivKey) (*bstypes.BTCValidator, error) { // commission - commission := sdk.NewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] + commission := sdkmath.LegacyNewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] // description description := stakingtypes.Description{} // key pairs @@ -73,7 +73,7 @@ func GenRandomBTCDelegation( covenantThreshold uint32, slashingAddress, changeAddress string, startHeight, endHeight, totalSat uint64, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) (*bstypes.BTCDelegation, error) { net := &chaincfg.SimNetParams delPK := delSK.PubKey() @@ -181,7 +181,7 @@ func GenBTCStakingSlashingTxWithOutPoint( stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) *TestStakingSlashingInfo { stakingInfo, err := btcstaking.BuildStakingInfo( @@ -242,7 +242,7 @@ func GenBTCStakingSlashingTx( stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) *TestStakingSlashingInfo { // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) @@ -274,7 +274,7 @@ func GenBTCUnbondingSlashingTx( stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) *TestUnbondingSlashingInfo { unbondingInfo, err := btcstaking.BuildUnbondingInfo( diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index ae6ce6886..11cf971cc 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -58,25 +58,25 @@ func GenRandomEvidence(r *rand.Rand, sk *btcec.PrivateKey, height uint64) (*ftyp if err != nil { return nil, err } - cLch := GenRandomByteArray(r, 32) - cSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), cLch...)) + cAppHash := GenRandomByteArray(r, 32) + cSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), cAppHash...)) if err != nil { return nil, err } - fLch := GenRandomByteArray(r, 32) - fSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), fLch...)) + fAppHash := GenRandomByteArray(r, 32) + fSig, err := eots.Sign(sk, sr, append(sdk.Uint64ToBigEndian(height), fAppHash...)) if err != nil { return nil, err } evidence := &ftypes.Evidence{ - ValBtcPk: bip340PK, - BlockHeight: height, - PubRand: bbn.NewSchnorrPubRandFromFieldVal(pr), - CanonicalLastCommitHash: cLch, - ForkLastCommitHash: fLch, + ValBtcPk: bip340PK, + BlockHeight: height, + PubRand: bbn.NewSchnorrPubRandFromFieldVal(pr), + CanonicalAppHash: cAppHash, + ForkAppHash: fAppHash, CanonicalFinalitySig: bbn.NewSchnorrEOTSSigFromModNScalar(cSig), - ForkFinalitySig: bbn.NewSchnorrEOTSSigFromModNScalar(fSig), + ForkFinalitySig: bbn.NewSchnorrEOTSSigFromModNScalar(fSig), } return evidence, nil -} \ No newline at end of file +} diff --git a/testutil/datagen/genesiskey.go b/testutil/datagen/genesiskey.go index 89e2ee6ff..631b24d39 100644 --- a/testutil/datagen/genesiskey.go +++ b/testutil/datagen/genesiskey.go @@ -15,7 +15,7 @@ func GenerateGenesisKey() *types.GenesisKey { tmValPrivKey := ed255192.GenPrivKey() blsPrivKey := bls12381.GenPrivKey() tmValPubKey := tmValPrivKey.PubKey() - valPubKey, err := codec.FromTmPubKeyInterface(tmValPubKey) + valPubKey, err := codec.FromCmtPubKeyInterface(tmValPubKey) if err != nil { panic(err) } diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index b06bd5e18..e1741a836 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -3,10 +3,11 @@ package datagen import ( "math/rand" + sdkmath "cosmossdk.io/math" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" itypes "github.com/babylonchain/babylon/x/incentive/types" - secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -63,7 +64,7 @@ func GenRandomWithdrawnCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins { // a subset of the coin has been withdrawn amount := coin.Amount.Uint64() withdrawnAmount := RandomInt(r, int(amount)-1) + 1 - withdrawnCoin := sdk.NewCoin(coin.Denom, sdk.NewIntFromUint64(withdrawnAmount)) + withdrawnCoin := sdk.NewCoin(coin.Denom, sdkmath.NewIntFromUint64(withdrawnAmount)) withdrawnCoins = withdrawnCoins.Add(withdrawnCoin) } return withdrawnCoins diff --git a/testutil/datagen/priv_validator.go b/testutil/datagen/priv_validator.go index 42af77a70..9e29d3914 100644 --- a/testutil/datagen/priv_validator.go +++ b/testutil/datagen/priv_validator.go @@ -27,7 +27,7 @@ func NewPV() PV { // GetPubKey implements PrivValidator interface func (pv PV) GetPubKey() (crypto.PubKey, error) { - return cryptocodec.ToTmPubKeyInterface(pv.PrivKey.PubKey()) + return cryptocodec.ToCmtPubKeyInterface(pv.PrivKey.PubKey()) } // SignVote implements PrivValidator interface diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index 37cb771bf..8e76b28ac 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -29,7 +29,7 @@ func GetRandomRawBtcCheckpoint(r *rand.Rand) *btctxformatter.RawBtcCheckpoint { rawCkpt := GenRandomRawCheckpoint(r) return &btctxformatter.RawBtcCheckpoint{ Epoch: rawCkpt.EpochNum, - LastCommitHash: *rawCkpt.LastCommitHash, + AppHash: *rawCkpt.AppHash, BitMap: rawCkpt.Bitmap, SubmitterAddress: GenRandomByteArray(r, btctxformatter.AddressLength), BlsSig: rawCkpt.BlsMultiSig.Bytes(), @@ -46,13 +46,13 @@ func GenRandomRawCheckpointWithMeta(r *rand.Rand) *types.RawCheckpointWithMeta { } func GenRandomRawCheckpoint(r *rand.Rand) *types.RawCheckpoint { - randomHashBytes := GenRandomLastCommitHash(r) + randomHashBytes := GenRandomAppHash(r) randomBLSSig := GenRandomBlsMultiSig(r) return &types.RawCheckpoint{ - EpochNum: GenRandomEpochNum(r), - LastCommitHash: &randomHashBytes, - Bitmap: bitmap.New(types.BitmapBits), - BlsMultiSig: &randomBLSSig, + EpochNum: GenRandomEpochNum(r), + AppHash: &randomHashBytes, + Bitmap: bitmap.New(types.BitmapBits), + BlsMultiSig: &randomBLSSig, } } @@ -110,8 +110,8 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe // ensure sufficient signers signerNum := n/3 + 1 epochNum := GenRandomEpochNum(r) - lch := GenRandomLastCommitHash(r) - msgBytes := types.GetSignBytes(epochNum, lch) + appHash := GenRandomAppHash(r) + msgBytes := types.GetSignBytes(epochNum, appHash) sigs := GenerateBLSSigs(privKeys[:signerNum], msgBytes) multiSig, _ := bls12381.AggrSigList(sigs) bm := bitmap.New(types.BitmapBits) @@ -119,16 +119,16 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe bm.Set(i, true) } btcCheckpoint := &types.RawCheckpoint{ - EpochNum: epochNum, - LastCommitHash: &lch, - Bitmap: bm, - BlsMultiSig: &multiSig, + EpochNum: epochNum, + AppHash: &appHash, + Bitmap: bm, + BlsMultiSig: &multiSig, } return btcCheckpoint } -func GenRandomLastCommitHash(r *rand.Rand) types.LastCommitHash { +func GenRandomAppHash(r *rand.Rand) types.AppHash { return GenRandomByteArray(r, types.HashSize) } diff --git a/testutil/datagen/tendermint.go b/testutil/datagen/tendermint.go index a0a44edef..6a87e9095 100644 --- a/testutil/datagen/tendermint.go +++ b/testutil/datagen/tendermint.go @@ -6,7 +6,7 @@ import ( zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) func GenRandomTMHeader(r *rand.Rand, chainID string, height uint64) *tmproto.Header { @@ -14,7 +14,7 @@ func GenRandomTMHeader(r *rand.Rand, chainID string, height uint64) *tmproto.Hea ChainID: chainID, Height: int64(height), Time: time.Now(), - LastCommitHash: GenRandomByteArray(r, 32), + AppHash: GenRandomByteArray(r, 32), } } @@ -24,7 +24,7 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp Header: &tmproto.Header{ ChainID: chainID, Height: int64(height), - LastCommitHash: GenRandomByteArray(r, 32), + AppHash: GenRandomByteArray(r, 32), }, }, } @@ -32,7 +32,7 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp func HeaderToHeaderInfo(header *ibctmtypes.Header) *zctypes.HeaderInfo { return &zctypes.HeaderInfo{ - Hash: header.Header.LastCommitHash, + Hash: header.Header.AppHash, ChainId: header.Header.ChainID, Height: uint64(header.Header.Height), } diff --git a/testutil/keeper/btccheckpoint.go b/testutil/keeper/btccheckpoint.go index 22a5860aa..9b7e36e4c 100644 --- a/testutil/keeper/btccheckpoint.go +++ b/testutil/keeper/btccheckpoint.go @@ -1,19 +1,21 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + "github.com/cosmos/cosmos-sdk/runtime" "testing" "math/big" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/btccheckpoint/keeper" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -26,14 +28,12 @@ func NewBTCCheckpointKeeper( ek btcctypes.CheckpointingKeeper, ik btcctypes.IncentiveKeeper, powLimit *big.Int) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(btcctypes.StoreKey) - tstoreKey := sdk.NewTransientStoreKey(btcctypes.TStoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(btcctypes.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(btcctypes.StoreKey) + tstoreKey := storetypes.NewTransientStoreKey(btcctypes.TStoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -41,9 +41,8 @@ func NewBTCCheckpointKeeper( k := keeper.NewKeeper( cdc, - storeKey, + runtime.NewKVStoreService(storeKey), tstoreKey, - memStoreKey, lk, ek, ik, diff --git a/testutil/keeper/btclightclient.go b/testutil/keeper/btclightclient.go index f6b887b3f..52ffa7478 100644 --- a/testutil/keeper/btclightclient.go +++ b/testutil/keeper/btclightclient.go @@ -1,31 +1,31 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" bapp "github.com/babylonchain/babylon/app" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func BTCLightClientKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -35,8 +35,7 @@ func BTCLightClientKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), testCfg, ) diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index 45c2af94a..350e53900 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -1,18 +1,20 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -20,13 +22,11 @@ import ( ) func BTCStakingKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -34,8 +34,7 @@ func BTCStakingKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, btcc k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), btclcKeeper, btccKeeper, &chaincfg.SimNetParams, diff --git a/testutil/keeper/checkpointing.go b/testutil/keeper/checkpointing.go index 895ae3ead..6e73487e8 100644 --- a/testutil/keeper/checkpointing.go +++ b/testutil/keeper/checkpointing.go @@ -1,30 +1,30 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.BlsSigner, cliCtx client.Context) (*keeper.Keeper, sdk.Context, *codec.ProtoCodec) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -33,8 +33,7 @@ func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.Bl k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), signer, ek, cliCtx, diff --git a/testutil/keeper/epoching.go b/testutil/keeper/epoching.go index d1dab7cdc..c142ac994 100644 --- a/testutil/keeper/epoching.go +++ b/testutil/keeper/epoching.go @@ -1,15 +1,17 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + "github.com/cosmos/cosmos-sdk/runtime" "testing" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -20,13 +22,11 @@ import ( ) func EpochingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -34,8 +34,7 @@ func EpochingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), // TODO: make this compile at the moment, will fix for integrated testing nil, nil, diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index 7a8d056f6..c9de2df4b 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -1,17 +1,19 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -19,13 +21,11 @@ import ( ) func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types.IncentiveKeeper) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -33,10 +33,7 @@ func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, - nil, - nil, + runtime.NewKVStoreService(storeKey), bsKeeper, iKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go index 471402eaf..0ffdb9d21 100644 --- a/testutil/keeper/incentive.go +++ b/testutil/keeper/incentive.go @@ -1,17 +1,19 @@ package keeper import ( + storemetrics "cosmossdk.io/store/metrics" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/incentive/keeper" "github.com/babylonchain/babylon/x/incentive/types" - tmdb "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -19,13 +21,11 @@ import ( ) func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, epochingKeeper types.EpochingKeeper) (*keeper.Keeper, sdk.Context) { - storeKey := sdk.NewKVStoreKey(types.StoreKey) - memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -33,8 +33,7 @@ func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper ty k := keeper.NewKeeper( cdc, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), bankKeeper, accountKeeper, epochingKeeper, diff --git a/testutil/keeper/zoneconcierge.go b/testutil/keeper/zoneconcierge.go index 819d1348a..692f01bd4 100644 --- a/testutil/keeper/zoneconcierge.go +++ b/testutil/keeper/zoneconcierge.go @@ -1,26 +1,27 @@ package keeper import ( + "cosmossdk.io/store/metrics" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/log" + "cosmossdk.io/store" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/zoneconcierge/keeper" "github.com/babylonchain/babylon/x/zoneconcierge/types" - tmdb "github.com/cometbft/cometbft-db" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/libs/log" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" "github.com/stretchr/testify/require" ) @@ -56,24 +57,23 @@ func (zoneconciergePortKeeper) BindPort(ctx sdk.Context, portID string) *capabil type zoneconciergeStoreQuerier struct{} -func (zoneconciergeStoreQuerier) Query(req abci.RequestQuery) abci.ResponseQuery { - return abci.ResponseQuery{ +func (zoneconciergeStoreQuerier) Query(req *storetypes.RequestQuery) (*storetypes.ResponseQuery, error) { + return &storetypes.ResponseQuery{ ProofOps: &tmcrypto.ProofOps{ Ops: []tmcrypto.ProofOp{ tmcrypto.ProofOp{}, }, }, - } + }, nil } -func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper, tmClient types.TMClient) (*keeper.Keeper, sdk.Context) { - logger := log.NewNopLogger() - - storeKey := sdk.NewKVStoreKey(types.StoreKey) +func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper, cmtClient types.CometClient) (*keeper.Keeper, sdk.Context) { + logger := log.NewTestLogger(t) + storeKey := storetypes.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, logger, metrics.NewNoOpMetrics()) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) @@ -83,8 +83,7 @@ func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, c capabilityKeeper := capabilitykeeper.NewKeeper(appCodec, storeKey, memStoreKey) k := keeper.NewKeeper( appCodec, - storeKey, - memStoreKey, + runtime.NewKVStoreService(storeKey), nil, // TODO: mock this keeper nil, // TODO: mock this keeper zoneconciergeChannelKeeper{}, @@ -95,7 +94,7 @@ func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, c checkpointingKeeper, btccKeeper, epochingKeeper, - tmClient, + cmtClient, zoneconciergeStoreQuerier{}, capabilityKeeper.ScopeToModule("ZoneconciergeScopedKeeper"), authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/testutil/mocks/checkpointing_expected_keepers.go b/testutil/mocks/checkpointing_expected_keepers.go index 2c7327500..45aeab84f 100644 --- a/testutil/mocks/checkpointing_expected_keepers.go +++ b/testutil/mocks/checkpointing_expected_keepers.go @@ -5,90 +5,16 @@ package mocks import ( + context "context" reflect "reflect" types "github.com/babylonchain/babylon/x/checkpointing/types" types0 "github.com/babylonchain/babylon/x/epoching/types" types1 "github.com/cosmos/cosmos-sdk/types" - types2 "github.com/cosmos/cosmos-sdk/x/auth/types" - types3 "github.com/cosmos/cosmos-sdk/x/staking/types" + types2 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" ) -// MockAccountKeeper is a mock of AccountKeeper interface. -type MockAccountKeeper struct { - ctrl *gomock.Controller - recorder *MockAccountKeeperMockRecorder -} - -// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. -type MockAccountKeeperMockRecorder struct { - mock *MockAccountKeeper -} - -// NewMockAccountKeeper creates a new mock instance. -func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { - mock := &MockAccountKeeper{ctrl: ctrl} - mock.recorder = &MockAccountKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { - return m.recorder -} - -// GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx types1.Context, addr types1.AccAddress) types2.AccountI { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types2.AccountI) - return ret0 -} - -// GetAccount indicates an expected call of GetAccount. -func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) -} - -// MockBankKeeper is a mock of BankKeeper interface. -type MockBankKeeper struct { - ctrl *gomock.Controller - recorder *MockBankKeeperMockRecorder -} - -// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. -type MockBankKeeperMockRecorder struct { - mock *MockBankKeeper -} - -// NewMockBankKeeper creates a new mock instance. -func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { - mock := &MockBankKeeper{ctrl: ctrl} - mock.recorder = &MockBankKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { - return m.recorder -} - -// SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx types1.Context, addr types1.AccAddress) types1.Coins { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) - ret0, _ := ret[0].(types1.Coins) - return ret0 -} - -// SpendableCoins indicates an expected call of SpendableCoins. -func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) -} - // MockEpochingKeeper is a mock of EpochingKeeper interface. type MockEpochingKeeper struct { ctrl *gomock.Controller @@ -113,7 +39,7 @@ func (m *MockEpochingKeeper) EXPECT() *MockEpochingKeeperMockRecorder { } // CheckMsgCreateValidator mocks base method. -func (m *MockEpochingKeeper) CheckMsgCreateValidator(ctx types1.Context, msg *types3.MsgCreateValidator) error { +func (m *MockEpochingKeeper) CheckMsgCreateValidator(ctx context.Context, msg *types2.MsgCreateValidator) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckMsgCreateValidator", ctx, msg) ret0, _ := ret[0].(error) @@ -127,7 +53,7 @@ func (mr *MockEpochingKeeperMockRecorder) CheckMsgCreateValidator(ctx, msg inter } // EnqueueMsg mocks base method. -func (m *MockEpochingKeeper) EnqueueMsg(ctx types1.Context, msg types0.QueuedMessage) { +func (m *MockEpochingKeeper) EnqueueMsg(ctx context.Context, msg types0.QueuedMessage) { m.ctrl.T.Helper() m.ctrl.Call(m, "EnqueueMsg", ctx, msg) } @@ -139,7 +65,7 @@ func (mr *MockEpochingKeeperMockRecorder) EnqueueMsg(ctx, msg interface{}) *gomo } // GetEpoch mocks base method. -func (m *MockEpochingKeeper) GetEpoch(ctx types1.Context) *types0.Epoch { +func (m *MockEpochingKeeper) GetEpoch(ctx context.Context) *types0.Epoch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpoch", ctx) ret0, _ := ret[0].(*types0.Epoch) @@ -153,7 +79,7 @@ func (mr *MockEpochingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call } // GetTotalVotingPower mocks base method. -func (m *MockEpochingKeeper) GetTotalVotingPower(ctx types1.Context, epochNumber uint64) int64 { +func (m *MockEpochingKeeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTotalVotingPower", ctx, epochNumber) ret0, _ := ret[0].(int64) @@ -167,7 +93,7 @@ func (mr *MockEpochingKeeperMockRecorder) GetTotalVotingPower(ctx, epochNumber i } // GetValidatorSet mocks base method. -func (m *MockEpochingKeeper) GetValidatorSet(ctx types1.Context, epochNumer uint64) types0.ValidatorSet { +func (m *MockEpochingKeeper) GetValidatorSet(ctx context.Context, epochNumer uint64) types0.ValidatorSet { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorSet", ctx, epochNumer) ret0, _ := ret[0].(types0.ValidatorSet) @@ -204,7 +130,7 @@ func (m *MockCheckpointingHooks) EXPECT() *MockCheckpointingHooksMockRecorder { } // AfterBlsKeyRegistered mocks base method. -func (m *MockCheckpointingHooks) AfterBlsKeyRegistered(ctx types1.Context, valAddr types1.ValAddress) error { +func (m *MockCheckpointingHooks) AfterBlsKeyRegistered(ctx context.Context, valAddr types1.ValAddress) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AfterBlsKeyRegistered", ctx, valAddr) ret0, _ := ret[0].(error) @@ -218,7 +144,7 @@ func (mr *MockCheckpointingHooksMockRecorder) AfterBlsKeyRegistered(ctx, valAddr } // AfterRawCheckpointBlsSigVerified mocks base method. -func (m *MockCheckpointingHooks) AfterRawCheckpointBlsSigVerified(ctx types1.Context, ckpt *types.RawCheckpoint) error { +func (m *MockCheckpointingHooks) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *types.RawCheckpoint) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AfterRawCheckpointBlsSigVerified", ctx, ckpt) ret0, _ := ret[0].(error) @@ -232,7 +158,7 @@ func (mr *MockCheckpointingHooksMockRecorder) AfterRawCheckpointBlsSigVerified(c } // AfterRawCheckpointConfirmed mocks base method. -func (m *MockCheckpointingHooks) AfterRawCheckpointConfirmed(ctx types1.Context, epoch uint64) error { +func (m *MockCheckpointingHooks) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AfterRawCheckpointConfirmed", ctx, epoch) ret0, _ := ret[0].(error) @@ -246,7 +172,7 @@ func (mr *MockCheckpointingHooksMockRecorder) AfterRawCheckpointConfirmed(ctx, e } // AfterRawCheckpointFinalized mocks base method. -func (m *MockCheckpointingHooks) AfterRawCheckpointFinalized(ctx types1.Context, epoch uint64) error { +func (m *MockCheckpointingHooks) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AfterRawCheckpointFinalized", ctx, epoch) ret0, _ := ret[0].(error) @@ -260,7 +186,7 @@ func (mr *MockCheckpointingHooksMockRecorder) AfterRawCheckpointFinalized(ctx, e } // AfterRawCheckpointForgotten mocks base method. -func (m *MockCheckpointingHooks) AfterRawCheckpointForgotten(ctx types1.Context, ckpt *types.RawCheckpoint) error { +func (m *MockCheckpointingHooks) AfterRawCheckpointForgotten(ctx context.Context, ckpt *types.RawCheckpoint) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AfterRawCheckpointForgotten", ctx, ckpt) ret0, _ := ret[0].(error) diff --git a/types/retry/log.go b/types/retry/log.go index 55c07f6bf..07b95b0f8 100644 --- a/types/retry/log.go +++ b/types/retry/log.go @@ -1,9 +1,9 @@ package retry import ( - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" "os" ) // TODO add log formatters -var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) +var logger = log.NewLogger(os.Stdout) diff --git a/wasmbinding/bindings/utils.go b/wasmbinding/bindings/utils.go index 14f90d3f4..a4c278cd9 100644 --- a/wasmbinding/bindings/utils.go +++ b/wasmbinding/bindings/utils.go @@ -4,7 +4,7 @@ import ( lcTypes "github.com/babylonchain/babylon/x/btclightclient/types" ) -// translate BTCHeaderInfo to BtcBlockHeaderInfo +// AsBtcBlockHeaderInfo translates BTCHeaderInfo to BtcBlockHeaderInfo func AsBtcBlockHeaderInfo(info *lcTypes.BTCHeaderInfo) *BtcBlockHeaderInfo { if info == nil { return nil diff --git a/wasmbinding/test/custom_query_test.go b/wasmbinding/test/custom_query_test.go index 359da5b20..723808fef 100644 --- a/wasmbinding/test/custom_query_test.go +++ b/wasmbinding/test/custom_query_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "cosmossdk.io/math" + "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/babylonchain/babylon/app" @@ -48,7 +50,7 @@ func TestQueryEpoch(t *testing.T) { } resp := bindings.CurrentEpochResponse{} queryCustom(t, ctx, babylonApp, contractAddress, query, &resp) - require.Equal(t, resp.Epoch, uint64(0)) + require.Equal(t, resp.Epoch, uint64(1)) newEpoch := babylonApp.EpochingKeeper.IncEpoch(ctx) @@ -199,7 +201,8 @@ func setupAppWithContext(t *testing.T) (*app.BabylonApp, sdk.Context) { func setupAppWithContextAndCustomHeight(t *testing.T, height int64) (*app.BabylonApp, sdk.Context) { babylonApp := app.Setup(t, false) - ctx := babylonApp.BaseApp.NewContext(false, tmproto.Header{Height: height, Time: time.Now().UTC()}) + ctx := babylonApp.BaseApp.NewContext(false). + WithBlockHeader(tmproto.Header{Height: height, Time: time.Now().UTC()}) return babylonApp, ctx } @@ -234,7 +237,7 @@ func fundAccount( acc sdk.AccAddress) { err := mintCoinsTo(bbn.BankKeeper, ctx, acc, sdk.NewCoins( - sdk.NewCoin("ubbn", sdk.NewInt(10000000000)), + sdk.NewCoin("ubbn", math.NewInt(10000000000)), )) require.NoError(t, err) } diff --git a/x/btccheckpoint/abci.go b/x/btccheckpoint/abci.go index 34136c669..2307557f2 100644 --- a/x/btccheckpoint/abci.go +++ b/x/btccheckpoint/abci.go @@ -1,15 +1,14 @@ package btccheckpoint import ( + "context" "github.com/babylonchain/babylon/x/btccheckpoint/keeper" - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // EndBlocker checks if during block execution btc light client head had been // updated. If the head had been updated, status of all available checkpoints // is checked to determine if any of them became confirmed/finalized/abandonded. -func EndBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestEndBlock) { +func EndBlocker(ctx context.Context, k keeper.Keeper) { if k.BtcLightClientUpdated(ctx) { k.OnTipChange(ctx) } diff --git a/x/btccheckpoint/client/cli/tx.go b/x/btccheckpoint/client/cli/tx.go index 752ae5679..9d9f5b608 100644 --- a/x/btccheckpoint/client/cli/tx.go +++ b/x/btccheckpoint/client/cli/tx.go @@ -2,8 +2,6 @@ package cli import ( "fmt" - "time" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -14,16 +12,6 @@ import ( "github.com/babylonchain/babylon/x/btccheckpoint/types" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - -//nolint:unused -const ( - flagPacketTimeoutTimestamp = "packet-timeout-timestamp" - listSeparator = "," -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ diff --git a/x/btccheckpoint/genesis.go b/x/btccheckpoint/genesis.go index a5246537b..7b55f84a0 100644 --- a/x/btccheckpoint/genesis.go +++ b/x/btccheckpoint/genesis.go @@ -1,14 +1,14 @@ package btccheckpoint import ( + "context" "github.com/babylonchain/babylon/x/btccheckpoint/keeper" "github.com/babylonchain/babylon/x/btccheckpoint/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the capability module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { // set params for this module if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) @@ -16,7 +16,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } // ExportGenesis returns the capability module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) diff --git a/x/btccheckpoint/genesis_test.go b/x/btccheckpoint/genesis_test.go index da1d512d9..5266094df 100644 --- a/x/btccheckpoint/genesis_test.go +++ b/x/btccheckpoint/genesis_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/babylonchain/babylon/x/btccheckpoint" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" simapp "github.com/babylonchain/babylon/app" @@ -13,7 +12,7 @@ import ( func TestExportGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) if err := app.BtcCheckpointKeeper.SetParams(ctx, types.DefaultParams()); err != nil { panic(err) @@ -25,7 +24,7 @@ func TestExportGenesis(t *testing.T) { func TestInitGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) genesisState := types.GenesisState{ Params: types.Params{ diff --git a/x/btccheckpoint/keeper/grpc_query.go b/x/btccheckpoint/keeper/grpc_query.go index a5c600aeb..d5e155029 100644 --- a/x/btccheckpoint/keeper/grpc_query.go +++ b/x/btccheckpoint/keeper/grpc_query.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/babylonchain/babylon/x/btccheckpoint/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" @@ -15,7 +14,7 @@ import ( var _ types.QueryServer = Keeper{} -func (k Keeper) getCheckpointInfo(ctx sdk.Context, epochNum uint64, epochData *types.EpochData) (*types.BTCCheckpointInfo, error) { +func (k Keeper) getCheckpointInfo(ctx context.Context, epochNum uint64, epochData *types.EpochData) (*types.BTCCheckpointInfo, error) { bestSubmission := k.GetEpochBestSubmissionBtcInfo(ctx, epochData) if bestSubmission == nil { @@ -62,15 +61,12 @@ func (k Keeper) BtcCheckpointInfo(c context.Context, req *types.QueryBtcCheckpoi return resp, nil } -func (k Keeper) BtcCheckpointsInfo(c context.Context, req *types.QueryBtcCheckpointsInfoRequest) (*types.QueryBtcCheckpointsInfoResponse, error) { +func (k Keeper) BtcCheckpointsInfo(ctx context.Context, req *types.QueryBtcCheckpointsInfoRequest) (*types.QueryBtcCheckpointsInfoResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } - ctx := sdk.UnwrapSDKContext(c) - - store := ctx.KVStore(k.storeKey) - epochDataStore := prefix.NewStore(store, types.EpochDataPrefix) + epochDataStore := k.epochDataStore(ctx) ckptInfoList := []*types.BTCCheckpointInfo{} // iterate over epochDataStore, where key is the epoch number and value is the epoch data diff --git a/x/btccheckpoint/keeper/grpc_query_params_test.go b/x/btccheckpoint/keeper/grpc_query_params_test.go index 4863c0de8..b9db61836 100644 --- a/x/btccheckpoint/keeper/grpc_query_params_test.go +++ b/x/btccheckpoint/keeper/grpc_query_params_test.go @@ -6,7 +6,6 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btccheckpoint/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -53,7 +52,6 @@ func FuzzParamsQuery(f *testing.F) { } keeper, ctx := testkeeper.NewBTCCheckpointKeeper(t, nil, nil, nil, nil) - wctx := sdk.WrapSDKContext(ctx) // if setParamsFlag == 0, set params setParamsFlag := r.Intn(2) @@ -63,7 +61,7 @@ func FuzzParamsQuery(f *testing.F) { } } req := types.QueryParamsRequest{} - resp, err := keeper.Params(wctx, &req) + resp, err := keeper.Params(ctx, &req) require.NoError(t, err) // if setParamsFlag == 0, resp.Params should be changed, otherwise default if setParamsFlag == 0 { diff --git a/x/btccheckpoint/keeper/hooks.go b/x/btccheckpoint/keeper/hooks.go index 126e49fbd..3fd096a46 100644 --- a/x/btccheckpoint/keeper/hooks.go +++ b/x/btccheckpoint/keeper/hooks.go @@ -1,12 +1,13 @@ package keeper import ( + "context" ltypes "github.com/babylonchain/babylon/x/btclightclient/types" etypes "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -// Helper interface to be sure Hooks implement both epoching and light client hooks +// HandledHooks Helper interface to ensure Hooks implements +// both epoching and btclightclient hooks type HandledHooks interface { ltypes.BTCLightClientHooks etypes.EpochingHooks @@ -20,18 +21,18 @@ var _ HandledHooks = Hooks{} func (k Keeper) Hooks() Hooks { return Hooks{k} } -func (h Hooks) AfterBTCRollBack(ctx sdk.Context, headerInfo *ltypes.BTCHeaderInfo) { +func (h Hooks) AfterBTCRollBack(ctx context.Context, _ *ltypes.BTCHeaderInfo) { h.k.setBtcLightClientUpdated(ctx) } -func (h Hooks) AfterBTCRollForward(ctx sdk.Context, headerInfo *ltypes.BTCHeaderInfo) { +func (h Hooks) AfterBTCRollForward(ctx context.Context, _ *ltypes.BTCHeaderInfo) { h.k.setBtcLightClientUpdated(ctx) } -func (h Hooks) AfterBTCHeaderInserted(ctx sdk.Context, headerInfo *ltypes.BTCHeaderInfo) {} +func (h Hooks) AfterBTCHeaderInserted(_ context.Context, _ *ltypes.BTCHeaderInfo) {} -func (h Hooks) AfterEpochBegins(ctx sdk.Context, epoch uint64) {} +func (h Hooks) AfterEpochBegins(_ context.Context, _ uint64) {} -func (h Hooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) {} +func (h Hooks) AfterEpochEnds(_ context.Context, _ uint64) {} -func (h Hooks) BeforeSlashThreshold(ctx sdk.Context, valSet etypes.ValidatorSet) {} +func (h Hooks) BeforeSlashThreshold(_ context.Context, _ etypes.ValidatorSet) {} diff --git a/x/btccheckpoint/keeper/incentive.go b/x/btccheckpoint/keeper/incentive.go index c1574b788..36c17f035 100644 --- a/x/btccheckpoint/keeper/incentive.go +++ b/x/btccheckpoint/keeper/incentive.go @@ -1,14 +1,14 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/btccheckpoint/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // rewardBTCTimestamping finds the (submitter, reporter) pairs of all submissions at the // given finalised epoch according to the given epoch data, then distribute rewards to them // by invoking the incentive module -func (k Keeper) rewardBTCTimestamping(ctx sdk.Context, epoch uint64, ed *types.EpochData, bestIdx int) { +func (k Keeper) rewardBTCTimestamping(ctx context.Context, epoch uint64, ed *types.EpochData, bestIdx int) { var ( bestSubmissionAddrs *types.CheckpointAddressPair otherSubmissionAddrs []*types.CheckpointAddressPair diff --git a/x/btccheckpoint/keeper/keeper.go b/x/btccheckpoint/keeper/keeper.go index 2a9a7b05a..7f01dad7a 100644 --- a/x/btccheckpoint/keeper/keeper.go +++ b/x/btccheckpoint/keeper/keeper.go @@ -1,26 +1,28 @@ package keeper import ( + "context" + corestoretypes "cosmossdk.io/core/store" + storetypes "cosmossdk.io/store/types" "encoding/hex" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" "math/big" + "cosmossdk.io/log" + "cosmossdk.io/store/prefix" txformat "github.com/babylonchain/babylon/btctxformatter" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btccheckpoint/types" - "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type ( Keeper struct { cdc codec.BinaryCodec - storeKey storetypes.StoreKey - tstoreKey storetypes.StoreKey - memKey storetypes.StoreKey + storeService corestoretypes.KVStoreService + tsKey storetypes.StoreKey btcLightClientKeeper types.BTCLightClientKeeper checkpointingKeeper types.CheckpointingKeeper incentiveKeeper types.IncentiveKeeper @@ -55,9 +57,8 @@ const ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - tstoreKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, + tsKey storetypes.StoreKey, bk types.BTCLightClientKeeper, ck types.CheckpointingKeeper, ik types.IncentiveKeeper, @@ -67,9 +68,8 @@ func NewKeeper( return Keeper{ cdc: cdc, - storeKey: storeKey, - tstoreKey: tstoreKey, - memKey: memKey, + storeService: storeService, + tsKey: tsKey, btcLightClientKeeper: bk, checkpointingKeeper: ck, incentiveKeeper: ik, @@ -86,7 +86,7 @@ func (k Keeper) GetPowLimit() *big.Int { // hex string to bytes. // NOTE: keeper could probably cache decoded tag, but it is rather improbable this function // will ever be a bottleneck so it is not worth it. -func (k Keeper) GetExpectedTag(ctx sdk.Context) txformat.BabylonTag { +func (k Keeper) GetExpectedTag(ctx context.Context) txformat.BabylonTag { tag := k.GetParams(ctx).CheckpointTag tagAsBytes, err := hex.DecodeString(tag) @@ -102,11 +102,11 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -func (k Keeper) GetBlockHeight(ctx sdk.Context, b *bbn.BTCHeaderHashBytes) (uint64, error) { +func (k Keeper) GetBlockHeight(ctx context.Context, b *bbn.BTCHeaderHashBytes) (uint64, error) { return k.btcLightClientKeeper.BlockHeight(ctx, b) } -func (k Keeper) headerDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) { +func (k Keeper) headerDepth(ctx context.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) { blockDepth, err := k.btcLightClientKeeper.MainChainDepth(ctx, headerHash) if err != nil { @@ -121,7 +121,7 @@ func (k Keeper) headerDepth(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) // - it is on main chain // - its lowest depth is larger than highest depth of new submission func (k Keeper) checkAncestors( - ctx sdk.Context, + ctx context.Context, submisionEpoch uint64, newSubmissionInfo *types.SubmissionBtcInfo, ) error { @@ -175,25 +175,27 @@ func (k Keeper) checkAncestors( return nil } -func (k Keeper) setBtcLightClientUpdated(ctx sdk.Context) { - store := ctx.TransientStore(k.tstoreKey) +func (k Keeper) setBtcLightClientUpdated(ctx context.Context) { + store := sdk.UnwrapSDKContext(ctx).TransientStore(k.tsKey) store.Set(types.GetBtcLightClientUpdatedKey(), []byte{1}) } // BtcLightClientUpdated checks if btc light client was updated during block execution -func (k Keeper) BtcLightClientUpdated(ctx sdk.Context) bool { +func (k Keeper) BtcLightClientUpdated(ctx context.Context) bool { // transient store is cleared after each block execution, therfore if // BtcLightClientKey is set, it means setBtcLightClientUpdated was called during // current block execution - store := ctx.TransientStore(k.tstoreKey) + store := sdk.UnwrapSDKContext(ctx).TransientStore(k.tsKey) lcUpdated := store.Get(types.GetBtcLightClientUpdatedKey()) return len(lcUpdated) > 0 } -func (k Keeper) getLastFinalizedEpochNumber(ctx sdk.Context) uint64 { - store := ctx.KVStore(k.storeKey) - epoch := store.Get(types.GetLatestFinalizedEpochKey()) - +func (k Keeper) getLastFinalizedEpochNumber(ctx context.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + epoch, err := store.Get(types.GetLatestFinalizedEpochKey()) + if err != nil { + panic(err) + } if len(epoch) == 0 { return uint64(0) } @@ -201,13 +203,15 @@ func (k Keeper) getLastFinalizedEpochNumber(ctx sdk.Context) uint64 { return sdk.BigEndianToUint64(epoch) } -func (k Keeper) setLastFinalizedEpochNumber(ctx sdk.Context, epoch uint64) { - store := ctx.KVStore(k.storeKey) - store.Set(types.GetLatestFinalizedEpochKey(), sdk.Uint64ToBigEndian(epoch)) +func (k Keeper) setLastFinalizedEpochNumber(ctx context.Context, epoch uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.GetLatestFinalizedEpochKey(), sdk.Uint64ToBigEndian(epoch)); err != nil { + panic(err) + } } func (k Keeper) getEpochChanges( - ctx sdk.Context, + ctx context.Context, parentEpochBestSubmission *types.SubmissionBtcInfo, ed *types.EpochData) *epochChangesSummary { @@ -265,7 +269,7 @@ func (k Keeper) getEpochChanges( } // OnTipChange is the callback function to be called when btc light client tip changes -func (k Keeper) OnTipChange(ctx sdk.Context) { +func (k Keeper) OnTipChange(ctx context.Context) { k.checkCheckpoints(ctx) } @@ -299,11 +303,8 @@ func (k Keeper) OnTipChange(ctx sdk.Context) { // 7. If the epoch loses all of its submissions, delete all submissions from child // epoch as then we do not have parent for those. -func (k Keeper) checkCheckpoints(ctx sdk.Context) { - store := prefix.NewStore( - ctx.KVStore(k.storeKey), - types.EpochDataPrefix, - ) +func (k Keeper) checkCheckpoints(ctx context.Context) { + store := k.epochDataStore(ctx) lastFinalizedEpoch := k.getLastFinalizedEpochNumber(ctx) @@ -427,3 +428,8 @@ func (k Keeper) checkCheckpoints(ctx sdk.Context) { store.Set(it.Key(), k.cdc.MustMarshal(¤tEpoch)) } } + +func (k *Keeper) epochDataStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.EpochDataPrefix) +} diff --git a/x/btccheckpoint/keeper/msg_server.go b/x/btccheckpoint/keeper/msg_server.go index d6d1605d3..b5faff81c 100644 --- a/x/btccheckpoint/keeper/msg_server.go +++ b/x/btccheckpoint/keeper/msg_server.go @@ -24,12 +24,12 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { // TODO at some point add proper logging of error // TODO emit some events for external consumers. Those should be probably emited // at EndBlockerCallback -func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBTCSpvProof) (*types.MsgInsertBTCSpvProofResponse, error) { +func (ms msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBTCSpvProof) (*types.MsgInsertBTCSpvProofResponse, error) { // Get the SDK wrapped context sdkCtx := sdk.UnwrapSDKContext(ctx) - rawSubmission, err := types.ParseSubmission(req, m.k.GetPowLimit(), m.k.GetExpectedTag(sdkCtx)) + rawSubmission, err := types.ParseSubmission(req, ms.k.GetPowLimit(), ms.k.GetExpectedTag(sdkCtx)) if err != nil { return nil, types.ErrInvalidCheckpointProof.Wrap(err.Error()) @@ -37,11 +37,11 @@ func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBT submissionKey := rawSubmission.GetSubmissionKey() - if m.k.HasSubmission(sdkCtx, submissionKey) { + if ms.k.HasSubmission(sdkCtx, submissionKey) { return nil, types.ErrDuplicatedSubmission } - newSubmissionOldestHeaderDepth, err := m.k.GetSubmissionBtcInfo(sdkCtx, submissionKey) + newSubmissionOldestHeaderDepth, err := ms.k.GetSubmissionBtcInfo(sdkCtx, submissionKey) if err != nil { return nil, types.ErrInvalidHeader.Wrap(err.Error()) @@ -53,7 +53,7 @@ func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBT // - header is proved to be part of the chain we know about through BTCLightClient // - this is new checkpoint submission // Verify if this is expected checkpoint - err = m.k.checkpointingKeeper.VerifyCheckpoint(sdkCtx, rawSubmission.CheckpointData) + err = ms.k.checkpointingKeeper.VerifyCheckpoint(sdkCtx, rawSubmission.CheckpointData) if err != nil { return nil, err @@ -63,7 +63,7 @@ func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBT // by checkpointing module epochNum := rawSubmission.CheckpointData.Epoch - err = m.k.checkAncestors(sdkCtx, epochNum, newSubmissionOldestHeaderDepth) + err = ms.k.checkAncestors(sdkCtx, epochNum, newSubmissionOldestHeaderDepth) if err != nil { return nil, err @@ -81,7 +81,7 @@ func (m msgServer) InsertBTCSpvProof(ctx context.Context, req *types.MsgInsertBT submissionData := rawSubmission.GetSubmissionData(epochNum, txsInfo) // Everything is fine, save new checkpoint and update Epoch data - err = m.k.addEpochSubmission( + err = ms.k.addEpochSubmission( sdkCtx, epochNum, submissionKey, diff --git a/x/btccheckpoint/keeper/msg_server_test.go b/x/btccheckpoint/keeper/msg_server_test.go index 0129a0528..651eedb7d 100644 --- a/x/btccheckpoint/keeper/msg_server_test.go +++ b/x/btccheckpoint/keeper/msg_server_test.go @@ -40,11 +40,6 @@ func b2Hash(m *btcctypes.MsgInsertBTCSpvProof) *bbn.BTCHeaderHashBytes { return m.Proofs[1].ConfirmingBtcHeader.Hash() } -//nolint:unused -func b2TxIdx(m *btcctypes.MsgInsertBTCSpvProof) uint32 { - return m.Proofs[1].BtcTransactionIndex -} - func InitTestKeepers( t *testing.T, ) *TestKeepers { @@ -58,7 +53,7 @@ func InitTestKeepers( return &TestKeepers{ SdkCtx: ctx, - Ctx: sdk.WrapSDKContext(ctx), + Ctx: ctx, BTCLightClient: lc, Checkpointing: cc, Incentive: ic, @@ -421,12 +416,12 @@ func TestLeaveOnlyBestSubmissionWhenEpochFinalized(t *testing.T) { require.Len(t, ed.Keys, 3) // deepest submission is submission in msg3 - tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(wDeep+1)) - tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(wDeep+2)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(wDeep+3)) - tk.BTCLightClient.SetDepth(b1Hash(msg3), uint64(wDeep+4)) - tk.BTCLightClient.SetDepth(b2Hash(msg3), uint64(wDeep+5)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), wDeep) + tk.BTCLightClient.SetDepth(b2Hash(msg1), wDeep+1) + tk.BTCLightClient.SetDepth(b1Hash(msg2), wDeep+2) + tk.BTCLightClient.SetDepth(b2Hash(msg2), wDeep+3) + tk.BTCLightClient.SetDepth(b1Hash(msg3), wDeep+4) + tk.BTCLightClient.SetDepth(b2Hash(msg3), wDeep+5) tk.onTipChange() @@ -466,10 +461,10 @@ func TestTxIdxShouldBreakTies(t *testing.T) { // Both submissions have the same depth the most fresh block i.e // it is the same block // When finalizing the one with lower TxIx should be treated as better - tk.BTCLightClient.SetDepth(b1Hash(msg1), uint64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg1), uint64(wDeep+1)) - tk.BTCLightClient.SetDepth(b1Hash(msg2), uint64(wDeep)) - tk.BTCLightClient.SetDepth(b2Hash(msg2), uint64(wDeep+3)) + tk.BTCLightClient.SetDepth(b1Hash(msg1), wDeep) + tk.BTCLightClient.SetDepth(b2Hash(msg1), wDeep+1) + tk.BTCLightClient.SetDepth(b1Hash(msg2), wDeep) + tk.BTCLightClient.SetDepth(b2Hash(msg2), wDeep+3) tk.onTipChange() @@ -527,8 +522,8 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { } // Now we will return depth enough for moving submission to confirmed - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(kDeep)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(kDeep)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), kDeep) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), kDeep) // fire tip change callback tk.onTipChange() @@ -544,8 +539,8 @@ func TestStateTransitionOfValidSubmission(t *testing.T) { t.Errorf("Epoch should be in submitted stated") } - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(wDeep)) - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(wDeep)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), wDeep) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), wDeep) tk.onTipChange() @@ -602,21 +597,21 @@ func FuzzConfirmAndDinalizeManyEpochs(f *testing.F) { } finalizationDepth = finalizationDepth - 1 } else if epoch <= uint64(numFinalizedEpochs+numConfirmedEpochs) { - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(confirmationDepth)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), confirmationDepth) confirmationDepth = confirmationDepth - 1 - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(confirmationDepth)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), confirmationDepth) // first submission is always deepest one, and second block is the most recent one if j == 1 { - bestSumbissionInfos[epoch] = uint64(confirmationDepth) + bestSumbissionInfos[epoch] = confirmationDepth } confirmationDepth = confirmationDepth - 1 } else { - tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), uint64(sumbissionDepth)) + tk.BTCLightClient.SetDepth(blck1.HeaderBytes.Hash(), sumbissionDepth) sumbissionDepth = sumbissionDepth - 1 - tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), uint64(sumbissionDepth)) + tk.BTCLightClient.SetDepth(blck2.HeaderBytes.Hash(), sumbissionDepth) // first submission is always deepest one, and second block is the most recent one if j == 1 { - bestSumbissionInfos[epoch] = uint64(sumbissionDepth) + bestSumbissionInfos[epoch] = sumbissionDepth } sumbissionDepth = sumbissionDepth - 1 } diff --git a/x/btccheckpoint/keeper/params.go b/x/btccheckpoint/keeper/params.go index c69f94aa2..a4333facb 100644 --- a/x/btccheckpoint/keeper/params.go +++ b/x/btccheckpoint/keeper/params.go @@ -1,25 +1,27 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/btccheckpoint/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // SetParams sets the x/btccheckpoint module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) - return nil + return store.Set(types.ParamsKey, bz) } // GetParams returns the current x/btccheckpoint module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } diff --git a/x/btccheckpoint/keeper/submissions.go b/x/btccheckpoint/keeper/submissions.go index 2a5bf29fa..9bf5d400c 100644 --- a/x/btccheckpoint/keeper/submissions.go +++ b/x/btccheckpoint/keeper/submissions.go @@ -1,23 +1,28 @@ package keeper import ( + "context" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "math" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btccheckpoint/types" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) HasSubmission(ctx sdk.Context, sk types.SubmissionKey) bool { - store := ctx.KVStore(k.storeKey) +func (k Keeper) HasSubmission(ctx context.Context, sk types.SubmissionKey) bool { + store := k.storeService.OpenKVStore(ctx) kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - return store.Has(kBytes) + has, err := store.Has(kBytes) + if err != nil { + panic(err) + } + return has } // GetBestSubmission gets the status and the best submission of a given finalized epoch -func (k Keeper) GetBestSubmission(ctx sdk.Context, epochNumber uint64) (types.BtcStatus, *types.SubmissionKey, error) { +func (k Keeper) GetBestSubmission(ctx context.Context, epochNumber uint64) (types.BtcStatus, *types.SubmissionKey, error) { // find the btc checkpoint tx index of this epoch ed := k.GetEpochData(ctx, epochNumber) if ed == nil { @@ -39,7 +44,7 @@ func (k Keeper) GetBestSubmission(ctx sdk.Context, epochNumber uint64) (types.Bt // Provided submmission should be known to btclightclient and all of its blocks // should be on btc main chaing as viewed by btclightclient func (k Keeper) addEpochSubmission( - ctx sdk.Context, + ctx context.Context, epochNum uint64, sk types.SubmissionKey, sd types.SubmissionData, @@ -78,25 +83,32 @@ func (k Keeper) addEpochSubmission( return nil } -func (k Keeper) saveSubmission(ctx sdk.Context, sk types.SubmissionKey, sd types.SubmissionData) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) saveSubmission(ctx context.Context, sk types.SubmissionKey, sd types.SubmissionData) { + store := k.storeService.OpenKVStore(ctx) kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) sBytes := k.cdc.MustMarshal(&sd) - store.Set(kBytes, sBytes) + if err := store.Set(kBytes, sBytes); err != nil { + panic(err) + } } -func (k Keeper) deleteSubmission(ctx sdk.Context, sk types.SubmissionKey) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) deleteSubmission(ctx context.Context, sk types.SubmissionKey) { + store := k.storeService.OpenKVStore(ctx) kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - store.Delete(kBytes) + if err := store.Delete(kBytes); err != nil { + panic(err) + } } // GetSubmissionData returns submission data for a given key or nil if there is no data // under the given key -func (k Keeper) GetSubmissionData(ctx sdk.Context, sk types.SubmissionKey) *types.SubmissionData { - store := ctx.KVStore(k.storeKey) +func (k Keeper) GetSubmissionData(ctx context.Context, sk types.SubmissionKey) *types.SubmissionData { + store := k.storeService.OpenKVStore(ctx) kBytes := types.PrefixedSubmisionKey(k.cdc, &sk) - sdBytes := store.Get(kBytes) + sdBytes, err := store.Get(kBytes) + if err != nil { + panic(err) + } if len(sdBytes) == 0 { return nil @@ -107,7 +119,7 @@ func (k Keeper) GetSubmissionData(ctx sdk.Context, sk types.SubmissionKey) *type return &sd } -func (k Keeper) checkSubmissionStatus(ctx sdk.Context, info *types.SubmissionBtcInfo) types.BtcStatus { +func (k Keeper) checkSubmissionStatus(ctx context.Context, info *types.SubmissionBtcInfo) types.BtcStatus { subDepth := info.SubmissionDepth() if subDepth >= k.GetParams(ctx).CheckpointFinalizationTimeout { return types.Finalized @@ -118,14 +130,14 @@ func (k Keeper) checkSubmissionStatus(ctx sdk.Context, info *types.SubmissionBtc } } -func (k Keeper) GetSubmissionBtcInfo(ctx sdk.Context, sk types.SubmissionKey) (*types.SubmissionBtcInfo, error) { +func (k Keeper) GetSubmissionBtcInfo(ctx context.Context, sk types.SubmissionKey) (*types.SubmissionBtcInfo, error) { var youngestBlockDepth uint64 = math.MaxUint64 var youngestBlockHash *bbn.BTCHeaderHashBytes var lowestIndexInMostFreshBlock uint32 = math.MaxUint32 - var oldestBlockDepth uint64 = uint64(0) + var oldestBlockDepth = uint64(0) for _, tk := range sk.Key { currentBlockDepth, err := k.headerDepth(ctx, tk.Hash) @@ -177,14 +189,14 @@ func (k Keeper) GetSubmissionBtcInfo(ctx sdk.Context, sk types.SubmissionKey) (* }, nil } -func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *types.EpochData) *types.SubmissionBtcInfo { - // there is no submissions for this epoch, so transitivly there is no best submission +func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx context.Context, ed *types.EpochData) *types.SubmissionBtcInfo { + // there are no submissions for this epoch, so transitivly there is no best submission if ed == nil || len(ed.Keys) == 0 { return nil } // There is only one submission for this epoch: - // - either epoch is already finalized and we already chosen the best submission + // - either epoch is already finalized, and we already chose the best submission // - or we only received one submission for this epoch // Either way, we do not need to decide which submission is the best one. if len(ed.Keys) == 1 { @@ -192,11 +204,11 @@ func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *types.EpochDa btcInfo, err := k.GetSubmissionBtcInfo(ctx, sk) if err != nil { - k.Logger(ctx).Debug("Previously stored submission is not valid anymore. Submission key: %+v", sk) + k.Logger(sdk.UnwrapSDKContext(ctx)).Debug("Previously stored submission is not valid anymore. Submission key: %+v", sk) } // we only log error, as the only error which we can receive here is that submission - // is not longer on btc canoncial chain, which essentially means that there is no valid submission + // is no longer on btc canonical chain, which essentially means that there is no valid submission return btcInfo } @@ -207,9 +219,12 @@ func (k Keeper) GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *types.EpochDa } // GetEpochData returns epoch data for given epoch, if there is not epoch data yet returns nil -func (k Keeper) GetEpochData(ctx sdk.Context, e uint64) *types.EpochData { - store := ctx.KVStore(k.storeKey) - bytes := store.Get(types.GetEpochIndexKey(e)) +func (k Keeper) GetEpochData(ctx context.Context, e uint64) *types.EpochData { + store := k.storeService.OpenKVStore(ctx) + bytes, err := store.Get(types.GetEpochIndexKey(e)) + if err != nil { + panic(err) + } // note: Cannot check len(bytes) == 0, as empty bytes encoding of types.EpochData // is epoch data with Status == Submitted and no valid submissions @@ -222,15 +237,17 @@ func (k Keeper) GetEpochData(ctx sdk.Context, e uint64) *types.EpochData { return ed } -func (k Keeper) saveEpochData(ctx sdk.Context, e uint64, ed *types.EpochData) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) saveEpochData(ctx context.Context, e uint64, ed *types.EpochData) { + store := k.storeService.OpenKVStore(ctx) ek := types.GetEpochIndexKey(e) eb := k.cdc.MustMarshal(ed) - store.Set(ek, eb) + if err := store.Set(ek, eb); err != nil { + panic(err) + } } func (k Keeper) clearEpochData( - ctx sdk.Context, + ctx context.Context, epoch []byte, epochDataStore prefix.Store, currentEpoch *types.EpochData) { diff --git a/x/btccheckpoint/module.go b/x/btccheckpoint/module.go index 879902e9a..40db44228 100644 --- a/x/btccheckpoint/module.go +++ b/x/btccheckpoint/module.go @@ -2,6 +2,7 @@ package btccheckpoint import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -22,8 +23,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -77,7 +80,10 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Rout // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { - types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck // generally we don't handle errors in these registration functions + err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } } // GetTxCmd returns the capability module's root tx command. @@ -98,23 +104,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper - // TODO: add dependencies to staking, slashing and evidence + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -138,14 +137,12 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the capability module's genesis initialization It returns // no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. @@ -158,11 +155,21 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(_ context.Context) error { + return nil +} // EndBlock executes all ABCI EndBlock logic respective to the capability module. It // returns no validator updates. -func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { - EndBlocker(ctx, am.keeper, req) - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { + EndBlocker(ctx, am.keeper) + return []abci.ValidatorUpdate{}, nil +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker } diff --git a/x/btccheckpoint/module_simulation.go b/x/btccheckpoint/module_simulation.go index 804960957..cd9dd2454 100644 --- a/x/btccheckpoint/module_simulation.go +++ b/x/btccheckpoint/module_simulation.go @@ -1,11 +1,9 @@ package btccheckpoint import ( - simappparams "github.com/babylonchain/babylon/app/params" btccheckpointsimulation "github.com/babylonchain/babylon/x/btccheckpoint/simulation" "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -14,7 +12,6 @@ import ( // avoid unused import issue var ( _ = btccheckpointsimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -31,16 +28,17 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&btccheckpointGenesis) } +// RegisterStoreDecoder registers a decoder +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { +} + // ProposalContents doesn't return any content functions for governance proposals func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { return nil } -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} - // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { +func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { operations := make([]simtypes.WeightedOperation, 0) return operations diff --git a/x/btccheckpoint/types/btcutils.go b/x/btccheckpoint/types/btcutils.go index 6d761e567..37670301f 100644 --- a/x/btccheckpoint/types/btcutils.go +++ b/x/btccheckpoint/types/btcutils.go @@ -25,7 +25,7 @@ const ( maxOpReturnPkScriptSize = 83 ) -// Parsed proof represent semantically valid: +// ParsedProof represent semantically valid: // - Bitcoin Header // - Bitcoin Header hash // - Bitcoin Transaction diff --git a/x/btccheckpoint/types/btcutils_test.go b/x/btccheckpoint/types/btcutils_test.go index 1f27123ed..576929eea 100644 --- a/x/btccheckpoint/types/btcutils_test.go +++ b/x/btccheckpoint/types/btcutils_test.go @@ -11,16 +11,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" ) -//nolint:unused -func hashFromString(s string) *chainhash.Hash { - hash, e := chainhash.NewHashFromStr(s) - if e != nil { - panic("Invalid hex sting") - } - - return hash -} - // Sanity test checking mostly btcd code, that we can realy parse bitcoin transaction func TestBtcTransactionParsing(t *testing.T) { // Few randomly chosed btc valid btc transactions diff --git a/x/btccheckpoint/types/errors.go b/x/btccheckpoint/types/errors.go index 37318dce5..64a3dd681 100644 --- a/x/btccheckpoint/types/errors.go +++ b/x/btccheckpoint/types/errors.go @@ -1,7 +1,5 @@ package types -// DONTCOVER - import ( errorsmod "cosmossdk.io/errors" ) @@ -12,7 +10,6 @@ var ( ErrDuplicatedSubmission = errorsmod.Register(ModuleName, 1101, "Duplicated submission") ErrNoCheckpointsForPreviousEpoch = errorsmod.Register(ModuleName, 1102, "No checkpoints for previous epoch") ErrInvalidHeader = errorsmod.Register(ModuleName, 1103, "Proof headers are invalid") - ErrProvidedHeaderFromDifferentForks = errorsmod.Register(ModuleName, 1104, "Proof header from different forks") - ErrProvidedHeaderDoesNotHaveAncestor = errorsmod.Register(ModuleName, 1105, "Proof header does not have ancestor in previous epoch") - ErrEpochAlreadyFinalized = errorsmod.Register(ModuleName, 1106, "Submission denied. Epoch already finalized") + ErrProvidedHeaderDoesNotHaveAncestor = errorsmod.Register(ModuleName, 1104, "Proof header does not have ancestor in previous epoch") + ErrEpochAlreadyFinalized = errorsmod.Register(ModuleName, 1105, "Submission denied. Epoch already finalized") ) diff --git a/x/btccheckpoint/types/expected_keepers.go b/x/btccheckpoint/types/expected_keepers.go index 9a047165e..1d9da3675 100644 --- a/x/btccheckpoint/types/expected_keepers.go +++ b/x/btccheckpoint/types/expected_keepers.go @@ -1,37 +1,23 @@ package types import ( + "context" txformat "github.com/babylonchain/babylon/btctxformatter" bbn "github.com/babylonchain/babylon/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here -} - type BTCLightClientKeeper interface { // BlockHeight should validate if header with given hash is valid and if it is // part of known chain. In case this is true it should return this block height // in case this is false it should return error - BlockHeight(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) + BlockHeight(ctx context.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) // MainChainDepth returns the depth of the header in the main chain or error if the header does not exist - MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) + MainChainDepth(ctx context.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) } type CheckpointingKeeper interface { - VerifyCheckpoint(ctx sdk.Context, checkpoint txformat.RawBtcCheckpoint) error + VerifyCheckpoint(ctx context.Context, checkpoint txformat.RawBtcCheckpoint) error // It quite mouthfull to have 4 different methods to operate on checkpoint state // but this approach decouples both modules a bit more than having some kind // of shared enum passed into the methods. Both modules are free to evolve their @@ -39,19 +25,19 @@ type CheckpointingKeeper interface { // SetCheckpointSubmitted informs checkpointing module that checkpoint was // successfully submitted on btc chain. - SetCheckpointSubmitted(ctx sdk.Context, epoch uint64) + SetCheckpointSubmitted(ctx context.Context, epoch uint64) // SetCheckpointConfirmed informs checkpointing module that checkpoint was // successfully submitted on btc chain, and it is at least K-deep on the main chain - SetCheckpointConfirmed(ctx sdk.Context, epoch uint64) + SetCheckpointConfirmed(ctx context.Context, epoch uint64) // SetCheckpointFinalized informs checkpointing module that checkpoint was // successfully submitted on btc chain, and it is at least W-deep on the main chain - SetCheckpointFinalized(ctx sdk.Context, epoch uint64) + SetCheckpointFinalized(ctx context.Context, epoch uint64) // SetCheckpointForgotten informs checkpointing module that this checkpoint lost // all submissions on btc chain - SetCheckpointForgotten(ctx sdk.Context, epoch uint64) + SetCheckpointForgotten(ctx context.Context, epoch uint64) } type IncentiveKeeper interface { - RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rewardDistInfo *RewardDistInfo) + RewardBTCTimestamping(ctx context.Context, epoch uint64, rewardDistInfo *RewardDistInfo) } diff --git a/x/btccheckpoint/types/genesis.go b/x/btccheckpoint/types/genesis.go index a6cdfe807..2fb67af14 100644 --- a/x/btccheckpoint/types/genesis.go +++ b/x/btccheckpoint/types/genesis.go @@ -1,8 +1,5 @@ package types -// DefaultIndex is the default capability global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ diff --git a/x/btccheckpoint/types/incentive.go b/x/btccheckpoint/types/incentive.go index 970eaf4ea..d426a8d8a 100644 --- a/x/btccheckpoint/types/incentive.go +++ b/x/btccheckpoint/types/incentive.go @@ -1,7 +1,7 @@ package types import ( - fmt "fmt" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/btccheckpoint/types/mock_keepers.go b/x/btccheckpoint/types/mock_keepers.go index 1d426b64a..3d533d24f 100644 --- a/x/btccheckpoint/types/mock_keepers.go +++ b/x/btccheckpoint/types/mock_keepers.go @@ -1,11 +1,11 @@ package types import ( + "context" "errors" txformat "github.com/babylonchain/babylon/btctxformatter" bbn "github.com/babylonchain/babylon/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) type MockBTCLightClientKeeper struct { @@ -53,12 +53,12 @@ func (mc *MockBTCLightClientKeeper) DeleteHeader(header *bbn.BTCHeaderHashBytes) delete(mc.headers, header.String()) } -func (mb MockBTCLightClientKeeper) BlockHeight(ctx sdk.Context, header *bbn.BTCHeaderHashBytes) (uint64, error) { +func (mb MockBTCLightClientKeeper) BlockHeight(ctx context.Context, header *bbn.BTCHeaderHashBytes) (uint64, error) { // todo not used return uint64(10), nil } -func (ck MockBTCLightClientKeeper) MainChainDepth(ctx sdk.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) { +func (ck MockBTCLightClientKeeper) MainChainDepth(ctx context.Context, headerBytes *bbn.BTCHeaderHashBytes) (uint64, error) { depth, ok := ck.headers[headerBytes.String()] if ok { return depth, nil @@ -67,7 +67,7 @@ func (ck MockBTCLightClientKeeper) MainChainDepth(ctx sdk.Context, headerBytes * } } -func (ck MockCheckpointingKeeper) VerifyCheckpoint(ctx sdk.Context, checkpoint txformat.RawBtcCheckpoint) error { +func (ck MockCheckpointingKeeper) VerifyCheckpoint(ctx context.Context, checkpoint txformat.RawBtcCheckpoint) error { if ck.returnError { return errors.New("bad checkpoints") } @@ -77,23 +77,23 @@ func (ck MockCheckpointingKeeper) VerifyCheckpoint(ctx sdk.Context, checkpoint t // SetCheckpointSubmitted Informs checkpointing module that checkpoint was // successfully submitted on btc chain. -func (ck MockCheckpointingKeeper) SetCheckpointSubmitted(ctx sdk.Context, epoch uint64) { +func (ck MockCheckpointingKeeper) SetCheckpointSubmitted(ctx context.Context, epoch uint64) { } // SetCheckpointConfirmed Informs checkpointing module that checkpoint was // successfully submitted on btc chain, and it is at least K-deep on the main chain -func (ck MockCheckpointingKeeper) SetCheckpointConfirmed(ctx sdk.Context, epoch uint64) { +func (ck MockCheckpointingKeeper) SetCheckpointConfirmed(ctx context.Context, epoch uint64) { } // SetCheckpointFinalized Informs checkpointing module that checkpoint was // successfully submitted on btc chain, and it is at least W-deep on the main chain -func (ck MockCheckpointingKeeper) SetCheckpointFinalized(ctx sdk.Context, epoch uint64) { +func (ck MockCheckpointingKeeper) SetCheckpointFinalized(ctx context.Context, epoch uint64) { } // SetCheckpointForgotten Informs checkpointing module that was in submitted state // lost all its checkpoints and is checkpoint empty -func (ck MockCheckpointingKeeper) SetCheckpointForgotten(ctx sdk.Context, epoch uint64) { +func (ck MockCheckpointingKeeper) SetCheckpointForgotten(ctx context.Context, epoch uint64) { } -func (ik *MockIncentiveKeeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rewardDistInfo *RewardDistInfo) { +func (ik *MockIncentiveKeeper) RewardBTCTimestamping(ctx context.Context, epoch uint64, rewardDistInfo *RewardDistInfo) { } diff --git a/x/btccheckpoint/types/msgs.go b/x/btccheckpoint/types/msgs.go index 8ddca7c89..96fca5003 100644 --- a/x/btccheckpoint/types/msgs.go +++ b/x/btccheckpoint/types/msgs.go @@ -18,7 +18,7 @@ var ( _ sdk.Msg = (*MsgUpdateParams)(nil) ) -// Parse and Validate transactions which should contain OP_RETURN data. +// ParseTwoProofs Parse and Validate transactions which should contain OP_RETURN data. // OP_RETURN bytes are not validated in any way. It is up to the caller attach // semantic meaning and validity to those bytes. // Returned ParsedProofs are in same order as raw proofs diff --git a/x/btccheckpoint/types/params.go b/x/btccheckpoint/types/params.go index 0a33579d6..6a72e6e32 100644 --- a/x/btccheckpoint/types/params.go +++ b/x/btccheckpoint/types/params.go @@ -2,7 +2,7 @@ package types import ( "encoding/hex" - fmt "fmt" + "fmt" txformat "github.com/babylonchain/babylon/btctxformatter" ) diff --git a/x/btccheckpoint/types/tx.pb.go b/x/btccheckpoint/types/tx.pb.go index d36a3a6ba..f02e60b7d 100644 --- a/x/btccheckpoint/types/tx.pb.go +++ b/x/btccheckpoint/types/tx.pb.go @@ -229,34 +229,35 @@ func init() { func init() { proto.RegisterFile("babylon/btccheckpoint/v1/tx.proto", fileDescriptor_69a562325f8b35c5) } var fileDescriptor_69a562325f8b35c5 = []byte{ - // 425 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xcb, 0xaa, 0xd3, 0x40, - 0x18, 0xce, 0x9c, 0x23, 0x85, 0xce, 0x11, 0xc5, 0x50, 0x38, 0x69, 0x28, 0x31, 0x06, 0x0a, 0x55, - 0x34, 0xa1, 0x15, 0xbb, 0x10, 0x14, 0x8c, 0x2b, 0x17, 0xc5, 0x92, 0xea, 0xc6, 0x8d, 0x24, 0xe9, - 0x38, 0x09, 0x36, 0x99, 0x61, 0xfe, 0x69, 0x69, 0x71, 0xe7, 0x13, 0xb8, 0xf5, 0x2d, 0x5c, 0xf8, - 0x10, 0x5d, 0x16, 0x57, 0xae, 0x44, 0xda, 0x85, 0x5b, 0x1f, 0x41, 0x9a, 0x0b, 0xbd, 0xd8, 0x88, - 0x67, 0x37, 0xff, 0xcc, 0x77, 0x9b, 0x8f, 0x1f, 0xdf, 0x09, 0xfc, 0x60, 0x31, 0x61, 0xa9, 0x13, - 0xc8, 0x30, 0x8c, 0x48, 0xf8, 0x9e, 0xb3, 0x38, 0x95, 0xce, 0xac, 0xeb, 0xc8, 0xb9, 0xcd, 0x05, - 0x93, 0x4c, 0xd5, 0x0a, 0x88, 0x7d, 0x00, 0xb1, 0x67, 0x5d, 0xfd, 0x7e, 0x25, 0xf9, 0x10, 0x9a, - 0xe9, 0xe8, 0xcd, 0x90, 0x41, 0xc2, 0xe0, 0x6d, 0x36, 0x39, 0xf9, 0x50, 0x3c, 0x5d, 0xe6, 0x93, - 0x93, 0x00, 0xdd, 0xb2, 0x13, 0xa0, 0xc5, 0x43, 0xbb, 0xd2, 0x81, 0xfb, 0xc2, 0x4f, 0x4a, 0x7e, - 0x83, 0x32, 0xca, 0x72, 0xdd, 0xed, 0x29, 0xbf, 0xb5, 0x00, 0x37, 0x06, 0x40, 0x5f, 0xa4, 0x40, - 0x84, 0x74, 0x5f, 0x3d, 0x1f, 0xf1, 0xd9, 0x50, 0x30, 0xf6, 0x4e, 0x6d, 0xe1, 0x3a, 0x4c, 0x83, - 0x24, 0x96, 0x92, 0x08, 0x0d, 0x99, 0xa8, 0x53, 0xf7, 0x76, 0x17, 0xea, 0x13, 0x5c, 0xe3, 0x5b, - 0x18, 0x68, 0x67, 0xe6, 0x79, 0xe7, 0xa2, 0xd7, 0xb6, 0xab, 0xfe, 0x6f, 0xef, 0x89, 0x7a, 0x05, - 0xc9, 0x32, 0x70, 0xeb, 0x94, 0xa9, 0x47, 0x80, 0xb3, 0x14, 0x88, 0xf5, 0x19, 0xe1, 0x9b, 0x03, - 0xa0, 0xaf, 0xf9, 0xd8, 0x97, 0x64, 0x98, 0x7d, 0x42, 0xed, 0xe3, 0xba, 0x3f, 0x95, 0x11, 0x13, - 0xb1, 0x5c, 0xe4, 0x81, 0x5c, 0xed, 0xdb, 0xd7, 0x07, 0x8d, 0xa2, 0xa3, 0x67, 0xe3, 0xb1, 0x20, - 0x00, 0x23, 0x29, 0xe2, 0x94, 0x7a, 0x3b, 0xa8, 0xfa, 0x14, 0xd7, 0xf2, 0x1a, 0xb4, 0x33, 0x13, - 0x75, 0x2e, 0x7a, 0x66, 0x75, 0xd4, 0xdc, 0xc9, 0xbd, 0xb6, 0xfc, 0x71, 0x5b, 0xf1, 0x0a, 0xd6, - 0xe3, 0x1b, 0x1f, 0x7f, 0x7d, 0xb9, 0xb7, 0xd3, 0xb3, 0x9a, 0xf8, 0xf2, 0x28, 0x5a, 0x19, 0xbb, - 0xf7, 0x1b, 0xe1, 0xf3, 0x01, 0x50, 0xf5, 0x03, 0xbe, 0xf5, 0x77, 0xa1, 0x76, 0xb5, 0xef, 0xa9, - 0x2e, 0xf4, 0xfe, 0xd5, 0xf0, 0x65, 0x08, 0x75, 0x82, 0xaf, 0x1f, 0xf4, 0x76, 0xf7, 0x9f, 0x3a, - 0xfb, 0x50, 0xbd, 0xfb, 0xdf, 0xd0, 0xd2, 0xcd, 0x7d, 0xb9, 0x5c, 0x1b, 0x68, 0xb5, 0x36, 0xd0, - 0xcf, 0xb5, 0x81, 0x3e, 0x6d, 0x0c, 0x65, 0xb5, 0x31, 0x94, 0xef, 0x1b, 0x43, 0x79, 0xf3, 0x88, - 0xc6, 0x32, 0x9a, 0x06, 0x76, 0xc8, 0x12, 0xa7, 0x90, 0x0d, 0x23, 0x3f, 0x4e, 0xcb, 0xc1, 0x99, - 0x1f, 0xed, 0xab, 0x5c, 0x70, 0x02, 0x41, 0x2d, 0x5b, 0xcb, 0x87, 0x7f, 0x02, 0x00, 0x00, 0xff, - 0xff, 0x53, 0xca, 0xbd, 0x84, 0x74, 0x03, 0x00, 0x00, + // 439 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0x4e, 0xce, 0x48, 0x4d, 0xce, 0x2e, 0xc8, 0xcf, 0xcc, + 0x2b, 0xd1, 0x2f, 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x80, + 0x2a, 0xd1, 0x43, 0x51, 0xa2, 0x57, 0x66, 0x28, 0xa5, 0x83, 0x53, 0x33, 0xaa, 0x52, 0xb0, 0x39, + 0x52, 0x92, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0xf1, 0x60, 0x9e, 0x3e, 0x84, 0x03, 0x95, 0x12, + 0x87, 0xf0, 0xf4, 0x73, 0x8b, 0xd3, 0x41, 0xba, 0x73, 0x8b, 0xd3, 0xa1, 0x12, 0xaa, 0x38, 0x6d, + 0x28, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0xe9, 0x17, 0x49, 0xcf, 0x4f, 0xcf, 0x87, 0x98, 0x0b, 0x62, + 0x41, 0x44, 0x95, 0x9a, 0x19, 0xb9, 0x44, 0x7c, 0x8b, 0xd3, 0x3d, 0xf3, 0x8a, 0x53, 0x8b, 0x4a, + 0x9c, 0x42, 0x9c, 0x83, 0x0b, 0xca, 0x02, 0x8a, 0xf2, 0xf3, 0xd3, 0x84, 0x64, 0xb8, 0x38, 0x8b, + 0x4b, 0x93, 0x72, 0x33, 0x4b, 0x4a, 0x52, 0x8b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x10, + 0x02, 0x42, 0xb6, 0x5c, 0x6c, 0x05, 0x20, 0x65, 0xc5, 0x12, 0x4c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, + 0xaa, 0x7a, 0xb8, 0x02, 0x40, 0x0f, 0xc9, 0xd0, 0x20, 0xa8, 0x26, 0x2b, 0xbe, 0xa6, 0xe7, 0x1b, + 0xb4, 0x10, 0xc6, 0x29, 0xc9, 0x71, 0xc9, 0x60, 0x73, 0x44, 0x50, 0x6a, 0x71, 0x41, 0x7e, 0x5e, + 0x71, 0xaa, 0xd2, 0x4c, 0x46, 0x2e, 0x7e, 0xdf, 0xe2, 0xf4, 0xd0, 0x82, 0x94, 0xc4, 0x92, 0xd4, + 0x00, 0xb0, 0xaf, 0x84, 0xcc, 0xb8, 0x38, 0x13, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x32, 0x4b, 0x2a, + 0x21, 0x0e, 0x74, 0x92, 0xb8, 0xb4, 0x45, 0x57, 0x04, 0x1a, 0x68, 0x8e, 0x29, 0x29, 0x45, 0xa9, + 0xc5, 0xc5, 0xc1, 0x25, 0x45, 0x99, 0x79, 0xe9, 0x41, 0x08, 0xa5, 0x42, 0x76, 0x5c, 0x6c, 0x90, + 0x70, 0x91, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x36, 0x52, 0xc0, 0xed, 0x74, 0x88, 0x4d, 0x4e, 0x2c, + 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0x75, 0x41, 0xdd, 0x0e, 0x37, 0x4f, 0x49, 0x92, 0x4b, 0x1c, + 0xcd, 0x69, 0x30, 0x67, 0x1b, 0x7d, 0x67, 0xe4, 0x62, 0xf6, 0x2d, 0x4e, 0x17, 0xaa, 0xe6, 0x12, + 0xc4, 0x0c, 0x60, 0x3d, 0xdc, 0xf6, 0x62, 0x0b, 0x0b, 0x29, 0x33, 0xd2, 0xd4, 0xc3, 0x1c, 0x21, + 0x94, 0xc3, 0xc5, 0x83, 0x12, 0x6e, 0x9a, 0x78, 0xcd, 0x41, 0x56, 0x2a, 0x65, 0x48, 0xb4, 0x52, + 0x98, 0x6d, 0x52, 0xac, 0x0d, 0xcf, 0x37, 0x68, 0x31, 0x3a, 0xf9, 0x9f, 0x78, 0x24, 0xc7, 0x78, + 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, + 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x69, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, + 0xae, 0x3e, 0xd4, 0xf4, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x18, 0x47, 0xbf, 0x02, 0x2d, 0x1d, 0x97, + 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x93, 0xab, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x2e, 0x80, 0x63, 0x8c, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/btccheckpoint/types/types.go b/x/btccheckpoint/types/types.go index 75a862bbe..e825908de 100644 --- a/x/btccheckpoint/types/types.go +++ b/x/btccheckpoint/types/types.go @@ -11,7 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Semantically valid checkpoint submission with: +// RawCheckpointSubmission Semantically valid checkpoint submission with: // - valid submitter address // - at least 2 parsed proof // Modelling proofs as separate Proof1 and Proof2, as this is more explicit than @@ -82,22 +82,22 @@ func toTransactionKey(p *ParsedProof) TransactionKey { } } -func (rsc *RawCheckpointSubmission) GetSubmissionKey() SubmissionKey { +func (s *RawCheckpointSubmission) GetSubmissionKey() SubmissionKey { var keys []*TransactionKey - k1 := toTransactionKey(&rsc.Proof1) + k1 := toTransactionKey(&s.Proof1) keys = append(keys, &k1) - k2 := toTransactionKey(&rsc.Proof2) + k2 := toTransactionKey(&s.Proof2) keys = append(keys, &k2) return SubmissionKey{ Key: keys, } } -func (rsc *RawCheckpointSubmission) GetSubmissionData(epochNum uint64, txsInfo []*TransactionInfo) SubmissionData { +func (s *RawCheckpointSubmission) GetSubmissionData(epochNum uint64, txsInfo []*TransactionInfo) SubmissionData { return SubmissionData{ VigilanteAddresses: &CheckpointAddresses{ - Reporter: rsc.Reporter.Bytes(), - Submitter: rsc.CheckpointData.SubmitterAddress, + Reporter: s.Reporter.Bytes(), + Submitter: s.CheckpointData.SubmitterAddress, }, TxsInfo: txsInfo, Epoch: epochNum, diff --git a/x/btclightclient/client/cli/query.go b/x/btclightclient/client/cli/query.go index fb955b3d1..17faccb2b 100644 --- a/x/btclightclient/client/cli/query.go +++ b/x/btclightclient/client/cli/query.go @@ -11,7 +11,7 @@ import ( ) // GetQueryCmd returns the cli query commands for this module -func GetQueryCmd(queryRoute string) *cobra.Command { +func GetQueryCmd(_ string) *cobra.Command { // Group btclightclient queries under a subcommand cmd := &cobra.Command{ Use: types.ModuleName, diff --git a/x/btclightclient/genesis.go b/x/btclightclient/genesis.go index 6de07fb09..a71067089 100644 --- a/x/btclightclient/genesis.go +++ b/x/btclightclient/genesis.go @@ -1,14 +1,14 @@ package btclightclient import ( + "context" "github.com/babylonchain/babylon/x/btclightclient/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the capability module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { if err := genState.Validate(); err != nil { panic(err) } @@ -17,7 +17,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } // ExportGenesis returns the capability module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() baseBTCHeader := k.GetBaseBTCHeader(ctx) if baseBTCHeader == nil { diff --git a/x/btclightclient/keeper/base_btc_header.go b/x/btclightclient/keeper/base_btc_header.go index bc96dccf0..6c860a512 100644 --- a/x/btclightclient/keeper/base_btc_header.go +++ b/x/btclightclient/keeper/base_btc_header.go @@ -1,16 +1,16 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) *types.BTCHeaderInfo { +func (k Keeper) GetBaseBTCHeader(ctx context.Context) *types.BTCHeaderInfo { return k.headersState(ctx).BaseHeader() } // SetBaseBTCHeader checks whether a base BTC header exist, if not inserts it into storage -func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BTCHeaderInfo) { +func (k Keeper) SetBaseBTCHeader(ctx context.Context, baseBTCHeader types.BTCHeaderInfo) { existingHeader := k.headersState(ctx).BaseHeader() if existingHeader != nil { panic("A base BTC Header has already been set") diff --git a/x/btclightclient/keeper/export_headers_state_test.go b/x/btclightclient/keeper/export_headers_state_test.go index c82276f8c..af489048f 100644 --- a/x/btclightclient/keeper/export_headers_state_test.go +++ b/x/btclightclient/keeper/export_headers_state_test.go @@ -1,7 +1,9 @@ package keeper -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + "context" +) -func (k *Keeper) HeadersState(ctx sdk.Context) headersState { +func (k *Keeper) HeadersState(ctx context.Context) headersState { return k.headersState(ctx) } diff --git a/x/btclightclient/keeper/grpc_query_test.go b/x/btclightclient/keeper/grpc_query_test.go index 92218af53..c5ea9e5f4 100644 --- a/x/btclightclient/keeper/grpc_query_test.go +++ b/x/btclightclient/keeper/grpc_query_test.go @@ -10,7 +10,6 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) func FuzzHashesQuery(f *testing.F) { @@ -36,10 +35,9 @@ func FuzzHashesQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - sdkCtx := sdk.WrapSDKContext(ctx) // Test nil request - resp, err := blcKeeper.Hashes(sdkCtx, nil) + resp, err := blcKeeper.Hashes(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -53,7 +51,7 @@ func FuzzHashesQuery(f *testing.F) { key := datagen.GenRandomByteArray(r, bzSz) pagination := constructRequestWithKey(r, key) hashesRequest := types.NewQueryHashesRequest(pagination) - resp, err = blcKeeper.Hashes(sdkCtx, hashesRequest) + resp, err = blcKeeper.Hashes(ctx, hashesRequest) if resp != nil { t.Errorf("Invalid key led to a non-nil response") } @@ -87,7 +85,7 @@ func FuzzHashesQuery(f *testing.F) { hashesFound := make(map[string]bool, 0) for headersRetrieved := uint64(0); headersRetrieved < chainSize; headersRetrieved += limit { - resp, err = blcKeeper.Hashes(sdkCtx, hashesRequest) + resp, err = blcKeeper.Hashes(ctx, hashesRequest) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -136,10 +134,9 @@ func FuzzContainsQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - sdkCtx := sdk.WrapSDKContext(ctx) // Test nil input - resp, err := blcKeeper.Contains(sdkCtx, nil) + resp, err := blcKeeper.Contains(ctx, nil) if resp != nil { t.Fatalf("Nil input led to a non-nil response") } @@ -159,7 +156,7 @@ func FuzzContainsQuery(f *testing.F) { // Test with a non-existent header query, _ := types.NewQueryContainsRequest(datagen.GenRandomBTCHeaderInfo(r).Hash.MarshalHex()) - resp, err = blcKeeper.Contains(sdkCtx, query) + resp, err = blcKeeper.Contains(ctx, query) if err != nil { t.Errorf("Valid input let to an error: %s", err) } @@ -172,7 +169,7 @@ func FuzzContainsQuery(f *testing.F) { // Test with an existing header query, _ = types.NewQueryContainsRequest(chain.GetRandomHeaderInfo(r).Hash.MarshalHex()) - resp, err = blcKeeper.Contains(sdkCtx, query) + resp, err = blcKeeper.Contains(ctx, query) if err != nil { t.Errorf("Valid input let to an error: %s", err) } @@ -204,10 +201,9 @@ func FuzzMainChainQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - sdkCtx := sdk.WrapSDKContext(ctx) // Test nil input - resp, err := blcKeeper.MainChain(sdkCtx, nil) + resp, err := blcKeeper.MainChain(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -221,7 +217,7 @@ func FuzzMainChainQuery(f *testing.F) { key := datagen.GenRandomByteArray(r, bzSz) pagination := constructRequestWithKey(r, key) mainchainRequest := types.NewQueryMainChainRequest(pagination) - resp, err = blcKeeper.MainChain(sdkCtx, mainchainRequest) + resp, err = blcKeeper.MainChain(ctx, mainchainRequest) if resp != nil { t.Errorf("Invalid key led to a non-nil response") } @@ -242,7 +238,7 @@ func FuzzMainChainQuery(f *testing.F) { // Check whether the key being set to an element that does not exist leads to an error pagination = constructRequestWithKey(r, datagen.GenRandomBTCHeaderInfo(r).Hash.MustMarshal()) mainchainRequest = types.NewQueryMainChainRequest(pagination) - resp, err = blcKeeper.MainChain(sdkCtx, mainchainRequest) + resp, err = blcKeeper.MainChain(ctx, mainchainRequest) if resp != nil { t.Errorf("Key corresponding to header that does not exist led to a non-nil response") } @@ -279,7 +275,7 @@ func FuzzMainChainQuery(f *testing.F) { // Generate the initial query mainchainRequest = types.NewQueryMainChainRequest(pagination) for headersRetrieved := uint64(0); headersRetrieved < mcSize; headersRetrieved += limit { - resp, err = blcKeeper.MainChain(sdkCtx, mainchainRequest) + resp, err = blcKeeper.MainChain(ctx, mainchainRequest) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -332,10 +328,9 @@ func FuzzTipQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - sdkCtx := sdk.WrapSDKContext(ctx) // Test nil input - resp, err := blcKeeper.Tip(sdkCtx, nil) + resp, err := blcKeeper.Tip(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -353,7 +348,7 @@ func FuzzTipQuery(f *testing.F) { datagen.RandomInt(r, 50)+100, ) - resp, err = blcKeeper.Tip(sdkCtx, types.NewQueryTipRequest()) + resp, err = blcKeeper.Tip(ctx, types.NewQueryTipRequest()) if err != nil { t.Errorf("valid input led to an error: %s", err) } @@ -379,10 +374,9 @@ func FuzzBaseHeaderQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) - sdkCtx := sdk.WrapSDKContext(ctx) // Test nil input - resp, err := blcKeeper.BaseHeader(sdkCtx, nil) + resp, err := blcKeeper.BaseHeader(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -400,7 +394,7 @@ func FuzzBaseHeaderQuery(f *testing.F) { datagen.RandomInt(r, 50)+100, ) - resp, err = blcKeeper.BaseHeader(sdkCtx, types.NewQueryBaseHeaderRequest()) + resp, err = blcKeeper.BaseHeader(ctx, types.NewQueryBaseHeaderRequest()) if err != nil { t.Errorf("valid input led to an error: %s", err) } diff --git a/x/btclightclient/keeper/hooks.go b/x/btclightclient/keeper/hooks.go index cc8820791..17ff31d24 100644 --- a/x/btclightclient/keeper/hooks.go +++ b/x/btclightclient/keeper/hooks.go @@ -1,29 +1,29 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // Implements BTCLightClientHooks interface var _ types.BTCLightClientHooks = Keeper{} // AfterBTCHeaderInserted - call hook if registered -func (k Keeper) AfterBTCHeaderInserted(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) AfterBTCHeaderInserted(ctx context.Context, headerInfo *types.BTCHeaderInfo) { if k.hooks != nil { k.hooks.AfterBTCHeaderInserted(ctx, headerInfo) } } // AfterBTCRollBack - call hook if registered -func (k Keeper) AfterBTCRollBack(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) AfterBTCRollBack(ctx context.Context, headerInfo *types.BTCHeaderInfo) { if k.hooks != nil { k.hooks.AfterBTCRollBack(ctx, headerInfo) } } // AfterBTCRollForward - call hook if registered -func (k Keeper) AfterBTCRollForward(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) AfterBTCRollForward(ctx context.Context, headerInfo *types.BTCHeaderInfo) { if k.hooks != nil { k.hooks.AfterBTCRollForward(ctx, headerInfo) } diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index 336c4bd2b..9168b962d 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -1,12 +1,13 @@ package keeper import ( + "context" + corestoretypes "cosmossdk.io/core/store" "fmt" + "cosmossdk.io/log" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/wire" - "github.com/cometbft/cometbft/libs/log" - storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/cosmos/cosmos-sdk/codec" @@ -15,12 +16,11 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - hooks types.BTCLightClientHooks - btcConfig bbn.BtcConfig - bl *types.BtcLightClient + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService + hooks types.BTCLightClientHooks + btcConfig bbn.BtcConfig + bl *types.BtcLightClient } ) @@ -28,19 +28,17 @@ var _ types.BtcChainReadStore = (*headersState)(nil) func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, btcConfig bbn.BtcConfig, ) Keeper { bl := types.NewBtcLightClientFromParams(btcConfig.NetParams()) return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - hooks: nil, - btcConfig: btcConfig, - bl: bl, + cdc: cdc, + storeService: storeService, + hooks: nil, + btcConfig: btcConfig, + bl: bl, } } @@ -59,7 +57,7 @@ func (k *Keeper) SetHooks(bh types.BTCLightClientHooks) *Keeper { } func (k Keeper) insertHeaders( - ctx sdk.Context, + ctx context.Context, headers []*wire.BlockHeader, ) error { @@ -90,7 +88,7 @@ func (k Keeper) insertHeaders( return nil } -func (k Keeper) InsertHeaders(ctx sdk.Context, headers []bbn.BTCHeaderBytes) error { +func (k Keeper) InsertHeaders(ctx context.Context, headers []bbn.BTCHeaderBytes) error { if len(headers) == 0 { return types.ErrEmptyMessage } @@ -104,7 +102,7 @@ func (k Keeper) InsertHeaders(ctx sdk.Context, headers []bbn.BTCHeaderBytes) err } // BlockHeight returns the height of the provided header -func (k Keeper) BlockHeight(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) { +func (k Keeper) BlockHeight(ctx context.Context, headerHash *bbn.BTCHeaderHashBytes) (uint64, error) { if headerHash == nil { return 0, types.ErrEmptyMessage } @@ -119,7 +117,7 @@ func (k Keeper) BlockHeight(ctx sdk.Context, headerHash *bbn.BTCHeaderHashBytes) } // MainChainDepth returns the depth of the header in the main chain, or error if it does not exists -func (k Keeper) MainChainDepth(ctx sdk.Context, headerHashBytes *bbn.BTCHeaderHashBytes) (uint64, error) { +func (k Keeper) MainChainDepth(ctx context.Context, headerHashBytes *bbn.BTCHeaderHashBytes) (uint64, error) { if headerHashBytes == nil { return 0, types.ErrEmptyMessage } @@ -141,12 +139,12 @@ func (k Keeper) MainChainDepth(ctx sdk.Context, headerHashBytes *bbn.BTCHeaderHa return headerDepth, nil } -func (k Keeper) GetTipInfo(ctx sdk.Context) *types.BTCHeaderInfo { +func (k Keeper) GetTipInfo(ctx context.Context) *types.BTCHeaderInfo { return k.headersState(ctx).GetTip() } // GetHeaderByHash returns header with given hash, if it does not exists returns nil -func (k Keeper) GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *types.BTCHeaderInfo { +func (k Keeper) GetHeaderByHash(ctx context.Context, hash *bbn.BTCHeaderHashBytes) *types.BTCHeaderInfo { info, err := k.headersState(ctx).GetHeaderByHash(hash) if err != nil { @@ -157,7 +155,7 @@ func (k Keeper) GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) * } // GetHeaderByHeight returns header with given height from main chain, returns nil if such header is not found -func (k Keeper) GetHeaderByHeight(ctx sdk.Context, height uint64) *types.BTCHeaderInfo { +func (k Keeper) GetHeaderByHeight(ctx context.Context, height uint64) *types.BTCHeaderInfo { header, err := k.headersState(ctx).GetHeaderByHeight(height) if err != nil { @@ -170,7 +168,7 @@ func (k Keeper) GetHeaderByHeight(ctx sdk.Context, height uint64) *types.BTCHead // GetMainChainFrom returns the current canonical chain from the given height up to the tip // If the height is higher than the tip, it returns an empty slice // If startHeight is 0, it returns the entire main chain -func (k Keeper) GetMainChainFrom(ctx sdk.Context, startHeight uint64) []*types.BTCHeaderInfo { +func (k Keeper) GetMainChainFrom(ctx context.Context, startHeight uint64) []*types.BTCHeaderInfo { headers := make([]*types.BTCHeaderInfo, 0) accHeaderFn := func(header *types.BTCHeaderInfo) bool { headers = append(headers, header) @@ -182,7 +180,7 @@ func (k Keeper) GetMainChainFrom(ctx sdk.Context, startHeight uint64) []*types.B // GetMainChainUpTo returns the current canonical chain as a collection of block headers // starting from the tip and ending on the header that has `depth` distance from it. -func (k Keeper) GetMainChainUpTo(ctx sdk.Context, depth uint64) []*types.BTCHeaderInfo { +func (k Keeper) GetMainChainUpTo(ctx context.Context, depth uint64) []*types.BTCHeaderInfo { headers := make([]*types.BTCHeaderInfo, 0) var currentDepth = uint64(0) @@ -202,13 +200,13 @@ func (k Keeper) GetMainChainUpTo(ctx sdk.Context, depth uint64) []*types.BTCHead return headers } -// Retrieves whole header chain in reverse order -func (K Keeper) GetMainChainReverse(ctx sdk.Context) []*types.BTCHeaderInfo { +// GetMainChainReverse Retrieves whole header chain in reverse order +func (k Keeper) GetMainChainReverse(ctx context.Context) []*types.BTCHeaderInfo { headers := make([]*types.BTCHeaderInfo, 0) accHeaderFn := func(header *types.BTCHeaderInfo) bool { headers = append(headers, header) return false } - K.headersState(ctx).IterateReverseHeaders(accHeaderFn) + k.headersState(ctx).IterateReverseHeaders(accHeaderFn) return headers } diff --git a/x/btclightclient/keeper/msg_server_test.go b/x/btclightclient/keeper/msg_server_test.go index 85b7de00f..338a80ee9 100644 --- a/x/btclightclient/keeper/msg_server_test.go +++ b/x/btclightclient/keeper/msg_server_test.go @@ -16,7 +16,7 @@ import ( func setupMsgServer(t testing.TB) (types.MsgServer, *keeper.Keeper, context.Context) { k, ctx := keepertest.BTCLightClientKeeper(t) - return keeper.NewMsgServerImpl(*k), k, sdk.WrapSDKContext(ctx) + return keeper.NewMsgServerImpl(*k), k, ctx } // Property: Inserting valid chain which has current tip as parent, should always update the chain @@ -106,7 +106,7 @@ func FuzzMsgServerReorgChain(f *testing.F) { reorgDepth := r.Intn(int(chainLength-1)) + 1 forkHeaderHeight := initTip.Height - uint64(reorgDepth) - forkHeader := blcKeeper.GetHeaderByHeight(ctx, uint64(forkHeaderHeight)) + forkHeader := blcKeeper.GetHeaderByHeight(ctx, forkHeaderHeight) require.NotNil(t, forkHeader) // fork chain will always be longer that current c diff --git a/x/btclightclient/keeper/state.go b/x/btclightclient/keeper/state.go index 04c8057b8..8767fee74 100644 --- a/x/btclightclient/keeper/state.go +++ b/x/btclightclient/keeper/state.go @@ -1,28 +1,31 @@ package keeper import ( + "context" + storetypes "cosmossdk.io/store/types" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) type headersState struct { cdc codec.BinaryCodec - headers sdk.KVStore - hashToHeight sdk.KVStore + headers storetypes.KVStore + hashToHeight storetypes.KVStore } -func (k Keeper) headersState(ctx sdk.Context) headersState { +func (k Keeper) headersState(ctx context.Context) headersState { // Build the headersState storage - store := ctx.KVStore(k.storeKey) + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) return headersState{ cdc: k.cdc, - headers: prefix.NewStore(store, types.HeadersObjectPrefix), - hashToHeight: prefix.NewStore(store, types.HashToHeightPrefix), + headers: prefix.NewStore(storeAdapter, types.HeadersObjectPrefix), + hashToHeight: prefix.NewStore(storeAdapter, types.HashToHeightPrefix), } } @@ -75,7 +78,7 @@ func (s headersState) rollBackHeadersUpTo(height uint64) { } } -// GetHeader Retrieve a header by its height and hash +// GetHeaderByHeight Retrieve a header by its height and hash func (s headersState) GetHeaderByHeight(height uint64) (*types.BTCHeaderInfo, error) { headersKey := types.HeadersObjectKey(height) @@ -143,7 +146,7 @@ func (s headersState) TipExists() bool { } func (s headersState) IterateReverseHeaders(fn func(*types.BTCHeaderInfo) bool) { - // Iterate it in reverse in order to get highest heights first + // Iterate it in reverse in order to get the highest heights first iter := s.headers.ReverseIterator(nil, nil) defer iter.Close() diff --git a/x/btclightclient/keeper/triggers.go b/x/btclightclient/keeper/triggers.go index b3be7b9cc..8b9bdaea1 100644 --- a/x/btclightclient/keeper/triggers.go +++ b/x/btclightclient/keeper/triggers.go @@ -1,27 +1,28 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) triggerHeaderInserted(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) triggerHeaderInserted(ctx context.Context, headerInfo *types.BTCHeaderInfo) { // Trigger AfterBTCHeaderInserted hook k.AfterBTCHeaderInserted(ctx, headerInfo) // Emit HeaderInserted event - ctx.EventManager().EmitTypedEvent(&types.EventBTCHeaderInserted{Header: headerInfo}) //nolint:errcheck + sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(&types.EventBTCHeaderInserted{Header: headerInfo}) //nolint:errcheck } -func (k Keeper) triggerRollBack(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) triggerRollBack(ctx context.Context, headerInfo *types.BTCHeaderInfo) { // Trigger AfterBTCRollBack hook k.AfterBTCRollBack(ctx, headerInfo) // Emit BTCRollBack event - ctx.EventManager().EmitTypedEvent(&types.EventBTCRollBack{Header: headerInfo}) //nolint:errcheck + sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(&types.EventBTCRollBack{Header: headerInfo}) //nolint:errcheck } -func (k Keeper) triggerRollForward(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (k Keeper) triggerRollForward(ctx context.Context, headerInfo *types.BTCHeaderInfo) { // Trigger AfterBTCRollForward hook k.AfterBTCRollForward(ctx, headerInfo) // Emit BTCRollForward event - ctx.EventManager().EmitTypedEvent(&types.EventBTCRollForward{Header: headerInfo}) //nolint:errcheck + sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(&types.EventBTCRollForward{Header: headerInfo}) //nolint:errcheck } diff --git a/x/btclightclient/keeper/utils_test.go b/x/btclightclient/keeper/utils_test.go index 639458f72..413918cc3 100644 --- a/x/btclightclient/keeper/utils_test.go +++ b/x/btclightclient/keeper/utils_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "context" "math/big" "math/rand" "testing" @@ -12,7 +13,6 @@ import ( "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -38,15 +38,15 @@ func NewMockHooks() *MockHooks { } } -func (m *MockHooks) AfterBTCRollForward(_ sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (m *MockHooks) AfterBTCRollForward(_ context.Context, headerInfo *types.BTCHeaderInfo) { m.AfterBTCRollForwardStore = append(m.AfterBTCRollForwardStore, headerInfo) } -func (m *MockHooks) AfterBTCRollBack(_ sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (m *MockHooks) AfterBTCRollBack(_ context.Context, headerInfo *types.BTCHeaderInfo) { m.AfterBTCRollBackStore = append(m.AfterBTCRollBackStore, headerInfo) } -func (m *MockHooks) AfterBTCHeaderInserted(_ sdk.Context, headerInfo *types.BTCHeaderInfo) { +func (m *MockHooks) AfterBTCHeaderInserted(_ context.Context, headerInfo *types.BTCHeaderInfo) { m.AfterBTCHeaderInsertedStore = append(m.AfterBTCHeaderInsertedStore, headerInfo) } @@ -60,7 +60,7 @@ func genRandomChain( t *testing.T, r *rand.Rand, k *keeper.Keeper, - ctx sdk.Context, + ctx context.Context, initialHeight uint64, chainLength uint64, ) (*types.BTCHeaderInfo, *datagen.BTCHeaderPartialChain) { @@ -82,7 +82,7 @@ func genRandomChain( func checkTip( t *testing.T, - ctx sdk.Context, + ctx context.Context, blcKeeper *keeper.Keeper, expectedWork sdkmath.Uint, expectedHeight uint64, diff --git a/x/btclightclient/module.go b/x/btclightclient/module.go index ba1fd57be..6c6982d4a 100644 --- a/x/btclightclient/module.go +++ b/x/btclightclient/module.go @@ -2,6 +2,7 @@ package btclightclient import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -22,8 +23,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -98,22 +101,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -137,14 +134,12 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the btclightclient module's genesis initialization It returns // no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. @@ -157,10 +152,20 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(_ context.Context) error { + return nil +} // EndBlock executes all ABCI EndBlock logic respective to the capability module. It // returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(_ context.Context) ([]abci.ValidatorUpdate, error) { + return []abci.ValidatorUpdate{}, nil +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker } diff --git a/x/btclightclient/module_simulation.go b/x/btclightclient/module_simulation.go index 1de351013..2ee7e42a0 100644 --- a/x/btclightclient/module_simulation.go +++ b/x/btclightclient/module_simulation.go @@ -1,12 +1,10 @@ package btclightclient import ( - simappparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/testutil/sample" btclightclientsimulation "github.com/babylonchain/babylon/x/btclightclient/simulation" "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -16,7 +14,6 @@ import ( var ( _ = sample.AccAddress _ = btclightclientsimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -31,16 +28,17 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(btclightclientGenesis) } +// RegisterStoreDecoder registers a decoder +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { +} + // ProposalContents doesn't return any content functions for governance proposals func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { return nil } -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} - // WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { +func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { operations := make([]simtypes.WeightedOperation, 0) return operations diff --git a/x/btclightclient/types/btc_header_info_test.go b/x/btclightclient/types/btc_header_info_test.go index cd523563a..8a9e8d2ea 100644 --- a/x/btclightclient/types/btc_header_info_test.go +++ b/x/btclightclient/types/btc_header_info_test.go @@ -38,7 +38,7 @@ func FuzzNewHeaderInfo(f *testing.F) { gotHeaderBytes := headerInfo.Header.MustMarshal() if !bytes.Equal(expectedHeaderBytes.MustMarshal(), gotHeaderBytes) { - t.Errorf("Expected header %s got %s", expectedHeaderBytes, gotHeaderBytes) + t.Errorf("Expected header %v got %s", expectedHeaderBytes, gotHeaderBytes) } gotHashBytes := *headerInfo.Hash diff --git a/x/btclightclient/types/btc_light_client.go b/x/btclightclient/types/btc_light_client.go index f3b36b438..ea4f20c89 100644 --- a/x/btclightclient/types/btc_light_client.go +++ b/x/btclightclient/types/btc_light_client.go @@ -256,7 +256,7 @@ func (d *DisableHeaderInTheFutureValidationTimeSource) AdjustedTime() time.Time return d.h.Timestamp } -func (d *DisableHeaderInTheFutureValidationTimeSource) AddTimeSample(id string, timeVal time.Time) { +func (d *DisableHeaderInTheFutureValidationTimeSource) AddTimeSample(_ string, _ time.Time) { //no op } @@ -390,7 +390,8 @@ func (l *BtcLightClient) InsertHeaders(readStore BtcChainReadStore, headers []*w tipOfNewChain := store.headers[len(store.headers)-1] if tipOfNewChain.totalWork.LTE(currentTip.totalWork) { - return nil, fmt.Errorf("new chain work %d, is not better than current tip work %d: %w", tipOfNewChain.totalWork, currentTip.totalWork, ErrChainWithNotEnoughWork) + return nil, fmt.Errorf("new chain work %s, is not better than current tip work %s: %w", + tipOfNewChain.totalWork.String(), currentTip.totalWork.String(), ErrChainWithNotEnoughWork) } return &InsertResult{ diff --git a/x/btclightclient/types/btclightclient.pb.go b/x/btclightclient/types/btclightclient.pb.go index d4dc5f187..dd50ff83e 100644 --- a/x/btclightclient/types/btclightclient.pb.go +++ b/x/btclightclient/types/btclightclient.pb.go @@ -4,9 +4,9 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -37,7 +37,7 @@ type BTCHeaderInfo struct { Header *github_com_babylonchain_babylon_types.BTCHeaderBytes `protobuf:"bytes,1,opt,name=header,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderBytes" json:"header,omitempty"` Hash *github_com_babylonchain_babylon_types.BTCHeaderHashBytes `protobuf:"bytes,2,opt,name=hash,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderHashBytes" json:"hash,omitempty"` Height uint64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` - Work *github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,4,opt,name=work,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"work,omitempty"` + Work *cosmossdk_io_math.Uint `protobuf:"bytes,4,opt,name=work,proto3,customtype=cosmossdk.io/math.Uint" json:"work,omitempty"` } func (m *BTCHeaderInfo) Reset() { *m = BTCHeaderInfo{} } @@ -89,25 +89,25 @@ func init() { } var fileDescriptor_84bf438d909b681d = []byte{ - // 279 bytes of a gzipped FileDescriptorProto + // 282 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4b, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0x65, 0x86, 0x68, 0x22, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0xf5, 0x7a, 0x68, 0xb2, 0x65, 0x86, 0x52, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0x55, 0xfa, 0x20, 0x16, 0x44, - 0x83, 0x52, 0x0f, 0x13, 0x17, 0xaf, 0x53, 0x88, 0xb3, 0x47, 0x6a, 0x62, 0x4a, 0x6a, 0x91, 0x67, - 0x5e, 0x5a, 0xbe, 0x50, 0x00, 0x17, 0x5b, 0x06, 0x98, 0x27, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe3, - 0x64, 0x71, 0xeb, 0x9e, 0xbc, 0x49, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, - 0x3e, 0xd4, 0x86, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x18, 0x47, 0xbf, 0xa4, 0xb2, 0x20, 0xb5, 0x58, - 0x0f, 0x6e, 0x90, 0x53, 0x65, 0x49, 0x6a, 0x71, 0x10, 0xd4, 0x1c, 0xa1, 0x00, 0x2e, 0x96, 0x8c, - 0xc4, 0xe2, 0x0c, 0x09, 0x26, 0xb0, 0x79, 0x36, 0xb7, 0xee, 0xc9, 0x5b, 0x90, 0x68, 0x9e, 0x47, - 0x62, 0x71, 0x06, 0xc4, 0x4c, 0xb0, 0x49, 0x42, 0x62, 0x20, 0x37, 0x82, 0xbc, 0x27, 0xc1, 0xac, - 0xc0, 0xa8, 0xc1, 0x12, 0x04, 0xe5, 0x09, 0xd9, 0x73, 0xb1, 0x94, 0xe7, 0x17, 0x65, 0x4b, 0xb0, - 0x80, 0x6d, 0xd2, 0xbe, 0x75, 0x4f, 0x5e, 0x1d, 0xc9, 0xa6, 0xe4, 0xfc, 0xe2, 0xdc, 0xfc, 0x62, - 0x28, 0xa5, 0x5b, 0x9c, 0x92, 0x0d, 0xb5, 0x26, 0x34, 0x33, 0xaf, 0x24, 0x08, 0xac, 0xd1, 0x29, - 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, - 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xcc, 0x08, 0x39, 0xb9, 0x02, - 0x3d, 0x8e, 0xc0, 0x86, 0x27, 0xb1, 0x81, 0xc3, 0xd9, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x8a, - 0x8d, 0x9c, 0x25, 0xca, 0x01, 0x00, 0x00, + 0x83, 0xd2, 0x6f, 0x46, 0x2e, 0x5e, 0xa7, 0x10, 0x67, 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0x22, 0xcf, + 0xbc, 0xb4, 0x7c, 0xa1, 0x00, 0x2e, 0xb6, 0x0c, 0x30, 0x4f, 0x82, 0x51, 0x81, 0x51, 0x83, 0xc7, + 0xc9, 0xe2, 0xd6, 0x3d, 0x79, 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, + 0x7d, 0xa8, 0x0d, 0xc9, 0x19, 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, + 0x1e, 0xdc, 0x20, 0xa7, 0xca, 0x92, 0xd4, 0xe2, 0x20, 0xa8, 0x39, 0x42, 0x01, 0x5c, 0x2c, 0x19, + 0x89, 0xc5, 0x19, 0x12, 0x4c, 0x60, 0xf3, 0x6c, 0x6e, 0xdd, 0x93, 0xb7, 0x20, 0xd1, 0x3c, 0x8f, + 0xc4, 0xe2, 0x0c, 0x88, 0x99, 0x60, 0x93, 0x84, 0xc4, 0x40, 0x6e, 0x04, 0x79, 0x4f, 0x82, 0x59, + 0x81, 0x51, 0x83, 0x25, 0x08, 0xca, 0x13, 0xd2, 0xe3, 0x62, 0x29, 0xcf, 0x2f, 0xca, 0x96, 0x60, + 0x01, 0xdb, 0x24, 0x75, 0xeb, 0x9e, 0xbc, 0x58, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x71, 0x4a, + 0xb6, 0x5e, 0x66, 0xbe, 0x7e, 0x6e, 0x62, 0x49, 0x86, 0x5e, 0x68, 0x66, 0x5e, 0x49, 0x10, 0x58, + 0x9d, 0x53, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, + 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x99, 0x11, 0x72, + 0x61, 0x05, 0x7a, 0x94, 0x80, 0x9d, 0x9c, 0xc4, 0x06, 0x0e, 0x56, 0x63, 0x40, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x13, 0xc6, 0x69, 0x40, 0xb9, 0x01, 0x00, 0x00, } func (m *BTCHeaderInfo) Marshal() (dAtA []byte, err error) { @@ -362,7 +362,7 @@ func (m *BTCHeaderInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_cosmos_cosmos_sdk_types.Uint + var v cosmossdk_io_math.Uint m.Work = &v if err := m.Work.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/x/btclightclient/types/codec.go b/x/btclightclient/types/codec.go index 4bf793fb8..c1e243974 100644 --- a/x/btclightclient/types/codec.go +++ b/x/btclightclient/types/codec.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterCodec(_ *codec.LegacyAmino) { } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { diff --git a/x/btclightclient/types/expected_keepers.go b/x/btclightclient/types/expected_keepers.go index 3db963c48..387b50c76 100644 --- a/x/btclightclient/types/expected_keepers.go +++ b/x/btclightclient/types/expected_keepers.go @@ -1,24 +1,11 @@ package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" + "context" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here -} - type BTCLightClientHooks interface { - AfterBTCRollBack(ctx sdk.Context, headerInfo *BTCHeaderInfo) // Must be called after the chain is rolled back - AfterBTCRollForward(ctx sdk.Context, headerInfo *BTCHeaderInfo) // Must be called after the chain is rolled forward - AfterBTCHeaderInserted(ctx sdk.Context, headerInfo *BTCHeaderInfo) // Must be called after a header is inserted + AfterBTCRollBack(ctx context.Context, headerInfo *BTCHeaderInfo) // Must be called after the chain is rolled back + AfterBTCRollForward(ctx context.Context, headerInfo *BTCHeaderInfo) // Must be called after the chain is rolled forward + AfterBTCHeaderInserted(ctx context.Context, headerInfo *BTCHeaderInfo) // Must be called after a header is inserted } diff --git a/x/btclightclient/types/hooks.go b/x/btclightclient/types/hooks.go index a2d2af000..779bfc7ca 100644 --- a/x/btclightclient/types/hooks.go +++ b/x/btclightclient/types/hooks.go @@ -1,7 +1,7 @@ package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "context" ) var _ BTCLightClientHooks = &MultiBTCLightClientHooks{} @@ -12,19 +12,19 @@ func NewMultiBTCLightClientHooks(hooks ...BTCLightClientHooks) MultiBTCLightClie return hooks } -func (h MultiBTCLightClientHooks) AfterBTCHeaderInserted(ctx sdk.Context, headerInfo *BTCHeaderInfo) { +func (h MultiBTCLightClientHooks) AfterBTCHeaderInserted(ctx context.Context, headerInfo *BTCHeaderInfo) { for i := range h { h[i].AfterBTCHeaderInserted(ctx, headerInfo) } } -func (h MultiBTCLightClientHooks) AfterBTCRollBack(ctx sdk.Context, headerInfo *BTCHeaderInfo) { +func (h MultiBTCLightClientHooks) AfterBTCRollBack(ctx context.Context, headerInfo *BTCHeaderInfo) { for i := range h { h[i].AfterBTCRollBack(ctx, headerInfo) } } -func (h MultiBTCLightClientHooks) AfterBTCRollForward(ctx sdk.Context, headerInfo *BTCHeaderInfo) { +func (h MultiBTCLightClientHooks) AfterBTCRollForward(ctx context.Context, headerInfo *BTCHeaderInfo) { for i := range h { h[i].AfterBTCRollForward(ctx, headerInfo) } diff --git a/x/btclightclient/types/msgs_test.go b/x/btclightclient/types/msgs_test.go index 9e05a4b75..a55240fa0 100644 --- a/x/btclightclient/types/msgs_test.go +++ b/x/btclightclient/types/msgs_test.go @@ -29,7 +29,8 @@ func FuzzMsgInsertHeader(f *testing.F) { // Get the signer structure var signer sdk.AccAddress - signer.Unmarshal(addressBytes) //nolint:errcheck // this is a test + err := signer.Unmarshal(addressBytes) + require.NoError(t, err) // Perform modifications on the header errorKind = r.Intn(2) @@ -41,7 +42,7 @@ func FuzzMsgInsertHeader(f *testing.F) { bitsBig = sdkmath.NewUintFromBigInt(&maxDifficulty) case 1: // Zero PoW - bitsBig = sdk.NewUint(0) + bitsBig = sdkmath.NewUint(0) default: bitsBig = sdkmath.NewUintFromBigInt(&maxDifficulty) } @@ -66,7 +67,7 @@ func FuzzMsgInsertHeader(f *testing.F) { } // empty string - _, err := types.NewMsgInsertHeaders(signer, "") + _, err = types.NewMsgInsertHeaders(signer, "") require.NotNil(t, err) // hex string with invalid length diff --git a/x/btclightclient/types/tx.pb.go b/x/btclightclient/types/tx.pb.go index cbb091e00..c642f99a5 100644 --- a/x/btclightclient/types/tx.pb.go +++ b/x/btclightclient/types/tx.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -122,24 +123,26 @@ func init() { } var fileDescriptor_5f638eee60234021 = []byte{ - // 266 bytes of a gzipped FileDescriptorProto + // 303 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0x65, 0x86, 0xfa, 0x25, 0x15, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0x35, 0x7a, 0xa8, 0x6a, 0xf4, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xaa, 0xf4, 0x41, - 0x2c, 0x88, 0x06, 0xa5, 0x3a, 0x2e, 0x01, 0xdf, 0xe2, 0x74, 0xcf, 0xbc, 0xe2, 0xd4, 0xa2, 0x12, - 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0xa2, 0x62, 0x21, 0x31, 0x2e, 0xb6, 0xe2, 0xcc, 0xf4, 0xbc, 0xd4, - 0x22, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x28, 0x4f, 0x28, 0x88, 0x8b, 0x3d, 0x03, 0xa2, - 0x44, 0x82, 0x49, 0x81, 0x59, 0x83, 0xc7, 0xc9, 0xe2, 0xd6, 0x3d, 0x79, 0x93, 0xf4, 0xcc, 0x92, - 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0xe5, 0xc9, 0x19, 0x89, 0x99, 0x79, 0x30, - 0x8e, 0x7e, 0x49, 0x65, 0x41, 0x6a, 0xb1, 0x9e, 0x53, 0x88, 0x33, 0xc4, 0x78, 0xa7, 0xca, 0x92, - 0xd4, 0xe2, 0x20, 0x98, 0x41, 0x4a, 0x52, 0x5c, 0x12, 0xe8, 0xf6, 0x07, 0xa5, 0x16, 0x17, 0xe4, - 0xe7, 0x15, 0xa7, 0x1a, 0x55, 0x71, 0x31, 0xfb, 0x16, 0xa7, 0x0b, 0x15, 0x73, 0xf1, 0xa2, 0xba, - 0x4f, 0x5b, 0x0f, 0xa7, 0x2f, 0xf5, 0xd0, 0x0d, 0x93, 0x32, 0x26, 0x41, 0x31, 0xcc, 0x66, 0x25, - 0x06, 0xa7, 0x80, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, - 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x23, 0xe4, - 0xe1, 0x0a, 0xf4, 0x08, 0x02, 0x87, 0x40, 0x12, 0x1b, 0x38, 0xc0, 0x8d, 0x01, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x75, 0x7e, 0xfc, 0x67, 0xc7, 0x01, 0x00, 0x00, + 0x2c, 0x88, 0x06, 0x29, 0xf1, 0xe4, 0xfc, 0xe2, 0xdc, 0xfc, 0x62, 0xfd, 0xdc, 0xe2, 0x74, 0x90, + 0x41, 0xb9, 0xc5, 0xe9, 0x10, 0x09, 0xa5, 0x6e, 0x46, 0x2e, 0x01, 0xdf, 0xe2, 0x74, 0xcf, 0xbc, + 0xe2, 0xd4, 0xa2, 0x12, 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0xa2, 0x62, 0x21, 0x31, 0x2e, 0xb6, 0xe2, + 0xcc, 0xf4, 0xbc, 0xd4, 0x22, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x28, 0x4f, 0x28, 0x88, + 0x8b, 0x3d, 0x03, 0xa2, 0x44, 0x82, 0x49, 0x81, 0x59, 0x83, 0xc7, 0xc9, 0xe2, 0xd6, 0x3d, 0x79, + 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0xb3, 0x92, 0x33, + 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x3d, 0xa7, 0x10, 0x67, 0x88, + 0xf1, 0x4e, 0x95, 0x25, 0xa9, 0xc5, 0x41, 0x30, 0x83, 0xac, 0xb8, 0x9b, 0x9e, 0x6f, 0xd0, 0x82, + 0x5a, 0xa0, 0x24, 0xc5, 0x25, 0x81, 0xee, 0x98, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, + 0xa3, 0x46, 0x46, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0xa1, 0x62, 0x2e, 0x5e, 0x54, 0xd7, 0x6a, 0xeb, + 0xe1, 0x0c, 0x0d, 0x3d, 0x74, 0xd3, 0xa4, 0x8c, 0x49, 0x50, 0x0c, 0xb3, 0x5a, 0x89, 0x41, 0x8a, + 0xb5, 0xe1, 0xf9, 0x06, 0x2d, 0x46, 0xa7, 0x80, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, + 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, + 0x63, 0x88, 0x32, 0x23, 0x14, 0x0a, 0x15, 0xe8, 0xf1, 0x09, 0x0e, 0x96, 0x24, 0x36, 0x70, 0x34, + 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x5f, 0xe3, 0xd7, 0xf6, 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/btclightclient/types/work_test.go b/x/btclightclient/types/work_test.go index e4e20a458..a68410244 100644 --- a/x/btclightclient/types/work_test.go +++ b/x/btclightclient/types/work_test.go @@ -1,9 +1,9 @@ package types_test import ( + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" "math/rand" "testing" ) @@ -14,12 +14,12 @@ func FuzzCumulativeWork(f *testing.F) { r := rand.New(rand.NewSource(seed)) numa := r.Uint64() numb := r.Uint64() - biga := sdk.NewUint(numa) - bigb := sdk.NewUint(numb) + biga := sdkmath.NewUint(numa) + bigb := sdkmath.NewUint(numb) gotSum := types.CumulativeWork(biga, bigb) - expectedSum := sdk.NewUint(0) + expectedSum := sdkmath.NewUint(0) expectedSum = expectedSum.Add(biga) expectedSum = expectedSum.Add(bigb) diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index eb54889d4..9c94b9481 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -1,16 +1,16 @@ package btcstaking import ( + "context" "time" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) // index BTC height at the current height @@ -23,11 +23,11 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) if k.IsBTCStakingActivated(ctx) { k.RecordRewardDistCache(ctx) } - + return nil } -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { +func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index d3fcbb98f..6dedadee8 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -14,8 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" - secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/spf13/cobra" ) @@ -81,7 +80,7 @@ func NewCreateBTCValidatorCmd() *cobra.Command { ) // get commission rateStr, _ := fs.GetString(FlagCommissionRate) - rate, err := sdk.NewDecFromStr(rateStr) + rate, err := sdkmath.LegacyNewDecFromStr(rateStr) if err != nil { return err } diff --git a/x/btcstaking/genesis.go b/x/btcstaking/genesis.go index f8d5ff7cc..4131d8bd4 100644 --- a/x/btcstaking/genesis.go +++ b/x/btcstaking/genesis.go @@ -1,20 +1,20 @@ package btcstaking import ( + "context" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) } } // ExportGenesis returns the module's exported genesis -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) diff --git a/x/btcstaking/keeper/btc_delegations.go b/x/btcstaking/keeper/btc_delegations.go index a0b006548..74a67fa98 100644 --- a/x/btcstaking/keeper/btc_delegations.go +++ b/x/btcstaking/keeper/btc_delegations.go @@ -1,20 +1,21 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/runtime" ) -func (k Keeper) setBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) { +func (k Keeper) setBTCDelegation(ctx context.Context, btcDel *types.BTCDelegation) { store := k.btcDelegationStore(ctx) stakingTxHash := btcDel.MustGetStakingTxHash() btcDelBytes := k.cdc.MustMarshal(btcDel) store.Set(stakingTxHash[:], btcDelBytes) } -func (k Keeper) getBTCDelegation(ctx sdk.Context, stakingTxHash chainhash.Hash) *types.BTCDelegation { +func (k Keeper) getBTCDelegation(ctx context.Context, stakingTxHash chainhash.Hash) *types.BTCDelegation { store := k.btcDelegationStore(ctx) btcDelBytes := store.Get(stakingTxHash[:]) if len(btcDelBytes) == 0 { @@ -29,7 +30,7 @@ func (k Keeper) getBTCDelegation(ctx sdk.Context, stakingTxHash chainhash.Hash) // prefix: BTCDelegationKey // key: BTC delegation's staking tx hash // value: BTCDelegation -func (k Keeper) btcDelegationStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BTCDelegationKey) +func (k Keeper) btcDelegationStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.BTCDelegationKey) } diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index e41d275fc..f3db189c9 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -1,18 +1,19 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" ) // AddBTCDelegation indexes the given BTC delegation in the BTC delegator store, and saves // it under BTC delegation store -func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) error { +func (k Keeper) AddBTCDelegation(ctx context.Context, btcDel *types.BTCDelegation) error { if err := btcDel.ValidateBasic(); err != nil { return err } @@ -54,7 +55,7 @@ func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) e // updateBTCDelegation updates an existing BTC delegation w.r.t. validator BTC PK, delegator BTC PK, // and staking tx hash by using a given function func (k Keeper) updateBTCDelegation( - ctx sdk.Context, + ctx context.Context, stakingTxHashStr string, modifyFn func(*types.BTCDelegation) error, ) error { @@ -77,7 +78,7 @@ func (k Keeper) updateBTCDelegation( // AddCovenantSigToBTCDelegation adds a given covenant sig to a BTC delegation // with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddCovenantSigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, covenantSig *bbn.BIP340Signature) error { +func (k Keeper) AddCovenantSigToBTCDelegation(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, covenantSig *bbn.BIP340Signature) error { addCovenantSig := func(btcDel *types.BTCDelegation) error { if btcDel.CovenantSig != nil { return fmt.Errorf("the BTC delegation with staking tx hash %s already has a covenant signature", stakingTxHash) @@ -90,7 +91,7 @@ func (k Keeper) AddCovenantSigToBTCDelegation(ctx sdk.Context, valBTCPK *bbn.BIP } func (k Keeper) AddUndelegationToBTCDelegation( - ctx sdk.Context, + ctx context.Context, stakingTxHash string, ud *types.BTCUndelegation, ) error { @@ -106,7 +107,7 @@ func (k Keeper) AddUndelegationToBTCDelegation( } func (k Keeper) AddCovenantSigsToUndelegation( - ctx sdk.Context, + ctx context.Context, stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, slashUnbondingTxSig *bbn.BIP340Signature, @@ -129,7 +130,7 @@ func (k Keeper) AddCovenantSigsToUndelegation( } // hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator -func (k Keeper) hasBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { +func (k Keeper) hasBTCDelegatorDelegations(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { valBTCPKBytes := valBTCPK.MustMarshal() delBTCPKBytes := delBTCPK.MustMarshal() @@ -141,7 +142,7 @@ func (k Keeper) hasBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340 } // getBTCDelegatorDelegationIndex gets the BTC delegation index with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegatorDelegationIndex(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegationIndex, error) { +func (k Keeper) getBTCDelegatorDelegationIndex(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegationIndex, error) { valBTCPKBytes := valBTCPK.MustMarshal() delBTCPKBytes := delBTCPK.MustMarshal() store := k.btcDelegatorStore(ctx, valBTCPK) @@ -163,7 +164,7 @@ func (k Keeper) getBTCDelegatorDelegationIndex(ctx sdk.Context, valBTCPK *bbn.BI } // getBTCDelegatorDelegations gets the BTC delegations with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { +func (k Keeper) getBTCDelegatorDelegations(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) if err != nil { return nil, err @@ -183,7 +184,7 @@ func (k Keeper) getBTCDelegatorDelegations(ctx sdk.Context, valBTCPK *bbn.BIP340 } // GetBTCDelegation gets the BTC delegation with a given staking tx hash -func (k Keeper) GetBTCDelegation(ctx sdk.Context, stakingTxHashStr string) (*types.BTCDelegation, error) { +func (k Keeper) GetBTCDelegation(ctx context.Context, stakingTxHashStr string) (*types.BTCDelegation, error) { // decode staking tx hash string stakingTxHash, err := chainhash.NewHashFromStr(stakingTxHashStr) if err != nil { @@ -197,8 +198,8 @@ func (k Keeper) GetBTCDelegation(ctx sdk.Context, stakingTxHashStr string) (*typ // prefix: BTCDelegatorKey || validator's Bitcoin secp256k1 PK // key: delegator's Bitcoin secp256k1 PK // value: BTCDelegatorDelegationIndex (a list of BTCDelegations' staking tx hashes) -func (k Keeper) btcDelegatorStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { - store := ctx.KVStore(k.storeKey) - delegationStore := prefix.NewStore(store, types.BTCDelegatorKey) +func (k Keeper) btcDelegatorStore(ctx context.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + delegationStore := prefix.NewStore(storeAdapter, types.BTCDelegatorKey) return prefix.NewStore(delegationStore, valBTCPK.MustMarshal()) } diff --git a/x/btcstaking/keeper/btc_height_index.go b/x/btcstaking/keeper/btc_height_index.go index 6b82878ca..90954aa05 100644 --- a/x/btcstaking/keeper/btc_height_index.go +++ b/x/btcstaking/keeper/btc_height_index.go @@ -1,14 +1,16 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // IndexBTCHeight indexes the current BTC height, and saves it to KVStore -func (k Keeper) IndexBTCHeight(ctx sdk.Context) { - babylonHeight := uint64(ctx.BlockHeight()) +func (k Keeper) IndexBTCHeight(ctx context.Context) { + babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) btcTip := k.btclcKeeper.GetTipInfo(ctx) if btcTip == nil { return @@ -18,7 +20,7 @@ func (k Keeper) IndexBTCHeight(ctx sdk.Context) { store.Set(sdk.Uint64ToBigEndian(babylonHeight), sdk.Uint64ToBigEndian(btcHeight)) } -func (k Keeper) GetBTCHeightAtBabylonHeight(ctx sdk.Context, babylonHeight uint64) (uint64, error) { +func (k Keeper) GetBTCHeightAtBabylonHeight(ctx context.Context, babylonHeight uint64) (uint64, error) { store := k.btcHeightStore(ctx) btcHeightBytes := store.Get(sdk.Uint64ToBigEndian(babylonHeight)) if len(btcHeightBytes) == 0 { @@ -27,8 +29,8 @@ func (k Keeper) GetBTCHeightAtBabylonHeight(ctx sdk.Context, babylonHeight uint6 return sdk.BigEndianToUint64(btcHeightBytes), nil } -func (k Keeper) GetCurrentBTCHeight(ctx sdk.Context) (uint64, error) { - babylonHeight := uint64(ctx.BlockHeight()) +func (k Keeper) GetCurrentBTCHeight(ctx context.Context) (uint64, error) { + babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) return k.GetBTCHeightAtBabylonHeight(ctx, babylonHeight) } @@ -36,7 +38,7 @@ func (k Keeper) GetCurrentBTCHeight(ctx sdk.Context) (uint64, error) { // prefix: BTCHeightKey // key: Babylon block height // value: BTC block height -func (k Keeper) btcHeightStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BTCHeightKey) +func (k Keeper) btcHeightStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.BTCHeightKey) } diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 5efc1fab2..f70923473 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -1,28 +1,30 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" ) // SetBTCValidator adds the given BTC validator to KVStore -func (k Keeper) SetBTCValidator(ctx sdk.Context, btcVal *types.BTCValidator) { +func (k Keeper) SetBTCValidator(ctx context.Context, btcVal *types.BTCValidator) { store := k.btcValidatorStore(ctx) btcValBytes := k.cdc.MustMarshal(btcVal) store.Set(btcVal.BtcPk.MustMarshal(), btcValBytes) } // HasBTCValidator checks if the BTC validator exists -func (k Keeper) HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool { +func (k Keeper) HasBTCValidator(ctx context.Context, valBTCPK []byte) bool { store := k.btcValidatorStore(ctx) return store.Has(valBTCPK) } // GetBTCValidator gets the BTC validator with the given validator Bitcoin PK -func (k Keeper) GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*types.BTCValidator, error) { +func (k Keeper) GetBTCValidator(ctx context.Context, valBTCPK []byte) (*types.BTCValidator, error) { store := k.btcValidatorStore(ctx) if !k.HasBTCValidator(ctx, valBTCPK) { return nil, types.ErrBTCValNotFound @@ -35,7 +37,7 @@ func (k Keeper) GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*types.BTCVal // SlashBTCValidator slashes a BTC validator with the given PK // A slashed BTC validator will not have voting power -func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { +func (k Keeper) SlashBTCValidator(ctx context.Context, valBTCPK []byte) error { btcVal, err := k.GetBTCValidator(ctx, valBTCPK) if err != nil { return err @@ -43,7 +45,7 @@ func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { if btcVal.IsSlashed() { return types.ErrBTCValAlreadySlashed } - btcVal.SlashedBabylonHeight = uint64(ctx.BlockHeight()) + btcVal.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) btcTip := k.btclcKeeper.GetTipInfo(ctx) if btcTip == nil { panic(fmt.Errorf("failed to get current BTC tip")) @@ -57,7 +59,7 @@ func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error { // prefix: BTCValidatorKey // key: Bitcoin secp256k1 PK // value: BTCValidator object -func (k Keeper) btcValidatorStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BTCValidatorKey) +func (k Keeper) btcValidatorStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.BTCValidatorKey) } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index da3a98c82..fcc3ae25a 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + sdkmath "cosmossdk.io/math" "errors" "math/rand" "testing" @@ -185,7 +186,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random number of BTC validators numBTCVals := datagen.RandomInt(r, 5) + 1 @@ -291,7 +292,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random number of BTC validators numBTCVals := datagen.RandomInt(r, 5) + 1 @@ -467,7 +468,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a random batch of validators var btcVals []*types.BTCValidator @@ -583,7 +584,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // Generate a btc validator btcVal, err := datagen.GenRandomBTCValidator(r) diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go index 7864e7d44..e69a55a3c 100644 --- a/x/btcstaking/keeper/incentive.go +++ b/x/btcstaking/keeper/incentive.go @@ -1,14 +1,16 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) RecordRewardDistCache(ctx sdk.Context) { +func (k Keeper) RecordRewardDistCache(ctx context.Context) { // get BTC tip height and w, which are necessary for determining a BTC // delegation's voting power btcTipHeight, err := k.GetCurrentBTCHeight(ctx) @@ -65,15 +67,15 @@ func (k Keeper) RecordRewardDistCache(ctx sdk.Context) { } // all good, set the reward distribution cache of the current height - k.setRewardDistCache(ctx, uint64(ctx.BlockHeight()), rdc) + k.setRewardDistCache(ctx, uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()), rdc) } -func (k Keeper) setRewardDistCache(ctx sdk.Context, height uint64, rdc *types.RewardDistCache) { +func (k Keeper) setRewardDistCache(ctx context.Context, height uint64, rdc *types.RewardDistCache) { store := k.rewardDistCacheStore(ctx) store.Set(sdk.Uint64ToBigEndian(height), k.cdc.MustMarshal(rdc)) } -func (k Keeper) GetRewardDistCache(ctx sdk.Context, height uint64) (*types.RewardDistCache, error) { +func (k Keeper) GetRewardDistCache(ctx context.Context, height uint64) (*types.RewardDistCache, error) { store := k.rewardDistCacheStore(ctx) rdcBytes := store.Get(sdk.Uint64ToBigEndian(height)) if len(rdcBytes) == 0 { @@ -84,7 +86,7 @@ func (k Keeper) GetRewardDistCache(ctx sdk.Context, height uint64) (*types.Rewar return &rdc, nil } -func (k Keeper) RemoveRewardDistCache(ctx sdk.Context, height uint64) { +func (k Keeper) RemoveRewardDistCache(ctx context.Context, height uint64) { store := k.rewardDistCacheStore(ctx) store.Delete(sdk.Uint64ToBigEndian(height)) } @@ -93,7 +95,7 @@ func (k Keeper) RemoveRewardDistCache(ctx sdk.Context, height uint64) { // prefix: RewardDistCacheKey // key: Babylon block height // value: RewardDistCache -func (k Keeper) rewardDistCacheStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.RewardDistCacheKey) +func (k Keeper) rewardDistCacheStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.RewardDistCacheKey) } diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 7ea89f0e4..595b9e4e3 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + sdkmath "cosmossdk.io/math" "math/rand" "testing" @@ -12,7 +13,6 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -43,7 +43,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 diff --git a/x/btcstaking/keeper/keeper.go b/x/btcstaking/keeper/keeper.go index 217755381..fc11217f0 100644 --- a/x/btcstaking/keeper/keeper.go +++ b/x/btcstaking/keeper/keeper.go @@ -1,21 +1,20 @@ package keeper import ( + corestoretypes "cosmossdk.io/core/store" "fmt" + "cosmossdk.io/log" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" - "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService btclcKeeper types.BTCLightClientKeeper btccKeeper types.BtcCheckpointKeeper @@ -29,8 +28,7 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper, @@ -39,9 +37,8 @@ func NewKeeper( authority string, ) Keeper { return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, + cdc: cdc, + storeService: storeService, btclcKeeper: btclcKeeper, btccKeeper: btccKeeper, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index de2bd86d5..bbae6cc4a 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "context" + sdkmath "cosmossdk.io/math" "errors" "math/rand" "testing" @@ -28,7 +29,7 @@ import ( func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { k, ctx := keepertest.BTCStakingKeeper(t, nil, nil) - return k, keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) + return k, keeper.NewMsgServerImpl(*k), ctx } func TestMsgServer(t *testing.T) { @@ -101,8 +102,8 @@ func getCovenantInfo(t *testing.T, CovenantQuorum: 1, SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, - MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), + SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) @@ -144,7 +145,7 @@ func createDelegation( validatorPK *btcec.PublicKey, covenantPK *btcec.PublicKey, slashingAddress, changeAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, stakingTime uint16, ) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) @@ -325,7 +326,7 @@ func createUndelegation( validatorPK *btcec.PublicKey, covenantPK *btcec.PublicKey, slashingAddress, changeAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, ) *types.MsgBTCUndelegate { stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) require.NoError(t, err) @@ -397,10 +398,9 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) ms := keeper.NewMsgServerImpl(*bsKeeper) - goCtx := sdk.WrapSDKContext(ctx) // set covenant PK to params - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) @@ -408,7 +408,7 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { /* generate and insert new BTC validator */ - _, validatorPK, _ := createValidator(t, r, goCtx, ms) + _, validatorPK, _ := createValidator(t, r, ctx, ms) /* generate and insert new BTC delegation @@ -416,7 +416,7 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { stakingTxHash, _, delPK, msgCreateBTCDel := createDelegation( t, r, - goCtx, + ctx, ms, btccKeeper, btclcKeeper, @@ -437,7 +437,7 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { /* generate and insert new covenant signature */ - createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) }) } @@ -453,7 +453,6 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) ms := keeper.NewMsgServerImpl(*bsKeeper) - goCtx := sdk.WrapSDKContext(ctx) // set covenant PK to params _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) @@ -467,8 +466,8 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { CovenantQuorum: 1, SlashingAddress: slashingAddress.String(), MinSlashingTxFeeSat: 10, - MinCommissionRate: sdk.MustNewDecFromStr("0.01"), - SlashingRate: sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), + SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveBtcValidators: 100, }) require.NoError(t, err) @@ -546,7 +545,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { SlashingTx: testStakingInfo.SlashingTx, DelegatorSig: delegatorSig, } - _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) + _, err = ms.CreateBTCDelegation(ctx, msgCreateBTCDel) require.Error(t, err) require.True(t, errors.Is(err, types.ErrBTCValNotFound)) } @@ -565,16 +564,15 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) ms := keeper.NewMsgServerImpl(*bsKeeper) - goCtx := sdk.WrapSDKContext(ctx) - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - _, validatorPK, _ := createValidator(t, r, goCtx, ms) + _, validatorPK, _ := createValidator(t, r, ctx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, r, - goCtx, + ctx, ms, btccKeeper, btclcKeeper, @@ -586,12 +584,12 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) undelegateMsg := createUndelegation( t, r, - goCtx, + ctx, ms, net, btclcKeeper, @@ -630,16 +628,15 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) ms := keeper.NewMsgServerImpl(*bsKeeper) - goCtx := sdk.WrapSDKContext(ctx) - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, goCtx, ms, net, bsKeeper, ctx) + covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - _, validatorPK, _ := createValidator(t, r, goCtx, ms) + _, validatorPK, _ := createValidator(t, r, ctx, ms) stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( t, r, - goCtx, + ctx, ms, btccKeeper, btclcKeeper, @@ -651,12 +648,12 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { 1000, ) actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createCovenantSig(t, r, goCtx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) undelegateMsg := createUndelegation( t, r, - goCtx, + ctx, ms, net, btclcKeeper, @@ -741,7 +738,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { } btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.AddCovenantUnbondingSigs(goCtx, &covenantSigsMsg) + _, err = ms.AddCovenantUnbondingSigs(ctx, &covenantSigsMsg) require.NoError(t, err) delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index bd119d8d0..bf50bf807 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -1,26 +1,28 @@ package keeper import ( + "context" "cosmossdk.io/math" "github.com/babylonchain/babylon/x/btcstaking/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // SetParams sets the x/btcstaking module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) - return nil + return store.Set(types.ParamsKey, bz) } // GetParams returns the current x/btcstaking module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } @@ -29,6 +31,6 @@ func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { } // MinCommissionRate returns the minimal commission rate of BTC validators -func (k Keeper) MinCommissionRate(ctx sdk.Context) math.LegacyDec { +func (k Keeper) MinCommissionRate(ctx context.Context) math.LegacyDec { return k.GetParams(ctx).MinCommissionRate } diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go index ed94316a2..b4c07587b 100644 --- a/x/btcstaking/keeper/query_params_test.go +++ b/x/btcstaking/keeper/query_params_test.go @@ -5,19 +5,17 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) - wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) require.NoError(t, err) - response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + response, err := keeper.Params(ctx, &types.QueryParamsRequest{}) require.NoError(t, err) require.Equal(t, &types.QueryParamsResponse{Params: params}, response) } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 62defef79..9eaf340f2 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -1,20 +1,22 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) // RecordVotingPowerTable computes the voting power table at the current block height // and saves the power table to KVStore // triggered upon each EndBlock -func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { +func (k Keeper) RecordVotingPowerTable(ctx context.Context) { // tip of Babylon and Bitcoin - babylonTipHeight := uint64(ctx.BlockHeight()) + babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) btcTipHeight, err := k.GetCurrentBTCHeight(ctx) if err != nil { return @@ -85,13 +87,13 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) { } // SetVotingPower sets the voting power of a given BTC validator at a given Babylon height -func (k Keeper) SetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64, power uint64) { +func (k Keeper) SetVotingPower(ctx context.Context, valBTCPK []byte, height uint64, power uint64) { store := k.votingPowerStore(ctx, height) store.Set(valBTCPK, sdk.Uint64ToBigEndian(power)) } // GetVotingPower gets the voting power of a given BTC validator at a given Babylon height -func (k Keeper) GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 { +func (k Keeper) GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 { if !k.HasBTCValidator(ctx, valBTCPK) { return 0 } @@ -104,7 +106,7 @@ func (k Keeper) GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) } // HasVotingPowerTable checks if the voting power table exists at a given height -func (k Keeper) HasVotingPowerTable(ctx sdk.Context, height uint64) bool { +func (k Keeper) HasVotingPowerTable(ctx context.Context, height uint64) bool { store := k.votingPowerStore(ctx, height) iter := store.Iterator(nil, nil) defer iter.Close() @@ -112,7 +114,7 @@ func (k Keeper) HasVotingPowerTable(ctx sdk.Context, height uint64) bool { } // GetVotingPowerTable gets the voting power table, i.e., validator set at a given height -func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 { +func (k Keeper) GetVotingPowerTable(ctx context.Context, height uint64) map[string]uint64 { store := k.votingPowerStore(ctx, height) iter := store.Iterator(nil, nil) defer iter.Close() @@ -139,9 +141,9 @@ func (k Keeper) GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]u // GetBTCStakingActivatedHeight returns the height when the BTC staking protocol is activated // i.e., the first height where a BTC validator has voting power // Before the BTC staking protocol is activated, we don't index or tally any block -func (k Keeper) GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) { - store := ctx.KVStore(k.storeKey) - votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) +func (k Keeper) GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + votingPowerStore := prefix.NewStore(storeAdapter, types.VotingPowerKey) iter := votingPowerStore.Iterator(nil, nil) defer iter.Close() // if the iterator is valid, then there exists a height that has a BTC validator with voting power @@ -152,9 +154,9 @@ func (k Keeper) GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) { } } -func (k Keeper) IsBTCStakingActivated(ctx sdk.Context) bool { - store := ctx.KVStore(k.storeKey) - votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) +func (k Keeper) IsBTCStakingActivated(ctx context.Context) bool { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + votingPowerStore := prefix.NewStore(storeAdapter, types.VotingPowerKey) iter := votingPowerStore.Iterator(nil, nil) defer iter.Close() // if the iterator is valid, then BTC staking is already activated @@ -165,8 +167,8 @@ func (k Keeper) IsBTCStakingActivated(ctx sdk.Context) bool { // prefix: (VotingPowerKey || Babylon block height) // key: Bitcoin secp256k1 PK // value: voting power quantified in Satoshi -func (k Keeper) votingPowerStore(ctx sdk.Context, height uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - votingPowerStore := prefix.NewStore(store, types.VotingPowerKey) +func (k Keeper) votingPowerStore(ctx context.Context, height uint64) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + votingPowerStore := prefix.NewStore(storeAdapter, types.VotingPowerKey) return prefix.NewStore(votingPowerStore, sdk.Uint64ToBigEndian(height)) } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 1f768361c..966a0a0c0 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + sdkmath "cosmossdk.io/math" "math/rand" "testing" @@ -12,7 +13,6 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -43,7 +43,7 @@ func FuzzVotingPowerTable(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators btcVals := []*types.BTCValidator{} @@ -107,7 +107,7 @@ func FuzzVotingPowerTable(f *testing.F) { keeper.RecordVotingPowerTable(ctx) for i := uint64(0); i < numBTCValsWithVotingPower; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) - require.Equal(t, uint64(numBTCDels)*stakingValue, power) + require.Equal(t, numBTCDels*stakingValue, power) } for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) @@ -150,7 +150,7 @@ func FuzzVotingPowerTable(f *testing.F) { if i == slashedIdx { require.Zero(t, power) } else { - require.Equal(t, uint64(numBTCDels)*stakingValue, power) + require.Equal(t, numBTCDels*stakingValue, power) } } for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { @@ -215,7 +215,7 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators, each with a BTC delegation with random power btcValsWithMeta := []*types.BTCValidatorWithMeta{} @@ -319,7 +319,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // generate a random batch of validators, each with a BTC delegation with random power btcValsWithMeta := []*types.BTCValidatorWithMeta{} diff --git a/x/btcstaking/module.go b/x/btcstaking/module.go index 1d263739a..a415eee5a 100644 --- a/x/btcstaking/module.go +++ b/x/btcstaking/module.go @@ -2,6 +2,7 @@ package btcstaking import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -21,8 +22,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -90,22 +93,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -119,14 +116,12 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the module's genesis initialization. It returns no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. @@ -138,10 +133,18 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { return EndBlocker(ctx, am.keeper) } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker +} diff --git a/x/btcstaking/module_simulation.go b/x/btcstaking/module_simulation.go index b13d1bdf3..6a816bd75 100644 --- a/x/btcstaking/module_simulation.go +++ b/x/btcstaking/module_simulation.go @@ -7,7 +7,6 @@ import ( btcstakingsimulation "github.com/babylonchain/babylon/x/btcstaking/simulation" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -22,8 +21,6 @@ var ( _ = rand.Rand{} ) -const () - // GenerateGenesisState creates a randomized GenState of the module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { accs := make([]string, len(simState.Accounts)) @@ -36,8 +33,9 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&btcstakingGenesis) } -// RegisterStoreDecoder registers a decoder. -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +// RegisterStoreDecoder registers a decoder +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { +} // ProposalContents doesn't return any content functions for governance proposals func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index d8b90c4e8..98e298e24 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -2,6 +2,7 @@ package types import ( "bytes" + sdkmath "cosmossdk.io/math" "encoding/hex" "github.com/babylonchain/babylon/btcstaking" @@ -10,7 +11,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" ) type BTCSlashingTx []byte @@ -86,7 +86,7 @@ func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { func (tx *BTCSlashingTx) Validate( net *chaincfg.Params, slashingAddress string, - slashingRate sdk.Dec, + slashingRate sdkmath.LegacyDec, slashingTxMinFee, stakingOutputValue int64, ) error { msgTx, err := tx.ToMsgTx() diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index c26ce5b61..f280a47a7 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -1,6 +1,7 @@ package types_test import ( + sdkmath "cosmossdk.io/math" "math/rand" "testing" @@ -10,7 +11,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -35,7 +35,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 1e229d75e..c3ab1dae6 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -4,11 +4,11 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/cosmos-proto" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -83,7 +83,7 @@ type BTCValidator struct { // description defines the description terms for the BTC validator. Description *types.Description `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` // commission defines the commission rate of BTC validator. - Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` + Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` // babylon_pk is the Babylon secp256k1 PK of this BTC validator BabylonPk *secp256k1.PubKey `protobuf:"bytes,3,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // btc_pk is the Bitcoin secp256k1 PK of this BTC validator @@ -660,73 +660,73 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1043 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdf, 0x6e, 0x1a, 0xc7, - 0x17, 0xf6, 0xb2, 0x18, 0x9b, 0x03, 0xfc, 0x4c, 0x26, 0x8e, 0x7f, 0xd4, 0x51, 0x81, 0xd2, 0xd4, - 0x42, 0x55, 0x03, 0xb5, 0xf3, 0x47, 0x69, 0x2f, 0x2a, 0x05, 0xe3, 0x36, 0x28, 0x89, 0x4d, 0x17, - 0x9c, 0xfe, 0x51, 0xdb, 0xd5, 0xec, 0xee, 0x78, 0x59, 0x01, 0x3b, 0x2b, 0x66, 0xa0, 0xf8, 0xbe, - 0x0f, 0xd0, 0x87, 0xe8, 0x4d, 0x7b, 0xdd, 0x87, 0xe8, 0x65, 0xd4, 0xab, 0xca, 0x17, 0x28, 0xb2, - 0x5f, 0xa4, 0xda, 0xd9, 0x59, 0x76, 0xed, 0xda, 0x6d, 0x52, 0xdc, 0x2b, 0x33, 0x73, 0xce, 0xf9, - 0xce, 0x77, 0xbe, 0xef, 0x78, 0x00, 0xb6, 0x0c, 0x6c, 0x1c, 0x0f, 0xa8, 0x5b, 0x37, 0xb8, 0xc9, - 0x38, 0xee, 0x3b, 0xae, 0x5d, 0x9f, 0x6c, 0xc7, 0x4e, 0x35, 0x6f, 0x44, 0x39, 0x45, 0xb7, 0x64, - 0x5e, 0x2d, 0x16, 0x99, 0x6c, 0x6f, 0xae, 0xdb, 0xd4, 0xa6, 0x22, 0xa3, 0xee, 0x7f, 0x0a, 0x92, - 0x37, 0xdf, 0x32, 0x29, 0x1b, 0x52, 0xa6, 0x07, 0x81, 0xe0, 0x20, 0x43, 0x95, 0xe0, 0x54, 0x37, - 0x47, 0xc7, 0x1e, 0xa7, 0x75, 0x46, 0x4c, 0x6f, 0xe7, 0xc1, 0xc3, 0xfe, 0x76, 0xbd, 0x4f, 0x8e, - 0xc3, 0x9c, 0x3b, 0x32, 0x27, 0xe2, 0x63, 0x10, 0x8e, 0xb7, 0xeb, 0xe7, 0x18, 0x6d, 0x96, 0x2e, - 0x67, 0xee, 0x51, 0x2f, 0x48, 0xa8, 0xcc, 0x54, 0xc8, 0x36, 0xba, 0xbb, 0x2f, 0xf0, 0xc0, 0xb1, - 0x30, 0xa7, 0x23, 0xb4, 0x07, 0x19, 0x8b, 0x30, 0x73, 0xe4, 0x78, 0xdc, 0xa1, 0x6e, 0x41, 0x29, - 0x2b, 0xd5, 0xcc, 0xce, 0xbb, 0x35, 0xc9, 0x2f, 0x9a, 0x4a, 0x74, 0xab, 0x35, 0xa3, 0x54, 0x2d, - 0x5e, 0x87, 0xbe, 0x04, 0x30, 0xe9, 0x70, 0xe8, 0x30, 0xe6, 0xa3, 0x24, 0xca, 0x4a, 0x35, 0xdd, - 0x78, 0x74, 0x32, 0x2b, 0x6d, 0xd9, 0x0e, 0xef, 0x8d, 0x8d, 0x9a, 0x49, 0x87, 0xf5, 0x70, 0x4a, - 0xf1, 0xe7, 0x2e, 0xb3, 0xfa, 0x75, 0x7e, 0xec, 0x11, 0x56, 0x6b, 0x12, 0xf3, 0xf7, 0x5f, 0xef, - 0x82, 0x6c, 0xd9, 0x24, 0xa6, 0x16, 0xc3, 0x42, 0x9f, 0x00, 0xc8, 0xa1, 0x74, 0xaf, 0x5f, 0x50, - 0x05, 0xbf, 0x52, 0xc8, 0x2f, 0x50, 0xac, 0x36, 0x57, 0xac, 0xd6, 0x1e, 0x1b, 0x4f, 0xc9, 0xb1, - 0x96, 0x96, 0x25, 0xed, 0x3e, 0x7a, 0x0e, 0x29, 0x83, 0x9b, 0x7e, 0x6d, 0xb2, 0xac, 0x54, 0xb3, - 0x8d, 0x87, 0x27, 0xb3, 0xd2, 0x4e, 0x8c, 0x95, 0xcc, 0x34, 0x7b, 0xd8, 0x71, 0xc3, 0x83, 0x24, - 0xd6, 0x68, 0xb5, 0xef, 0xdd, 0xff, 0x50, 0x42, 0x2e, 0x1b, 0xdc, 0x6c, 0xf7, 0xd1, 0xc7, 0xa0, - 0x7a, 0xd4, 0x2b, 0x2c, 0x0b, 0x1e, 0xd5, 0xda, 0xa5, 0x1b, 0x50, 0x6b, 0x8f, 0x28, 0x3d, 0x3a, - 0x38, 0x6a, 0x53, 0xc6, 0x88, 0x98, 0x42, 0xf3, 0x8b, 0xd0, 0x7d, 0xd8, 0x60, 0x03, 0xcc, 0x7a, - 0xc4, 0xd2, 0xc3, 0x91, 0x7a, 0xc4, 0xb1, 0x7b, 0xbc, 0x90, 0x2a, 0x2b, 0xd5, 0xa4, 0xb6, 0x2e, - 0xa3, 0x8d, 0x20, 0xf8, 0x44, 0xc4, 0xd0, 0x07, 0x80, 0xe6, 0x55, 0xdc, 0x0c, 0x2b, 0x56, 0x44, - 0x45, 0x3e, 0xac, 0xe0, 0x66, 0x90, 0x5d, 0xf9, 0x21, 0x01, 0xeb, 0x71, 0x83, 0xbf, 0x70, 0x78, - 0xef, 0x39, 0xe1, 0x38, 0xa6, 0x83, 0x72, 0x1d, 0x3a, 0x6c, 0x40, 0x4a, 0x32, 0x49, 0x08, 0x26, - 0xf2, 0x84, 0xde, 0x81, 0xec, 0x84, 0x72, 0xc7, 0xb5, 0x75, 0x8f, 0x7e, 0x4f, 0x46, 0xc2, 0xb0, - 0xa4, 0x96, 0x09, 0xee, 0xda, 0xfe, 0xd5, 0xdf, 0xc8, 0x90, 0x7c, 0x63, 0x19, 0x96, 0xaf, 0x90, - 0xe1, 0x97, 0x14, 0xe4, 0x1a, 0xdd, 0xdd, 0x26, 0x19, 0x10, 0x1b, 0xf3, 0xbf, 0xee, 0x91, 0xb2, - 0xc0, 0x1e, 0x25, 0xae, 0x71, 0x8f, 0xd4, 0x7f, 0xb3, 0x47, 0xdf, 0xc2, 0xda, 0x04, 0x0f, 0xf4, - 0x80, 0x8e, 0x3e, 0x70, 0x98, 0xaf, 0x9c, 0xba, 0x00, 0xa7, 0xec, 0x04, 0x0f, 0x1a, 0x3e, 0xad, - 0x67, 0x0e, 0x13, 0x16, 0x32, 0x8e, 0x47, 0xfc, 0xbc, 0xc6, 0x19, 0x71, 0x27, 0xcd, 0x78, 0x1b, - 0x80, 0xb8, 0xd6, 0xf9, 0xed, 0x4d, 0x13, 0xd7, 0x92, 0xe1, 0xdb, 0x90, 0xe6, 0x94, 0xe3, 0x81, - 0xce, 0x70, 0xb8, 0xa9, 0xab, 0xe2, 0xa2, 0x83, 0x45, 0xad, 0x1c, 0x51, 0xe7, 0xd3, 0xc2, 0xaa, - 0x2f, 0xa6, 0x96, 0x96, 0x37, 0xdd, 0xa9, 0xf0, 0x59, 0x86, 0xe9, 0x98, 0x7b, 0x63, 0xae, 0x3b, - 0xd6, 0xb4, 0x90, 0x2e, 0x2b, 0xd5, 0x9c, 0x96, 0x97, 0x91, 0x03, 0x11, 0x68, 0x59, 0x53, 0xb4, - 0x03, 0x19, 0xe1, 0xbd, 0x44, 0x03, 0x61, 0xcd, 0x8d, 0x93, 0x59, 0xc9, 0x77, 0xbf, 0x23, 0x23, - 0xdd, 0xa9, 0x06, 0x6c, 0xfe, 0x19, 0x7d, 0x07, 0x39, 0x2b, 0xd8, 0x0b, 0x3a, 0xd2, 0x99, 0x63, - 0x17, 0x32, 0xa2, 0xea, 0xa3, 0x93, 0x59, 0xe9, 0xc1, 0x9b, 0x88, 0xd7, 0x71, 0x6c, 0x17, 0xf3, - 0xf1, 0x88, 0x68, 0xd9, 0x39, 0x5e, 0xc7, 0xb1, 0xd1, 0x37, 0x90, 0x35, 0xe9, 0x84, 0xb8, 0xd8, - 0xe5, 0x02, 0x3e, 0xbb, 0x28, 0x7c, 0x26, 0x84, 0xf3, 0xd1, 0x3f, 0x87, 0xbc, 0x6f, 0xfc, 0xd8, - 0xb5, 0xe6, 0xbb, 0x5d, 0xc8, 0x89, 0x2d, 0xda, 0xba, 0x62, 0x8b, 0x1a, 0xdd, 0xdd, 0xc3, 0x58, - 0xb6, 0xb6, 0x66, 0x70, 0x33, 0x7e, 0x51, 0x79, 0xa5, 0xc2, 0xda, 0x85, 0x24, 0x7f, 0x09, 0xc6, - 0xae, 0x41, 0x5d, 0x4b, 0x2a, 0x2b, 0x1e, 0x0d, 0x2d, 0x33, 0xbf, 0xeb, 0x4e, 0xd1, 0x7b, 0xf0, - 0xbf, 0x58, 0x8a, 0x33, 0x24, 0xe2, 0x3f, 0x23, 0xa7, 0xe5, 0xa2, 0x24, 0x67, 0x48, 0x2e, 0x5a, - 0xa4, 0xbe, 0x8e, 0x45, 0x14, 0x36, 0x62, 0x16, 0x85, 0xd5, 0xbe, 0x98, 0xc9, 0x45, 0xc5, 0x5c, - 0x8f, 0xbc, 0x92, 0xb8, 0xbe, 0xaa, 0x43, 0xb8, 0x15, 0x79, 0x16, 0xef, 0xb7, 0xbc, 0x68, 0xbf, - 0x9b, 0x73, 0xf3, 0x62, 0xed, 0x28, 0x6c, 0xcc, 0xdb, 0x45, 0x1a, 0xfa, 0xfd, 0x52, 0x0b, 0xcf, - 0x17, 0x02, 0x1f, 0x86, 0xb8, 0x1d, 0xc7, 0xae, 0xfc, 0xac, 0xc0, 0xcd, 0x0b, 0x16, 0xb7, 0xdc, - 0x23, 0xfa, 0x3a, 0x36, 0x5f, 0xcd, 0x35, 0xf1, 0xdf, 0x70, 0xed, 0xc0, 0xff, 0xa3, 0xa7, 0x9b, - 0x8e, 0xa2, 0x37, 0x9c, 0xa1, 0x47, 0x90, 0xb4, 0xc8, 0x80, 0x15, 0x94, 0xb2, 0x5a, 0xcd, 0xec, - 0xdc, 0xb9, 0x7a, 0xe1, 0xa3, 0x22, 0x4d, 0x54, 0x54, 0xf6, 0xe1, 0xf6, 0xe5, 0xa0, 0x2d, 0xd7, - 0x22, 0x53, 0x54, 0x87, 0xf5, 0xe8, 0x51, 0xd2, 0x7b, 0x98, 0xf5, 0x82, 0x77, 0xd5, 0x6f, 0x94, - 0xd5, 0x6e, 0xcc, 0x9f, 0xa7, 0x27, 0x98, 0xf5, 0xfc, 0x47, 0xb2, 0xf2, 0x93, 0x02, 0xb9, 0xf9, - 0x20, 0x42, 0xca, 0x4f, 0x21, 0xb1, 0xf0, 0x97, 0x6b, 0xc2, 0xeb, 0xa3, 0xa7, 0xa0, 0x5e, 0x8b, - 0xb8, 0x3e, 0xca, 0xfb, 0x5d, 0x61, 0x7b, 0x34, 0x6d, 0x87, 0x63, 0x3e, 0x66, 0x28, 0x03, 0x2b, - 0xed, 0xbd, 0xfd, 0x66, 0x6b, 0xff, 0xb3, 0xfc, 0x12, 0x02, 0x48, 0x3d, 0xde, 0xed, 0xb6, 0x5e, - 0xec, 0xe5, 0x15, 0x94, 0x83, 0xf4, 0xe1, 0x7e, 0xe3, 0x20, 0x08, 0x25, 0x50, 0x16, 0x56, 0x83, - 0xe3, 0x5e, 0x33, 0xaf, 0xa2, 0x15, 0x50, 0x1f, 0xef, 0x7f, 0x95, 0x4f, 0x36, 0x9e, 0xfd, 0x76, - 0x5a, 0x54, 0x5e, 0x9e, 0x16, 0x95, 0x57, 0xa7, 0x45, 0xe5, 0xc7, 0xb3, 0xe2, 0xd2, 0xcb, 0xb3, - 0xe2, 0xd2, 0x1f, 0x67, 0xc5, 0xa5, 0xaf, 0xff, 0x71, 0xe8, 0x69, 0xfc, 0xa7, 0xa9, 0x20, 0x6e, - 0xa4, 0xc4, 0x4f, 0xd3, 0x7b, 0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0x01, 0xc5, 0x51, 0x3c, 0x77, - 0x0b, 0x00, 0x00, + // 1046 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdd, 0x6e, 0x1b, 0x45, + 0x14, 0xce, 0xda, 0x8e, 0x5b, 0x1f, 0xdb, 0xc4, 0x9d, 0xa6, 0xc1, 0x24, 0xc2, 0x36, 0xa6, 0x54, + 0x16, 0xa2, 0x6b, 0x92, 0xfe, 0x08, 0xb8, 0x40, 0xaa, 0xe3, 0x40, 0xad, 0xe6, 0xc7, 0xac, 0x9d, + 0x22, 0x10, 0xb0, 0x9a, 0xdd, 0x9d, 0xac, 0x57, 0xb6, 0x77, 0x56, 0x9e, 0xb1, 0xb1, 0xef, 0x79, + 0x00, 0x1e, 0x82, 0x1b, 0xb8, 0xe6, 0x21, 0xb8, 0xac, 0xe0, 0x06, 0xe5, 0x22, 0xaa, 0x92, 0x17, + 0x41, 0x3b, 0x3b, 0xeb, 0xdd, 0x84, 0x04, 0x5a, 0x1c, 0xee, 0x3c, 0x73, 0xce, 0xf9, 0xce, 0x77, + 0xbe, 0xef, 0x64, 0xb2, 0x70, 0xcf, 0xc0, 0xc6, 0x6c, 0x40, 0xdd, 0xba, 0xc1, 0x4d, 0xc6, 0x71, + 0xdf, 0x71, 0xed, 0xfa, 0x64, 0x33, 0x76, 0x52, 0xbd, 0x11, 0xe5, 0x14, 0xdd, 0x91, 0x79, 0x6a, + 0x2c, 0x32, 0xd9, 0x5c, 0x5f, 0xb5, 0xa9, 0x4d, 0x45, 0x46, 0xdd, 0xff, 0x15, 0x24, 0xaf, 0xbf, + 0x65, 0x52, 0x36, 0xa4, 0x4c, 0x0f, 0x02, 0xc1, 0x41, 0x86, 0xaa, 0xc1, 0xa9, 0x6e, 0x8e, 0x66, + 0x1e, 0xa7, 0x75, 0x46, 0x4c, 0x6f, 0xeb, 0xd1, 0xe3, 0xfe, 0x66, 0xbd, 0x4f, 0x66, 0x61, 0xce, + 0x5d, 0x99, 0x13, 0xf1, 0x31, 0x08, 0xc7, 0x9b, 0xf5, 0x73, 0x8c, 0xd6, 0xcb, 0x97, 0x33, 0xf7, + 0xa8, 0x17, 0x24, 0x54, 0xff, 0x48, 0x42, 0xae, 0xd1, 0xdd, 0x7e, 0x8e, 0x07, 0x8e, 0x85, 0x39, + 0x1d, 0xa1, 0x1d, 0xc8, 0x5a, 0x84, 0x99, 0x23, 0xc7, 0xe3, 0x0e, 0x75, 0x8b, 0x4a, 0x45, 0xa9, + 0x65, 0xb7, 0xde, 0x55, 0x25, 0xbf, 0x68, 0x2a, 0xd1, 0x4d, 0x6d, 0x46, 0xa9, 0x5a, 0xbc, 0x0e, + 0xed, 0x01, 0x98, 0x74, 0x38, 0x74, 0x18, 0xf3, 0x51, 0x12, 0x15, 0xa5, 0x96, 0x69, 0xdc, 0x3f, + 0x3e, 0x29, 0x6f, 0x04, 0x40, 0xcc, 0xea, 0xab, 0x0e, 0xad, 0x0f, 0x31, 0xef, 0xa9, 0xbb, 0xc4, + 0xc6, 0xe6, 0xac, 0x49, 0xcc, 0xdf, 0x7f, 0xbd, 0x0f, 0xb2, 0x4f, 0x93, 0x98, 0x5a, 0x0c, 0x00, + 0x7d, 0x0a, 0x20, 0x27, 0xd1, 0xbd, 0x7e, 0x31, 0x29, 0x48, 0x95, 0x43, 0x52, 0x81, 0x4c, 0xea, + 0x5c, 0x26, 0xb5, 0x3d, 0x36, 0x9e, 0x91, 0x99, 0x96, 0x91, 0x25, 0xed, 0x3e, 0xda, 0x83, 0xb4, + 0xc1, 0x4d, 0xbf, 0x36, 0x55, 0x51, 0x6a, 0xb9, 0xc6, 0xe3, 0xe3, 0x93, 0xf2, 0x96, 0xed, 0xf0, + 0xde, 0xd8, 0x50, 0x4d, 0x3a, 0xac, 0xcb, 0x4c, 0xb3, 0x87, 0x1d, 0x37, 0x3c, 0xd4, 0xf9, 0xcc, + 0x23, 0x4c, 0x6d, 0xb4, 0xda, 0x0f, 0x1e, 0x7e, 0x28, 0x21, 0x97, 0x0d, 0x6e, 0xb6, 0xfb, 0xe8, + 0x13, 0x48, 0x7a, 0xd4, 0x2b, 0x2e, 0x0b, 0x1e, 0x35, 0xf5, 0x52, 0xdb, 0xd5, 0xf6, 0x88, 0xd2, + 0xa3, 0x83, 0xa3, 0x36, 0x65, 0x8c, 0x88, 0x29, 0x34, 0xbf, 0x08, 0x3d, 0x84, 0x35, 0x36, 0xc0, + 0xac, 0x47, 0x2c, 0x3d, 0x1c, 0xa9, 0x47, 0x1c, 0xbb, 0xc7, 0x8b, 0xe9, 0x8a, 0x52, 0x4b, 0x69, + 0xab, 0x32, 0xda, 0x08, 0x82, 0x4f, 0x45, 0x0c, 0x7d, 0x00, 0x68, 0x5e, 0xc5, 0xcd, 0xb0, 0xe2, + 0x86, 0xa8, 0x28, 0x84, 0x15, 0xdc, 0x0c, 0xb2, 0xab, 0x3f, 0x24, 0x60, 0x35, 0xee, 0xea, 0x97, + 0x0e, 0xef, 0xed, 0x11, 0x8e, 0x63, 0x3a, 0x28, 0xd7, 0xa1, 0xc3, 0x1a, 0xa4, 0x25, 0x93, 0x84, + 0x60, 0x22, 0x4f, 0xe8, 0x1d, 0xc8, 0x4d, 0x28, 0x77, 0x5c, 0x5b, 0xf7, 0xe8, 0xf7, 0x64, 0x24, + 0x0c, 0x4b, 0x69, 0xd9, 0xe0, 0xae, 0xed, 0x5f, 0xfd, 0x83, 0x0c, 0xa9, 0xd7, 0x96, 0x61, 0xf9, + 0x0a, 0x19, 0x7e, 0x49, 0x43, 0xbe, 0xd1, 0xdd, 0x6e, 0x92, 0x01, 0xb1, 0x31, 0xff, 0xfb, 0x1e, + 0x29, 0x0b, 0xec, 0x51, 0xe2, 0x1a, 0xf7, 0x28, 0xf9, 0x5f, 0xf6, 0xe8, 0x5b, 0x58, 0x99, 0xe0, + 0x81, 0x1e, 0xd0, 0xd1, 0x07, 0x0e, 0xf3, 0x95, 0x4b, 0x2e, 0xc0, 0x29, 0x37, 0xc1, 0x83, 0x86, + 0x4f, 0x6b, 0xd7, 0x61, 0xc2, 0x42, 0xc6, 0xf1, 0x88, 0x9f, 0xd7, 0x38, 0x2b, 0xee, 0xa4, 0x19, + 0x6f, 0x03, 0x10, 0xd7, 0x3a, 0xbf, 0xbd, 0x19, 0xe2, 0x5a, 0x32, 0xbc, 0x01, 0x19, 0x4e, 0x39, + 0x1e, 0xe8, 0x0c, 0x87, 0x9b, 0x7a, 0x53, 0x5c, 0x74, 0xb0, 0xa8, 0x95, 0x23, 0xea, 0x7c, 0x5a, + 0xbc, 0xe9, 0x8b, 0xa9, 0x65, 0xe4, 0x4d, 0x77, 0x2a, 0x7c, 0x96, 0x61, 0x3a, 0xe6, 0xde, 0x98, + 0xeb, 0x8e, 0x35, 0x2d, 0x66, 0x2a, 0x4a, 0x2d, 0xaf, 0x15, 0x64, 0xe4, 0x40, 0x04, 0x5a, 0xd6, + 0x14, 0x6d, 0x41, 0x56, 0x78, 0x2f, 0xd1, 0x40, 0x58, 0x73, 0xeb, 0xf8, 0xa4, 0xec, 0xbb, 0xdf, + 0x91, 0x91, 0xee, 0x54, 0x03, 0x36, 0xff, 0x8d, 0xbe, 0x83, 0xbc, 0x15, 0xec, 0x05, 0x1d, 0xe9, + 0xcc, 0xb1, 0x8b, 0x59, 0x51, 0xf5, 0xf1, 0xf1, 0x49, 0xf9, 0xd1, 0xeb, 0x88, 0xd7, 0x71, 0x6c, + 0x17, 0xf3, 0xf1, 0x88, 0x68, 0xb9, 0x39, 0x5e, 0xc7, 0xb1, 0xd1, 0x37, 0x90, 0x33, 0xe9, 0x84, + 0xb8, 0xd8, 0xe5, 0x02, 0x3e, 0xb7, 0x28, 0x7c, 0x36, 0x84, 0xf3, 0xd1, 0xbf, 0x80, 0x82, 0x6f, + 0xfc, 0xd8, 0xb5, 0xe6, 0xbb, 0x5d, 0xcc, 0x8b, 0x2d, 0xba, 0x77, 0xc5, 0x16, 0x35, 0xba, 0xdb, + 0x87, 0xb1, 0x6c, 0x6d, 0xc5, 0xe0, 0x66, 0xfc, 0xa2, 0xfa, 0x32, 0x09, 0x2b, 0x17, 0x92, 0xfc, + 0x25, 0x18, 0xbb, 0x06, 0x75, 0x2d, 0xa9, 0xac, 0x78, 0x34, 0xb4, 0xec, 0xfc, 0xae, 0x3b, 0x45, + 0xef, 0xc1, 0x1b, 0xb1, 0x14, 0x67, 0x48, 0xc4, 0x5f, 0x46, 0x5e, 0xcb, 0x47, 0x49, 0xce, 0x90, + 0x5c, 0xb4, 0x28, 0xf9, 0x2a, 0x16, 0x51, 0x58, 0x8b, 0x59, 0x14, 0x56, 0xfb, 0x62, 0xa6, 0x16, + 0x15, 0x73, 0x35, 0xf2, 0x4a, 0xe2, 0xfa, 0xaa, 0x0e, 0xe1, 0x4e, 0xe4, 0x59, 0xbc, 0xdf, 0xf2, + 0xa2, 0xfd, 0x6e, 0xcf, 0xcd, 0x8b, 0xb5, 0xa3, 0xb0, 0x36, 0x6f, 0x17, 0x69, 0xe8, 0xf7, 0x4b, + 0x2f, 0x3c, 0x5f, 0x08, 0x7c, 0x18, 0xe2, 0x76, 0x1c, 0xbb, 0xfa, 0xb3, 0x02, 0xb7, 0x2f, 0x58, + 0xdc, 0x72, 0x8f, 0xe8, 0xab, 0xd8, 0x7c, 0x35, 0xd7, 0xc4, 0xff, 0xc3, 0xb5, 0x03, 0x6f, 0x46, + 0x4f, 0x37, 0x1d, 0x45, 0x6f, 0x38, 0x43, 0x1f, 0x41, 0xca, 0x22, 0x03, 0x56, 0x54, 0x2a, 0xc9, + 0x5a, 0x76, 0xeb, 0xee, 0xd5, 0x0b, 0x1f, 0x15, 0x69, 0xa2, 0xa2, 0xba, 0x0f, 0x1b, 0x97, 0x83, + 0xb6, 0x5c, 0x8b, 0x4c, 0x51, 0x1d, 0x56, 0xa3, 0x47, 0x49, 0xef, 0x61, 0xd6, 0x0b, 0xde, 0x55, + 0xbf, 0x51, 0x4e, 0xbb, 0x35, 0x7f, 0x9e, 0x9e, 0x62, 0xd6, 0xf3, 0x1f, 0xc9, 0xea, 0x4f, 0x0a, + 0xe4, 0xe7, 0x83, 0x08, 0x29, 0x3f, 0x83, 0xc4, 0xc2, 0xff, 0x5c, 0x13, 0x5e, 0x1f, 0x3d, 0x83, + 0xe4, 0xb5, 0x88, 0xeb, 0xa3, 0xbc, 0xdf, 0x15, 0xb6, 0x47, 0xd3, 0x76, 0x38, 0xe6, 0x63, 0x86, + 0xb2, 0x70, 0xa3, 0xbd, 0xb3, 0xdf, 0x6c, 0xed, 0x7f, 0x5e, 0x58, 0x42, 0x00, 0xe9, 0x27, 0xdb, + 0xdd, 0xd6, 0xf3, 0x9d, 0x82, 0x82, 0xf2, 0x90, 0x39, 0xdc, 0x6f, 0x1c, 0x04, 0xa1, 0x04, 0xca, + 0xc1, 0xcd, 0xe0, 0xb8, 0xd3, 0x2c, 0x24, 0xd1, 0x0d, 0x48, 0x3e, 0xd9, 0xff, 0xaa, 0x90, 0x6a, + 0xec, 0xfe, 0x76, 0x5a, 0x52, 0x5e, 0x9c, 0x96, 0x94, 0x97, 0xa7, 0x25, 0xe5, 0xc7, 0xb3, 0xd2, + 0xd2, 0x8b, 0xb3, 0xd2, 0xd2, 0x9f, 0x67, 0xa5, 0xa5, 0xaf, 0xff, 0x75, 0xe8, 0x69, 0xfc, 0x7b, + 0x54, 0x10, 0x37, 0xd2, 0xe2, 0x7b, 0xf4, 0xc1, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf9, 0x12, + 0x09, 0xab, 0x6c, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1593,7 +1593,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_cosmos_cosmos_sdk_types.Dec + var v cosmossdk_io_math.LegacyDec m.Commission = &v if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 2ba17e1b1..a0ae8fee4 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -1,6 +1,7 @@ package types_test import ( + sdkmath "cosmossdk.io/math" "math/rand" "testing" @@ -9,7 +10,6 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -38,7 +38,7 @@ func FuzzStakingTx(f *testing.F) { // with value below the dust threshold, causing test failure. // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdk.NewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) testInfo := datagen.GenBTCStakingSlashingTx( r, t, diff --git a/x/btcstaking/types/expected_keepers.go b/x/btcstaking/types/expected_keepers.go index 3d8748b39..7d5a8a1ed 100644 --- a/x/btcstaking/types/expected_keepers.go +++ b/x/btcstaking/types/expected_keepers.go @@ -1,33 +1,20 @@ package types import ( + "context" "math/big" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here -} - type BTCLightClientKeeper interface { - GetTipInfo(ctx sdk.Context) *btclctypes.BTCHeaderInfo - GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo + GetTipInfo(ctx context.Context) *btclctypes.BTCHeaderInfo + GetHeaderByHash(ctx context.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo } type BtcCheckpointKeeper interface { GetPowLimit() *big.Int - GetParams(ctx sdk.Context) (p btcctypes.Params) + GetParams(ctx context.Context) (p btcctypes.Params) } diff --git a/x/btcstaking/types/genesis.go b/x/btcstaking/types/genesis.go index 97cad7616..ab1a56d77 100644 --- a/x/btcstaking/types/genesis.go +++ b/x/btcstaking/types/genesis.go @@ -1,8 +1,5 @@ package types -// DefaultIndex is the default global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ diff --git a/x/btcstaking/types/incentive.go b/x/btcstaking/types/incentive.go index 9764a6282..f6787e7a3 100644 --- a/x/btcstaking/types/incentive.go +++ b/x/btcstaking/types/incentive.go @@ -1,7 +1,7 @@ package types import ( - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -36,8 +36,8 @@ func (rdc *RewardDistCache) FilterVotedBTCVals(voterBTCPKs map[string]struct{}) } // GetBTCValPortion returns the portion of a BTC validator's voting power out of the total voting power -func (rdc *RewardDistCache) GetBTCValPortion(v *BTCValDistInfo) sdk.Dec { - return math.LegacyNewDec(int64(v.TotalVotingPower)).QuoTruncate(math.LegacyNewDec(int64(rdc.TotalVotingPower))) +func (rdc *RewardDistCache) GetBTCValPortion(v *BTCValDistInfo) sdkmath.LegacyDec { + return sdkmath.LegacyNewDec(int64(v.TotalVotingPower)).QuoTruncate(sdkmath.LegacyNewDec(int64(rdc.TotalVotingPower))) } func NewBTCValDistInfo(btcVal *BTCValidator) *BTCValDistInfo { @@ -67,10 +67,10 @@ func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wVal } } -// GetBTCValPortion returns the portion of a BTC delegation's voting power out of +// GetBTCDelPortion returns the portion of a BTC delegation's voting power out of // the BTC validator's total voting power -func (v *BTCValDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdk.Dec { - return math.LegacyNewDec(int64(d.VotingPower)).QuoTruncate(math.LegacyNewDec(int64(v.TotalVotingPower))) +func (v *BTCValDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdkmath.LegacyDec { + return sdkmath.LegacyNewDec(int64(d.VotingPower)).QuoTruncate(sdkmath.LegacyNewDec(int64(v.TotalVotingPower))) } func (d *BTCDelDistInfo) GetAddress() sdk.AccAddress { diff --git a/x/btcstaking/types/incentive.pb.go b/x/btcstaking/types/incentive.pb.go index 4b1e578c2..7cda115fd 100644 --- a/x/btcstaking/types/incentive.pb.go +++ b/x/btcstaking/types/incentive.pb.go @@ -4,11 +4,11 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/cosmos-proto" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -89,7 +89,7 @@ type BTCValDistInfo struct { // babylon_pk is the Babylon public key of the BTC validator BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // commission defines the commission rate of BTC validator - Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` + Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` // total_voting_power is the total voting power of the BTC validator TotalVotingPower uint64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` // btc_dels is a list of BTC delegations' voting power information under this BTC validator @@ -216,37 +216,37 @@ func init() { } var fileDescriptor_ac354c3bd6d7a66b = []byte{ - // 467 bytes of a gzipped FileDescriptorProto + // 468 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0xeb, 0xee, 0x0f, 0xcc, 0x9d, 0x00, 0x45, 0x20, 0x95, 0x1d, 0xd2, 0x52, 0x69, 0xa8, - 0x07, 0x66, 0xd3, 0x0e, 0x26, 0x4e, 0x08, 0x65, 0xb9, 0x4c, 0x80, 0x14, 0x45, 0xa8, 0x42, 0x5c, - 0x2a, 0xdb, 0x35, 0xa9, 0x95, 0x34, 0x8e, 0x6a, 0x2f, 0x23, 0x47, 0xbe, 0x01, 0x1f, 0x86, 0x0f, - 0xc1, 0x71, 0xe2, 0x84, 0x76, 0x98, 0x50, 0x7b, 0xe2, 0x5b, 0xa0, 0x38, 0x1e, 0x4b, 0x11, 0x50, - 0x71, 0x4a, 0xde, 0x3c, 0xcf, 0xfb, 0xbe, 0x4f, 0x7e, 0x89, 0xe1, 0x3e, 0x25, 0xb4, 0x48, 0x64, - 0x8a, 0xa9, 0x66, 0x4a, 0x93, 0x58, 0xa4, 0x11, 0xce, 0x07, 0x58, 0xa4, 0x8c, 0xa7, 0x5a, 0xe4, - 0x1c, 0x65, 0x73, 0xa9, 0xa5, 0x73, 0xcf, 0xda, 0xd0, 0xb5, 0x0d, 0xe5, 0x83, 0xbd, 0xbb, 0x91, - 0x8c, 0xa4, 0x71, 0xe0, 0xf2, 0xae, 0x32, 0xef, 0xdd, 0x67, 0x52, 0xcd, 0xa4, 0x1a, 0x57, 0x42, - 0x55, 0x58, 0xa9, 0x57, 0x55, 0x98, 0xcd, 0x8b, 0x4c, 0x4b, 0xac, 0x38, 0xcb, 0x86, 0x4f, 0x8f, - 0xe2, 0x01, 0x8e, 0x79, 0x61, 0x3d, 0xbd, 0x8f, 0x00, 0xde, 0x0e, 0xf9, 0x19, 0x99, 0x4f, 0x7c, - 0xa1, 0xf4, 0x31, 0x61, 0x53, 0xee, 0x3c, 0x82, 0x8e, 0x96, 0x9a, 0x24, 0xe3, 0x5c, 0x6a, 0x91, - 0x46, 0xe3, 0x4c, 0x9e, 0xf1, 0x79, 0x1b, 0x74, 0x41, 0x7f, 0x33, 0xbc, 0x63, 0x94, 0x91, 0x11, - 0x82, 0xf2, 0xb9, 0xf3, 0x02, 0xde, 0xa4, 0x9a, 0x8d, 0x73, 0x92, 0xa8, 0x76, 0xb3, 0xbb, 0xd1, - 0x6f, 0x0d, 0xf7, 0xd1, 0x1f, 0x5f, 0x00, 0x79, 0x6f, 0x8e, 0x47, 0x24, 0x29, 0xf7, 0x9c, 0xa4, - 0xef, 0x65, 0x78, 0x83, 0x6a, 0x36, 0x22, 0x89, 0xea, 0xfd, 0x68, 0xc2, 0x5b, 0xab, 0x9a, 0xf3, - 0x1a, 0x6e, 0x97, 0x43, 0xb3, 0xd8, 0xac, 0xdd, 0xf5, 0x8e, 0x2e, 0x2e, 0x3b, 0xc3, 0x48, 0xe8, - 0xe9, 0x29, 0x45, 0x4c, 0xce, 0xb0, 0x5d, 0xc0, 0xa6, 0x44, 0xa4, 0x57, 0x05, 0xd6, 0x45, 0xc6, - 0x15, 0xf2, 0x4e, 0x82, 0xc3, 0x27, 0x8f, 0x83, 0x53, 0xfa, 0x92, 0x17, 0xe1, 0x16, 0xd5, 0x2c, - 0x88, 0x9d, 0xe7, 0x10, 0x5a, 0x53, 0x39, 0xb2, 0xd9, 0x05, 0xfd, 0xd6, 0xb0, 0x83, 0x2c, 0xac, - 0x0a, 0x0f, 0xfa, 0x85, 0x07, 0xd9, 0xde, 0x1d, 0xdb, 0x12, 0xc4, 0xce, 0x5b, 0x08, 0x99, 0x9c, - 0xcd, 0x84, 0x52, 0x42, 0xa6, 0xed, 0x8d, 0x2e, 0xe8, 0xef, 0x78, 0xcf, 0x2e, 0x2e, 0x3b, 0x0f, - 0x6b, 0x91, 0xae, 0x60, 0x9b, 0xcb, 0x81, 0x9a, 0xc4, 0x36, 0x8f, 0xcf, 0xd9, 0xd7, 0xcf, 0x07, - 0xd0, 0x2e, 0xf3, 0x39, 0x0b, 0x6b, 0xb3, 0xfe, 0xc2, 0x7a, 0xf3, 0xdf, 0xac, 0x27, 0x3c, 0x51, - 0xed, 0xad, 0x75, 0xac, 0x7d, 0xbe, 0xca, 0xda, 0xe7, 0x89, 0xea, 0x29, 0x83, 0xba, 0x26, 0xfd, - 0xc6, 0x06, 0xfc, 0x37, 0x9b, 0x07, 0x70, 0x77, 0x25, 0x7b, 0xd3, 0x64, 0x6f, 0xe5, 0xd7, 0xb1, - 0xbd, 0x57, 0x5f, 0x16, 0x2e, 0x38, 0x5f, 0xb8, 0xe0, 0xfb, 0xc2, 0x05, 0x9f, 0x96, 0x6e, 0xe3, - 0x7c, 0xe9, 0x36, 0xbe, 0x2d, 0xdd, 0xc6, 0xbb, 0xb5, 0xdf, 0xf4, 0x43, 0xfd, 0xac, 0x18, 0xa0, - 0x74, 0xdb, 0xfc, 0xb9, 0x87, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb4, 0x48, 0xf9, 0xd9, 0x4e, - 0x03, 0x00, 0x00, + 0x18, 0xc6, 0xeb, 0xee, 0x0f, 0xcc, 0x9d, 0x00, 0x45, 0x20, 0x95, 0x21, 0xa5, 0xa5, 0xd2, 0xa4, + 0x1e, 0x98, 0x4d, 0x3b, 0xd8, 0x11, 0xa1, 0x2c, 0x97, 0x89, 0x4d, 0x8a, 0x22, 0xd4, 0x03, 0x97, + 0xca, 0x76, 0x4d, 0x6a, 0xe5, 0x8f, 0xa3, 0xda, 0xcb, 0xc8, 0x91, 0x6f, 0xc0, 0x87, 0xe1, 0x43, + 0x70, 0x9c, 0x38, 0xa1, 0x1d, 0x2a, 0xd4, 0x7e, 0x11, 0x14, 0xc7, 0xb0, 0x16, 0x01, 0x15, 0xb7, + 0xbc, 0x79, 0x1e, 0xbf, 0xcf, 0x9b, 0xdf, 0x1b, 0xc3, 0x43, 0x4a, 0x68, 0x99, 0xc8, 0x0c, 0x53, + 0xcd, 0x94, 0x26, 0xb1, 0xc8, 0x22, 0x5c, 0x0c, 0xb0, 0xc8, 0x18, 0xcf, 0xb4, 0x28, 0x38, 0xca, + 0x67, 0x52, 0x4b, 0xe7, 0x91, 0xb5, 0xa1, 0x5b, 0x1b, 0x2a, 0x06, 0x07, 0x0f, 0x23, 0x19, 0x49, + 0xe3, 0xc0, 0xd5, 0x53, 0x6d, 0x3e, 0x78, 0xcc, 0xa4, 0x4a, 0xa5, 0x1a, 0xd7, 0x42, 0x5d, 0x58, + 0xa9, 0x57, 0x57, 0x98, 0xcd, 0xca, 0x5c, 0x4b, 0xac, 0x38, 0xcb, 0x87, 0x2f, 0x4f, 0xe2, 0x01, + 0x8e, 0x79, 0x69, 0x3d, 0xbd, 0x8f, 0x00, 0xde, 0x0f, 0xf9, 0x15, 0x99, 0x4d, 0x7c, 0xa1, 0xf4, + 0x29, 0x61, 0x53, 0xee, 0x3c, 0x83, 0x8e, 0x96, 0x9a, 0x24, 0xe3, 0x42, 0x6a, 0x91, 0x45, 0xe3, + 0x5c, 0x5e, 0xf1, 0x59, 0x1b, 0x74, 0x41, 0x7f, 0x3b, 0x7c, 0x60, 0x94, 0x91, 0x11, 0x82, 0xea, + 0xbd, 0xf3, 0x1a, 0xde, 0xa5, 0x9a, 0x8d, 0x0b, 0x92, 0xa8, 0x76, 0xb3, 0xbb, 0xd5, 0x6f, 0x0d, + 0x0f, 0xd1, 0x1f, 0x3f, 0x00, 0x79, 0x6f, 0x4f, 0x47, 0x24, 0xa9, 0x72, 0xce, 0xb2, 0xf7, 0x32, + 0xbc, 0x43, 0x35, 0x1b, 0x91, 0x44, 0xf5, 0xe6, 0x4d, 0x78, 0x6f, 0x5d, 0x73, 0x2e, 0xe0, 0x6e, + 0xd5, 0x34, 0x8f, 0x4d, 0xec, 0xbe, 0x77, 0x72, 0x33, 0xef, 0x0c, 0x23, 0xa1, 0xa7, 0x97, 0x14, + 0x31, 0x99, 0x62, 0x1b, 0xc0, 0xa6, 0x44, 0x64, 0x3f, 0x0b, 0xac, 0xcb, 0x9c, 0x2b, 0xe4, 0x9d, + 0x05, 0xc7, 0x2f, 0x9e, 0x07, 0x97, 0xf4, 0x0d, 0x2f, 0xc3, 0x1d, 0xaa, 0x59, 0x10, 0x3b, 0xaf, + 0x20, 0xb4, 0xa6, 0xaa, 0x65, 0xb3, 0x0b, 0xfa, 0xad, 0x61, 0x07, 0x59, 0x58, 0x35, 0x1e, 0xf4, + 0x0b, 0x0f, 0xb2, 0x67, 0xf7, 0xec, 0x91, 0x20, 0x76, 0x2e, 0x20, 0x64, 0x32, 0x4d, 0x85, 0x52, + 0x42, 0x66, 0xed, 0xad, 0x2e, 0xe8, 0xef, 0x79, 0x47, 0x37, 0xf3, 0xce, 0x93, 0xba, 0x85, 0x9a, + 0xc4, 0x48, 0x48, 0x9c, 0x12, 0x3d, 0x45, 0xe7, 0x3c, 0x22, 0xac, 0xf4, 0x39, 0xfb, 0xfa, 0xf9, + 0x08, 0xda, 0x04, 0x9f, 0xb3, 0x70, 0xa5, 0xc1, 0x5f, 0x00, 0x6f, 0xff, 0x1b, 0xf0, 0x84, 0x27, + 0xaa, 0xbd, 0xb3, 0x09, 0xb0, 0xcf, 0xd7, 0x01, 0xfb, 0x3c, 0x51, 0x3d, 0x65, 0xf8, 0xae, 0x48, + 0xbf, 0x01, 0x01, 0xff, 0x0d, 0xe4, 0x29, 0xdc, 0x5f, 0x9b, 0xbd, 0x69, 0x66, 0x6f, 0x15, 0xb7, + 0x63, 0x7b, 0xe7, 0x5f, 0x16, 0x2e, 0xb8, 0x5e, 0xb8, 0xe0, 0xfb, 0xc2, 0x05, 0x9f, 0x96, 0x6e, + 0xe3, 0x7a, 0xe9, 0x36, 0xbe, 0x2d, 0xdd, 0xc6, 0xbb, 0x8d, 0x8b, 0xfc, 0xb0, 0x7a, 0x41, 0xcc, + 0x56, 0xe9, 0xae, 0xf9, 0x5d, 0x8f, 0x7f, 0x04, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x41, 0xf4, 0x9f, + 0x43, 0x03, 0x00, 0x00, } func (m *RewardDistCache) Marshal() (dAtA []byte, err error) { @@ -723,7 +723,7 @@ func (m *BTCValDistInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_cosmos_cosmos_sdk_types.Dec + var v cosmossdk_io_math.LegacyDec m.Commission = &v if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/x/btcstaking/types/mocked_keepers.go b/x/btcstaking/types/mocked_keepers.go index 7d3ec29f7..3ab694c07 100644 --- a/x/btcstaking/types/mocked_keepers.go +++ b/x/btcstaking/types/mocked_keepers.go @@ -5,91 +5,16 @@ package types import ( + context "context" big "math/big" reflect "reflect" types "github.com/babylonchain/babylon/types" types0 "github.com/babylonchain/babylon/x/btccheckpoint/types" types1 "github.com/babylonchain/babylon/x/btclightclient/types" - types2 "github.com/cosmos/cosmos-sdk/types" - types3 "github.com/cosmos/cosmos-sdk/x/auth/types" gomock "github.com/golang/mock/gomock" ) -// MockAccountKeeper is a mock of AccountKeeper interface. -type MockAccountKeeper struct { - ctrl *gomock.Controller - recorder *MockAccountKeeperMockRecorder -} - -// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. -type MockAccountKeeperMockRecorder struct { - mock *MockAccountKeeper -} - -// NewMockAccountKeeper creates a new mock instance. -func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { - mock := &MockAccountKeeper{ctrl: ctrl} - mock.recorder = &MockAccountKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { - return m.recorder -} - -// GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx types2.Context, addr types2.AccAddress) types3.AccountI { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types3.AccountI) - return ret0 -} - -// GetAccount indicates an expected call of GetAccount. -func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) -} - -// MockBankKeeper is a mock of BankKeeper interface. -type MockBankKeeper struct { - ctrl *gomock.Controller - recorder *MockBankKeeperMockRecorder -} - -// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. -type MockBankKeeperMockRecorder struct { - mock *MockBankKeeper -} - -// NewMockBankKeeper creates a new mock instance. -func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { - mock := &MockBankKeeper{ctrl: ctrl} - mock.recorder = &MockBankKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { - return m.recorder -} - -// SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx types2.Context, addr types2.AccAddress) types2.Coins { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) - ret0, _ := ret[0].(types2.Coins) - return ret0 -} - -// SpendableCoins indicates an expected call of SpendableCoins. -func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) -} - // MockBTCLightClientKeeper is a mock of BTCLightClientKeeper interface. type MockBTCLightClientKeeper struct { ctrl *gomock.Controller @@ -114,7 +39,7 @@ func (m *MockBTCLightClientKeeper) EXPECT() *MockBTCLightClientKeeperMockRecorde } // GetHeaderByHash mocks base method. -func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx types2.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx context.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHeaderByHash", ctx, hash) ret0, _ := ret[0].(*types1.BTCHeaderInfo) @@ -128,7 +53,7 @@ func (mr *MockBTCLightClientKeeperMockRecorder) GetHeaderByHash(ctx, hash interf } // GetTipInfo mocks base method. -func (m *MockBTCLightClientKeeper) GetTipInfo(ctx types2.Context) *types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetTipInfo(ctx context.Context) *types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTipInfo", ctx) ret0, _ := ret[0].(*types1.BTCHeaderInfo) @@ -165,7 +90,7 @@ func (m *MockBtcCheckpointKeeper) EXPECT() *MockBtcCheckpointKeeperMockRecorder } // GetParams mocks base method. -func (m *MockBtcCheckpointKeeper) GetParams(ctx types2.Context) types0.Params { +func (m *MockBtcCheckpointKeeper) GetParams(ctx context.Context) types0.Params { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetParams", ctx) ret0, _ := ret[0].(types0.Params) diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 8acb8e744..6b36c964c 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -3,13 +3,12 @@ package types import ( "fmt" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -50,9 +49,9 @@ func DefaultParams() Params { CovenantQuorum: 1, // TODO: default values for multisig covenant SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, - MinCommissionRate: math.LegacyZeroDec(), + MinCommissionRate: sdkmath.LegacyZeroDec(), // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. - SlashingRate: math.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 + SlashingRate: sdkmath.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 MaxActiveBtcValidators: defaultMaxActiveBtcValidators, } } @@ -69,7 +68,7 @@ func validateMinSlashingTxFeeSat(fee int64) error { return nil } -func validateMinCommissionRate(rate sdk.Dec) error { +func validateMinCommissionRate(rate sdkmath.LegacyDec) error { if rate.IsNil() { return fmt.Errorf("minimum commission rate cannot be nil") } @@ -78,7 +77,7 @@ func validateMinCommissionRate(rate sdk.Dec) error { return fmt.Errorf("minimum commission rate cannot be negative") } - if rate.GT(math.LegacyOneDec()) { + if rate.GT(sdkmath.LegacyOneDec()) { return fmt.Errorf("minimum commission rate cannot be greater than 100%%") } return nil diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index af5a903b5..8b471927a 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -4,10 +4,10 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" _ "github.com/cosmos/cosmos-proto" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -42,10 +42,10 @@ type Params struct { // TODO: change to satoshi per byte? MinSlashingTxFeeSat int64 `protobuf:"varint,4,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators - MinCommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_commission_rate"` + MinCommissionRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_commission_rate"` // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). - SlashingRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slashing_rate"` + SlashingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slashing_rate"` // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol MaxActiveBtcValidators uint32 `protobuf:"varint,7,opt,name=max_active_btc_validators,json=maxActiveBtcValidators,proto3" json:"max_active_btc_validators,omitempty"` } @@ -119,36 +119,36 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcb, 0x6e, 0xd3, 0x40, - 0x18, 0x85, 0x6d, 0x52, 0x82, 0x18, 0x52, 0x0a, 0xe6, 0xe6, 0x76, 0xe1, 0x44, 0x5d, 0x40, 0x58, - 0xd4, 0xa6, 0xb4, 0x42, 0x02, 0xb1, 0xa9, 0x41, 0x48, 0x08, 0x16, 0xc1, 0x45, 0x48, 0xb0, 0x60, - 0xf4, 0x7b, 0x32, 0x38, 0xa3, 0x64, 0x66, 0x82, 0x67, 0x6c, 0x39, 0x6f, 0xc1, 0x92, 0x25, 0x0f, - 0xc1, 0x43, 0x74, 0x59, 0xb1, 0x42, 0x5d, 0x44, 0x28, 0x79, 0x02, 0xde, 0x00, 0x79, 0x7c, 0xa1, - 0x3b, 0xc4, 0xca, 0x9e, 0x73, 0x8e, 0xbe, 0x33, 0x97, 0x1f, 0xed, 0xc6, 0x10, 0x2f, 0x66, 0x52, - 0x04, 0xb1, 0x26, 0x4a, 0xc3, 0x94, 0x89, 0x24, 0xc8, 0xf7, 0x83, 0x39, 0xa4, 0xc0, 0x95, 0x3f, - 0x4f, 0xa5, 0x96, 0xce, 0xad, 0x3a, 0xe3, 0xff, 0xcd, 0xf8, 0xf9, 0xfe, 0xce, 0xcd, 0x44, 0x26, - 0xd2, 0x24, 0x82, 0xf2, 0xaf, 0x0a, 0xef, 0x6c, 0x13, 0xa9, 0xb8, 0x54, 0xb8, 0x32, 0xaa, 0x45, - 0x65, 0xed, 0xfe, 0xee, 0xa0, 0xee, 0xc8, 0x80, 0x9d, 0xf7, 0xa8, 0x47, 0x64, 0x4e, 0x05, 0x08, - 0x8d, 0xe7, 0x53, 0xe5, 0xda, 0x83, 0xce, 0xb0, 0x17, 0x3e, 0x3a, 0x5b, 0xf6, 0x1f, 0x26, 0x4c, - 0x4f, 0xb2, 0xd8, 0x27, 0x92, 0x07, 0x75, 0x2f, 0x99, 0x00, 0x13, 0xcd, 0x22, 0xd0, 0x8b, 0x39, - 0x55, 0x7e, 0xf8, 0x72, 0x74, 0x70, 0xf8, 0x60, 0x94, 0xc5, 0xaf, 0xe8, 0x22, 0xba, 0xd2, 0xb0, - 0x46, 0x53, 0xe5, 0xdc, 0x43, 0x5b, 0x2d, 0xfa, 0x73, 0x26, 0xd3, 0x8c, 0xbb, 0x17, 0x06, 0xf6, - 0x70, 0x33, 0xba, 0xda, 0xc8, 0x6f, 0x8c, 0xea, 0xdc, 0x47, 0xd7, 0xd4, 0x0c, 0xd4, 0x84, 0x89, - 0x04, 0xc3, 0x78, 0x9c, 0x52, 0xa5, 0xdc, 0xce, 0xc0, 0x1e, 0x5e, 0x8e, 0xb6, 0x1a, 0xfd, 0xa8, - 0x92, 0x9d, 0x43, 0x74, 0x87, 0x33, 0x81, 0xdb, 0xb8, 0x2e, 0xf0, 0x27, 0x4a, 0xb1, 0x02, 0xed, - 0x6e, 0x0c, 0xec, 0x61, 0x27, 0xba, 0xc1, 0x99, 0x38, 0xae, 0xdd, 0xb7, 0xc5, 0x0b, 0x4a, 0x8f, - 0x41, 0x3b, 0x1f, 0x51, 0x29, 0x63, 0x22, 0x39, 0x67, 0x4a, 0x31, 0x29, 0x70, 0x0a, 0x9a, 0xba, - 0x17, 0xcb, 0x8e, 0xd0, 0x3f, 0x59, 0xf6, 0xad, 0xb3, 0x65, 0xff, 0xee, 0xb9, 0xf3, 0x56, 0xb7, - 0x55, 0x7f, 0xf6, 0xd4, 0x78, 0x5a, 0x1f, 0xf6, 0x39, 0x25, 0xd1, 0x75, 0xce, 0xc4, 0xb3, 0x96, - 0x14, 0x81, 0xa6, 0x0e, 0xa0, 0xcd, 0x76, 0x47, 0x86, 0xdc, 0x35, 0xe4, 0xa7, 0xff, 0x47, 0xfe, - 0xf1, 0x7d, 0x0f, 0xd5, 0xcf, 0x54, 0xf6, 0xf4, 0x1a, 0xa4, 0xa9, 0x78, 0x8c, 0xb6, 0x39, 0x14, - 0x18, 0x88, 0x66, 0x39, 0xc5, 0xb1, 0x26, 0x38, 0x87, 0x19, 0x1b, 0x83, 0x96, 0xa9, 0x72, 0x2f, - 0x99, 0x6b, 0xbd, 0xcd, 0xa1, 0x38, 0x32, 0x7e, 0xa8, 0xc9, 0xbb, 0xd6, 0x7d, 0xb2, 0xf1, 0xf5, - 0x5b, 0xdf, 0x0a, 0x5f, 0x9f, 0xac, 0x3c, 0xfb, 0x74, 0xe5, 0xd9, 0xbf, 0x56, 0x9e, 0xfd, 0x65, - 0xed, 0x59, 0xa7, 0x6b, 0xcf, 0xfa, 0xb9, 0xf6, 0xac, 0x0f, 0xff, 0x7c, 0xe8, 0xe2, 0xfc, 0x4c, - 0x9a, 0xed, 0xc6, 0x5d, 0x33, 0x48, 0x07, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x89, 0x23, 0x96, - 0x04, 0xb6, 0x02, 0x00, 0x00, + // 454 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x6d, 0x52, 0x82, 0x58, 0x52, 0x0a, 0xe6, 0x9f, 0x5b, 0x24, 0x27, 0x2a, 0x07, 0xc2, + 0x01, 0x9b, 0xd0, 0x0a, 0x09, 0x6e, 0x35, 0x08, 0x09, 0xd1, 0x43, 0x70, 0x50, 0x25, 0xb8, 0xac, + 0xc6, 0x9b, 0xc5, 0x59, 0x25, 0xbb, 0x1b, 0xbc, 0x1b, 0xcb, 0x79, 0x0b, 0x8e, 0x1c, 0x79, 0x08, + 0x1e, 0xa2, 0xc7, 0x8a, 0x13, 0x2a, 0x52, 0x84, 0x92, 0x17, 0x41, 0x5e, 0xff, 0x81, 0x1b, 0xbd, + 0x79, 0xbe, 0xef, 0xf3, 0x6f, 0x56, 0x33, 0x83, 0xf6, 0x63, 0x88, 0x97, 0x33, 0x29, 0x82, 0x58, + 0x13, 0xa5, 0x61, 0xca, 0x44, 0x12, 0x64, 0x83, 0x60, 0x0e, 0x29, 0x70, 0xe5, 0xcf, 0x53, 0xa9, + 0xa5, 0x73, 0xa7, 0xca, 0xf8, 0x7f, 0x33, 0x7e, 0x36, 0xd8, 0xbb, 0x9d, 0xc8, 0x44, 0x9a, 0x44, + 0x50, 0x7c, 0x95, 0xe1, 0xbd, 0x5d, 0x22, 0x15, 0x97, 0x0a, 0x97, 0x46, 0x59, 0x94, 0xd6, 0xfe, + 0xaf, 0x16, 0x6a, 0x0f, 0x0d, 0xd8, 0xf9, 0x80, 0x3a, 0x44, 0x66, 0x54, 0x80, 0xd0, 0x78, 0x3e, + 0x55, 0xae, 0xdd, 0x6b, 0xf5, 0x3b, 0xe1, 0xb3, 0xf3, 0x55, 0xf7, 0x69, 0xc2, 0xf4, 0x64, 0x11, + 0xfb, 0x44, 0xf2, 0xa0, 0xea, 0x4b, 0x26, 0xc0, 0x44, 0x5d, 0x04, 0x7a, 0x39, 0xa7, 0xca, 0x0f, + 0xdf, 0x0c, 0x0f, 0x0e, 0x9f, 0x0c, 0x17, 0xf1, 0x5b, 0xba, 0x8c, 0xae, 0xd5, 0xac, 0xe1, 0x54, + 0x39, 0x0f, 0xd1, 0x4e, 0x83, 0xfe, 0xbc, 0x90, 0xe9, 0x82, 0xbb, 0x97, 0x7a, 0x76, 0x7f, 0x3b, + 0xba, 0x5e, 0xcb, 0xef, 0x8c, 0xea, 0x3c, 0x42, 0x37, 0xd4, 0x0c, 0xd4, 0x84, 0x89, 0x04, 0xc3, + 0x78, 0x9c, 0x52, 0xa5, 0xdc, 0x56, 0xcf, 0xee, 0x5f, 0x8d, 0x76, 0x6a, 0xfd, 0xa8, 0x94, 0x9d, + 0x43, 0x74, 0x8f, 0x33, 0x81, 0x9b, 0xb8, 0xce, 0xf1, 0x27, 0x4a, 0xb1, 0x02, 0xed, 0x6e, 0xf5, + 0xec, 0x7e, 0x2b, 0xba, 0xc5, 0x99, 0x18, 0x55, 0xee, 0xfb, 0xfc, 0x35, 0xa5, 0x23, 0xd0, 0xce, + 0x08, 0x15, 0x32, 0x26, 0x92, 0x73, 0xa6, 0x14, 0x93, 0x02, 0xa7, 0xa0, 0xa9, 0x7b, 0xb9, 0xe8, + 0x11, 0x3e, 0x38, 0x5d, 0x75, 0xad, 0xf3, 0x55, 0xf7, 0x7e, 0x39, 0x22, 0x35, 0x9e, 0xfa, 0x4c, + 0x06, 0x1c, 0xf4, 0xc4, 0x3f, 0xa6, 0x09, 0x90, 0xe5, 0x2b, 0x4a, 0xa2, 0x9b, 0x9c, 0x89, 0x97, + 0xcd, 0xef, 0x11, 0x68, 0xea, 0x9c, 0xa0, 0xed, 0xe6, 0x19, 0x06, 0xd7, 0x36, 0xb8, 0xc1, 0x05, + 0x70, 0x3f, 0xbe, 0x3f, 0x46, 0xd5, 0x42, 0x0a, 0x78, 0xa7, 0xe6, 0x18, 0xee, 0x73, 0xb4, 0xcb, + 0x21, 0xc7, 0x40, 0x34, 0xcb, 0x28, 0x8e, 0x35, 0xc1, 0x19, 0xcc, 0xd8, 0x18, 0xb4, 0x4c, 0x95, + 0x7b, 0xc5, 0x0c, 0xf0, 0x2e, 0x87, 0xfc, 0xc8, 0xf8, 0xa1, 0x26, 0x27, 0x8d, 0xfb, 0x62, 0xeb, + 0xeb, 0xb7, 0xae, 0x15, 0x1e, 0x9f, 0xae, 0x3d, 0xfb, 0x6c, 0xed, 0xd9, 0xbf, 0xd7, 0x9e, 0xfd, + 0x65, 0xe3, 0x59, 0x67, 0x1b, 0xcf, 0xfa, 0xb9, 0xf1, 0xac, 0x8f, 0xff, 0x5d, 0x69, 0xfe, 0xef, + 0xf5, 0x99, 0xfd, 0xc6, 0x6d, 0x73, 0x32, 0x07, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x98, 0x41, + 0x8e, 0x46, 0xa0, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { diff --git a/x/btcstaking/types/pop.go b/x/btcstaking/types/pop.go index f8dc529d4..8a02d1694 100644 --- a/x/btcstaking/types/pop.go +++ b/x/btcstaking/types/pop.go @@ -211,11 +211,11 @@ func (pop *ProofOfPossession) VerifyECDSA(babylonPK cryptotypes.PubKey, bip340PK return nil } -func (p *ProofOfPossession) ValidateBasic() error { - if len(p.BabylonSig) == 0 { +func (pop *ProofOfPossession) ValidateBasic() error { + if len(pop.BabylonSig) == 0 { return fmt.Errorf("empty Babylon signature") } - if p.BtcSig == nil { + if pop.BtcSig == nil { return fmt.Errorf("empty BTC signature") } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 66046b25c..a5555ac5b 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -5,12 +5,12 @@ package types import ( context "context" + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" types1 "github.com/babylonchain/babylon/x/btccheckpoint/types" _ "github.com/cosmos/cosmos-proto" secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/cosmos-sdk/types/msgservice" types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" @@ -41,7 +41,7 @@ type MsgCreateBTCValidator struct { // description defines the description terms for the BTC validator. Description *types.Description `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` // commission defines the commission rate of BTC validator. - Commission *github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission,omitempty"` + Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` // babylon_pk is the Babylon secp256k1 PK of this BTC validator BabylonPk *secp256k1.PubKey `protobuf:"bytes,4,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // btc_pk is the Bitcoin secp256k1 PK of this BTC validator @@ -725,79 +725,81 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1149 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0xcf, 0x6f, 0x1a, 0x47, - 0x14, 0x36, 0xc6, 0xd0, 0xf2, 0x0c, 0x76, 0xb2, 0x4d, 0x9c, 0x35, 0x51, 0x80, 0x90, 0x26, 0xa1, - 0x55, 0xbc, 0xc4, 0x24, 0xb6, 0x5a, 0x57, 0xaa, 0x14, 0xec, 0x4a, 0x89, 0x12, 0x54, 0xb4, 0xe0, - 0xa8, 0x8a, 0xd4, 0xa2, 0x61, 0x77, 0xbc, 0xac, 0x80, 0x9d, 0xd5, 0xce, 0x80, 0x40, 0xbd, 0xf5, - 0x58, 0xa9, 0x52, 0x4f, 0xbd, 0xf4, 0x9f, 0xe8, 0x21, 0xe7, 0x1e, 0x7a, 0x8a, 0x7a, 0x8a, 0x72, - 0xa8, 0x2a, 0x1f, 0xac, 0xca, 0x3e, 0xf4, 0xdf, 0xa8, 0x76, 0x76, 0xf6, 0x07, 0x0e, 0xb4, 0xb8, - 0xf4, 0xd4, 0x13, 0xcc, 0xce, 0xf7, 0xbe, 0xf7, 0xde, 0xf7, 0x7e, 0xb0, 0x40, 0xae, 0x8d, 0xda, - 0xe3, 0x1e, 0xb1, 0xca, 0x6d, 0xa6, 0x51, 0x86, 0xba, 0xa6, 0x65, 0x94, 0x87, 0xdb, 0x65, 0x36, - 0x52, 0x6c, 0x87, 0x30, 0x22, 0x5d, 0x15, 0xf7, 0x4a, 0x78, 0xaf, 0x0c, 0xb7, 0xb3, 0x57, 0x0c, - 0x62, 0x10, 0x8e, 0x28, 0xbb, 0xdf, 0x3c, 0x70, 0x76, 0x53, 0x23, 0xb4, 0x4f, 0x68, 0xcb, 0xbb, - 0xf0, 0x0e, 0xe2, 0xea, 0x9a, 0x77, 0x2a, 0xf7, 0x29, 0xe7, 0xef, 0x53, 0x43, 0x5c, 0x14, 0xc5, - 0x85, 0xe6, 0x8c, 0x6d, 0x46, 0xca, 0x14, 0x6b, 0x76, 0x65, 0x67, 0xb7, 0xbb, 0x5d, 0xee, 0xe2, - 0xb1, 0x6f, 0x5c, 0x9c, 0x1e, 0xa4, 0x8d, 0x1c, 0xd4, 0xf7, 0x31, 0xf7, 0x22, 0x18, 0xad, 0x83, - 0xb5, 0xae, 0x4d, 0x4c, 0x8b, 0xb9, 0xb0, 0x89, 0x07, 0x02, 0xfd, 0xbe, 0xf0, 0x1a, 0xb2, 0xb5, - 0x31, 0x43, 0xdb, 0xfe, 0x59, 0xa0, 0xf2, 0x33, 0xfc, 0x12, 0xdb, 0x03, 0x14, 0x7f, 0x8c, 0xc3, - 0xd5, 0x1a, 0x35, 0xf6, 0x1d, 0x8c, 0x18, 0xae, 0x36, 0xf7, 0x9f, 0xa3, 0x9e, 0xa9, 0x23, 0x46, - 0x1c, 0x69, 0x03, 0x92, 0xd4, 0x34, 0x2c, 0xec, 0xc8, 0xb1, 0x42, 0xac, 0x94, 0x52, 0xc5, 0x49, - 0xfa, 0x0c, 0x56, 0x75, 0x4c, 0x35, 0xc7, 0xb4, 0x99, 0x49, 0x2c, 0x79, 0xb9, 0x10, 0x2b, 0xad, - 0x56, 0x6e, 0x29, 0x42, 0xab, 0x50, 0x61, 0x1e, 0x8e, 0x72, 0x10, 0x42, 0xd5, 0xa8, 0x9d, 0xf4, - 0x05, 0x80, 0x46, 0xfa, 0x7d, 0x93, 0x52, 0x97, 0x25, 0xee, 0xba, 0xa8, 0x7e, 0x74, 0x7c, 0x92, - 0xbf, 0x63, 0x98, 0xac, 0x33, 0x68, 0x2b, 0x1a, 0xe9, 0x97, 0x7d, 0x61, 0xf9, 0xc7, 0x16, 0xd5, - 0xbb, 0x65, 0x36, 0xb6, 0x31, 0x55, 0x0e, 0xb0, 0xf6, 0xe6, 0xe5, 0x16, 0x08, 0x97, 0x07, 0x58, - 0x53, 0x23, 0x5c, 0xd2, 0xa7, 0x00, 0x22, 0xeb, 0x96, 0xdd, 0x95, 0x57, 0x78, 0x7c, 0x79, 0x3f, - 0x3e, 0xaf, 0x48, 0x4a, 0x50, 0x24, 0xa5, 0x3e, 0x68, 0x3f, 0xc5, 0x63, 0x35, 0x25, 0x4c, 0xea, - 0x5d, 0xa9, 0x06, 0xc9, 0x36, 0xd3, 0x5c, 0xdb, 0x44, 0x21, 0x56, 0x4a, 0x57, 0x77, 0x8f, 0x4f, - 0xf2, 0x95, 0x48, 0x54, 0x02, 0xa9, 0x75, 0x90, 0x69, 0xf9, 0x07, 0x11, 0x58, 0xf5, 0x49, 0xfd, - 0xc1, 0xc3, 0xfb, 0x82, 0x32, 0xd1, 0x66, 0x5a, 0xbd, 0x2b, 0xed, 0x41, 0xdc, 0x26, 0xb6, 0x9c, - 0xe4, 0x71, 0x94, 0x94, 0xa9, 0xdd, 0xa8, 0xd4, 0x1d, 0x42, 0x8e, 0x3e, 0x3f, 0xaa, 0x13, 0x4a, - 0x31, 0xcf, 0x42, 0x75, 0x8d, 0x8a, 0x79, 0xb8, 0x31, 0xb5, 0x38, 0x2a, 0xa6, 0x36, 0xb1, 0x28, - 0x2e, 0x7e, 0x97, 0x80, 0x8d, 0x28, 0xe2, 0x00, 0xf7, 0xb0, 0x81, 0xb8, 0xc0, 0xb3, 0xea, 0x37, - 0x29, 0xcf, 0xf2, 0x85, 0xe5, 0x11, 0xf9, 0xc4, 0xff, 0x45, 0x3e, 0xd2, 0x0b, 0xc8, 0xb8, 0x20, - 0xec, 0xb4, 0x84, 0xc2, 0x2b, 0x0b, 0x29, 0xbc, 0xea, 0x91, 0x55, 0xb9, 0xce, 0x5f, 0xc2, 0xfa, - 0x10, 0xf5, 0x04, 0x71, 0xab, 0x67, 0x52, 0x26, 0x27, 0x0a, 0xf1, 0x05, 0xd8, 0xd3, 0x43, 0xd4, - 0xe3, 0xd4, 0xcf, 0x4c, 0xca, 0xa4, 0x9b, 0x90, 0x16, 0xf9, 0xb5, 0x98, 0xd9, 0xc7, 0xbc, 0x9e, - 0x19, 0x2f, 0x02, 0xd3, 0x32, 0x9a, 0x66, 0x1f, 0x4b, 0xb7, 0xbc, 0xec, 0x5c, 0xc8, 0x10, 0xf5, - 0x06, 0x58, 0x7e, 0xa7, 0x10, 0x2b, 0xc5, 0x55, 0xdf, 0xee, 0xb9, 0xfb, 0x4c, 0x7a, 0x0c, 0x10, - 0xf0, 0x8c, 0xe4, 0x77, 0xb9, 0x8a, 0x1f, 0x44, 0x55, 0x8c, 0x4c, 0xfa, 0x70, 0x5b, 0x69, 0x3a, - 0xc8, 0xa2, 0x48, 0x73, 0x2b, 0xfa, 0xc4, 0x3a, 0x22, 0x6a, 0xca, 0x77, 0x38, 0x92, 0x2a, 0xb0, - 0x4a, 0x7b, 0x88, 0x76, 0x04, 0x55, 0x8a, 0x4b, 0x79, 0xf9, 0xf8, 0x24, 0x9f, 0xa9, 0x36, 0xf7, - 0x1b, 0xe2, 0xa6, 0x39, 0x52, 0x81, 0x06, 0xdf, 0xa5, 0xaf, 0x20, 0xa3, 0x7b, 0x2d, 0x42, 0x9c, - 0x16, 0x35, 0x0d, 0x19, 0xb8, 0xd5, 0xc7, 0xc7, 0x27, 0xf9, 0x9d, 0x8b, 0x48, 0xd4, 0x30, 0x0d, - 0x0b, 0xb1, 0x81, 0x83, 0xd5, 0x74, 0xc0, 0xd7, 0x30, 0x8d, 0x62, 0x01, 0x72, 0xd3, 0xdb, 0x31, - 0xe8, 0xd8, 0x5f, 0x96, 0xe1, 0x52, 0x8d, 0x1a, 0xd5, 0xe6, 0xfe, 0xa1, 0x25, 0x4c, 0xf1, 0xcc, - 0x5e, 0xbd, 0x09, 0xe9, 0x81, 0xd5, 0x26, 0x96, 0x2e, 0x72, 0x74, 0xbb, 0x35, 0xad, 0xae, 0x06, - 0xcf, 0x9a, 0x23, 0xe9, 0x36, 0xac, 0x45, 0x20, 0x6e, 0x65, 0xe2, 0xbc, 0x32, 0x99, 0x10, 0xe4, - 0xd6, 0xe6, 0x2e, 0xac, 0x87, 0x30, 0xaf, 0x3a, 0x2b, 0xbc, 0x3a, 0xa1, 0xb5, 0x57, 0x9f, 0x73, - 0xaa, 0x26, 0xe6, 0x51, 0x95, 0xc0, 0x46, 0x44, 0x55, 0xdf, 0xda, 0x95, 0x37, 0xb9, 0xa8, 0xbc, - 0x57, 0x42, 0x79, 0x05, 0xaf, 0x2b, 0x73, 0x16, 0xe4, 0xf3, 0x1a, 0x06, 0x02, 0xff, 0xbc, 0x0c, - 0x97, 0x6b, 0xd4, 0x78, 0xa4, 0xeb, 0xfb, 0x64, 0x88, 0x2d, 0x64, 0xb1, 0x86, 0x69, 0xcc, 0x54, - 0xb8, 0x06, 0x49, 0x77, 0x6a, 0xc4, 0x26, 0x58, 0x60, 0xd9, 0x0d, 0x51, 0xcf, 0xdb, 0x9d, 0x3a, - 0xe6, 0x74, 0xf1, 0xc5, 0xe8, 0x74, 0xec, 0xd2, 0xdd, 0x81, 0xf5, 0x70, 0x58, 0x5a, 0x1d, 0x44, - 0x3b, 0xbc, 0x6a, 0x29, 0x35, 0x13, 0x8c, 0xc1, 0x63, 0x44, 0x3b, 0xd2, 0x53, 0x88, 0xbb, 0x6a, - 0x27, 0x16, 0x55, 0xdb, 0x65, 0x29, 0x5e, 0x87, 0xcd, 0xb7, 0xf4, 0x0b, 0xd4, 0xfd, 0x21, 0x06, - 0xeb, 0x35, 0x6a, 0x1c, 0xda, 0x3a, 0x62, 0xb8, 0xce, 0x7f, 0xbe, 0xa5, 0x5d, 0x48, 0xa1, 0x01, - 0xeb, 0x10, 0xc7, 0x64, 0x63, 0x4f, 0xde, 0xaa, 0xfc, 0xe6, 0xe5, 0xd6, 0x15, 0xb1, 0x53, 0x1f, - 0xe9, 0xba, 0x83, 0x29, 0x6d, 0x30, 0xc7, 0xb4, 0x0c, 0x35, 0x84, 0x4a, 0x9f, 0x40, 0xd2, 0x7b, - 0x01, 0x10, 0x5b, 0xf8, 0xc6, 0xac, 0x65, 0xca, 0x41, 0xd5, 0x95, 0x57, 0x27, 0xf9, 0x25, 0x55, - 0x98, 0xec, 0xad, 0x7d, 0xf3, 0xe7, 0x4f, 0x1f, 0x86, 0x64, 0xc5, 0x4d, 0xb8, 0x76, 0x2e, 0xae, - 0x20, 0xe6, 0xdf, 0xe2, 0x70, 0x7d, 0x32, 0xa3, 0x43, 0xbf, 0xe7, 0x1b, 0xa6, 0x41, 0xff, 0xe7, - 0xbd, 0xa1, 0xc1, 0xa5, 0xe8, 0x0e, 0x69, 0xfd, 0x27, 0x8d, 0xb2, 0x16, 0x59, 0x41, 0xee, 0x78, - 0x31, 0xd8, 0x0c, 0xe6, 0xfe, 0x2d, 0x6f, 0x0b, 0x2f, 0x81, 0x0d, 0x9f, 0xfb, 0x70, 0xc2, 0x6b, - 0xf1, 0x36, 0xdc, 0xfa, 0x9b, 0xba, 0xfa, 0xf5, 0xaf, 0xfc, 0x9a, 0x80, 0x78, 0x8d, 0x1a, 0xd2, - 0x08, 0xa4, 0x29, 0xef, 0x79, 0xf7, 0x66, 0x74, 0xdd, 0xd4, 0x17, 0x8f, 0xec, 0xc3, 0x8b, 0xa0, - 0xfd, 0x08, 0xa4, 0xaf, 0xe1, 0xbd, 0x69, 0xaf, 0x28, 0x5b, 0x73, 0x90, 0x85, 0xf0, 0xec, 0xce, - 0x85, 0xe0, 0x81, 0x73, 0x13, 0x32, 0x93, 0xbf, 0x36, 0x77, 0x67, 0xf3, 0x4c, 0x00, 0xb3, 0xe5, - 0x39, 0x81, 0x81, 0xab, 0x1e, 0xac, 0x9d, 0xdb, 0xbb, 0xa5, 0xd9, 0x14, 0x93, 0xc8, 0xec, 0xfd, - 0x79, 0x91, 0x81, 0xb7, 0x6f, 0x63, 0x20, 0xcf, 0x1c, 0xea, 0xca, 0x5c, 0x74, 0x13, 0x36, 0xd9, - 0xbd, 0x8b, 0xdb, 0x04, 0xc1, 0x1c, 0x41, 0x7a, 0x62, 0x29, 0xde, 0x99, 0xcd, 0x15, 0xc5, 0x65, - 0x95, 0xf9, 0x70, 0xbe, 0x9f, 0xea, 0xb3, 0x57, 0xa7, 0xb9, 0xd8, 0xeb, 0xd3, 0x5c, 0xec, 0x8f, - 0xd3, 0x5c, 0xec, 0xfb, 0xb3, 0xdc, 0xd2, 0xeb, 0xb3, 0xdc, 0xd2, 0xef, 0x67, 0xb9, 0xa5, 0x17, - 0xff, 0xb8, 0x4b, 0x46, 0xd1, 0x7f, 0x41, 0x7c, 0xd2, 0xda, 0x49, 0xfe, 0x2f, 0xe8, 0xc1, 0x5f, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x92, 0xce, 0x72, 0x31, 0x45, 0x0e, 0x00, 0x00, + // 1169 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0x4f, 0x6f, 0x1a, 0x47, + 0x14, 0x37, 0xc6, 0xd0, 0xf2, 0x00, 0x3b, 0xd9, 0x3a, 0x0e, 0xc6, 0x0a, 0x10, 0xdc, 0x24, 0x34, + 0x8a, 0x97, 0x98, 0xc4, 0x96, 0xea, 0x4a, 0x95, 0x82, 0x5d, 0x29, 0x51, 0x8c, 0x8a, 0x16, 0x9c, + 0x43, 0xa4, 0x16, 0x0d, 0xbb, 0xe3, 0x65, 0x05, 0xec, 0xac, 0x76, 0x06, 0x04, 0xea, 0xa5, 0xea, + 0xb1, 0xa7, 0x9e, 0x7a, 0xa8, 0xd4, 0xef, 0x90, 0x43, 0x3e, 0x44, 0xa4, 0x1e, 0x9a, 0xe6, 0x54, + 0xf9, 0x60, 0x55, 0xf6, 0x21, 0x5f, 0xa3, 0xda, 0xdd, 0xd9, 0x7f, 0x36, 0xb4, 0xb8, 0xf4, 0x94, + 0x1b, 0xb3, 0xf3, 0x7b, 0xbf, 0xf7, 0xde, 0xef, 0xfd, 0x61, 0x17, 0x72, 0x6d, 0xd4, 0x1e, 0xf7, + 0x88, 0x5e, 0x6e, 0x33, 0x99, 0x32, 0xd4, 0xd5, 0x74, 0xb5, 0x3c, 0xdc, 0x2e, 0xb3, 0x91, 0x68, + 0x98, 0x84, 0x11, 0xe1, 0x06, 0xbf, 0x17, 0xfd, 0x7b, 0x71, 0xb8, 0x9d, 0x5d, 0x55, 0x89, 0x4a, + 0x6c, 0x44, 0xd9, 0xfa, 0xe5, 0x80, 0xb3, 0xeb, 0x32, 0xa1, 0x7d, 0x42, 0x5b, 0xce, 0x85, 0x73, + 0xe0, 0x57, 0x37, 0x9d, 0x53, 0xb9, 0x4f, 0x6d, 0xfe, 0x3e, 0x55, 0xf9, 0x45, 0x91, 0x5f, 0xc8, + 0xe6, 0xd8, 0x60, 0xa4, 0x4c, 0xb1, 0x6c, 0x54, 0x76, 0x76, 0xbb, 0xdb, 0xe5, 0x2e, 0x1e, 0xbb, + 0xc6, 0xc5, 0xc9, 0x41, 0x1a, 0xc8, 0x44, 0x7d, 0x17, 0xf3, 0x20, 0x80, 0x91, 0x3b, 0x58, 0xee, + 0x1a, 0x44, 0xd3, 0x99, 0x05, 0x0b, 0x3d, 0xe0, 0xe8, 0x4f, 0xb9, 0x57, 0x9f, 0xad, 0x8d, 0x19, + 0xda, 0x76, 0xcf, 0x1c, 0x95, 0x9f, 0xe2, 0x97, 0x18, 0x0e, 0xa0, 0xf8, 0x6b, 0x14, 0x6e, 0xd4, + 0xa8, 0xba, 0x6f, 0x62, 0xc4, 0x70, 0xb5, 0xb9, 0xff, 0x02, 0xf5, 0x34, 0x05, 0x31, 0x62, 0x0a, + 0x6b, 0x10, 0xa7, 0x9a, 0xaa, 0x63, 0x33, 0x13, 0x29, 0x44, 0x4a, 0x09, 0x89, 0x9f, 0x84, 0xaf, + 0x20, 0xa9, 0x60, 0x2a, 0x9b, 0x9a, 0xc1, 0x34, 0xa2, 0x67, 0x16, 0x0b, 0x91, 0x52, 0xb2, 0xb2, + 0x29, 0x72, 0xad, 0x7c, 0x85, 0xed, 0x70, 0xc4, 0x03, 0x1f, 0x2a, 0x05, 0xed, 0x84, 0x1a, 0x80, + 0x4c, 0xfa, 0x7d, 0x8d, 0x52, 0x8b, 0x25, 0x6a, 0xb9, 0xa8, 0x6e, 0x9d, 0x9c, 0xe6, 0x37, 0x1c, + 0x22, 0xaa, 0x74, 0x45, 0x8d, 0x94, 0xfb, 0x88, 0x75, 0xc4, 0x43, 0xac, 0x22, 0x79, 0x7c, 0x80, + 0xe5, 0x77, 0xaf, 0xb7, 0x80, 0xfb, 0x39, 0xc0, 0xb2, 0x14, 0x20, 0x10, 0xbe, 0x04, 0xe0, 0xa9, + 0xb6, 0x8c, 0x6e, 0x66, 0xc9, 0x0e, 0x2a, 0xef, 0x06, 0xe5, 0x54, 0x46, 0xf4, 0x2a, 0x23, 0xd6, + 0x07, 0xed, 0xe7, 0x78, 0x2c, 0x25, 0xb8, 0x49, 0xbd, 0x2b, 0xd4, 0x20, 0xde, 0x66, 0xb2, 0x65, + 0x1b, 0x2b, 0x44, 0x4a, 0xa9, 0xea, 0xee, 0xc9, 0x69, 0xbe, 0xa2, 0x6a, 0xac, 0x33, 0x68, 0x8b, + 0x32, 0xe9, 0x97, 0x39, 0x52, 0xee, 0x20, 0x4d, 0x77, 0x0f, 0x65, 0x36, 0x36, 0x30, 0x15, 0xab, + 0xcf, 0xea, 0x8f, 0x1e, 0x3f, 0xe4, 0x94, 0xb1, 0x36, 0x93, 0xeb, 0x5d, 0x61, 0x0f, 0xa2, 0x06, + 0x31, 0x32, 0x71, 0x3b, 0x8e, 0x92, 0x38, 0xb1, 0x05, 0xc5, 0xba, 0x49, 0xc8, 0xf1, 0xd7, 0xc7, + 0x75, 0x42, 0x29, 0xb6, 0xb3, 0x90, 0x2c, 0xa3, 0xbd, 0xe4, 0x0f, 0xef, 0x5f, 0xdd, 0xe7, 0x6a, + 0x17, 0xf3, 0x70, 0x6b, 0x62, 0x79, 0x24, 0x4c, 0x0d, 0xa2, 0x53, 0x5c, 0xfc, 0x25, 0x06, 0x6b, + 0x41, 0xc4, 0x01, 0xee, 0x61, 0x15, 0xd9, 0x12, 0x4f, 0xab, 0x60, 0x58, 0xab, 0xc5, 0x2b, 0x6b, + 0xc5, 0x93, 0x8b, 0xfe, 0x87, 0xe4, 0x84, 0x97, 0x90, 0xb6, 0x40, 0xd8, 0x6c, 0x71, 0xb9, 0x97, + 0xe6, 0x92, 0x3b, 0xe9, 0x90, 0x55, 0x6d, 0xd1, 0xbf, 0x81, 0x95, 0x21, 0xea, 0x71, 0xe2, 0x56, + 0x4f, 0xa3, 0x2c, 0x13, 0x2b, 0x44, 0xe7, 0x60, 0x4f, 0x0d, 0x51, 0xcf, 0xa6, 0x3e, 0xd4, 0x28, + 0x13, 0x6e, 0x43, 0x8a, 0xe7, 0xd7, 0x62, 0x5a, 0x1f, 0xdb, 0xc5, 0x4d, 0x3b, 0x11, 0x68, 0xba, + 0xda, 0xd4, 0xfa, 0x58, 0xd8, 0x74, 0xb2, 0xb3, 0x20, 0x43, 0xd4, 0x1b, 0xe0, 0xcc, 0x47, 0x85, + 0x48, 0x29, 0x2a, 0xb9, 0x76, 0x2f, 0xac, 0x67, 0xc2, 0x53, 0x00, 0x8f, 0x67, 0x94, 0xf9, 0xd8, + 0x56, 0xf1, 0xb3, 0xa0, 0x8a, 0x81, 0x59, 0x1f, 0x6e, 0x8b, 0x4d, 0x13, 0xe9, 0x14, 0xc9, 0x56, + 0x45, 0x9f, 0xe9, 0xc7, 0x44, 0x4a, 0xb8, 0x0e, 0x47, 0x42, 0x05, 0x92, 0xb4, 0x87, 0x68, 0x87, + 0x53, 0x25, 0x6c, 0x29, 0xaf, 0x9f, 0x9c, 0xe6, 0xd3, 0xd5, 0xe6, 0x7e, 0x83, 0xdf, 0x34, 0x47, + 0x12, 0x50, 0xef, 0xb7, 0xf0, 0x2d, 0xa4, 0x15, 0xa7, 0x45, 0x88, 0xd9, 0xa2, 0x9a, 0x9a, 0x01, + 0xdb, 0xea, 0xf3, 0x93, 0xd3, 0xfc, 0xce, 0x55, 0x24, 0x6a, 0x68, 0xaa, 0x8e, 0xd8, 0xc0, 0xc4, + 0x52, 0xca, 0xe3, 0x6b, 0x68, 0x6a, 0xb8, 0x7b, 0x0b, 0x90, 0x9b, 0xdc, 0x9b, 0x5e, 0xfb, 0xfe, + 0xbe, 0x08, 0xd7, 0x6a, 0x54, 0xad, 0x36, 0xf7, 0x8f, 0x74, 0xce, 0x83, 0xa7, 0x36, 0xee, 0x6d, + 0x48, 0x0d, 0xf4, 0x36, 0xd1, 0x15, 0x9e, 0xb0, 0xd5, 0xba, 0x29, 0x29, 0xe9, 0x3d, 0x6b, 0x8e, + 0x84, 0x3b, 0xb0, 0x1c, 0x80, 0x58, 0x65, 0x8a, 0xda, 0x65, 0x4a, 0xfb, 0x20, 0xab, 0x50, 0xf7, + 0x60, 0xc5, 0x87, 0x39, 0xa5, 0x5a, 0xb2, 0x4b, 0xe5, 0x5b, 0x3b, 0xc5, 0xba, 0x20, 0x71, 0x6c, + 0x16, 0x89, 0x09, 0xac, 0x05, 0x24, 0x76, 0xad, 0x2d, 0xad, 0xe3, 0xf3, 0x6a, 0xbd, 0xea, 0x6b, + 0xcd, 0x79, 0x2f, 0x69, 0x9e, 0x85, 0xcc, 0x45, 0x41, 0x3d, 0xb5, 0x7f, 0x5b, 0x84, 0xeb, 0x35, + 0xaa, 0x3e, 0x51, 0x94, 0x7d, 0x32, 0xc4, 0x3a, 0xd2, 0x59, 0x43, 0x53, 0xa7, 0xca, 0x5d, 0x83, + 0xb8, 0x35, 0x4f, 0x7c, 0x47, 0xcc, 0xb1, 0x13, 0x87, 0xa8, 0xe7, 0xac, 0x58, 0x05, 0xdb, 0x74, + 0xd1, 0xf9, 0xe8, 0x14, 0x6c, 0xd1, 0xdd, 0x85, 0x15, 0x7f, 0x8c, 0x5a, 0x1d, 0x44, 0x3b, 0x76, + 0x09, 0x13, 0x52, 0xda, 0x1b, 0x90, 0xa7, 0x88, 0x76, 0x84, 0xe7, 0x10, 0xb5, 0xa4, 0x8f, 0xcd, + 0x2b, 0xbd, 0xc5, 0x12, 0x56, 0x7a, 0x03, 0xd6, 0x2f, 0x89, 0xe9, 0x49, 0xfd, 0x73, 0x04, 0x56, + 0x6a, 0x54, 0x3d, 0x32, 0x14, 0xc4, 0x70, 0xdd, 0xfe, 0x9f, 0x17, 0x76, 0x21, 0x81, 0x06, 0xac, + 0x43, 0x4c, 0x8d, 0x8d, 0x1d, 0xad, 0xab, 0x99, 0x77, 0xaf, 0xb7, 0x56, 0xf9, 0xea, 0x7d, 0xa2, + 0x28, 0x26, 0xa6, 0xb4, 0xc1, 0x4c, 0x4d, 0x57, 0x25, 0x1f, 0x2a, 0x7c, 0x01, 0x71, 0xe7, 0x4d, + 0x81, 0x2f, 0xeb, 0x5b, 0xd3, 0x76, 0xae, 0x0d, 0xaa, 0x2e, 0xbd, 0x39, 0xcd, 0x2f, 0x48, 0xdc, + 0x64, 0x6f, 0xd9, 0x0a, 0xd9, 0x27, 0x2b, 0xae, 0xc3, 0xcd, 0x0b, 0x71, 0x79, 0x31, 0x9f, 0x47, + 0x61, 0x23, 0x9c, 0xd1, 0x91, 0x3b, 0x0d, 0x0d, 0x4d, 0xa5, 0x1f, 0x78, 0xa3, 0xc8, 0x70, 0x2d, + 0xb8, 0x5d, 0x5a, 0xff, 0x4b, 0xd7, 0x2c, 0x07, 0x96, 0x93, 0x35, 0x6b, 0x0c, 0xd6, 0xbd, 0x8d, + 0x70, 0xc9, 0xdb, 0xdc, 0xeb, 0x61, 0xcd, 0xe5, 0x3e, 0x0a, 0x79, 0x0d, 0xb7, 0xed, 0x1d, 0xd8, + 0xfc, 0x87, 0x22, 0xbb, 0xcd, 0x50, 0xf9, 0x23, 0x06, 0xd1, 0x1a, 0x55, 0x85, 0x11, 0x08, 0x13, + 0xde, 0x0e, 0x1f, 0x4c, 0x69, 0xc1, 0x89, 0x2f, 0x2b, 0xd9, 0xc7, 0x57, 0x41, 0xbb, 0x11, 0x08, + 0xdf, 0xc1, 0x27, 0x93, 0x5e, 0x6b, 0xb6, 0x66, 0x20, 0xf3, 0xe1, 0xd9, 0x9d, 0x2b, 0xc1, 0x3d, + 0xe7, 0x1a, 0xa4, 0xc3, 0x7f, 0x4a, 0xf7, 0xa6, 0xf3, 0x84, 0x80, 0xd9, 0xf2, 0x8c, 0x40, 0xcf, + 0x55, 0x0f, 0x96, 0x2f, 0x6c, 0xe4, 0xd2, 0x74, 0x8a, 0x30, 0x32, 0xfb, 0x70, 0x56, 0xa4, 0xe7, + 0xed, 0xc7, 0x08, 0x64, 0xa6, 0x4e, 0x78, 0x65, 0x26, 0xba, 0x90, 0x4d, 0x76, 0xef, 0xea, 0x36, + 0x5e, 0x30, 0xc7, 0x90, 0x0a, 0x6d, 0xc8, 0xbb, 0xd3, 0xb9, 0x82, 0xb8, 0xac, 0x38, 0x1b, 0xce, + 0xf5, 0x93, 0x8d, 0x7d, 0xff, 0xfe, 0xd5, 0xfd, 0x48, 0xf5, 0xf0, 0xcd, 0x59, 0x2e, 0xf2, 0xf6, + 0x2c, 0x17, 0xf9, 0xeb, 0x2c, 0x17, 0xf9, 0xe9, 0x3c, 0xb7, 0xf0, 0xf6, 0x3c, 0xb7, 0xf0, 0xe7, + 0x79, 0x6e, 0xe1, 0xe5, 0xbf, 0xee, 0x97, 0x51, 0xf0, 0x13, 0xca, 0x9e, 0xbe, 0x76, 0xdc, 0xfe, + 0x84, 0x7a, 0xf4, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x1a, 0xb9, 0xe0, 0x82, 0x0e, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2087,7 +2089,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_cosmos_cosmos_sdk_types.Dec + var v cosmossdk_io_math.LegacyDec m.Commission = &v if err := m.Commission.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/x/checkpointing/abci.go b/x/checkpointing/abci.go index eee07578d..0f5b5733d 100644 --- a/x/checkpointing/abci.go +++ b/x/checkpointing/abci.go @@ -1,6 +1,7 @@ package checkpointing import ( + "context" "fmt" "time" @@ -8,18 +9,18 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/keeper" - abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" ) // BeginBlocker is called at the beginning of every block. // Upon each BeginBlock, if reaching the second block after the epoch begins, then -// - extract the LastCommitHash from the block +// - extract the AppHash from the block // - create a raw checkpoint with the status of ACCUMULATING // - start a BLS signer which creates a BLS sig transaction and distributes it to the network -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + sdkCtx := sdk.UnwrapSDKContext(ctx) // if this block is the second block of an epoch epoch := k.GetEpoch(ctx) @@ -32,14 +33,15 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) if epoch.IsSecondBlock(ctx) { // note that this epochNum is obtained after the BeginBlocker of the epoching module is executed // meaning that the epochNum has been incremented upon a new epoch - lch := ctx.BlockHeader().LastCommitHash - ckpt, err := k.BuildRawCheckpoint(ctx, epoch.EpochNumber-1, lch) + + appHash := sdkCtx.BlockHeader().AppHash + ckpt, err := k.BuildRawCheckpoint(ctx, epoch.EpochNumber-1, appHash) if err != nil { panic("failed to generate a raw checkpoint") } // emit BeginEpoch event - err = ctx.EventManager().EmitTypedEvent( + err = sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointAccumulating{ Checkpoint: ckpt, }, @@ -50,11 +52,12 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) curValSet := k.GetValidatorSet(ctx, epoch.EpochNumber-1) go func() { - err := k.SendBlsSig(ctx, epoch.EpochNumber-1, lch, curValSet) + err := k.SendBlsSig(ctx, epoch.EpochNumber-1, appHash, curValSet) if err != nil { // failing to send a BLS-sig causes a panicking situation panic(err) } }() } + return nil } diff --git a/x/checkpointing/client/cli/tx.go b/x/checkpointing/client/cli/tx.go index f309c0654..7dc4aba4f 100644 --- a/x/checkpointing/client/cli/tx.go +++ b/x/checkpointing/client/cli/tx.go @@ -1,7 +1,10 @@ package cli import ( + "cosmossdk.io/core/address" "fmt" + appparams "github.com/babylonchain/babylon/app/params" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "os" "path/filepath" "strconv" @@ -19,12 +22,6 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/types" ) -//nolint:unused -const ( - flagPacketTimeoutTimestamp = "packet-timeout-timestamp" - listSeparator = "," -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -36,7 +33,7 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand(CmdTxAddBlsSig()) - cmd.AddCommand(CmdWrappedCreateValidator()) + cmd.AddCommand(CmdWrappedCreateValidator(authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr))) return cmd } @@ -57,7 +54,7 @@ func CmdTxAddBlsSig() *cobra.Command { return err } - lch, err := types.NewLastCommitHashFromHex(args[1]) + appHash, err := types.NewAppHashFromHex(args[1]) if err != nil { return err } @@ -72,7 +69,7 @@ func CmdTxAddBlsSig() *cobra.Command { return err } - msg := types.NewMsgAddBlsSig(clientCtx.GetFromAddress(), epoch_num, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(clientCtx.GetFromAddress(), epoch_num, appHash, blsSig, addr) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, @@ -83,15 +80,14 @@ func CmdTxAddBlsSig() *cobra.Command { return cmd } -func CmdWrappedCreateValidator() *cobra.Command { - cmd := cosmoscli.NewCreateValidatorCmd() - cmd.Long = strings.TrimSpace( - string(`create-validator will create a new validator initialized +func CmdWrappedCreateValidator(valAddrCodec address.Codec) *cobra.Command { + cmd := cosmoscli.NewCreateValidatorCmd(valAddrCodec) + cmd.Long = strings.TrimSpace(`create-validator will create a new validator initialized with a self-delegation to it using the BLS key generated for the validator (e.g., via babylond create-bls-key). This command creates a MsgWrappedCreateValidator message which is a wrapper of cosmos-sdk's create validator with a pair of BLS key. The BLS key should exist in priv_validator_key.json -before running the command (e.g., via babylond create-bls-key).`)) +before running the command (e.g., via babylond create-bls-key).`) cmd.RunE = func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { @@ -99,14 +95,18 @@ before running the command (e.g., via babylond create-bls-key).`)) } txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + val, err := parseAndValidateValidatorJSON(clientCtx.Codec, args[0]) if err != nil { return err } txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) - txf, msg, err := buildWrappedCreateValidatorMsg(clientCtx, txf, cmd.Flags()) + txf, msg, err := buildWrappedCreateValidatorMsg(clientCtx, txf, cmd.Flags(), val, valAddrCodec) if err != nil { return err } diff --git a/x/checkpointing/client/cli/tx_test.go b/x/checkpointing/client/cli/tx_test.go index 98452e3bd..03aeb9f8f 100644 --- a/x/checkpointing/client/cli/tx_test.go +++ b/x/checkpointing/client/cli/tx_test.go @@ -1,58 +1,54 @@ package cli_test import ( - "bytes" "context" + sdkmath "cosmossdk.io/math" "fmt" + "github.com/babylonchain/babylon/app" + "github.com/cosmos/cosmos-sdk/testutil" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "io" "path/filepath" "testing" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/testutil/mock" - "github.com/golang/mock/gomock" - - "github.com/cosmos/gogoproto/proto" "github.com/stretchr/testify/suite" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking/client/cli" - abci "github.com/cometbft/cometbft/abci/types" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmbytes "github.com/cometbft/cometbft/libs/bytes" tmos "github.com/cometbft/cometbft/libs/os" rpcclient "github.com/cometbft/cometbft/rpc/client" rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" coretypes "github.com/cometbft/cometbft/rpc/core/types" tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/privval" testutilcli "github.com/babylonchain/babylon/testutil/cli" checkpointcli "github.com/babylonchain/babylon/x/checkpointing/client/cli" ) -type mockTendermintRPC struct { +type mockCometRPC struct { rpcclientmock.Client responseQuery abci.ResponseQuery } -func newMockTendermintRPC(respQuery abci.ResponseQuery) mockTendermintRPC { - return mockTendermintRPC{responseQuery: respQuery} +func newMockCometRPC(respQuery abci.ResponseQuery) mockCometRPC { + return mockCometRPC{responseQuery: respQuery} } -func (mockTendermintRPC) BroadcastTxSync(_ context.Context, _ tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { +func (mockCometRPC) BroadcastTxSync(_ context.Context, _ tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { return &coretypes.ResultBroadcastTx{}, nil } -func (m mockTendermintRPC) ABCIQueryWithOptions( +func (m mockCometRPC) ABCIQueryWithOptions( _ context.Context, _ string, _ tmbytes.HexBytes, _ rpcclient.ABCIQueryOptions, @@ -64,7 +60,7 @@ type CLITestSuite struct { suite.Suite kr keyring.Keyring - encCfg params.EncodingConfig + encCfg *params.EncodingConfig baseCtx client.Context clientCtx client.Context addrs []sdk.AccAddress @@ -72,46 +68,43 @@ type CLITestSuite struct { func (s *CLITestSuite) SetupSuite() { s.encCfg = app.GetEncodingConfig() - s.kr = keyring.NewInMemory(s.encCfg.Marshaler) - ctrl := gomock.NewController(s.T()) - mockAccountRetriever := mock.NewMockAccountRetriever(ctrl) - mockAccountRetriever.EXPECT().EnsureExists(gomock.Any(), gomock.Any()).Return(nil) - mockAccountRetriever.EXPECT().GetAccountNumberSequence(gomock.Any(), gomock.Any()).Return(uint64(0), uint64(0), nil) + s.kr = keyring.NewInMemory(s.encCfg.Codec) s.baseCtx = client.Context{}. WithKeyring(s.kr). WithTxConfig(s.encCfg.TxConfig). - WithCodec(s.encCfg.Marshaler). - WithClient(mockTendermintRPC{Client: rpcclientmock.Client{}}). - WithAccountRetriever(mockAccountRetriever). + WithCodec(s.encCfg.Codec). + WithClient(mockCometRPC{Client: rpcclientmock.Client{}}). + WithAccountRetriever(client.MockAccountRetriever{}). WithOutput(io.Discard). WithChainID("test-chain") - var outBuf bytes.Buffer ctxGen := func() client.Context { - bz, _ := s.encCfg.Marshaler.Marshal(&sdk.TxResponse{}) - c := newMockTendermintRPC(abci.ResponseQuery{ + bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) + c := newMockCometRPC(abci.ResponseQuery{ Value: bz, }) return s.baseCtx.WithClient(c) } - s.clientCtx = ctxGen().WithOutput(&outBuf) + s.clientCtx = ctxGen() + s.addrs = make([]sdk.AccAddress, 0) for i := 0; i < 3; i++ { - k, _, err := s.clientCtx.Keyring.NewMnemonic(fmt.Sprintf("NewWrappedValidator%v", i), keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + k, _, err := s.clientCtx.Keyring.NewMnemonic("NewValidator", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) s.Require().NoError(err) - pub, _ := k.GetPubKey() + pub, err := k.GetPubKey() + s.Require().NoError(err) newAddr := sdk.AccAddress(pub.Address()) s.addrs = append(s.addrs, newAddr) } } -// test cases copied from https://github.com/cosmos/cosmos-sdk/blob/dabcedce71b43161c8357d051715d0d3a0919883/x/staking/client/cli/tx_test.go#L191 +// test cases copied from https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/x/staking/client/cli/tx_test.go#L163 func (s *CLITestSuite) TestCmdWrappedCreateValidator() { require := s.Require() homeDir := s.T().TempDir() - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() pvKeyFile := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0777) require.NoError(err) @@ -119,103 +112,145 @@ func (s *CLITestSuite) TestCmdWrappedCreateValidator() { err = tmos.EnsureDir(filepath.Dir(pvStateFile), 0777) require.NoError(err) wrappedPV := privval.LoadOrGenWrappedFilePV(pvKeyFile, pvStateFile) - cmd := checkpointcli.CmdWrappedCreateValidator() + cmd := checkpointcli.CmdWrappedCreateValidator(authcodec.NewBech32Codec("cosmosvaloper")) consPrivKey := wrappedPV.GetValPrivKey() - consPubKey, err := cryptocodec.FromTmPubKeyInterface(consPrivKey.PubKey()) + consPubKey, err := cryptocodec.FromCmtPubKeyInterface(consPrivKey.PubKey()) require.NoError(err) consPubKeyBz, err := s.clientCtx.Codec.MarshalInterfaceJSON(consPubKey) require.NoError(err) require.NotNil(consPubKeyBz) + validJSON := fmt.Sprintf(` + { + "pubkey": %s, + "amount": "%dstake", + "moniker": "NewValidator", + "identity": "AFAF00C4", + "website": "https://newvalidator.io", + "security": "contact@newvalidator.io", + "details": "'Hey, I am a new validator. Please delegate!'", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, consPubKeyBz, 100) + validJSONFile := testutil.WriteToNewTempFile(s.T(), validJSON) + defer validJSONFile.Close() + + validJSONWithoutOptionalFields := fmt.Sprintf(` + { + "pubkey": %s, + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, consPubKeyBz, 100) + validJSONWOOptionalFile := testutil.WriteToNewTempFile(s.T(), validJSONWithoutOptionalFields) + defer validJSONWOOptionalFile.Close() + + noAmountJSON := fmt.Sprintf(` + { + "pubkey": %s, + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, consPubKeyBz) + noAmountJSONFile := testutil.WriteToNewTempFile(s.T(), noAmountJSON) + defer noAmountJSONFile.Close() + + noPubKeyJSON := fmt.Sprintf(` + { + "amount": "%dstake", + "moniker": "NewValidator", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noPubKeyJSONFile := testutil.WriteToNewTempFile(s.T(), noPubKeyJSON) + defer noPubKeyJSONFile.Close() + + noMonikerJSON := fmt.Sprintf(` + { + "pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"oWg2ISpLF405Jcm2vXV+2v4fnjodh6aafuIdeoW+rUw="}, + "amount": "%dstake", + "commission-rate": "0.5", + "commission-max-rate": "1.0", + "commission-max-change-rate": "0.1", + "min-self-delegation": "1" + }`, 100) + noMonikerJSONFile := testutil.WriteToNewTempFile(s.T(), noMonikerJSON) + defer noMonikerJSONFile.Close() + testCases := []struct { name string args []string - expectErr bool - expectedCode uint32 - respType proto.Message + expectErrMsg string }{ { "invalid transaction (missing amount)", []string{ - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noAmountJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)).String()), fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir), }, - true, 0, nil, + "must specify amount of coins to bond", }, { "invalid transaction (missing pubkey)", []string{ - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noPubKeyJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)).String()), fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir), }, - true, 0, nil, + "must specify the JSON encoded pubkey", }, { "invalid transaction (missing moniker)", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + noMonikerJSONFile.Name(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)).String()), + fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir), + }, + "must specify the moniker name", + }, + { + "valid transaction with all fields", + []string{ + validJSONFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(10))).String()), fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir), }, - true, 0, nil, + "", }, { - "valid transaction", + "valid transaction without optional fields", []string{ - fmt.Sprintf("--%s=%s", cli.FlagPubKey, consPubKeyBz), - fmt.Sprintf("--%s=%dstake", cli.FlagAmount, 100), - fmt.Sprintf("--%s=NewValidator", cli.FlagMoniker), - fmt.Sprintf("--%s=AFAF00C4", cli.FlagIdentity), - fmt.Sprintf("--%s=https://newvalidator.io", cli.FlagWebsite), - fmt.Sprintf("--%s=contact@newvalidator.io", cli.FlagSecurityContact), - fmt.Sprintf("--%s='Hey, I am a new validator. Please delegate!'", cli.FlagDetails), - fmt.Sprintf("--%s=0.5", cli.FlagCommissionRate), - fmt.Sprintf("--%s=1.0", cli.FlagCommissionMaxRate), - fmt.Sprintf("--%s=0.1", cli.FlagCommissionMaxChangeRate), - fmt.Sprintf("--%s=1", cli.FlagMinSelfDelegation), + validJSONWOOptionalFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(10))).String()), fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir), }, - false, 0, &sdk.TxResponse{}, + "", }, } @@ -223,16 +258,14 @@ func (s *CLITestSuite) TestCmdWrappedCreateValidator() { tc := tc s.Run(tc.name, func() { out, err := testutilcli.ExecTestCLICmd(s.clientCtx, cmd, tc.args) - if tc.expectErr { + if tc.expectErrMsg != "" { require.Error(err) + require.Contains(err.Error(), tc.expectErrMsg) } else { require.NoError(err, "test: %s\noutput: %s", tc.name, out.String()) - err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType) + resp := &sdk.TxResponse{} + err = s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), resp) require.NoError(err, out.String(), "test: %s, output\n:", tc.name, out.String()) - - txResp := tc.respType.(*sdk.TxResponse) - require.Equal(tc.expectedCode, txResp.Code, - "test: %s, output\n:", tc.name, out.String()) } }) } diff --git a/x/checkpointing/client/cli/utils.go b/x/checkpointing/client/cli/utils.go index 6a0f1479e..3495a9c2b 100644 --- a/x/checkpointing/client/cli/utils.go +++ b/x/checkpointing/client/cli/utils.go @@ -1,95 +1,153 @@ package cli import ( + "cosmossdk.io/core/address" + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + "encoding/json" "errors" "fmt" + "github.com/cosmos/cosmos-sdk/codec" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "os" "path/filepath" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" flag "github.com/spf13/pflag" - errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/privval" "github.com/babylonchain/babylon/x/checkpointing/types" - tmconfig "github.com/cometbft/cometbft/config" + cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" cosmoscli "github.com/cosmos/cosmos-sdk/x/staking/client/cli" staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// copied from https://github.com/cosmos/cosmos-sdk/blob/7167371f87ae641012549922a292050562821dce/x/staking/client/cli/tx.go#L340 -func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet) (tx.Factory, *staketypes.MsgCreateValidator, error) { - fAmount, _ := fs.GetString(cosmoscli.FlagAmount) - amount, err := sdk.ParseCoinNormalized(fAmount) +// validator struct to define the fields of the validator +type validator struct { + Amount sdk.Coin + PubKey cryptotypes.PubKey + Moniker string + Identity string + Website string + Security string + Details string + CommissionRates staketypes.CommissionRates + MinSelfDelegation sdkmath.Int +} + +// copied from https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/x/staking/client/cli/utils.go#L20 +func parseAndValidateValidatorJSON(cdc codec.Codec, path string) (validator, error) { + type internalVal struct { + Amount string `json:"amount"` + PubKey json.RawMessage `json:"pubkey"` + Moniker string `json:"moniker"` + Identity string `json:"identity,omitempty"` + Website string `json:"website,omitempty"` + Security string `json:"security,omitempty"` + Details string `json:"details,omitempty"` + CommissionRate string `json:"commission-rate"` + CommissionMaxRate string `json:"commission-max-rate"` + CommissionMaxChange string `json:"commission-max-change-rate"` + MinSelfDelegation string `json:"min-self-delegation"` + } + + contents, err := os.ReadFile(path) if err != nil { - return txf, nil, err + return validator{}, err } - valAddr := clientCtx.GetFromAddress() - pkStr, err := fs.GetString(cosmoscli.FlagPubKey) + var v internalVal + err = json.Unmarshal(contents, &v) if err != nil { - return txf, nil, err + return validator{}, err } - var pk cryptotypes.PubKey - if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(pkStr), &pk); err != nil { - return txf, nil, err + if v.Amount == "" { + return validator{}, fmt.Errorf("must specify amount of coins to bond") + } + amount, err := sdk.ParseCoinNormalized(v.Amount) + if err != nil { + return validator{}, err } - moniker, _ := fs.GetString(cosmoscli.FlagMoniker) - identity, _ := fs.GetString(cosmoscli.FlagIdentity) - website, _ := fs.GetString(cosmoscli.FlagWebsite) - security, _ := fs.GetString(cosmoscli.FlagSecurityContact) - details, _ := fs.GetString(cosmoscli.FlagDetails) - description := staketypes.NewDescription( - moniker, - identity, - website, - security, - details, - ) + if v.PubKey == nil { + return validator{}, fmt.Errorf("must specify the JSON encoded pubkey") + } + var pk cryptotypes.PubKey + if err := cdc.UnmarshalInterfaceJSON(v.PubKey, &pk); err != nil { + return validator{}, err + } - // get the initial validator commission parameters - rateStr, _ := fs.GetString(cosmoscli.FlagCommissionRate) - maxRateStr, _ := fs.GetString(cosmoscli.FlagCommissionMaxRate) - maxChangeRateStr, _ := fs.GetString(cosmoscli.FlagCommissionMaxChangeRate) + if v.Moniker == "" { + return validator{}, fmt.Errorf("must specify the moniker name") + } - commissionRates, err := buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr) + commissionRates, err := buildCommissionRates(v.CommissionRate, v.CommissionMaxRate, v.CommissionMaxChange) if err != nil { - return txf, nil, err + return validator{}, err } - // get the initial validator min self delegation - msbStr, _ := fs.GetString(cosmoscli.FlagMinSelfDelegation) - - minSelfDelegation, ok := sdk.NewIntFromString(msbStr) - if !ok { - return txf, nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer") + if v.MinSelfDelegation == "" { + return validator{}, fmt.Errorf("must specify minimum self delegation") } + minSelfDelegation, ok := sdkmath.NewIntFromString(v.MinSelfDelegation) + if !ok { + return validator{}, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer") + } + + return validator{ + Amount: amount, + PubKey: pk, + Moniker: v.Moniker, + Identity: v.Identity, + Website: v.Website, + Security: v.Security, + Details: v.Details, + CommissionRates: commissionRates, + MinSelfDelegation: minSelfDelegation, + }, nil +} +// copied from https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/x/staking/client/cli/tx.go#L382 +func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet, val validator, valAc address.Codec) (tx.Factory, *staketypes.MsgCreateValidator, error) { + valAddr := clientCtx.GetFromAddress() + + description := staketypes.NewDescription( + val.Moniker, + val.Identity, + val.Website, + val.Security, + val.Details, + ) + + valStr, err := valAc.BytesToString(valAddr) + if err != nil { + return txf, nil, err + } msg, err := staketypes.NewMsgCreateValidator( - sdk.ValAddress(valAddr), pk, amount, description, commissionRates, minSelfDelegation, + valStr, val.PubKey, val.Amount, description, val.CommissionRates, val.MinSelfDelegation, ) if err != nil { return txf, nil, err } - if err := msg.ValidateBasic(); err != nil { + if err := msg.Validate(valAc); err != nil { return txf, nil, err } genOnly, _ := fs.GetBool(flags.FlagGenerateOnly) if genOnly { ip, _ := fs.GetString(cosmoscli.FlagIP) + p2pPort, _ := fs.GetUint(cosmoscli.FlagP2PPort) nodeID, _ := fs.GetString(cosmoscli.FlagNodeID) - if nodeID != "" && ip != "" { - txf = txf.WithMemo(fmt.Sprintf("%s@%s", nodeID, ip)) + if nodeID != "" && ip != "" && p2pPort > 0 { + txf = txf.WithMemo(fmt.Sprintf("%s@%s:%d", nodeID, ip, p2pPort)) } } @@ -97,8 +155,8 @@ func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *fl } // buildWrappedCreateValidatorMsg builds a MsgWrappedCreateValidator that wraps MsgCreateValidator with BLS key -func buildWrappedCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet) (tx.Factory, *types.MsgWrappedCreateValidator, error) { - txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, fs) +func buildWrappedCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet, val validator, valAc address.Codec) (tx.Factory, *types.MsgWrappedCreateValidator, error) { + txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, fs, val, valAc) if err != nil { return txf, nil, err } @@ -119,22 +177,23 @@ func buildWrappedCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs return txf, wrappedMsg, nil } +// Copied from https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/x/staking/client/cli/utils.go#L104 func buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr string) (commission staketypes.CommissionRates, err error) { if rateStr == "" || maxRateStr == "" || maxChangeRateStr == "" { return commission, errors.New("must specify all validator commission parameters") } - rate, err := sdk.NewDecFromStr(rateStr) + rate, err := sdkmath.LegacyNewDecFromStr(rateStr) if err != nil { return commission, err } - maxRate, err := sdk.NewDecFromStr(maxRateStr) + maxRate, err := sdkmath.LegacyNewDecFromStr(maxRateStr) if err != nil { return commission, err } - maxChangeRate, err := sdk.NewDecFromStr(maxChangeRateStr) + maxChangeRate, err := sdkmath.LegacyNewDecFromStr(maxChangeRateStr) if err != nil { return commission, err } @@ -145,7 +204,7 @@ func buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr string) (commiss } func getValKeyFromFile(homeDir string) (*privval.ValidatorKeys, error) { - nodeCfg := tmconfig.DefaultConfig() + nodeCfg := cmtconfig.DefaultConfig() keyPath := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) statePath := filepath.Join(homeDir, nodeCfg.PrivValidatorStateFile()) if !tmos.FileExists(keyPath) { diff --git a/x/checkpointing/client/testutil/cli_test.go b/x/checkpointing/client/testutil/cli_test.go deleted file mode 100644 index 110b2e6a7..000000000 --- a/x/checkpointing/client/testutil/cli_test.go +++ /dev/null @@ -1 +0,0 @@ -package testutil diff --git a/x/checkpointing/client/testutil/suite.go b/x/checkpointing/client/testutil/suite.go deleted file mode 100644 index 110b2e6a7..000000000 --- a/x/checkpointing/client/testutil/suite.go +++ /dev/null @@ -1 +0,0 @@ -package testutil diff --git a/x/checkpointing/exported/exported.go b/x/checkpointing/exported/exported.go deleted file mode 100644 index eabd8aa3d..000000000 --- a/x/checkpointing/exported/exported.go +++ /dev/null @@ -1 +0,0 @@ -package exported diff --git a/x/checkpointing/genesis.go b/x/checkpointing/genesis.go index 60bbca23f..1641e7805 100644 --- a/x/checkpointing/genesis.go +++ b/x/checkpointing/genesis.go @@ -1,19 +1,19 @@ package checkpointing import ( + "context" "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the capability module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { k.SetGenBlsKeys(ctx, genState.GenesisKeys) } // ExportGenesis returns the capability module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() return genesis } diff --git a/x/checkpointing/genesis_test.go b/x/checkpointing/genesis_test.go index 5fdc738c9..ba7e05bac 100644 --- a/x/checkpointing/genesis_test.go +++ b/x/checkpointing/genesis_test.go @@ -14,12 +14,11 @@ import ( simapp "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/x/checkpointing/types" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" ) func TestInitGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) ckptKeeper := app.CheckpointingKeeper valNum := 10 @@ -27,7 +26,7 @@ func TestInitGenesis(t *testing.T) { for i := 0; i < valNum; i++ { valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) require.NoError(t, err) - valPubkey, err := cryptocodec.FromTmPubKeyInterface(valKeys.ValPubkey) + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) require.NoError(t, err) genKey, err := types.NewGenesisKey( sdk.ValAddress(valKeys.ValPubkey.Address()), diff --git a/x/checkpointing/keeper/bls_signer.go b/x/checkpointing/keeper/bls_signer.go index e6d432316..71fcf2f67 100644 --- a/x/checkpointing/keeper/bls_signer.go +++ b/x/checkpointing/keeper/bls_signer.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "time" @@ -20,8 +21,9 @@ type BlsSigner interface { GetBlsPubkey() (bls12381.PublicKey, error) } -// SendBlsSig prepares a BLS signature message and sends it to Tendermint -func (k Keeper) SendBlsSig(ctx sdk.Context, epochNum uint64, lch types.LastCommitHash, valSet epochingtypes.ValidatorSet) error { +// SendBlsSig prepares a BLS signature message and sends it to Comet +func (k Keeper) SendBlsSig(ctx context.Context, epochNum uint64, appHash types.AppHash, valSet epochingtypes.ValidatorSet) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) // get self address addr := k.blsSigner.GetAddress() @@ -33,31 +35,31 @@ func (k Keeper) SendBlsSig(ctx sdk.Context, epochNum uint64, lch types.LastCommi } // get BLS signature by signing - signBytes := types.GetSignBytes(epochNum, lch) + signBytes := types.GetSignBytes(epochNum, appHash) blsSig, err := k.blsSigner.SignMsgWithBls(signBytes) if err != nil { return err } // create MsgAddBlsSig message - msg := types.NewMsgAddBlsSig(k.clientCtx.GetFromAddress(), epochNum, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(k.clientCtx.GetFromAddress(), epochNum, appHash, blsSig, addr) - // keep sending the message to Tendermint until success or timeout + // keep sending the message to Comet until success or timeout // TODO should read the parameters from config file var res *sdk.TxResponse err = retry.Do(1*time.Second, 1*time.Minute, func() error { - res, err = tx.SendMsgToTendermint(k.clientCtx, msg) + res, err = tx.SendMsgToComet(ctx, k.clientCtx, msg) if err != nil { return err } return nil }) if err != nil { - ctx.Logger().Error(fmt.Sprintf("Failed to send the BLS sig tx for epoch %v: %v", epochNum, err)) + sdkCtx.Logger().Error(fmt.Sprintf("Failed to send the BLS sig tx for epoch %v: %v", epochNum, err)) return err } - ctx.Logger().Info(fmt.Sprintf("Successfully sent BLS-sig tx for epoch %d, tx hash: %s, gas used: %d, gas wanted: %d", epochNum, res.TxHash, res.GasUsed, res.GasWanted)) + sdkCtx.Logger().Info(fmt.Sprintf("Successfully sent BLS-sig tx for epoch %d, tx hash: %s, gas used: %d, gas wanted: %d", epochNum, res.TxHash, res.GasUsed, res.GasWanted)) return nil } diff --git a/x/checkpointing/keeper/ckpt_state.go b/x/checkpointing/keeper/ckpt_state.go index afe8093de..3cc30b4e2 100644 --- a/x/checkpointing/keeper/ckpt_state.go +++ b/x/checkpointing/keeper/ckpt_state.go @@ -1,23 +1,26 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) type CheckpointsState struct { cdc codec.BinaryCodec - checkpoints sdk.KVStore + checkpoints storetypes.KVStore } -func (k Keeper) CheckpointsState(ctx sdk.Context) CheckpointsState { +func (k Keeper) CheckpointsState(ctx context.Context) CheckpointsState { // Build the CheckpointsState storage - store := ctx.KVStore(k.storeKey) + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) return CheckpointsState{ cdc: k.cdc, - checkpoints: prefix.NewStore(store, types.CkptsObjectPrefix), + checkpoints: prefix.NewStore(storeAdapter, types.CkptsObjectPrefix), } } diff --git a/x/checkpointing/keeper/genesis_bls.go b/x/checkpointing/keeper/genesis_bls.go index 6983782f6..71c3cab2e 100644 --- a/x/checkpointing/keeper/genesis_bls.go +++ b/x/checkpointing/keeper/genesis_bls.go @@ -1,12 +1,13 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/checkpointing/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // SetGenBlsKeys registers BLS keys with each validator at genesis -func (k Keeper) SetGenBlsKeys(ctx sdk.Context, genKeys []*types.GenesisKey) { +func (k Keeper) SetGenBlsKeys(ctx context.Context, genKeys []*types.GenesisKey) { for _, key := range genKeys { addr, err := sdk.ValAddressFromBech32(key.ValidatorAddress) if err != nil { diff --git a/x/checkpointing/keeper/grpc_query_bls_test.go b/x/checkpointing/keeper/grpc_query_bls_test.go index 322d7eb32..c76784d19 100644 --- a/x/checkpointing/keeper/grpc_query_bls_test.go +++ b/x/checkpointing/keeper/grpc_query_bls_test.go @@ -4,13 +4,14 @@ import ( "math/rand" "testing" + "cosmossdk.io/math" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" ) @@ -25,6 +26,7 @@ func FuzzQueryBLSKeySet(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generated for setup helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) @@ -35,8 +37,6 @@ func FuzzQueryBLSKeySet(f *testing.F) { genesisBLSPubkey, err := ck.GetBlsPubKey(helper.Ctx, genesisVal.Addr) require.NoError(t, err) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) @@ -53,7 +53,8 @@ func FuzzQueryBLSKeySet(f *testing.F) { // add n new validators via MsgWrappedCreateValidator n := r.Intn(3) + 1 - addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(100000000)) + addrs, err := app.AddTestAddrs(helper.App, helper.Ctx, n, math.NewInt(100000000)) + require.NoError(t, err) wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) for i := 0; i < n; i++ { @@ -64,12 +65,10 @@ func FuzzQueryBLSKeySet(f *testing.F) { require.NoError(t, err) } - // EndBlock of block 1 - ctx = helper.EndBlock() - - // go to BeginBlock of block 11, and thus entering epoch 2 + // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = ek.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) diff --git a/x/checkpointing/keeper/grpc_query_checkpoint.go b/x/checkpointing/keeper/grpc_query_checkpoint.go index 94107561e..b0b2c28e8 100644 --- a/x/checkpointing/keeper/grpc_query_checkpoint.go +++ b/x/checkpointing/keeper/grpc_query_checkpoint.go @@ -160,7 +160,7 @@ func (k Keeper) LastCheckpointWithStatus(ctx context.Context, req *types.QueryLa } // GetLastCheckpointedEpoch returns the last epoch number that associates with a checkpoint -func (k Keeper) GetLastCheckpointedEpoch(ctx sdk.Context) (uint64, error) { +func (k Keeper) GetLastCheckpointedEpoch(ctx context.Context) (uint64, error) { curEpoch := k.GetEpoch(ctx).EpochNumber if curEpoch <= 0 { return 0, fmt.Errorf("current epoch should be more than 0") diff --git a/x/checkpointing/keeper/grpc_query_checkpoint_test.go b/x/checkpointing/keeper/grpc_query_checkpoint_test.go index 39a029973..10e21715a 100644 --- a/x/checkpointing/keeper/grpc_query_checkpoint_test.go +++ b/x/checkpointing/keeper/grpc_query_checkpoint_test.go @@ -9,7 +9,6 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/keeper" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/babylonchain/babylon/testutil/mocks" @@ -28,7 +27,6 @@ func FuzzQueryEpoch(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - sdkCtx := sdk.WrapSDKContext(ctx) // test querying a raw checkpoint with epoch number mockCkptWithMeta := datagen.GenRandomRawCheckpointWithMeta(r) @@ -39,13 +37,13 @@ func FuzzQueryEpoch(f *testing.F) { require.NoError(t, err) ckptRequest := types.NewQueryRawCheckpointRequest(mockCkptWithMeta.Ckpt.EpochNum) - ckptResp, err := ckptKeeper.RawCheckpoint(sdkCtx, ckptRequest) + ckptResp, err := ckptKeeper.RawCheckpoint(ctx, ckptRequest) require.NoError(t, err) require.True(t, ckptResp.RawCheckpoint.Equal(mockCkptWithMeta)) // test querying the status of a given epoch number statusRequest := types.NewQueryEpochStatusRequest(mockCkptWithMeta.Ckpt.EpochNum) - statusResp, err := ckptKeeper.EpochStatus(sdkCtx, statusRequest) + statusResp, err := ckptKeeper.EpochStatus(ctx, statusRequest) require.NoError(t, err) require.Equal(t, mockCkptWithMeta.Status, statusResp.Status) }) @@ -56,7 +54,6 @@ func FuzzQueryRawCheckpoints(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - sdkCtx := sdk.WrapSDKContext(ctx) // add a random number of checkpoints checkpoints := datagen.GenRandomSequenceRawCheckpointsWithMeta(r) @@ -74,7 +71,7 @@ func FuzzQueryRawCheckpoints(f *testing.F) { pageLimit := endEpoch - startEpoch + 1 pagination := &query.PageRequest{Key: types.CkptsObjectKey(startEpoch), Limit: pageLimit} - ckptResp, err := ckptKeeper.RawCheckpoints(sdkCtx, &types.QueryRawCheckpointsRequest{Pagination: pagination}) + ckptResp, err := ckptKeeper.RawCheckpoints(ctx, &types.QueryRawCheckpointsRequest{Pagination: pagination}) require.NoError(t, err) require.Equal(t, int(pageLimit), len(ckptResp.RawCheckpoints)) require.Nil(t, ckptResp.Pagination.NextKey) @@ -97,7 +94,6 @@ func FuzzQueryStatusCount(f *testing.F) { ek := mocks.NewMockEpochingKeeper(ctrl) ek.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: tipEpoch + 1}) ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) - sdkCtx := sdk.WrapSDKContext(ctx) expectedCounts := make(map[string]uint64) epochCount := uint64(r.Int63n(int64(tipEpoch))) for e, ckpt := range checkpoints { @@ -117,7 +113,7 @@ func FuzzQueryStatusCount(f *testing.F) { } countRequest := types.NewQueryRecentEpochStatusCountRequest(epochCount) - resp, err := ckptKeeper.RecentEpochStatusCount(sdkCtx, countRequest) + resp, err := ckptKeeper.RecentEpochStatusCount(ctx, countRequest) require.NoError(t, err) require.Equal(t, expectedResp, resp) }) diff --git a/x/checkpointing/keeper/hooks.go b/x/checkpointing/keeper/hooks.go index fa29134d2..4e3adace7 100644 --- a/x/checkpointing/keeper/hooks.go +++ b/x/checkpointing/keeper/hooks.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/checkpointing/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -9,7 +10,7 @@ import ( var _ types.CheckpointingHooks = Keeper{} // AfterBlsKeyRegistered - call hook if registered -func (k Keeper) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error { +func (k Keeper) AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error { if k.hooks != nil { return k.hooks.AfterBlsKeyRegistered(ctx, valAddr) } @@ -17,14 +18,14 @@ func (k Keeper) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) e } // AfterRawCheckpointConfirmed - call hook if the checkpoint is confirmed -func (k Keeper) AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error { +func (k Keeper) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { if k.hooks != nil { return k.hooks.AfterRawCheckpointConfirmed(ctx, epoch) } return nil } -func (k Keeper) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *types.RawCheckpoint) error { +func (k Keeper) AfterRawCheckpointForgotten(ctx context.Context, ckpt *types.RawCheckpoint) error { if k.hooks != nil { return k.hooks.AfterRawCheckpointForgotten(ctx, ckpt) } @@ -32,7 +33,7 @@ func (k Keeper) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *types.RawChec } // AfterRawCheckpointFinalized - call hook if the checkpoint is finalized -func (k Keeper) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error { +func (k Keeper) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { if k.hooks != nil { return k.hooks.AfterRawCheckpointFinalized(ctx, epoch) } @@ -40,7 +41,7 @@ func (k Keeper) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error } // AfterRawCheckpointBlsSigVerified - call hook if the checkpoint's BLS sig is verified -func (k Keeper) AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *types.RawCheckpoint) error { +func (k Keeper) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *types.RawCheckpoint) error { if k.hooks != nil { return k.hooks.AfterRawCheckpointBlsSigVerified(ctx, ckpt) } diff --git a/x/checkpointing/keeper/keeper.go b/x/checkpointing/keeper/keeper.go index 1de85b530..299e9df30 100644 --- a/x/checkpointing/keeper/keeper.go +++ b/x/checkpointing/keeper/keeper.go @@ -1,15 +1,16 @@ package keeper import ( + "context" + corestoretypes "cosmossdk.io/core/store" "errors" "fmt" txformat "github.com/babylonchain/babylon/btctxformatter" - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/babylonchain/babylon/crypto/bls12381" @@ -20,8 +21,7 @@ import ( type ( Keeper struct { cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + storeService corestoretypes.KVStoreService blsSigner BlsSigner epochingKeeper types.EpochingKeeper hooks types.CheckpointingHooks @@ -31,16 +31,14 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, signer BlsSigner, ek types.EpochingKeeper, clientCtx client.Context, ) Keeper { return Keeper{ cdc: cdc, - storeKey: storeKey, - memKey: memKey, + storeService: storeService, blsSigner: signer, epochingKeeper: ek, hooks: nil, @@ -65,8 +63,9 @@ func (k *Keeper) SetHooks(sh types.CheckpointingHooks) *Keeper { // addBlsSig adds a BLS signature to the raw checkpoint and updates the status // if sufficient signatures are accumulated for the epoch. -func (k Keeper) addBlsSig(ctx sdk.Context, sig *types.BlsSig) error { +func (k Keeper) addBlsSig(ctx context.Context, sig *types.BlsSig) error { // assuming stateless checks have done in Antehandler + sdkCtx := sdk.UnwrapSDKContext(ctx) // get raw checkpoint ckptWithMeta, err := k.GetRawCheckpoint(ctx, sig.GetEpochNum()) @@ -79,9 +78,9 @@ func (k Keeper) addBlsSig(ctx sdk.Context, sig *types.BlsSig) error { return nil } - if !sig.LastCommitHash.Equal(*ckptWithMeta.Ckpt.LastCommitHash) { + if !sig.AppHash.Equal(*ckptWithMeta.Ckpt.AppHash) { // processed BlsSig message is for invalid last commit hash - return types.ErrInvalidLastCommitHash + return types.ErrInvalidAppHash } // get signer's address @@ -98,7 +97,7 @@ func (k Keeper) addBlsSig(ctx sdk.Context, sig *types.BlsSig) error { } // verify BLS sig - signBytes := types.GetSignBytes(sig.GetEpochNum(), *sig.LastCommitHash) + signBytes := types.GetSignBytes(sig.GetEpochNum(), *sig.AppHash) ok, err := bls12381.Verify(*sig.BlsSig, signerBlsKey, signBytes) if err != nil { return err @@ -115,16 +114,16 @@ func (k Keeper) addBlsSig(ctx sdk.Context, sig *types.BlsSig) error { if ckptWithMeta.Status == types.Sealed { // emit event - err = ctx.EventManager().EmitTypedEvent( + err = sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointSealed{Checkpoint: ckptWithMeta}, ) if err != nil { - k.Logger(ctx).Error("failed to emit checkpoint sealed event for epoch %v", ckptWithMeta.Ckpt.EpochNum) + k.Logger(sdkCtx).Error("failed to emit checkpoint sealed event for epoch %v", ckptWithMeta.Ckpt.EpochNum) } // record state update of Sealed ckptWithMeta.RecordStateUpdate(ctx, types.Sealed) // log in console - k.Logger(ctx).Info(fmt.Sprintf("Checkpointing: checkpoint for epoch %v is Sealed", ckptWithMeta.Ckpt.EpochNum)) + k.Logger(sdkCtx).Info(fmt.Sprintf("Checkpointing: checkpoint for epoch %v is Sealed", ckptWithMeta.Ckpt.EpochNum)) } // if reaching this line, it means ckptWithMeta is updated, @@ -136,11 +135,11 @@ func (k Keeper) addBlsSig(ctx sdk.Context, sig *types.BlsSig) error { return nil } -func (k Keeper) GetRawCheckpoint(ctx sdk.Context, epochNum uint64) (*types.RawCheckpointWithMeta, error) { +func (k Keeper) GetRawCheckpoint(ctx context.Context, epochNum uint64) (*types.RawCheckpointWithMeta, error) { return k.CheckpointsState(ctx).GetRawCkptWithMeta(epochNum) } -func (k Keeper) GetStatus(ctx sdk.Context, epochNum uint64) (types.CheckpointStatus, error) { +func (k Keeper) GetStatus(ctx context.Context, epochNum uint64) (types.CheckpointStatus, error) { ckptWithMeta, err := k.GetRawCheckpoint(ctx, epochNum) if err != nil { return -1, err @@ -149,18 +148,18 @@ func (k Keeper) GetStatus(ctx sdk.Context, epochNum uint64) (types.CheckpointSta } // AddRawCheckpoint adds a raw checkpoint into the storage -func (k Keeper) AddRawCheckpoint(ctx sdk.Context, ckptWithMeta *types.RawCheckpointWithMeta) error { +func (k Keeper) AddRawCheckpoint(ctx context.Context, ckptWithMeta *types.RawCheckpointWithMeta) error { return k.CheckpointsState(ctx).CreateRawCkptWithMeta(ckptWithMeta) } -func (k Keeper) BuildRawCheckpoint(ctx sdk.Context, epochNum uint64, lch types.LastCommitHash) (*types.RawCheckpointWithMeta, error) { - ckptWithMeta := types.NewCheckpointWithMeta(types.NewCheckpoint(epochNum, lch), types.Accumulating) +func (k Keeper) BuildRawCheckpoint(ctx context.Context, epochNum uint64, appHash types.AppHash) (*types.RawCheckpointWithMeta, error) { + ckptWithMeta := types.NewCheckpointWithMeta(types.NewCheckpoint(epochNum, appHash), types.Accumulating) ckptWithMeta.RecordStateUpdate(ctx, types.Accumulating) // record the state update of Accumulating err := k.AddRawCheckpoint(ctx, ckptWithMeta) if err != nil { return nil, err } - k.Logger(ctx).Info(fmt.Sprintf("Checkpointing: a new raw checkpoint is built for epoch %v", epochNum)) + k.Logger(sdk.UnwrapSDKContext(ctx)).Info(fmt.Sprintf("Checkpointing: a new raw checkpoint is built for epoch %v", epochNum)) return ckptWithMeta, nil } @@ -169,7 +168,7 @@ func (k Keeper) BuildRawCheckpoint(ctx sdk.Context, epochNum uint64, lch types.L // the raw checkpoint and decides whether it is an invalid checkpoint or a // conflicting checkpoint. A conflicting checkpoint indicates the existence // of a fork -func (k Keeper) VerifyCheckpoint(ctx sdk.Context, checkpoint txformat.RawBtcCheckpoint) error { +func (k Keeper) VerifyCheckpoint(ctx context.Context, checkpoint txformat.RawBtcCheckpoint) error { _, err := k.verifyCkptBytes(ctx, &checkpoint) if err != nil { if errors.Is(err, types.ErrConflictingCheckpoint) { @@ -185,7 +184,8 @@ func (k Keeper) VerifyCheckpoint(ctx sdk.Context, checkpoint txformat.RawBtcChec // the raw checkpoint and decides whether it is an invalid checkpoint or a // conflicting checkpoint. A conflicting checkpoint indicates the existence // of a fork -func (k Keeper) verifyCkptBytes(ctx sdk.Context, rawCheckpoint *txformat.RawBtcCheckpoint) (*types.RawCheckpointWithMeta, error) { +func (k Keeper) verifyCkptBytes(ctx context.Context, rawCheckpoint *txformat.RawBtcCheckpoint) (*types.RawCheckpointWithMeta, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) ckpt, err := types.FromBTCCkptToRawCkpt(rawCheckpoint) if err != nil { return nil, fmt.Errorf("failed to decode raw checkpoint from BTC raw checkpoint: %w", err) @@ -229,7 +229,7 @@ func (k Keeper) verifyCkptBytes(ctx sdk.Context, rawCheckpoint *txformat.RawBtcC if sum <= totalPower*1/3 { return nil, types.ErrInvalidRawCheckpoint.Wrap("insufficient voting power") } - msgBytes := types.GetSignBytes(ckpt.GetEpochNum(), *ckpt.LastCommitHash) + msgBytes := types.GetSignBytes(ckpt.GetEpochNum(), *ckpt.AppHash) ok, err := bls12381.VerifyMultiSig(*ckpt.BlsMultiSig, signersPubKeys, msgBytes) if err != nil { return nil, err @@ -244,17 +244,17 @@ func (k Keeper) verifyCkptBytes(ctx sdk.Context, rawCheckpoint *txformat.RawBtcC return nil, fmt.Errorf("failed to record verified checkpoint of epoch %d for monitoring: %w", ckpt.EpochNum, err) } - // now the checkpoint's multi-sig is valid, if the lastcommithash is the + // now the checkpoint's multi-sig is valid, if the AppHash is the // same with that of the local checkpoint, it means it is valid except that // it is signed by a different signer set - if ckptWithMeta.Ckpt.LastCommitHash.Equal(*ckpt.LastCommitHash) { + if ckptWithMeta.Ckpt.AppHash.Equal(*ckpt.AppHash) { return ckptWithMeta, nil } // multi-sig is valid but the quorum is on a different branch, meaning conflicting is observed - k.Logger(ctx).Error(types.ErrConflictingCheckpoint.Wrapf("epoch %v", ckpt.EpochNum).Error()) + k.Logger(sdkCtx).Error(types.ErrConflictingCheckpoint.Wrapf("epoch %v", ckpt.EpochNum).Error()) // report conflicting checkpoint event - err = ctx.EventManager().EmitTypedEvent( + err = sdkCtx.EventManager().EmitTypedEvent( &types.EventConflictingCheckpoint{ ConflictingCheckpoint: ckpt, LocalCheckpoint: ckptWithMeta, @@ -273,63 +273,67 @@ func (k *Keeper) SetEpochingKeeper(ek types.EpochingKeeper) { // SetCheckpointSubmitted sets the status of a checkpoint to SUBMITTED, // and records the associated state update in lifecycle -func (k Keeper) SetCheckpointSubmitted(ctx sdk.Context, epoch uint64) { +func (k Keeper) SetCheckpointSubmitted(ctx context.Context, epoch uint64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) ckpt := k.setCheckpointStatus(ctx, epoch, types.Sealed, types.Submitted) - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointSubmitted{Checkpoint: ckpt}, ) if err != nil { - k.Logger(ctx).Error("failed to emit checkpoint submitted event for epoch %v", ckpt.Ckpt.EpochNum) + k.Logger(sdkCtx).Error("failed to emit checkpoint submitted event for epoch %v", ckpt.Ckpt.EpochNum) } } // SetCheckpointConfirmed sets the status of a checkpoint to CONFIRMED, // and records the associated state update in lifecycle -func (k Keeper) SetCheckpointConfirmed(ctx sdk.Context, epoch uint64) { +func (k Keeper) SetCheckpointConfirmed(ctx context.Context, epoch uint64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) ckpt := k.setCheckpointStatus(ctx, epoch, types.Submitted, types.Confirmed) - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointConfirmed{Checkpoint: ckpt}, ) if err != nil { - k.Logger(ctx).Error("failed to emit checkpoint confirmed event for epoch %v: %v", ckpt.Ckpt.EpochNum, err) + k.Logger(sdkCtx).Error("failed to emit checkpoint confirmed event for epoch %v: %v", ckpt.Ckpt.EpochNum, err) } // invoke hook if err := k.AfterRawCheckpointConfirmed(ctx, epoch); err != nil { - k.Logger(ctx).Error("failed to trigger checkpoint confirmed hook for epoch %v: %v", ckpt.Ckpt.EpochNum, err) + k.Logger(sdkCtx).Error("failed to trigger checkpoint confirmed hook for epoch %v: %v", ckpt.Ckpt.EpochNum, err) } } // SetCheckpointFinalized sets the status of a checkpoint to FINALIZED, // and records the associated state update in lifecycle -func (k Keeper) SetCheckpointFinalized(ctx sdk.Context, epoch uint64) { +func (k Keeper) SetCheckpointFinalized(ctx context.Context, epoch uint64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) ckpt := k.setCheckpointStatus(ctx, epoch, types.Confirmed, types.Finalized) - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointFinalized{Checkpoint: ckpt}, ) if err != nil { - k.Logger(ctx).Error("failed to emit checkpoint finalized event for epoch %v: %v", ckpt.Ckpt.EpochNum, err) + k.Logger(sdkCtx).Error("failed to emit checkpoint finalized event for epoch %v: %v", ckpt.Ckpt.EpochNum, err) } // invoke hook, which is currently subscribed by ZoneConcierge if err := k.AfterRawCheckpointFinalized(ctx, epoch); err != nil { - k.Logger(ctx).Error("failed to trigger checkpoint finalized hook for epoch %v: %v", ckpt.Ckpt.EpochNum, err) + k.Logger(sdkCtx).Error("failed to trigger checkpoint finalized hook for epoch %v: %v", ckpt.Ckpt.EpochNum, err) } } // SetCheckpointForgotten rolls back the status of a checkpoint to Sealed, // and records the associated state update in lifecycle -func (k Keeper) SetCheckpointForgotten(ctx sdk.Context, epoch uint64) { +func (k Keeper) SetCheckpointForgotten(ctx context.Context, epoch uint64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) ckpt := k.setCheckpointStatus(ctx, epoch, types.Submitted, types.Sealed) - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventCheckpointForgotten{Checkpoint: ckpt}, ) if err != nil { - k.Logger(ctx).Error("failed to emit checkpoint forgotten event for epoch %v", ckpt.Ckpt.EpochNum) + k.Logger(sdkCtx).Error("failed to emit checkpoint forgotten event for epoch %v", ckpt.Ckpt.EpochNum) } } // setCheckpointStatus sets a ckptWithMeta to the given state, // and records the state update in its lifecycle -func (k Keeper) setCheckpointStatus(ctx sdk.Context, epoch uint64, from types.CheckpointStatus, to types.CheckpointStatus) *types.RawCheckpointWithMeta { +func (k Keeper) setCheckpointStatus(ctx context.Context, epoch uint64, from types.CheckpointStatus, to types.CheckpointStatus) *types.RawCheckpointWithMeta { ckptWithMeta, err := k.GetRawCheckpoint(ctx, epoch) if err != nil { // TODO: ignore err for now @@ -349,20 +353,20 @@ func (k Keeper) setCheckpointStatus(ctx sdk.Context, epoch uint64, from types.Ch panic("failed to update checkpoint status") } statusChangeMsg := fmt.Sprintf("Checkpointing: checkpoint status for epoch %v successfully changed from %v to %v", epoch, from.String(), to.String()) - k.Logger(ctx).Info(statusChangeMsg) + k.Logger(sdk.UnwrapSDKContext(ctx)).Info(statusChangeMsg) return ckptWithMeta } -func (k Keeper) UpdateCheckpoint(ctx sdk.Context, ckptWithMeta *types.RawCheckpointWithMeta) error { +func (k Keeper) UpdateCheckpoint(ctx context.Context, ckptWithMeta *types.RawCheckpointWithMeta) error { return k.CheckpointsState(ctx).UpdateCheckpoint(ckptWithMeta) } -func (k Keeper) CreateRegistration(ctx sdk.Context, blsPubKey bls12381.PublicKey, valAddr sdk.ValAddress) error { +func (k Keeper) CreateRegistration(ctx context.Context, blsPubKey bls12381.PublicKey, valAddr sdk.ValAddress) error { return k.RegistrationState(ctx).CreateRegistration(blsPubKey, valAddr) } // GetBLSPubKeySet returns the set of BLS public keys in the same order of the validator set for a given epoch -func (k Keeper) GetBLSPubKeySet(ctx sdk.Context, epochNumber uint64) ([]*types.ValidatorWithBlsKey, error) { +func (k Keeper) GetBLSPubKeySet(ctx context.Context, epochNumber uint64) ([]*types.ValidatorWithBlsKey, error) { valset := k.GetValidatorSet(ctx, epochNumber) valWithblsKeys := make([]*types.ValidatorWithBlsKey, len(valset)) for i, val := range valset { @@ -380,18 +384,18 @@ func (k Keeper) GetBLSPubKeySet(ctx sdk.Context, epochNumber uint64) ([]*types.V return valWithblsKeys, nil } -func (k Keeper) GetBlsPubKey(ctx sdk.Context, address sdk.ValAddress) (bls12381.PublicKey, error) { +func (k Keeper) GetBlsPubKey(ctx context.Context, address sdk.ValAddress) (bls12381.PublicKey, error) { return k.RegistrationState(ctx).GetBlsPubKey(address) } -func (k Keeper) GetEpoch(ctx sdk.Context) *epochingtypes.Epoch { +func (k Keeper) GetEpoch(ctx context.Context) *epochingtypes.Epoch { return k.epochingKeeper.GetEpoch(ctx) } -func (k Keeper) GetValidatorSet(ctx sdk.Context, epochNumber uint64) epochingtypes.ValidatorSet { +func (k Keeper) GetValidatorSet(ctx context.Context, epochNumber uint64) epochingtypes.ValidatorSet { return k.epochingKeeper.GetValidatorSet(ctx, epochNumber) } -func (k Keeper) GetTotalVotingPower(ctx sdk.Context, epochNumber uint64) int64 { +func (k Keeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { return k.epochingKeeper.GetTotalVotingPower(ctx, epochNumber) } diff --git a/x/checkpointing/keeper/keeper_test.go b/x/checkpointing/keeper/keeper_test.go index 84c3ec822..c79337a96 100644 --- a/x/checkpointing/keeper/keeper_test.go +++ b/x/checkpointing/keeper/keeper_test.go @@ -53,7 +53,7 @@ func FuzzKeeperAddRawCheckpoint(f *testing.F) { _, err = ckptKeeper.BuildRawCheckpoint( ctx, mockCkptWithMeta.Ckpt.EpochNum, - datagen.GenRandomLastCommitHash(r), + datagen.GenRandomAppHash(r), ) require.Errorf(t, err, "raw checkpoint with the same epoch already exists") }) @@ -181,7 +181,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { localCkptWithMeta.Status = types.Sealed localCkptWithMeta.PowerSum = 10 localCkptWithMeta.Ckpt.Bitmap = bm - msgBytes := types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, *localCkptWithMeta.Ckpt.LastCommitHash) + msgBytes := types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, *localCkptWithMeta.Ckpt.AppHash) sig := bls12381.Sign(blsPrivKey1, msgBytes) localCkptWithMeta.Ckpt.BlsMultiSig = &sig _ = ckptKeeper.AddRawCheckpoint( @@ -193,7 +193,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { rawBtcCheckpoint := makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - localCkptWithMeta.Ckpt.LastCommitHash.MustMarshal(), + localCkptWithMeta.Ckpt.AppHash.MustMarshal(), localCkptWithMeta.Ckpt.Bitmap, localCkptWithMeta.Ckpt.BlsMultiSig.Bytes(), t, @@ -206,7 +206,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { rawBtcCheckpoint = makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - localCkptWithMeta.Ckpt.LastCommitHash.MustMarshal(), + localCkptWithMeta.Ckpt.AppHash.MustMarshal(), localCkptWithMeta.Ckpt.Bitmap, datagen.GenRandomByteArray(r, btctxformatter.BlsSigLength), t, @@ -214,13 +214,13 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { err = ckptKeeper.VerifyCheckpoint(ctx, *rawBtcCheckpoint) require.ErrorIs(t, err, types.ErrInvalidRawCheckpoint) - // 3. check a conflicting checkpoint; signed on a random lastcommithash - conflictLastCommitHash := datagen.GenRandomByteArray(r, btctxformatter.LastCommitHashLength) - msgBytes = types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, conflictLastCommitHash) + // 3. check a conflicting checkpoint; signed on a random AppHash + conflictAppHash := datagen.GenRandomByteArray(r, btctxformatter.AppHashLength) + msgBytes = types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, conflictAppHash) rawBtcCheckpoint = makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - conflictLastCommitHash, + conflictAppHash, localCkptWithMeta.Ckpt.Bitmap, bls12381.Sign(blsPrivKey1, msgBytes), t, @@ -238,7 +238,7 @@ func makeBtcCkptBytes(r *rand.Rand, epoch uint64, lch []byte, bitmap []byte, bls rawBTCCkpt := &btctxformatter.RawBtcCheckpoint{ Epoch: epoch, - LastCommitHash: lch, + AppHash: lch, BitMap: bitmap, SubmitterAddress: address, BlsSig: blsSig, diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index cf68639f3..58041b52e 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -33,17 +33,18 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { // a genesis validator is generate for setup helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) n := r.Intn(3) + 1 - addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(100000000)) + addrs, err := app.AddTestAddrs(helper.App, helper.Ctx, n, math.NewInt(100000000)) + require.NoError(t, err) // add n new validators with zero voting power via MsgWrappedCreateValidator wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) @@ -59,12 +60,10 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { } require.Len(t, ek.GetCurrentEpochMsgs(ctx), n) - // EndBlock of block 1 - ctx = helper.EndBlock() - - // go to BeginBlock of block 11, and thus entering epoch 2 + // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = ek.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) @@ -80,13 +79,14 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { // ensure all validators (not just validators in the val set) have correct bond status // - the 1st validator is bonded // - all the rest are unbonded since they have zero voting power - iterator := helper.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + iterator, err := helper.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + require.NoError(t, err) defer iterator.Close() count := 0 for ; iterator.Valid(); iterator.Next() { valAddr := sdk.ValAddress(iterator.Value()) - val, found := helper.StakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) + val, err := helper.StakingKeeper.GetValidator(ctx, valAddr) + require.NoError(t, err) count++ if count == 1 { require.Equal(t, stakingtypes.Bonded, val.Status) @@ -108,24 +108,25 @@ func FuzzWrappedCreateValidator_InsufficientBalance(f *testing.F) { // a genesis validator is generate for setup helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) n := r.Intn(3) + 1 balance := r.Int63n(100) - addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(balance)) + addrs, err := app.AddTestAddrs(helper.App, helper.Ctx, n, math.NewInt(balance)) + require.NoError(t, err) // add n new validators with value more than the delegator balance via MsgWrappedCreateValidator wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) for i := 0; i < n; i++ { // make sure the value is more than the balance - value := sdk.NewInt(balance).Add(sdk.NewInt(r.Int63n(100))) + value := math.NewInt(balance).Add(math.NewInt(r.Int63n(100))) msg, err := buildMsgWrappedCreateValidatorWithAmount(addrs[i], value) require.NoError(t, err) wcvMsgs[i] = msg @@ -148,18 +149,19 @@ func FuzzWrappedCreateValidator(f *testing.F) { // a genesis validator is generate for setup helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) // add n new validators via MsgWrappedCreateValidator n := r.Intn(3) - addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(100000000)) + addrs, err := app.AddTestAddrs(helper.App, helper.Ctx, n, math.NewInt(100000000)) + require.NoError(t, err) wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) for i := 0; i < n; i++ { @@ -174,12 +176,10 @@ func FuzzWrappedCreateValidator(f *testing.F) { } require.Len(t, ek.GetCurrentEpochMsgs(ctx), n) - // EndBlock of block 1 - ctx = helper.EndBlock() - - // go to BeginBlock of block 11, and thus entering epoch 2 + // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = ek.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) @@ -213,36 +213,39 @@ func FuzzAddBlsSig_NoError(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) + // a genesis validator is generate for setup + helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built for i := uint64(0); i < 2; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + _, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err := ck.GetRawCheckpoint(ctx, endingEpoch) + ckpt, err := ck.GetRawCheckpoint(ctx, endingEpoch) require.NoError(t, err) // add BLS signatures - n := len(helper.ValBlsPrivKeys) + n := len(helper.GenValidators.BlsPrivKeys) totalPower := uint64(ck.GetTotalVotingPower(ctx, endingEpoch)) for i := 0; i < n; i++ { - lch := ctx.BlockHeader().LastCommitHash - blsPrivKey := helper.ValBlsPrivKeys[i].BlsKey - addr := helper.ValBlsPrivKeys[i].Address - signBytes := types.GetSignBytes(endingEpoch, lch) + blsPrivKey := helper.GenValidators.BlsPrivKeys[i] + appHash := ckpt.Ckpt.AppHash.MustMarshal() + addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) + require.NoError(t, err) + signBytes := types.GetSignBytes(endingEpoch, appHash) blsSig := bls12381.Sign(blsPrivKey, signBytes) // create MsgAddBlsSig message - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) _, err = msgServer.AddBlsSig(ctx, msg) require.NoError(t, err) afterCkpt, err := ck.GetRawCheckpoint(ctx, endingEpoch) @@ -263,29 +266,34 @@ func FuzzAddBlsSig_NotInValSet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + var err error helper := testepoching.NewHelperWithValSet(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now + epoch := ek.GetEpoch(ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built for i := uint64(0); i < 2; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err := ck.GetRawCheckpoint(ctx, endingEpoch) + _, err = ck.GetRawCheckpoint(ctx, endingEpoch) require.NoError(t, err) // build BLS sig from a random validator (not in the validator set) - lch := ctx.BlockHeader().LastCommitHash + appHash := ctx.BlockHeader().AppHash blsPrivKey := bls12381.GenPrivKey() valAddr := datagen.GenRandomValidatorAddress() - signBytes := types.GetSignBytes(endingEpoch, lch) + signBytes := types.GetSignBytes(endingEpoch, appHash) blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(valAddr), endingEpoch, lch, blsSig, valAddr) + msg := types.NewMsgAddBlsSig(sdk.AccAddress(valAddr), endingEpoch, appHash, blsSig, valAddr) _, err = msgServer.AddBlsSig(ctx, msg) require.Error(t, err, types.ErrCkptDoesNotExist) @@ -301,71 +309,76 @@ func FuzzAddBlsSig_CkptNotExist(f *testing.F) { r := rand.New(rand.NewSource(seed)) helper := testepoching.NewHelperWithValSet(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) // build BLS signature from a random validator of the validator set - n := len(helper.ValBlsPrivKeys) + n := len(helper.GenValidators.BlsPrivKeys) i := r.Intn(n) - lch := ctx.BlockHeader().LastCommitHash - blsPrivKey := helper.ValBlsPrivKeys[i].BlsKey - addr := helper.ValBlsPrivKeys[i].Address - signBytes := types.GetSignBytes(epoch.EpochNumber-1, lch) + appHash := ctx.BlockHeader().AppHash + blsPrivKey := helper.GenValidators.BlsPrivKeys[i] + addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) + require.NoError(t, err) + signBytes := types.GetSignBytes(epoch.EpochNumber-1, appHash) blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), epoch.EpochNumber-1, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), epoch.EpochNumber-1, appHash, blsSig, addr) // add the BLS signature - _, err := msgServer.AddBlsSig(ctx, msg) + _, err = msgServer.AddBlsSig(ctx, msg) require.Error(t, err, types.ErrCkptDoesNotExist) }) } -// FuzzAddBlsSig_WrongLastCommitHash tests adding BLS signatures via MsgAddBlsSig +// FuzzAddBlsSig_WrongAppHash tests adding BLS signatures via MsgAddBlsSig // in a scenario where the signature is signed over wrong last_commit_hash // 4. a BLS signature is rejected if the signature is invalid -func FuzzAddBlsSig_WrongLastCommitHash(f *testing.F) { +func FuzzAddBlsSig_WrongAppHash(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 4) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + var err error helper := testepoching.NewHelperWithValSet(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) + // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built for i := uint64(0); i < 2; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err := ck.GetRawCheckpoint(ctx, endingEpoch) + _, err = ck.GetRawCheckpoint(ctx, endingEpoch) require.NoError(t, err) // build BLS sig from a random validator - n := len(helper.ValBlsPrivKeys) + n := len(helper.GenValidators.BlsPrivKeys) i := r.Intn(n) // inject random last commit hash - lch := datagen.GenRandomLastCommitHash(r) - blsPrivKey := helper.ValBlsPrivKeys[i].BlsKey - addr := helper.ValBlsPrivKeys[i].Address - signBytes := types.GetSignBytes(endingEpoch, lch) + appHash := datagen.GenRandomAppHash(r) + blsPrivKey := helper.GenValidators.BlsPrivKeys[i] + addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) + require.NoError(t, err) + signBytes := types.GetSignBytes(endingEpoch, appHash) blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) // add the BLS signature _, err = msgServer.AddBlsSig(ctx, msg) - require.Error(t, err, types.ErrInvalidLastCommitHash) + require.Error(t, err, types.ErrInvalidAppHash) }) } @@ -381,27 +394,29 @@ func FuzzAddBlsSig_InvalidSignature(f *testing.F) { ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) + ctx := helper.Ctx // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built for i := uint64(0); i < 2; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + _, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 _, err := ck.GetRawCheckpoint(ctx, endingEpoch) require.NoError(t, err) // build BLS sig from a random validator - n := len(helper.ValBlsPrivKeys) + n := len(helper.GenValidators.BlsPrivKeys) i := r.Intn(n) // inject random last commit hash - lch := ctx.BlockHeader().LastCommitHash - addr := helper.ValBlsPrivKeys[i].Address + appHash := ctx.BlockHeader().AppHash + addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) + require.NoError(t, err) blsSig := datagen.GenRandomBlsMultiSig(r) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, lch, blsSig, addr) + msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) // add the BLS signature message _, err = msgServer.AddBlsSig(ctx, msg) @@ -415,24 +430,24 @@ func buildMsgWrappedCreateValidator(addr sdk.AccAddress) (*types.MsgWrappedCreat } func buildMsgWrappedCreateValidatorWithAmount(addr sdk.AccAddress, bondTokens math.Int) (*types.MsgWrappedCreateValidator, error) { - tmValPrivkey := ed25519.GenPrivKey() + cmtValPrivkey := ed25519.GenPrivKey() bondCoin := sdk.NewCoin(appparams.DefaultBondDenom, bondTokens) description := stakingtypes.NewDescription("foo_moniker", "", "", "", "") - commission := stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission := stakingtypes.NewCommissionRates(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()) - pk, err := codec.FromTmPubKeyInterface(tmValPrivkey.PubKey()) + pk, err := codec.FromCmtPubKeyInterface(cmtValPrivkey.PubKey()) if err != nil { return nil, err } createValidatorMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), pk, bondCoin, description, commission, sdk.OneInt(), + sdk.ValAddress(addr).String(), pk, bondCoin, description, commission, math.OneInt(), ) if err != nil { return nil, err } blsPrivKey := bls12381.GenPrivKey() - pop, err := privval.BuildPoP(tmValPrivkey, blsPrivKey) + pop, err := privval.BuildPoP(cmtValPrivkey, blsPrivKey) if err != nil { return nil, err } diff --git a/x/checkpointing/keeper/registration_state.go b/x/checkpointing/keeper/registration_state.go index fe6038f27..4649439e7 100644 --- a/x/checkpointing/keeper/registration_state.go +++ b/x/checkpointing/keeper/registration_state.go @@ -1,28 +1,31 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) type RegistrationState struct { cdc codec.BinaryCodec // addrToBlsKeys maps validator addresses to BLS public keys - addrToBlsKeys sdk.KVStore + addrToBlsKeys storetypes.KVStore // blsKeysToAddr maps BLS public keys to validator addresses - blsKeysToAddr sdk.KVStore + blsKeysToAddr storetypes.KVStore } -func (k Keeper) RegistrationState(ctx sdk.Context) RegistrationState { +func (k Keeper) RegistrationState(ctx context.Context) RegistrationState { // Build the RegistrationState storage - store := ctx.KVStore(k.storeKey) + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) return RegistrationState{ cdc: k.cdc, - addrToBlsKeys: prefix.NewStore(store, types.AddrToBlsKeyPrefix), - blsKeysToAddr: prefix.NewStore(store, types.BlsKeyToAddrPrefix), + addrToBlsKeys: prefix.NewStore(storeAdapter, types.AddrToBlsKeyPrefix), + blsKeysToAddr: prefix.NewStore(storeAdapter, types.BlsKeyToAddrPrefix), } } diff --git a/x/checkpointing/keeper/val_bls_set.go b/x/checkpointing/keeper/val_bls_set.go index 001d60410..a3ba8df88 100644 --- a/x/checkpointing/keeper/val_bls_set.go +++ b/x/checkpointing/keeper/val_bls_set.go @@ -1,16 +1,18 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "fmt" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // GetValidatorBlsKeySet returns the set of validators of a given epoch with BLS public key // the validators are ordered by their address in ascending order -func (k Keeper) GetValidatorBlsKeySet(ctx sdk.Context, epochNumber uint64) *types.ValidatorWithBlsKeySet { - store := k.valBlsSetStore(ctx, epochNumber) +func (k Keeper) GetValidatorBlsKeySet(ctx context.Context, epochNumber uint64) *types.ValidatorWithBlsKeySet { + store := k.valBlsSetStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) valBlsKeySetBytes := store.Get(epochNumberBytes) valBlsKeySet, err := types.BytesToValidatorBlsKeySet(k.cdc, valBlsKeySetBytes) @@ -20,14 +22,14 @@ func (k Keeper) GetValidatorBlsKeySet(ctx sdk.Context, epochNumber uint64) *type return valBlsKeySet } -func (k Keeper) GetCurrentValidatorBlsKeySet(ctx sdk.Context) *types.ValidatorWithBlsKeySet { +func (k Keeper) GetCurrentValidatorBlsKeySet(ctx context.Context) *types.ValidatorWithBlsKeySet { epochNumber := k.GetEpoch(ctx).EpochNumber return k.GetValidatorBlsKeySet(ctx, epochNumber) } // InitValidatorBLSSet stores the validator set with BLS keys in the beginning of the current epoch // This is called upon BeginBlock -func (k Keeper) InitValidatorBLSSet(ctx sdk.Context) error { +func (k Keeper) InitValidatorBLSSet(ctx context.Context) error { epochNumber := k.GetEpoch(ctx).EpochNumber valset := k.GetValidatorSet(ctx, epochNumber) valBlsSet := &types.ValidatorWithBlsKeySet{ @@ -46,7 +48,7 @@ func (k Keeper) InitValidatorBLSSet(ctx sdk.Context) error { valBlsSet.ValSet[i] = valBls } valBlsSetBytes := types.ValidatorBlsKeySetToBytes(k.cdc, valBlsSet) - store := k.valBlsSetStore(ctx, epochNumber) + store := k.valBlsSetStore(ctx) store.Set(types.ValidatorBlsKeySetKey(epochNumber), valBlsSetBytes) return nil @@ -54,8 +56,8 @@ func (k Keeper) InitValidatorBLSSet(ctx sdk.Context) error { // ClearValidatorSet removes the validator BLS set of a given epoch // TODO: This is called upon the epoch is checkpointed -func (k Keeper) ClearValidatorSet(ctx sdk.Context, epochNumber uint64) { - store := k.valBlsSetStore(ctx, epochNumber) +func (k Keeper) ClearValidatorSet(ctx context.Context, epochNumber uint64) { + store := k.valBlsSetStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) store.Delete(epochNumberBytes) } @@ -64,7 +66,7 @@ func (k Keeper) ClearValidatorSet(ctx sdk.Context, epochNumber uint64) { // prefix: ValidatorBLSSetKey // key: epoch number // value: ValidatorBLSKeySet -func (k Keeper) valBlsSetStore(ctx sdk.Context, epochNumber uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.ValidatorBlsKeySetPrefix) +func (k Keeper) valBlsSetStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.ValidatorBlsKeySetPrefix) } diff --git a/x/checkpointing/keeper/val_bls_set_test.go b/x/checkpointing/keeper/val_bls_set_test.go index 13aaa7939..7853b2b59 100644 --- a/x/checkpointing/keeper/val_bls_set_test.go +++ b/x/checkpointing/keeper/val_bls_set_test.go @@ -4,13 +4,14 @@ import ( "math/rand" "testing" + "cosmossdk.io/math" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -20,6 +21,7 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generated for setup helper := testepoching.NewHelper(t) + ctx := helper.Ctx ek := helper.EpochingKeeper ck := helper.App.CheckpointingKeeper queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) @@ -29,8 +31,7 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { genesisBLSPubkey, err := ck.GetBlsPubKey(helper.Ctx, genesisVal.Addr) require.NoError(t, err) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // epoch 1 right now epoch := ek.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) @@ -42,7 +43,8 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { // add n new validators via MsgWrappedCreateValidator n := r.Intn(10) + 1 - addrs := app.AddTestAddrs(helper.App, helper.Ctx, n, sdk.NewInt(100000000)) + addrs, err := app.AddTestAddrs(helper.App, helper.Ctx, n, math.NewInt(100000000)) + require.NoError(t, err) wcvMsgs := make([]*types.MsgWrappedCreateValidator, n) for i := 0; i < n; i++ { @@ -53,12 +55,10 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { require.NoError(t, err) } - // EndBlock of block 1 - ctx = helper.EndBlock() - - // go to BeginBlock of block 11, and thus entering epoch 2 + // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = ek.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) diff --git a/x/checkpointing/module.go b/x/checkpointing/module.go index 3e9db023e..5e4bf3184 100644 --- a/x/checkpointing/module.go +++ b/x/checkpointing/module.go @@ -2,6 +2,7 @@ package checkpointing import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -22,8 +23,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -98,23 +101,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper - // TODO: add dependencies to staking, slashing and evidence + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -138,14 +134,13 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the capability module's genesis initialization It returns // no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. @@ -158,12 +153,20 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } // EndBlock executes all ABCI EndBlock logic respective to the capability module. It // returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(_ context.Context) ([]abci.ValidatorUpdate, error) { + return []abci.ValidatorUpdate{}, nil +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker } diff --git a/x/checkpointing/module_simulation.go b/x/checkpointing/module_simulation.go index db476aa0d..1303f639b 100644 --- a/x/checkpointing/module_simulation.go +++ b/x/checkpointing/module_simulation.go @@ -1,12 +1,10 @@ package checkpointing import ( - simappparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/testutil/sample" checkpointingsimulation "github.com/babylonchain/babylon/x/checkpointing/simulation" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -16,7 +14,6 @@ import ( var ( _ = sample.AccAddress _ = checkpointingsimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -33,14 +30,15 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&headeroracleGenesis) } +// RegisterStoreDecoder registers a decoder +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { +} + // ProposalContents doesn't return any content functions for governance proposals func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { return nil } -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} - // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { operations := make([]simtypes.WeightedOperation, 0) diff --git a/x/checkpointing/simulation/genesis.go b/x/checkpointing/simulation/genesis.go deleted file mode 100644 index 02eddda2b..000000000 --- a/x/checkpointing/simulation/genesis.go +++ /dev/null @@ -1 +0,0 @@ -package simulation diff --git a/x/checkpointing/simulation/operations.go b/x/checkpointing/simulation/operations.go deleted file mode 100644 index 02eddda2b..000000000 --- a/x/checkpointing/simulation/operations.go +++ /dev/null @@ -1 +0,0 @@ -package simulation diff --git a/x/checkpointing/testckpt/helper.go b/x/checkpointing/testckpt/helper.go index 9f57a1311..c832bc361 100644 --- a/x/checkpointing/testckpt/helper.go +++ b/x/checkpointing/testckpt/helper.go @@ -1,24 +1,22 @@ package testckpt import ( + "context" "testing" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/app" appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" epochingkeeper "github.com/babylonchain/babylon/x/epoching/keeper" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/baseapp" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - proto "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/gogoproto/proto" "github.com/stretchr/testify/require" ) @@ -26,7 +24,7 @@ import ( type Helper struct { t *testing.T - Ctx sdk.Context + Ctx context.Context App *app.BabylonApp CheckpointingKeeper *keeper.Keeper MsgSrvr types.MsgServer @@ -37,67 +35,41 @@ type Helper struct { GenAccs []authtypes.GenesisAccount } -// NewHelper creates the helper for testing the epoching module -func NewHelper(t *testing.T, n int) *Helper { - accs, balances := datagen.GenRandomAccWithBalance(n) - app := app.SetupWithGenesisAccounts(accs, balances...) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - - checkpointingKeeper := app.CheckpointingKeeper - epochingKeeper := app.EpochingKeeper - stakingKeeper := app.StakingKeeper - queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) - types.RegisterQueryServer(queryHelper, checkpointingKeeper) - queryClient := types.NewQueryClient(queryHelper) - msgSrvr := keeper.NewMsgServerImpl(checkpointingKeeper) - - return &Helper{ - t: t, - Ctx: ctx, - App: app, - CheckpointingKeeper: &checkpointingKeeper, - MsgSrvr: msgSrvr, - QueryClient: queryClient, - StakingKeeper: stakingKeeper, - EpochingKeeper: &epochingKeeper, - GenAccs: accs, - } -} - // CreateValidator calls handler to create a new staking validator -func (h *Helper) CreateValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount math.Int, ok bool) { +func (h *Helper) CreateValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount sdkmath.Int) { coin := sdk.NewCoin(appparams.DefaultBondDenom, stakeAmount) - h.createValidator(addr, pk, blsPK, pop, coin, ok) + h.createValidator(addr, pk, blsPK, pop, coin) } // CreateValidatorWithValPower calls handler to create a new staking validator with zero commission -func (h *Helper) CreateValidatorWithValPower(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, valPower int64, ok bool) math.Int { +func (h *Helper) CreateValidatorWithValPower(addr sdk.ValAddress, pk cryptotypes.PubKey, + blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, valPower int64) sdkmath.Int { amount := h.StakingKeeper.TokensFromConsensusPower(h.Ctx, valPower) coin := sdk.NewCoin(appparams.DefaultBondDenom, amount) - h.createValidator(addr, pk, blsPK, pop, coin, ok) + h.createValidator(addr, pk, blsPK, pop, coin) return amount } // CreateValidatorMsg returns a message used to create validator in this service. -func (h *Helper) CreateValidatorMsg(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount math.Int) *types.MsgWrappedCreateValidator { +func (h *Helper) CreateValidatorMsg(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount sdkmath.Int) *types.MsgWrappedCreateValidator { coin := sdk.NewCoin(appparams.DefaultBondDenom, stakeAmount) - msg, err := stakingtypes.NewMsgCreateValidator(addr, pk, coin, stakingtypes.Description{}, ZeroCommission(), sdk.OneInt()) + msg, err := stakingtypes.NewMsgCreateValidator(addr.String(), pk, coin, stakingtypes.Description{}, ZeroCommission(), sdkmath.OneInt()) require.NoError(h.t, err) wmsg, err := types.NewMsgWrappedCreateValidator(msg, blsPK, pop) require.NoError(h.t, err) return wmsg } -func (h *Helper) createValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, coin sdk.Coin, ok bool) { - h.Handle(func(ctx sdk.Context) (proto.Message, error) { +func (h *Helper) createValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, coin sdk.Coin) { + h.Handle(func(ctx context.Context) (proto.Message, error) { return h.CreateValidatorMsg(addr, pk, blsPK, pop, coin.Amount), nil }) } // Handle executes an action function with the Helper's context, wraps the result into an SDK service result, and performs two assertions before returning it -func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Result { +func (h *Helper) Handle(action func(context.Context) (proto.Message, error)) *sdk.Result { res, err := action(h.Ctx) - r, _ := sdk.WrapServiceResult(h.Ctx, res, err) + r, _ := sdk.WrapServiceResult(sdk.UnwrapSDKContext(h.Ctx), res, err) require.NotNil(h.t, r) require.NoError(h.t, err) return r @@ -105,5 +77,5 @@ func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Re // ZeroCommission constructs a commission rates with all zeros. func ZeroCommission() stakingtypes.CommissionRates { - return stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + return stakingtypes.NewCommissionRates(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()) } diff --git a/x/checkpointing/types/checkpoint.pb.go b/x/checkpointing/types/checkpoint.pb.go index 587a53aaa..3ba6609ef 100644 --- a/x/checkpointing/types/checkpoint.pb.go +++ b/x/checkpointing/types/checkpoint.pb.go @@ -73,9 +73,9 @@ func (CheckpointStatus) EnumDescriptor() ([]byte, []int) { type RawCheckpoint struct { // epoch_num defines the epoch number the raw checkpoint is for EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` - // last_commit_hash defines the 'LastCommitHash' that individual BLS sigs are + // app_hash defines the 'AppHash' that individual BLS sigs are // signed on - LastCommitHash *LastCommitHash `protobuf:"bytes,2,opt,name=last_commit_hash,json=lastCommitHash,proto3,customtype=LastCommitHash" json:"last_commit_hash,omitempty"` + AppHash *AppHash `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3,customtype=AppHash" json:"app_hash,omitempty"` // bitmap defines the bitmap that indicates the signers of the BLS multi sig Bitmap []byte `protobuf:"bytes,3,opt,name=bitmap,proto3" json:"bitmap,omitempty"` // bls_multi_sig defines the multi sig that is aggregated from individual BLS @@ -276,9 +276,9 @@ func (m *CheckpointStateUpdate) GetBlockTime() *time.Time { type BlsSig struct { // epoch_num defines the epoch number that the BLS sig is signed on EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` - // last_commit_hash defines the 'LastCommitHash' that the BLS sig is signed on - LastCommitHash *LastCommitHash `protobuf:"bytes,2,opt,name=last_commit_hash,json=lastCommitHash,proto3,customtype=LastCommitHash" json:"last_commit_hash,omitempty"` - BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` + // last_commit_hash defines the 'AppHash' that the BLS sig is signed on + AppHash *AppHash `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3,customtype=AppHash" json:"app_hash,omitempty"` + BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` // can't find cosmos_proto.scalar when compiling due to cosmos v0.45.4 does // not support scalar string signer_address = 4 [(cosmos_proto.scalar) = // "cosmos.AddressString"]; the signer_address defines the address of the @@ -346,55 +346,55 @@ func init() { } var fileDescriptor_73996df9c6aabde4 = []byte{ - // 764 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0x4d, 0x8b, 0xdb, 0x46, - 0x18, 0xc7, 0xad, 0x5d, 0xc5, 0x8d, 0xc7, 0x6b, 0x23, 0x86, 0x6e, 0x31, 0x2e, 0xd8, 0xee, 0x42, - 0xa9, 0x9b, 0x83, 0x84, 0x1d, 0x0a, 0x7d, 0xa5, 0x95, 0x5f, 0xb6, 0x31, 0xb1, 0x9d, 0x45, 0xb2, - 0x5b, 0x08, 0x14, 0x31, 0x92, 0x67, 0x47, 0x83, 0x47, 0x1a, 0xa1, 0x19, 0x25, 0x75, 0x3f, 0x41, - 0xd9, 0x53, 0xbe, 0xc0, 0x42, 0xa1, 0x1f, 0xa5, 0x97, 0x1e, 0x73, 0x2c, 0x39, 0xa4, 0x65, 0xf7, - 0x92, 0xb6, 0x5f, 0xa2, 0x68, 0xe4, 0xbc, 0x38, 0xe9, 0xd2, 0x17, 0xda, 0x9b, 0xe6, 0xef, 0xff, - 0xff, 0x61, 0x9e, 0xdf, 0x33, 0x8f, 0xc1, 0xbb, 0x3e, 0xf2, 0x37, 0x8c, 0xc7, 0x56, 0x10, 0xe2, - 0x60, 0x9d, 0x70, 0x1a, 0x4b, 0x1a, 0x13, 0xeb, 0x5e, 0xef, 0x05, 0xc1, 0x4c, 0x52, 0x2e, 0x39, - 0x6c, 0x6c, 0xad, 0xe6, 0x8e, 0xd5, 0xbc, 0xd7, 0x6b, 0xb6, 0x09, 0xe7, 0x84, 0x61, 0x4b, 0xf9, - 0xfc, 0xec, 0xd4, 0x92, 0x34, 0xc2, 0x42, 0xa2, 0x28, 0x29, 0xa2, 0xcd, 0xd7, 0x09, 0x27, 0x5c, - 0x7d, 0x5a, 0xf9, 0x57, 0xa1, 0x1e, 0xfd, 0xae, 0x81, 0x9a, 0x83, 0xee, 0x0f, 0x9f, 0x95, 0x83, - 0x6f, 0x82, 0x0a, 0x4e, 0x78, 0x10, 0x7a, 0x71, 0x16, 0x35, 0xb4, 0x8e, 0xd6, 0xd5, 0x9d, 0xeb, - 0x4a, 0x98, 0x67, 0x11, 0xfc, 0x18, 0x18, 0x0c, 0x09, 0xe9, 0x05, 0x3c, 0x8a, 0xa8, 0xf4, 0x42, - 0x24, 0xc2, 0xc6, 0x5e, 0x47, 0xeb, 0x1e, 0x0c, 0xe0, 0xa3, 0xc7, 0xed, 0xfa, 0x14, 0x09, 0x39, - 0x54, 0x3f, 0xdd, 0x42, 0x22, 0x74, 0xea, 0x6c, 0xe7, 0x0c, 0xdf, 0x00, 0x65, 0x9f, 0xca, 0x08, - 0x25, 0x8d, 0xfd, 0x3c, 0xe3, 0x6c, 0x4f, 0x10, 0x81, 0x9a, 0xcf, 0x84, 0x17, 0x65, 0x4c, 0x52, - 0x4f, 0x50, 0xd2, 0xd0, 0x55, 0xc9, 0x4f, 0x1e, 0x3d, 0x6e, 0x7f, 0x40, 0xa8, 0x0c, 0x33, 0xdf, - 0x0c, 0x78, 0x64, 0x6d, 0x7b, 0x0f, 0x42, 0x44, 0x63, 0xeb, 0x19, 0xb3, 0x74, 0x93, 0x48, 0x6e, - 0xf9, 0x4c, 0xf4, 0xfa, 0x37, 0xdf, 0xef, 0x99, 0x2e, 0x25, 0x31, 0x92, 0x59, 0x8a, 0x9d, 0xaa, - 0xcf, 0xc4, 0x2c, 0x2f, 0xe9, 0x52, 0xf2, 0xa1, 0xfe, 0xe4, 0xbb, 0xb6, 0x76, 0xf4, 0xeb, 0x1e, - 0x38, 0xdc, 0xe9, 0xf6, 0x4b, 0x2a, 0xc3, 0x19, 0x96, 0x08, 0x7e, 0x04, 0xf4, 0x60, 0x9d, 0x48, - 0xd5, 0x70, 0xb5, 0xff, 0x8e, 0x79, 0x15, 0x67, 0x73, 0x27, 0xee, 0xa8, 0x10, 0x1c, 0x80, 0xb2, - 0x90, 0x48, 0x66, 0x42, 0xb1, 0xa8, 0xf7, 0x6f, 0x5c, 0x1d, 0x7f, 0x9e, 0x75, 0x55, 0xc2, 0xd9, - 0x26, 0xe1, 0x57, 0x20, 0xbf, 0xaf, 0x87, 0x08, 0x49, 0xbd, 0x64, 0x5d, 0x00, 0xfa, 0x77, 0x04, - 0x4e, 0x32, 0x9f, 0xd1, 0xe0, 0x36, 0xde, 0x38, 0x15, 0x9f, 0x09, 0x9b, 0x90, 0xf4, 0x64, 0x9d, - 0x4f, 0x35, 0xe1, 0xf7, 0x71, 0xea, 0x89, 0x2c, 0x52, 0x78, 0x75, 0xe7, 0xba, 0x12, 0xdc, 0x2c, - 0x82, 0x33, 0x50, 0x61, 0xf4, 0x14, 0x07, 0x9b, 0x80, 0xe1, 0xc6, 0xb5, 0xce, 0x7e, 0xb7, 0xda, - 0xb7, 0xfe, 0x6e, 0x0b, 0x78, 0x99, 0xac, 0x90, 0xc4, 0xce, 0xf3, 0x0a, 0x5b, 0xd6, 0x3f, 0x68, - 0xe0, 0xf0, 0x4f, 0xad, 0xf0, 0x33, 0x70, 0x2d, 0x6f, 0x1a, 0x2b, 0xd8, 0xff, 0x8c, 0x56, 0x11, - 0x84, 0x6f, 0x81, 0x03, 0x9f, 0xf1, 0x60, 0xed, 0x85, 0x98, 0x92, 0x50, 0x2a, 0xec, 0x7a, 0x3e, - 0x70, 0x1e, 0xac, 0x6f, 0x29, 0x09, 0x7e, 0x0a, 0x40, 0x61, 0xc9, 0xf7, 0x40, 0xe1, 0xac, 0xf6, - 0x9b, 0x66, 0xb1, 0x24, 0xe6, 0xd3, 0x25, 0x31, 0x17, 0x4f, 0x97, 0x64, 0xa0, 0x3f, 0xf8, 0xb9, - 0xad, 0xe5, 0xc4, 0x78, 0xb0, 0xce, 0xd5, 0x6d, 0x17, 0x4f, 0x34, 0x50, 0x1e, 0x30, 0xe1, 0x52, - 0xf2, 0x7f, 0x2e, 0xc6, 0x17, 0xe0, 0xb5, 0x7c, 0xf8, 0xf9, 0xd3, 0xdf, 0xff, 0x2f, 0x9e, 0x7e, - 0xd9, 0x2f, 0xae, 0xfc, 0x36, 0xa8, 0x0b, 0x4a, 0x62, 0x9c, 0x7a, 0x68, 0xb5, 0x4a, 0xb1, 0x10, - 0x6a, 0xf4, 0x15, 0xa7, 0x56, 0xa8, 0x76, 0x21, 0xaa, 0x56, 0x4b, 0x37, 0x7e, 0xd3, 0x80, 0xf1, - 0x32, 0x70, 0x68, 0x82, 0xc6, 0xf0, 0xf6, 0xc9, 0xc2, 0x73, 0x17, 0xf6, 0x62, 0xe9, 0x7a, 0xf6, - 0x70, 0xb8, 0x9c, 0x2d, 0xa7, 0xf6, 0x62, 0x32, 0xff, 0xdc, 0x28, 0x35, 0x8d, 0xb3, 0xf3, 0xce, - 0x81, 0x1d, 0x04, 0x59, 0x94, 0x31, 0x94, 0x0f, 0x0d, 0x1e, 0x01, 0xf8, 0xa2, 0xdf, 0x1d, 0xdb, - 0xd3, 0xf1, 0xc8, 0xd0, 0x9a, 0xe0, 0xec, 0xbc, 0x53, 0x76, 0x31, 0x62, 0x78, 0x05, 0xbb, 0xe0, - 0x70, 0xc7, 0xb3, 0x1c, 0xcc, 0x26, 0x8b, 0xc5, 0x78, 0x64, 0xec, 0x35, 0x6b, 0x67, 0xe7, 0x9d, - 0x8a, 0x9b, 0xf9, 0x11, 0x95, 0xf2, 0x55, 0xe7, 0xf0, 0xce, 0xfc, 0x78, 0xe2, 0xcc, 0xc6, 0x23, - 0x63, 0xbf, 0x70, 0x0e, 0x79, 0x7c, 0x4a, 0xd3, 0xe8, 0x55, 0xe7, 0xf1, 0x64, 0x6e, 0x4f, 0x27, - 0x77, 0xc7, 0x23, 0x43, 0x2f, 0x9c, 0xc7, 0x34, 0x46, 0x8c, 0x7e, 0x83, 0x57, 0x4d, 0xfd, 0xdb, - 0xef, 0x5b, 0xa5, 0xc1, 0x9d, 0x1f, 0x2f, 0x5a, 0xda, 0xc3, 0x8b, 0x96, 0xf6, 0xcb, 0x45, 0x4b, - 0x7b, 0x70, 0xd9, 0x2a, 0x3d, 0xbc, 0x6c, 0x95, 0x7e, 0xba, 0x6c, 0x95, 0xee, 0xbe, 0xf7, 0x57, - 0xd8, 0xbf, 0x7e, 0xe9, 0x7f, 0x5a, 0x6e, 0x12, 0x2c, 0xfc, 0xb2, 0x7a, 0x53, 0x37, 0xff, 0x08, - 0x00, 0x00, 0xff, 0xff, 0xad, 0xde, 0x97, 0x7b, 0xcd, 0x05, 0x00, 0x00, + // 755 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x5d, 0x6b, 0xe3, 0x46, + 0x14, 0xb5, 0x62, 0xad, 0x13, 0x8f, 0xe3, 0xc5, 0x0c, 0x4d, 0x31, 0x2e, 0xd8, 0x6e, 0xa0, 0xad, + 0xbb, 0x0f, 0x12, 0xf6, 0x52, 0xe8, 0x07, 0xa5, 0x95, 0x3f, 0xd2, 0x35, 0x1b, 0x7b, 0x83, 0x64, + 0xb7, 0xb0, 0x50, 0xc4, 0x48, 0x9e, 0x8c, 0x06, 0x4b, 0x1a, 0xa1, 0x19, 0x6d, 0xea, 0xfe, 0x82, + 0x92, 0xa7, 0xfc, 0x81, 0x40, 0xa1, 0x3f, 0xa5, 0x2f, 0x7d, 0x0c, 0x85, 0x42, 0xc9, 0x43, 0x5a, + 0x92, 0x97, 0xb6, 0xbf, 0xa2, 0x68, 0xe4, 0x7c, 0x38, 0x69, 0x68, 0xbb, 0xe4, 0xed, 0xea, 0x70, + 0xce, 0x65, 0xce, 0xb9, 0xf7, 0x0a, 0xbc, 0xef, 0x20, 0x67, 0xe1, 0xb3, 0x50, 0x77, 0x3d, 0xec, + 0xce, 0x23, 0x46, 0x43, 0x41, 0x43, 0xa2, 0xbf, 0x6a, 0xdf, 0x00, 0xb4, 0x28, 0x66, 0x82, 0xc1, + 0xea, 0x92, 0xaa, 0xad, 0x50, 0xb5, 0x57, 0xed, 0x5a, 0x83, 0x30, 0x46, 0x7c, 0xac, 0x4b, 0x9e, + 0x93, 0xec, 0xeb, 0x82, 0x06, 0x98, 0x0b, 0x14, 0x44, 0x99, 0xb4, 0xf6, 0x06, 0x61, 0x84, 0xc9, + 0x52, 0x4f, 0xab, 0x0c, 0xdd, 0xfe, 0x45, 0x01, 0x65, 0x13, 0x1d, 0xf4, 0xae, 0xda, 0xc1, 0xb7, + 0x40, 0x11, 0x47, 0xcc, 0xf5, 0xec, 0x30, 0x09, 0xaa, 0x4a, 0x53, 0x69, 0xa9, 0xe6, 0x86, 0x04, + 0xc6, 0x49, 0x00, 0xdf, 0x05, 0x1b, 0x28, 0x8a, 0x6c, 0x0f, 0x71, 0xaf, 0xba, 0xd6, 0x54, 0x5a, + 0x9b, 0xdd, 0xd2, 0xe9, 0x59, 0x63, 0xdd, 0x88, 0xa2, 0x67, 0x88, 0x7b, 0xe6, 0x3a, 0xca, 0x0a, + 0xf8, 0x26, 0x28, 0x38, 0x54, 0x04, 0x28, 0xaa, 0xe6, 0x53, 0x96, 0xb9, 0xfc, 0x82, 0x08, 0x94, + 0x1d, 0x9f, 0xdb, 0x41, 0xe2, 0x0b, 0x6a, 0x73, 0x4a, 0xaa, 0xaa, 0x6c, 0xf2, 0xe9, 0xe9, 0x59, + 0xe3, 0x23, 0x42, 0x85, 0x97, 0x38, 0x9a, 0xcb, 0x02, 0x7d, 0xe9, 0xd2, 0xf5, 0x10, 0x0d, 0xf5, + 0xab, 0x74, 0xe2, 0x45, 0x24, 0x98, 0xee, 0xf8, 0xbc, 0xdd, 0x79, 0xfa, 0x61, 0x5b, 0xb3, 0x28, + 0x09, 0x91, 0x48, 0x62, 0x6c, 0x96, 0x1c, 0x9f, 0x8f, 0xd2, 0x96, 0x16, 0x25, 0x1f, 0xab, 0x7f, + 0x7c, 0xdf, 0x50, 0xb6, 0xff, 0x5c, 0x03, 0x5b, 0x2b, 0xbe, 0xbe, 0xa2, 0xc2, 0x1b, 0x61, 0x81, + 0xe0, 0x27, 0x40, 0x75, 0xe7, 0x91, 0x90, 0xd6, 0x4a, 0x9d, 0xf7, 0xb4, 0xfb, 0x12, 0xd5, 0x56, + 0xe4, 0xa6, 0x14, 0xc1, 0x2e, 0x28, 0x70, 0x81, 0x44, 0xc2, 0xa5, 0xfb, 0xc7, 0x9d, 0x27, 0xf7, + 0xcb, 0xaf, 0xb5, 0x96, 0x54, 0x98, 0x4b, 0x25, 0xfc, 0x1a, 0xa4, 0xef, 0xb5, 0x11, 0x21, 0xb1, + 0x1d, 0xcd, 0xb3, 0x80, 0x5e, 0x2f, 0x81, 0xbd, 0xc4, 0xf1, 0xa9, 0xfb, 0x1c, 0x2f, 0xcc, 0xa2, + 0xe3, 0x73, 0x83, 0x90, 0x78, 0x6f, 0x9e, 0xce, 0x2f, 0x62, 0x07, 0x38, 0xb6, 0x79, 0x12, 0xc8, + 0x78, 0x55, 0x73, 0x43, 0x02, 0x56, 0x12, 0xc0, 0x11, 0x28, 0xfa, 0x74, 0x1f, 0xbb, 0x0b, 0xd7, + 0xc7, 0xd5, 0x47, 0xcd, 0x7c, 0xab, 0xd4, 0xd1, 0xff, 0xab, 0x05, 0x3c, 0x8d, 0x66, 0x48, 0x60, + 0xf3, 0xba, 0xc3, 0x32, 0xeb, 0x1f, 0x15, 0xb0, 0xf5, 0x8f, 0x54, 0xf8, 0x39, 0x78, 0x94, 0x9a, + 0xc6, 0x32, 0xec, 0xff, 0x97, 0x56, 0x26, 0x84, 0x6f, 0x83, 0x4d, 0xc7, 0x67, 0xee, 0xdc, 0xf6, + 0x30, 0x25, 0x9e, 0x90, 0xb1, 0xab, 0xe9, 0xc0, 0x99, 0x3b, 0x7f, 0x26, 0x21, 0xf8, 0x19, 0x00, + 0x19, 0x25, 0xdd, 0x78, 0x19, 0x67, 0xa9, 0x53, 0xd3, 0xb2, 0x73, 0xd0, 0x2e, 0xcf, 0x41, 0x9b, + 0x5c, 0x9e, 0x43, 0x57, 0x3d, 0xfa, 0xad, 0xa1, 0xa4, 0x89, 0x31, 0x77, 0x9e, 0xa2, 0x4b, 0x17, + 0x3f, 0x2b, 0xa0, 0xd0, 0xf5, 0xb9, 0x45, 0xc9, 0xc3, 0x9c, 0xc0, 0x97, 0x60, 0x3d, 0x1d, 0x73, + 0xba, 0xe4, 0xf9, 0x87, 0x58, 0xf2, 0x82, 0x93, 0x3d, 0xee, 0x1d, 0xf0, 0x98, 0x53, 0x12, 0xe2, + 0xd8, 0x46, 0xb3, 0x59, 0x8c, 0x39, 0x97, 0x43, 0x2e, 0x9a, 0xe5, 0x0c, 0x35, 0x32, 0x50, 0x9a, + 0xca, 0x3d, 0xf9, 0x4b, 0x01, 0x95, 0xdb, 0xd1, 0x42, 0x0d, 0x54, 0x7b, 0xcf, 0xf7, 0x26, 0xb6, + 0x35, 0x31, 0x26, 0x53, 0xcb, 0x36, 0x7a, 0xbd, 0xe9, 0x68, 0xba, 0x6b, 0x4c, 0x86, 0xe3, 0x2f, + 0x2a, 0xb9, 0x5a, 0xe5, 0xf0, 0xb8, 0xb9, 0x69, 0xb8, 0x6e, 0x12, 0x24, 0x3e, 0x4a, 0xc7, 0x03, + 0xb7, 0x01, 0xbc, 0xc9, 0xb7, 0x06, 0xc6, 0xee, 0xa0, 0x5f, 0x51, 0x6a, 0xe0, 0xf0, 0xb8, 0x59, + 0xb0, 0x30, 0xf2, 0xf1, 0x0c, 0xb6, 0xc0, 0xd6, 0x0a, 0x67, 0xda, 0x1d, 0x0d, 0x27, 0x93, 0x41, + 0xbf, 0xb2, 0x56, 0x2b, 0x1f, 0x1e, 0x37, 0x8b, 0x56, 0xe2, 0x04, 0x54, 0x88, 0xbb, 0xcc, 0xde, + 0x8b, 0xf1, 0xce, 0xd0, 0x1c, 0x0d, 0xfa, 0x95, 0x7c, 0xc6, 0xec, 0xb1, 0x70, 0x9f, 0xc6, 0xc1, + 0x5d, 0xe6, 0xce, 0x70, 0x6c, 0xec, 0x0e, 0x5f, 0x0e, 0xfa, 0x15, 0x35, 0x63, 0xee, 0xd0, 0x10, + 0xf9, 0xf4, 0x5b, 0x3c, 0xab, 0xa9, 0xdf, 0xfd, 0x50, 0xcf, 0x75, 0x5f, 0xfc, 0x74, 0x5e, 0x57, + 0x4e, 0xce, 0xeb, 0xca, 0xef, 0xe7, 0x75, 0xe5, 0xe8, 0xa2, 0x9e, 0x3b, 0xb9, 0xa8, 0xe7, 0x7e, + 0xbd, 0xa8, 0xe7, 0x5e, 0x7e, 0xf0, 0x6f, 0xb1, 0x7f, 0x73, 0xeb, 0xdf, 0x2b, 0x16, 0x11, 0xe6, + 0x4e, 0x41, 0x6e, 0xcf, 0xd3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9c, 0x61, 0x57, 0xf2, 0xa1, + 0x05, 0x00, 0x00, } func (this *RawCheckpoint) Equal(that interface{}) bool { @@ -419,11 +419,11 @@ func (this *RawCheckpoint) Equal(that interface{}) bool { if this.EpochNum != that1.EpochNum { return false } - if that1.LastCommitHash == nil { - if this.LastCommitHash != nil { + if that1.AppHash == nil { + if this.AppHash != nil { return false } - } else if !this.LastCommitHash.Equal(*that1.LastCommitHash) { + } else if !this.AppHash.Equal(*that1.AppHash) { return false } if !bytes.Equal(this.Bitmap, that1.Bitmap) { @@ -556,11 +556,11 @@ func (m *RawCheckpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - if m.LastCommitHash != nil { + if m.AppHash != nil { { - size := m.LastCommitHash.Size() + size := m.AppHash.Size() i -= size - if _, err := m.LastCommitHash.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.AppHash.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintCheckpoint(dAtA, i, uint64(size)) @@ -729,11 +729,11 @@ func (m *BlsSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - if m.LastCommitHash != nil { + if m.AppHash != nil { { - size := m.LastCommitHash.Size() + size := m.AppHash.Size() i -= size - if _, err := m.LastCommitHash.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.AppHash.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintCheckpoint(dAtA, i, uint64(size)) @@ -769,8 +769,8 @@ func (m *RawCheckpoint) Size() (n int) { if m.EpochNum != 0 { n += 1 + sovCheckpoint(uint64(m.EpochNum)) } - if m.LastCommitHash != nil { - l = m.LastCommitHash.Size() + if m.AppHash != nil { + l = m.AppHash.Size() n += 1 + l + sovCheckpoint(uint64(l)) } l = len(m.Bitmap) @@ -841,8 +841,8 @@ func (m *BlsSig) Size() (n int) { if m.EpochNum != 0 { n += 1 + sovCheckpoint(uint64(m.EpochNum)) } - if m.LastCommitHash != nil { - l = m.LastCommitHash.Size() + if m.AppHash != nil { + l = m.AppHash.Size() n += 1 + l + sovCheckpoint(uint64(l)) } if m.BlsSig != nil { @@ -912,7 +912,7 @@ func (m *RawCheckpoint) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -939,9 +939,9 @@ func (m *RawCheckpoint) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v LastCommitHash - m.LastCommitHash = &v - if err := m.LastCommitHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v AppHash + m.AppHash = &v + if err := m.AppHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1402,7 +1402,7 @@ func (m *BlsSig) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1429,9 +1429,9 @@ func (m *BlsSig) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v LastCommitHash - m.LastCommitHash = &v - if err := m.LastCommitHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v AppHash + m.AppHash = &v + if err := m.AppHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/checkpointing/types/errors.go b/x/checkpointing/types/errors.go index 722b11db7..810ccb8d8 100644 --- a/x/checkpointing/types/errors.go +++ b/x/checkpointing/types/errors.go @@ -17,5 +17,5 @@ var ( ErrBlsPrivKeyDoesNotExist = errorsmod.Register(ModuleName, 1211, "BLS private key does not exist") ErrInvalidBlsSignature = errorsmod.Register(ModuleName, 1212, "BLS signature is invalid") ErrConflictingCheckpoint = errorsmod.Register(ModuleName, 1213, "Conflicting checkpoint is found") - ErrInvalidLastCommitHash = errorsmod.Register(ModuleName, 1214, "Provided last commit hash is Invalid") + ErrInvalidAppHash = errorsmod.Register(ModuleName, 1214, "Provided app hash is Invalid") ) diff --git a/x/checkpointing/types/expected_keepers.go b/x/checkpointing/types/expected_keepers.go index 36eda39a8..609c2f880 100644 --- a/x/checkpointing/types/expected_keepers.go +++ b/x/checkpointing/types/expected_keepers.go @@ -1,31 +1,19 @@ package types import ( + "context" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here -} - // EpochingKeeper defines the expected interface needed to retrieve epoch info type EpochingKeeper interface { - GetEpoch(ctx sdk.Context) *epochingtypes.Epoch - EnqueueMsg(ctx sdk.Context, msg epochingtypes.QueuedMessage) - GetValidatorSet(ctx sdk.Context, epochNumer uint64) epochingtypes.ValidatorSet - GetTotalVotingPower(ctx sdk.Context, epochNumber uint64) int64 - CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCreateValidator) error + GetEpoch(ctx context.Context) *epochingtypes.Epoch + EnqueueMsg(ctx context.Context, msg epochingtypes.QueuedMessage) + GetValidatorSet(ctx context.Context, epochNumer uint64) epochingtypes.ValidatorSet + GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 + CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) error } // Event Hooks @@ -36,9 +24,9 @@ type EpochingKeeper interface { // CheckpointingHooks event hooks for raw checkpoint object (noalias) type CheckpointingHooks interface { - AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error // Must be called when a BLS key is registered - AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error // Must be called when a raw checkpoint is CONFIRMED - AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *RawCheckpoint) error // Must be called when a raw checkpoint is FORGOTTEN - AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error // Must be called when a raw checkpoint is FINALIZED - AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *RawCheckpoint) error // Must be called when a raw checkpoint's multi-sig is verified + AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error // Must be called when a BLS key is registered + AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error // Must be called when a raw checkpoint is CONFIRMED + AfterRawCheckpointForgotten(ctx context.Context, ckpt *RawCheckpoint) error // Must be called when a raw checkpoint is FORGOTTEN + AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error // Must be called when a raw checkpoint is FINALIZED + AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *RawCheckpoint) error // Must be called when a raw checkpoint's multi-sig is verified } diff --git a/x/checkpointing/types/genesis.go b/x/checkpointing/types/genesis.go index 0f00a9af9..1ab34122e 100644 --- a/x/checkpointing/types/genesis.go +++ b/x/checkpointing/types/genesis.go @@ -14,9 +14,6 @@ import ( "github.com/babylonchain/babylon/crypto/bls12381" ) -// DefaultIndex is the default capability global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() *GenesisState { return &GenesisState{} diff --git a/x/checkpointing/types/hooks.go b/x/checkpointing/types/hooks.go index 50a97c406..3d8008870 100644 --- a/x/checkpointing/types/hooks.go +++ b/x/checkpointing/types/hooks.go @@ -1,6 +1,7 @@ package types import ( + "context" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,7 +14,7 @@ func NewMultiCheckpointingHooks(hooks ...CheckpointingHooks) MultiCheckpointingH return hooks } -func (h MultiCheckpointingHooks) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error { +func (h MultiCheckpointingHooks) AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error { for i := range h { if err := h[i].AfterBlsKeyRegistered(ctx, valAddr); err != nil { return err @@ -22,7 +23,7 @@ func (h MultiCheckpointingHooks) AfterBlsKeyRegistered(ctx sdk.Context, valAddr return nil } -func (h MultiCheckpointingHooks) AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error { +func (h MultiCheckpointingHooks) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { for i := range h { if err := h[i].AfterRawCheckpointConfirmed(ctx, epoch); err != nil { return err @@ -31,14 +32,14 @@ func (h MultiCheckpointingHooks) AfterRawCheckpointConfirmed(ctx sdk.Context, ep return nil } -func (h MultiCheckpointingHooks) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *RawCheckpoint) error { +func (h MultiCheckpointingHooks) AfterRawCheckpointForgotten(ctx context.Context, ckpt *RawCheckpoint) error { for i := range h { return h[i].AfterRawCheckpointForgotten(ctx, ckpt) } return nil } -func (h MultiCheckpointingHooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error { +func (h MultiCheckpointingHooks) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { for i := range h { if err := h[i].AfterRawCheckpointFinalized(ctx, epoch); err != nil { return err @@ -47,7 +48,7 @@ func (h MultiCheckpointingHooks) AfterRawCheckpointFinalized(ctx sdk.Context, ep return nil } -func (h MultiCheckpointingHooks) AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *RawCheckpoint) error { +func (h MultiCheckpointingHooks) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *RawCheckpoint) error { for i := range h { if err := h[i].AfterRawCheckpointBlsSigVerified(ctx, ckpt); err != nil { return err diff --git a/x/checkpointing/types/msgs.go b/x/checkpointing/types/msgs.go index 4e82fc200..d824e0e6b 100644 --- a/x/checkpointing/types/msgs.go +++ b/x/checkpointing/types/msgs.go @@ -14,16 +14,17 @@ import ( var ( // Ensure that MsgInsertHeader implements all functions of the Msg interface _ sdk.Msg = (*MsgAddBlsSig)(nil) + _ sdk.Msg = (*MsgWrappedCreateValidator)(nil) ) -func NewMsgAddBlsSig(signer sdk.AccAddress, epochNum uint64, lch LastCommitHash, sig bls12381.Signature, addr sdk.ValAddress) *MsgAddBlsSig { +func NewMsgAddBlsSig(signer sdk.AccAddress, epochNum uint64, appHash AppHash, sig bls12381.Signature, addr sdk.ValAddress) *MsgAddBlsSig { return &MsgAddBlsSig{ Signer: signer.String(), BlsSig: &BlsSig{ - EpochNum: epochNum, - LastCommitHash: &lch, - BlsSig: &sig, - SignerAddress: addr.String(), + EpochNum: epochNum, + AppHash: &appHash, + BlsSig: &sig, + SignerAddress: addr.String(), }, } } @@ -50,7 +51,7 @@ func (m *MsgAddBlsSig) ValidateBasic() error { if err != nil { return err } - err = m.BlsSig.LastCommitHash.ValidateBasic() + err = m.BlsSig.AppHash.ValidateBasic() if err != nil { return err } @@ -78,12 +79,8 @@ func (m *MsgWrappedCreateValidator) ValidateBasic() error { if m.MsgCreateValidator == nil { return errors.New("MsgCreateValidator is nil") } - err := m.MsgCreateValidator.ValidateBasic() - if err != nil { - return err - } var pubKey ed255192.PubKey - err = pubKey.Unmarshal(m.MsgCreateValidator.Pubkey.GetValue()) + err := pubKey.Unmarshal(m.MsgCreateValidator.Pubkey.GetValue()) if err != nil { return err } @@ -95,10 +92,6 @@ func (m *MsgWrappedCreateValidator) ValidateBasic() error { return nil } -func (m *MsgWrappedCreateValidator) GetSigners() []sdk.AccAddress { - return m.MsgCreateValidator.GetSigners() -} - // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces // Needed since msg.MsgCreateValidator.Pubkey is in type Any func (msg MsgWrappedCreateValidator) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { diff --git a/x/checkpointing/types/msgs_test.go b/x/checkpointing/types/msgs_test.go index d92a753e1..f8191cce7 100644 --- a/x/checkpointing/types/msgs_test.go +++ b/x/checkpointing/types/msgs_test.go @@ -3,7 +3,7 @@ package types_test import ( "testing" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/privval" @@ -53,19 +53,19 @@ func TestMsgDecode(t *testing.T) { require.NotNil(t, msgWithType.MsgCreateValidator.Pubkey.GetCachedValue()) } -func buildMsgWrappedCreateValidatorWithAmount(addr sdk.AccAddress, bondTokens math.Int) (*types.MsgWrappedCreateValidator, error) { +func buildMsgWrappedCreateValidatorWithAmount(addr sdk.AccAddress, bondTokens sdkmath.Int) (*types.MsgWrappedCreateValidator, error) { tmValPrivkey := ed25519.GenPrivKey() bondCoin := sdk.NewCoin(appparams.DefaultBondDenom, bondTokens) description := stakingtypes.NewDescription("foo_moniker", "", "", "", "") - commission := stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission := stakingtypes.NewCommissionRates(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()) - pk, err := cryptocodec.FromTmPubKeyInterface(tmValPrivkey.PubKey()) + pk, err := cryptocodec.FromCmtPubKeyInterface(tmValPrivkey.PubKey()) if err != nil { return nil, err } createValidatorMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), pk, bondCoin, description, commission, sdk.OneInt(), + sdk.ValAddress(addr).String(), pk, bondCoin, description, commission, sdkmath.OneInt(), ) if err != nil { return nil, err diff --git a/x/checkpointing/types/pop_test.go b/x/checkpointing/types/pop_test.go index 3beeca1aa..3bd2aec76 100644 --- a/x/checkpointing/types/pop_test.go +++ b/x/checkpointing/types/pop_test.go @@ -14,7 +14,7 @@ func TestProofOfPossession_IsValid(t *testing.T) { blsPrivKey := bls12381.GenPrivKey() pop, err := privval.BuildPoP(valPrivKey, blsPrivKey) require.NoError(t, err) - valpk, err := codec.FromTmPubKeyInterface(valPrivKey.PubKey()) + valpk, err := codec.FromCmtPubKeyInterface(valPrivKey.PubKey()) require.NoError(t, err) require.True(t, pop.IsValid(blsPrivKey.PubKey(), valpk)) } diff --git a/x/checkpointing/types/tx.pb.go b/x/checkpointing/types/tx.pb.go index 83b25f08a..d6d2350fa 100644 --- a/x/checkpointing/types/tx.pb.go +++ b/x/checkpointing/types/tx.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" @@ -197,35 +198,37 @@ func init() { func init() { proto.RegisterFile("babylon/checkpointing/v1/tx.proto", fileDescriptor_6b16c54750152c21) } var fileDescriptor_6b16c54750152c21 = []byte{ - // 447 bytes of a gzipped FileDescriptorProto + // 476 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x93, 0x4f, 0x6b, 0xd4, 0x40, - 0x18, 0xc6, 0x33, 0x16, 0x56, 0x3b, 0x7a, 0x1a, 0x96, 0x92, 0xe6, 0x90, 0xdd, 0xae, 0x50, 0xaa, - 0xe0, 0xc4, 0x6e, 0xf1, 0xa0, 0x9e, 0xba, 0x1e, 0x25, 0x08, 0x29, 0x28, 0x88, 0x10, 0x26, 0xc9, - 0x30, 0x3b, 0xe4, 0xcf, 0x84, 0xbc, 0xe3, 0xd2, 0x7c, 0x00, 0x41, 0x3c, 0xf9, 0x11, 0xfa, 0x21, - 0x04, 0xbf, 0x82, 0xc7, 0xe2, 0xc9, 0xa3, 0xec, 0x5e, 0xfc, 0x06, 0x5e, 0x65, 0x93, 0x09, 0xbb, - 0x56, 0xa3, 0xe2, 0x29, 0x99, 0x37, 0xbf, 0xf7, 0x7d, 0x9e, 0xf7, 0x49, 0x82, 0x0f, 0x22, 0x16, - 0xd5, 0x99, 0x2a, 0xbc, 0x78, 0xce, 0xe3, 0xb4, 0x54, 0xb2, 0xd0, 0xb2, 0x10, 0xde, 0xe2, 0xd8, - 0xd3, 0xe7, 0xb4, 0xac, 0x94, 0x56, 0xc4, 0x36, 0x08, 0xfd, 0x09, 0xa1, 0x8b, 0x63, 0x67, 0x28, - 0x94, 0x50, 0x0d, 0xe4, 0xad, 0xef, 0x5a, 0xde, 0xb9, 0xd3, 0x3b, 0x72, 0x53, 0x30, 0xe8, 0x61, - 0x2f, 0x1a, 0x65, 0x10, 0xa6, 0xbc, 0x36, 0xdc, 0x28, 0x56, 0x90, 0x2b, 0xf0, 0x40, 0xb3, 0xb4, - 0x05, 0x22, 0xae, 0xd9, 0xc6, 0xa3, 0xb3, 0xdf, 0x02, 0x61, 0x6b, 0xa6, 0x3d, 0xb4, 0x8f, 0x26, - 0x6f, 0x10, 0xbe, 0xe5, 0x83, 0x38, 0x4d, 0x92, 0x59, 0x06, 0x67, 0x52, 0x90, 0xfb, 0x78, 0x00, - 0x52, 0x14, 0xbc, 0xb2, 0xd1, 0x18, 0x1d, 0xed, 0xce, 0xec, 0xcf, 0x1f, 0xee, 0x0d, 0x4d, 0xcb, - 0x69, 0x92, 0x54, 0x1c, 0xe0, 0x4c, 0x57, 0xb2, 0x10, 0x81, 0xe1, 0xc8, 0x43, 0x7c, 0x7d, 0xed, - 0x07, 0xa4, 0xb0, 0xaf, 0x8d, 0xd1, 0xd1, 0xcd, 0xe9, 0x98, 0xf6, 0x65, 0x42, 0x5b, 0x91, 0x60, - 0x10, 0x35, 0xd7, 0x47, 0x37, 0xde, 0x5e, 0x8c, 0xac, 0x6f, 0x17, 0x23, 0x6b, 0xb2, 0x87, 0x87, - 0xdb, 0x36, 0x02, 0x0e, 0xa5, 0x2a, 0x80, 0x4f, 0x3e, 0x22, 0xbc, 0xef, 0x83, 0x78, 0x51, 0xb1, - 0xb2, 0xe4, 0xc9, 0x93, 0x8a, 0x33, 0xcd, 0x9f, 0xb3, 0x4c, 0x26, 0x4c, 0xab, 0x8a, 0x4c, 0xf1, - 0x4e, 0xca, 0xeb, 0xc6, 0xe9, 0xdf, 0x64, 0x9f, 0xf2, 0x3a, 0x58, 0xc3, 0xe4, 0x15, 0x1e, 0xe6, - 0x20, 0xc2, 0xb8, 0x19, 0x15, 0x2e, 0xba, 0x59, 0xc6, 0xfb, 0x5d, 0x6a, 0x76, 0x35, 0x61, 0x52, - 0x13, 0x26, 0xf5, 0x41, 0x5c, 0x51, 0x0f, 0x48, 0xfe, 0x4b, 0x6d, 0x6b, 0xa3, 0xdb, 0xf8, 0xa0, - 0xd7, 0x78, 0xb7, 0xde, 0xf4, 0x3b, 0xc2, 0x3b, 0x3e, 0x08, 0x12, 0xe3, 0xdd, 0xcd, 0x2b, 0x38, - 0xec, 0x5f, 0x64, 0x3b, 0x23, 0x87, 0xfe, 0x1b, 0xd7, 0x89, 0x91, 0x77, 0x08, 0xef, 0xf5, 0x04, - 0x79, 0xf2, 0xc7, 0x51, 0xbf, 0x6f, 0x72, 0x1e, 0xff, 0x47, 0x53, 0x67, 0x66, 0xf6, 0xec, 0xd3, - 0xd2, 0x45, 0x97, 0x4b, 0x17, 0x7d, 0x5d, 0xba, 0xe8, 0xfd, 0xca, 0xb5, 0x2e, 0x57, 0xae, 0xf5, - 0x65, 0xe5, 0x5a, 0x2f, 0x1f, 0x08, 0xa9, 0xe7, 0xaf, 0x23, 0x1a, 0xab, 0xdc, 0x33, 0x02, 0xf1, - 0x9c, 0xc9, 0xa2, 0x3b, 0x78, 0xe7, 0x57, 0x7e, 0x08, 0x5d, 0x97, 0x1c, 0xa2, 0x41, 0xf3, 0x41, - 0x9f, 0xfc, 0x08, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xef, 0x32, 0xf7, 0xb4, 0x03, 0x00, 0x00, + 0x18, 0xc6, 0x33, 0x16, 0x57, 0x3a, 0x7a, 0x1a, 0x97, 0xba, 0xcd, 0x21, 0xfb, 0x47, 0x28, 0x75, + 0xc1, 0x89, 0xdd, 0xe2, 0xc1, 0x7a, 0xea, 0x7a, 0x94, 0x45, 0x48, 0x41, 0x41, 0x84, 0x65, 0x92, + 0x0c, 0xb3, 0xc3, 0x26, 0x99, 0x90, 0x77, 0x5c, 0x9a, 0x9b, 0x78, 0x12, 0x4f, 0xfd, 0x08, 0xfd, + 0x08, 0x3d, 0xf8, 0x21, 0x3c, 0x2e, 0x9e, 0x3c, 0xca, 0xee, 0xa1, 0x7e, 0x0a, 0x91, 0x4d, 0x26, + 0xec, 0x5a, 0x1b, 0x15, 0x4f, 0xc9, 0xcc, 0xfc, 0xde, 0xe7, 0x7d, 0x9e, 0x37, 0x19, 0xdc, 0xf5, + 0x99, 0x9f, 0x47, 0x2a, 0x71, 0x83, 0x09, 0x0f, 0xa6, 0xa9, 0x92, 0x89, 0x96, 0x89, 0x70, 0x67, + 0x07, 0xae, 0x3e, 0xa5, 0x69, 0xa6, 0xb4, 0x22, 0x2d, 0x83, 0xd0, 0x5f, 0x10, 0x3a, 0x3b, 0xb0, + 0x9b, 0x42, 0x09, 0x55, 0x40, 0xee, 0xea, 0xad, 0xe4, 0xed, 0x07, 0xb5, 0x92, 0xeb, 0x0d, 0x83, + 0xee, 0xd5, 0xa2, 0x7e, 0x04, 0xe3, 0x29, 0xcf, 0x0d, 0xd7, 0x0e, 0x14, 0xc4, 0x0a, 0x5c, 0xd0, + 0x6c, 0x5a, 0x02, 0x3e, 0xd7, 0x6c, 0xed, 0xd1, 0xde, 0x2d, 0x81, 0x71, 0x69, 0xa6, 0x5c, 0x98, + 0xa3, 0x7b, 0xa6, 0x36, 0x86, 0x42, 0x38, 0x06, 0x51, 0x1e, 0xf4, 0xce, 0x10, 0xbe, 0x33, 0x02, + 0x71, 0x1c, 0x86, 0xc3, 0x08, 0x4e, 0xa4, 0x20, 0x8f, 0x70, 0x03, 0xa4, 0x48, 0x78, 0xd6, 0x42, + 0x1d, 0xb4, 0xbf, 0x3d, 0x6c, 0x7d, 0xf9, 0xf4, 0xb0, 0x69, 0xb4, 0x8e, 0xc3, 0x30, 0xe3, 0x00, + 0x27, 0x3a, 0x93, 0x89, 0xf0, 0x0c, 0x47, 0x9e, 0xe0, 0x5b, 0x2b, 0xa3, 0x20, 0x45, 0xeb, 0x46, + 0x07, 0xed, 0xdf, 0x1e, 0x74, 0x68, 0xdd, 0xb0, 0x68, 0xd9, 0xc4, 0x6b, 0xf8, 0xc5, 0xf3, 0xe8, + 0xee, 0x87, 0xf3, 0xb6, 0xf5, 0xfd, 0xbc, 0x6d, 0xbd, 0xbf, 0xbc, 0xe8, 0x1b, 0xbd, 0xde, 0x0e, + 0x6e, 0x6e, 0x3a, 0xf2, 0x38, 0xa4, 0x2a, 0x01, 0xde, 0x9b, 0x23, 0xbc, 0x3b, 0x02, 0xf1, 0x2a, + 0x63, 0x69, 0xca, 0xc3, 0x67, 0x19, 0x67, 0x9a, 0xbf, 0x64, 0x91, 0x0c, 0x99, 0x56, 0x19, 0x19, + 0xe0, 0xad, 0x29, 0xcf, 0x0b, 0xd3, 0x7f, 0x73, 0xf0, 0x9c, 0xe7, 0xde, 0x0a, 0x26, 0x6f, 0x70, + 0x33, 0x06, 0x31, 0x0e, 0x0a, 0xa9, 0xf1, 0xac, 0xd2, 0x32, 0x31, 0xfa, 0xd4, 0xc4, 0x36, 0x03, + 0xa7, 0x66, 0xe0, 0x74, 0x04, 0xe2, 0x4a, 0x77, 0x8f, 0xc4, 0xbf, 0xed, 0x1d, 0x75, 0x37, 0xc3, + 0x5d, 0xdb, 0xa8, 0x77, 0x1f, 0x77, 0x6b, 0x13, 0x55, 0xb9, 0x07, 0x3f, 0x10, 0xde, 0x1a, 0x81, + 0x20, 0x01, 0xde, 0x5e, 0x7f, 0xa6, 0xbd, 0xfa, 0x84, 0x9b, 0xc3, 0xb3, 0xe9, 0xbf, 0x71, 0x55, + 0x33, 0xf2, 0x11, 0xe1, 0x9d, 0x9a, 0x09, 0x1f, 0xfe, 0x51, 0xea, 0xfa, 0x22, 0xfb, 0xe9, 0x7f, + 0x14, 0x55, 0x66, 0xec, 0x9b, 0xef, 0x2e, 0x2f, 0xfa, 0x68, 0xf8, 0xe2, 0xf3, 0xc2, 0x41, 0xf3, + 0x85, 0x83, 0xbe, 0x2d, 0x1c, 0x74, 0xb6, 0x74, 0xac, 0xf9, 0xd2, 0xb1, 0xbe, 0x2e, 0x1d, 0xeb, + 0xf5, 0x63, 0x21, 0xf5, 0xe4, 0xad, 0x4f, 0x03, 0x15, 0xbb, 0xa6, 0x4f, 0x30, 0x61, 0x32, 0xa9, + 0x16, 0xee, 0xe9, 0x95, 0x4b, 0xa5, 0xf3, 0x94, 0x83, 0xdf, 0x28, 0xfe, 0xfd, 0xc3, 0x9f, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xb7, 0x5a, 0x7e, 0xa6, 0xf8, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/checkpointing/types/types.go b/x/checkpointing/types/types.go index 3f2a26b4a..279c758a2 100644 --- a/x/checkpointing/types/types.go +++ b/x/checkpointing/types/types.go @@ -1,6 +1,7 @@ package types import ( + "context" "crypto/sha256" "encoding/hex" "errors" @@ -18,18 +19,18 @@ const ( BitmapBits = 104 // 104 bits for 104 validators at top ) -type LastCommitHash []byte +type AppHash []byte type BlsSigHash []byte type RawCkptHash []byte -func NewCheckpoint(epochNum uint64, lch LastCommitHash) *RawCheckpoint { +func NewCheckpoint(epochNum uint64, appHash AppHash) *RawCheckpoint { return &RawCheckpoint{ - EpochNum: epochNum, - LastCommitHash: &lch, - Bitmap: bitmap.New(BitmapBits), // 13 bytes, holding 100 validators - BlsMultiSig: nil, + EpochNum: epochNum, + AppHash: &appHash, + Bitmap: bitmap.New(BitmapBits), // 13 bytes, holding 100 validators + BlsMultiSig: nil, } } @@ -110,8 +111,9 @@ func (cm *RawCheckpointWithMeta) IsMoreMatureThanStatus(status CheckpointStatus) // RecordStateUpdate appends a new state update to the raw ckpt with meta // where the time/height are captured by the current ctx -func (cm *RawCheckpointWithMeta) RecordStateUpdate(ctx sdk.Context, status CheckpointStatus) { - height, time := ctx.BlockHeight(), ctx.BlockTime() +func (cm *RawCheckpointWithMeta) RecordStateUpdate(ctx context.Context, status CheckpointStatus) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + height, time := sdkCtx.BlockHeight(), sdkCtx.BlockTime() stateUpdate := &CheckpointStateUpdate{ State: status, BlockHeight: uint64(height), @@ -120,82 +122,78 @@ func (cm *RawCheckpointWithMeta) RecordStateUpdate(ctx sdk.Context, status Check cm.Lifecycle = append(cm.Lifecycle, stateUpdate) } -func NewLastCommitHashFromHex(s string) (LastCommitHash, error) { +func NewAppHashFromHex(s string) (AppHash, error) { bz, err := hex.DecodeString(s) if err != nil { return nil, err } - var lch LastCommitHash + var appHash AppHash - err = lch.Unmarshal(bz) + err = appHash.Unmarshal(bz) if err != nil { return nil, err } - return lch, nil + return appHash, nil } -func (lch *LastCommitHash) Unmarshal(bz []byte) error { +func (appHash *AppHash) Unmarshal(bz []byte) error { if len(bz) != HashSize { - return errors.New("invalid lastCommitHash length") + return errors.New("invalid appHash length") } - *lch = bz + *appHash = bz return nil } -func (lch *LastCommitHash) Size() (n int) { - if lch == nil { +func (appHash *AppHash) Size() (n int) { + if appHash == nil { return 0 } - return len(*lch) + return len(*appHash) } -func (lch *LastCommitHash) Equal(l LastCommitHash) bool { - return lch.String() == l.String() +func (appHash *AppHash) Equal(l AppHash) bool { + return appHash.String() == l.String() } -func (lch *LastCommitHash) String() string { - return hex.EncodeToString(*lch) +func (appHash *AppHash) String() string { + return hex.EncodeToString(*appHash) } -func (lch *LastCommitHash) MustMarshal() []byte { - bz, err := lch.Marshal() +func (appHash *AppHash) MustMarshal() []byte { + bz, err := appHash.Marshal() if err != nil { panic(err) } return bz } -func (lch *LastCommitHash) Marshal() ([]byte, error) { - return *lch, nil +func (appHash *AppHash) Marshal() ([]byte, error) { + return *appHash, nil } -func (lch LastCommitHash) MarshalTo(data []byte) (int, error) { - copy(data, lch) +func (appHash AppHash) MarshalTo(data []byte) (int, error) { + copy(data, appHash) return len(data), nil } -func (lch *LastCommitHash) ValidateBasic() error { - if lch == nil { - return errors.New("invalid lastCommitHash") +func (appHash *AppHash) ValidateBasic() error { + if appHash == nil { + return errors.New("invalid appHash") } - if len(*lch) != HashSize { - return errors.New("invalid lastCommitHash") + if len(*appHash) != HashSize { + return errors.New("invalid appHash") } return nil } -func RawCkptToBytes(cdc codec.BinaryCodec, ckpt *RawCheckpoint) []byte { - return cdc.MustMarshal(ckpt) -} - // ValidateBasic does sanity checks on a raw checkpoint func (ckpt RawCheckpoint) ValidateBasic() error { if ckpt.Bitmap == nil { return ErrInvalidRawCheckpoint.Wrapf("bitmap cannot be empty") } - err := ckpt.LastCommitHash.ValidateBasic() + err := ckpt.AppHash.ValidateBasic() if err != nil { return ErrInvalidRawCheckpoint.Wrapf(err.Error()) } diff --git a/x/checkpointing/types/types_test.go b/x/checkpointing/types/types_test.go index d6d06a1f3..c29abc0e6 100644 --- a/x/checkpointing/types/types_test.go +++ b/x/checkpointing/types/types_test.go @@ -20,7 +20,7 @@ func TestRawCheckpointWithMeta_Accumulate1(t *testing.T) { n := 1 totalPower := int64(10) ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - lch := datagen.GenRandomLastCommitHash(r) + lch := datagen.GenRandomAppHash(r) msg := types.GetSignBytes(epochNum, lch) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, lch) @@ -43,7 +43,7 @@ func TestRawCheckpointWithMeta_Accumulate4(t *testing.T) { n := 4 totalPower := int64(10) * int64(n) ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - lch := datagen.GenRandomLastCommitHash(r) + lch := datagen.GenRandomAppHash(r) msg := types.GetSignBytes(epochNum, lch) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, lch) diff --git a/x/checkpointing/types/utils.go b/x/checkpointing/types/utils.go index 54b8b9dfd..d1a6c80b3 100644 --- a/x/checkpointing/types/utils.go +++ b/x/checkpointing/types/utils.go @@ -14,7 +14,7 @@ import ( func (m BlsSig) Hash() BlsSigHash { fields := [][]byte{ sdk.Uint64ToBigEndian(m.EpochNum), - m.LastCommitHash.MustMarshal(), + m.AppHash.MustMarshal(), m.BlsSig.MustMarshal(), []byte(m.SignerAddress), } @@ -24,7 +24,7 @@ func (m BlsSig) Hash() BlsSigHash { func (m RawCheckpoint) Hash() RawCkptHash { fields := [][]byte{ sdk.Uint64ToBigEndian(m.EpochNum), - m.LastCommitHash.MustMarshal(), + m.AppHash.MustMarshal(), m.BlsMultiSig.MustMarshal(), m.Bitmap, } @@ -38,7 +38,7 @@ func (m RawCheckpoint) HashStr() string { // SignedMsg is the message corresponding to the BLS sig in this raw checkpoint // Its value should be (epoch_number || last_commit_hash) func (m RawCheckpoint) SignedMsg() []byte { - return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.LastCommitHash...) + return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.AppHash...) } func hash(fields [][]byte) []byte { @@ -78,8 +78,8 @@ func FromBTCCkptBytesToRawCkpt(btcCkptBytes []byte) (*RawCheckpoint, error) { } func FromBTCCkptToRawCkpt(btcCkpt *btctxformatter.RawBtcCheckpoint) (*RawCheckpoint, error) { - var lch LastCommitHash - err := lch.Unmarshal(btcCkpt.LastCommitHash) + var appHash AppHash + err := appHash.Unmarshal(btcCkpt.AppHash) if err != nil { return nil, err } @@ -89,17 +89,17 @@ func FromBTCCkptToRawCkpt(btcCkpt *btctxformatter.RawBtcCheckpoint) (*RawCheckpo return nil, err } rawCheckpoint := &RawCheckpoint{ - EpochNum: btcCkpt.Epoch, - LastCommitHash: &lch, - Bitmap: btcCkpt.BitMap, - BlsMultiSig: &blsSig, + EpochNum: btcCkpt.Epoch, + AppHash: &appHash, + Bitmap: btcCkpt.BitMap, + BlsMultiSig: &blsSig, } return rawCheckpoint, nil } func FromRawCkptToBTCCkpt(rawCkpt *RawCheckpoint, address []byte) (*btctxformatter.RawBtcCheckpoint, error) { - lchBytes, err := rawCkpt.LastCommitHash.Marshal() + appHashBytes, err := rawCkpt.AppHash.Marshal() if err != nil { return nil, err } @@ -110,7 +110,7 @@ func FromRawCkptToBTCCkpt(rawCkpt *RawCheckpoint, address []byte) (*btctxformatt btcCkpt := &btctxformatter.RawBtcCheckpoint{ Epoch: rawCkpt.EpochNum, - LastCommitHash: lchBytes, + AppHash: appHashBytes, BitMap: rawCkpt.Bitmap, SubmitterAddress: address, BlsSig: blsSigBytes, diff --git a/x/checkpointing/types/val_bls_set.go b/x/checkpointing/types/val_bls_set.go index 0eac54da1..8db7764e5 100644 --- a/x/checkpointing/types/val_bls_set.go +++ b/x/checkpointing/types/val_bls_set.go @@ -1,7 +1,7 @@ package types import ( - fmt "fmt" + "fmt" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/boljen/go-bitmap" diff --git a/x/epoching/abci.go b/x/epoching/abci.go index a41acd104..4e468d65a 100644 --- a/x/epoching/abci.go +++ b/x/epoching/abci.go @@ -1,6 +1,7 @@ package epoching import ( + "context" "fmt" "time" @@ -24,9 +25,10 @@ import ( // - record the sealer header for the previous epoch // // NOTE: we follow Cosmos SDK's slashing/evidence modules for MVP. No need to modify them at the moment. -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + sdkCtx := sdk.UnwrapSDKContext(ctx) // record the current AppHash k.RecordAppHash(ctx) @@ -45,19 +47,20 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) // trigger AfterEpochBegins hook k.AfterEpochBegins(ctx, incEpoch.EpochNumber) // emit BeginEpoch event - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventBeginEpoch{ EpochNumber: incEpoch.EpochNumber, }, ) if err != nil { - panic(err) + return err } } if epoch.IsSecondBlock(ctx) { k.RecordSealerHeaderForPrevEpoch(ctx) } + return nil } // EndBlocker is called at the end of every block. @@ -66,9 +69,10 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) // - trigger AfterEpochEnds hook // - emit EndEpoch event // NOTE: The epoching module is not responsible for checkpoint-assisted unbonding (unbonding -> unbonded). Instead, it wraps the staking module and exposes interfaces to the checkpointing module. The checkpointing module will do the actual checkpoint-assisted unbonding upon each EndBlock. -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { +func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + sdkCtx := sdk.UnwrapSDKContext(ctx) validatorSetUpdate := []abci.ValidatorUpdate{} // if reaching an epoch boundary, then @@ -76,7 +80,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { if epoch.IsLastBlock(ctx) { // finalise this epoch, i.e., record the current header and the Merkle root of all AppHashs in this epoch if err := k.RecordLastHeaderAndAppHashRoot(ctx); err != nil { - panic(err) + return nil, err } // get all msgs in the msg queue queuedMsgs := k.GetCurrentEpochMsgs(ctx) @@ -89,7 +93,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { // honest validators will have consistent execution results on the queued messages if err != nil { // emit an event signalling the failed execution - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventHandleQueuedMsg{ EpochNumber: epoch.EpochNumber, Height: msg.BlockHeight, @@ -99,14 +103,14 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { }, ) if err != nil { - panic(err) + return nil, err } // skip this failed msg continue } // for each event, emit an wrapped event EventTypeHandleQueuedMsg, which attaches the original attributes plus the original event type, the epoch number, txid and msgid to the event here for _, event := range res.Events { - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventHandleQueuedMsg{ OriginalEventType: event.Type, EpochNumber: epoch.EpochNumber, @@ -116,27 +120,27 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { }, ) if err != nil { - panic(err) + return nil, err } } } // update validator set validatorSetUpdate = k.ApplyAndReturnValidatorSetUpdates(ctx) - ctx.Logger().Info(fmt.Sprintf("Epoching: validator set update of epoch %d: %v", epoch.EpochNumber, validatorSetUpdate)) + sdkCtx.Logger().Info(fmt.Sprintf("Epoching: validator set update of epoch %d: %v", epoch.EpochNumber, validatorSetUpdate)) // trigger AfterEpochEnds hook k.AfterEpochEnds(ctx, epoch.EpochNumber) // emit EndEpoch event - err := ctx.EventManager().EmitTypedEvent( + err := sdkCtx.EventManager().EmitTypedEvent( &types.EventEndEpoch{ EpochNumber: epoch.EpochNumber, }, ) if err != nil { - panic(err) + return nil, err } } - return validatorSetUpdate + return validatorSetUpdate, nil } diff --git a/x/epoching/client/cli/tx.go b/x/epoching/client/cli/tx.go index 52ad580f9..c72da9c14 100644 --- a/x/epoching/client/cli/tx.go +++ b/x/epoching/client/cli/tx.go @@ -2,10 +2,8 @@ package cli import ( "fmt" - "strings" - "time" - "github.com/spf13/cobra" + "strings" "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/x/epoching/types" @@ -17,10 +15,6 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ @@ -73,7 +67,7 @@ $ %s tx epoching delegate %s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 1000%s --fro return err } - stakingMsg := stakingtypes.NewMsgDelegate(delAddr, valAddr, amount) + stakingMsg := stakingtypes.NewMsgDelegate(delAddr.String(), valAddr.String(), amount) msg := types.NewMsgWrappedDelegate(stakingMsg) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) @@ -123,7 +117,7 @@ $ %s tx epoching redelegate %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj %s1l2rsakp return err } - stakingMsg := stakingtypes.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, amount) + stakingMsg := stakingtypes.NewMsgBeginRedelegate(delAddr.String(), valSrcAddr.String(), valDstAddr.String(), amount) msg := types.NewMsgWrappedBeginRedelegate(stakingMsg) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) @@ -168,7 +162,7 @@ $ %s tx epoching unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100%s --from m return err } - stakingMsg := stakingtypes.NewMsgUndelegate(delAddr, valAddr, amount) + stakingMsg := stakingtypes.NewMsgUndelegate(delAddr.String(), valAddr.String(), amount) msg := types.NewMsgWrappedUndelegate(stakingMsg) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) diff --git a/x/epoching/genesis.go b/x/epoching/genesis.go index 9cd920c89..efd7b985d 100644 --- a/x/epoching/genesis.go +++ b/x/epoching/genesis.go @@ -1,14 +1,14 @@ package epoching import ( + "context" "github.com/babylonchain/babylon/x/epoching/keeper" "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the capability module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { // set params for this module if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) @@ -25,7 +25,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } // ExportGenesis returns the capability module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) diff --git a/x/epoching/genesis_test.go b/x/epoching/genesis_test.go index 85a21d31f..ae92a5024 100644 --- a/x/epoching/genesis_test.go +++ b/x/epoching/genesis_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/babylonchain/babylon/x/epoching" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" simapp "github.com/babylonchain/babylon/app" @@ -13,7 +12,7 @@ import ( func TestExportGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) if err := app.EpochingKeeper.SetParams(ctx, types.DefaultParams()); err != nil { panic(err) @@ -25,7 +24,7 @@ func TestExportGenesis(t *testing.T) { func TestInitGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) genesisState := types.GenesisState{ Params: types.Params{ diff --git a/x/epoching/keeper/apphash_chain.go b/x/epoching/keeper/apphash_chain.go index 511ebe336..b42fe0701 100644 --- a/x/epoching/keeper/apphash_chain.go +++ b/x/epoching/keeper/apphash_chain.go @@ -1,46 +1,59 @@ package keeper import ( + "context" "crypto/sha256" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cometbft/cometbft/crypto/merkle" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) setAppHash(ctx sdk.Context, height uint64, appHash []byte) { +func (k Keeper) setAppHash(ctx context.Context, height uint64, appHash []byte) { store := k.appHashStore(ctx) heightBytes := sdk.Uint64ToBigEndian(height) store.Set(heightBytes, appHash) } // GetAppHash gets the AppHash of the header at the given height -func (k Keeper) GetAppHash(ctx sdk.Context, height uint64) ([]byte, error) { +func (k Keeper) GetAppHash(ctx context.Context, height uint64) ([]byte, error) { store := k.appHashStore(ctx) heightBytes := sdk.Uint64ToBigEndian(height) appHash := store.Get(heightBytes) if appHash == nil { - return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "height %d is now known in DB yet", height) + return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "height %d is not known in DB yet", height) } return appHash, nil } // RecordAppHash stores the AppHash of the current header to KVStore -func (k Keeper) RecordAppHash(ctx sdk.Context) { - header := ctx.BlockHeader() - height := uint64(header.Height) - k.setAppHash(ctx, height, header.AppHash) +func (k Keeper) RecordAppHash(ctx context.Context) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + height := uint64(sdkCtx.BlockHeader().Height) + appHash := sdkCtx.BlockHeader().AppHash + // HACK: the app hash for the first height is set to nil + // instead of the hash of an empty byte slice as intended + // see proposed fix: https://github.com/cosmos/cosmos-sdk/pull/18524 + if height == 1 { + // $ echo -n '' | sha256sum + // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + emptyHash := sha256.Sum256([]byte{}) + appHash = emptyHash[:] + } + k.setAppHash(ctx, height, appHash) } -// GetAllAppHashsForEpoch fetches all AppHashs in the given epoch -func (k Keeper) GetAllAppHashsForEpoch(ctx sdk.Context, epoch *types.Epoch) ([][]byte, error) { +// GetAllAppHashesForEpoch fetches all AppHashes in the given epoch +func (k Keeper) GetAllAppHashesForEpoch(ctx context.Context, epoch *types.Epoch) ([][]byte, error) { // if this epoch is the most recent AND has not ended, then we cannot get all AppHashs for this epoch - if k.GetEpoch(ctx).EpochNumber == epoch.EpochNumber && !epoch.IsLastBlock(ctx) { - return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "GetAllAppHashsForEpoch can only be invoked when this epoch has ended") + if k.GetEpoch(ctx).EpochNumber == epoch.EpochNumber && !epoch.IsLastBlock(sdk.UnwrapSDKContext(ctx)) { + return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "GetAllAppHashesForEpoch can only be invoked when this epoch has ended") } // fetch each AppHash in this epoch @@ -57,7 +70,7 @@ func (k Keeper) GetAllAppHashsForEpoch(ctx sdk.Context, epoch *types.Epoch) ([][ } // ProveAppHashInEpoch generates a proof that the given appHash is in a given epoch -func (k Keeper) ProveAppHashInEpoch(ctx sdk.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) { +func (k Keeper) ProveAppHashInEpoch(ctx context.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) { // ensure height is inside this epoch epoch, err := k.GetHistoricalEpoch(ctx, epochNumber) if err != nil { @@ -71,7 +84,7 @@ func (k Keeper) ProveAppHashInEpoch(ctx sdk.Context, height uint64, epochNumber idx := height - epoch.FirstBlockHeight // fetch all AppHashs, calculate Merkle tree and proof - appHashs, err := k.GetAllAppHashsForEpoch(ctx, epoch) + appHashs, err := k.GetAllAppHashesForEpoch(ctx, epoch) if err != nil { return nil, err } @@ -103,7 +116,7 @@ func VerifyAppHashInclusion(appHash []byte, appHashRoot []byte, proof *tmcrypto. // prefix: AppHashKey // key: height // value: AppHash in bytes -func (k Keeper) appHashStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.AppHashKey) +func (k Keeper) appHashStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.AppHashKey) } diff --git a/x/epoching/keeper/apphash_chain_test.go b/x/epoching/keeper/apphash_chain_test.go index 15cae8998..fb7a23dab 100644 --- a/x/epoching/keeper/apphash_chain_test.go +++ b/x/epoching/keeper/apphash_chain_test.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/epoching/keeper" "github.com/babylonchain/babylon/x/epoching/testepoching" - "github.com/babylonchain/babylon/x/epoching/types" "github.com/stretchr/testify/require" ) @@ -16,41 +15,28 @@ func FuzzAppHashChain(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + var err error helper := testepoching.NewHelper(t) ctx, k := helper.Ctx, helper.EpochingKeeper // ensure that the epoch info is correct at the genesis epoch := k.GetEpoch(ctx) - require.Equal(t, epoch.EpochNumber, uint64(0)) - require.Equal(t, epoch.FirstBlockHeight, uint64(0)) + require.Equal(t, epoch.EpochNumber, uint64(1)) + require.Equal(t, epoch.FirstBlockHeight, uint64(1)) // set a random epoch interval - epochInterval := r.Uint64()%100 + 2 // the epoch interval should at at least 2 - - params := types.Params{ - EpochInterval: epochInterval, - } - - if err := k.SetParams(ctx, params); err != nil { - panic(err) - } + epochInterval := k.GetParams(ctx).EpochInterval // reach the end of the 1st epoch - expectedHeight := epochInterval - expectedAppHashs := [][]byte{} + expectedHeight := epochInterval - 1 for i := uint64(0); i < expectedHeight; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) - expectedAppHashs = append(expectedAppHashs, ctx.BlockHeader().AppHash) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } // ensure epoch number is 1 epoch = k.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) - // ensure appHashs are same as expectedAppHashs - appHashs, err := k.GetAllAppHashsForEpoch(ctx, epoch) - require.NoError(t, err) - require.Equal(t, expectedAppHashs, appHashs) - // ensure prover and verifier are correct randomHeightInEpoch := uint64(r.Intn(int(expectedHeight)) + 1) randomAppHash, err := k.GetAppHash(ctx, randomHeightInEpoch) diff --git a/x/epoching/keeper/epoch_msg_queue.go b/x/epoching/keeper/epoch_msg_queue.go index e2dbd4426..7e5706b32 100644 --- a/x/epoching/keeper/epoch_msg_queue.go +++ b/x/epoching/keeper/epoch_msg_queue.go @@ -1,16 +1,19 @@ package keeper import ( + "context" + storetypes "cosmossdk.io/store/types" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) // InitMsgQueue initialises the msg queue length of the current epoch to 0 -func (k Keeper) InitMsgQueue(ctx sdk.Context) { +func (k Keeper) InitMsgQueue(ctx context.Context) { store := k.msgQueueLengthStore(ctx) epochNumber := k.GetEpoch(ctx).EpochNumber @@ -20,7 +23,7 @@ func (k Keeper) InitMsgQueue(ctx sdk.Context) { } // GetQueueLength fetches the number of queued messages of a given epoch -func (k Keeper) GetQueueLength(ctx sdk.Context, epochNumber uint64) uint64 { +func (k Keeper) GetQueueLength(ctx context.Context, epochNumber uint64) uint64 { store := k.msgQueueLengthStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) @@ -33,14 +36,14 @@ func (k Keeper) GetQueueLength(ctx sdk.Context, epochNumber uint64) uint64 { return sdk.BigEndianToUint64(bz) } -// GetQueueLength fetches the number of queued messages of the current epoch -func (k Keeper) GetCurrentQueueLength(ctx sdk.Context) uint64 { +// GetCurrentQueueLength fetches the number of queued messages of the current epoch +func (k Keeper) GetCurrentQueueLength(ctx context.Context) uint64 { epochNumber := k.GetEpoch(ctx).EpochNumber return k.GetQueueLength(ctx, epochNumber) } // incCurrentQueueLength adds the queue length of the current epoch by 1 -func (k Keeper) incCurrentQueueLength(ctx sdk.Context) { +func (k Keeper) incCurrentQueueLength(ctx context.Context) { store := k.msgQueueLengthStore(ctx) epochNumber := k.GetEpoch(ctx).EpochNumber @@ -54,7 +57,7 @@ func (k Keeper) incCurrentQueueLength(ctx sdk.Context) { } // EnqueueMsg enqueues a message to the queue of the current epoch -func (k Keeper) EnqueueMsg(ctx sdk.Context, msg types.QueuedMessage) { +func (k Keeper) EnqueueMsg(ctx context.Context, msg types.QueuedMessage) { epochNumber := k.GetEpoch(ctx).EpochNumber store := k.msgQueueStore(ctx, epochNumber) @@ -73,12 +76,12 @@ func (k Keeper) EnqueueMsg(ctx sdk.Context, msg types.QueuedMessage) { } // GetEpochMsgs returns the set of messages queued in a given epoch -func (k Keeper) GetEpochMsgs(ctx sdk.Context, epochNumber uint64) []*types.QueuedMessage { +func (k Keeper) GetEpochMsgs(ctx context.Context, epochNumber uint64) []*types.QueuedMessage { queuedMsgs := []*types.QueuedMessage{} store := k.msgQueueStore(ctx, epochNumber) // add each queued msg to queuedMsgs - iterator := sdk.KVStorePrefixIterator(store, nil) + iterator := storetypes.KVStorePrefixIterator(store, nil) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { queuedMsgBytes := iterator.Value() @@ -97,13 +100,13 @@ func (k Keeper) GetEpochMsgs(ctx sdk.Context, epochNumber uint64) []*types.Queue } // GetCurrentEpochMsgs returns the set of messages queued in the current epoch -func (k Keeper) GetCurrentEpochMsgs(ctx sdk.Context) []*types.QueuedMessage { +func (k Keeper) GetCurrentEpochMsgs(ctx context.Context) []*types.QueuedMessage { epochNumber := k.GetEpoch(ctx).EpochNumber return k.GetEpochMsgs(ctx, epochNumber) } // HandleQueuedMsg unwraps a QueuedMessage and forwards it to the staking module -func (k Keeper) HandleQueuedMsg(ctx sdk.Context, msg *types.QueuedMessage) (*sdk.Result, error) { +func (k Keeper) HandleQueuedMsg(ctx context.Context, msg *types.QueuedMessage) (*sdk.Result, error) { var ( unwrappedMsgWithType sdk.Msg err error @@ -120,7 +123,7 @@ func (k Keeper) HandleQueuedMsg(ctx sdk.Context, msg *types.QueuedMessage) (*sdk // Create a new Context based off of the existing Context with a MultiStore branch // in case message processing fails. At this point, the MultiStore is a branch of a branch. - handlerCtx, msCache := cacheTxContext(ctx, msg.TxId, msg.MsgId, msg.BlockHeight) + handlerCtx, msCache := cacheTxContext(sdk.UnwrapSDKContext(ctx), msg.TxId, msg.MsgId, msg.BlockHeight) // handle the unwrapped message result, err := handler(handlerCtx, unwrappedMsgWithType) @@ -135,7 +138,8 @@ func (k Keeper) HandleQueuedMsg(ctx sdk.Context, msg *types.QueuedMessage) (*sdk switch unwrappedMsg := msg.Msg.(type) { case *types.QueuedMessage_MsgCreateValidator: // handle self-delegation - delAddr, err := sdk.AccAddressFromBech32(unwrappedMsg.MsgCreateValidator.DelegatorAddress) + // Delegator and Validator address are the same + delAddr, err := sdk.AccAddressFromBech32(unwrappedMsg.MsgCreateValidator.ValidatorAddress) if err != nil { return nil, err } @@ -202,20 +206,18 @@ func (k Keeper) HandleQueuedMsg(ctx sdk.Context, msg *types.QueuedMessage) (*sdk } // based on a function with the same name in `baseapp.go` -func cacheTxContext(ctx sdk.Context, txid []byte, msgid []byte, height uint64) (sdk.Context, sdk.CacheMultiStore) { +func cacheTxContext(ctx sdk.Context, txid []byte, msgid []byte, height uint64) (sdk.Context, storetypes.CacheMultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 msCache := ms.CacheMultiStore() if msCache.TracingEnabled() { msCache = msCache.SetTracingContext( - sdk.TraceContext( - map[string]interface{}{ - "txHash": fmt.Sprintf("%X", txid), - "msgHash": fmt.Sprintf("%X", msgid), - "height": fmt.Sprintf("%d", height), - }, - ), - ).(sdk.CacheMultiStore) + map[string]interface{}{ + "txHash": fmt.Sprintf("%X", txid), + "msgHash": fmt.Sprintf("%X", msgid), + "height": fmt.Sprintf("%d", height), + }, + ).(storetypes.CacheMultiStore) } return ctx.WithMultiStore(msCache), msCache @@ -225,9 +227,9 @@ func cacheTxContext(ctx sdk.Context, txid []byte, msgid []byte, height uint64) ( // prefix: MsgQueueKey || epochNumber // key: index // value: msg -func (k Keeper) msgQueueStore(ctx sdk.Context, epochNumber uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - msgQueueStore := prefix.NewStore(store, types.MsgQueueKey) +func (k Keeper) msgQueueStore(ctx context.Context, epochNumber uint64) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + msgQueueStore := prefix.NewStore(storeAdapter, types.MsgQueueKey) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) return prefix.NewStore(msgQueueStore, epochNumberBytes) } @@ -236,7 +238,7 @@ func (k Keeper) msgQueueStore(ctx sdk.Context, epochNumber uint64) prefix.Store // prefix: QueueLengthKey // key: epochNumber // value: queue length -func (k Keeper) msgQueueLengthStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.QueueLengthKey) +func (k Keeper) msgQueueLengthStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.QueueLengthKey) } diff --git a/x/epoching/keeper/epoch_msg_queue_test.go b/x/epoching/keeper/epoch_msg_queue_test.go index 8d41bd685..7a0098ab1 100644 --- a/x/epoching/keeper/epoch_msg_queue_test.go +++ b/x/epoching/keeper/epoch_msg_queue_test.go @@ -1,10 +1,11 @@ package keeper_test import ( - "github.com/babylonchain/babylon/testutil/datagen" "math/rand" "testing" + "github.com/babylonchain/babylon/testutil/datagen" + appparams "github.com/babylonchain/babylon/app/params" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -30,9 +31,7 @@ func FuzzEnqueueMsg(f *testing.F) { require.Empty(t, keeper.GetCurrentEpochMsgs(ctx)) require.Equal(t, uint64(0), keeper.GetCurrentQueueLength(ctx)) - // enter the 1st block and thus epoch 1 - // Note that the genesis block does not trigger BeginBlock or EndBlock - ctx = helper.GenAndApplyEmptyBlock(r) + // at epoch 1 right now epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) // ensure that the epoch msg queue is correct at epoch 1 @@ -76,8 +75,7 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { require.NotEmpty(t, genAccs) genAddr := genAccs[0].GetAddress() - // BeginBlock of block 1, and thus entering epoch 1 - ctx = helper.BeginBlock(r) + // at epoch 1 right now epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) @@ -103,12 +101,10 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { epochMsgs := keeper.GetCurrentEpochMsgs(ctx) require.Equal(t, numNewDels, int64(len(epochMsgs))) - // EndBlock of block 1 - ctx = helper.EndBlock() - // go to BeginBlock of block 11, and thus entering epoch 2 for i := uint64(0); i < params.EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) @@ -132,15 +128,14 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { r := rand.New(rand.NewSource(seed)) helper := testepoching.NewHelperWithValSet(t) - _, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs + ctx, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator require.NotNil(t, genAccs) require.NotEmpty(t, genAccs) genAddr := genAccs[0].GetAddress() - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // at epoch 1 right now epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) @@ -168,12 +163,10 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { epochMsgs := keeper.GetCurrentEpochMsgs(ctx) require.Equal(t, numNewUndels, int64(len(epochMsgs))) - // EndBlock of block 1 - ctx = helper.EndBlock() - // enter epoch 2 for i := uint64(0); i < keeper.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) @@ -188,7 +181,8 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { require.Equal(t, valPower-reducedPower, valPower2) // ensure the genesis account has these unbonding tokens - unbondingDels := helper.StakingKeeper.GetAllUnbondingDelegations(ctx, genAddr) + unbondingDels, err := helper.StakingKeeper.GetAllUnbondingDelegations(ctx, genAddr) + require.NoError(t, err) require.Equal(t, 1, len(unbondingDels)) // there is only 1 type of tokens // from cosmos v47, all undelegations made at the same height are represented @@ -206,15 +200,14 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { r := rand.New(rand.NewSource(seed)) helper := testepoching.NewHelperWithValSet(t) - _, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs + ctx, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator require.NotNil(t, genAccs) require.NotEmpty(t, genAccs) genAddr := genAccs[0].GetAddress() - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) + // at epoch 1 right now epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) @@ -249,12 +242,10 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { epochMsgs := keeper.GetCurrentEpochMsgs(ctx) require.Equal(t, numNewRedels, int64(len(epochMsgs))) - // EndBlock of block 1 - ctx = helper.EndBlock() - // enter epoch 2 for i := uint64(0); i < keeper.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) require.Equal(t, uint64(2), epoch.EpochNumber) @@ -277,7 +268,8 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { require.Equal(t, val2Power+redelegatedPower, val2Power2) // ensure the genesis account has these redelegating tokens - redelegations := helper.StakingKeeper.GetAllRedelegations(ctx, genAddr, val1, val2) + redelegations, err := helper.StakingKeeper.GetAllRedelegations(ctx, genAddr, val1, val2) + require.NoError(t, err) require.Equal(t, 1, len(redelegations)) // there is only 1 type of tokens require.Equal(t, numNewRedels, int64(len(redelegations[0].Entries))) // there are numNewRedels entries for _, entry := range redelegations[0].Entries { diff --git a/x/epoching/keeper/epoch_slashed_val_set.go b/x/epoching/keeper/epoch_slashed_val_set.go index 71feae801..a52b89624 100644 --- a/x/epoching/keeper/epoch_slashed_val_set.go +++ b/x/epoching/keeper/epoch_slashed_val_set.go @@ -1,21 +1,23 @@ package keeper import ( + "context" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // setSlashedVotingPower sets the total amount of voting power that has been slashed in the epoch -func (k Keeper) setSlashedVotingPower(ctx sdk.Context, epochNumber uint64, power int64) { +func (k Keeper) setSlashedVotingPower(ctx context.Context, epochNumber uint64, power int64) { store := k.slashedVotingPowerStore(ctx) // key: epochNumber epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) // value: power - powerBytes, err := sdk.NewInt(power).Marshal() + powerBytes, err := sdkmath.NewInt(power).Marshal() if err != nil { panic(errorsmod.Wrap(types.ErrMarshal, err.Error())) } @@ -25,13 +27,13 @@ func (k Keeper) setSlashedVotingPower(ctx sdk.Context, epochNumber uint64, power // InitSlashedVotingPower sets the slashed voting power of the current epoch to 0 // This is called upon initialising the genesis state and upon a new epoch -func (k Keeper) InitSlashedVotingPower(ctx sdk.Context) { +func (k Keeper) InitSlashedVotingPower(ctx context.Context) { epochNumber := k.GetEpoch(ctx).EpochNumber k.setSlashedVotingPower(ctx, epochNumber, 0) } // GetSlashedVotingPower fetches the amount of slashed voting power of a given epoch -func (k Keeper) GetSlashedVotingPower(ctx sdk.Context, epochNumber uint64) int64 { +func (k Keeper) GetSlashedVotingPower(ctx context.Context, epochNumber uint64) int64 { store := k.slashedVotingPowerStore(ctx) // key: epochNumber @@ -41,7 +43,7 @@ func (k Keeper) GetSlashedVotingPower(ctx sdk.Context, epochNumber uint64) int64 panic(types.ErrUnknownSlashedVotingPower) } // get value - var slashedVotingPower math.Int + var slashedVotingPower sdkmath.Int if err := slashedVotingPower.Unmarshal(bz); err != nil { panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) } @@ -51,14 +53,14 @@ func (k Keeper) GetSlashedVotingPower(ctx sdk.Context, epochNumber uint64) int64 // AddSlashedValidator adds a slashed validator to the set of the current epoch // This is called upon hook `BeforeValidatorSlashed` exposed by the staking module -func (k Keeper) AddSlashedValidator(ctx sdk.Context, valAddr sdk.ValAddress) error { +func (k Keeper) AddSlashedValidator(ctx context.Context, valAddr sdk.ValAddress) error { epochNumber := k.GetEpoch(ctx).EpochNumber store := k.slashedValSetStore(ctx, epochNumber) thisVotingPower, err := k.GetValidatorVotingPower(ctx, epochNumber, valAddr) if err != nil { panic(errorsmod.Wrap(types.ErrMarshal, err.Error())) } - thisVotingPowerBytes, err := sdk.NewInt(thisVotingPower).Marshal() + thisVotingPowerBytes, err := sdkmath.NewInt(thisVotingPower).Marshal() if err != nil { panic(errorsmod.Wrap(types.ErrMarshal, err.Error())) } @@ -79,7 +81,7 @@ func (k Keeper) AddSlashedValidator(ctx sdk.Context, valAddr sdk.ValAddress) err } // GetSlashedValidators returns the set of slashed validators of a given epoch -func (k Keeper) GetSlashedValidators(ctx sdk.Context, epochNumber uint64) types.ValidatorSet { +func (k Keeper) GetSlashedValidators(ctx context.Context, epochNumber uint64) types.ValidatorSet { valSet := types.ValidatorSet{} store := k.slashedValSetStore(ctx, epochNumber) // add each valAddr, which is the key @@ -91,7 +93,7 @@ func (k Keeper) GetSlashedValidators(ctx sdk.Context, epochNumber uint64) types. if powerBytes == nil { panic(types.ErrUnknownValidator) } - var power math.Int + var power sdkmath.Int if err := power.Unmarshal(powerBytes); err != nil { panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) } @@ -104,7 +106,7 @@ func (k Keeper) GetSlashedValidators(ctx sdk.Context, epochNumber uint64) types. // ClearSlashedValidators removes all slashed validators in the set // TODO: This is called upon the epoch is checkpointed -func (k Keeper) ClearSlashedValidators(ctx sdk.Context, epochNumber uint64) { +func (k Keeper) ClearSlashedValidators(ctx context.Context, epochNumber uint64) { // prefix : SlashedValidatorSetKey || epochNumber store := k.slashedValSetStore(ctx, epochNumber) @@ -123,16 +125,16 @@ func (k Keeper) ClearSlashedValidators(ctx sdk.Context, epochNumber uint64) { // slashedValSetStore returns the KVStore of the slashed validator set for a given epoch // prefix : SlashedValidatorSetKey || epochNumber -func (k Keeper) slashedValSetStore(ctx sdk.Context, epochNumber uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - slashedValStore := prefix.NewStore(store, types.SlashedValidatorSetKey) +func (k Keeper) slashedValSetStore(ctx context.Context, epochNumber uint64) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + slashedValStore := prefix.NewStore(storeAdapter, types.SlashedValidatorSetKey) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) return prefix.NewStore(slashedValStore, epochNumberBytes) } // slashedVotingPower returns the KVStore of the slashed voting power // prefix: SlashedVotingPowerKey -func (k Keeper) slashedVotingPowerStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.SlashedVotingPowerKey) +func (k Keeper) slashedVotingPowerStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.SlashedVotingPowerKey) } diff --git a/x/epoching/keeper/epoch_slashed_val_set_test.go b/x/epoching/keeper/epoch_slashed_val_set_test.go index 9c6b9cb33..f46006257 100644 --- a/x/epoching/keeper/epoch_slashed_val_set_test.go +++ b/x/epoching/keeper/epoch_slashed_val_set_test.go @@ -1,11 +1,13 @@ package keeper_test import ( - "github.com/babylonchain/babylon/testutil/datagen" "math/rand" "sort" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,10 +18,11 @@ func FuzzSlashedValSet(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + var err error helper := testepoching.NewHelperWithValSet(t) ctx, keeper, stakingKeeper := helper.Ctx, helper.EpochingKeeper, helper.StakingKeeper - getValSet := keeper.GetValidatorSet(ctx, 0) + getValSet := keeper.GetValidatorSet(ctx, 1) // slash a random subset of validators numSlashed := r.Intn(len(getValSet)) @@ -27,7 +30,8 @@ func FuzzSlashedValSet(f *testing.F) { for i := 0; i < numSlashed; i++ { idx := r.Intn(len(getValSet)) slashedVal := getValSet[idx] - stakingKeeper.Slash(ctx, sdk.ConsAddress(slashedVal.Addr), 0, slashedVal.Power, sdk.OneDec()) + _, err = stakingKeeper.Slash(ctx, slashedVal.Addr, 0, slashedVal.Power, sdkmath.LegacyOneDec()) + require.NoError(t, err) // add the slashed validator to the slashed validator set excpectedSlashedVals = append(excpectedSlashedVals, slashedVal.Addr) // remove the slashed validator from the validator set in order to avoid slashing a validator more than once @@ -35,7 +39,7 @@ func FuzzSlashedValSet(f *testing.F) { } // check whether the slashed validator set in DB is consistent or not - actualSlashedVals := keeper.GetSlashedValidators(ctx, 0) + actualSlashedVals := keeper.GetSlashedValidators(ctx, 1) require.Equal(t, len(excpectedSlashedVals), len(actualSlashedVals)) sortVals(excpectedSlashedVals) actualSlashedVals = types.NewSortedValidatorSet(actualSlashedVals) @@ -43,12 +47,16 @@ func FuzzSlashedValSet(f *testing.F) { require.Equal(t, excpectedSlashedVals[i], actualSlashedVals[i].GetValAddress()) } - // go to the 1st block and thus epoch 1 - ctx = helper.GenAndApplyEmptyBlock(r) + // go to epoch 2 + epochInterval := keeper.GetParams(ctx).EpochInterval + for i := uint64(0); i < epochInterval; i++ { + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) + } epochNumber := keeper.GetEpoch(ctx).EpochNumber - require.Equal(t, uint64(1), epochNumber) + require.Equal(t, uint64(2), epochNumber) // no validator is slashed in epoch 1 - require.Empty(t, keeper.GetSlashedValidators(ctx, 1)) + require.Empty(t, keeper.GetSlashedValidators(ctx, 2)) }) } diff --git a/x/epoching/keeper/epoch_val_set.go b/x/epoching/keeper/epoch_val_set.go index 8f7de9a85..4095df178 100644 --- a/x/epoching/keeper/epoch_val_set.go +++ b/x/epoching/keeper/epoch_val_set.go @@ -1,16 +1,17 @@ package keeper import ( + "context" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // GetValidatorSet returns the set of validators of a given epoch, where the validators are ordered by their address in ascending order -func (k Keeper) GetValidatorSet(ctx sdk.Context, epochNumber uint64) types.ValidatorSet { +func (k Keeper) GetValidatorSet(ctx context.Context, epochNumber uint64) types.ValidatorSet { vals := []types.Validator{} store := k.valSetStore(ctx, epochNumber) @@ -19,7 +20,7 @@ func (k Keeper) GetValidatorSet(ctx sdk.Context, epochNumber uint64) types.Valid for ; iterator.Valid(); iterator.Next() { addr := sdk.ValAddress(iterator.Key()) powerBytes := iterator.Value() - var power math.Int + var power sdkmath.Int if err := power.Unmarshal(powerBytes); err != nil { panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) } @@ -32,34 +33,22 @@ func (k Keeper) GetValidatorSet(ctx sdk.Context, epochNumber uint64) types.Valid return types.NewSortedValidatorSet(vals) } -func (k Keeper) GetCurrentValidatorSet(ctx sdk.Context) types.ValidatorSet { +func (k Keeper) GetCurrentValidatorSet(ctx context.Context) types.ValidatorSet { epochNumber := k.GetEpoch(ctx).EpochNumber return k.GetValidatorSet(ctx, epochNumber) } -func (k Keeper) GetValidatorPubkey(ctx sdk.Context, valAddr sdk.ValAddress) (cryptotypes.PubKey, bool) { - validator, found := k.stk.GetValidator(ctx, valAddr) - if !found { - return nil, false - } - pubkey, err := validator.ConsPubKey() - if err != nil { - return nil, false - } - return pubkey, true -} - // InitValidatorSet stores the validator set in the beginning of the current epoch // This is called upon BeginBlock -func (k Keeper) InitValidatorSet(ctx sdk.Context) { +func (k Keeper) InitValidatorSet(ctx context.Context) { epochNumber := k.GetEpoch(ctx).EpochNumber store := k.valSetStore(ctx, epochNumber) totalPower := int64(0) // store the validator set - k.stk.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { + err := k.stk.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power int64) (stop bool) { addrBytes := []byte(addr) - powerBytes, err := sdk.NewInt(power).Marshal() + powerBytes, err := sdkmath.NewInt(power).Marshal() if err != nil { panic(errorsmod.Wrap(types.ErrMarshal, err.Error())) } @@ -69,9 +58,13 @@ func (k Keeper) InitValidatorSet(ctx sdk.Context) { return false }) + if err != nil { + panic(err) + } + // store total voting power of this validator set epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) - totalPowerBytes, err := sdk.NewInt(totalPower).Marshal() + totalPowerBytes, err := sdkmath.NewInt(totalPower).Marshal() if err != nil { panic(errorsmod.Wrap(types.ErrMarshal, err.Error())) } @@ -80,7 +73,7 @@ func (k Keeper) InitValidatorSet(ctx sdk.Context) { // ClearValidatorSet removes the validator set of a given epoch // TODO: This is called upon the epoch is checkpointed -func (k Keeper) ClearValidatorSet(ctx sdk.Context, epochNumber uint64) { +func (k Keeper) ClearValidatorSet(ctx context.Context, epochNumber uint64) { store := k.valSetStore(ctx, epochNumber) iterator := store.Iterator(nil, nil) defer iterator.Close() @@ -96,14 +89,14 @@ func (k Keeper) ClearValidatorSet(ctx sdk.Context, epochNumber uint64) { } // GetValidatorVotingPower returns the voting power of a given validator in a given epoch -func (k Keeper) GetValidatorVotingPower(ctx sdk.Context, epochNumber uint64, valAddr sdk.ValAddress) (int64, error) { +func (k Keeper) GetValidatorVotingPower(ctx context.Context, epochNumber uint64, valAddr sdk.ValAddress) (int64, error) { store := k.valSetStore(ctx, epochNumber) powerBytes := store.Get(valAddr) if powerBytes == nil { return 0, types.ErrUnknownValidator } - var power math.Int + var power sdkmath.Int if err := power.Unmarshal(powerBytes); err != nil { panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) } @@ -111,20 +104,20 @@ func (k Keeper) GetValidatorVotingPower(ctx sdk.Context, epochNumber uint64, val return power.Int64(), nil } -func (k Keeper) GetCurrentValidatorVotingPower(ctx sdk.Context, valAddr sdk.ValAddress) (int64, error) { +func (k Keeper) GetCurrentValidatorVotingPower(ctx context.Context, valAddr sdk.ValAddress) (int64, error) { epochNumber := k.GetEpoch(ctx).EpochNumber return k.GetValidatorVotingPower(ctx, epochNumber, valAddr) } // GetTotalVotingPower returns the total voting power of a given epoch -func (k Keeper) GetTotalVotingPower(ctx sdk.Context, epochNumber uint64) int64 { +func (k Keeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) store := k.votingPowerStore(ctx) powerBytes := store.Get(epochNumberBytes) if powerBytes == nil { panic(types.ErrUnknownTotalVotingPower) } - var power math.Int + var power sdkmath.Int if err := power.Unmarshal(powerBytes); err != nil { panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) } @@ -135,9 +128,9 @@ func (k Keeper) GetTotalVotingPower(ctx sdk.Context, epochNumber uint64) int64 { // prefix: ValidatorSetKey || epochNumber // key: string(address) // value: voting power (in int64 as per Cosmos SDK) -func (k Keeper) valSetStore(ctx sdk.Context, epochNumber uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - valSetStore := prefix.NewStore(store, types.ValidatorSetKey) +func (k Keeper) valSetStore(ctx context.Context, epochNumber uint64) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + valSetStore := prefix.NewStore(storeAdapter, types.ValidatorSetKey) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) return prefix.NewStore(valSetStore, epochNumberBytes) } @@ -146,7 +139,7 @@ func (k Keeper) valSetStore(ctx sdk.Context, epochNumber uint64) prefix.Store { // prefix: ValidatorSetKey // key: epochNumber // value: total voting power (in int64 as per Cosmos SDK) -func (k Keeper) votingPowerStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.VotingPowerKey) +func (k Keeper) votingPowerStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.VotingPowerKey) } diff --git a/x/epoching/keeper/epoch_val_set_test.go b/x/epoching/keeper/epoch_val_set_test.go index 842330805..bbab84b0b 100644 --- a/x/epoching/keeper/epoch_val_set_test.go +++ b/x/epoching/keeper/epoch_val_set_test.go @@ -17,7 +17,8 @@ func FuzzEpochValSet(f *testing.F) { helper := testepoching.NewHelperWithValSet(t) ctx, keeper := helper.Ctx, helper.EpochingKeeper - valSet := helper.StakingKeeper.GetLastValidators(helper.Ctx) + valSet, err := helper.StakingKeeper.GetLastValidators(helper.Ctx) + require.NoError(t, err) getValSet := keeper.GetValidatorSet(ctx, 0) require.Equal(t, len(valSet), len(getValSet)) for i := range getValSet { @@ -29,7 +30,8 @@ func FuzzEpochValSet(f *testing.F) { // generate a random number of new blocks numIncBlocks := r.Uint64()%1000 + 1 for i := uint64(0); i < numIncBlocks; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } // check whether the validator set remains the same or not diff --git a/x/epoching/keeper/epochs.go b/x/epoching/keeper/epochs.go index 7e8cba969..567ad1909 100644 --- a/x/epoching/keeper/epochs.go +++ b/x/epoching/keeper/epochs.go @@ -1,28 +1,32 @@ package keeper import ( + "context" errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cometbft/cometbft/crypto/merkle" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) -const ( - DefaultEpochNumber = 0 -) - // setEpochNumber sets epoch number -func (k Keeper) setEpochNumber(ctx sdk.Context, epochNumber uint64) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) setEpochNumber(ctx context.Context, epochNumber uint64) { + store := k.storeService.OpenKVStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) - store.Set(types.EpochNumberKey, epochNumberBytes) + err := store.Set(types.EpochNumberKey, epochNumberBytes) + if err != nil { + panic(err) + } } -func (k Keeper) getEpochNumber(ctx sdk.Context) uint64 { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.EpochNumberKey) +func (k Keeper) getEpochNumber(ctx context.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.EpochNumberKey) + if err != nil { + panic(err) + } if bz == nil { panic(types.ErrUnknownEpochNumber) } @@ -30,14 +34,14 @@ func (k Keeper) getEpochNumber(ctx sdk.Context) uint64 { return epochNumber } -func (k Keeper) setEpochInfo(ctx sdk.Context, epochNumber uint64, epoch *types.Epoch) { +func (k Keeper) setEpochInfo(ctx context.Context, epochNumber uint64, epoch *types.Epoch) { store := k.epochInfoStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) epochBytes := k.cdc.MustMarshal(epoch) store.Set(epochNumberBytes, epochBytes) } -func (k Keeper) getEpochInfo(ctx sdk.Context, epochNumber uint64) (*types.Epoch, error) { +func (k Keeper) getEpochInfo(ctx context.Context, epochNumber uint64) (*types.Epoch, error) { store := k.epochInfoStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) bz := store.Get(epochNumberBytes) @@ -50,8 +54,8 @@ func (k Keeper) getEpochInfo(ctx sdk.Context, epochNumber uint64) (*types.Epoch, } // InitEpoch sets the zero epoch number to DB -func (k Keeper) InitEpoch(ctx sdk.Context) { - header := ctx.BlockHeader() +func (k Keeper) InitEpoch(ctx context.Context) { + header := sdk.UnwrapSDKContext(ctx).BlockHeader() if header.Height > 0 { panic("InitEpoch can be invoked only at genesis") } @@ -63,7 +67,7 @@ func (k Keeper) InitEpoch(ctx sdk.Context) { } // GetEpoch fetches the current epoch -func (k Keeper) GetEpoch(ctx sdk.Context) *types.Epoch { +func (k Keeper) GetEpoch(ctx context.Context) *types.Epoch { epochNumber := k.getEpochNumber(ctx) epoch, err := k.getEpochInfo(ctx, epochNumber) if err != nil { @@ -72,27 +76,27 @@ func (k Keeper) GetEpoch(ctx sdk.Context) *types.Epoch { return epoch } -func (k Keeper) GetHistoricalEpoch(ctx sdk.Context, epochNumber uint64) (*types.Epoch, error) { +func (k Keeper) GetHistoricalEpoch(ctx context.Context, epochNumber uint64) (*types.Epoch, error) { epoch, err := k.getEpochInfo(ctx, epochNumber) return epoch, err } // RecordLastHeaderAndAppHashRoot records the last header and Merkle root of all AppHashs // for the current epoch, and stores the epoch metadata to KVStore -func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx sdk.Context) error { +func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx context.Context) error { epoch := k.GetEpoch(ctx) if !epoch.IsLastBlock(ctx) { return errorsmod.Wrapf(types.ErrInvalidHeight, "RecordLastBlockHeader can only be invoked at the last block of an epoch") } // record last block header - header := ctx.BlockHeader() + header := sdk.UnwrapSDKContext(ctx).BlockHeader() epoch.LastBlockHeader = &header // calculate and record the Merkle root - appHashs, err := k.GetAllAppHashsForEpoch(ctx, epoch) + appHashes, err := k.GetAllAppHashesForEpoch(ctx, epoch) if err != nil { return err } - appHashRoot := merkle.HashFromByteSlices(appHashs) + appHashRoot := merkle.HashFromByteSlices(appHashes) epoch.AppHashRoot = appHashRoot // save back to KVStore k.setEpochInfo(ctx, epoch.EpochNumber, epoch) @@ -102,13 +106,13 @@ func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx sdk.Context) error { // RecordSealerHeaderForPrevEpoch records the sealer header for the previous epoch, // where the sealer header of an epoch is the 2nd header of the next epoch // This validator set of the epoch has generated a BLS multisig on `last_commit_hash` of the sealer header -func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx sdk.Context) *types.Epoch { +func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch { // get the sealer header epoch := k.GetEpoch(ctx) if !epoch.IsSecondBlock(ctx) { panic("RecordSealerHeaderForPrevEpoch can only be invoked at the second header of a non-zero epoch") } - header := ctx.BlockHeader() + header := sdk.UnwrapSDKContext(ctx).BlockHeader() // get the sealed epoch, i.e., the epoch earlier than the current epoch sealedEpoch, err := k.GetHistoricalEpoch(ctx, epoch.EpochNumber-1) @@ -125,13 +129,14 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx sdk.Context) *types.Epoch { // IncEpoch adds epoch number by 1 // CONTRACT: can only be invoked at the first block of an epoch -func (k Keeper) IncEpoch(ctx sdk.Context) types.Epoch { +func (k Keeper) IncEpoch(ctx context.Context) types.Epoch { + sdkCtx := sdk.UnwrapSDKContext(ctx) epochNumber := k.GetEpoch(ctx).EpochNumber incrementedEpochNumber := epochNumber + 1 k.setEpochNumber(ctx, incrementedEpochNumber) epochInterval := k.GetParams(ctx).EpochInterval - newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(ctx.BlockHeight()), nil) + newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(sdkCtx.BlockHeight()), nil) k.setEpochInfo(ctx, incrementedEpochNumber, &newEpoch) return newEpoch @@ -141,7 +146,7 @@ func (k Keeper) IncEpoch(ctx sdk.Context) types.Epoch { // prefix: EpochInfoKey // key: epochNumber // value: epoch metadata -func (k Keeper) epochInfoStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.EpochInfoKey) +func (k Keeper) epochInfoStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.EpochInfoKey) } diff --git a/x/epoching/keeper/epochs_test.go b/x/epoching/keeper/epochs_test.go index 3a864a5ea..04551b988 100644 --- a/x/epoching/keeper/epochs_test.go +++ b/x/epoching/keeper/epochs_test.go @@ -6,7 +6,6 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/epoching/testepoching" - "github.com/babylonchain/babylon/x/epoching/types" "github.com/stretchr/testify/require" ) @@ -20,29 +19,25 @@ func FuzzEpochs(f *testing.F) { ctx, keeper := helper.Ctx, helper.EpochingKeeper // ensure that the epoch info is correct at the genesis epoch := keeper.GetEpoch(ctx) - require.Equal(t, epoch.EpochNumber, uint64(0)) - require.Equal(t, epoch.FirstBlockHeight, uint64(0)) + require.Equal(t, epoch.EpochNumber, uint64(1)) + require.Equal(t, epoch.FirstBlockHeight, uint64(1)) // set a random epoch interval - epochInterval := r.Uint64()%100 + 2 // the epoch interval should at at least 2 - - params := types.Params{ - EpochInterval: epochInterval, - } - - if err := keeper.SetParams(ctx, params); err != nil { - panic(err) - } + epochInterval := keeper.GetParams(ctx).EpochInterval // increment a random number of new blocks numIncBlocks := r.Uint64()%1000 + 1 + var err error for i := uint64(0); i < numIncBlocks; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + // TODO: Figure out why when ctx height is 1, GenAndApplyEmptyBlock + // will still give ctx height 1 once, then start to increment + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } // ensure that the epoch info is still correct - expectedEpochNumber := numIncBlocks / epochInterval - if numIncBlocks%epochInterval > 0 { + expectedEpochNumber := (numIncBlocks + 1) / epochInterval + if (numIncBlocks+1)%epochInterval > 0 { expectedEpochNumber += 1 } actualNewEpoch := keeper.GetEpoch(ctx) diff --git a/x/epoching/keeper/grpc_query_test.go b/x/epoching/keeper/grpc_query_test.go index 6689b04ea..735fe4fca 100644 --- a/x/epoching/keeper/grpc_query_test.go +++ b/x/epoching/keeper/grpc_query_test.go @@ -9,7 +9,6 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" ) @@ -39,7 +38,6 @@ func FuzzParamsQuery(f *testing.F) { helper := testepoching.NewHelper(t) ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient - wctx := sdk.WrapSDKContext(ctx) // if setParamsFlag == 0, set params setParamsFlag := r.Intn(2) if setParamsFlag == 0 { @@ -48,7 +46,7 @@ func FuzzParamsQuery(f *testing.F) { } } req := types.QueryParamsRequest{} - resp, err := queryClient.Params(wctx, &req) + resp, err := queryClient.Params(ctx, &req) require.NoError(t, err) // if setParamsFlag == 0, resp.Params should be changed, otherwise default if setParamsFlag == 0 { @@ -69,21 +67,20 @@ func FuzzCurrentEpoch(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - increment := datagen.RandomInt(r, 100) + increment := datagen.RandomInt(r, 100) + 1 helper := testepoching.NewHelper(t) ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient - wctx := sdk.WrapSDKContext(ctx) epochInterval := keeper.GetParams(ctx).EpochInterval - for i := uint64(0); i < increment; i++ { + // starting from epoch 1 + for i := uint64(1); i < increment; i++ { // this ensures that IncEpoch is invoked only at the first header of each epoch ctx = ctx.WithBlockHeader(*datagen.GenRandomTMHeader(r, "chain-test", i*epochInterval+1)) - wctx = sdk.WrapSDKContext(ctx) keeper.IncEpoch(ctx) } req := types.QueryCurrentEpochRequest{} - resp, err := queryClient.CurrentEpoch(wctx, &req) + resp, err := queryClient.CurrentEpoch(ctx, &req) require.NoError(t, err) require.Equal(t, increment, resp.CurrentEpoch) require.Equal(t, increment*epochInterval, resp.EpochBoundary) @@ -95,18 +92,19 @@ func FuzzEpochsInfo(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - numEpochs := datagen.RandomInt(r, 10) + 1 + var err error + numEpochs := datagen.RandomInt(r, 10) + 2 limit := datagen.RandomInt(r, 10) + 1 helper := testepoching.NewHelper(t) ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient - wctx := sdk.WrapSDKContext(ctx) - // enque the first block of the numEpochs'th epoch + // enque the 1st block of the numEpochs'th epoch epochInterval := keeper.GetParams(ctx).EpochInterval - for i := uint64(0); i < numEpochs-1; i++ { + for i := uint64(0); i < (numEpochs - 2); i++ { // exclude the existing epoch 0 and 1 for j := uint64(0); j < epochInterval; j++ { - helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } } @@ -116,10 +114,10 @@ func FuzzEpochsInfo(f *testing.F) { Limit: limit, }, } - resp, err := queryClient.EpochsInfo(wctx, &req) + resp, err := queryClient.EpochsInfo(ctx, &req) require.NoError(t, err) - require.Equal(t, testepoching.Min(numEpochs, limit), uint64(len(resp.Epochs))) + require.Equal(t, min(numEpochs, limit), uint64(len(resp.Epochs))) for i, epoch := range resp.Epochs { require.Equal(t, uint64(i), epoch.EpochNumber) } @@ -141,7 +139,6 @@ func FuzzEpochMsgsQuery(f *testing.F) { txidsMap := map[string]bool{} helper := testepoching.NewHelper(t) ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient - wctx := sdk.WrapSDKContext(ctx) // enque a random number of msgs with random txids for i := uint64(0); i < numMsgs; i++ { txid := datagen.GenRandomByteArray(r, 32) @@ -154,15 +151,15 @@ func FuzzEpochMsgsQuery(f *testing.F) { } // get epoch msgs req := types.QueryEpochMsgsRequest{ - EpochNum: 0, + EpochNum: 1, Pagination: &query.PageRequest{ Limit: limit, }, } - resp, err := queryClient.EpochMsgs(wctx, &req) + resp, err := queryClient.EpochMsgs(ctx, &req) require.NoError(t, err) - require.Equal(t, testepoching.Min(uint64(len(txidsMap)), limit), uint64(len(resp.Msgs))) + require.Equal(t, min(uint64(len(txidsMap)), limit), uint64(len(resp.Msgs))) for idx := range resp.Msgs { _, ok := txidsMap[string(resp.Msgs[idx].TxId)] require.True(t, ok) @@ -170,12 +167,12 @@ func FuzzEpochMsgsQuery(f *testing.F) { // epoch 1 is out of scope req = types.QueryEpochMsgsRequest{ - EpochNum: 1, + EpochNum: 2, Pagination: &query.PageRequest{ Limit: limit, }, } - _, err = queryClient.EpochMsgs(wctx, &req) + _, err = queryClient.EpochMsgs(ctx, &req) require.Error(t, err) }) } @@ -193,7 +190,7 @@ func FuzzEpochValSetQuery(f *testing.F) { limit := uint64(r.Int() % 100) req := &types.QueryEpochValSetRequest{ - EpochNum: 0, + EpochNum: 1, Pagination: &query.PageRequest{ Limit: limit, }, @@ -205,7 +202,8 @@ func FuzzEpochValSetQuery(f *testing.F) { // generate a random number of new blocks numIncBlocks := r.Uint64()%1000 + 1 for i := uint64(0); i < numIncBlocks; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) } // check whether the validator set remains the same or not diff --git a/x/epoching/keeper/hooks.go b/x/epoching/keeper/hooks.go index a30ab69e6..30cd92fc4 100644 --- a/x/epoching/keeper/hooks.go +++ b/x/epoching/keeper/hooks.go @@ -1,6 +1,8 @@ package keeper import ( + "context" + "cosmossdk.io/math" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,21 +13,21 @@ import ( var _ types.EpochingHooks = Keeper{} // AfterEpochBegins - call hook if registered -func (k Keeper) AfterEpochBegins(ctx sdk.Context, epoch uint64) { +func (k Keeper) AfterEpochBegins(ctx context.Context, epoch uint64) { if k.hooks != nil { k.hooks.AfterEpochBegins(ctx, epoch) } } // AfterEpochEnds - call hook if registered -func (k Keeper) AfterEpochEnds(ctx sdk.Context, epoch uint64) { +func (k Keeper) AfterEpochEnds(ctx context.Context, epoch uint64) { if k.hooks != nil { k.hooks.AfterEpochEnds(ctx, epoch) } } // BeforeSlashThreshold triggers the BeforeSlashThreshold hook for other modules that register this hook -func (k Keeper) BeforeSlashThreshold(ctx sdk.Context, valSet types.ValidatorSet) { +func (k Keeper) BeforeSlashThreshold(ctx context.Context, valSet types.ValidatorSet) { if k.hooks != nil { k.hooks.BeforeSlashThreshold(ctx, valSet) } @@ -44,7 +46,8 @@ var _ checkpointingtypes.CheckpointingHooks = Hooks{} func (k Keeper) Hooks() Hooks { return Hooks{k} } // BeforeValidatorSlashed records the slash event -func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) error { +func (h Hooks) BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction math.LegacyDec) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) thresholds := []float64{float64(1) / float64(3), float64(2) / float64(3)} epochNumber := h.k.GetEpoch(ctx).EpochNumber @@ -68,7 +71,7 @@ func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, f slashedVals := h.k.GetSlashedValidators(ctx, epochNumber) slashedVals = append(slashedVals, thisVal) event := types.NewEventSlashThreshold(slashedVotingPower, totalVotingPower, slashedVals) - if err := ctx.EventManager().EmitTypedEvent(&event); err != nil { + if err := sdkCtx.EventManager().EmitTypedEvent(&event); err != nil { panic(err) } h.k.BeforeSlashThreshold(ctx, slashedVals) @@ -84,57 +87,59 @@ func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, f return nil } -func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) error { - return h.k.RecordNewValState(ctx, valAddr, types.BondState_CREATED) +func (h Hooks) AfterValidatorCreated(ctx context.Context, valAddr sdk.ValAddress) error { + return h.k.RecordNewValState(sdk.UnwrapSDKContext(ctx), valAddr, types.BondState_CREATED) } -func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { - return h.k.RecordNewValState(ctx, valAddr, types.BondState_REMOVED) +func (h Hooks) AfterValidatorRemoved(ctx context.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { + return h.k.RecordNewValState(sdk.UnwrapSDKContext(ctx), valAddr, types.BondState_REMOVED) } -func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { - return h.k.RecordNewValState(ctx, valAddr, types.BondState_BONDED) +func (h Hooks) AfterValidatorBonded(ctx context.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { + return h.k.RecordNewValState(sdk.UnwrapSDKContext(ctx), valAddr, types.BondState_BONDED) } -func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { - return h.k.RecordNewValState(ctx, valAddr, types.BondState_UNBONDING) +func (h Hooks) AfterValidatorBeginUnbonding(ctx context.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error { + return h.k.RecordNewValState(sdk.UnwrapSDKContext(ctx), valAddr, types.BondState_UNBONDING) } -func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { - return h.k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_REMOVED) +func (h Hooks) BeforeDelegationRemoved(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { + return h.k.RecordNewDelegationState(sdk.UnwrapSDKContext(ctx), delAddr, valAddr, types.BondState_REMOVED) } -func (h Hooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error { - // finalise all unbonding validators/delegations in this epoch - h.k.ApplyMatureUnbonding(ctx, epoch) +func (h Hooks) AfterUnbondingInitiated(ctx context.Context, id uint64) error { return nil } // Other hooks that are not used in the epoching module -func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) error { +func (h Hooks) BeforeValidatorModified(ctx context.Context, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { +func (h Hooks) BeforeDelegationCreated(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { +func (h Hooks) BeforeDelegationSharesModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { +func (h Hooks) AfterDelegationModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error { return nil } - -func (h Hooks) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +// Checkpointing hooks +func (h Hooks) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { + // finalise all unbonding validators/delegations in this epoch + h.k.ApplyMatureUnbonding(ctx, epoch) return nil } -func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +func (h Hooks) AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error { return nil } + +func (h Hooks) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { return nil } + +func (h Hooks) AfterRawCheckpointForgotten(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return nil } -func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { +func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return nil } diff --git a/x/epoching/keeper/keeper.go b/x/epoching/keeper/keeper.go index dbe78f4f9..11741f489 100644 --- a/x/epoching/keeper/keeper.go +++ b/x/epoching/keeper/keeper.go @@ -2,25 +2,23 @@ package keeper import ( "fmt" - - "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + corestoretypes "cosmossdk.io/core/store" + "cosmossdk.io/log" "github.com/babylonchain/babylon/x/epoching/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey - hooks types.EpochingHooks - bk types.BankKeeper - stk types.StakingKeeper - router *baseapp.MsgServiceRouter + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService + hooks types.EpochingHooks + bk types.BankKeeper + stk types.StakingKeeper + router *baseapp.MsgServiceRouter // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -29,21 +27,19 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, bk types.BankKeeper, stk types.StakingKeeper, authority string, ) Keeper { return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, - hooks: nil, - bk: bk, - stk: stk, - authority: authority, + cdc: cdc, + storeService: storeService, + hooks: nil, + bk: bk, + stk: stk, + authority: authority, } } diff --git a/x/epoching/keeper/keeper_test.go b/x/epoching/keeper/keeper_test.go index 64a9aae1c..807bdf5f5 100644 --- a/x/epoching/keeper/keeper_test.go +++ b/x/epoching/keeper/keeper_test.go @@ -16,11 +16,11 @@ func TestParams(t *testing.T) { expParams := types.DefaultParams() - //check that the empty keeper loads the default + // check that the empty keeper loads the default resParams := helper.EpochingKeeper.GetParams(ctx) require.True(t, expParams.Equal(resParams)) - //modify a params, save, and retrieve + // modify a params, save, and retrieve expParams.EpochInterval = 777 if err := keeper.SetParams(ctx, expParams); err != nil { diff --git a/x/epoching/keeper/lifecycle_delegation.go b/x/epoching/keeper/lifecycle_delegation.go index 1e5896ba5..0e6b5b4cc 100644 --- a/x/epoching/keeper/lifecycle_delegation.go +++ b/x/epoching/keeper/lifecycle_delegation.go @@ -1,15 +1,17 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // TODO: add more tests on the lifecycle record // RecordNewDelegationState adds a state for a delegation lifecycle, including created, bonded, unbonding and unbonded -func (k Keeper) RecordNewDelegationState(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, state types.BondState) error { +func (k Keeper) RecordNewDelegationState(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, state types.BondState) error { lc := k.GetDelegationLifecycle(ctx, delAddr) if lc == nil { lc = &types.DelegationLifecycle{ @@ -17,7 +19,8 @@ func (k Keeper) RecordNewDelegationState(ctx sdk.Context, delAddr sdk.AccAddress DelLife: []*types.DelegationStateUpdate{}, } } - height, time := ctx.BlockHeight(), ctx.BlockTime() + sdkCtx := sdk.UnwrapSDKContext(ctx) + height, time := sdkCtx.BlockHeight(), sdkCtx.BlockTime() DelegationStateUpdate := types.DelegationStateUpdate{ State: state, ValAddr: valAddr.String(), @@ -29,15 +32,15 @@ func (k Keeper) RecordNewDelegationState(ctx sdk.Context, delAddr sdk.AccAddress return nil } -func (k Keeper) SetDelegationLifecycle(ctx sdk.Context, delAddr sdk.AccAddress, lc *types.DelegationLifecycle) { +func (k Keeper) SetDelegationLifecycle(ctx context.Context, delAddr sdk.AccAddress, lc *types.DelegationLifecycle) { store := k.delegationLifecycleStore(ctx) lcBytes := k.cdc.MustMarshal(lc) - store.Set([]byte(delAddr), lcBytes) + store.Set(delAddr, lcBytes) } -func (k Keeper) GetDelegationLifecycle(ctx sdk.Context, delAddr sdk.AccAddress) *types.DelegationLifecycle { +func (k Keeper) GetDelegationLifecycle(ctx context.Context, delAddr sdk.AccAddress) *types.DelegationLifecycle { store := k.delegationLifecycleStore(ctx) - lcBytes := store.Get([]byte(delAddr)) + lcBytes := store.Get(delAddr) if len(lcBytes) == 0 { return nil } @@ -50,7 +53,7 @@ func (k Keeper) GetDelegationLifecycle(ctx sdk.Context, delAddr sdk.AccAddress) // prefix: DelegationLifecycleKey // key: del_addr // value: DelegationLifecycle object -func (k Keeper) delegationLifecycleStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.DelegationLifecycleKey) +func (k Keeper) delegationLifecycleStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.DelegationLifecycleKey) } diff --git a/x/epoching/keeper/lifecycle_validator.go b/x/epoching/keeper/lifecycle_validator.go index 637f69810..07fc6b860 100644 --- a/x/epoching/keeper/lifecycle_validator.go +++ b/x/epoching/keeper/lifecycle_validator.go @@ -1,8 +1,10 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -28,15 +30,15 @@ func (k Keeper) RecordNewValState(ctx sdk.Context, valAddr sdk.ValAddress, state return nil } -func (k Keeper) SetValLifecycle(ctx sdk.Context, valAddr sdk.ValAddress, lc *types.ValidatorLifecycle) { +func (k Keeper) SetValLifecycle(ctx context.Context, valAddr sdk.ValAddress, lc *types.ValidatorLifecycle) { store := k.valLifecycleStore(ctx) lcBytes := k.cdc.MustMarshal(lc) - store.Set([]byte(valAddr), lcBytes) + store.Set(valAddr, lcBytes) } -func (k Keeper) GetValLifecycle(ctx sdk.Context, valAddr sdk.ValAddress) *types.ValidatorLifecycle { +func (k Keeper) GetValLifecycle(ctx context.Context, valAddr sdk.ValAddress) *types.ValidatorLifecycle { store := k.valLifecycleStore(ctx) - lcBytes := store.Get([]byte(valAddr)) + lcBytes := store.Get(valAddr) if len(lcBytes) == 0 { return nil } @@ -49,7 +51,7 @@ func (k Keeper) GetValLifecycle(ctx sdk.Context, valAddr sdk.ValAddress) *types. // prefix: ValidatorLifecycleKey // key: val_addr // value: ValidatorLifecycle object -func (k Keeper) valLifecycleStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.ValidatorLifecycleKey) +func (k Keeper) valLifecycleStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.ValidatorLifecycleKey) } diff --git a/x/epoching/keeper/modified_staking.go b/x/epoching/keeper/modified_staking.go index b68b1c22f..23fd3134e 100644 --- a/x/epoching/keeper/modified_staking.go +++ b/x/epoching/keeper/modified_staking.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "github.com/babylonchain/babylon/x/epoching/types" @@ -17,9 +18,10 @@ import ( // - an unbonding/redelegation becomes mature when its corresponding epoch and all previous epochs have been checkpointed. // Triggered by the checkpointing module upon the above condition. // (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/keeper/val_state_change.go#L32-L91) -func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { +func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) // save the current ctx for emitting events and recording lifecycle - currentCtx := ctx + currentSdkCtx := sdkCtx // get the ctx of the last block of the given epoch, while offsetting the time to nullify UnbondingTime finalizedEpoch, err := k.GetHistoricalEpoch(ctx, epochNumber) @@ -27,21 +29,32 @@ func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { panic(err) } epochBoundaryHeader := finalizedEpoch.LastBlockHeader - epochBoundaryHeader.Time = epochBoundaryHeader.Time.Add(k.stk.GetParams(ctx).UnbondingTime) // nullifies the effect of UnbondingTime in staking module - ctx = ctx.WithBlockHeader(*epochBoundaryHeader) + params, err := k.stk.GetParams(ctx) + if err != nil { + panic(err) + } + epochBoundaryHeader.Time = epochBoundaryHeader.Time.Add(params.UnbondingTime) // nullifies the effect of UnbondingTime in staking module + ctx = sdkCtx.WithBlockHeader(*epochBoundaryHeader) // unbond all mature validators till the last block of the given epoch - matureValidators := k.getAllMatureValidators(ctx) - currentCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following unbonding validators matured in epoch %d: %v", epochNumber, matureValidators)) - k.stk.UnbondAllMatureValidators(ctx) + matureValidators := k.getAllMatureValidators(sdkCtx) + currentSdkCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following unbonding validators matured in epoch %d: %v", epochNumber, matureValidators)) + if err := k.stk.UnbondAllMatureValidators(ctx); err != nil { + panic(err) + } // record state update of being UNBONDED for mature validators for _, valAddr := range matureValidators { - k.RecordNewValState(currentCtx, valAddr, types.BondState_UNBONDED) //nolint:errcheck // either we ignore the error here, or propoagate up the stack + if err := k.RecordNewValState(currentSdkCtx, valAddr, types.BondState_UNBONDED); err != nil { + panic(err) + } } // get all mature unbonding delegations the epoch boundary from the ubd queue. - matureUnbonds := k.stk.DequeueAllMatureUBDQueue(ctx, epochBoundaryHeader.Time) - currentCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following unbonding delegations matured in epoch %d: %v", epochNumber, matureUnbonds)) + matureUnbonds, err := k.stk.DequeueAllMatureUBDQueue(ctx, epochBoundaryHeader.Time) + if err != nil { + panic(err) + } + currentSdkCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following unbonding delegations matured in epoch %d: %v", epochNumber, matureUnbonds)) // unbond all mature delegations for _, dvPair := range matureUnbonds { @@ -60,9 +73,11 @@ func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { // Babylon modification: record delegation state // AFTER mature, unbonded from the validator - k.RecordNewDelegationState(currentCtx, delAddr, valAddr, types.BondState_UNBONDED) //nolint:errcheck // either we ignore the error here, or propoagate up the stack + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valAddr, types.BondState_UNBONDED); err != nil { + panic(err) + } - currentCtx.EventManager().EmitEvent( + currentSdkCtx.EventManager().EmitEvent( sdk.NewEvent( stakingtypes.EventTypeCompleteUnbonding, sdk.NewAttribute(sdk.AttributeKeyAmount, balances.String()), @@ -73,8 +88,11 @@ func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { } // get all mature redelegations till the epoch boundary from the red queue. - matureRedelegations := k.stk.DequeueAllMatureRedelegationQueue(ctx, epochBoundaryHeader.Time) - currentCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following redelegations matured in epoch %d: %v", epochNumber, matureRedelegations)) + matureRedelegations, err := k.stk.DequeueAllMatureRedelegationQueue(ctx, epochBoundaryHeader.Time) + if err != nil { + panic(err) + } + currentSdkCtx.Logger().Info(fmt.Sprintf("Epoching: start completing the following redelegations matured in epoch %d: %v", epochNumber, matureRedelegations)) // finish all mature redelegations for _, dvvTriplet := range matureRedelegations { @@ -102,11 +120,17 @@ func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { // Babylon modification: record delegation state // AFTER mature, unbonded from the source validator, created/bonded to the destination validator - k.RecordNewDelegationState(currentCtx, delAddr, valSrcAddr, types.BondState_UNBONDED) //nolint:errcheck // either we ignore the error here, or propoagate up the stack - k.RecordNewDelegationState(currentCtx, delAddr, valDstAddr, types.BondState_CREATED) //nolint:errcheck // either we ignore the error here, or propoagate up the stack - k.RecordNewDelegationState(currentCtx, delAddr, valDstAddr, types.BondState_BONDED) //nolint:errcheck // either we ignore the error here, or propoagate up the stack + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valSrcAddr, types.BondState_UNBONDED); err != nil { + panic(err) + } + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, types.BondState_CREATED); err != nil { + panic(err) + } + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, types.BondState_BONDED); err != nil { + panic(err) + } - currentCtx.EventManager().EmitEvent( + currentSdkCtx.EventManager().EmitEvent( sdk.NewEvent( stakingtypes.EventTypeCompleteRedelegation, sdk.NewAttribute(sdk.AttributeKeyAmount, balances.String()), @@ -126,7 +150,7 @@ func (k Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochNumber uint64) { // * Updates relevant indices. // Triggered upon every epoch. // (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/keeper/val_state_change.go#L18-L30) -func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) []abci.ValidatorUpdate { +func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx context.Context) []abci.ValidatorUpdate { validatorUpdates, err := k.stk.ApplyAndReturnValidatorSetUpdates(ctx) if err != nil { panic(err) @@ -148,7 +172,10 @@ func (k Keeper) getAllMatureValidators(ctx sdk.Context) []sdk.ValAddress { // ValidatorQueueKey | timeBzLen (8-byte big endian) | timeBz | heightBz (8-byte big endian), // so it may be possible that certain validator addresses that are iterated // over are not ready to unbond, so an explicit check is required. - unbondingValIterator := k.stk.ValidatorQueueIterator(ctx, blockTime, blockHeight) + unbondingValIterator, err := k.stk.ValidatorQueueIterator(ctx, blockTime, blockHeight) + if err != nil { + panic(fmt.Errorf("could not get iterator to validator's queue: %s", err)) + } defer unbondingValIterator.Close() for ; unbondingValIterator.Valid(); unbondingValIterator.Next() { @@ -167,9 +194,9 @@ func (k Keeper) getAllMatureValidators(ctx sdk.Context) []sdk.ValAddress { if err != nil { panic(err) } - val, found := k.stk.GetValidator(ctx, addr) - if !found { - panic("validator in the unbonding queue was not found") + val, err := k.stk.GetValidator(ctx, addr) + if err != nil { + panic(err) } if !val.IsUnbonding() { diff --git a/x/epoching/keeper/msg_server.go b/x/epoching/keeper/msg_server.go index 17c2e1dec..44890e382 100644 --- a/x/epoching/keeper/msg_server.go +++ b/x/epoching/keeper/msg_server.go @@ -9,7 +9,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) type msgServer struct { @@ -25,7 +24,7 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} // WrappedDelegate handles the MsgWrappedDelegate request -func (k msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedDelegate) (*types.MsgWrappedDelegateResponse, error) { +func (ms msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedDelegate) (*types.MsgWrappedDelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // verification rules ported from staking module @@ -33,20 +32,23 @@ func (k msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedD if valErr != nil { return nil, valErr } - if _, found := k.stk.GetValidator(ctx, valAddr); !found { - return nil, stakingtypes.ErrNoValidatorFound + if _, err := ms.stk.GetValidator(ctx, valAddr); err != nil { + return nil, err } if _, err := sdk.AccAddressFromBech32(msg.Msg.DelegatorAddress); err != nil { return nil, err } - bondDenom := k.stk.BondDenom(ctx) + bondDenom, err := ms.stk.BondDenom(ctx) + if err != nil { + return nil, err + } if msg.Msg.Amount.Denom != bondDenom { return nil, errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Msg.Amount.Denom, bondDenom, ) } - blockHeight := uint64(ctx.BlockHeight()) + blockHeight := uint64(ctx.BlockHeader().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } @@ -58,7 +60,7 @@ func (k msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedD return nil, err } - k.EnqueueMsg(ctx, queuedMsg) + ms.EnqueueMsg(ctx, queuedMsg) err = ctx.EventManager().EmitTypedEvents( &types.EventWrappedDelegate{ @@ -66,7 +68,7 @@ func (k msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedD ValidatorAddress: msg.Msg.ValidatorAddress, Amount: msg.Msg.Amount.Amount.Uint64(), Denom: msg.Msg.Amount.GetDenom(), - EpochBoundary: k.GetEpoch(ctx).GetLastBlockHeight(), + EpochBoundary: ms.GetEpoch(ctx).GetLastBlockHeight(), }, ) if err != nil { @@ -77,7 +79,7 @@ func (k msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedD } // WrappedUndelegate handles the MsgWrappedUndelegate request -func (k msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappedUndelegate) (*types.MsgWrappedUndelegateResponse, error) { +func (ms msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappedUndelegate) (*types.MsgWrappedUndelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // verification rules ported from staking module @@ -89,17 +91,20 @@ func (k msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappe if err != nil { return nil, err } - if _, err := k.stk.ValidateUnbondAmount(ctx, delegatorAddress, valAddr, msg.Msg.Amount.Amount); err != nil { + if _, err := ms.stk.ValidateUnbondAmount(ctx, delegatorAddress, valAddr, msg.Msg.Amount.Amount); err != nil { + return nil, err + } + bondDenom, err := ms.stk.BondDenom(ctx) + if err != nil { return nil, err } - bondDenom := k.stk.BondDenom(ctx) if msg.Msg.Amount.Denom != bondDenom { return nil, errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Msg.Amount.Denom, bondDenom, ) } - blockHeight := uint64(ctx.BlockHeight()) + blockHeight := uint64(ctx.BlockHeader().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } @@ -111,7 +116,7 @@ func (k msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappe return nil, err } - k.EnqueueMsg(ctx, queuedMsg) + ms.EnqueueMsg(ctx, queuedMsg) err = ctx.EventManager().EmitTypedEvents( &types.EventWrappedUndelegate{ @@ -119,7 +124,7 @@ func (k msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappe ValidatorAddress: msg.Msg.ValidatorAddress, Amount: msg.Msg.Amount.Amount.Uint64(), Denom: msg.Msg.Amount.GetDenom(), - EpochBoundary: k.GetEpoch(ctx).GetLastBlockHeight(), + EpochBoundary: ms.GetEpoch(ctx).GetLastBlockHeight(), }, ) if err != nil { @@ -130,7 +135,7 @@ func (k msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappe } // WrappedBeginRedelegate handles the MsgWrappedBeginRedelegate request -func (k msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgWrappedBeginRedelegate) (*types.MsgWrappedBeginRedelegateResponse, error) { +func (ms msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgWrappedBeginRedelegate) (*types.MsgWrappedBeginRedelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // verification rules ported from staking module @@ -142,10 +147,13 @@ func (k msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgW if err != nil { return nil, err } - if _, err := k.stk.ValidateUnbondAmount(ctx, delegatorAddress, valSrcAddr, msg.Msg.Amount.Amount); err != nil { + if _, err := ms.stk.ValidateUnbondAmount(ctx, delegatorAddress, valSrcAddr, msg.Msg.Amount.Amount); err != nil { + return nil, err + } + bondDenom, err := ms.stk.BondDenom(ctx) + if err != nil { return nil, err } - bondDenom := k.stk.BondDenom(ctx) if msg.Msg.Amount.Denom != bondDenom { return nil, errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Msg.Amount.Denom, bondDenom, @@ -155,7 +163,7 @@ func (k msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgW return nil, err } - blockHeight := uint64(ctx.BlockHeight()) + blockHeight := uint64(ctx.BlockHeader().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } @@ -167,7 +175,7 @@ func (k msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgW return nil, err } - k.EnqueueMsg(ctx, queuedMsg) + ms.EnqueueMsg(ctx, queuedMsg) err = ctx.EventManager().EmitTypedEvents( &types.EventWrappedBeginRedelegate{ DelegatorAddress: msg.Msg.DelegatorAddress, @@ -175,7 +183,7 @@ func (k msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgW DestinationValidatorAddress: msg.Msg.ValidatorDstAddress, Amount: msg.Msg.Amount.Amount.Uint64(), Denom: msg.Msg.Amount.GetDenom(), - EpochBoundary: k.GetEpoch(ctx).GetLastBlockHeight(), + EpochBoundary: ms.GetEpoch(ctx).GetLastBlockHeight(), }, ) if err != nil { diff --git a/x/epoching/keeper/msg_server_test.go b/x/epoching/keeper/msg_server_test.go index 46057efe5..e08652432 100644 --- a/x/epoching/keeper/msg_server_test.go +++ b/x/epoching/keeper/msg_server_test.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" ) @@ -18,8 +17,8 @@ func TestMsgWrappedDelegate(t *testing.T) { helper := testepoching.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx := helper.GenAndApplyEmptyBlock(r) - wctx := sdk.WrapSDKContext(ctx) + ctx, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) testCases := []struct { name string @@ -34,7 +33,7 @@ func TestMsgWrappedDelegate(t *testing.T) { } for _, tc := range testCases { wrappedMsg := types.NewMsgWrappedDelegate(tc.req) - _, err := msgSrvr.WrappedDelegate(wctx, wrappedMsg) + _, err := msgSrvr.WrappedDelegate(ctx, wrappedMsg) if tc.expectErr { require.Error(t, err) } else { @@ -48,8 +47,8 @@ func TestMsgWrappedUndelegate(t *testing.T) { helper := testepoching.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx := helper.GenAndApplyEmptyBlock(r) - wctx := sdk.WrapSDKContext(ctx) + ctx, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) testCases := []struct { name string @@ -64,7 +63,7 @@ func TestMsgWrappedUndelegate(t *testing.T) { } for _, tc := range testCases { wrappedMsg := types.NewMsgWrappedUndelegate(tc.req) - _, err := msgSrvr.WrappedUndelegate(wctx, wrappedMsg) + _, err := msgSrvr.WrappedUndelegate(ctx, wrappedMsg) if tc.expectErr { require.Error(t, err) } else { @@ -78,8 +77,8 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { helper := testepoching.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx := helper.GenAndApplyEmptyBlock(r) - wctx := sdk.WrapSDKContext(ctx) + ctx, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) testCases := []struct { name string @@ -95,7 +94,7 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { for _, tc := range testCases { wrappedMsg := types.NewMsgWrappedBeginRedelegate(tc.req) - _, err := msgSrvr.WrappedBeginRedelegate(wctx, wrappedMsg) + _, err := msgSrvr.WrappedBeginRedelegate(ctx, wrappedMsg) if tc.expectErr { require.Error(t, err) } else { diff --git a/x/epoching/keeper/params.go b/x/epoching/keeper/params.go index 255ea6b27..3f0a67762 100644 --- a/x/epoching/keeper/params.go +++ b/x/epoching/keeper/params.go @@ -1,28 +1,33 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params // SetParams sets the x/epoching module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) + err := store.Set(types.ParamsKey, bz) + if err != nil { + return err + } return nil } // GetParams returns the current x/epoching module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } diff --git a/x/epoching/keeper/staking_functions.go b/x/epoching/keeper/staking_functions.go index 3ff138444..ec5e9d0b7 100644 --- a/x/epoching/keeper/staking_functions.go +++ b/x/epoching/keeper/staking_functions.go @@ -1,11 +1,12 @@ package keeper import ( + "context" + errorsmod "cosmossdk.io/errors" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/epoching/types" ) @@ -14,7 +15,8 @@ import ( // The checkpointing module will use this function to verify the `MsgCreateValidator` message // inside a `MsgWrappedCreateValidator` message. // (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.46.10/x/staking/keeper/msg_server.go#L34-L108) -func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCreateValidator) error { +func (k Keeper) CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) // ensure validator address is correctly encoded valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) if err != nil { @@ -22,7 +24,10 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr } // get parameters of the staking module - sParams := k.stk.GetParams(ctx) + sParams, err := k.stk.GetParams(ctx) + if err != nil { + return err + } // check commission rate if msg.Commission.Rate.LT(sParams.MinCommissionRate) { @@ -30,7 +35,7 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr } // ensure the validator operator was not registered before - if _, found := k.stk.GetValidator(ctx, valAddr); found { + if _, err := k.stk.GetValidator(ctx, valAddr); err == nil { return stakingtypes.ErrValidatorOwnerExists } @@ -41,7 +46,7 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr } // ensure the validator was not registered before - if _, found := k.stk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found { + if _, err := k.stk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); err == nil { return stakingtypes.ErrValidatorPubKeyExists } @@ -58,8 +63,8 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr } // ensure public key type is supported - cp := ctx.ConsensusParams() - if cp != nil && cp.Validator != nil { + cp := sdkCtx.ConsensusParams() + if cp.Validator != nil { pkType := pk.Type() hasKeyType := false for _, keyType := range cp.Validator.PubKeyTypes { @@ -77,7 +82,7 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr } // check validator - validator, err := stakingtypes.NewValidator(valAddr, pk, msg.Description) + validator, err := stakingtypes.NewValidator(valAddr.String(), pk, msg.Description) if err != nil { return err } @@ -85,14 +90,14 @@ func (k Keeper) CheckMsgCreateValidator(ctx sdk.Context, msg *stakingtypes.MsgCr // check if SetInitialCommission fails or not commission := stakingtypes.NewCommissionWithTime( msg.Commission.Rate, msg.Commission.MaxRate, - msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, + msg.Commission.MaxChangeRate, sdkCtx.BlockHeader().Time, ) if _, err := validator.SetInitialCommission(commission); err != nil { return err } - // sanity check on delegator address - delegatorAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddress) + // sanity check on delegator address -- delegator is the same as validator + delegatorAddr := sdk.AccAddress(valAddr) if err != nil { return err } diff --git a/x/epoching/module.go b/x/epoching/module.go index f8215345c..34f93dcdb 100644 --- a/x/epoching/module.go +++ b/x/epoching/module.go @@ -2,6 +2,7 @@ package epoching import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -22,8 +23,9 @@ import ( ) var ( - _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} _ module.AppModuleSimulation = AppModule{} ) @@ -64,7 +66,7 @@ func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { } // ValidateGenesis performs genesis state validation for the capability module. -func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { var genState types.GenesisState if err := cdc.UnmarshalJSON(bz, &genState); err != nil { return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) @@ -94,6 +96,7 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { // ---------------------------------------------------------------------------- // AppModule // ---------------------------------------------------------------------------- +var _ appmodule.AppModule = AppModule{} // AppModule implements the AppModule interface for the capability module. type AppModule struct { @@ -141,14 +144,12 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the capability module's genesis initialization It returns // no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. @@ -160,10 +161,18 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 1 } -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { return EndBlocker(ctx, am.keeper) } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker +} diff --git a/x/epoching/module_simulation.go b/x/epoching/module_simulation.go index f5591fede..0b622d3e4 100644 --- a/x/epoching/module_simulation.go +++ b/x/epoching/module_simulation.go @@ -1,11 +1,9 @@ package epoching import ( - simappparams "github.com/babylonchain/babylon/app/params" epochingsimulation "github.com/babylonchain/babylon/x/epoching/simulation" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -14,7 +12,6 @@ import ( // avoid unused import issue var ( _ = epochingsimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -30,7 +27,7 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = epochingsimulation.NewDecodeStore(am.cdc) } diff --git a/x/epoching/simulation/decoder_test.go b/x/epoching/simulation/decoder_test.go index 79f173215..a93085931 100644 --- a/x/epoching/simulation/decoder_test.go +++ b/x/epoching/simulation/decoder_test.go @@ -1,7 +1,9 @@ package simulation_test import ( + sdkmath "cosmossdk.io/math" "fmt" + "github.com/babylonchain/babylon/app" "testing" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -12,22 +14,18 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/kv" - "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/x/epoching/simulation" "github.com/babylonchain/babylon/x/epoching/types" ) -// nolint:deadcode,unused,varcheck var ( delPk1 = ed25519.GenPrivKey().PubKey() - delAddr1 = sdk.AccAddress(delPk1.Address()) valAddr1 = sdk.ValAddress(delPk1.Address()) - consAddr1 = sdk.ConsAddress(delPk1.Address().Bytes()) - oneBytes, _ = sdk.NewInt(1).Marshal() + oneBytes, _ = sdkmath.NewInt(1).Marshal() ) func TestDecodeStore(t *testing.T) { - cdc := app.GetEncodingConfig().Marshaler + cdc := app.GetEncodingConfig().Codec dec := simulation.NewDecodeStore(cdc) epochNumber := uint64(123) @@ -65,8 +63,8 @@ func TestDecodeStore(t *testing.T) { {"QueuedMsg", fmt.Sprintf("%v\n%v", queuedMsg.MsgId, queuedMsg.MsgId)}, {"ValidatorSet", fmt.Sprintf("%v\n%v", valSet, valSet)}, {"SlashedValidatorSet", fmt.Sprintf("%v\n%v", valSet, valSet)}, - {"VotingPower", fmt.Sprintf("%v\n%v", sdk.NewInt(1), sdk.NewInt(1))}, - {"SlashedVotingPower", fmt.Sprintf("%v\n%v", sdk.NewInt(1), sdk.NewInt(1))}, + {"VotingPower", fmt.Sprintf("%v\n%v", sdkmath.NewInt(1), sdkmath.NewInt(1))}, + {"SlashedVotingPower", fmt.Sprintf("%v\n%v", sdkmath.NewInt(1), sdkmath.NewInt(1))}, {"other", ""}, } for i, tt := range tests { diff --git a/x/epoching/simulation/genesis.go b/x/epoching/simulation/genesis.go index 05bbd0515..1adca826d 100644 --- a/x/epoching/simulation/genesis.go +++ b/x/epoching/simulation/genesis.go @@ -1,7 +1,5 @@ package simulation -// DONTCOVER - import ( "encoding/json" "fmt" @@ -25,7 +23,7 @@ func genEpochInterval(r *rand.Rand) uint64 { func RandomizedGenState(simState *module.SimulationState) { var epochInterval uint64 simState.AppParams.GetOrGenerate( - simState.Cdc, EpochIntervalKey, &epochInterval, simState.Rand, + EpochIntervalKey, &epochInterval, simState.Rand, func(r *rand.Rand) { epochInterval = genEpochInterval(r) }, ) params := types.NewParams(epochInterval) diff --git a/x/epoching/simulation/genesis_test.go b/x/epoching/simulation/genesis_test.go index feda296ef..3e8a7d10b 100644 --- a/x/epoching/simulation/genesis_test.go +++ b/x/epoching/simulation/genesis_test.go @@ -19,7 +19,7 @@ import ( ) // TestRandomizedGenState tests the normal scenario of applying RandomizedGenState given a fixed seed. -// Abonormal scenarios are not tested here. +// Abnormal scenarios are not tested here. func TestRandomizedGenState(t *testing.T) { interfaceRegistry := codectypes.NewInterfaceRegistry() cryptocodec.RegisterInterfaces(interfaceRegistry) diff --git a/x/epoching/simulation/operations.go b/x/epoching/simulation/operations.go index e338bde0a..893364f88 100644 --- a/x/epoching/simulation/operations.go +++ b/x/epoching/simulation/operations.go @@ -33,19 +33,19 @@ func WeightedOperations( weightMsgWrappedBeginRedelegate int ) - appParams.GetOrGenerate(cdc, OpWeightMsgWrappedDelegate, &weightMsgWrappedDelegate, nil, + appParams.GetOrGenerate(OpWeightMsgWrappedDelegate, &weightMsgWrappedDelegate, nil, func(_ *rand.Rand) { weightMsgWrappedDelegate = simappparams.DefaultWeightMsgDelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs }, ) - appParams.GetOrGenerate(cdc, OpWeightMsgWrappedUndelegate, &weightMsgWrappedUndelegate, nil, + appParams.GetOrGenerate(OpWeightMsgWrappedUndelegate, &weightMsgWrappedUndelegate, nil, func(_ *rand.Rand) { weightMsgWrappedUndelegate = simappparams.DefaultWeightMsgUndelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs }, ) - appParams.GetOrGenerate(cdc, OpWeightMsgWrappedBeginRedelegate, &weightMsgWrappedBeginRedelegate, nil, + appParams.GetOrGenerate(OpWeightMsgWrappedBeginRedelegate, &weightMsgWrappedBeginRedelegate, nil, func(_ *rand.Rand) { weightMsgWrappedBeginRedelegate = simappparams.DefaultWeightMsgBeginRedelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs }, @@ -80,9 +80,9 @@ func SimulateMsgWrappedDelegate(ak types.AccountKeeper, bk types.BankKeeper, stk // pick a random validator i := r.Intn(len(valSet)) - val, ok := stk.GetValidator(ctx, valSet[i].Addr) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to pick a validator"), nil, nil + val, err := stk.GetValidator(ctx, valSet[i].Addr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to pick a validator"), nil, err } if val.InvalidExRate() { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "validator's invalid exchange rate"), nil, nil @@ -90,12 +90,16 @@ func SimulateMsgWrappedDelegate(ak types.AccountKeeper, bk types.BankKeeper, stk // pick a random bondAmt simAccount, _ := simtypes.RandomAcc(r, accs) - denom := stk.GetParams(ctx).BondDenom + params, err := stk.GetParams(ctx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to get params"), nil, err + } + denom := params.BondDenom amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount if !amount.IsPositive() { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "balance is negative"), nil, nil } - amount, err := simtypes.RandPositiveInt(r, amount) + amount, err = simtypes.RandPositiveInt(r, amount) if err != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to generate positive amount"), nil, err } @@ -113,14 +117,13 @@ func SimulateMsgWrappedDelegate(ak types.AccountKeeper, bk types.BankKeeper, stk } } - msg := stakingtypes.NewMsgDelegate(simAccount.Address, val.GetOperator(), bondAmt) + msg := stakingtypes.NewMsgDelegate(simAccount.Address.String(), val.GetOperator(), bondAmt) wmsg := types.NewMsgWrappedDelegate(msg) txCtx := simulation.OperationInput{ App: app, TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, Cdc: nil, Msg: wmsg, - MsgType: wmsg.Type(), Context: ctx, SimAccount: simAccount, AccountKeeper: ak, @@ -144,24 +147,36 @@ func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, s // pick a random validator i := r.Intn(len(valSet)) - val, ok := stk.GetValidator(ctx, valSet[i].Addr) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to pick a validator"), nil, nil + val, err := stk.GetValidator(ctx, valSet[i].Addr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to pick a validator"), nil, err } if val.InvalidExRate() { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "validator's invalid exchange rate"), nil, nil } // pick a random delegator from validator - valAddr := val.GetOperator() - delegations := stk.GetValidatorDelegations(ctx, val.GetOperator()) + valAddr, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err + } + delegations, err := stk.GetValidatorDelegations(ctx, valAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator delegations"), nil, err + } if delegations == nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "keeper does not have any delegation entries"), nil, nil } delegation := delegations[r.Intn(len(delegations))] - delAddr := delegation.GetDelegatorAddr() - - if stk.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) { + delAddr, err := sdk.AccAddressFromBech32(delegation.GetDelegatorAddr()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get delegation address"), nil, err + } + hasMaxUnbEntries, err := stk.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "could not check whether delegator has max unbonding entries"), nil, err + } + if hasMaxUnbEntries { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "keeper reaches max unbonding delegation entries"), nil, nil } @@ -178,8 +193,12 @@ func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, s return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unbond amount is zero"), nil, nil } + bondDenom, err := stk.BondDenom(ctx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get bond denomination"), nil, err + } msg := stakingtypes.NewMsgUndelegate( - delAddr, valAddr, sdk.NewCoin(stk.BondDenom(ctx), unbondAmt), + delAddr.String(), valAddr.String(), sdk.NewCoin(bondDenom, unbondAmt), ) wmsg := types.NewMsgWrappedUndelegate(msg) @@ -194,7 +213,7 @@ func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, s } // if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error if simAccount.PrivKey == nil { - return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) + return simtypes.NoOpMsg(types.ModuleName, wmsg.Type(), "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) } account := ak.GetAccount(ctx, delAddr) @@ -206,7 +225,6 @@ func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, s TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, Cdc: nil, Msg: wmsg, - MsgType: wmsg.Type(), Context: ctx, SimAccount: simAccount, AccountKeeper: ak, @@ -219,7 +237,7 @@ func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, s } } -// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values +// SimulateMsgWrappedBeginRedelegate generates a MsgBeginRedelegate with random values func SimulateMsgWrappedBeginRedelegate(ak types.AccountKeeper, bk types.BankKeeper, stk types.StakingKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, @@ -232,33 +250,54 @@ func SimulateMsgWrappedBeginRedelegate(ak types.AccountKeeper, bk types.BankKeep // pick a random source validator i := r.Intn(len(valSet)) - srcVal, ok := stk.GetValidator(ctx, valSet[i].Addr) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, nil + srcVal, err := stk.GetValidator(ctx, valSet[i].Addr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, err } - srcAddr := srcVal.GetOperator() - delegations := stk.GetValidatorDelegations(ctx, srcAddr) + srcAddr, err := sdk.ValAddressFromBech32(srcVal.GetOperator()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err + } + + delegations, err := stk.GetValidatorDelegations(ctx, srcAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to find validator delegations"), nil, err + } if delegations == nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "keeper does have any delegation entries"), nil, nil } // pick a random delegator from src validator delegation := delegations[r.Intn(len(delegations))] - delAddr := delegation.GetDelegatorAddr() + delAddr, err := sdk.AccAddressFromBech32(delegation.GetDelegatorAddr()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get delegation address"), nil, err + } - if stk.HasReceivingRedelegation(ctx, delAddr, srcAddr) { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "receiving redelegation is not allowed"), nil, nil // skip + hasRedel, err := stk.HasReceivingRedelegation(ctx, delAddr, srcAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "could not check whether validator has redelegations"), nil, err + } + if hasRedel { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "receiving redelegation is not allowed"), nil, nil } // pick a random destination validator i = r.Intn(len(valSet)) - destVal, ok := stk.GetValidator(ctx, valSet[i].Addr) - if !ok { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, nil + destVal, err := stk.GetValidator(ctx, valSet[i].Addr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, err + } + destAddr, err := sdk.ValAddressFromBech32(destVal.GetOperator()) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err } - destAddr := destVal.GetOperator() - if srcAddr.Equals(destAddr) || destVal.InvalidExRate() || stk.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) { + hasMaxRedelEntries, err := stk.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to check whether delegator has max redelegations"), nil, err + } + if srcAddr.Equals(destAddr) || destVal.InvalidExRate() || hasMaxRedelEntries { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "checks failed"), nil, nil } @@ -303,9 +342,13 @@ func SimulateMsgWrappedBeginRedelegate(ak types.AccountKeeper, bk types.BankKeep account := ak.GetAccount(ctx, delAddr) spendable := bk.SpendableCoins(ctx, account.GetAddress()) + bondDenom, err := stk.BondDenom(ctx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "could not get bond denomination"), nil, err + } msg := stakingtypes.NewMsgBeginRedelegate( - delAddr, srcAddr, destAddr, - sdk.NewCoin(stk.BondDenom(ctx), redAmt), + delAddr.String(), srcAddr.String(), destAddr.String(), + sdk.NewCoin(bondDenom, redAmt), ) wmsg := types.NewMsgWrappedBeginRedelegate(msg) @@ -315,7 +358,6 @@ func SimulateMsgWrappedBeginRedelegate(ak types.AccountKeeper, bk types.BankKeep TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, Cdc: nil, Msg: wmsg, - MsgType: wmsg.Type(), Context: ctx, SimAccount: simAccount, AccountKeeper: ak, diff --git a/x/epoching/testepoching/helper.go b/x/epoching/testepoching/helper.go index 44ac8672f..97c861b0c 100644 --- a/x/epoching/testepoching/helper.go +++ b/x/epoching/testepoching/helper.go @@ -4,8 +4,14 @@ import ( "math/rand" "testing" + "cosmossdk.io/core/header" "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/privval" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/ed25519" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "cosmossdk.io/math" proto "github.com/cosmos/gogoproto/proto" @@ -13,7 +19,6 @@ import ( appparams "github.com/babylonchain/babylon/app/params" - abci "github.com/cometbft/cometbft/abci/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -28,9 +33,9 @@ import ( "github.com/babylonchain/babylon/x/epoching/types" ) -type ValidatorInfo struct { - BlsKey bls12381.PrivateKey - Address sdk.ValAddress +type GenesisValidators struct { + GenesisKeys []*checkpointingtypes.GenesisKey + BlsPrivKeys []bls12381.PrivateKey } // Helper is a structure which wraps the entire app and exposes functionalities for testing the epoching module @@ -44,25 +49,25 @@ type Helper struct { QueryClient types.QueryClient StakingKeeper *stakingkeeper.Keeper - GenAccs []authtypes.GenesisAccount - ValBlsPrivKeys []ValidatorInfo + GenAccs []authtypes.GenesisAccount + GenValidators *GenesisValidators } // NewHelper creates the helper for testing the epoching module func NewHelper(t *testing.T) *Helper { - app := app.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + valSet, err := GenesisValidatorSet(1) + require.NoError(t, err) + // generate genesis account + acc := authtypes.NewBaseAccount(valSet.GenesisKeys[0].ValPubkey.Address().Bytes(), valSet.GenesisKeys[0].ValPubkey, 0, 0) + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), + } - epochingKeeper := app.EpochingKeeper + app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, []authtypes.GenesisAccount{acc}, balance) + ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 - // add BLS pubkey to the genesis validator - valSet := epochingKeeper.GetValidatorSet(ctx, 0) - require.Len(t, valSet, 1) - genesisVal := valSet[0] - blsPrivKey := bls12381.GenPrivKey() - genesisBLSPubkey := blsPrivKey.PubKey() - err := app.CheckpointingKeeper.CreateRegistration(ctx, genesisBLSPubkey, genesisVal.Addr) - require.NoError(t, err) + epochingKeeper := app.EpochingKeeper querier := keeper.Querier{Keeper: epochingKeeper} queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) @@ -79,17 +84,14 @@ func NewHelper(t *testing.T) *Helper { queryClient, app.StakingKeeper, nil, - []ValidatorInfo{ValidatorInfo{ - blsPrivKey, - genesisVal.Addr, - }}, + valSet, } } // NewHelperWithValSet is same as NewHelper, except that it creates a set of validators func NewHelperWithValSet(t *testing.T) *Helper { // generate the validator set with 10 validators - tmValSet, err := GenTmValidatorSet(10) + valSet, err := GenesisValidatorSet(10) require.NoError(t, err) // generate the genesis account @@ -103,77 +105,77 @@ func NewHelperWithValSet(t *testing.T) *Helper { GenAccs := []authtypes.GenesisAccount{acc} // setup the app and ctx - app := app.SetupWithGenesisValSet(t, tmValSet, GenAccs, balance) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, GenAccs, balance) + ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 // get necessary subsets of the app/keeper epochingKeeper := app.EpochingKeeper - valInfos := []ValidatorInfo{} - // add BLS pubkey to the genesis validator - valSet := epochingKeeper.GetValidatorSet(ctx, 0) - for _, val := range valSet { - blsPrivKey := bls12381.GenPrivKey() - valInfos = append(valInfos, ValidatorInfo{blsPrivKey, val.Addr}) - blsPubkey := blsPrivKey.PubKey() - err = app.CheckpointingKeeper.CreateRegistration(ctx, blsPubkey, val.Addr) - require.NoError(t, err) - } querier := keeper.Querier{Keeper: epochingKeeper} queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, querier) queryClient := types.NewQueryClient(queryHelper) msgSrvr := keeper.NewMsgServerImpl(epochingKeeper) - return &Helper{t, ctx, app, &epochingKeeper, msgSrvr, queryClient, app.StakingKeeper, GenAccs, valInfos} + return &Helper{ + t, + ctx, + app, + &epochingKeeper, + msgSrvr, + queryClient, + app.StakingKeeper, + GenAccs, + valSet, + } } // GenAndApplyEmptyBlock generates a new empty block and appends it to the current blockchain -func (h *Helper) GenAndApplyEmptyBlock(r *rand.Rand) sdk.Context { +func (h *Helper) GenAndApplyEmptyBlock(r *rand.Rand) (sdk.Context, error) { newHeight := h.App.LastBlockHeight() + 1 - valSet := h.StakingKeeper.GetLastValidators(h.Ctx) + valSet, err := h.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return sdk.Context{}, err + } valhash := CalculateValHash(valSet) newHeader := tmproto.Header{ Height: newHeight, ValidatorsHash: valhash, NextValidatorsHash: valhash, - AppHash: datagen.GenRandomByteArray(r, 32), - LastCommitHash: datagen.GenRandomLastCommitHash(r), } - h.App.BeginBlock(abci.RequestBeginBlock{Header: newHeader}) - h.App.EndBlock(abci.RequestEndBlock{}) - h.App.Commit() + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + }) + if err != nil { + return sdk.Context{}, err + } - h.Ctx = h.Ctx.WithBlockHeader(newHeader) - return h.Ctx -} + newHeader.AppHash = resp.AppHash + ctxDuringHeight := h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + }).WithBlockHeader(newHeader) -func (h *Helper) BeginBlock(r *rand.Rand) sdk.Context { - newHeight := h.App.LastBlockHeight() + 1 - valSet := h.StakingKeeper.GetLastValidators(h.Ctx) - valhash := CalculateValHash(valSet) - newHeader := tmproto.Header{ - Height: newHeight, - AppHash: datagen.GenRandomByteArray(r, 32), - ValidatorsHash: valhash, - NextValidatorsHash: valhash, + _, err = h.App.Commit() + if err != nil { + return sdk.Context{}, err } - h.App.BeginBlock(abci.RequestBeginBlock{Header: newHeader}) - h.Ctx = h.Ctx.WithBlockHeader(newHeader) - return h.Ctx -} + if newHeight == 1 { + // do it again + // TODO: Figure out why when ctx height is 1, GenAndApplyEmptyBlock + // will still give ctx height 1 once, then start to increment + return h.GenAndApplyEmptyBlock(r) + } -func (h *Helper) EndBlock() sdk.Context { - h.App.EndBlock(abci.RequestEndBlock{}) - h.App.Commit() - return h.Ctx + return ctxDuringHeight, nil } // WrappedDelegate calls handler to delegate stake for a validator func (h *Helper) WrappedDelegate(delegator sdk.AccAddress, val sdk.ValAddress, amount math.Int) *sdk.Result { coin := sdk.NewCoin(appparams.DefaultBondDenom, amount) - msg := stakingtypes.NewMsgDelegate(delegator, val, coin) + msg := stakingtypes.NewMsgDelegate(delegator.String(), val.String(), coin) wmsg := types.NewMsgWrappedDelegate(msg) return h.Handle(func(ctx sdk.Context) (proto.Message, error) { return h.MsgSrvr.WrappedDelegate(ctx, wmsg) @@ -183,7 +185,7 @@ func (h *Helper) WrappedDelegate(delegator sdk.AccAddress, val sdk.ValAddress, a // WrappedDelegateWithPower calls handler to delegate stake for a validator func (h *Helper) WrappedDelegateWithPower(delegator sdk.AccAddress, val sdk.ValAddress, power int64) *sdk.Result { coin := sdk.NewCoin(appparams.DefaultBondDenom, h.StakingKeeper.TokensFromConsensusPower(h.Ctx, power)) - msg := stakingtypes.NewMsgDelegate(delegator, val, coin) + msg := stakingtypes.NewMsgDelegate(delegator.String(), val.String(), coin) wmsg := types.NewMsgWrappedDelegate(msg) return h.Handle(func(ctx sdk.Context) (proto.Message, error) { return h.MsgSrvr.WrappedDelegate(ctx, wmsg) @@ -193,7 +195,7 @@ func (h *Helper) WrappedDelegateWithPower(delegator sdk.AccAddress, val sdk.ValA // WrappedUndelegate calls handler to unbound some stake from a validator. func (h *Helper) WrappedUndelegate(delegator sdk.AccAddress, val sdk.ValAddress, amount math.Int) *sdk.Result { unbondAmt := sdk.NewCoin(appparams.DefaultBondDenom, amount) - msg := stakingtypes.NewMsgUndelegate(delegator, val, unbondAmt) + msg := stakingtypes.NewMsgUndelegate(delegator.String(), val.String(), unbondAmt) wmsg := types.NewMsgWrappedUndelegate(msg) return h.Handle(func(ctx sdk.Context) (proto.Message, error) { return h.MsgSrvr.WrappedUndelegate(ctx, wmsg) @@ -203,7 +205,7 @@ func (h *Helper) WrappedUndelegate(delegator sdk.AccAddress, val sdk.ValAddress, // WrappedBeginRedelegate calls handler to redelegate some stake from a validator to another func (h *Helper) WrappedBeginRedelegate(delegator sdk.AccAddress, srcVal sdk.ValAddress, dstVal sdk.ValAddress, amount math.Int) *sdk.Result { unbondAmt := sdk.NewCoin(appparams.DefaultBondDenom, amount) - msg := stakingtypes.NewMsgBeginRedelegate(delegator, srcVal, dstVal, unbondAmt) + msg := stakingtypes.NewMsgBeginRedelegate(delegator.String(), srcVal.String(), dstVal.String(), unbondAmt) wmsg := types.NewMsgWrappedBeginRedelegate(msg) return h.Handle(func(ctx sdk.Context) (proto.Message, error) { return h.MsgSrvr.WrappedBeginRedelegate(ctx, wmsg) @@ -213,7 +215,9 @@ func (h *Helper) WrappedBeginRedelegate(delegator sdk.AccAddress, srcVal sdk.Val // Handle executes an action function with the Helper's context, wraps the result into an SDK service result, and performs two assertions before returning it func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Result { res, err := action(h.Ctx) - r, _ := sdk.WrapServiceResult(h.Ctx, res, err) + require.NoError(h.t, err) + r, err := sdk.WrapServiceResult(h.Ctx, res, err) + require.NoError(h.t, err) require.NotNil(h.t, r) require.NoError(h.t, err) return r @@ -222,8 +226,8 @@ func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Re // CheckValidator asserts that a validor exists and has a given status (if status!="") // and if has a right jailed flag. func (h *Helper) CheckValidator(addr sdk.ValAddress, status stakingtypes.BondStatus, jailed bool) stakingtypes.Validator { - v, ok := h.StakingKeeper.GetValidator(h.Ctx, addr) - require.True(h.t, ok) + v, err := h.StakingKeeper.GetValidator(h.Ctx, addr) + require.NoError(h.t, err) require.Equal(h.t, jailed, v.Jailed, "wrong Jalied status") if status >= 0 { require.Equal(h.t, status, v.Status) @@ -236,3 +240,36 @@ func (h *Helper) CheckDelegator(delegator sdk.AccAddress, val sdk.ValAddress, fo _, ok := h.StakingKeeper.GetDelegation(h.Ctx, delegator, val) require.Equal(h.t, ok, found) } + +// GenesisValidatorSet generates a set with `numVals` genesis validators +func GenesisValidatorSet(numVals int) (*GenesisValidators, error) { + genesisKeys := make([]*checkpointingtypes.GenesisKey, 0, numVals) + blsPrivKeys := make([]bls12381.PrivateKey, 0, numVals) + for i := 0; i < numVals; i++ { + blsPrivKey := bls12381.GenPrivKey() + // create validator set with single validator + valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), blsPrivKey) + if err != nil { + return nil, err + } + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) + if err != nil { + return nil, err + } + genesisKey, err := checkpointingtypes.NewGenesisKey( + sdk.ValAddress(valKeys.ValPubkey.Address()), + &valKeys.BlsPubkey, + valKeys.PoP, + &cosmosed.PubKey{Key: valPubkey.Bytes()}, + ) + if err != nil { + return nil, err + } + genesisKeys = append(genesisKeys, genesisKey) + blsPrivKeys = append(blsPrivKeys, blsPrivKey) + } + return &GenesisValidators{ + GenesisKeys: genesisKeys, + BlsPrivKeys: blsPrivKeys, + }, nil +} diff --git a/x/epoching/testepoching/tm.go b/x/epoching/testepoching/tm.go deleted file mode 100644 index c7cc83b08..000000000 --- a/x/epoching/testepoching/tm.go +++ /dev/null @@ -1,59 +0,0 @@ -package testepoching - -import ( - "cosmossdk.io/math" - "github.com/babylonchain/babylon/testutil/datagen" - tmcrypto "github.com/cometbft/cometbft/crypto" - tmtypes "github.com/cometbft/cometbft/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -// GetTmConsPubKey gets the validator's public key as a tmcrypto.PubKey. -func GetTmConsPubKey(v stakingtypes.Validator) (tmcrypto.PubKey, error) { - pk, err := v.ConsPubKey() - if err != nil { - return nil, err - } - - return cryptocodec.ToTmPubKeyInterface(pk) -} - -// ToTmValidator casts an SDK validator to a tendermint type Validator. -func ToTmValidator(v stakingtypes.Validator, r math.Int) (*tmtypes.Validator, error) { - tmPk, err := GetTmConsPubKey(v) - if err != nil { - return nil, err - } - - return tmtypes.NewValidator(tmPk, v.ConsensusPower(r)), nil -} - -// ToTmValidators casts all validators to the corresponding tendermint type. -func ToTmValidators(v stakingtypes.Validators, r math.Int) ([]*tmtypes.Validator, error) { - validators := make([]*tmtypes.Validator, len(v)) - var err error - for i, val := range v { - validators[i], err = ToTmValidator(val, r) - if err != nil { - return nil, err - } - } - - return validators, nil -} - -// GenTmValidatorSet generates a set with `numVals` Tendermint validators -func GenTmValidatorSet(numVals int) (*tmtypes.ValidatorSet, error) { - vals := []*tmtypes.Validator{} - for i := 0; i < numVals; i++ { - privVal := datagen.NewPV() - pubKey, err := privVal.GetPubKey() - if err != nil { - return nil, err - } - val := tmtypes.NewValidator(pubKey, 1) - vals = append(vals, val) - } - return tmtypes.NewValidatorSet(vals), nil -} diff --git a/x/epoching/testepoching/utils.go b/x/epoching/testepoching/utils.go deleted file mode 100644 index 087dab2de..000000000 --- a/x/epoching/testepoching/utils.go +++ /dev/null @@ -1,8 +0,0 @@ -package testepoching - -func Min(a, b uint64) uint64 { - if a < b { - return a - } - return b -} diff --git a/x/epoching/testepoching/validator.go b/x/epoching/testepoching/validator.go index 30400f002..353bdcf1d 100644 --- a/x/epoching/testepoching/validator.go +++ b/x/epoching/testepoching/validator.go @@ -1,24 +1,11 @@ package testepoching import ( - "testing" - "github.com/cometbft/cometbft/crypto/merkle" - "github.com/stretchr/testify/require" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// NewValidator is a testing helper method to create validators in tests -func NewValidator(t testing.TB, operator sdk.ValAddress, pubKey cryptotypes.PubKey) stakingtypes.Validator { - v, err := stakingtypes.NewValidator(operator, pubKey, stakingtypes.Description{}) - require.NoError(t, err) - return v -} - -// calculate validator hash and new header +// CalculateValHash calculate validator hash and new header // (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/test_helpers.go#L156-L163) func CalculateValHash(valSet []stakingtypes.Validator) []byte { bzs := make([][]byte, len(valSet)) @@ -28,8 +15,3 @@ func CalculateValHash(valSet []stakingtypes.Validator) []byte { } return merkle.HashFromByteSlices(bzs) } - -// ZeroCommission constructs a commission rates with all zeros. -func ZeroCommission() stakingtypes.CommissionRates { - return stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) -} diff --git a/x/epoching/types/epoching.go b/x/epoching/types/epoching.go index bed9745dc..1d416d32a 100644 --- a/x/epoching/types/epoching.go +++ b/x/epoching/types/epoching.go @@ -1,6 +1,7 @@ package types import ( + "context" "time" errorsmod "cosmossdk.io/errors" @@ -41,19 +42,19 @@ func (e Epoch) GetSecondBlockHeight() uint64 { return e.FirstBlockHeight + 1 } -func (e Epoch) IsLastBlock(ctx sdk.Context) bool { - return e.GetLastBlockHeight() == uint64(ctx.BlockHeight()) +func (e Epoch) IsLastBlock(ctx context.Context) bool { + return e.GetLastBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) } -func (e Epoch) IsFirstBlock(ctx sdk.Context) bool { - return e.FirstBlockHeight == uint64(ctx.BlockHeight()) +func (e Epoch) IsFirstBlock(ctx context.Context) bool { + return e.FirstBlockHeight == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) } -func (e Epoch) IsSecondBlock(ctx sdk.Context) bool { +func (e Epoch) IsSecondBlock(ctx context.Context) bool { if e.EpochNumber == 0 { return false } - return e.GetSecondBlockHeight() == uint64(ctx.BlockHeight()) + return e.GetSecondBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) } // IsFirstBlockOfNextEpoch checks whether the current block is the first block of @@ -61,11 +62,12 @@ func (e Epoch) IsSecondBlock(ctx sdk.Context) bool { // CONTRACT: IsFirstBlockOfNextEpoch can only be called by the epoching module // once upon the first block of a new epoch // other modules should use IsFirstBlock instead. -func (e Epoch) IsFirstBlockOfNextEpoch(ctx sdk.Context) bool { +func (e Epoch) IsFirstBlockOfNextEpoch(ctx context.Context) bool { + sdkCtx := sdk.UnwrapSDKContext(ctx) if e.EpochNumber == 0 { - return ctx.BlockHeight() == 1 + return sdkCtx.BlockHeader().Height == 1 } else { - height := uint64(ctx.BlockHeight()) + height := uint64(sdkCtx.BlockHeader().Height) return e.FirstBlockHeight+e.CurrentEpochInterval == height } } @@ -138,15 +140,6 @@ func NewQueuedMessage(blockHeight uint64, blockTime time.Time, txid []byte, msg return queuedMsg, nil } -func (qm QueuedMessage) GetSigners() []sdk.AccAddress { - return qm.UnwrapToSdkMsg().GetSigners() -} - -func (qm QueuedMessage) ValidateBasic() error { - return qm.UnwrapToSdkMsg().ValidateBasic() - -} - // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces func (qm QueuedMessage) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { var pubKey cryptotypes.PubKey diff --git a/x/epoching/types/expected_keepers.go b/x/epoching/types/expected_keepers.go index 17e23b9ab..367aa21b4 100644 --- a/x/epoching/types/expected_keepers.go +++ b/x/epoching/types/expected_keepers.go @@ -1,51 +1,49 @@ package types import ( + "context" + storetypes "cosmossdk.io/store/types" "time" "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI // Methods imported from account should be defined here } // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin // Methods imported from bank should be defined here } // StakingKeeper defines the staking module interface contract needed by the // epoching module. type StakingKeeper interface { - GetParams(ctx sdk.Context) stakingtypes.Params - DequeueAllMatureUBDQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []stakingtypes.DVPair) - CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) - DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []stakingtypes.DVVTriplet) - CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (sdk.Coins, error) - ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) ([]abci.ValidatorUpdate, error) - IterateLastValidatorPowers(ctx sdk.Context, handler func(operator sdk.ValAddress, power int64) bool) - GetValidator(ctx sdk.Context, addr sdk.ValAddress) (stakingtypes.Validator, bool) - GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) []stakingtypes.Delegation - HasMaxUnbondingDelegationEntries(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) bool - BondDenom(ctx sdk.Context) string - HasReceivingRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) bool - HasMaxRedelegationEntries(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorSrcAddr, validatorDstAddr sdk.ValAddress) bool - ValidateUnbondAmount(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int) (sdk.Dec, error) - ValidatorQueueIterator(ctx sdk.Context, endTime time.Time, endHeight int64) sdk.Iterator - UnbondingToUnbonded(ctx sdk.Context, validator stakingtypes.Validator) stakingtypes.Validator - RemoveValidator(ctx sdk.Context, address sdk.ValAddress) - UnbondAllMatureValidators(ctx sdk.Context) - GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (stakingtypes.Validator, bool) + GetParams(ctx context.Context) (stakingtypes.Params, error) + DequeueAllMatureUBDQueue(ctx context.Context, currTime time.Time) ([]stakingtypes.DVPair, error) + CompleteUnbonding(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DequeueAllMatureRedelegationQueue(ctx context.Context, currTime time.Time) ([]stakingtypes.DVVTriplet, error) + CompleteRedelegation(ctx context.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (sdk.Coins, error) + ApplyAndReturnValidatorSetUpdates(ctx context.Context) ([]abci.ValidatorUpdate, error) + IterateLastValidatorPowers(ctx context.Context, handler func(operator sdk.ValAddress, power int64) bool) error + GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) + GetValidatorDelegations(ctx context.Context, valAddr sdk.ValAddress) ([]stakingtypes.Delegation, error) + HasMaxUnbondingDelegationEntries(ctx context.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) (bool, error) + BondDenom(ctx context.Context) (string, error) + HasReceivingRedelegation(ctx context.Context, delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) (bool, error) + HasMaxRedelegationEntries(ctx context.Context, delegatorAddr sdk.AccAddress, validatorSrcAddr, validatorDstAddr sdk.ValAddress) (bool, error) + ValidateUnbondAmount(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int) (math.LegacyDec, error) + ValidatorQueueIterator(ctx context.Context, endTime time.Time, endHeight int64) (storetypes.Iterator, error) + UnbondAllMatureValidators(ctx context.Context) error + GetValidatorByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (stakingtypes.Validator, error) } // Event Hooks @@ -56,14 +54,14 @@ type StakingKeeper interface { // EpochingHooks event hooks for epoching validator object (noalias) type EpochingHooks interface { - AfterEpochBegins(ctx sdk.Context, epoch uint64) // Must be called after an epoch begins - AfterEpochEnds(ctx sdk.Context, epoch uint64) // Must be called after an epoch ends - BeforeSlashThreshold(ctx sdk.Context, valSet ValidatorSet) // Must be called before a certain threshold (1/3 or 2/3) of validators are slashed in a single epoch + AfterEpochBegins(ctx context.Context, epoch uint64) // Must be called after an epoch begins + AfterEpochEnds(ctx context.Context, epoch uint64) // Must be called after an epoch ends + BeforeSlashThreshold(ctx context.Context, valSet ValidatorSet) // Must be called before a certain threshold (1/3 or 2/3) of validators are slashed in a single epoch } // StakingHooks event hooks for staking validator object (noalias) type StakingHooks interface { - BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) // Must be called right before a validator is slashed + BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction math.LegacyDec) // Must be called right before a validator is slashed AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) error // Must be called when a validator is created AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error // Must be called when a validator is deleted AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) error // Must be called when a validator is bonded diff --git a/x/epoching/types/genesis_test.go b/x/epoching/types/genesis_test.go index 59038f82c..925ae1db6 100644 --- a/x/epoching/types/genesis_test.go +++ b/x/epoching/types/genesis_test.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/babylon/testutil/nullify" "github.com/babylonchain/babylon/x/epoching" "github.com/babylonchain/babylon/x/epoching/types" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" ) @@ -15,7 +14,7 @@ func TestGenesis(t *testing.T) { // This test requires setting up the staking module // Otherwise the epoching module cannot initialise the genesis validator set app := app.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) keeper := app.EpochingKeeper genesisState := types.GenesisState{ diff --git a/x/epoching/types/hooks.go b/x/epoching/types/hooks.go index 9a220da68..ce921259e 100644 --- a/x/epoching/types/hooks.go +++ b/x/epoching/types/hooks.go @@ -1,7 +1,7 @@ package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "context" ) // combine multiple Epoching hooks, all hook functions are run in array sequence @@ -13,19 +13,19 @@ func NewMultiEpochingHooks(hooks ...EpochingHooks) MultiEpochingHooks { return hooks } -func (h MultiEpochingHooks) AfterEpochBegins(ctx sdk.Context, epoch uint64) { +func (h MultiEpochingHooks) AfterEpochBegins(ctx context.Context, epoch uint64) { for i := range h { h[i].AfterEpochBegins(ctx, epoch) } } -func (h MultiEpochingHooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) { +func (h MultiEpochingHooks) AfterEpochEnds(ctx context.Context, epoch uint64) { for i := range h { h[i].AfterEpochEnds(ctx, epoch) } } -func (h MultiEpochingHooks) BeforeSlashThreshold(ctx sdk.Context, valSet ValidatorSet) { +func (h MultiEpochingHooks) BeforeSlashThreshold(ctx context.Context, valSet ValidatorSet) { for i := range h { h[i].BeforeSlashThreshold(ctx, valSet) } diff --git a/x/epoching/types/msg.go b/x/epoching/types/msg.go index bac4f93b5..0bac4757e 100644 --- a/x/epoching/types/msg.go +++ b/x/epoching/types/msg.go @@ -34,25 +34,12 @@ func (msg MsgWrappedDelegate) Route() string { return RouterKey } // Type implements the sdk.Msg interface. func (msg MsgWrappedDelegate) Type() string { return TypeMsgWrappedDelegate } -// GetSigners implements the sdk.Msg interface. It returns the address(es) that -// must sign over msg.GetSignBytes(). -// If the validator address is not same as delegator's, then the validator must -// sign the msg as well. -func (msg MsgWrappedDelegate) GetSigners() []sdk.AccAddress { - return msg.Msg.GetSigners() -} - -// GetSignBytes returns the message bytes to sign over. -func (msg MsgWrappedDelegate) GetSignBytes() []byte { - return msg.Msg.GetSignBytes() -} - // ValidateBasic implements the sdk.Msg interface. func (msg MsgWrappedDelegate) ValidateBasic() error { if msg.Msg == nil { return ErrNoWrappedMsg } - return msg.Msg.ValidateBasic() + return nil } // NewMsgWrappedUndelegate creates a new MsgWrappedUndelegate instance. @@ -68,25 +55,12 @@ func (msg MsgWrappedUndelegate) Route() string { return RouterKey } // Type implements the sdk.Msg interface. func (msg MsgWrappedUndelegate) Type() string { return TypeMsgWrappedUndelegate } -// GetSigners implements the sdk.Msg interface. It returns the address(es) that -// must sign over msg.GetSignBytes(). -// If the validator address is not same as delegator's, then the validator must -// sign the msg as well. -func (msg MsgWrappedUndelegate) GetSigners() []sdk.AccAddress { - return msg.Msg.GetSigners() -} - -// GetSignBytes returns the message bytes to sign over. -func (msg MsgWrappedUndelegate) GetSignBytes() []byte { - return msg.Msg.GetSignBytes() -} - // ValidateBasic implements the sdk.Msg interface. func (msg MsgWrappedUndelegate) ValidateBasic() error { if msg.Msg == nil { return ErrNoWrappedMsg } - return msg.Msg.ValidateBasic() + return nil } // NewMsgWrappedBeginRedelegate creates a new MsgWrappedBeginRedelegate instance. @@ -102,25 +76,12 @@ func (msg MsgWrappedBeginRedelegate) Route() string { return RouterKey } // Type implements the sdk.Msg interface. func (msg MsgWrappedBeginRedelegate) Type() string { return TypeMsgWrappedBeginRedelegate } -// GetSigners implements the sdk.Msg interface. It returns the address(es) that -// must sign over msg.GetSignBytes(). -// If the validator address is not same as delegator's, then the validator must -// sign the msg as well. -func (msg MsgWrappedBeginRedelegate) GetSigners() []sdk.AccAddress { - return msg.Msg.GetSigners() -} - -// GetSignBytes returns the message bytes to sign over. -func (msg MsgWrappedBeginRedelegate) GetSignBytes() []byte { - return msg.Msg.GetSignBytes() -} - // ValidateBasic implements the sdk.Msg interface. func (msg MsgWrappedBeginRedelegate) ValidateBasic() error { if msg.Msg == nil { return ErrNoWrappedMsg } - return msg.Msg.ValidateBasic() + return nil } // GetSigners returns the expected signers for a MsgUpdateParams message. diff --git a/x/epoching/types/msg_test.go b/x/epoching/types/msg_test.go index 6e1f3ca07..61f37f04f 100644 --- a/x/epoching/types/msg_test.go +++ b/x/epoching/types/msg_test.go @@ -1,6 +1,7 @@ package types_test import ( + sdkmath "cosmossdk.io/math" appparams "github.com/babylonchain/babylon/app/params" "testing" "time" @@ -27,10 +28,7 @@ var ( valAddr2 = sdk.ValAddress(pk2.Address()) valAddr3 = sdk.ValAddress(pk3.Address()) - emptyAddr sdk.ValAddress - - coinPos = sdk.NewInt64Coin(appparams.DefaultBondDenom, 1000) - coinZero = sdk.NewInt64Coin(appparams.DefaultBondDenom, 0) + coinPos = sdk.NewInt64Coin(appparams.DefaultBondDenom, 1000) ) func TestMsgDecode(t *testing.T) { @@ -49,7 +47,7 @@ func TestMsgDecode(t *testing.T) { require.True(t, pk1.Equals(pkUnmarshaled.(*ed25519.PubKey))) // create unwrapped msg - msgUnwrapped := stakingtypes.NewMsgDelegate(sdk.AccAddress(valAddr1), valAddr2, coinPos) + msgUnwrapped := stakingtypes.NewMsgDelegate(sdk.AccAddress(valAddr1).String(), valAddr2.String(), coinPos) // wrap and marshal msg msg := types.NewMsgWrappedDelegate(msgUnwrapped) @@ -68,8 +66,8 @@ func TestMsgDecode(t *testing.T) { var qmsgUnmarshaled sdk.Msg var msgCreateValUnmarshaled sdk.Msg - commission1 := stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) - msgcreateval1, err := stakingtypes.NewMsgCreateValidator(valAddr1, pk1, coinPos, stakingtypes.Description{}, commission1, sdk.OneInt()) + commission1 := stakingtypes.NewCommissionRates(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()) + msgcreateval1, err := stakingtypes.NewMsgCreateValidator(valAddr1.String(), pk1, coinPos, stakingtypes.Description{}, commission1, sdkmath.OneInt()) require.NoError(t, err) qmsg, err := types.NewQueuedMessage(1, time.Now(), []byte("tx id 1"), msgcreateval1) require.NoError(t, err) @@ -102,11 +100,6 @@ func TestMsgWrappedDelegate(t *testing.T) { }{ {"basic good", sdk.AccAddress(valAddr1), valAddr2, coinPos, true}, {"no wrapped msg", nil, nil, coinPos, false}, - {"self bond", sdk.AccAddress(valAddr1), valAddr1, coinPos, true}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, coinPos, false}, - {"empty validator", sdk.AccAddress(valAddr1), emptyAddr, coinPos, false}, - {"empty bond", sdk.AccAddress(valAddr1), valAddr2, coinZero, false}, - {"nil bold", sdk.AccAddress(valAddr1), valAddr2, sdk.Coin{}, false}, } for _, tc := range tests { @@ -114,7 +107,7 @@ func TestMsgWrappedDelegate(t *testing.T) { if tc.delegatorAddr == nil { msg = types.NewMsgWrappedDelegate(nil) } else { - msgUnwrapped := stakingtypes.NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) + msgUnwrapped := stakingtypes.NewMsgDelegate(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.bond) msg = types.NewMsgWrappedDelegate(msgUnwrapped) } if tc.expectPass { @@ -137,11 +130,6 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { }{ {"regular", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), true}, {"no wrapped msg", nil, nil, nil, coinPos, false}, - {"zero amount", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.NewInt64Coin(appparams.DefaultBondDenom, 0), false}, - {"nil amount", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.Coin{}, false}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, valAddr3, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), false}, - {"empty source validator", sdk.AccAddress(valAddr1), emptyAddr, valAddr3, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), false}, - {"empty destination validator", sdk.AccAddress(valAddr1), valAddr2, emptyAddr, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), false}, } for _, tc := range tests { @@ -149,7 +137,7 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { if tc.delegatorAddr == nil { msg = types.NewMsgWrappedBeginRedelegate(nil) } else { - msgUnwrapped := stakingtypes.NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.amount) + msgUnwrapped := stakingtypes.NewMsgBeginRedelegate(tc.delegatorAddr.String(), tc.validatorSrcAddr.String(), tc.validatorDstAddr.String(), tc.amount) msg = types.NewMsgWrappedBeginRedelegate(msgUnwrapped) } if tc.expectPass { @@ -171,10 +159,6 @@ func TestMsgWrappedUndelegate(t *testing.T) { }{ {"regular", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), true}, {"no wrapped msg", nil, nil, coinPos, false}, - {"zero amount", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(appparams.DefaultBondDenom, 0), false}, - {"nil amount", sdk.AccAddress(valAddr1), valAddr2, sdk.Coin{}, false}, - {"empty delegator", sdk.AccAddress(emptyAddr), valAddr1, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), false}, - {"empty validator", sdk.AccAddress(valAddr1), emptyAddr, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), false}, } for _, tc := range tests { @@ -182,7 +166,7 @@ func TestMsgWrappedUndelegate(t *testing.T) { if tc.delegatorAddr == nil { msg = types.NewMsgWrappedUndelegate(nil) } else { - msgUnwrapped := stakingtypes.NewMsgUndelegate(tc.delegatorAddr, tc.validatorAddr, tc.amount) + msgUnwrapped := stakingtypes.NewMsgUndelegate(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.amount) msg = types.NewMsgWrappedUndelegate(msgUnwrapped) } if tc.expectPass { diff --git a/x/epoching/types/params.go b/x/epoching/types/params.go index 1b14269ed..e03a402fd 100644 --- a/x/epoching/types/params.go +++ b/x/epoching/types/params.go @@ -1,17 +1,13 @@ package types import ( - fmt "fmt" + "fmt" ) const ( DefaultEpochInterval uint64 = 10 ) -var ( - KeyEpochInterval = []byte("EpochInterval") -) - // NewParams creates a new Params instance func NewParams(epochInterval uint64) Params { return Params{ diff --git a/x/epoching/types/tx.pb.go b/x/epoching/types/tx.pb.go index 19605482e..fc3ec2d6e 100644 --- a/x/epoching/types/tx.pb.go +++ b/x/epoching/types/tx.pb.go @@ -370,40 +370,40 @@ func init() { func init() { proto.RegisterFile("babylon/epoching/v1/tx.proto", fileDescriptor_a5fc8fed8f4e58b6) } var fileDescriptor_a5fc8fed8f4e58b6 = []byte{ - // 514 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x31, 0x6f, 0xd3, 0x40, - 0x1c, 0xc5, 0x6d, 0x82, 0x2a, 0xfa, 0x07, 0x51, 0x61, 0x22, 0x9a, 0x98, 0xc8, 0x29, 0x29, 0x08, - 0xa8, 0xe0, 0x4c, 0x8a, 0x28, 0xa2, 0x62, 0x21, 0x62, 0x42, 0x8a, 0x84, 0x8c, 0x2a, 0x04, 0x0b, - 0x3a, 0xdb, 0xa7, 0x8b, 0xd5, 0xda, 0x67, 0x7c, 0xd7, 0xaa, 0xd9, 0x10, 0x13, 0x23, 0x03, 0x1f, - 0xa0, 0x1f, 0x81, 0x81, 0x0f, 0xd1, 0x81, 0xa1, 0x62, 0x62, 0x42, 0x28, 0x19, 0xe0, 0x63, 0xa0, - 0xd8, 0x67, 0x5f, 0x70, 0x92, 0x26, 0xdd, 0x62, 0xbd, 0xf7, 0x7f, 0xbf, 0x17, 0xf9, 0xc9, 0xd0, - 0x70, 0xb1, 0xdb, 0xdf, 0x63, 0x91, 0x4d, 0x62, 0xe6, 0xf5, 0x82, 0x88, 0xda, 0x07, 0x6d, 0x5b, - 0x1c, 0xa2, 0x38, 0x61, 0x82, 0x19, 0x57, 0xa5, 0x8a, 0x72, 0x15, 0x1d, 0xb4, 0xcd, 0x2a, 0x65, - 0x94, 0xa5, 0xba, 0x3d, 0xfa, 0x95, 0x59, 0xcd, 0xa6, 0xc7, 0x78, 0xc8, 0xb8, 0xcd, 0x05, 0xde, - 0xcd, 0x62, 0x5c, 0x22, 0xb0, 0xca, 0x32, 0xd7, 0xa6, 0x91, 0x62, 0x9c, 0xe0, 0x90, 0x4b, 0x47, - 0x3d, 0x8b, 0x78, 0x97, 0x65, 0x67, 0x0f, 0x52, 0x5a, 0x95, 0xe9, 0x21, 0x4f, 0xcf, 0x42, 0x4e, - 0x33, 0xa1, 0xb5, 0x03, 0x46, 0x97, 0xd3, 0xd7, 0x09, 0x8e, 0x63, 0xe2, 0x3f, 0x27, 0x7b, 0x84, - 0x62, 0x41, 0x8c, 0x47, 0x50, 0x09, 0x39, 0xad, 0xe9, 0x6b, 0xfa, 0x9d, 0x8b, 0x9b, 0xeb, 0x48, - 0x46, 0xc9, 0x6a, 0x48, 0x56, 0x43, 0x5d, 0x4e, 0xf3, 0x0b, 0x67, 0xe4, 0xdf, 0xbe, 0xf0, 0xe9, - 0xa8, 0xa9, 0xfd, 0x3d, 0x6a, 0x6a, 0xad, 0x06, 0x98, 0x93, 0xb1, 0x0e, 0xe1, 0x31, 0x8b, 0x38, - 0x69, 0xbd, 0x81, 0xaa, 0x52, 0x77, 0x22, 0x3f, 0xc7, 0x3e, 0x1e, 0xc7, 0xde, 0x3a, 0x05, 0xab, - 0x6e, 0xca, 0x60, 0x0b, 0x1a, 0xd3, 0xa2, 0x0b, 0xb4, 0x07, 0x75, 0xa5, 0x77, 0x08, 0x0d, 0x22, - 0x87, 0x14, 0xfc, 0xa7, 0xe3, 0xfc, 0x8d, 0x53, 0xf8, 0xa5, 0xc3, 0x72, 0x89, 0x75, 0xb8, 0x31, - 0x13, 0x52, 0x34, 0xf9, 0xa2, 0xc3, 0xca, 0xe8, 0xaf, 0xc4, 0x3e, 0x16, 0xe4, 0x65, 0xfa, 0x1e, - 0x8d, 0x2d, 0x58, 0xc6, 0xfb, 0xa2, 0xc7, 0x92, 0x40, 0xf4, 0xd3, 0x1a, 0xcb, 0x9d, 0xda, 0x8f, - 0x6f, 0xf7, 0xab, 0xb2, 0xc9, 0x33, 0xdf, 0x4f, 0x08, 0xe7, 0xaf, 0x44, 0x12, 0x44, 0xd4, 0x51, - 0x56, 0xe3, 0x09, 0x2c, 0x65, 0x4b, 0xa8, 0x9d, 0x4b, 0xbb, 0x5f, 0x47, 0x53, 0x86, 0x87, 0x32, - 0x48, 0xe7, 0xfc, 0xf1, 0xaf, 0xa6, 0xe6, 0xc8, 0x83, 0xed, 0xcb, 0x1f, 0xff, 0x7c, 0xdd, 0x50, - 0x51, 0xad, 0x3a, 0xac, 0x96, 0x5a, 0xe5, 0x8d, 0x37, 0xbf, 0x57, 0xa0, 0xd2, 0xe5, 0xd4, 0xd8, - 0x85, 0x95, 0xf2, 0x60, 0x6e, 0x4f, 0x05, 0x4e, 0x4e, 0xc0, 0xb4, 0x17, 0x34, 0xe6, 0x50, 0xe3, - 0x3d, 0x5c, 0x99, 0x1c, 0xca, 0xdd, 0x39, 0x29, 0xca, 0x6a, 0xb6, 0x17, 0xb6, 0x16, 0xc8, 0x0f, - 0x3a, 0x5c, 0x9b, 0xb1, 0x10, 0x34, 0x27, 0xad, 0xe4, 0x37, 0xb7, 0xce, 0xe6, 0x2f, 0x2a, 0xb8, - 0x70, 0xe9, 0xbf, 0x61, 0xdc, 0x9c, 0x95, 0x33, 0xee, 0x32, 0xef, 0x2d, 0xe2, 0xca, 0x19, 0x9d, - 0x17, 0xc7, 0x03, 0x4b, 0x3f, 0x19, 0x58, 0xfa, 0xef, 0x81, 0xa5, 0x7f, 0x1e, 0x5a, 0xda, 0xc9, - 0xd0, 0xd2, 0x7e, 0x0e, 0x2d, 0xed, 0xed, 0x03, 0x1a, 0x88, 0xde, 0xbe, 0x8b, 0x3c, 0x16, 0xda, - 0x32, 0xd1, 0xeb, 0xe1, 0x20, 0xca, 0x1f, 0xec, 0x43, 0xf5, 0x11, 0x12, 0xfd, 0x98, 0x70, 0x77, - 0x29, 0xfd, 0x9a, 0x3c, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x69, 0x5c, 0x18, 0x0f, 0x05, - 0x00, 0x00, + // 526 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xc1, 0x6a, 0x13, 0x41, + 0x1c, 0xc6, 0x77, 0x8d, 0x16, 0xfa, 0x57, 0xac, 0xae, 0xc1, 0x26, 0x63, 0xd8, 0xd4, 0x54, 0x51, + 0x83, 0xce, 0x9a, 0x8a, 0x15, 0x8b, 0x17, 0x83, 0x27, 0x21, 0x20, 0x11, 0x11, 0x04, 0x91, 0xd9, + 0xec, 0x30, 0x59, 0xd2, 0xdd, 0x59, 0x77, 0xa6, 0xa5, 0xb9, 0x15, 0x4f, 0x1e, 0x3d, 0xf8, 0x00, + 0x7d, 0x84, 0x1e, 0x7c, 0x88, 0x1e, 0x8b, 0x5e, 0x3c, 0x89, 0x24, 0x87, 0xfa, 0x18, 0xb2, 0xbb, + 0xb3, 0xbb, 0x31, 0xc9, 0xb6, 0xf1, 0x96, 0xc9, 0xff, 0xfb, 0x7f, 0xdf, 0x6f, 0xd8, 0x8f, 0x81, + 0x9a, 0x4d, 0xec, 0xe1, 0x36, 0xf7, 0x2d, 0x1a, 0xf0, 0x5e, 0xdf, 0xf5, 0x99, 0xb5, 0xdb, 0xb2, + 0xe4, 0x1e, 0x0e, 0x42, 0x2e, 0xb9, 0x71, 0x4d, 0x4d, 0x71, 0x3a, 0xc5, 0xbb, 0x2d, 0x54, 0x66, + 0x9c, 0xf1, 0x78, 0x6e, 0x45, 0xbf, 0x12, 0x29, 0xaa, 0xf7, 0xb8, 0xf0, 0xb8, 0xb0, 0x84, 0x24, + 0x83, 0xc4, 0xc6, 0xa6, 0x92, 0xe4, 0x5e, 0x68, 0x6d, 0x5e, 0x52, 0x40, 0x42, 0xe2, 0x09, 0xa5, + 0xa8, 0x26, 0x16, 0x1f, 0x12, 0xef, 0xe4, 0xa0, 0x46, 0xab, 0xca, 0xdd, 0x13, 0xf1, 0x9a, 0x27, + 0x58, 0x32, 0x68, 0xbc, 0x07, 0xa3, 0x23, 0xd8, 0xdb, 0x90, 0x04, 0x01, 0x75, 0x5e, 0xd0, 0x6d, + 0xca, 0x88, 0xa4, 0xc6, 0x63, 0x28, 0x79, 0x82, 0x55, 0xf4, 0x35, 0xfd, 0xee, 0xc5, 0x8d, 0x75, + 0xac, 0xac, 0x14, 0x1a, 0x56, 0x68, 0xb8, 0x23, 0x58, 0xba, 0xd1, 0x8d, 0xf4, 0x5b, 0x57, 0x3e, + 0x1f, 0xd4, 0xb5, 0x3f, 0x07, 0x75, 0xed, 0xd3, 0xc9, 0x61, 0x33, 0xfa, 0xa7, 0x51, 0x03, 0x34, + 0x6b, 0xdf, 0xa5, 0x22, 0xe0, 0xbe, 0xa0, 0x0d, 0x02, 0xe5, 0x7c, 0xfa, 0xc6, 0x77, 0xd2, 0xf8, + 0x27, 0x93, 0xf1, 0xb7, 0x4f, 0x89, 0xcf, 0x77, 0x8a, 0x00, 0x4c, 0xa8, 0xcd, 0x8b, 0xc8, 0x10, + 0x06, 0x50, 0xcd, 0xe7, 0x6d, 0xca, 0x5c, 0xbf, 0x4b, 0x33, 0x8e, 0x67, 0x93, 0x1c, 0xcd, 0x53, + 0x38, 0xa6, 0x16, 0x8b, 0x60, 0xd6, 0xe1, 0x66, 0x61, 0x58, 0x46, 0xf4, 0x55, 0x87, 0x95, 0xe8, + 0x6a, 0x81, 0x43, 0x24, 0x7d, 0x15, 0x7f, 0x5f, 0x63, 0x13, 0x96, 0xc9, 0x8e, 0xec, 0xf3, 0xd0, + 0x95, 0xc3, 0x18, 0x67, 0xb9, 0x5d, 0xf9, 0xfe, 0xed, 0x41, 0x59, 0x11, 0x3d, 0x77, 0x9c, 0x90, + 0x0a, 0xf1, 0x5a, 0x86, 0xae, 0xcf, 0xba, 0xb9, 0xd4, 0x78, 0x0a, 0x4b, 0x49, 0x43, 0x2a, 0xe7, + 0xe2, 0x3b, 0xdc, 0xc0, 0x73, 0x0a, 0x89, 0x93, 0x90, 0xf6, 0xf9, 0xa3, 0x5f, 0x75, 0xad, 0xab, + 0x16, 0xb6, 0x2e, 0x47, 0xd4, 0xb9, 0x55, 0xa3, 0x0a, 0xab, 0x53, 0x54, 0x29, 0xf1, 0xc6, 0x8f, + 0x12, 0x94, 0x3a, 0x82, 0x19, 0x03, 0x58, 0x99, 0x2e, 0xd2, 0x9d, 0xb9, 0x81, 0xb3, 0x95, 0x40, + 0xd6, 0x82, 0xc2, 0x34, 0xd4, 0xf8, 0x08, 0x57, 0x67, 0x8b, 0x73, 0xef, 0x0c, 0x97, 0x5c, 0x8a, + 0x5a, 0x0b, 0x4b, 0xb3, 0xc8, 0x7d, 0x1d, 0xae, 0x17, 0x34, 0x05, 0x9f, 0xe1, 0x36, 0xa5, 0x47, + 0x9b, 0xff, 0xa7, 0xcf, 0x10, 0x6c, 0xb8, 0xf4, 0x4f, 0x31, 0x6e, 0x15, 0xf9, 0x4c, 0xaa, 0xd0, + 0xfd, 0x45, 0x54, 0x69, 0x06, 0xba, 0xb0, 0x7f, 0x72, 0xd8, 0xd4, 0xdb, 0x2f, 0x8f, 0x46, 0xa6, + 0x7e, 0x3c, 0x32, 0xf5, 0xdf, 0x23, 0x53, 0xff, 0x32, 0x36, 0xb5, 0xe3, 0xb1, 0xa9, 0xfd, 0x1c, + 0x9b, 0xda, 0xbb, 0x87, 0xcc, 0x95, 0xfd, 0x1d, 0x1b, 0xf7, 0xb8, 0x67, 0x29, 0xe3, 0x5e, 0x9f, + 0xb8, 0x7e, 0x7a, 0xb0, 0xf6, 0xf2, 0x37, 0x4a, 0x0e, 0x03, 0x2a, 0xec, 0xa5, 0xf8, 0xb1, 0x79, + 0xf4, 0x37, 0x00, 0x00, 0xff, 0xff, 0x58, 0xb7, 0x7d, 0x0e, 0x2e, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/epoching/types/validator.go b/x/epoching/types/validator.go index 78c6cc667..70c2c63fe 100644 --- a/x/epoching/types/validator.go +++ b/x/epoching/types/validator.go @@ -3,7 +3,7 @@ package types import ( "bytes" "encoding/json" - fmt "fmt" + "fmt" "sort" "github.com/boljen/go-bitmap" @@ -13,7 +13,7 @@ import ( ) func (v *Validator) GetValAddress() sdk.ValAddress { - return sdk.ValAddress(v.Addr) + return v.Addr } func (v *Validator) GetValAddressStr() string { diff --git a/x/finality/abci.go b/x/finality/abci.go index 32faa3ceb..1faf65fe7 100644 --- a/x/finality/abci.go +++ b/x/finality/abci.go @@ -1,20 +1,21 @@ package finality import ( + "context" "time" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + return nil } -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { +func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) // if the BTC staking protocol is activated, i.e., there exists a height where a BTC validator @@ -26,5 +27,5 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { k.TallyBlocks(ctx) } - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } diff --git a/x/finality/client/cli/tx.go b/x/finality/client/cli/tx.go index 7c941c0cd..b7c4d8aca 100644 --- a/x/finality/client/cli/tx.go +++ b/x/finality/client/cli/tx.go @@ -134,7 +134,7 @@ func NewAddFinalitySigCmd() *cobra.Command { Signer: clientCtx.FromAddress.String(), ValBtcPk: valBTCPK, BlockHeight: blockHeight, - BlockLastCommitHash: blockLch, + BlockAppHash: blockLch, FinalitySig: finalitySig, } diff --git a/x/finality/genesis.go b/x/finality/genesis.go index fc1a75ae0..6f771fb4b 100644 --- a/x/finality/genesis.go +++ b/x/finality/genesis.go @@ -1,20 +1,20 @@ package finality import ( + "context" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) } } // ExportGenesis returns the module's exported genesis -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) diff --git a/x/finality/keeper/evidence.go b/x/finality/keeper/evidence.go index 947603be8..bae60ed9b 100644 --- a/x/finality/keeper/evidence.go +++ b/x/finality/keeper/evidence.go @@ -1,24 +1,26 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) SetEvidence(ctx sdk.Context, evidence *types.Evidence) { +func (k Keeper) SetEvidence(ctx context.Context, evidence *types.Evidence) { store := k.evidenceStore(ctx, evidence.ValBtcPk) store.Set(sdk.Uint64ToBigEndian(evidence.BlockHeight), k.cdc.MustMarshal(evidence)) } -func (k Keeper) HasEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { +func (k Keeper) HasEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { store := k.evidenceStore(ctx, valBtcPK) return store.Has(valBtcPK.MustMarshal()) } -func (k Keeper) GetEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { - if uint64(ctx.BlockHeight()) < height { +func (k Keeper) GetEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { + if uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) < height { return nil, types.ErrHeightTooHigh } store := k.evidenceStore(ctx, valBtcPK) @@ -36,7 +38,7 @@ func (k Keeper) GetEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height // NOTE: it's possible that the CanonicalFinalitySig field is empty for // an evidence, which happens when the BTC validator signed a fork block // but hasn't signed the canonical block yet. -func (k Keeper) GetFirstSlashableEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) *types.Evidence { +func (k Keeper) GetFirstSlashableEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey) *types.Evidence { store := k.evidenceStore(ctx, valBtcPK) iter := store.Iterator(nil, nil) defer iter.Close() @@ -55,8 +57,8 @@ func (k Keeper) GetFirstSlashableEvidence(ctx sdk.Context, valBtcPK *bbn.BIP340P // prefix: EvidenceKey // key: (BTC validator PK || height) // value: Evidence -func (k Keeper) evidenceStore(ctx sdk.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { - store := ctx.KVStore(k.storeKey) - eStore := prefix.NewStore(store, types.EvidenceKey) +func (k Keeper) evidenceStore(ctx context.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + eStore := prefix.NewStore(storeAdapter, types.EvidenceKey) return prefix.NewStore(eStore, valBTCPK.MustMarshal()) } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index 27b7bfde1..fe74f087a 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -3,8 +3,9 @@ package keeper import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/store/prefix" + "cosmossdk.io/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "google.golang.org/grpc/codes" @@ -155,8 +156,8 @@ func (k Keeper) ListEvidences(ctx context.Context, req *types.QueryListEvidences sdkCtx := sdk.UnwrapSDKContext(ctx) var evidences []*types.Evidence - store := sdkCtx.KVStore(k.storeKey) - eStore := prefix.NewStore(store, types.EvidenceKey) + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + eStore := prefix.NewStore(storeAdapter, types.EvidenceKey) pageRes, err := query.FilteredPaginate(eStore, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) { // NOTE: we have to strip the rest bytes after the first 32 bytes diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 9aaee9ca6..7f492b3ed 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -63,10 +63,10 @@ func FuzzBlock(f *testing.F) { ctx = sdk.UnwrapSDKContext(ctx) height := datagen.RandomInt(r, 100) - lch := datagen.GenRandomByteArray(r, 32) + appHash := datagen.GenRandomByteArray(r, 32) ib := &types.IndexedBlock{ - Height: height, - LastCommitHash: lch, + Height: height, + AppHash: appHash, } if datagen.RandomInt(r, 2) == 1 { @@ -80,7 +80,7 @@ func FuzzBlock(f *testing.F) { resp, err := keeper.Block(ctx, req) require.NoError(t, err) require.Equal(t, height, resp.Block.Height) - require.Equal(t, lch, resp.Block.LastCommitHash) + require.Equal(t, appHash, resp.Block.AppHash) }) } @@ -101,8 +101,8 @@ func FuzzListBlocks(f *testing.F) { indexedBlocks := make(map[uint64]*types.IndexedBlock) for i := startHeight; i < startHeight+numIndexedBlocks; i++ { ib := &types.IndexedBlock{ - Height: i, - LastCommitHash: datagen.GenRandomByteArray(r, 32), + Height: i, + AppHash: datagen.GenRandomByteArray(r, 32), } // randomly finalise some of them if datagen.RandomInt(r, 2) == 1 { @@ -133,7 +133,7 @@ func FuzzListBlocks(f *testing.F) { require.LessOrEqual(t, len(resp1.Blocks), int(limit)) // check if pagination takes effect require.EqualValues(t, resp1.Pagination.Total, len(finalizedIndexedBlocks)) for _, actualIB := range resp1.Blocks { - require.Equal(t, finalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + require.Equal(t, finalizedIndexedBlocks[actualIB.Height].AppHash, actualIB.AppHash) } } @@ -152,7 +152,7 @@ func FuzzListBlocks(f *testing.F) { require.LessOrEqual(t, len(resp2.Blocks), int(limit)) // check if pagination takes effect require.EqualValues(t, resp2.Pagination.Total, len(nonFinalizedIndexedBlocks)) for _, actualIB := range resp2.Blocks { - require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + require.Equal(t, nonFinalizedIndexedBlocks[actualIB.Height].AppHash, actualIB.AppHash) } } @@ -170,7 +170,7 @@ func FuzzListBlocks(f *testing.F) { require.LessOrEqual(t, len(resp3.Blocks), int(limit)) // check if pagination takes effect require.EqualValues(t, resp3.Pagination.Total, len(indexedBlocks)) for _, actualIB := range resp3.Blocks { - require.Equal(t, indexedBlocks[actualIB.Height].LastCommitHash, actualIB.LastCommitHash) + require.Equal(t, indexedBlocks[actualIB.Height].AppHash, actualIB.AppHash) } }) } @@ -322,8 +322,8 @@ func FuzzListEvidences(f *testing.F) { require.LessOrEqual(t, len(resp.Evidences), int(limit)) // check if pagination takes effect require.EqualValues(t, resp.Pagination.Total, numEvidences) // ensure evidences before startHeight are not included for _, actualEvidence := range resp.Evidences { - require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].CanonicalLastCommitHash, actualEvidence.CanonicalLastCommitHash) - require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].ForkLastCommitHash, actualEvidence.ForkLastCommitHash) + require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].CanonicalAppHash, actualEvidence.CanonicalAppHash) + require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].ForkAppHash, actualEvidence.ForkAppHash) } }) } diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go index bb7465aaa..62b3ba7e1 100644 --- a/x/finality/keeper/indexed_blocks.go +++ b/x/finality/keeper/indexed_blocks.go @@ -1,35 +1,37 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/finality/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // IndexBlock indexes the current block, saves the corresponding indexed block // to KVStore -func (k Keeper) IndexBlock(ctx sdk.Context) { - header := ctx.BlockHeader() +func (k Keeper) IndexBlock(ctx context.Context) { + header := sdk.UnwrapSDKContext(ctx).BlockHeader() ib := &types.IndexedBlock{ - Height: uint64(header.Height), - LastCommitHash: header.LastCommitHash, - Finalized: false, + Height: uint64(header.Height), + AppHash: header.AppHash, + Finalized: false, } k.SetBlock(ctx, ib) } -func (k Keeper) SetBlock(ctx sdk.Context, block *types.IndexedBlock) { +func (k Keeper) SetBlock(ctx context.Context, block *types.IndexedBlock) { store := k.blockStore(ctx) blockBytes := k.cdc.MustMarshal(block) store.Set(sdk.Uint64ToBigEndian(block.Height), blockBytes) } -func (k Keeper) HasBlock(ctx sdk.Context, height uint64) bool { +func (k Keeper) HasBlock(ctx context.Context, height uint64) bool { store := k.blockStore(ctx) return store.Has(sdk.Uint64ToBigEndian(height)) } -func (k Keeper) GetBlock(ctx sdk.Context, height uint64) (*types.IndexedBlock, error) { +func (k Keeper) GetBlock(ctx context.Context, height uint64) (*types.IndexedBlock, error) { store := k.blockStore(ctx) blockBytes := store.Get(sdk.Uint64ToBigEndian(height)) if len(blockBytes) == 0 { @@ -44,7 +46,7 @@ func (k Keeper) GetBlock(ctx sdk.Context, height uint64) (*types.IndexedBlock, e // prefix: BlockKey // key: block height // value: IndexedBlock -func (k Keeper) blockStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BlockKey) +func (k Keeper) blockStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.BlockKey) } diff --git a/x/finality/keeper/keeper.go b/x/finality/keeper/keeper.go index d87b2dd1f..ec312b805 100644 --- a/x/finality/keeper/keeper.go +++ b/x/finality/keeper/keeper.go @@ -1,11 +1,11 @@ package keeper import ( + corestoretypes "cosmossdk.io/core/store" "fmt" - "github.com/cometbft/cometbft/libs/log" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/babylonchain/babylon/x/finality/types" @@ -13,12 +13,9 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper BTCStakingKeeper types.BTCStakingKeeper IncentiveKeeper types.IncentiveKeeper // the address capable of executing a MsgUpdateParams message. Typically, this @@ -29,22 +26,16 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, btctakingKeeper types.BTCStakingKeeper, incentiveKeeper types.IncentiveKeeper, authority string, ) Keeper { return Keeper{ - cdc: cdc, - storeKey: storeKey, - memKey: memKey, + cdc: cdc, + storeService: storeService, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, BTCStakingKeeper: btctakingKeeper, IncentiveKeeper: incentiveKeeper, authority: authority, diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index db406c442..25831d066 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -102,7 +102,7 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal if err != nil { return nil, err } - if !bytes.Equal(indexedBlock.LastCommitHash, req.BlockLastCommitHash) { + if !bytes.Equal(indexedBlock.AppHash, req.BlockAppHash) { // the BTC validator votes for a fork! // construct evidence @@ -110,9 +110,9 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal ValBtcPk: req.ValBtcPk, BlockHeight: req.BlockHeight, PubRand: pubRand, - CanonicalLastCommitHash: indexedBlock.LastCommitHash, + CanonicalAppHash: indexedBlock.AppHash, CanonicalFinalitySig: nil, - ForkLastCommitHash: req.BlockLastCommitHash, + ForkAppHash: req.BlockAppHash, ForkFinalitySig: req.FinalitySig, } @@ -202,7 +202,7 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi // slashBTCValidator slashes a BTC validator with the given evidence // including setting its voting power to zero, extracting its BTC SK, // and emit an event -func (k Keeper) slashBTCValidator(ctx sdk.Context, valBtcPk *bbn.BIP340PubKey, evidence *types.Evidence) { +func (k Keeper) slashBTCValidator(ctx context.Context, valBtcPk *bbn.BIP340PubKey, evidence *types.Evidence) { // slash this BTC validator, i.e., set its voting power to zero if err := k.BTCStakingKeeper.SlashBTCValidator(ctx, valBtcPk.MustMarshal()); err != nil { panic(fmt.Errorf("failed to slash BTC validator: %v", err)) @@ -210,7 +210,7 @@ func (k Keeper) slashBTCValidator(ctx sdk.Context, valBtcPk *bbn.BIP340PubKey, e // emit slashing event eventSlashing := types.NewEventSlashedBTCValidator(evidence) - if err := ctx.EventManager().EmitTypedEvent(eventSlashing); err != nil { + if err := sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(eventSlashing); err != nil { panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) } } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 5e24e3447..659e04b37 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -12,14 +12,13 @@ import ( "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { fKeeper, ctx := keepertest.FinalityKeeper(t, nil, nil) - return fKeeper, keeper.NewMsgServerImpl(*fKeeper), sdk.WrapSDKContext(ctx) + return fKeeper, keeper.NewMsgServerImpl(*fKeeper), ctx } func TestMsgServer(t *testing.T) { @@ -157,7 +156,7 @@ func FuzzAddFinalitySig(f *testing.F) { // Case 3: successful if the BTC validator has voting power and has not casted this vote yet // index this block first - ctx = ctx.WithBlockHeader(tmproto.Header{Height: int64(blockHeight), LastCommitHash: blockHash}) + ctx = ctx.WithBlockHeader(tmproto.Header{Height: int64(blockHeight), AppHash: blockHash}) fKeeper.IndexBlock(ctx) bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) // add vote and it should work @@ -189,7 +188,7 @@ func FuzzAddFinalitySig(f *testing.F) { require.NoError(t, err) require.Equal(t, msg2.BlockHeight, evidence.BlockHeight) require.Equal(t, msg2.ValBtcPk.MustMarshal(), evidence.ValBtcPk.MustMarshal()) - require.Equal(t, msg2.BlockLastCommitHash, evidence.ForkLastCommitHash) + require.Equal(t, msg2.BlockAppHash, evidence.ForkAppHash) require.Equal(t, msg2.FinalitySig.MustMarshal(), evidence.ForkFinalitySig.MustMarshal()) // extract the SK and assert the extracted SK is correct btcSK2, err := evidence.ExtractBTCSK() diff --git a/x/finality/keeper/params.go b/x/finality/keeper/params.go index f4e9ce5e4..2b0d78a79 100644 --- a/x/finality/keeper/params.go +++ b/x/finality/keeper/params.go @@ -1,25 +1,27 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // SetParams sets the x/finality module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) - return nil + return store.Set(types.ParamsKey, bz) } // GetParams returns the current x/finality module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } diff --git a/x/finality/keeper/public_randomness.go b/x/finality/keeper/public_randomness.go index b2bb050ca..e3b8458de 100644 --- a/x/finality/keeper/public_randomness.go +++ b/x/finality/keeper/public_randomness.go @@ -1,33 +1,35 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) setPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64, pr *bbn.SchnorrPubRand) { +func (k Keeper) setPubRand(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64, pr *bbn.SchnorrPubRand) { store := k.pubRandStore(ctx, valBtcPK) store.Set(sdk.Uint64ToBigEndian(height), *pr) } // SetPubRandList sets a list of public randomness starting from a given startHeight // for a given BTC validator -func (k Keeper) SetPubRandList(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand) { +func (k Keeper) SetPubRandList(ctx context.Context, valBtcPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand) { for i, pr := range pubRandList { k.setPubRand(ctx, valBtcPK, startHeight+uint64(i), &pr) } } -func (k Keeper) HasPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { +func (k Keeper) HasPubRand(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { store := k.pubRandStore(ctx, valBtcPK) return store.Has(sdk.Uint64ToBigEndian(height)) } -func (k Keeper) GetPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*bbn.SchnorrPubRand, error) { +func (k Keeper) GetPubRand(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*bbn.SchnorrPubRand, error) { store := k.pubRandStore(ctx, valBtcPK) prBytes := store.Get(sdk.Uint64ToBigEndian(height)) if len(prBytes) == 0 { @@ -36,7 +38,7 @@ func (k Keeper) GetPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey, height u return bbn.NewSchnorrPubRand(prBytes) } -func (k Keeper) IsFirstPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) bool { +func (k Keeper) IsFirstPubRand(ctx context.Context, valBtcPK *bbn.BIP340PubKey) bool { store := k.pubRandStore(ctx, valBtcPK) iter := store.ReverseIterator(nil, nil) @@ -45,7 +47,7 @@ func (k Keeper) IsFirstPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) bool } // GetLastPubRand retrieves the last public randomness committed by the given BTC validator -func (k Keeper) GetLastPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) (uint64, *bbn.SchnorrPubRand, error) { +func (k Keeper) GetLastPubRand(ctx context.Context, valBtcPK *bbn.BIP340PubKey) (uint64, *bbn.SchnorrPubRand, error) { store := k.pubRandStore(ctx, valBtcPK) iter := store.ReverseIterator(nil, nil) @@ -67,8 +69,8 @@ func (k Keeper) GetLastPubRand(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) (uin // prefix: PubRandKey // key: (BTC validator || PK block height) // value: PublicRandomness -func (k Keeper) pubRandStore(ctx sdk.Context, valBtcPK *bbn.BIP340PubKey) prefix.Store { - store := ctx.KVStore(k.storeKey) - prefixedStore := prefix.NewStore(store, types.PubRandKey) +func (k Keeper) pubRandStore(ctx context.Context, valBtcPK *bbn.BIP340PubKey) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + prefixedStore := prefix.NewStore(storeAdapter, types.PubRandKey) return prefix.NewStore(prefixedStore, valBtcPK.MustMarshal()) } diff --git a/x/finality/keeper/query_params_test.go b/x/finality/keeper/query_params_test.go index 01ad9a661..857747a54 100644 --- a/x/finality/keeper/query_params_test.go +++ b/x/finality/keeper/query_params_test.go @@ -5,18 +5,16 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/finality/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) - wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) require.NoError(t, err) - response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + response, err := keeper.Params(ctx, &types.QueryParamsRequest{}) require.NoError(t, err) require.Equal(t, &types.QueryParamsResponse{Params: params}, response) } diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index 13eece9e1..9283fe30a 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "github.com/babylonchain/babylon/x/finality/types" @@ -15,12 +16,13 @@ import ( // - finalised blocks (i.e., block with validator set AND QC of this validator set) // - non-finalisable blocks (i.e., block with no active validator) // but without block that has validator set AND does not receive QC -func (k Keeper) TallyBlocks(ctx sdk.Context) { +func (k Keeper) TallyBlocks(ctx context.Context) { + sdkCtx := sdk.UnwrapSDKContext(ctx) activatedHeight, err := k.BTCStakingKeeper.GetBTCStakingActivatedHeight(ctx) if err != nil { // invoking TallyBlocks when BTC staking protocol is not activated is a programming error panic(fmt.Errorf("cannot tally a block when the BTC staking protocol hasn't been activated yet, current height: %v, activated height: %v", - ctx.BlockHeight(), activatedHeight)) + sdkCtx.BlockHeight(), activatedHeight)) } // start finalising blocks since max(activatedHeight, nextHeightToFinalize) @@ -36,7 +38,7 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { // - has validators, finalised: impossible to happen, panic // - does not have validators, finalised: impossible to happen, panic // After this for loop, the blocks since earliest activated height are either finalised or non-finalisable - for i := startHeight; i <= uint64(ctx.BlockHeight()); i++ { + for i := startHeight; i <= uint64(sdkCtx.BlockHeight()); i++ { ib, err := k.GetBlock(ctx, i) if err != nil { panic(err) // failing to get an existing block is a programming error @@ -74,7 +76,7 @@ func (k Keeper) TallyBlocks(ctx sdk.Context) { // finalizeBlock sets a block to be finalised in KVStore and distributes rewards to // BTC validators and delegations -func (k Keeper) finalizeBlock(ctx sdk.Context, block *types.IndexedBlock, voterBTCPKs map[string]struct{}) { +func (k Keeper) finalizeBlock(ctx context.Context, block *types.IndexedBlock, voterBTCPKs map[string]struct{}) { // set block to be finalised in KVStore block.Finalized = true k.SetBlock(ctx, block) @@ -108,16 +110,21 @@ func tally(valSet map[string]uint64, voterBTCPKs map[string]struct{}) bool { } // setNextHeightToFinalize sets the next height to finalise as the given height -func (k Keeper) setNextHeightToFinalize(ctx sdk.Context, height uint64) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) setNextHeightToFinalize(ctx context.Context, height uint64) { + store := k.storeService.OpenKVStore(ctx) heightBytes := sdk.Uint64ToBigEndian(height) - store.Set(types.NextHeightToFinalizeKey, heightBytes) + if err := store.Set(types.NextHeightToFinalizeKey, heightBytes); err != nil { + panic(err) + } } // getNextHeightToFinalize gets the next height to finalise -func (k Keeper) getNextHeightToFinalize(ctx sdk.Context) uint64 { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.NextHeightToFinalizeKey) +func (k Keeper) getNextHeightToFinalize(ctx context.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.NextHeightToFinalizeKey) + if err != nil { + panic(err) + } if bz == nil { return 0 } diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index d6135d83d..2252c09e4 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -35,7 +35,7 @@ func FuzzTallying_PanicCases(f *testing.F) { // Case 2: expect to panic if finalised block with nil validator set fKeeper.SetBlock(ctx, &types.IndexedBlock{ Height: 1, - LastCommitHash: datagen.GenRandomByteArray(r, 32), + AppHash: datagen.GenRandomByteArray(r, 32), Finalized: true, }) // activate BTC staking protocol at height 1 @@ -67,7 +67,7 @@ func FuzzTallying_FinalizingNoBlock(f *testing.F) { // index blocks fKeeper.SetBlock(ctx, &types.IndexedBlock{ Height: i, - LastCommitHash: datagen.GenRandomByteArray(r, 32), + AppHash: datagen.GenRandomByteArray(r, 32), Finalized: false, }) // this block does not have QC @@ -110,7 +110,7 @@ func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { // index blocks fKeeper.SetBlock(ctx, &types.IndexedBlock{ Height: i, - LastCommitHash: datagen.GenRandomByteArray(r, 32), + AppHash: datagen.GenRandomByteArray(r, 32), Finalized: false, }) if i < activatedHeight+numWithQCs { diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index 167cf805e..e27e6dd1e 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -1,26 +1,28 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) SetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { +func (k Keeper) SetSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { store := k.voteStore(ctx, height) store.Set(valBtcPK.MustMarshal(), sig.MustMarshal()) } -func (k Keeper) HasSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { +func (k Keeper) HasSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { store := k.voteStore(ctx, height) return store.Has(valBtcPK.MustMarshal()) } -func (k Keeper) GetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { - if uint64(ctx.BlockHeight()) < height { +func (k Keeper) GetSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { + if uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) < height { return nil, types.ErrHeightTooHigh } store := k.voteStore(ctx, height) @@ -36,7 +38,7 @@ func (k Keeper) GetSig(ctx sdk.Context, height uint64, valBtcPK *bbn.BIP340PubKe } // GetSigSet gets all EOTS signatures at a given height -func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.SchnorrEOTSSig { +func (k Keeper) GetSigSet(ctx context.Context, height uint64) map[string]*bbn.SchnorrEOTSSig { store := k.voteStore(ctx, height) iter := store.Iterator(nil, nil) defer iter.Close() @@ -64,7 +66,7 @@ func (k Keeper) GetSigSet(ctx sdk.Context, height uint64) map[string]*bbn.Schnor } // GetVoters gets returns a map of voters' BTC PKs to the given height -func (k Keeper) GetVoters(ctx sdk.Context, height uint64) map[string]struct{} { +func (k Keeper) GetVoters(ctx context.Context, height uint64) map[string]struct{} { store := k.voteStore(ctx, height) iter := store.Iterator(nil, nil) defer iter.Close() @@ -91,8 +93,8 @@ func (k Keeper) GetVoters(ctx sdk.Context, height uint64) map[string]struct{} { // prefix: VoteKey // key: (block height || BTC validator PK) // value: EOTS sig -func (k Keeper) voteStore(ctx sdk.Context, height uint64) prefix.Store { - store := ctx.KVStore(k.storeKey) - prefixedStore := prefix.NewStore(store, types.VoteKey) +func (k Keeper) voteStore(ctx context.Context, height uint64) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + prefixedStore := prefix.NewStore(storeAdapter, types.VoteKey) return prefix.NewStore(prefixedStore, sdk.Uint64ToBigEndian(height)) } diff --git a/x/finality/module.go b/x/finality/module.go index 0a64bc245..81a1f8df0 100644 --- a/x/finality/module.go +++ b/x/finality/module.go @@ -2,6 +2,7 @@ package finality import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -21,8 +22,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -90,22 +93,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -119,14 +116,12 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the module's genesis initialization. It returns no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. @@ -138,10 +133,18 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { return EndBlocker(ctx, am.keeper) } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker +} diff --git a/x/finality/module_simulation.go b/x/finality/module_simulation.go index e02b4f14a..4db5a78c0 100644 --- a/x/finality/module_simulation.go +++ b/x/finality/module_simulation.go @@ -7,7 +7,6 @@ import ( finalitysimulation "github.com/babylonchain/babylon/x/finality/simulation" "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -22,8 +21,6 @@ var ( _ = rand.Rand{} ) -const () - // GenerateGenesisState creates a randomized GenState of the module. func (AppModule) GenerateGenesisState(simState *module.SimulationState) { accs := make([]string, len(simState.Accounts)) @@ -37,7 +34,7 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { } // RegisterStoreDecoder registers a decoder. -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} // ProposalContents doesn't return any content functions for governance proposals func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 77a4ed23a..1b3e86b4f 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -7,15 +7,14 @@ import ( // x/finality module sentinel errors var ( ErrBlockNotFound = errorsmod.Register(ModuleName, 1100, "Block is not found") - ErrDuplicatedBlock = errorsmod.Register(ModuleName, 1101, "Block is already in KVStore") - ErrVoteNotFound = errorsmod.Register(ModuleName, 1102, "vote is not found") - ErrHeightTooHigh = errorsmod.Register(ModuleName, 1103, "the chain has not reached the given height yet") - ErrPubRandNotFound = errorsmod.Register(ModuleName, 1104, "public randomness is not found") - ErrNoPubRandYet = errorsmod.Register(ModuleName, 1105, "the BTC validator has not committed any public randomness yet") - ErrTooFewPubRand = errorsmod.Register(ModuleName, 1106, "the request contains too few public randomness") - ErrInvalidPubRand = errorsmod.Register(ModuleName, 1107, "the public randomness list is invalid") - ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1108, "evidence is not found") - ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1109, "finality signature is not valid") - ErrDuplicatedFinalitySig = errorsmod.Register(ModuleName, 1110, "the finality signature has been casted before") - ErrNoSlashableEvidence = errorsmod.Register(ModuleName, 1111, "there is no slashable evidence") + ErrVoteNotFound = errorsmod.Register(ModuleName, 1101, "vote is not found") + ErrHeightTooHigh = errorsmod.Register(ModuleName, 1102, "the chain has not reached the given height yet") + ErrPubRandNotFound = errorsmod.Register(ModuleName, 1103, "public randomness is not found") + ErrNoPubRandYet = errorsmod.Register(ModuleName, 1104, "the BTC validator has not committed any public randomness yet") + ErrTooFewPubRand = errorsmod.Register(ModuleName, 1105, "the request contains too few public randomness") + ErrInvalidPubRand = errorsmod.Register(ModuleName, 1106, "the public randomness list is invalid") + ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1107, "evidence is not found") + ErrInvalidFinalitySig = errorsmod.Register(ModuleName, 1108, "finality signature is not valid") + ErrDuplicatedFinalitySig = errorsmod.Register(ModuleName, 1109, "the finality signature has been casted before") + ErrNoSlashableEvidence = errorsmod.Register(ModuleName, 1110, "there is no slashable evidence") ) diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index f57656676..0bab833f8 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -1,36 +1,23 @@ package types import ( + "context" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) type BTCStakingKeeper interface { - GetBTCValidator(ctx sdk.Context, valBTCPK []byte) (*bstypes.BTCValidator, error) - HasBTCValidator(ctx sdk.Context, valBTCPK []byte) bool - SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error - GetVotingPower(ctx sdk.Context, valBTCPK []byte, height uint64) uint64 - GetVotingPowerTable(ctx sdk.Context, height uint64) map[string]uint64 - GetBTCStakingActivatedHeight(ctx sdk.Context) (uint64, error) - RecordRewardDistCache(ctx sdk.Context) - GetRewardDistCache(ctx sdk.Context, height uint64) (*bstypes.RewardDistCache, error) - RemoveRewardDistCache(ctx sdk.Context, height uint64) -} - -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here + GetBTCValidator(ctx context.Context, valBTCPK []byte) (*bstypes.BTCValidator, error) + HasBTCValidator(ctx context.Context, valBTCPK []byte) bool + SlashBTCValidator(ctx context.Context, valBTCPK []byte) error + GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 + GetVotingPowerTable(ctx context.Context, height uint64) map[string]uint64 + GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) + RecordRewardDistCache(ctx context.Context) + GetRewardDistCache(ctx context.Context, height uint64) (*bstypes.RewardDistCache, error) + RemoveRewardDistCache(ctx context.Context, height uint64) } // IncentiveKeeper defines the expected interface needed to distribute rewards. type IncentiveKeeper interface { - RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.RewardDistCache) + RewardBTCStaking(ctx context.Context, height uint64, rdc *bstypes.RewardDistCache) } diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go index 3e6cbc6d2..5ccb82231 100644 --- a/x/finality/types/finality.go +++ b/x/finality/types/finality.go @@ -16,7 +16,7 @@ func msgToSignForVote(blockHeight uint64, blockHash []byte) []byte { } func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { - if !bytes.Equal(ib.LastCommitHash, ib2.LastCommitHash) { + if !bytes.Equal(ib.AppHash, ib2.AppHash) { return false } if ib.Height != ib2.Height { @@ -27,15 +27,15 @@ func (ib *IndexedBlock) Equal(ib2 *IndexedBlock) bool { } func (ib *IndexedBlock) MsgToSign() []byte { - return msgToSignForVote(ib.Height, ib.LastCommitHash) + return msgToSignForVote(ib.Height, ib.AppHash) } func (e *Evidence) canonicalMsgToSign() []byte { - return msgToSignForVote(e.BlockHeight, e.CanonicalLastCommitHash) + return msgToSignForVote(e.BlockHeight, e.CanonicalAppHash) } func (e *Evidence) forkMsgToSign() []byte { - return msgToSignForVote(e.BlockHeight, e.ForkLastCommitHash) + return msgToSignForVote(e.BlockHeight, e.ForkAppHash) } func (e *Evidence) ValidateBasic() error { @@ -45,11 +45,11 @@ func (e *Evidence) ValidateBasic() error { if e.PubRand == nil { return fmt.Errorf("empty PubRand") } - if len(e.CanonicalLastCommitHash) != 32 { - return fmt.Errorf("malformed CanonicalLastCommitHash") + if len(e.CanonicalAppHash) != 32 { + return fmt.Errorf("malformed CanonicalAppHash") } - if len(e.ForkLastCommitHash) != 32 { - return fmt.Errorf("malformed ForkLastCommitHash") + if len(e.ForkAppHash) != 32 { + return fmt.Errorf("malformed ForkAppHash") } if e.ForkFinalitySig == nil { return fmt.Errorf("empty ValBtcPk") diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 8e9afb343..7fc2fbd68 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -29,7 +29,7 @@ type IndexedBlock struct { // height is the height of the block Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` // last_commit_hash is the last_commit_hash of the block - LastCommitHash []byte `protobuf:"bytes,2,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` + AppHash []byte `protobuf:"bytes,2,opt,name=last_commit_hash,json=appHash,proto3" json:"last_commit_hash,omitempty"` // finalized indicates whether the IndexedBlock is finalised by 2/3 // BTC validators or not Finalized bool `protobuf:"varint,3,opt,name=finalized,proto3" json:"finalized,omitempty"` @@ -75,9 +75,9 @@ func (m *IndexedBlock) GetHeight() uint64 { return 0 } -func (m *IndexedBlock) GetLastCommitHash() []byte { +func (m *IndexedBlock) GetAppHash() []byte { if m != nil { - return m.LastCommitHash + return m.AppHash } return nil } @@ -99,9 +99,9 @@ type Evidence struct { // pub_rand is the public randomness the BTC validator has committed to PubRand *github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,3,opt,name=pub_rand,json=pubRand,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand,omitempty"` // canonical_last_commit_hash is the last_commit_hash of the canonical block - CanonicalLastCommitHash []byte `protobuf:"bytes,4,opt,name=canonical_last_commit_hash,json=canonicalLastCommitHash,proto3" json:"canonical_last_commit_hash,omitempty"` + CanonicalAppHash []byte `protobuf:"bytes,4,opt,name=canonical_last_commit_hash,json=canonicalAppHash,proto3" json:"canonical_last_commit_hash,omitempty"` // fork_last_commit_hash is the last_commit_hash of the fork block - ForkLastCommitHash []byte `protobuf:"bytes,5,opt,name=fork_last_commit_hash,json=forkLastCommitHash,proto3" json:"fork_last_commit_hash,omitempty"` + ForkAppHash []byte `protobuf:"bytes,5,opt,name=fork_last_commit_hash,json=forkAppHash,proto3" json:"fork_last_commit_hash,omitempty"` // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` @@ -152,16 +152,16 @@ func (m *Evidence) GetBlockHeight() uint64 { return 0 } -func (m *Evidence) GetCanonicalLastCommitHash() []byte { +func (m *Evidence) GetCanonicalAppHash() []byte { if m != nil { - return m.CanonicalLastCommitHash + return m.CanonicalAppHash } return nil } -func (m *Evidence) GetForkLastCommitHash() []byte { +func (m *Evidence) GetForkAppHash() []byte { if m != nil { - return m.ForkLastCommitHash + return m.ForkAppHash } return nil } @@ -237,10 +237,10 @@ func (m *IndexedBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - if len(m.LastCommitHash) > 0 { - i -= len(m.LastCommitHash) - copy(dAtA[i:], m.LastCommitHash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.LastCommitHash))) + if len(m.AppHash) > 0 { + i -= len(m.AppHash) + copy(dAtA[i:], m.AppHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.AppHash))) i-- dAtA[i] = 0x12 } @@ -296,17 +296,17 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - if len(m.ForkLastCommitHash) > 0 { - i -= len(m.ForkLastCommitHash) - copy(dAtA[i:], m.ForkLastCommitHash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.ForkLastCommitHash))) + if len(m.ForkAppHash) > 0 { + i -= len(m.ForkAppHash) + copy(dAtA[i:], m.ForkAppHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.ForkAppHash))) i-- dAtA[i] = 0x2a } - if len(m.CanonicalLastCommitHash) > 0 { - i -= len(m.CanonicalLastCommitHash) - copy(dAtA[i:], m.CanonicalLastCommitHash) - i = encodeVarintFinality(dAtA, i, uint64(len(m.CanonicalLastCommitHash))) + if len(m.CanonicalAppHash) > 0 { + i -= len(m.CanonicalAppHash) + copy(dAtA[i:], m.CanonicalAppHash) + i = encodeVarintFinality(dAtA, i, uint64(len(m.CanonicalAppHash))) i-- dAtA[i] = 0x22 } @@ -362,7 +362,7 @@ func (m *IndexedBlock) Size() (n int) { if m.Height != 0 { n += 1 + sovFinality(uint64(m.Height)) } - l = len(m.LastCommitHash) + l = len(m.AppHash) if l > 0 { n += 1 + l + sovFinality(uint64(l)) } @@ -389,11 +389,11 @@ func (m *Evidence) Size() (n int) { l = m.PubRand.Size() n += 1 + l + sovFinality(uint64(l)) } - l = len(m.CanonicalLastCommitHash) + l = len(m.CanonicalAppHash) if l > 0 { n += 1 + l + sovFinality(uint64(l)) } - l = len(m.ForkLastCommitHash) + l = len(m.ForkAppHash) if l > 0 { n += 1 + l + sovFinality(uint64(l)) } @@ -464,7 +464,7 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -491,9 +491,9 @@ func (m *IndexedBlock) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.LastCommitHash = append(m.LastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.LastCommitHash == nil { - m.LastCommitHash = []byte{} + m.AppHash = append(m.AppHash[:0], dAtA[iNdEx:postIndex]...) + if m.AppHash == nil { + m.AppHash = []byte{} } iNdEx = postIndex case 3: @@ -657,7 +657,7 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CanonicalLastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CanonicalAppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -684,14 +684,14 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.CanonicalLastCommitHash = append(m.CanonicalLastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.CanonicalLastCommitHash == nil { - m.CanonicalLastCommitHash = []byte{} + m.CanonicalAppHash = append(m.CanonicalAppHash[:0], dAtA[iNdEx:postIndex]...) + if m.CanonicalAppHash == nil { + m.CanonicalAppHash = []byte{} } iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ForkLastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ForkAppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -718,9 +718,9 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ForkLastCommitHash = append(m.ForkLastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.ForkLastCommitHash == nil { - m.ForkLastCommitHash = []byte{} + m.ForkAppHash = append(m.ForkAppHash[:0], dAtA[iNdEx:postIndex]...) + if m.ForkAppHash == nil { + m.ForkAppHash = []byte{} } iNdEx = postIndex case 6: diff --git a/x/finality/types/genesis.go b/x/finality/types/genesis.go index 97cad7616..9d633ecd7 100644 --- a/x/finality/types/genesis.go +++ b/x/finality/types/genesis.go @@ -1,12 +1,8 @@ package types -// DefaultIndex is the default global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ - Params: DefaultParams(), } } @@ -14,6 +10,5 @@ func DefaultGenesis() *GenesisState { // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { - return gs.Params.Validate() } diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index dddc22a30..8c9149713 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -5,11 +5,10 @@ package types import ( + context "context" reflect "reflect" types "github.com/babylonchain/babylon/x/btcstaking/types" - types0 "github.com/cosmos/cosmos-sdk/types" - types1 "github.com/cosmos/cosmos-sdk/x/auth/types" gomock "github.com/golang/mock/gomock" ) @@ -37,7 +36,7 @@ func (m *MockBTCStakingKeeper) EXPECT() *MockBTCStakingKeeperMockRecorder { } // GetBTCStakingActivatedHeight mocks base method. -func (m *MockBTCStakingKeeper) GetBTCStakingActivatedHeight(ctx types0.Context) (uint64, error) { +func (m *MockBTCStakingKeeper) GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBTCStakingActivatedHeight", ctx) ret0, _ := ret[0].(uint64) @@ -52,7 +51,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetBTCStakingActivatedHeight(ctx int } // GetBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) GetBTCValidator(ctx types0.Context, valBTCPK []byte) (*types.BTCValidator, error) { +func (m *MockBTCStakingKeeper) GetBTCValidator(ctx context.Context, valBTCPK []byte) (*types.BTCValidator, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBTCValidator", ctx, valBTCPK) ret0, _ := ret[0].(*types.BTCValidator) @@ -67,7 +66,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetBTCValidator(ctx, valBTCPK interf } // GetRewardDistCache mocks base method. -func (m *MockBTCStakingKeeper) GetRewardDistCache(ctx types0.Context, height uint64) (*types.RewardDistCache, error) { +func (m *MockBTCStakingKeeper) GetRewardDistCache(ctx context.Context, height uint64) (*types.RewardDistCache, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRewardDistCache", ctx, height) ret0, _ := ret[0].(*types.RewardDistCache) @@ -82,7 +81,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetRewardDistCache(ctx, height inter } // GetVotingPower mocks base method. -func (m *MockBTCStakingKeeper) GetVotingPower(ctx types0.Context, valBTCPK []byte, height uint64) uint64 { +func (m *MockBTCStakingKeeper) GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetVotingPower", ctx, valBTCPK, height) ret0, _ := ret[0].(uint64) @@ -96,7 +95,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, valBTCPK, height } // GetVotingPowerTable mocks base method. -func (m *MockBTCStakingKeeper) GetVotingPowerTable(ctx types0.Context, height uint64) map[string]uint64 { +func (m *MockBTCStakingKeeper) GetVotingPowerTable(ctx context.Context, height uint64) map[string]uint64 { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetVotingPowerTable", ctx, height) ret0, _ := ret[0].(map[string]uint64) @@ -110,7 +109,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPowerTable(ctx, height inte } // HasBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) HasBTCValidator(ctx types0.Context, valBTCPK []byte) bool { +func (m *MockBTCStakingKeeper) HasBTCValidator(ctx context.Context, valBTCPK []byte) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasBTCValidator", ctx, valBTCPK) ret0, _ := ret[0].(bool) @@ -124,7 +123,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interf } // RecordRewardDistCache mocks base method. -func (m *MockBTCStakingKeeper) RecordRewardDistCache(ctx types0.Context) { +func (m *MockBTCStakingKeeper) RecordRewardDistCache(ctx context.Context) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRewardDistCache", ctx) } @@ -136,7 +135,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) RecordRewardDistCache(ctx interface{ } // RemoveRewardDistCache mocks base method. -func (m *MockBTCStakingKeeper) RemoveRewardDistCache(ctx types0.Context, height uint64) { +func (m *MockBTCStakingKeeper) RemoveRewardDistCache(ctx context.Context, height uint64) { m.ctrl.T.Helper() m.ctrl.Call(m, "RemoveRewardDistCache", ctx, height) } @@ -148,7 +147,7 @@ func (mr *MockBTCStakingKeeperMockRecorder) RemoveRewardDistCache(ctx, height in } // SlashBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx types0.Context, valBTCPK []byte) error { +func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx context.Context, valBTCPK []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashBTCValidator", ctx, valBTCPK) ret0, _ := ret[0].(error) @@ -161,80 +160,6 @@ func (mr *MockBTCStakingKeeperMockRecorder) SlashBTCValidator(ctx, valBTCPK inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).SlashBTCValidator), ctx, valBTCPK) } -// MockAccountKeeper is a mock of AccountKeeper interface. -type MockAccountKeeper struct { - ctrl *gomock.Controller - recorder *MockAccountKeeperMockRecorder -} - -// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. -type MockAccountKeeperMockRecorder struct { - mock *MockAccountKeeper -} - -// NewMockAccountKeeper creates a new mock instance. -func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { - mock := &MockAccountKeeper{ctrl: ctrl} - mock.recorder = &MockAccountKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { - return m.recorder -} - -// GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx types0.Context, addr types0.AccAddress) types1.AccountI { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types1.AccountI) - return ret0 -} - -// GetAccount indicates an expected call of GetAccount. -func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) -} - -// MockBankKeeper is a mock of BankKeeper interface. -type MockBankKeeper struct { - ctrl *gomock.Controller - recorder *MockBankKeeperMockRecorder -} - -// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. -type MockBankKeeperMockRecorder struct { - mock *MockBankKeeper -} - -// NewMockBankKeeper creates a new mock instance. -func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { - mock := &MockBankKeeper{ctrl: ctrl} - mock.recorder = &MockBankKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { - return m.recorder -} - -// SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx types0.Context, addr types0.AccAddress) types0.Coins { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) - ret0, _ := ret[0].(types0.Coins) - return ret0 -} - -// SpendableCoins indicates an expected call of SpendableCoins. -func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) -} - // MockIncentiveKeeper is a mock of IncentiveKeeper interface. type MockIncentiveKeeper struct { ctrl *gomock.Controller @@ -259,7 +184,7 @@ func (m *MockIncentiveKeeper) EXPECT() *MockIncentiveKeeperMockRecorder { } // RewardBTCStaking mocks base method. -func (m *MockIncentiveKeeper) RewardBTCStaking(ctx types0.Context, height uint64, rdc *types.RewardDistCache) { +func (m *MockIncentiveKeeper) RewardBTCStaking(ctx context.Context, height uint64, rdc *types.RewardDistCache) { m.ctrl.T.Helper() m.ctrl.Call(m, "RewardBTCStaking", ctx, height, rdc) } diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index c1e9ef4f9..e8f34cd02 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -42,7 +42,7 @@ func NewMsgAddFinalitySig(signer string, sk *btcec.PrivateKey, sr *eots.PrivateR Signer: signer, ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), BlockHeight: blockHeight, - BlockLastCommitHash: blockHash, + BlockAppHash: blockHash, } msgToSign := msg.MsgToSign() sig, err := eots.Sign(sk, sr, msgToSign) @@ -68,7 +68,7 @@ func (m *MsgAddFinalitySig) ValidateBasic() error { if m.ValBtcPk == nil { return fmt.Errorf("empty validator BTC PK") } - if len(m.BlockLastCommitHash) != tmhash.Size { + if len(m.BlockAppHash) != tmhash.Size { return fmt.Errorf("malformed block hash") } if m.FinalitySig == nil { @@ -79,7 +79,7 @@ func (m *MsgAddFinalitySig) ValidateBasic() error { } func (m *MsgAddFinalitySig) MsgToSign() []byte { - return msgToSignForVote(m.BlockHeight, m.BlockLastCommitHash) + return msgToSignForVote(m.BlockHeight, m.BlockAppHash) } func (m *MsgAddFinalitySig) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { diff --git a/x/finality/types/params.go b/x/finality/types/params.go index 5ea8d9515..de592797b 100644 --- a/x/finality/types/params.go +++ b/x/finality/types/params.go @@ -27,7 +27,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { func validateMinPubRand(minPubRand uint64) error { if minPubRand == 0 { - return fmt.Errorf("Min Pub Rand cannot be 0") + return fmt.Errorf("min Pub Rand cannot be 0") } return nil } diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index a3df9f579..1b1e87ff5 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -39,7 +39,7 @@ type MsgAddFinalitySig struct { // block_height is the height of the voted block BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` // block_last_commit_hash is the last_commit_hash of the voted block - BlockLastCommitHash []byte `protobuf:"bytes,4,opt,name=block_last_commit_hash,json=blockLastCommitHash,proto3" json:"block_last_commit_hash,omitempty"` + BlockAppHash []byte `protobuf:"bytes,4,opt,name=block_last_commit_hash,json=blockAppHash,proto3" json:"block_last_commit_hash,omitempty"` // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` @@ -94,9 +94,9 @@ func (m *MsgAddFinalitySig) GetBlockHeight() uint64 { return 0 } -func (m *MsgAddFinalitySig) GetBlockLastCommitHash() []byte { +func (m *MsgAddFinalitySig) GetBlockAppHash() []byte { if m != nil { - return m.BlockLastCommitHash + return m.BlockAppHash } return nil } @@ -348,47 +348,48 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } var fileDescriptor_2dd6da066b6baf1d = []byte{ - // 636 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0x8d, 0x93, 0x7c, 0xd5, 0xd7, 0x49, 0x28, 0xaa, 0x5b, 0xb5, 0x6e, 0x5a, 0x39, 0x21, 0x42, - 0xa8, 0x20, 0xb0, 0xfb, 0x47, 0x45, 0xd9, 0xd5, 0x08, 0x54, 0x68, 0x23, 0x22, 0xbb, 0x6c, 0x00, - 0xc9, 0x1a, 0xff, 0x74, 0x3c, 0xaa, 0xed, 0x31, 0x9e, 0x71, 0xd4, 0x6c, 0x79, 0x02, 0x16, 0x3c, - 0x08, 0x0b, 0xc4, 0x23, 0xa0, 0x2e, 0xab, 0xae, 0x50, 0x17, 0x11, 0x4a, 0x16, 0xbc, 0x06, 0x8a, - 0x7f, 0x9a, 0x92, 0xa4, 0x22, 0xb0, 0x60, 0xe7, 0x3b, 0xe7, 0xf8, 0x9e, 0x7b, 0xcf, 0xdc, 0x3b, - 0x60, 0xc5, 0x80, 0x46, 0xdb, 0x25, 0xbe, 0x7c, 0x84, 0x7d, 0xe8, 0x62, 0xd6, 0x96, 0x5b, 0xeb, - 0x32, 0x3b, 0x91, 0x82, 0x90, 0x30, 0xc2, 0xcf, 0xa5, 0xa8, 0x94, 0xa1, 0x52, 0x6b, 0xbd, 0x32, - 0x8f, 0x08, 0x22, 0x31, 0x2e, 0xf7, 0xbf, 0x12, 0x6a, 0x65, 0xc9, 0x24, 0xd4, 0x23, 0x54, 0x4f, - 0x80, 0x24, 0x48, 0xa1, 0xc5, 0x24, 0x92, 0x3d, 0x8a, 0xfa, 0xd9, 0x3d, 0x8a, 0x52, 0xa0, 0x36, - 0x4e, 0x3c, 0x80, 0x21, 0xf4, 0xd2, 0x5f, 0xeb, 0x5f, 0xf2, 0x60, 0xb6, 0x41, 0xd1, 0xae, 0x65, - 0x3d, 0x4b, 0x29, 0x1a, 0x46, 0xfc, 0x02, 0x98, 0xa2, 0x18, 0xf9, 0x76, 0x28, 0x70, 0x35, 0x6e, - 0x75, 0x5a, 0x4d, 0x23, 0xfe, 0x10, 0x80, 0x16, 0x74, 0x75, 0x83, 0x99, 0x7a, 0x70, 0x2c, 0xe4, - 0x6b, 0xdc, 0x6a, 0x59, 0xd9, 0xbe, 0xe8, 0x54, 0x37, 0x10, 0x66, 0x4e, 0x64, 0x48, 0x26, 0xf1, - 0xe4, 0x54, 0xd2, 0x74, 0x20, 0xf6, 0xb3, 0x40, 0x66, 0xed, 0xc0, 0xa6, 0x92, 0xf2, 0xbc, 0xb9, - 0xb9, 0xb5, 0xd6, 0x8c, 0x8c, 0x7d, 0xbb, 0xad, 0xfe, 0xdf, 0x82, 0xae, 0xc2, 0xcc, 0xe6, 0x31, - 0x7f, 0x0b, 0x94, 0x0d, 0x97, 0x98, 0xc7, 0xba, 0x63, 0x63, 0xe4, 0x30, 0xa1, 0x50, 0xe3, 0x56, - 0x8b, 0x6a, 0x29, 0x3e, 0xdb, 0x8b, 0x8f, 0xf8, 0x4d, 0xb0, 0x90, 0x50, 0x5c, 0x48, 0x99, 0x6e, - 0x12, 0xcf, 0xc3, 0x4c, 0x77, 0x20, 0x75, 0x84, 0x62, 0xbf, 0x08, 0x75, 0x2e, 0x46, 0x0f, 0x20, - 0x65, 0x4f, 0x62, 0x6c, 0x0f, 0x52, 0x87, 0x7f, 0x03, 0xca, 0x59, 0xdf, 0x3a, 0xc5, 0x48, 0xf8, - 0x2f, 0xae, 0xf7, 0xd1, 0x45, 0xa7, 0xba, 0x35, 0x59, 0xbd, 0x9a, 0xe9, 0xf8, 0x24, 0x0c, 0x9f, - 0xbe, 0x3c, 0xd4, 0x34, 0x8c, 0xd4, 0xd2, 0xd1, 0xc0, 0xa2, 0xfa, 0x32, 0x58, 0x1a, 0xf1, 0x4d, - 0xb5, 0x69, 0x40, 0x7c, 0x6a, 0xd7, 0xcf, 0xf3, 0x60, 0xbe, 0x41, 0x51, 0x52, 0x4b, 0x33, 0x32, - 0x54, 0xe8, 0x5b, 0x07, 0x98, 0xb2, 0x7f, 0x6f, 0x2c, 0x65, 0x30, 0x64, 0x43, 0xc6, 0xc6, 0x67, - 0xa9, 0xb1, 0x6f, 0xc1, 0x8d, 0x20, 0x32, 0xf4, 0x10, 0xfa, 0x96, 0xee, 0x62, 0xca, 0x84, 0x62, - 0xad, 0xf0, 0x57, 0x26, 0xa5, 0x5d, 0xaa, 0xa5, 0xe0, 0x4a, 0xbb, 0xfb, 0xa0, 0x30, 0x30, 0x7e, - 0xe7, 0xa2, 0x53, 0x7d, 0xf8, 0x27, 0xfd, 0x68, 0x18, 0xf9, 0x90, 0x45, 0xa1, 0xad, 0xf6, 0xb3, - 0xd4, 0x45, 0xb0, 0x32, 0xce, 0xd3, 0x4b, 0xd3, 0x3f, 0x72, 0xe0, 0x66, 0x83, 0xa2, 0x57, 0x81, - 0x05, 0x99, 0xdd, 0x8c, 0x87, 0x9c, 0xdf, 0x06, 0xd3, 0x30, 0x62, 0x0e, 0x09, 0x31, 0x6b, 0x27, - 0x96, 0x2b, 0xc2, 0xf9, 0xe7, 0x07, 0xf3, 0xe9, 0xfa, 0xec, 0x5a, 0x56, 0x68, 0x53, 0xaa, 0xb1, - 0x10, 0xfb, 0x48, 0x1d, 0x50, 0xf9, 0x1d, 0x30, 0x95, 0xac, 0x49, 0x7c, 0x17, 0xa5, 0x8d, 0x65, - 0x69, 0xcc, 0xa2, 0x4a, 0x89, 0x88, 0x52, 0x3c, 0xed, 0x54, 0x73, 0x6a, 0xfa, 0xc3, 0xe3, 0x99, - 0xf7, 0x3f, 0x3e, 0xdd, 0x1b, 0xa4, 0xaa, 0x2f, 0x81, 0xc5, 0xa1, 0xaa, 0xb2, 0x8a, 0x37, 0xbe, - 0xe6, 0x41, 0xa1, 0x41, 0x11, 0xef, 0x80, 0x99, 0xa1, 0x05, 0xbc, 0x33, 0x56, 0x6f, 0x64, 0xe0, - 0x2a, 0xd2, 0x64, 0xbc, 0x4c, 0x91, 0x7f, 0x07, 0x66, 0x47, 0x87, 0xf2, 0xee, 0x75, 0x49, 0x46, - 0xa8, 0x95, 0xf5, 0x89, 0xa9, 0x97, 0x92, 0x06, 0x28, 0xff, 0x72, 0x25, 0xb7, 0xaf, 0x4b, 0x71, - 0x95, 0x55, 0xb9, 0x3f, 0x09, 0x2b, 0xd3, 0x50, 0x5e, 0x9c, 0x76, 0x45, 0xee, 0xac, 0x2b, 0x72, - 0xdf, 0xbb, 0x22, 0xf7, 0xa1, 0x27, 0xe6, 0xce, 0x7a, 0x62, 0xee, 0x5b, 0x4f, 0xcc, 0xbd, 0x5e, - 0xfb, 0xdd, 0xc0, 0x9d, 0x0c, 0xde, 0xc6, 0x78, 0xf6, 0x8c, 0xa9, 0xf8, 0x61, 0xdc, 0xfc, 0x19, - 0x00, 0x00, 0xff, 0xff, 0x75, 0xe4, 0x6f, 0x66, 0xb9, 0x05, 0x00, 0x00, + // 653 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x4f, 0x4f, 0x13, 0x41, + 0x14, 0xef, 0xb6, 0x85, 0xc8, 0xb4, 0x62, 0x58, 0x08, 0x2c, 0x85, 0x6c, 0x6b, 0x63, 0x0c, 0x12, + 0xdd, 0xe5, 0x9f, 0x44, 0xb8, 0x51, 0xa3, 0x41, 0xa1, 0xb1, 0xd9, 0xe2, 0x45, 0x4d, 0x36, 0xb3, + 0x7f, 0x98, 0x9d, 0xb0, 0xbb, 0xb3, 0xee, 0x4c, 0x1b, 0x7a, 0x33, 0x7e, 0x02, 0x0f, 0x7e, 0x10, + 0x0e, 0x7e, 0x03, 0x2f, 0x5c, 0x4c, 0x88, 0x27, 0xc3, 0xa1, 0x1a, 0x38, 0xf0, 0x35, 0x4c, 0x77, + 0x67, 0x29, 0xb4, 0x25, 0x56, 0x0f, 0xde, 0x76, 0xe6, 0xf7, 0xdb, 0xf7, 0x7e, 0xef, 0xf7, 0xde, + 0x1b, 0x30, 0x6f, 0x40, 0xa3, 0xe5, 0x12, 0x5f, 0xdd, 0xc7, 0x3e, 0x74, 0x31, 0x6b, 0xa9, 0xcd, + 0x65, 0x95, 0x1d, 0x2a, 0x41, 0x48, 0x18, 0x11, 0x27, 0x39, 0xaa, 0x24, 0xa8, 0xd2, 0x5c, 0x2e, + 0x4c, 0x21, 0x82, 0x48, 0x84, 0xab, 0x9d, 0xaf, 0x98, 0x5a, 0x98, 0x35, 0x09, 0xf5, 0x08, 0xd5, + 0x63, 0x20, 0x3e, 0x70, 0x68, 0x26, 0x3e, 0xa9, 0x1e, 0x45, 0x9d, 0xe8, 0x1e, 0x45, 0x1c, 0x28, + 0x0d, 0x4a, 0x1e, 0xc0, 0x10, 0x7a, 0xfc, 0xd7, 0xf2, 0xd7, 0x34, 0x98, 0xa8, 0x52, 0xb4, 0x65, + 0x59, 0xcf, 0x39, 0xa5, 0x8e, 0x91, 0x38, 0x0d, 0x46, 0x29, 0x46, 0xbe, 0x1d, 0x4a, 0x42, 0x49, + 0x58, 0x18, 0xd3, 0xf8, 0x49, 0xdc, 0x03, 0xa0, 0x09, 0x5d, 0xdd, 0x60, 0xa6, 0x1e, 0x1c, 0x48, + 0xe9, 0x92, 0xb0, 0x90, 0xaf, 0xac, 0x9f, 0xb6, 0x8b, 0x2b, 0x08, 0x33, 0xa7, 0x61, 0x28, 0x26, + 0xf1, 0x54, 0x9e, 0xd2, 0x74, 0x20, 0xf6, 0x93, 0x83, 0xca, 0x5a, 0x81, 0x4d, 0x95, 0xca, 0x8b, + 0xda, 0xea, 0xda, 0x52, 0xad, 0x61, 0xec, 0xd8, 0x2d, 0xed, 0x56, 0x13, 0xba, 0x15, 0x66, 0xd6, + 0x0e, 0xc4, 0xbb, 0x20, 0x6f, 0xb8, 0xc4, 0x3c, 0xd0, 0x1d, 0x1b, 0x23, 0x87, 0x49, 0x99, 0x92, + 0xb0, 0x90, 0xd5, 0x72, 0xd1, 0xdd, 0x76, 0x74, 0x25, 0xae, 0x82, 0xe9, 0x98, 0xe2, 0x42, 0xca, + 0x74, 0x93, 0x78, 0x1e, 0x66, 0xba, 0x03, 0xa9, 0x23, 0x65, 0x3b, 0x22, 0xb4, 0xc9, 0x08, 0xdd, + 0x85, 0x94, 0x3d, 0x8d, 0xb0, 0x6d, 0x48, 0x1d, 0xf1, 0x2d, 0xc8, 0x27, 0x75, 0xeb, 0x14, 0x23, + 0x69, 0x24, 0xd2, 0xfb, 0xe4, 0xb4, 0x5d, 0x5c, 0x1b, 0x4e, 0x6f, 0xdd, 0x74, 0x7c, 0x12, 0x86, + 0xcf, 0x5e, 0xed, 0xd5, 0xeb, 0x18, 0x69, 0xb9, 0xfd, 0xae, 0x45, 0x9b, 0xb9, 0x8f, 0x17, 0x47, + 0x8b, 0xdc, 0x97, 0xf2, 0x1c, 0x98, 0xed, 0x33, 0x51, 0xb3, 0x69, 0x40, 0x7c, 0x6a, 0x97, 0x7f, + 0xa6, 0xc1, 0x54, 0x95, 0xa2, 0x58, 0x58, 0xad, 0x61, 0x68, 0xd0, 0xb7, 0x76, 0x31, 0x65, 0xff, + 0xdf, 0x65, 0xca, 0x60, 0xc8, 0x7a, 0x5c, 0x8e, 0xee, 0xb8, 0xcb, 0xef, 0xc0, 0xed, 0xa0, 0x61, + 0xe8, 0x21, 0xf4, 0x2d, 0xdd, 0xc5, 0x94, 0x49, 0xd9, 0x52, 0xe6, 0x9f, 0x1c, 0xe3, 0x55, 0x6a, + 0xb9, 0xe0, 0x4a, 0xb9, 0x3b, 0x20, 0xd3, 0xed, 0xc2, 0xc6, 0x69, 0xbb, 0xf8, 0xf8, 0x6f, 0xea, + 0xa9, 0x63, 0xe4, 0x43, 0xd6, 0x08, 0x6d, 0xad, 0x13, 0xe5, 0xba, 0xfd, 0x32, 0x98, 0x1f, 0x64, + 0xf0, 0x65, 0x07, 0x3e, 0x0b, 0xe0, 0x4e, 0x95, 0xa2, 0xd7, 0x81, 0x05, 0x99, 0x5d, 0x8b, 0xc6, + 0x5f, 0x5c, 0x07, 0x63, 0xb0, 0xc1, 0x1c, 0x12, 0x62, 0xd6, 0x8a, 0xfd, 0xaf, 0x48, 0xdf, 0xbf, + 0x3c, 0x9a, 0xe2, 0x8b, 0xb5, 0x65, 0x59, 0xa1, 0x4d, 0x69, 0x9d, 0x85, 0xd8, 0x47, 0x5a, 0x97, + 0x2a, 0x6e, 0x80, 0xd1, 0x78, 0x81, 0xa2, 0xc6, 0xe4, 0x56, 0xe6, 0x94, 0x01, 0x2b, 0xac, 0xc4, + 0x49, 0x2a, 0xd9, 0xe3, 0x76, 0x31, 0xa5, 0xf1, 0x1f, 0x36, 0xc7, 0x3b, 0x9a, 0xbb, 0xa1, 0xca, + 0xb3, 0x60, 0xa6, 0x47, 0x55, 0xa2, 0x78, 0xe5, 0x5b, 0x1a, 0x64, 0xaa, 0x14, 0x89, 0x0e, 0x18, + 0xef, 0x59, 0xcd, 0xfb, 0x03, 0xf3, 0xf5, 0x4d, 0x5f, 0x41, 0x19, 0x8e, 0x97, 0x64, 0x14, 0xdf, + 0x83, 0x89, 0xfe, 0x09, 0x7d, 0x70, 0x53, 0x90, 0x3e, 0x6a, 0x61, 0x79, 0x68, 0xea, 0x65, 0x4a, + 0x03, 0xe4, 0xaf, 0xb5, 0xe4, 0xde, 0x4d, 0x21, 0xae, 0xb2, 0x0a, 0x0f, 0x87, 0x61, 0x25, 0x39, + 0x0a, 0x23, 0x1f, 0x2e, 0x8e, 0x16, 0x85, 0xca, 0xcb, 0xe3, 0x33, 0x59, 0x38, 0x39, 0x93, 0x85, + 0x5f, 0x67, 0xb2, 0xf0, 0xe9, 0x5c, 0x4e, 0x9d, 0x9c, 0xcb, 0xa9, 0x1f, 0xe7, 0x72, 0xea, 0xcd, + 0xd2, 0x9f, 0x86, 0xf0, 0xb0, 0xfb, 0x78, 0x46, 0xf3, 0x68, 0x8c, 0x46, 0x2f, 0xe7, 0xea, 0xef, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xdf, 0xe0, 0xeb, 0xda, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -583,10 +584,10 @@ func (m *MsgAddFinalitySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - if len(m.BlockLastCommitHash) > 0 { - i -= len(m.BlockLastCommitHash) - copy(dAtA[i:], m.BlockLastCommitHash) - i = encodeVarintTx(dAtA, i, uint64(len(m.BlockLastCommitHash))) + if len(m.BlockAppHash) > 0 { + i -= len(m.BlockAppHash) + copy(dAtA[i:], m.BlockAppHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.BlockAppHash))) i-- dAtA[i] = 0x22 } @@ -827,7 +828,7 @@ func (m *MsgAddFinalitySig) Size() (n int) { if m.BlockHeight != 0 { n += 1 + sovTx(uint64(m.BlockHeight)) } - l = len(m.BlockLastCommitHash) + l = len(m.BlockAppHash) if l > 0 { n += 1 + l + sovTx(uint64(l)) } @@ -1033,7 +1034,7 @@ func (m *MsgAddFinalitySig) Unmarshal(dAtA []byte) error { } case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlockLastCommitHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BlockAppHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1060,9 +1061,9 @@ func (m *MsgAddFinalitySig) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BlockLastCommitHash = append(m.BlockLastCommitHash[:0], dAtA[iNdEx:postIndex]...) - if m.BlockLastCommitHash == nil { - m.BlockLastCommitHash = []byte{} + m.BlockAppHash = append(m.BlockAppHash[:0], dAtA[iNdEx:postIndex]...) + if m.BlockAppHash == nil { + m.BlockAppHash = []byte{} } iNdEx = postIndex case 5: diff --git a/x/incentive/abci.go b/x/incentive/abci.go index cd70e2b32..e8e685d09 100644 --- a/x/incentive/abci.go +++ b/x/incentive/abci.go @@ -1,6 +1,7 @@ package incentive import ( + "context" "time" "github.com/babylonchain/babylon/x/incentive/keeper" @@ -10,20 +11,21 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) // handle coins in the fee collector account, including // - send a portion of coins in the fee collector account to the incentive module account // - accumulate BTC staking gauge at the current height // - accumulate BTC timestamping gauge at the current epoch - if ctx.BlockHeight() > 0 { + if sdk.UnwrapSDKContext(ctx).BlockHeight() > 0 { k.HandleCoinsInFeeCollector(ctx) } + return nil } -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { +func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go index 294110267..f5d5afa93 100644 --- a/x/incentive/client/cli/tx.go +++ b/x/incentive/client/cli/tx.go @@ -2,8 +2,6 @@ package cli import ( "fmt" - "time" - "github.com/spf13/cobra" "github.com/babylonchain/babylon/x/incentive/types" @@ -12,10 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/tx" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go index 91a195219..1122dbdba 100644 --- a/x/incentive/genesis.go +++ b/x/incentive/genesis.go @@ -1,20 +1,20 @@ package incentive import ( + "context" "github.com/babylonchain/babylon/x/incentive/keeper" "github.com/babylonchain/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) } } // ExportGenesis returns the module's exported genesis -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index 1ca1c4934..252f2f082 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -1,16 +1,18 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/incentive/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // RewardBTCStaking distributes rewards to BTC validators/delegations at a given height according // to the reward distribution cache // (adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L12-L64) -func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.RewardDistCache) { +func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, rdc *bstypes.RewardDistCache) { gauge := k.GetBTCStakingGauge(ctx, height) if gauge == nil { // failing to get a reward gauge at previous height is a programming error @@ -36,9 +38,9 @@ func (k Keeper) RewardBTCStaking(ctx sdk.Context, height uint64, rdc *bstypes.Re // TODO: handle the change in the gauge due to the truncating operations } -func (k Keeper) accumulateBTCStakingReward(ctx sdk.Context, btcStakingReward sdk.Coins) { +func (k Keeper) accumulateBTCStakingReward(ctx context.Context, btcStakingReward sdk.Coins) { // update BTC staking gauge - height := uint64(ctx.BlockHeight()) + height := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) gauge := types.NewGauge(btcStakingReward...) k.SetBTCStakingGauge(ctx, height, gauge) @@ -50,13 +52,13 @@ func (k Keeper) accumulateBTCStakingReward(ctx sdk.Context, btcStakingReward sdk } } -func (k Keeper) SetBTCStakingGauge(ctx sdk.Context, height uint64, gauge *types.Gauge) { +func (k Keeper) SetBTCStakingGauge(ctx context.Context, height uint64, gauge *types.Gauge) { store := k.btcStakingGaugeStore(ctx) gaugeBytes := k.cdc.MustMarshal(gauge) store.Set(sdk.Uint64ToBigEndian(height), gaugeBytes) } -func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) *types.Gauge { +func (k Keeper) GetBTCStakingGauge(ctx context.Context, height uint64) *types.Gauge { store := k.btcStakingGaugeStore(ctx) gaugeBytes := store.Get(sdk.Uint64ToBigEndian(height)) if gaugeBytes == nil { @@ -73,7 +75,7 @@ func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) *types.Gauge // prefix: BTCStakingGaugeKey // key: gauge height // value: gauge of rewards for BTC staking at this height -func (k Keeper) btcStakingGaugeStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BTCStakingGaugeKey) +func (k Keeper) btcStakingGaugeStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.BTCStakingGaugeKey) } diff --git a/x/incentive/keeper/btc_timestamping_gauge.go b/x/incentive/keeper/btc_timestamping_gauge.go index 232c8c78b..96e5b28a7 100644 --- a/x/incentive/keeper/btc_timestamping_gauge.go +++ b/x/incentive/keeper/btc_timestamping_gauge.go @@ -1,16 +1,18 @@ package keeper import ( + "context" "cosmossdk.io/math" + "cosmossdk.io/store/prefix" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/babylonchain/babylon/x/incentive/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) // RewardBTCTimestamping distributes rewards to submitters/reporters of a checkpoint at a given epoch // according to the reward distribution cache -func (k Keeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rdi *btcctypes.RewardDistInfo) { +func (k Keeper) RewardBTCTimestamping(ctx context.Context, epoch uint64, rdi *btcctypes.RewardDistInfo) { gauge := k.GetBTCTimestampingGauge(ctx, epoch) if gauge == nil { // failing to get a reward gauge at a finalised epoch is a programming error @@ -67,7 +69,7 @@ func (k Keeper) RewardBTCTimestamping(ctx sdk.Context, epoch uint64, rdi *btccty } } -func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestampingReward sdk.Coins) { +func (k Keeper) accumulateBTCTimestampingReward(ctx context.Context, btcTimestampingReward sdk.Coins) { epoch := k.epochingKeeper.GetEpoch(ctx) // update BTC timestamping reward gauge @@ -90,13 +92,13 @@ func (k Keeper) accumulateBTCTimestampingReward(ctx sdk.Context, btcTimestamping } } -func (k Keeper) SetBTCTimestampingGauge(ctx sdk.Context, epoch uint64, gauge *types.Gauge) { +func (k Keeper) SetBTCTimestampingGauge(ctx context.Context, epoch uint64, gauge *types.Gauge) { store := k.btcTimestampingGaugeStore(ctx) gaugeBytes := k.cdc.MustMarshal(gauge) store.Set(sdk.Uint64ToBigEndian(epoch), gaugeBytes) } -func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) *types.Gauge { +func (k Keeper) GetBTCTimestampingGauge(ctx context.Context, epoch uint64) *types.Gauge { store := k.btcTimestampingGaugeStore(ctx) gaugeBytes := store.Get(sdk.Uint64ToBigEndian(epoch)) if gaugeBytes == nil { @@ -113,7 +115,7 @@ func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) *types.Ga // prefix: BTCTimestampingGaugeKey // key: epoch number // value: gauge of rewards for BTC timestamping at this epoch -func (k Keeper) btcTimestampingGaugeStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.BTCTimestampingGaugeKey) +func (k Keeper) btcTimestampingGaugeStore(ctx context.Context) prefix.Store { + storeAdaptor := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdaptor, types.BTCTimestampingGaugeKey) } diff --git a/x/incentive/keeper/grpc_query_test.go b/x/incentive/keeper/grpc_query_test.go index d0ad4ae8d..fff10259c 100644 --- a/x/incentive/keeper/grpc_query_test.go +++ b/x/incentive/keeper/grpc_query_test.go @@ -17,7 +17,6 @@ func FuzzRewardGaugesQuery(f *testing.F) { r := rand.New(rand.NewSource(seed)) keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) - wctx := sdk.WrapSDKContext(ctx) // generate a list of random RewardGauge map and insert them to KVStore // where in each map, key is stakeholder type and address is the reward gauge @@ -43,7 +42,7 @@ func FuzzRewardGaugesQuery(f *testing.F) { req := &types.QueryRewardGaugesRequest{ Address: sAddrList[i].String(), } - resp, err := keeper.RewardGauges(wctx, req) + resp, err := keeper.RewardGauges(ctx, req) require.NoError(t, err) for sTypeStr, rg := range rgMaps[i] { require.Equal(t, rg.Coins, resp.RewardGauges[sTypeStr].Coins) @@ -58,7 +57,6 @@ func FuzzBTCStakingGaugeQuery(f *testing.F) { r := rand.New(rand.NewSource(seed)) keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) - wctx := sdk.WrapSDKContext(ctx) // generate a list of random Gauges at random heights, then insert them to KVStore heightList := []uint64{} @@ -77,9 +75,9 @@ func FuzzBTCStakingGaugeQuery(f *testing.F) { req := &types.QueryBTCStakingGaugeRequest{ Height: heightList[i], } - resp, err := keeper.BTCStakingGauge(wctx, req) + resp, err := keeper.BTCStakingGauge(ctx, req) require.NoError(t, err) - require.True(t, resp.Gauge.Coins.IsEqual(gaugeList[i].Coins)) + require.True(t, resp.Gauge.Coins.Equal(gaugeList[i].Coins)) } }) } @@ -90,7 +88,6 @@ func FuzzBTCTimestampingGaugeQuery(f *testing.F) { r := rand.New(rand.NewSource(seed)) keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) - wctx := sdk.WrapSDKContext(ctx) // generate a list of random Gauges at random heights, then insert them to KVStore epochList := []uint64{} @@ -109,9 +106,9 @@ func FuzzBTCTimestampingGaugeQuery(f *testing.F) { req := &types.QueryBTCTimestampingGaugeRequest{ EpochNum: epochList[i], } - resp, err := keeper.BTCTimestampingGauge(wctx, req) + resp, err := keeper.BTCTimestampingGauge(ctx, req) require.NoError(t, err) - require.True(t, resp.Gauge.Coins.IsEqual(gaugeList[i].Coins)) + require.True(t, resp.Gauge.Coins.Equal(gaugeList[i].Coins)) } }) } diff --git a/x/incentive/keeper/intercept_fee_collector.go b/x/incentive/keeper/intercept_fee_collector.go index 5dba537a2..88500e558 100644 --- a/x/incentive/keeper/intercept_fee_collector.go +++ b/x/incentive/keeper/intercept_fee_collector.go @@ -1,15 +1,15 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // HandleCoinsInFeeCollector intercepts a portion of coins in fee collector, and distributes // them to BTC staking gauge and BTC timestamping gauge of the current height and epoch, respectively. // It is invoked upon every `BeginBlock`. // adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L15-L26 -func (k Keeper) HandleCoinsInFeeCollector(ctx sdk.Context) { +func (k Keeper) HandleCoinsInFeeCollector(ctx context.Context) { params := k.GetParams(ctx) // find the fee collector account diff --git a/x/incentive/keeper/intercept_fee_collector_test.go b/x/incentive/keeper/intercept_fee_collector_test.go index c3ddb6d1d..93c203e53 100644 --- a/x/incentive/keeper/intercept_fee_collector_test.go +++ b/x/incentive/keeper/intercept_fee_collector_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + sdkmath "cosmossdk.io/math" "math/rand" "testing" @@ -16,7 +17,7 @@ import ( var ( feeCollectorAcc = authtypes.NewEmptyModuleAccount(authtypes.FeeCollectorName) - fees = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))) + fees = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100))) ) func FuzzInterceptFeeCollector(f *testing.F) { diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 3e72c4efa..4d931f4fe 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -1,24 +1,23 @@ package keeper import ( + corestoretypes "cosmossdk.io/core/store" "fmt" + "cosmossdk.io/log" "github.com/babylonchain/babylon/x/incentive/types" - "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService + epochingKeeper types.EpochingKeeper bankKeeper types.BankKeeper accountKeeper types.AccountKeeper - epochingKeeper types.EpochingKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -29,8 +28,7 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, bankKeeper types.BankKeeper, accountKeeper types.AccountKeeper, epochingKeeper types.EpochingKeeper, @@ -39,11 +37,10 @@ func NewKeeper( ) Keeper { return Keeper{ cdc: cdc, - storeKey: storeKey, - memKey: memKey, + storeService: storeService, + epochingKeeper: epochingKeeper, bankKeeper: bankKeeper, accountKeeper: accountKeeper, - epochingKeeper: epochingKeeper, authority: authority, feeCollectorName: feeCollectorName, } diff --git a/x/incentive/keeper/msg_server_test.go b/x/incentive/keeper/msg_server_test.go index b320c7a58..5b68ded8d 100644 --- a/x/incentive/keeper/msg_server_test.go +++ b/x/incentive/keeper/msg_server_test.go @@ -9,14 +9,13 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/incentive/keeper" "github.com/babylonchain/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { k, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) - return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) + return keeper.NewMsgServerImpl(*k), ctx } func TestMsgServer(t *testing.T) { diff --git a/x/incentive/keeper/params.go b/x/incentive/keeper/params.go index 3ea5346a3..4e8c80e72 100644 --- a/x/incentive/keeper/params.go +++ b/x/incentive/keeper/params.go @@ -1,25 +1,27 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // SetParams sets the x/incentive module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) - return nil + return store.Set(types.ParamsKey, bz) } // GetParams returns the current x/incentive module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } diff --git a/x/incentive/keeper/query_params_test.go b/x/incentive/keeper/query_params_test.go index 5c20279cd..c98188c76 100644 --- a/x/incentive/keeper/query_params_test.go +++ b/x/incentive/keeper/query_params_test.go @@ -5,18 +5,16 @@ import ( testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/incentive/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func TestParamsQuery(t *testing.T) { keeper, ctx := testkeeper.IncentiveKeeper(t, nil, nil, nil) - wctx := sdk.WrapSDKContext(ctx) params := types.DefaultParams() err := keeper.SetParams(ctx, params) require.NoError(t, err) - response, err := keeper.Params(wctx, &types.QueryParamsRequest{}) + response, err := keeper.Params(ctx, &types.QueryParamsRequest{}) require.NoError(t, err) require.Equal(t, &types.QueryParamsResponse{Params: params}, response) } diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go index 5389672d9..f9dd70f71 100644 --- a/x/incentive/keeper/reward_gauge.go +++ b/x/incentive/keeper/reward_gauge.go @@ -1,12 +1,14 @@ package keeper import ( + "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/incentive/types" - "github.com/cosmos/cosmos-sdk/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) withdrawReward(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) (sdk.Coins, error) { +func (k Keeper) withdrawReward(ctx context.Context, sType types.StakeholderType, addr sdk.AccAddress) (sdk.Coins, error) { // retrieve reward gauge of the given stakeholder rg := k.GetRewardGauge(ctx, sType, addr) if rg == nil { @@ -29,7 +31,7 @@ func (k Keeper) withdrawReward(ctx sdk.Context, sType types.StakeholderType, add } // accumulateRewardGauge accumulates the given reward of of a given stakeholder in a given type -func (k Keeper) accumulateRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, reward sdk.Coins) { +func (k Keeper) accumulateRewardGauge(ctx context.Context, sType types.StakeholderType, addr sdk.AccAddress, reward sdk.Coins) { // if reward contains nothing, do nothing if !reward.IsAllPositive() { return @@ -45,13 +47,13 @@ func (k Keeper) accumulateRewardGauge(ctx sdk.Context, sType types.StakeholderTy k.SetRewardGauge(ctx, sType, addr, rg) } -func (k Keeper) SetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { +func (k Keeper) SetRewardGauge(ctx context.Context, sType types.StakeholderType, addr sdk.AccAddress, rg *types.RewardGauge) { store := k.rewardGaugeStore(ctx, sType) rgBytes := k.cdc.MustMarshal(rg) store.Set(addr.Bytes(), rgBytes) } -func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, addr sdk.AccAddress) *types.RewardGauge { +func (k Keeper) GetRewardGauge(ctx context.Context, sType types.StakeholderType, addr sdk.AccAddress) *types.RewardGauge { store := k.rewardGaugeStore(ctx, sType) rgBytes := store.Get(addr.Bytes()) if rgBytes == nil { @@ -68,8 +70,8 @@ func (k Keeper) GetRewardGauge(ctx sdk.Context, sType types.StakeholderType, add // prefix: RewardGaugeKey // key: (stakeholder type || stakeholder address) // value: reward gauge -func (k Keeper) rewardGaugeStore(ctx sdk.Context, sType types.StakeholderType) prefix.Store { - store := ctx.KVStore(k.storeKey) - rgStore := prefix.NewStore(store, types.RewardGaugeKey) +func (k Keeper) rewardGaugeStore(ctx context.Context, sType types.StakeholderType) prefix.Store { + storeAdaptor := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + rgStore := prefix.NewStore(storeAdaptor, types.RewardGaugeKey) return prefix.NewStore(rgStore, sType.Bytes()) } diff --git a/x/incentive/module.go b/x/incentive/module.go index 8401d3afa..862daf4ff 100644 --- a/x/incentive/module.go +++ b/x/incentive/module.go @@ -2,6 +2,7 @@ package incentive import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -19,8 +20,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -117,14 +120,12 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the module's genesis initialization. It returns no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. @@ -136,10 +137,18 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion is a sequence number for state-breaking change of the module. It should be incremented on each consensus-breaking change introduced by the module. To avoid wrong/empty versions, the initial version should be set to 1 func (AppModule) ConsensusVersion() uint64 { return 1 } -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { return EndBlocker(ctx, am.keeper) } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker +} diff --git a/x/incentive/module_simulation.go b/x/incentive/module_simulation.go index 0f62adfc4..1b5b69064 100644 --- a/x/incentive/module_simulation.go +++ b/x/incentive/module_simulation.go @@ -7,7 +7,6 @@ import ( incentivesimulation "github.com/babylonchain/babylon/x/incentive/simulation" "github.com/babylonchain/babylon/x/incentive/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -35,7 +34,7 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { } // RegisterStoreDecoder registers a decoder. -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} // ProposalContents doesn't return any content functions for governance proposals. func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go index 23390f945..3e56f8d36 100644 --- a/x/incentive/types/errors.go +++ b/x/incentive/types/errors.go @@ -1,7 +1,5 @@ package types -// DONTCOVER - import ( errorsmod "cosmossdk.io/errors" ) diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index 696ac50f9..a8000ec2d 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -1,23 +1,23 @@ package types import ( + "context" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI } type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - SendCoinsFromModuleToModule(ctx sdk.Context, senderModule string, recipientModule string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins + SendCoinsFromModuleToModule(ctx context.Context, senderModule string, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error } type EpochingKeeper interface { - GetEpoch(ctx sdk.Context) *epochingtypes.Epoch + GetEpoch(ctx context.Context) *epochingtypes.Epoch } diff --git a/x/incentive/types/genesis.go b/x/incentive/types/genesis.go index bdb9f4151..9d633ecd7 100644 --- a/x/incentive/types/genesis.go +++ b/x/incentive/types/genesis.go @@ -1,8 +1,5 @@ package types -// DefaultIndex is the default global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index 59ce080f0..6f76f0acf 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -1,7 +1,7 @@ package types import ( - fmt "fmt" + "fmt" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -37,7 +37,7 @@ func (rg *RewardGauge) SetFullyWithdrawn() { // IsFullyWithdrawn returns whether the reward gauge has nothing to withdraw func (rg *RewardGauge) IsFullyWithdrawn() bool { - return rg.Coins.IsEqual(rg.WithdrawnCoins) + return rg.Coins.Equal(rg.WithdrawnCoins) } func (rg *RewardGauge) Add(coins sdk.Coins) { @@ -55,7 +55,7 @@ func GetCoinsPortion(coinsInt sdk.Coins, portion math.LegacyDec) sdk.Coins { return portionCoinsInt } -// enum for stakeholder type, used as key prefix in KVStore +// StakeholderType enum for stakeholder type, used as key prefix in KVStore type StakeholderType byte const ( diff --git a/x/incentive/types/mocked_keepers.go b/x/incentive/types/mocked_keepers.go index 4cfffc5f9..833bbb50d 100644 --- a/x/incentive/types/mocked_keepers.go +++ b/x/incentive/types/mocked_keepers.go @@ -5,11 +5,11 @@ package types import ( + context "context" reflect "reflect" types "github.com/babylonchain/babylon/x/epoching/types" types0 "github.com/cosmos/cosmos-sdk/types" - types1 "github.com/cosmos/cosmos-sdk/x/auth/types" gomock "github.com/golang/mock/gomock" ) @@ -37,10 +37,10 @@ func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { } // GetAccount mocks base method. -func (m *MockAccountKeeper) GetAccount(ctx types0.Context, addr types0.AccAddress) types1.AccountI { +func (m *MockAccountKeeper) GetAccount(ctx context.Context, addr types0.AccAddress) types0.AccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAccount", ctx, addr) - ret0, _ := ret[0].(types1.AccountI) + ret0, _ := ret[0].(types0.AccountI) return ret0 } @@ -51,10 +51,10 @@ func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomo } // GetModuleAccount mocks base method. -func (m *MockAccountKeeper) GetModuleAccount(ctx types0.Context, name string) types1.ModuleAccountI { +func (m *MockAccountKeeper) GetModuleAccount(ctx context.Context, name string) types0.ModuleAccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAccount", ctx, name) - ret0, _ := ret[0].(types1.ModuleAccountI) + ret0, _ := ret[0].(types0.ModuleAccountI) return ret0 } @@ -88,7 +88,7 @@ func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { } // GetAllBalances mocks base method. -func (m *MockBankKeeper) GetAllBalances(ctx types0.Context, addr types0.AccAddress) types0.Coins { +func (m *MockBankKeeper) GetAllBalances(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) ret0, _ := ret[0].(types0.Coins) @@ -102,7 +102,7 @@ func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr interface{}) *gom } // SendCoinsFromModuleToAccount mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types0.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types0.AccAddress, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) ret0, _ := ret[0].(error) @@ -116,7 +116,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToAccount(ctx, senderMo } // SendCoinsFromModuleToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx types0.Context, senderModule, recipientModule string, amt types0.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt types0.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToModule", ctx, senderModule, recipientModule, amt) ret0, _ := ret[0].(error) @@ -130,7 +130,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToModule(ctx, senderMod } // SpendableCoins mocks base method. -func (m *MockBankKeeper) SpendableCoins(ctx types0.Context, addr types0.AccAddress) types0.Coins { +func (m *MockBankKeeper) SpendableCoins(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) ret0, _ := ret[0].(types0.Coins) @@ -167,7 +167,7 @@ func (m *MockEpochingKeeper) EXPECT() *MockEpochingKeeperMockRecorder { } // GetEpoch mocks base method. -func (m *MockEpochingKeeper) GetEpoch(ctx types0.Context) *types.Epoch { +func (m *MockEpochingKeeper) GetEpoch(ctx context.Context) *types.Epoch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpoch", ctx) ret0, _ := ret[0].(*types.Epoch) diff --git a/x/incentive/types/params.pb.go b/x/incentive/types/params.pb.go index cb5ba22b3..4b9fff8b7 100644 --- a/x/incentive/types/params.pb.go +++ b/x/incentive/types/params.pb.go @@ -4,9 +4,9 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" _ "github.com/cosmos/cosmos-proto" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -31,13 +31,13 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto type Params struct { // submitter_portion is the portion of rewards that goes to submitter - SubmitterPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=submitter_portion,json=submitterPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"submitter_portion"` + SubmitterPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=submitter_portion,json=submitterPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"submitter_portion"` // reporter_portion is the portion of rewards that goes to reporter - ReporterPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=reporter_portion,json=reporterPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"reporter_portion"` + ReporterPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=reporter_portion,json=reporterPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"reporter_portion"` // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations // NOTE: the portion of each BTC validator/delegation is calculated by using its voting // power and BTC validator's commission - BtcStakingPortion github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=btc_staking_portion,json=btcStakingPortion,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"btc_staking_portion"` + BtcStakingPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=btc_staking_portion,json=btcStakingPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"btc_staking_portion"` } func (m *Params) Reset() { *m = Params{} } @@ -79,25 +79,26 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/params.proto", fileDescriptor_c42276168f0adf4b) } var fileDescriptor_c42276168f0adf4b = []byte{ - // 287 bytes of a gzipped FileDescriptorProto + // 294 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0xcf, 0xcc, 0x4b, 0x4e, 0xcd, 0x2b, 0xc9, 0x2c, 0x4b, 0xd5, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x84, 0xca, 0xeb, 0xc1, 0xe5, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xb2, 0xfa, 0x20, 0x16, 0x44, 0xa1, 0x94, 0x64, - 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x48, 0x29, 0x5d, 0x60, 0xe2, - 0x62, 0x0b, 0x00, 0x1b, 0x2a, 0x94, 0xc9, 0x25, 0x58, 0x5c, 0x9a, 0x94, 0x9b, 0x59, 0x52, 0x92, + 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x71, 0x3c, 0x44, 0x02, 0xc2, 0x81, 0x48, 0x29, 0xad, 0x67, 0xe2, + 0x62, 0x0b, 0x00, 0x1b, 0x2a, 0x14, 0xc7, 0x25, 0x58, 0x5c, 0x9a, 0x94, 0x9b, 0x59, 0x52, 0x92, 0x5a, 0x14, 0x5f, 0x90, 0x5f, 0x54, 0x92, 0x99, 0x9f, 0x27, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe9, - 0x64, 0x73, 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0x6a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, - 0x7a, 0xc9, 0xf9, 0xb9, 0x50, 0x63, 0xa0, 0x94, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x49, 0x65, 0x41, - 0x6a, 0xb1, 0x9e, 0x4b, 0x6a, 0xf2, 0xa5, 0x2d, 0xba, 0x5c, 0x50, 0x5b, 0x5c, 0x52, 0x93, 0x83, - 0x04, 0xe0, 0xc6, 0x06, 0x40, 0x4c, 0x15, 0x4a, 0xe7, 0x12, 0x28, 0x4a, 0x05, 0x59, 0x81, 0x64, - 0x13, 0x13, 0x15, 0x6c, 0xe2, 0x87, 0x99, 0x0a, 0xb3, 0x28, 0x87, 0x4b, 0x38, 0xa9, 0x24, 0x39, - 0xbe, 0xb8, 0x24, 0x31, 0x3b, 0x33, 0x2f, 0x1d, 0x6e, 0x17, 0x33, 0x15, 0xec, 0x12, 0x4c, 0x2a, - 0x49, 0x0e, 0x86, 0x98, 0x0b, 0xb5, 0xcd, 0x8a, 0x65, 0xc6, 0x02, 0x79, 0x06, 0x27, 0xef, 0x13, - 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, - 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x44, 0xb2, 0x08, 0x1a, 0x77, 0xc9, - 0x19, 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0x52, 0x54, 0x83, 0xed, 0x4d, 0x62, 0x03, 0x47, - 0x93, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x1b, 0x30, 0x4e, 0x0c, 0x02, 0x00, 0x00, + 0x64, 0x78, 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0xd2, 0x10, 0xbd, 0xc5, 0x29, 0xd9, 0x7a, + 0x99, 0xf9, 0xfa, 0xb9, 0x89, 0x25, 0x19, 0x7a, 0x3e, 0xa9, 0xe9, 0x89, 0xc9, 0x95, 0x2e, 0xa9, + 0xc9, 0x97, 0xb6, 0xe8, 0x72, 0x41, 0x8d, 0x76, 0x49, 0x4d, 0x0e, 0x12, 0x80, 0x9b, 0x15, 0x00, + 0x31, 0x4a, 0x28, 0x86, 0x4b, 0xa0, 0x28, 0x15, 0x64, 0x2e, 0x92, 0xf1, 0x4c, 0xe4, 0x1a, 0xcf, + 0x0f, 0x33, 0x0a, 0x66, 0x7a, 0x22, 0x97, 0x70, 0x52, 0x49, 0x72, 0x7c, 0x71, 0x49, 0x62, 0x76, + 0x66, 0x5e, 0x3a, 0xdc, 0x02, 0x66, 0x72, 0x2d, 0x10, 0x4c, 0x2a, 0x49, 0x0e, 0x86, 0x18, 0x06, + 0xb5, 0xc2, 0x8a, 0x65, 0xc6, 0x02, 0x79, 0x06, 0x27, 0xef, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, + 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, + 0x3c, 0x96, 0x63, 0x88, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, + 0x87, 0x46, 0x4d, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x14, 0x93, 0x25, 0x95, + 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x58, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x55, 0x13, + 0x48, 0x09, 0xeb, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { diff --git a/x/incentive/types/tx.pb.go b/x/incentive/types/tx.pb.go index a7fa3568d..5e8fc701b 100644 --- a/x/incentive/types/tx.pb.go +++ b/x/incentive/types/tx.pb.go @@ -241,36 +241,37 @@ func init() { func init() { proto.RegisterFile("babylon/incentive/tx.proto", fileDescriptor_b4de6776d39a3a22) } var fileDescriptor_b4de6776d39a3a22 = []byte{ - // 455 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x41, 0x6b, 0xd4, 0x40, - 0x18, 0xdd, 0xb1, 0xb5, 0xd2, 0x51, 0x2a, 0x1d, 0x0a, 0x4d, 0x72, 0x48, 0x4b, 0xf0, 0xb0, 0x14, - 0x9b, 0x31, 0x15, 0x14, 0xbc, 0x35, 0x1e, 0x65, 0x41, 0x22, 0x22, 0x78, 0x50, 0x26, 0xc9, 0x30, - 0x19, 0x34, 0x33, 0x21, 0x33, 0xdd, 0x76, 0x2f, 0x1e, 0xfc, 0x05, 0xe2, 0xcf, 0xf0, 0xe4, 0xc1, - 0x1f, 0x51, 0xf0, 0x52, 0x3c, 0x79, 0x52, 0xd9, 0x3d, 0xf8, 0x37, 0x64, 0x32, 0x93, 0x76, 0x6d, - 0x0a, 0x7a, 0x9a, 0xf9, 0xf6, 0xbd, 0x79, 0xdf, 0xfb, 0xde, 0xb7, 0x81, 0x41, 0x4e, 0xf2, 0xd9, - 0x5b, 0x29, 0x30, 0x17, 0x05, 0x15, 0x9a, 0x4f, 0x29, 0xd6, 0x27, 0x71, 0xd3, 0x4a, 0x2d, 0xd1, - 0xa6, 0xc3, 0xe2, 0x73, 0x2c, 0xd8, 0x62, 0x92, 0xc9, 0x0e, 0xc5, 0xe6, 0x66, 0x89, 0x81, 0x5f, - 0x48, 0x55, 0x4b, 0xf5, 0xda, 0x02, 0xb6, 0x70, 0xd0, 0xb6, 0xad, 0x70, 0xad, 0x18, 0x9e, 0x26, - 0xe6, 0x70, 0x40, 0xe8, 0x80, 0x9c, 0x28, 0x8a, 0xa7, 0x49, 0x4e, 0x35, 0x49, 0x70, 0x21, 0xb9, - 0xe8, 0xf1, 0xa1, 0xb1, 0x86, 0xb4, 0xa4, 0x76, 0xc2, 0xd1, 0x21, 0xdc, 0x9c, 0x28, 0xf6, 0x82, - 0xeb, 0xaa, 0x6c, 0xc9, 0x71, 0x46, 0x8f, 0x49, 0x5b, 0x22, 0x04, 0x57, 0xf5, 0xac, 0xa1, 0x1e, - 0xd8, 0x05, 0xe3, 0xf5, 0xac, 0xbb, 0x23, 0x0f, 0xde, 0x20, 0x65, 0xd9, 0x52, 0xa5, 0xbc, 0x6b, - 0xdd, 0xcf, 0x7d, 0x19, 0xbd, 0x83, 0xfe, 0x40, 0x22, 0xa3, 0xaa, 0x91, 0x42, 0x51, 0x44, 0xe0, - 0x75, 0xe3, 0x46, 0x79, 0x60, 0x77, 0x65, 0x7c, 0xf3, 0xc0, 0x8f, 0xdd, 0x58, 0xc6, 0x6f, 0xec, - 0xfc, 0xc6, 0x8f, 0x25, 0x17, 0xe9, 0xbd, 0xd3, 0x1f, 0x3b, 0xa3, 0x4f, 0x3f, 0x77, 0xc6, 0x8c, - 0xeb, 0xea, 0x28, 0x8f, 0x0b, 0x59, 0xbb, 0x0c, 0xdc, 0xb1, 0xaf, 0xca, 0x37, 0xd8, 0x78, 0x51, - 0xdd, 0x03, 0x95, 0x59, 0xe5, 0xe8, 0x23, 0x80, 0xb7, 0x27, 0x8a, 0x3d, 0x6f, 0x4a, 0xa2, 0xe9, - 0xd3, 0x6e, 0x38, 0xf4, 0x00, 0xae, 0x93, 0x23, 0x5d, 0xc9, 0x96, 0xeb, 0x99, 0x1d, 0x23, 0xf5, - 0xbe, 0x7d, 0xd9, 0xdf, 0x72, 0xdd, 0x0f, 0xad, 0xf5, 0x67, 0xba, 0xe5, 0x82, 0x65, 0x17, 0x54, - 0xf4, 0x10, 0xae, 0xd9, 0x78, 0xba, 0x21, 0x8d, 0xdf, 0xc1, 0xf2, 0x62, 0xdb, 0x22, 0x5d, 0x35, - 0x7e, 0x33, 0x47, 0x7f, 0xb4, 0xf1, 0xfe, 0xf7, 0xe7, 0xbd, 0x0b, 0xa1, 0xc8, 0x87, 0xdb, 0x97, - 0x3c, 0xf5, 0x91, 0x1c, 0x7c, 0x05, 0x70, 0x65, 0xa2, 0x18, 0x2a, 0xe1, 0xc6, 0xa5, 0xdc, 0xef, - 0x5c, 0xd1, 0x6d, 0x10, 0x6d, 0x70, 0xf7, 0x7f, 0x58, 0xe7, 0x0b, 0x78, 0x05, 0x6f, 0xfd, 0x95, - 0x4c, 0x74, 0xf5, 0xeb, 0x65, 0x4e, 0xb0, 0xf7, 0x6f, 0x4e, 0xaf, 0x9f, 0x3e, 0x39, 0x9d, 0x87, - 0xe0, 0x6c, 0x1e, 0x82, 0x5f, 0xf3, 0x10, 0x7c, 0x58, 0x84, 0xa3, 0xb3, 0x45, 0x38, 0xfa, 0xbe, - 0x08, 0x47, 0x2f, 0x93, 0xa5, 0x45, 0x3a, 0xbd, 0xa2, 0x22, 0x5c, 0xf4, 0x05, 0x3e, 0x59, 0xfe, - 0x5a, 0xcc, 0x5e, 0xf3, 0xb5, 0xee, 0x4f, 0x79, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, - 0x67, 0xfa, 0x97, 0x4f, 0x03, 0x00, 0x00, + // 467 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x6b, 0xd4, 0x40, + 0x1c, 0xdd, 0xb1, 0x7f, 0xa4, 0x63, 0xa9, 0x74, 0x28, 0x34, 0x9b, 0x43, 0x5a, 0x82, 0x87, 0x65, + 0xb1, 0x19, 0xb7, 0x82, 0x42, 0x6f, 0xc6, 0xa3, 0x2c, 0x4a, 0x44, 0x04, 0x0f, 0xca, 0x24, 0x19, + 0x26, 0x83, 0x66, 0x26, 0x64, 0xa6, 0xdb, 0xee, 0x45, 0xc4, 0x4f, 0x20, 0x7e, 0x0c, 0x4f, 0x3d, + 0xf8, 0x21, 0x7a, 0x2c, 0x3d, 0x79, 0x52, 0xd9, 0x3d, 0xf4, 0x6b, 0xc8, 0x64, 0x26, 0xed, 0xda, + 0x14, 0xf4, 0x34, 0xf3, 0xdb, 0xf7, 0xe6, 0xfd, 0xde, 0xef, 0xfd, 0x36, 0xd0, 0x4f, 0x49, 0x3a, + 0xfd, 0x20, 0x05, 0xe6, 0x22, 0xa3, 0x42, 0xf3, 0x09, 0xc5, 0xfa, 0x38, 0xaa, 0x6a, 0xa9, 0x25, + 0xda, 0x74, 0x58, 0x74, 0x89, 0xf9, 0x5b, 0x4c, 0x32, 0xd9, 0xa0, 0xd8, 0xdc, 0x2c, 0xd1, 0xef, + 0x67, 0x52, 0x95, 0x52, 0xbd, 0xb3, 0x80, 0x2d, 0x1c, 0xb4, 0x6d, 0x2b, 0x5c, 0x2a, 0x86, 0x27, + 0x23, 0x73, 0x38, 0x20, 0x70, 0x40, 0x4a, 0x14, 0xc5, 0x93, 0x51, 0x4a, 0x35, 0x19, 0xe1, 0x4c, + 0x72, 0xd1, 0xe2, 0x5d, 0x63, 0x15, 0xa9, 0x49, 0xe9, 0x84, 0xc3, 0xe7, 0x70, 0x73, 0xac, 0xd8, + 0x6b, 0xae, 0x8b, 0xbc, 0x26, 0x47, 0x09, 0x3d, 0x22, 0x75, 0x8e, 0x10, 0x5c, 0xd6, 0xd3, 0x8a, + 0x7a, 0x60, 0x17, 0x0c, 0xd6, 0x92, 0xe6, 0x8e, 0x3c, 0x78, 0x9b, 0xe4, 0x79, 0x4d, 0x95, 0xf2, + 0x6e, 0x35, 0x3f, 0xb7, 0xe5, 0xc1, 0xfa, 0xe7, 0x8b, 0x93, 0x61, 0x5b, 0x85, 0x1f, 0x61, 0xbf, + 0x23, 0x98, 0x50, 0x55, 0x49, 0xa1, 0x28, 0x22, 0x70, 0xc5, 0x78, 0x53, 0x1e, 0xd8, 0x5d, 0x1a, + 0xdc, 0xd9, 0xef, 0x47, 0x6e, 0x48, 0xe3, 0x3e, 0x72, 0xee, 0xa3, 0xa7, 0x92, 0x8b, 0xf8, 0xc1, + 0xe9, 0xcf, 0x9d, 0xde, 0xb7, 0x5f, 0x3b, 0x03, 0xc6, 0x75, 0x71, 0x98, 0x46, 0x99, 0x2c, 0x5d, + 0x22, 0xee, 0xd8, 0x53, 0xf9, 0x7b, 0x6c, 0x9c, 0xa9, 0xe6, 0x81, 0x4a, 0xac, 0x72, 0xf8, 0x15, + 0xc0, 0xbb, 0x63, 0xc5, 0x5e, 0x55, 0x39, 0xd1, 0xf4, 0x45, 0x33, 0x2a, 0x7a, 0x04, 0xd7, 0xc8, + 0xa1, 0x2e, 0x64, 0xcd, 0xf5, 0xd4, 0x0e, 0x15, 0x7b, 0xe7, 0xdf, 0xf7, 0xb6, 0x5c, 0xf7, 0x27, + 0xd6, 0xfa, 0x4b, 0x5d, 0x73, 0xc1, 0x92, 0x2b, 0x2a, 0x7a, 0x0c, 0x57, 0x6d, 0x58, 0xcd, 0xc8, + 0xc6, 0x6f, 0x67, 0x95, 0x91, 0x6d, 0x11, 0x2f, 0x1b, 0xbf, 0x89, 0xa3, 0x1f, 0x6c, 0x98, 0x48, + 0xae, 0x84, 0xc2, 0x3e, 0xdc, 0xbe, 0xe6, 0xa9, 0x8d, 0x64, 0xff, 0x1c, 0xc0, 0xa5, 0xb1, 0x62, + 0x28, 0x87, 0x1b, 0xd7, 0xb6, 0x70, 0xef, 0x86, 0x6e, 0x9d, 0x68, 0xfd, 0xfb, 0xff, 0xc3, 0xba, + 0x5c, 0xc0, 0x5b, 0xb8, 0xfe, 0x57, 0x32, 0xe1, 0xcd, 0xaf, 0x17, 0x39, 0xfe, 0xf0, 0xdf, 0x9c, + 0x56, 0xdf, 0x5f, 0xf9, 0x74, 0x71, 0x32, 0x04, 0xf1, 0xb3, 0xd3, 0x59, 0x00, 0xce, 0x66, 0x01, + 0xf8, 0x3d, 0x0b, 0xc0, 0x97, 0x79, 0xd0, 0x3b, 0x9b, 0x07, 0xbd, 0x1f, 0xf3, 0xa0, 0xf7, 0x66, + 0xb4, 0xb0, 0x4f, 0x27, 0x9b, 0x15, 0x84, 0x8b, 0xb6, 0xc0, 0xc7, 0x8b, 0x9f, 0x90, 0x59, 0x6f, + 0xba, 0xda, 0xfc, 0x53, 0x1f, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc7, 0x73, 0x0d, 0x40, 0x64, + 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/monitor/genesis.go b/x/monitor/genesis.go index 3056ff875..b786d9685 100644 --- a/x/monitor/genesis.go +++ b/x/monitor/genesis.go @@ -1,18 +1,18 @@ package monitor import ( + "context" "github.com/babylonchain/babylon/x/monitor/keeper" "github.com/babylonchain/babylon/x/monitor/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the capability module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { } // ExportGenesis returns the capability module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() return genesis } diff --git a/x/monitor/genesis_test.go b/x/monitor/genesis_test.go index 11e9484a4..db95adb13 100644 --- a/x/monitor/genesis_test.go +++ b/x/monitor/genesis_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/babylonchain/babylon/x/monitor" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" simapp "github.com/babylonchain/babylon/app" @@ -13,7 +12,7 @@ import ( func TestExportGenesis(t *testing.T) { app := simapp.Setup(t, false) - ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + ctx := app.BaseApp.NewContext(false) genesisState := monitor.ExportGenesis(ctx, app.MonitorKeeper) require.Equal(t, genesisState, types.DefaultGenesis()) } diff --git a/x/monitor/keeper/grpc_query_test.go b/x/monitor/keeper/grpc_query_test.go index 61c8fbdbe..1c15f7897 100644 --- a/x/monitor/keeper/grpc_query_test.go +++ b/x/monitor/keeper/grpc_query_test.go @@ -4,11 +4,11 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/testutil/mocks" ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/babylonchain/babylon/x/epoching/testepoching" types2 "github.com/babylonchain/babylon/x/epoching/types" "github.com/babylonchain/babylon/x/monitor/types" "github.com/cosmos/cosmos-sdk/baseapp" @@ -20,20 +20,17 @@ func FuzzQueryEndedEpochBtcHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - // a genesis validator is generated for setup - helper := testepoching.NewHelper(t) - lck := helper.App.BTCLightClientKeeper - mk := helper.App.MonitorKeeper - ek := helper.EpochingKeeper - queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) + + babylonApp := app.Setup(t, false) + ctx := babylonApp.NewContext(false) + lck := babylonApp.BTCLightClientKeeper + mk := babylonApp.MonitorKeeper + + queryHelper := baseapp.NewQueryServerTestHelper(ctx, babylonApp.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, mk) queryClient := types.NewQueryClient(queryHelper) - // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - + // a genesis validator is generated for setup root := lck.GetBaseBTCHeader(ctx) chain := datagen.GenRandomValidChainStartingFrom( r, @@ -46,15 +43,8 @@ func FuzzQueryEndedEpochBtcHeight(f *testing.F) { err := lck.InsertHeaders(ctx, headerBytes) require.NoError(t, err) - // EndBlock of block 1 - ctx = helper.EndBlock() - // go to BeginBlock of block 11, and thus entering epoch 2 - for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx = helper.GenAndApplyEmptyBlock(r) - } - epoch = ek.GetEpoch(ctx) - require.Equal(t, uint64(2), epoch.EpochNumber) + mk.Hooks().AfterEpochEnds(ctx, 1) // query epoch 0 ended BTC light client height, should return base height req := types.QueryEndedEpochBtcHeightRequest{ @@ -78,24 +68,25 @@ func FuzzQueryReportedCheckpointBtcHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) + // a genesis validator is generated for setup - helper := testepoching.NewHelper(t) ctl := gomock.NewController(t) defer ctl.Finish() - lck := helper.App.BTCLightClientKeeper - mk := helper.App.MonitorKeeper - ek := helper.EpochingKeeper - ck := helper.App.CheckpointingKeeper + + babylonApp := app.Setup(t, false) + ctx := babylonApp.NewContext(false) + lck := babylonApp.BTCLightClientKeeper + mk := babylonApp.MonitorKeeper + ck := babylonApp.CheckpointingKeeper mockEk := mocks.NewMockEpochingKeeper(ctl) ck.SetEpochingKeeper(mockEk) - queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) + + queryHelper := baseapp.NewQueryServerTestHelper(ctx, babylonApp.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, mk) queryClient := types.NewQueryClient(queryHelper) // BeginBlock of block 1, and thus entering epoch 1 - ctx := helper.BeginBlock(r) - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) + mk.Hooks().AfterEpochEnds(ctx, 0) root := lck.GetBaseBTCHeader(ctx) chain := datagen.GenRandomValidChainStartingFrom( @@ -133,7 +124,7 @@ func FuzzQueryReportedCheckpointBtcHeight(f *testing.F) { // Verify checkpoint btcCkpt := btctxformatter.RawBtcCheckpoint{ Epoch: mockCkptWithMeta.Ckpt.EpochNum, - LastCommitHash: *mockCkptWithMeta.Ckpt.LastCommitHash, + AppHash: *mockCkptWithMeta.Ckpt.AppHash, BitMap: mockCkptWithMeta.Ckpt.Bitmap, SubmitterAddress: datagen.GenRandomByteArray(r, btctxformatter.AddressLength), BlsSig: *mockCkptWithMeta.Ckpt.BlsMultiSig, diff --git a/x/monitor/keeper/hooks.go b/x/monitor/keeper/hooks.go index 49a24e0e7..c343cd254 100644 --- a/x/monitor/keeper/hooks.go +++ b/x/monitor/keeper/hooks.go @@ -1,12 +1,13 @@ package keeper import ( + "context" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" etypes "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -// Helper interface to be sure Hooks implement both epoching and light client hooks +// HandledHooks Helper interface to be sure Hooks implement both epoching and light client hooks type HandledHooks interface { etypes.EpochingHooks checkpointingtypes.CheckpointingHooks @@ -16,32 +17,32 @@ type Hooks struct { k Keeper } -// Create new distribution hooks +// Hooks Create new distribution hooks func (k Keeper) Hooks() Hooks { return Hooks{k} } -func (h Hooks) AfterEpochBegins(ctx sdk.Context, epoch uint64) {} +func (h Hooks) AfterEpochBegins(ctx context.Context, epoch uint64) {} -func (h Hooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) { +func (h Hooks) AfterEpochEnds(ctx context.Context, epoch uint64) { h.k.updateBtcLightClientHeightForEpoch(ctx, epoch) } -func (h Hooks) BeforeSlashThreshold(ctx sdk.Context, valSet etypes.ValidatorSet) {} +func (h Hooks) BeforeSlashThreshold(ctx context.Context, valSet etypes.ValidatorSet) {} -func (h Hooks) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error { +func (h Hooks) AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error { +func (h Hooks) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { return nil } -func (h Hooks) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +func (h Hooks) AfterRawCheckpointForgotten(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return h.k.removeCheckpointRecord(ctx, ckpt) } -func (h Hooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error { +func (h Hooks) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { return nil } -func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return h.k.updateBtcLightClientHeightForCheckpoint(ctx, ckpt) } diff --git a/x/monitor/keeper/keeper.go b/x/monitor/keeper/keeper.go index d891d6f54..9bd40ab8b 100644 --- a/x/monitor/keeper/keeper.go +++ b/x/monitor/keeper/keeper.go @@ -1,36 +1,34 @@ package keeper import ( + "context" + corestoretypes "cosmossdk.io/core/store" "fmt" ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" + "cosmossdk.io/log" "github.com/babylonchain/babylon/x/monitor/types" - "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) type ( Keeper struct { cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + storeService corestoretypes.KVStoreService btcLightClientKeeper types.BTCLightClientKeeper } ) func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, bk types.BTCLightClientKeeper, ) Keeper { return Keeper{ cdc: cdc, - storeKey: storeKey, - memKey: memKey, + storeService: storeService, btcLightClientKeeper: bk, } } @@ -47,14 +45,16 @@ func bytesToUint64(bytes []byte) (uint64, error) { return sdk.BigEndianToUint64(bytes), nil } -func (k Keeper) updateBtcLightClientHeightForEpoch(ctx sdk.Context, epoch uint64) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) updateBtcLightClientHeightForEpoch(ctx context.Context, epoch uint64) { + store := k.storeService.OpenKVStore(ctx) currentTipHeight := k.btcLightClientKeeper.GetTipInfo(ctx).Height - store.Set(types.GetEpochEndLightClientHeightKey(epoch), sdk.Uint64ToBigEndian(currentTipHeight)) + if err := store.Set(types.GetEpochEndLightClientHeightKey(epoch), sdk.Uint64ToBigEndian(currentTipHeight)); err != nil { + panic(err) + } } -func (k Keeper) updateBtcLightClientHeightForCheckpoint(ctx sdk.Context, ckpt *ckpttypes.RawCheckpoint) error { - store := ctx.KVStore(k.storeKey) +func (k Keeper) updateBtcLightClientHeightForCheckpoint(ctx context.Context, ckpt *ckpttypes.RawCheckpoint) error { + store := k.storeService.OpenKVStore(ctx) ckptHashStr := ckpt.HashStr() storeKey, err := types.GetCheckpointReportedLightClientHeightKey(ckptHashStr) @@ -64,19 +64,21 @@ func (k Keeper) updateBtcLightClientHeightForCheckpoint(ctx sdk.Context, ckpt *c // if the checkpoint exists, meaning an earlier checkpoint with a lower BTC height is already recorded // we should keep the lower BTC height in the store - if store.Has(storeKey) { - k.Logger(ctx).With("module", fmt.Sprintf("checkpoint %s is already recorded", ckptHashStr)) + has, err := store.Has(storeKey) + if err != nil { + panic(err) + } + if has { + k.Logger(sdk.UnwrapSDKContext(ctx)).With("module", fmt.Sprintf("checkpoint %s is already recorded", ckptHashStr)) return nil } currentTipHeight := k.btcLightClientKeeper.GetTipInfo(ctx).Height - store.Set(storeKey, sdk.Uint64ToBigEndian(currentTipHeight)) - - return nil + return store.Set(storeKey, sdk.Uint64ToBigEndian(currentTipHeight)) } -func (k Keeper) removeCheckpointRecord(ctx sdk.Context, ckpt *ckpttypes.RawCheckpoint) error { - store := ctx.KVStore(k.storeKey) +func (k Keeper) removeCheckpointRecord(ctx context.Context, ckpt *ckpttypes.RawCheckpoint) error { + store := k.storeService.OpenKVStore(ctx) ckptHashStr := ckpt.HashStr() storeKey, err := types.GetCheckpointReportedLightClientHeightKey(ckptHashStr) @@ -84,19 +86,23 @@ func (k Keeper) removeCheckpointRecord(ctx sdk.Context, ckpt *ckpttypes.RawCheck return err } - store.Delete(storeKey) - + if err := store.Delete(storeKey); err != nil { + panic(err) + } return nil } -func (k Keeper) LightclientHeightAtEpochEnd(ctx sdk.Context, epoch uint64) (uint64, error) { +func (k Keeper) LightclientHeightAtEpochEnd(ctx context.Context, epoch uint64) (uint64, error) { if epoch == 0 { return k.btcLightClientKeeper.GetBaseBTCHeader(ctx).Height, nil } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) - btcHeightBytes := store.Get(types.GetEpochEndLightClientHeightKey(epoch)) + btcHeightBytes, err := store.Get(types.GetEpochEndLightClientHeightKey(epoch)) + if err != nil { + panic(err) + } // nil would be returned if key does not exist if btcHeightBytes == nil { // we do not have any key under given epoch, most probably epoch did not finish @@ -113,15 +119,18 @@ func (k Keeper) LightclientHeightAtEpochEnd(ctx sdk.Context, epoch uint64) (uint return btcHeight, nil } -func (k Keeper) LightclientHeightAtCheckpointReported(ctx sdk.Context, hashString string) (uint64, error) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) LightclientHeightAtCheckpointReported(ctx context.Context, hashString string) (uint64, error) { + store := k.storeService.OpenKVStore(ctx) storeKey, err := types.GetCheckpointReportedLightClientHeightKey(hashString) if err != nil { return 0, err } - btcHeightBytes := store.Get(storeKey) + btcHeightBytes, err := store.Get(storeKey) + if err != nil { + panic(err) + } // nil would be returned if key does not exist if btcHeightBytes == nil { return 0, types.ErrCheckpointNotReported.Wrapf("checkpoint hash: %s", hashString) diff --git a/x/monitor/module.go b/x/monitor/module.go index c1badb11c..b4674ff00 100644 --- a/x/monitor/module.go +++ b/x/monitor/module.go @@ -2,6 +2,7 @@ package monitor import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -22,8 +23,10 @@ import ( ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // ---------------------------------------------------------------------------- @@ -99,23 +102,16 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper - // TODO: add dependencies to staking, slashing and evidence + keeper keeper.Keeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, } } @@ -138,14 +134,12 @@ func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the capability module's genesis initialization It returns // no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. @@ -158,10 +152,20 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +func (am AppModule) BeginBlock(_ context.Context) error { + return nil +} // EndBlock executes all ABCI EndBlock logic respective to the capability module. It // returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(_ context.Context) ([]abci.ValidatorUpdate, error) { + return []abci.ValidatorUpdate{}, nil +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker } diff --git a/x/monitor/module_simulation.go b/x/monitor/module_simulation.go index 3c06916db..3db534441 100644 --- a/x/monitor/module_simulation.go +++ b/x/monitor/module_simulation.go @@ -1,11 +1,9 @@ package monitor import ( - simappparams "github.com/babylonchain/babylon/app/params" monitorsimulation "github.com/babylonchain/babylon/x/monitor/simulation" "github.com/babylonchain/babylon/x/monitor/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/simulation" @@ -14,7 +12,6 @@ import ( // avoid unused import issue var ( _ = monitorsimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -35,7 +32,7 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { diff --git a/x/monitor/types/errors.go b/x/monitor/types/errors.go index 85139bba8..d4a710f00 100644 --- a/x/monitor/types/errors.go +++ b/x/monitor/types/errors.go @@ -1,7 +1,5 @@ package types -// DONTCOVER - import ( errorsmod "cosmossdk.io/errors" ) diff --git a/x/monitor/types/expected_keepers.go b/x/monitor/types/expected_keepers.go index 6f3566003..0150c8dc2 100644 --- a/x/monitor/types/expected_keepers.go +++ b/x/monitor/types/expected_keepers.go @@ -1,24 +1,11 @@ package types import ( + "context" lc "github.com/babylonchain/babylon/x/btclightclient/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// AccountKeeper defines the expected account keeper used for simulations (noalias) -type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface needed to retrieve account balances. -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // Methods imported from bank should be defined here -} - type BTCLightClientKeeper interface { - GetTipInfo(ctx sdk.Context) *lc.BTCHeaderInfo - GetBaseBTCHeader(ctx sdk.Context) *lc.BTCHeaderInfo + GetTipInfo(ctx context.Context) *lc.BTCHeaderInfo + GetBaseBTCHeader(ctx context.Context) *lc.BTCHeaderInfo } diff --git a/x/monitor/types/genesis.go b/x/monitor/types/genesis.go index 094d1119c..5c5ec3cc2 100644 --- a/x/monitor/types/genesis.go +++ b/x/monitor/types/genesis.go @@ -1,8 +1,5 @@ package types -// DefaultIndex is the default capability global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() *GenesisState { return &GenesisState{} diff --git a/x/zoneconcierge/abci.go b/x/zoneconcierge/abci.go index 365c01ce7..7ea1514bb 100644 --- a/x/zoneconcierge/abci.go +++ b/x/zoneconcierge/abci.go @@ -1,23 +1,23 @@ package zoneconcierge import ( + "context" "time" "github.com/babylonchain/babylon/x/zoneconcierge/keeper" "github.com/babylonchain/babylon/x/zoneconcierge/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" ) // BeginBlocker sends a pending packet for every channel upon each new block, // so that the relayer is kept awake to relay headers -func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { +func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - + return nil } -func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { +func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } diff --git a/x/zoneconcierge/client/cli/tx.go b/x/zoneconcierge/client/cli/tx.go index 1e0d535f4..9ba77b787 100644 --- a/x/zoneconcierge/client/cli/tx.go +++ b/x/zoneconcierge/client/cli/tx.go @@ -2,8 +2,6 @@ package cli import ( "fmt" - "time" - "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" @@ -11,16 +9,6 @@ import ( "github.com/babylonchain/babylon/x/zoneconcierge/types" ) -var ( - DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) -) - -//nolint:unused -const ( - flagPacketTimeoutTimestamp = "packet-timeout-timestamp" - listSeparator = "," -) - // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { cmd := &cobra.Command{ diff --git a/x/zoneconcierge/genesis.go b/x/zoneconcierge/genesis.go index eb5ec4424..6cdc963bf 100644 --- a/x/zoneconcierge/genesis.go +++ b/x/zoneconcierge/genesis.go @@ -1,13 +1,15 @@ package zoneconcierge import ( + "context" "github.com/babylonchain/babylon/x/zoneconcierge/keeper" "github.com/babylonchain/babylon/x/zoneconcierge/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisState) { + sdkCtx := sdk.UnwrapSDKContext(ctx) // set params for this module if err := k.SetParams(ctx, genState.Params); err != nil { panic(err) @@ -16,10 +18,10 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) k.SetPort(ctx, genState.PortId) // Only try to bind to port if it is not already bound, since we may already own // port capability from capability InitGenesis - if !k.IsBound(ctx, genState.PortId) { + if !k.IsBound(sdkCtx, genState.PortId) { // module binds to the port on InitChain // and claims the returned capability - err := k.BindPort(ctx, genState.PortId) + err := k.BindPort(sdkCtx, genState.PortId) if err != nil { panic("could not claim port capability: " + err.Error()) } @@ -27,7 +29,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } // ExportGenesis returns the module's exported genesis -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() genesis.Params = k.GetParams(ctx) genesis.PortId = k.GetPort(ctx) diff --git a/x/zoneconcierge/keeper/canonical_chain_indexer.go b/x/zoneconcierge/keeper/canonical_chain_indexer.go index 860e53008..2348ba387 100644 --- a/x/zoneconcierge/keeper/canonical_chain_indexer.go +++ b/x/zoneconcierge/keeper/canonical_chain_indexer.go @@ -1,16 +1,18 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" sdkerrors "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" "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 -func (k Keeper) FindClosestHeader(ctx sdk.Context, chainID string, height uint64) (*types.IndexedHeader, error) { +func (k Keeper) FindClosestHeader(ctx context.Context, chainID string, height uint64) (*types.IndexedHeader, error) { chainInfo, err := k.GetChainInfo(ctx, chainID) if err != nil { return nil, fmt.Errorf("failed to get chain info for chain with ID %s: %w", chainID, err) @@ -37,7 +39,7 @@ func (k Keeper) FindClosestHeader(ctx sdk.Context, chainID string, height uint64 return &header, nil } -func (k Keeper) GetHeader(ctx sdk.Context, chainID string, height uint64) (*types.IndexedHeader, error) { +func (k Keeper) GetHeader(ctx context.Context, chainID string, height uint64) (*types.IndexedHeader, error) { store := k.canonicalChainStore(ctx, chainID) heightBytes := sdk.Uint64ToBigEndian(height) if !store.Has(heightBytes) { @@ -49,7 +51,7 @@ func (k Keeper) GetHeader(ctx sdk.Context, chainID string, height uint64) (*type return &header, nil } -func (k Keeper) insertHeader(ctx sdk.Context, chainID string, header *types.IndexedHeader) error { +func (k Keeper) insertHeader(ctx context.Context, chainID string, header *types.IndexedHeader) error { if header == nil { return sdkerrors.Wrapf(types.ErrInvalidHeader, "header is nil") } @@ -63,9 +65,9 @@ func (k Keeper) insertHeader(ctx sdk.Context, chainID string, header *types.Inde // prefix: CanonicalChainKey || chainID // key: height // value: IndexedHeader -func (k Keeper) canonicalChainStore(ctx sdk.Context, chainID string) prefix.Store { - store := ctx.KVStore(k.storeKey) - canonicalChainStore := prefix.NewStore(store, types.CanonicalChainKey) +func (k Keeper) canonicalChainStore(ctx context.Context, chainID string) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + canonicalChainStore := prefix.NewStore(storeAdapter, types.CanonicalChainKey) chainIDBytes := []byte(chainID) return prefix.NewStore(canonicalChainStore, chainIDBytes) } diff --git a/x/zoneconcierge/keeper/canonical_chain_indexer_test.go b/x/zoneconcierge/keeper/canonical_chain_indexer_test.go index e0f00c845..1b1f929ff 100644 --- a/x/zoneconcierge/keeper/canonical_chain_indexer_test.go +++ b/x/zoneconcierge/keeper/canonical_chain_indexer_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" "github.com/stretchr/testify/require" ) @@ -14,32 +15,32 @@ func FuzzCanonicalChainIndexer(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" // simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 - headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders) + headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChainID, 0, numHeaders) // check if the canonical chain index is correct or not for i := uint64(0); i < numHeaders; i++ { - header, err := zcKeeper.GetHeader(ctx, czChain.ChainID, i) + header, err := zcKeeper.GetHeader(ctx, czChainID, i) require.NoError(t, err) require.NotNil(t, header) - require.Equal(t, czChain.ChainID, header.ChainId) + require.Equal(t, czChainID, header.ChainId) require.Equal(t, i, header.Height) - require.Equal(t, headers[i].Header.LastCommitHash, header.Hash) + require.Equal(t, headers[i].Header.AppHash, header.Hash) } // check if the chain info is updated or not - chainInfo, err := zcKeeper.GetChainInfo(ctx, czChain.ChainID) + chainInfo, err := zcKeeper.GetChainInfo(ctx, czChainID) require.NoError(t, err) require.NotNil(t, chainInfo.LatestHeader) - require.Equal(t, czChain.ChainID, chainInfo.LatestHeader.ChainId) + require.Equal(t, czChainID, chainInfo.LatestHeader.ChainId) require.Equal(t, numHeaders-1, chainInfo.LatestHeader.Height) - require.Equal(t, headers[numHeaders-1].Header.LastCommitHash, chainInfo.LatestHeader.Hash) + require.Equal(t, headers[numHeaders-1].Header.AppHash, chainInfo.LatestHeader.Hash) }) } @@ -49,36 +50,36 @@ func FuzzFindClosestHeader(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" // no header at the moment, FindClosestHeader invocation should give error - _, err := zcKeeper.FindClosestHeader(ctx, czChain.ChainID, 100) + _, err := zcKeeper.FindClosestHeader(ctx, czChainID, 100) require.Error(t, err) // simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 - headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders) + headers := SimulateNewHeaders(ctx, r, &zcKeeper, czChainID, 0, numHeaders) - header, err := zcKeeper.FindClosestHeader(ctx, czChain.ChainID, numHeaders) + header, err := zcKeeper.FindClosestHeader(ctx, czChainID, numHeaders) require.NoError(t, err) - require.Equal(t, headers[len(headers)-1].Header.LastCommitHash, header.Hash) + require.Equal(t, headers[len(headers)-1].Header.AppHash, header.Hash) // skip a non-zero number of headers in between, in order to create a gap of non-timestamped headers gap := datagen.RandomInt(r, 10) + 1 // simulate a random number of blocks // where the new batch of headers has a gap with the previous batch - SimulateNewHeaders(ctx, r, &zcKeeper, czChain.ChainID, numHeaders+gap+1, numHeaders) + SimulateNewHeaders(ctx, r, &zcKeeper, czChainID, numHeaders+gap+1, numHeaders) // get a random height that is in this gap randomHeightInGap := datagen.RandomInt(r, int(gap+1)) + numHeaders // find the closest header with the given randomHeightInGap - header, err = zcKeeper.FindClosestHeader(ctx, czChain.ChainID, randomHeightInGap) + header, err = zcKeeper.FindClosestHeader(ctx, czChainID, randomHeightInGap) require.NoError(t, err) // the header should be the same as the last header in the last batch - require.Equal(t, headers[len(headers)-1].Header.LastCommitHash, header.Hash) + require.Equal(t, headers[len(headers)-1].Header.AppHash, header.Hash) }) } diff --git a/x/zoneconcierge/keeper/chain_info_indexer.go b/x/zoneconcierge/keeper/chain_info_indexer.go index cd71c2904..e7e7d06bd 100644 --- a/x/zoneconcierge/keeper/chain_info_indexer.go +++ b/x/zoneconcierge/keeper/chain_info_indexer.go @@ -1,21 +1,21 @@ package keeper import ( + "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" - + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/zoneconcierge/types" ) -func (k Keeper) setChainInfo(ctx sdk.Context, chainInfo *types.ChainInfo) { +func (k Keeper) setChainInfo(ctx context.Context, chainInfo *types.ChainInfo) { store := k.chainInfoStore(ctx) store.Set([]byte(chainInfo.ChainId), k.cdc.MustMarshal(chainInfo)) } -func (k Keeper) InitChainInfo(ctx sdk.Context, chainID string) (*types.ChainInfo, error) { +func (k Keeper) InitChainInfo(ctx context.Context, chainID string) (*types.ChainInfo, error) { if len(chainID) == 0 { return nil, fmt.Errorf("chainID is empty") } @@ -40,7 +40,7 @@ func (k Keeper) InitChainInfo(ctx sdk.Context, chainID string) (*types.ChainInfo // HasChainInfo returns whether the chain info exists for a given ID // Since IBC does not provide API that allows to initialise chain info right before creating an IBC connection, // we can only check its existence every time, and return an empty one if it's not initialised yet. -func (k Keeper) HasChainInfo(ctx sdk.Context, chainID string) bool { +func (k Keeper) HasChainInfo(ctx context.Context, chainID string) bool { store := k.chainInfoStore(ctx) return store.Has([]byte(chainID)) } @@ -48,7 +48,7 @@ func (k Keeper) HasChainInfo(ctx sdk.Context, chainID string) bool { // GetChainInfo returns the ChainInfo struct for a chain with a given ID // Since IBC does not provide API that allows to initialise chain info right before creating an IBC connection, // we can only check its existence every time, and return an empty one if it's not initialised yet. -func (k Keeper) GetChainInfo(ctx sdk.Context, chainID string) (*types.ChainInfo, error) { +func (k Keeper) GetChainInfo(ctx context.Context, chainID string) (*types.ChainInfo, error) { if !k.HasChainInfo(ctx, chainID) { return nil, types.ErrChainInfoNotFound } @@ -66,7 +66,7 @@ func (k Keeper) GetChainInfo(ctx sdk.Context, chainID string) (*types.ChainInfo, // Note that this function is triggered only upon receiving headers from the relayer, // and only a subset of headers in CZ are relayed. Thus TimestampedHeadersCount is not // equal to the total number of headers in CZ. -func (k Keeper) updateLatestHeader(ctx sdk.Context, chainID string, header *types.IndexedHeader) error { +func (k Keeper) updateLatestHeader(ctx context.Context, chainID string, header *types.IndexedHeader) error { if header == nil { return errorsmod.Wrapf(types.ErrInvalidHeader, "header is nil") } @@ -87,7 +87,7 @@ func (k Keeper) updateLatestHeader(ctx sdk.Context, chainID string, header *type // - If there is a fork header at the same height, add this fork to the set of latest fork headers // - If this fork header is newer than the previous one, replace the old fork headers with this fork header // - If this fork header is older than the current latest fork, ignore -func (k Keeper) tryToUpdateLatestForkHeader(ctx sdk.Context, chainID string, header *types.IndexedHeader) error { +func (k Keeper) tryToUpdateLatestForkHeader(ctx context.Context, chainID string, header *types.IndexedHeader) error { if header == nil { return errorsmod.Wrapf(types.ErrInvalidHeader, "header is nil") } @@ -118,7 +118,7 @@ func (k Keeper) tryToUpdateLatestForkHeader(ctx sdk.Context, chainID string, hea } // GetAllChainIDs gets all chain IDs that integrate Babylon -func (k Keeper) GetAllChainIDs(ctx sdk.Context) []string { +func (k Keeper) GetAllChainIDs(ctx context.Context) []string { chainIDs := []string{} iter := k.chainInfoStore(ctx).Iterator(nil, nil) defer iter.Close() @@ -135,7 +135,7 @@ func (k Keeper) GetAllChainIDs(ctx sdk.Context) []string { // prefix: ChainInfoKey // key: chainID // value: ChainInfo -func (k Keeper) chainInfoStore(ctx sdk.Context) prefix.Store { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.ChainInfoKey) +func (k Keeper) chainInfoStore(ctx context.Context) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(storeAdapter, types.ChainInfoKey) } diff --git a/x/zoneconcierge/keeper/epoch_chain_info_indexer.go b/x/zoneconcierge/keeper/epoch_chain_info_indexer.go index 286c36c1e..b5d1e9496 100644 --- a/x/zoneconcierge/keeper/epoch_chain_info_indexer.go +++ b/x/zoneconcierge/keeper/epoch_chain_info_indexer.go @@ -1,7 +1,9 @@ package keeper import ( - "github.com/cosmos/cosmos-sdk/store/prefix" + "context" + "cosmossdk.io/store/prefix" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" bbn "github.com/babylonchain/babylon/types" @@ -9,7 +11,7 @@ import ( ) // GetEpochChainInfo gets the latest chain info of a given epoch for a given chain ID -func (k Keeper) GetEpochChainInfo(ctx sdk.Context, chainID string, epochNumber uint64) (*types.ChainInfo, error) { +func (k Keeper) GetEpochChainInfo(ctx context.Context, chainID string, epochNumber uint64) (*types.ChainInfo, error) { if !k.EpochChainInfoExists(ctx, chainID, epochNumber) { return nil, types.ErrEpochChainInfoNotFound } @@ -23,14 +25,14 @@ func (k Keeper) GetEpochChainInfo(ctx sdk.Context, chainID string, epochNumber u } // EpochChainInfoExists checks if the latest chain info exists of a given epoch for a given chain ID -func (k Keeper) EpochChainInfoExists(ctx sdk.Context, chainID string, epochNumber uint64) bool { +func (k Keeper) EpochChainInfoExists(ctx context.Context, chainID string, epochNumber uint64) bool { store := k.epochChainInfoStore(ctx, chainID) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) return store.Has(epochNumberBytes) } // GetEpochHeaders gets the headers timestamped in a given epoch, in the ascending order -func (k Keeper) GetEpochHeaders(ctx sdk.Context, chainID string, epochNumber uint64) ([]*types.IndexedHeader, error) { +func (k Keeper) GetEpochHeaders(ctx context.Context, chainID string, epochNumber uint64) ([]*types.IndexedHeader, error) { headers := []*types.IndexedHeader{} // find the last timestamped header of this chain in the epoch @@ -70,11 +72,11 @@ func (k Keeper) GetEpochHeaders(ctx sdk.Context, chainID string, epochNumber uin // recordEpochChainInfo records the chain info for a given epoch number of given chain ID // where the latest chain info is retrieved from the chain info indexer -func (k Keeper) recordEpochChainInfo(ctx sdk.Context, chainID string, epochNumber uint64) { +func (k Keeper) recordEpochChainInfo(ctx context.Context, chainID string, epochNumber uint64) { // get the latest known chain info chainInfo, err := k.GetChainInfo(ctx, chainID) if err != nil { - k.Logger(ctx).Debug("chain info does not exist yet, nothing to record") + k.Logger(sdk.UnwrapSDKContext(ctx)).Debug("chain info does not exist yet, nothing to record") return } // NOTE: we can record epoch chain info without ancestor since IBC connection can be established at any height @@ -86,9 +88,9 @@ func (k Keeper) recordEpochChainInfo(ctx sdk.Context, chainID string, epochNumbe // prefix: EpochChainInfoKey || chainID // key: epochNumber // value: ChainInfo -func (k Keeper) epochChainInfoStore(ctx sdk.Context, chainID string) prefix.Store { - store := ctx.KVStore(k.storeKey) - epochChainInfoStore := prefix.NewStore(store, types.EpochChainInfoKey) +func (k Keeper) epochChainInfoStore(ctx context.Context, chainID string) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + epochChainInfoStore := prefix.NewStore(storeAdapter, types.EpochChainInfoKey) chainIDBytes := []byte(chainID) return prefix.NewStore(epochChainInfoStore, chainIDBytes) } diff --git a/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go b/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go index cbe7fc35e..c53d2a41d 100644 --- a/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go +++ b/x/zoneconcierge/keeper/epoch_chain_info_indexer_test.go @@ -4,8 +4,9 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" "github.com/stretchr/testify/require" ) @@ -15,29 +16,29 @@ func FuzzEpochChainInfoIndexer(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - epochingKeeper := babylonApp.EpochingKeeper + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" - ctx := babylonChain.GetContext() hooks := zcKeeper.Hooks() // enter a random epoch epochNum := datagen.RandomInt(r, 10) for j := uint64(0); j < epochNum; j++ { - epochingKeeper.IncEpoch(ctx) + babylonApp.EpochingKeeper.IncEpoch(ctx) } // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) + SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, 0, numHeaders, numForkHeaders) // end this epoch hooks.AfterEpochEnds(ctx, epochNum) // check if the chain info of this epoch is recorded or not - chainInfo, err := zcKeeper.GetEpochChainInfo(ctx, czChain.ChainID, epochNum) + chainInfo, err := zcKeeper.GetEpochChainInfo(ctx, czChainID, epochNum) require.NoError(t, err) require.Equal(t, numHeaders-1, chainInfo.LatestHeader.Height) require.Equal(t, numHeaders, chainInfo.TimestampedHeadersCount) @@ -51,15 +52,14 @@ func FuzzGetEpochHeaders(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - epochingKeeper := babylonApp.EpochingKeeper + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" - ctx := babylonChain.GetContext() hooks := zcKeeper.Hooks() numReqs := datagen.RandomInt(r, 5) + 1 - epochNumList := []uint64{datagen.RandomInt(r, 10) + 1} nextHeightList := []uint64{0} numHeadersList := []uint64{} @@ -72,12 +72,12 @@ func FuzzGetEpochHeaders(f *testing.F) { epochNum := epochNumList[i] // enter a random epoch if i == 0 { - for j := uint64(0); j < epochNum; j++ { - epochingKeeper.IncEpoch(ctx) + for j := uint64(1); j < epochNum; j++ { // starting from epoch 1 + babylonApp.EpochingKeeper.IncEpoch(ctx) } } else { for j := uint64(0); j < epochNum-epochNumList[i-1]; j++ { - epochingKeeper.IncEpoch(ctx) + babylonApp.EpochingKeeper.IncEpoch(ctx) } } @@ -85,7 +85,7 @@ func FuzzGetEpochHeaders(f *testing.F) { numHeadersList = append(numHeadersList, datagen.RandomInt(r, 100)+1) numForkHeadersList = append(numForkHeadersList, datagen.RandomInt(r, 10)+1) // trigger hooks to append these headers and fork headers - expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) + expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) expectedHeadersMap[epochNum] = expectedHeaders // prepare nextHeight for the next request nextHeightList = append(nextHeightList, nextHeightList[i]+numHeadersList[i]) @@ -100,11 +100,11 @@ func FuzzGetEpochHeaders(f *testing.F) { for i := uint64(0); i < numReqs; i++ { epochNum := epochNumList[i] // check if the headers are same as expected - headers, err := zcKeeper.GetEpochHeaders(ctx, czChain.ChainID, epochNum) + headers, err := zcKeeper.GetEpochHeaders(ctx, czChainID, epochNum) require.NoError(t, err) require.Equal(t, len(expectedHeadersMap[epochNum]), len(headers)) for j := 0; j < len(expectedHeadersMap[epochNum]); j++ { - require.Equal(t, expectedHeadersMap[epochNum][j].Header.LastCommitHash, headers[j].Hash) + require.Equal(t, expectedHeadersMap[epochNum][j].Header.AppHash, headers[j].Hash) } } }) diff --git a/x/zoneconcierge/keeper/epochs.go b/x/zoneconcierge/keeper/epochs.go index 679ecfa36..a14c4b3ac 100644 --- a/x/zoneconcierge/keeper/epochs.go +++ b/x/zoneconcierge/keeper/epochs.go @@ -1,18 +1,26 @@ package keeper import ( + "context" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" "github.com/babylonchain/babylon/x/zoneconcierge/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // GetLastSentSegment get last broadcasted btc light client segment -func (k Keeper) GetLastSentSegment(ctx sdk.Context) *types.BTCChainSegment { - store := ctx.KVStore(k.storeKey) - if !store.Has(types.FinalizingBTCTipKey) { +func (k Keeper) GetLastSentSegment(ctx context.Context) *types.BTCChainSegment { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(types.FinalizingBTCTipKey) + if err != nil { + panic(err) + } + if !has { return nil } - segmentBytes := store.Get(types.FinalizingBTCTipKey) + segmentBytes, err := store.Get(types.FinalizingBTCTipKey) + if err != nil { + panic(err) + } var segment types.BTCChainSegment k.cdc.MustUnmarshal(segmentBytes, &segment) return &segment @@ -20,31 +28,42 @@ func (k Keeper) GetLastSentSegment(ctx sdk.Context) *types.BTCChainSegment { // setLastSentSegment sets the last segment which was broadcasted to the other light clients // called upon each AfterRawCheckpointFinalized hook invocation -func (k Keeper) setLastSentSegment(ctx sdk.Context, segment *types.BTCChainSegment) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) setLastSentSegment(ctx context.Context, segment *types.BTCChainSegment) { + store := k.storeService.OpenKVStore(ctx) segmentBytes := k.cdc.MustMarshal(segment) - store.Set(types.FinalizingBTCTipKey, segmentBytes) + if err := store.Set(types.FinalizingBTCTipKey, segmentBytes); err != nil { + panic(err) + } } // GetFinalizedEpoch gets the last finalised epoch // used upon querying the last BTC-finalised chain info for CZs -func (k Keeper) GetFinalizedEpoch(ctx sdk.Context) (uint64, error) { - store := ctx.KVStore(k.storeKey) - if !store.Has(types.FinalizedEpochKey) { +func (k Keeper) GetFinalizedEpoch(ctx context.Context) (uint64, error) { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(types.FinalizedEpochKey) + if err != nil { + panic(err) + } + if !has { return 0, types.ErrFinalizedEpochNotFound } - epochNumberBytes := store.Get(types.FinalizedEpochKey) + epochNumberBytes, err := store.Get(types.FinalizedEpochKey) + if err != nil { + panic(err) + } return sdk.BigEndianToUint64(epochNumberBytes), nil } // setFinalizedEpoch sets the last finalised epoch // called upon each AfterRawCheckpointFinalized hook invocation -func (k Keeper) setFinalizedEpoch(ctx sdk.Context, epochNumber uint64) { - store := ctx.KVStore(k.storeKey) +func (k Keeper) setFinalizedEpoch(ctx context.Context, epochNumber uint64) { + store := k.storeService.OpenKVStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) - store.Set(types.FinalizedEpochKey, epochNumberBytes) + if err := store.Set(types.FinalizedEpochKey, epochNumberBytes); err != nil { + panic(err) + } } -func (k Keeper) GetEpoch(ctx sdk.Context) *epochingtypes.Epoch { +func (k Keeper) GetEpoch(ctx context.Context) *epochingtypes.Epoch { return k.epochingKeeper.GetEpoch(ctx) } diff --git a/x/zoneconcierge/keeper/fork_indexer.go b/x/zoneconcierge/keeper/fork_indexer.go index c2ab37c9b..2ec7e6ba3 100644 --- a/x/zoneconcierge/keeper/fork_indexer.go +++ b/x/zoneconcierge/keeper/fork_indexer.go @@ -2,15 +2,17 @@ package keeper import ( "bytes" + "context" + "github.com/cosmos/cosmos-sdk/runtime" sdkerrors "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/zoneconcierge/types" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ) // GetForks returns a list of forked headers at a given height -func (k Keeper) GetForks(ctx sdk.Context, chainID string, height uint64) *types.Forks { +func (k Keeper) GetForks(ctx context.Context, chainID string, height uint64) *types.Forks { store := k.forkStore(ctx, chainID) heightBytes := sdk.Uint64ToBigEndian(height) // if no fork at the moment, create an empty struct @@ -26,7 +28,7 @@ func (k Keeper) GetForks(ctx sdk.Context, chainID string, height uint64) *types. } // insertForkHeader inserts a forked header to the list of forked headers at the same height -func (k Keeper) insertForkHeader(ctx sdk.Context, chainID string, header *types.IndexedHeader) error { +func (k Keeper) insertForkHeader(ctx context.Context, chainID string, header *types.IndexedHeader) error { if header == nil { return sdkerrors.Wrapf(types.ErrInvalidHeader, "header is nil") } @@ -48,9 +50,9 @@ func (k Keeper) insertForkHeader(ctx sdk.Context, chainID string, header *types. // prefix: ForkKey || chainID // key: height that this fork starts from // value: a list of IndexedHeader, representing each header in the fork -func (k Keeper) forkStore(ctx sdk.Context, chainID string) prefix.Store { - store := ctx.KVStore(k.storeKey) - forkStore := prefix.NewStore(store, types.ForkKey) +func (k Keeper) forkStore(ctx context.Context, chainID string) prefix.Store { + storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + forkStore := prefix.NewStore(storeAdapter, types.ForkKey) chainIDBytes := []byte(chainID) return prefix.NewStore(forkStore, chainIDBytes) } diff --git a/x/zoneconcierge/keeper/fork_indexer_test.go b/x/zoneconcierge/keeper/fork_indexer_test.go index a5175b17e..8195f6b01 100644 --- a/x/zoneconcierge/keeper/fork_indexer_test.go +++ b/x/zoneconcierge/keeper/fork_indexer_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" "github.com/stretchr/testify/require" ) @@ -14,33 +15,33 @@ func FuzzForkIndexer(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - _, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) + _, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, 0, numHeaders, numForkHeaders) // check if the fork is updated or not - forks := zcKeeper.GetForks(ctx, czChain.ChainID, numHeaders-1) + forks := zcKeeper.GetForks(ctx, czChainID, numHeaders-1) require.Equal(t, numForkHeaders, uint64(len(forks.Headers))) for i := range forks.Headers { - require.Equal(t, czChain.ChainID, forks.Headers[i].ChainId) + require.Equal(t, czChainID, forks.Headers[i].ChainId) require.Equal(t, numHeaders-1, forks.Headers[i].Height) - require.Equal(t, forkHeaders[i].Header.LastCommitHash, forks.Headers[i].Hash) + require.Equal(t, forkHeaders[i].Header.AppHash, forks.Headers[i].Hash) } // check if the chain info is updated or not - chainInfo, err := zcKeeper.GetChainInfo(ctx, czChain.ChainID) + chainInfo, err := zcKeeper.GetChainInfo(ctx, czChainID) require.NoError(t, err) require.Equal(t, numForkHeaders, uint64(len(chainInfo.LatestForks.Headers))) for i := range forks.Headers { - require.Equal(t, czChain.ChainID, chainInfo.LatestForks.Headers[i].ChainId) + require.Equal(t, czChainID, chainInfo.LatestForks.Headers[i].ChainId) require.Equal(t, numHeaders-1, chainInfo.LatestForks.Headers[i].Height) - require.Equal(t, forkHeaders[i].Header.LastCommitHash, chainInfo.LatestForks.Headers[i].Hash) + require.Equal(t, forkHeaders[i].Header.AppHash, chainInfo.LatestForks.Headers[i].Hash) } }) } diff --git a/x/zoneconcierge/keeper/grpc_query_test.go b/x/zoneconcierge/keeper/grpc_query_test.go index 1b3f81327..9c7b55b0f 100644 --- a/x/zoneconcierge/keeper/grpc_query_test.go +++ b/x/zoneconcierge/keeper/grpc_query_test.go @@ -4,12 +4,13 @@ import ( "math/rand" "testing" + "github.com/babylonchain/babylon/app" btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/types/query" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -33,10 +34,9 @@ func FuzzChainList(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, _, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) // invoke the hook a random number of times with random chain IDs numHeaders := datagen.RandomInt(r, 100) + 1 @@ -79,10 +79,9 @@ func FuzzChainsInfo(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, _, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) var ( chainsInfo []chainInfo @@ -122,30 +121,30 @@ func FuzzHeader(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 2 numForkHeaders := datagen.RandomInt(r, 10) + 1 - headers, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) + headers, forkHeaders := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, 0, numHeaders, numForkHeaders) // find header at a random height and assert correctness against the expected header randomHeight := datagen.RandomInt(r, int(numHeaders-1)) - resp, err := zcKeeper.Header(ctx, &zctypes.QueryHeaderRequest{ChainId: czChain.ChainID, Height: randomHeight}) + resp, err := zcKeeper.Header(ctx, &zctypes.QueryHeaderRequest{ChainId: czChainID, Height: randomHeight}) require.NoError(t, err) - require.Equal(t, headers[randomHeight].Header.LastCommitHash, resp.Header.Hash) + require.Equal(t, headers[randomHeight].Header.AppHash, resp.Header.Hash) require.Len(t, resp.ForkHeaders.Headers, 0) // find the last header and fork headers then assert correctness - resp, err = zcKeeper.Header(ctx, &zctypes.QueryHeaderRequest{ChainId: czChain.ChainID, Height: numHeaders - 1}) + resp, err = zcKeeper.Header(ctx, &zctypes.QueryHeaderRequest{ChainId: czChainID, Height: numHeaders - 1}) require.NoError(t, err) - require.Equal(t, headers[numHeaders-1].Header.LastCommitHash, resp.Header.Hash) + require.Equal(t, headers[numHeaders-1].Header.AppHash, resp.Header.Hash) require.Len(t, resp.ForkHeaders.Headers, int(numForkHeaders)) for i := 0; i < int(numForkHeaders); i++ { - require.Equal(t, forkHeaders[i].Header.LastCommitHash, resp.ForkHeaders.Headers[i].Hash) + require.Equal(t, forkHeaders[i].Header.AppHash, resp.ForkHeaders.Headers[i].Hash) } }) } @@ -155,10 +154,11 @@ func FuzzEpochChainsInfo(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, _, babylonApp := SetupTest(t) + + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper + ctx := babylonApp.NewContext(false) - ctx := babylonChain.GetContext() hooks := zcKeeper.Hooks() // generate a random number of chains @@ -253,20 +253,20 @@ func FuzzListHeaders(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" // invoke the hook a random number of times to simulate a random number of blocks numHeaders := datagen.RandomInt(r, 100) + 1 numForkHeaders := datagen.RandomInt(r, 10) + 1 - headers, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, 0, numHeaders, numForkHeaders) + headers, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, 0, numHeaders, numForkHeaders) // a request with randomised pagination limit := datagen.RandomInt(r, int(numHeaders)) + 1 req := &zctypes.QueryListHeadersRequest{ - ChainId: czChain.ChainID, + ChainId: czChainID, Pagination: &query.PageRequest{ Limit: limit, }, @@ -275,7 +275,7 @@ func FuzzListHeaders(f *testing.F) { require.NoError(t, err) require.Equal(t, int(limit), len(resp.Headers)) for i := uint64(0); i < limit; i++ { - require.Equal(t, headers[i].Header.LastCommitHash, resp.Headers[i].Hash) + require.Equal(t, headers[i].Header.AppHash, resp.Headers[i].Hash) } }) } @@ -286,11 +286,12 @@ func FuzzListEpochHeaders(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - _, babylonChain, czChain, babylonApp := SetupTest(t) + babylonApp := app.Setup(t, false) zcKeeper := babylonApp.ZoneConciergeKeeper epochingKeeper := babylonApp.EpochingKeeper + ctx := babylonApp.NewContext(false) + czChainID := "test-chainid" - ctx := babylonChain.GetContext() hooks := zcKeeper.Hooks() numReqs := datagen.RandomInt(r, 5) + 1 @@ -307,7 +308,7 @@ func FuzzListEpochHeaders(f *testing.F) { epochNum := epochNumList[i] // enter a random epoch if i == 0 { - for j := uint64(0); j < epochNum; j++ { + for j := uint64(1); j < epochNum; j++ { // starting from epoch 1 epochingKeeper.IncEpoch(ctx) } } else { @@ -320,7 +321,7 @@ func FuzzListEpochHeaders(f *testing.F) { numHeadersList = append(numHeadersList, datagen.RandomInt(r, 100)+1) numForkHeadersList = append(numForkHeadersList, datagen.RandomInt(r, 10)+1) // trigger hooks to append these headers and fork headers - expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChain.ChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) + expectedHeaders, _ := SimulateNewHeadersAndForks(ctx, r, &zcKeeper, czChainID, nextHeightList[i], numHeadersList[i], numForkHeadersList[i]) expectedHeadersMap[epochNum] = expectedHeaders // prepare nextHeight for the next request nextHeightList = append(nextHeightList, nextHeightList[i]+numHeadersList[i]) @@ -336,7 +337,7 @@ func FuzzListEpochHeaders(f *testing.F) { epochNum := epochNumList[i] // make request req := &zctypes.QueryListEpochHeadersRequest{ - ChainId: czChain.ChainID, + ChainId: czChainID, EpochNum: epochNum, } resp, err := zcKeeper.ListEpochHeaders(ctx, req) @@ -346,7 +347,7 @@ func FuzzListEpochHeaders(f *testing.F) { headers := resp.Headers require.Equal(t, len(expectedHeadersMap[epochNum]), len(headers)) for j := 0; j < len(expectedHeadersMap[epochNum]); j++ { - require.Equal(t, expectedHeadersMap[epochNum][j].Header.LastCommitHash, headers[j].Hash) + require.Equal(t, expectedHeadersMap[epochNum][j].Header.AppHash, headers[j].Hash) } } }) @@ -397,15 +398,15 @@ func FuzzFinalizedChainInfo(f *testing.F) { mockBTCHeaderInfo := datagen.GenRandomBTCHeaderInfo(r) btclcKeeper.EXPECT().GetMainChainFrom(gomock.Any(), gomock.Any()).Return([]*btclightclienttypes.BTCHeaderInfo{mockBTCHeaderInfo}).AnyTimes() - // mock Tendermint client - // TODO: integration tests with Tendermint - tmClient := zctypes.NewMockTMClient(ctrl) + // mock Comet client + // TODO: integration tests with Comet + cmtClient := zctypes.NewMockCometClient(ctrl) resTx := &tmrpctypes.ResultTx{ Proof: tmtypes.TxProof{}, } - tmClient.EXPECT().Tx(gomock.Any(), gomock.Any(), true).Return(resTx, nil).AnyTimes() + cmtClient.EXPECT().Tx(gomock.Any(), gomock.Any(), true).Return(resTx, nil).AnyTimes() - zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, btclcKeeper, checkpointingKeeper, btccKeeper, epochingKeeper, tmClient) + zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, btclcKeeper, checkpointingKeeper, btccKeeper, epochingKeeper, cmtClient) hooks := zcKeeper.Hooks() var ( diff --git a/x/zoneconcierge/keeper/header_handler.go b/x/zoneconcierge/keeper/header_handler.go index 78d259d3c..e2b4b340f 100644 --- a/x/zoneconcierge/keeper/header_handler.go +++ b/x/zoneconcierge/keeper/header_handler.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,8 +10,8 @@ import ( ) // HandleHeaderWithValidCommit handles a CZ header with a valid QC -func (k Keeper) HandleHeaderWithValidCommit(ctx sdk.Context, txHash []byte, header *types.HeaderInfo, isOnFork bool) { - babylonHeader := ctx.BlockHeader() +func (k Keeper) HandleHeaderWithValidCommit(ctx context.Context, txHash []byte, header *types.HeaderInfo, isOnFork bool) { + babylonHeader := sdk.UnwrapSDKContext(ctx).BlockHeader() indexedHeader := types.IndexedHeader{ ChainId: header.ChainId, Hash: header.Hash, diff --git a/x/zoneconcierge/keeper/hooks.go b/x/zoneconcierge/keeper/hooks.go index ebbfedb97..c94feb577 100644 --- a/x/zoneconcierge/keeper/hooks.go +++ b/x/zoneconcierge/keeper/hooks.go @@ -1,6 +1,7 @@ package keeper import ( + "context" sdk "github.com/cosmos/cosmos-sdk/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" @@ -19,7 +20,7 @@ var _ epochingtypes.EpochingHooks = Hooks{} func (k Keeper) Hooks() Hooks { return Hooks{k} } // AfterEpochEnds is triggered upon an epoch has ended -func (h Hooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) { +func (h Hooks) AfterEpochEnds(ctx context.Context, epoch uint64) { // upon an epoch has ended, index the current chain info for each CZ for _, chainID := range h.k.GetAllChainIDs(ctx) { h.k.recordEpochChainInfo(ctx, chainID, epoch) @@ -27,7 +28,7 @@ func (h Hooks) AfterEpochEnds(ctx sdk.Context, epoch uint64) { } // AfterRawCheckpointFinalized is triggered upon an epoch has been finalised -func (h Hooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error { +func (h Hooks) AfterRawCheckpointFinalized(ctx context.Context, epoch uint64) error { // upon an epoch has been finalised, update the last finalised epoch h.k.setFinalizedEpoch(ctx, epoch) @@ -44,14 +45,14 @@ func (h Hooks) AfterRawCheckpointFinalized(ctx sdk.Context, epoch uint64) error // Other unused hooks -func (h Hooks) AfterBlsKeyRegistered(ctx sdk.Context, valAddr sdk.ValAddress) error { return nil } -func (h Hooks) AfterRawCheckpointConfirmed(ctx sdk.Context, epoch uint64) error { return nil } +func (h Hooks) AfterBlsKeyRegistered(ctx context.Context, valAddr sdk.ValAddress) error { return nil } +func (h Hooks) AfterRawCheckpointConfirmed(ctx context.Context, epoch uint64) error { return nil } -func (h Hooks) AfterRawCheckpointForgotten(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +func (h Hooks) AfterRawCheckpointForgotten(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return nil } -func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx sdk.Context, ckpt *checkpointingtypes.RawCheckpoint) error { +func (h Hooks) AfterRawCheckpointBlsSigVerified(ctx context.Context, ckpt *checkpointingtypes.RawCheckpoint) error { return nil } -func (h Hooks) AfterEpochBegins(ctx sdk.Context, epoch uint64) {} -func (h Hooks) BeforeSlashThreshold(ctx sdk.Context, valSet epochingtypes.ValidatorSet) {} +func (h Hooks) AfterEpochBegins(ctx context.Context, epoch uint64) {} +func (h Hooks) BeforeSlashThreshold(ctx context.Context, valSet epochingtypes.ValidatorSet) {} diff --git a/x/zoneconcierge/keeper/ibc_channels.go b/x/zoneconcierge/keeper/ibc_channels.go index a3709caee..d1ad90dc6 100644 --- a/x/zoneconcierge/keeper/ibc_channels.go +++ b/x/zoneconcierge/keeper/ibc_channels.go @@ -1,16 +1,17 @@ package keeper import ( + "context" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" ) -func (k Keeper) GetAllChannels(ctx sdk.Context) []channeltypes.IdentifiedChannel { - return k.channelKeeper.GetAllChannels(ctx) +func (k Keeper) GetAllChannels(ctx context.Context) []channeltypes.IdentifiedChannel { + return k.channelKeeper.GetAllChannels(sdk.UnwrapSDKContext(ctx)) } // GetAllOpenZCChannels returns all open channels that are connected to ZoneConcierge's port -func (k Keeper) GetAllOpenZCChannels(ctx sdk.Context) []channeltypes.IdentifiedChannel { +func (k Keeper) GetAllOpenZCChannels(ctx context.Context) []channeltypes.IdentifiedChannel { zcPort := k.GetPort(ctx) channels := k.GetAllChannels(ctx) @@ -30,10 +31,11 @@ func (k Keeper) GetAllOpenZCChannels(ctx sdk.Context) []channeltypes.IdentifiedC // isChannelUninitialized checks whether the channel is not initilialised yet // it's done by checking whether the packet sequence number is 1 (the first sequence number) or not -func (k Keeper) isChannelUninitialized(ctx sdk.Context, channel channeltypes.IdentifiedChannel) bool { +func (k Keeper) isChannelUninitialized(ctx context.Context, channel channeltypes.IdentifiedChannel) bool { + sdkCtx := sdk.UnwrapSDKContext(ctx) portID := channel.PortId channelID := channel.ChannelId // NOTE: channeltypes.IdentifiedChannel object is guaranteed to exist, so guaranteed to be found - nextSeqSend, _ := k.channelKeeper.GetNextSequenceSend(ctx, portID, channelID) + nextSeqSend, _ := k.channelKeeper.GetNextSequenceSend(sdkCtx, portID, channelID) return nextSeqSend == 1 } diff --git a/x/zoneconcierge/keeper/ibc_header_decorator.go b/x/zoneconcierge/keeper/ibc_header_decorator.go index cea7ffb9f..243008124 100644 --- a/x/zoneconcierge/keeper/ibc_header_decorator.go +++ b/x/zoneconcierge/keeper/ibc_header_decorator.go @@ -4,8 +4,8 @@ import ( "github.com/babylonchain/babylon/x/zoneconcierge/types" "github.com/cometbft/cometbft/crypto/tmhash" sdk "github.com/cosmos/cosmos-sdk/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) (*types.HeaderInfo, *ibctmtypes.ClientState) { @@ -19,7 +19,7 @@ func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) if err != nil { return nil, nil } - // ensure the ClientMsg is a Tendermint header + // ensure the ClientMsg is a Comet header ibctmHeader, ok := clientMsg.(*ibctmtypes.Header) if !ok { return nil, nil @@ -29,7 +29,7 @@ func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) headerInfo := &types.HeaderInfo{ ClientId: msgUpdateClient.ClientId, ChainId: ibctmHeader.Header.ChainID, - Hash: ibctmHeader.Header.LastCommitHash, + Hash: ibctmHeader.Header.AppHash, Height: uint64(ibctmHeader.Header.Height), Time: ibctmHeader.Header.Time, } @@ -39,13 +39,13 @@ func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) if !exist { return nil, nil } - // ensure the clientState is a Tendermint clientState - tmClientState, ok := clientState.(*ibctmtypes.ClientState) + // ensure the clientState is a Comet clientState + cmtClientState, ok := clientState.(*ibctmtypes.ClientState) if !ok { return nil, nil } - return headerInfo, tmClientState + return headerInfo, cmtClientState } type IBCHeaderDecorator struct { diff --git a/x/zoneconcierge/keeper/ibc_packet.go b/x/zoneconcierge/keeper/ibc_packet.go index 51b84cfec..5b3445f3e 100644 --- a/x/zoneconcierge/keeper/ibc_packet.go +++ b/x/zoneconcierge/keeper/ibc_packet.go @@ -1,23 +1,25 @@ package keeper import ( + "context" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "time" errorsmod "cosmossdk.io/errors" - metrics "github.com/armon/go-metrics" "github.com/babylonchain/babylon/x/zoneconcierge/types" "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - coretypes "github.com/cosmos/ibc-go/v7/modules/core/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + coretypes "github.com/cosmos/ibc-go/v8/modules/core/types" + "github.com/hashicorp/go-metrics" ) // SendIBCPacket sends an IBC packet to a channel // (adapted from https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/transfer/keeper/relay.go) -func (k Keeper) SendIBCPacket(ctx sdk.Context, channel channeltypes.IdentifiedChannel, packetData *types.ZoneconciergePacketData) error { +func (k Keeper) SendIBCPacket(ctx context.Context, channel channeltypes.IdentifiedChannel, packetData *types.ZoneconciergePacketData) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) // get src/dst ports and channels sourcePort := channel.PortId sourceChannel := channel.ChannelId @@ -26,18 +28,18 @@ func (k Keeper) SendIBCPacket(ctx sdk.Context, channel channeltypes.IdentifiedCh // begin createOutgoingPacket logic // See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay - channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) + channelCap, ok := k.scopedKeeper.GetCapability(sdkCtx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) if !ok { return errorsmod.Wrapf(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability: sourcePort: %s, sourceChannel: %s", sourcePort, sourceChannel) } // timeout - timeoutPeriod := time.Duration(k.GetParams(ctx).IbcPacketTimeoutSeconds) * time.Second - timeoutTime := uint64(ctx.BlockHeader().Time.Add(timeoutPeriod).UnixNano()) + timeoutPeriod := time.Duration(k.GetParams(sdkCtx).IbcPacketTimeoutSeconds) * time.Second + timeoutTime := uint64(sdkCtx.BlockHeader().Time.Add(timeoutPeriod).UnixNano()) zeroheight := clienttypes.ZeroHeight() seq, err := k.ics4Wrapper.SendPacket( - ctx, + sdkCtx, channelCap, sourcePort, sourceChannel, @@ -49,9 +51,9 @@ func (k Keeper) SendIBCPacket(ctx sdk.Context, channel channeltypes.IdentifiedCh // send packet if err != nil { // Failed/timeout packet should not make the system crash - k.Logger(ctx).Error(fmt.Sprintf("failed to send IBC packet (sequence number: %d) to channel %v port %s: %v", seq, destinationChannel, destinationPort, err)) + k.Logger(sdkCtx).Error(fmt.Sprintf("failed to send IBC packet (sequence number: %d) to channel %v port %s: %v", seq, destinationChannel, destinationPort, err)) } else { - k.Logger(ctx).Info(fmt.Sprintf("successfully sent IBC packet (sequence number: %d) to channel %v port %s", seq, destinationChannel, destinationPort)) + k.Logger(sdkCtx).Info(fmt.Sprintf("successfully sent IBC packet (sequence number: %d) to channel %v port %s", seq, destinationChannel, destinationPort)) } // metrics stuff diff --git a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go index 6844a51ee..d13783ec6 100644 --- a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go +++ b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" bbn "github.com/babylonchain/babylon/types" @@ -10,8 +11,8 @@ import ( epochingtypes "github.com/babylonchain/babylon/x/epoching/types" "github.com/babylonchain/babylon/x/zoneconcierge/types" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) // finalizedInfo is a private struct that stores metadata and proofs @@ -26,24 +27,25 @@ type finalizedInfo struct { } // getChainID gets the ID of the counterparty chain under the given channel -func (k Keeper) getChainID(ctx sdk.Context, channel channeltypes.IdentifiedChannel) (string, error) { +func (k Keeper) getChainID(ctx context.Context, channel channeltypes.IdentifiedChannel) (string, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) // get clientState under this channel - _, clientState, err := k.channelKeeper.GetChannelClientState(ctx, channel.PortId, channel.ChannelId) + _, clientState, err := k.channelKeeper.GetChannelClientState(sdkCtx, channel.PortId, channel.ChannelId) if err != nil { return "", err } - // cast clientState to tendermint clientState + // cast clientState to comet clientState // TODO: support for chains other than Cosmos zones - tmClient, ok := clientState.(*ibctmtypes.ClientState) + cmtClient, ok := clientState.(*ibctmtypes.ClientState) if !ok { - return "", fmt.Errorf("client must be a Tendermint client, expected: %T, got: %T", &ibctmtypes.ClientState{}, tmClient) + return "", fmt.Errorf("client must be a Comet client, expected: %T, got: %T", &ibctmtypes.ClientState{}, cmtClient) } - return tmClient.ChainId, nil + return cmtClient.ChainId, nil } // getFinalizedInfo returns metadata and proofs that are identical to all BTC timestamps in the same epoch func (k Keeper) getFinalizedInfo( - ctx sdk.Context, + ctx context.Context, epochNum uint64, headersToBroadcast []*btclctypes.BTCHeaderInfo) (*finalizedInfo, error) { finalizedEpochInfo, err := k.epochingKeeper.GetHistoricalEpoch(ctx, epochNum) @@ -93,7 +95,7 @@ func (k Keeper) getFinalizedInfo( // createBTCTimestamp creates a BTC timestamp from finalizedInfo for a given IBC channel // where the counterparty is a Cosmos zone -func (k Keeper) createBTCTimestamp(ctx sdk.Context, chainID string, channel channeltypes.IdentifiedChannel, finalizedInfo *finalizedInfo) (*types.BTCTimestamp, error) { +func (k Keeper) createBTCTimestamp(ctx context.Context, chainID string, channel channeltypes.IdentifiedChannel, finalizedInfo *finalizedInfo) (*types.BTCTimestamp, error) { // if the Babylon contract in this channel has not been initialised, get headers from // the tip to (w+1+len(finalizedInfo.BTCHeaders))-deep header var btcHeaders []*btclctypes.BTCHeaderInfo @@ -162,7 +164,7 @@ func (k Keeper) createBTCTimestamp(ctx sdk.Context, chainID string, channel chan // The header to be broadcasted are: // - either the whole known chain if we did not broadcast any headers yet // - headers from the child of the most recent header we sent which is still in the main chain up to the current tip -func (k Keeper) getHeadersToBroadcast(ctx sdk.Context) []*btclctypes.BTCHeaderInfo { +func (k Keeper) getHeadersToBroadcast(ctx context.Context) []*btclctypes.BTCHeaderInfo { lastSegment := k.GetLastSentSegment(ctx) if lastSegment == nil { @@ -190,29 +192,30 @@ func (k Keeper) getHeadersToBroadcast(ctx sdk.Context) []*btclctypes.BTCHeaderIn // BroadcastBTCTimestamps sends an IBC packet of BTC timestamp to all open IBC channels to ZoneConcierge func (k Keeper) BroadcastBTCTimestamps( - ctx sdk.Context, + ctx context.Context, epochNum uint64, headersToBroadcast []*btclctypes.BTCHeaderInfo, ) { + sdkCtx := sdk.UnwrapSDKContext(ctx) // Babylon does not broadcast BTC timestamps until finalising epoch 1 if epochNum < 1 { - k.Logger(ctx).Info("Babylon does not finalize epoch 1 yet, skip broadcasting BTC timestamps") + k.Logger(sdkCtx).Info("Babylon does not finalize epoch 1 yet, skip broadcasting BTC timestamps") return } // get all channels that are open and are connected to ZoneConcierge's port openZCChannels := k.GetAllOpenZCChannels(ctx) if len(openZCChannels) == 0 { - k.Logger(ctx).Info("no open IBC channel with ZoneConcierge, skip broadcasting BTC timestamps") + k.Logger(sdkCtx).Info("no open IBC channel with ZoneConcierge, skip broadcasting BTC timestamps") return } - k.Logger(ctx).Info("there exists open IBC channels with ZoneConcierge, generating BTC timestamps", "number of channels", len(openZCChannels)) + k.Logger(sdkCtx).Info("there exists open IBC channels with ZoneConcierge, generating BTC timestamps", "number of channels", len(openZCChannels)) // get all metadata shared across BTC timestamps in the same epoch finalizedInfo, err := k.getFinalizedInfo(ctx, epochNum, headersToBroadcast) if err != nil { - k.Logger(ctx).Error("failed to generate metadata shared across BTC timestamps in the same epoch, skip broadcasting BTC timestamps", "error", err) + k.Logger(sdkCtx).Error("failed to generate metadata shared across BTC timestamps in the same epoch, skip broadcasting BTC timestamps", "error", err) return } @@ -221,14 +224,14 @@ func (k Keeper) BroadcastBTCTimestamps( // get the ID of the chain under this channel chainID, err := k.getChainID(ctx, channel) if err != nil { - k.Logger(ctx).Error("failed to get chain ID, skip sending BTC timestamp for this chain", "channelID", channel.ChannelId, "error", err) + k.Logger(sdkCtx).Error("failed to get chain ID, skip sending BTC timestamp for this chain", "channelID", channel.ChannelId, "error", err) continue } // generate timestamp for this channel btcTimestamp, err := k.createBTCTimestamp(ctx, chainID, channel, finalizedInfo) if err != nil { - k.Logger(ctx).Error("failed to generate BTC timestamp, skip sending BTC timestamp for this chain", "chainID", chainID, "error", err) + k.Logger(sdkCtx).Error("failed to generate BTC timestamp, skip sending BTC timestamp for this chain", "chainID", chainID, "error", err) continue } @@ -236,7 +239,7 @@ func (k Keeper) BroadcastBTCTimestamps( packet := types.NewBTCTimestampPacketData(btcTimestamp) // send IBC packet if err := k.SendIBCPacket(ctx, channel, packet); err != nil { - k.Logger(ctx).Error("failed to send BTC timestamp IBC packet, skip sending BTC timestamp for this chain", "chainID", chainID, "channelID", channel.ChannelId, "error", err) + k.Logger(sdkCtx).Error("failed to send BTC timestamp IBC packet, skip sending BTC timestamp for this chain", "chainID", chainID, "channelID", channel.ChannelId, "error", err) continue } } diff --git a/x/zoneconcierge/keeper/keeper.go b/x/zoneconcierge/keeper/keeper.go index f3784b12b..b676178c8 100644 --- a/x/zoneconcierge/keeper/keeper.go +++ b/x/zoneconcierge/keeper/keeper.go @@ -1,21 +1,22 @@ package keeper import ( + "context" + corestoretypes "cosmossdk.io/core/store" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/zoneconcierge/types" - "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ) type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - memKey storetypes.StoreKey + cdc codec.BinaryCodec + storeService corestoretypes.KVStoreService ics4Wrapper types.ICS4Wrapper clientKeeper types.ClientKeeper @@ -27,8 +28,8 @@ type ( checkpointingKeeper types.CheckpointingKeeper btccKeeper types.BtcCheckpointKeeper epochingKeeper types.EpochingKeeper - tmClient types.TMClient - storeQuerier sdk.Queryable + cmtClient types.CometClient + storeQuerier storetypes.Queryable scopedKeeper types.ScopedKeeper // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. @@ -38,8 +39,7 @@ type ( func NewKeeper( cdc codec.BinaryCodec, - storeKey, - memKey storetypes.StoreKey, + storeService corestoretypes.KVStoreService, ics4Wrapper types.ICS4Wrapper, clientKeeper types.ClientKeeper, channelKeeper types.ChannelKeeper, @@ -50,15 +50,14 @@ func NewKeeper( checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper, - tmClient types.TMClient, - storeQuerier sdk.Queryable, + cmtClient types.CometClient, + storeQuerier storetypes.Queryable, scopedKeeper types.ScopedKeeper, authority string, ) *Keeper { return &Keeper{ cdc: cdc, - storeKey: storeKey, - memKey: memKey, + storeService: storeService, ics4Wrapper: ics4Wrapper, clientKeeper: clientKeeper, channelKeeper: channelKeeper, @@ -69,7 +68,7 @@ func NewKeeper( checkpointingKeeper: checkpointingKeeper, btccKeeper: btccKeeper, epochingKeeper: epochingKeeper, - tmClient: tmClient, + cmtClient: cmtClient, storeQuerier: storeQuerier, scopedKeeper: scopedKeeper, authority: authority, @@ -95,15 +94,21 @@ func (k Keeper) BindPort(ctx sdk.Context, portID string) error { } // GetPort returns the portID for the transfer module. Used in ExportGenesis -func (k Keeper) GetPort(ctx sdk.Context) string { - store := ctx.KVStore(k.storeKey) - return string(store.Get(types.PortKey)) +func (k Keeper) GetPort(ctx context.Context) string { + store := k.storeService.OpenKVStore(ctx) + port, err := store.Get(types.PortKey) + if err != nil { + panic(err) + } + return string(port) } // SetPort sets the portID for the transfer module. Used in InitGenesis -func (k Keeper) SetPort(ctx sdk.Context, portID string) { - store := ctx.KVStore(k.storeKey) - store.Set(types.PortKey, []byte(portID)) +func (k Keeper) SetPort(ctx context.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } } // AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function diff --git a/x/zoneconcierge/keeper/keeper_test.go b/x/zoneconcierge/keeper/keeper_test.go index 1c8d388b8..51b04f56d 100644 --- a/x/zoneconcierge/keeper/keeper_test.go +++ b/x/zoneconcierge/keeper/keeper_test.go @@ -1,42 +1,17 @@ package keeper_test import ( - "encoding/json" + "context" "math/rand" - "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v7/testing" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" - "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" ) -// SetupTest creates a coordinator with 2 test chains, and a ZoneConcierge keeper. -func SetupTest(t *testing.T) (*ibctesting.Coordinator, *ibctesting.TestChain, *ibctesting.TestChain, *app.BabylonApp) { - var bbnApp *app.BabylonApp - coordinator := ibctesting.NewCoordinator(t, 2) - // replace the first test chain with a Babylon chain - ibctesting.DefaultTestingAppInit = func() (ibctesting.TestingApp, map[string]json.RawMessage) { - babylonApp := app.Setup(t, false) - bbnApp = babylonApp - encCdc := app.GetEncodingConfig() - genesis := app.NewDefaultGenesisState(encCdc.Marshaler) - return babylonApp, genesis - } - babylonChainID := ibctesting.GetChainID(1) - coordinator.Chains[babylonChainID] = ibctesting.NewTestChain(t, coordinator, babylonChainID) - - babylonChain := coordinator.GetChain(ibctesting.GetChainID(1)) - czChain := coordinator.GetChain(ibctesting.GetChainID(2)) - - return coordinator, babylonChain, czChain, bbnApp -} - // SimulateNewHeaders generates a non-zero number of canonical headers -func SimulateNewHeaders(ctx sdk.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64) []*ibctmtypes.Header { +func SimulateNewHeaders(ctx context.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64) []*ibctmtypes.Header { headers := []*ibctmtypes.Header{} // invoke the hook a number of times to simulate a number of blocks for i := uint64(0); i < numHeaders; i++ { @@ -48,7 +23,7 @@ func SimulateNewHeaders(ctx sdk.Context, r *rand.Rand, k *zckeeper.Keeper, chain } // SimulateNewHeadersAndForks generates a random non-zero number of canonical headers and fork headers -func SimulateNewHeadersAndForks(ctx sdk.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64, numForkHeaders uint64) ([]*ibctmtypes.Header, []*ibctmtypes.Header) { +func SimulateNewHeadersAndForks(ctx context.Context, r *rand.Rand, k *zckeeper.Keeper, chainID string, startHeight uint64, numHeaders uint64, numForkHeaders uint64) ([]*ibctmtypes.Header, []*ibctmtypes.Header) { headers := []*ibctmtypes.Header{} // invoke the hook a number of times to simulate a number of blocks for i := uint64(0); i < numHeaders; i++ { diff --git a/x/zoneconcierge/keeper/msg_server_test.go b/x/zoneconcierge/keeper/msg_server_test.go index d7e4e1502..942926490 100644 --- a/x/zoneconcierge/keeper/msg_server_test.go +++ b/x/zoneconcierge/keeper/msg_server_test.go @@ -1,17 +1 @@ package keeper_test - -import ( - "context" - "testing" - - keepertest "github.com/babylonchain/babylon/testutil/keeper" - "github.com/babylonchain/babylon/x/zoneconcierge/keeper" - "github.com/babylonchain/babylon/x/zoneconcierge/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//nolint:unused -func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := keepertest.ZoneConciergeKeeper(t, nil, nil, nil, nil, nil) - return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) -} diff --git a/x/zoneconcierge/keeper/params.go b/x/zoneconcierge/keeper/params.go index 2cfae5c0f..04989eea9 100644 --- a/x/zoneconcierge/keeper/params.go +++ b/x/zoneconcierge/keeper/params.go @@ -1,28 +1,32 @@ package keeper import ( + "context" "github.com/babylonchain/babylon/x/zoneconcierge/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params // SetParams sets the x/zoneconcierge module parameters. -func (k Keeper) SetParams(ctx sdk.Context, p types.Params) error { +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { if err := p.Validate(); err != nil { return err } - store := ctx.KVStore(k.storeKey) + store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(&p) - store.Set(types.ParamsKey, bz) + if err := store.Set(types.ParamsKey, bz); err != nil { + panic(err) + } return nil } // GetParams returns the current x/zoneconcierge module parameters. -func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } if bz == nil { return p } diff --git a/x/zoneconcierge/keeper/proof_block_in_epoch.go b/x/zoneconcierge/keeper/proof_block_in_epoch.go index 0cdbabd35..c0f1754c6 100644 --- a/x/zoneconcierge/keeper/proof_block_in_epoch.go +++ b/x/zoneconcierge/keeper/proof_block_in_epoch.go @@ -1,17 +1,12 @@ package keeper import ( - epochingkeeper "github.com/babylonchain/babylon/x/epoching/keeper" + "context" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) ProveHeaderInEpoch(ctx sdk.Context, header *tmproto.Header, epoch *epochingtypes.Epoch) (*tmcrypto.Proof, error) { +func (k Keeper) ProveHeaderInEpoch(ctx context.Context, header *tmproto.Header, epoch *epochingtypes.Epoch) (*tmcrypto.Proof, error) { return k.epochingKeeper.ProveAppHashInEpoch(ctx, uint64(header.Height), epoch.EpochNumber) } - -func VerifyHeaderInEpoch(header *tmproto.Header, epoch *epochingtypes.Epoch, proof *tmcrypto.Proof) error { - return epochingkeeper.VerifyAppHashInclusion(header.AppHash, epoch.AppHashRoot, proof) -} diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed.go b/x/zoneconcierge/keeper/proof_epoch_sealed.go index a0d32e1a8..f1a35fc3d 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" errorsmod "cosmossdk.io/errors" @@ -47,7 +48,7 @@ func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, err // - the epoch's validator set has a valid multisig over the sealer header // - the epoch's validator set is committed to the sealer header's last_commit_hash // - the epoch's metadata is committed to the sealer header's last_commit_hash -func (k Keeper) ProveEpochSealed(ctx sdk.Context, epochNumber uint64) (*types.ProofEpochSealed, error) { +func (k Keeper) ProveEpochSealed(ctx context.Context, epochNumber uint64) (*types.ProofEpochSealed, error) { var ( proof = &types.ProofEpochSealed{} err error @@ -113,13 +114,13 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R // ensure the raw checkpoint's last_commit_hash is same as in the header of the sealer header // NOTE: since this proof is assembled by a Babylon node who has verified the checkpoint, - // the two lch values should always be the same, otherwise this Babylon node is malicious. + // the two appHash values should always be the same, otherwise this Babylon node is malicious. // This is different from the checkpoint verification rules in checkpointing, - // where a checkpoint with valid BLS multisig but different lch signals a dishonest majority equivocation. - lchInCkpt := rawCkpt.LastCommitHash - lchInSealerHeader := checkpointingtypes.LastCommitHash(epoch.SealerHeader.LastCommitHash) - if !lchInCkpt.Equal(lchInSealerHeader) { - return fmt.Errorf("LastCommitHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", lchInCkpt.String(), lchInSealerHeader.String()) + // where a checkpoint with valid BLS multisig but different appHash signals a dishonest majority equivocation. + appHashInCkpt := rawCkpt.AppHash + appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeader.AppHash) + if !appHashInCkpt.Equal(appHashInSealerHeader) { + return fmt.Errorf("AppHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", appHashInCkpt.String(), appHashInSealerHeader.String()) } /* diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go index 8f6dded9f..c0acbcc57 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go @@ -60,12 +60,12 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { // construct the rawCkpt // Note that the BlsMultiSig will be generated and assigned later - lch := checkpointingtypes.LastCommitHash(epoch.SealerHeader.LastCommitHash) + appHash := checkpointingtypes.AppHash(epoch.SealerHeader.AppHash) rawCkpt := &checkpointingtypes.RawCheckpoint{ - EpochNum: epoch.EpochNumber, - LastCommitHash: &lch, - Bitmap: bm, - BlsMultiSig: nil, + EpochNum: epoch.EpochNumber, + AppHash: &appHash, + Bitmap: bm, + BlsMultiSig: nil, } // let the subset generate a BLS multisig over sealer header's last_commit_hash diff --git a/x/zoneconcierge/keeper/proof_epoch_submitted.go b/x/zoneconcierge/keeper/proof_epoch_submitted.go index 758f2fc3e..9c421a9e9 100644 --- a/x/zoneconcierge/keeper/proof_epoch_submitted.go +++ b/x/zoneconcierge/keeper/proof_epoch_submitted.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "fmt" "math/big" @@ -9,12 +10,11 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/btcsuite/btcd/wire" - sdk "github.com/cosmos/cosmos-sdk/types" ) // ProveEpochSubmitted generates proof that the epoch's checkpoint is submitted to BTC // i.e., the two `TransactionInfo`s for the checkpoint -func (k Keeper) ProveEpochSubmitted(ctx sdk.Context, sk *btcctypes.SubmissionKey) ([]*btcctypes.TransactionInfo, error) { +func (k Keeper) ProveEpochSubmitted(ctx context.Context, sk *btcctypes.SubmissionKey) ([]*btcctypes.TransactionInfo, error) { bestSubmissionData := k.btccKeeper.GetSubmissionData(ctx, *sk) if bestSubmissionData == nil { return nil, fmt.Errorf("the best submission key for epoch %d has no submission data", bestSubmissionData.Epoch) diff --git a/x/zoneconcierge/keeper/proof_epoch_submitted_test.go b/x/zoneconcierge/keeper/proof_epoch_submitted_test.go index c8cacc3c5..89766045b 100644 --- a/x/zoneconcierge/keeper/proof_epoch_submitted_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_submitted_test.go @@ -5,7 +5,6 @@ import ( "math/rand" "testing" - txformat "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/testutil/datagen" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" @@ -65,7 +64,7 @@ func FuzzProofEpochSubmitted(f *testing.F) { tagAsBytes, _ := hex.DecodeString(babylonTag) // verify - err = zckeeper.VerifyEpochSubmitted(rawCkpt, txsInfo, btcHeaders, powLimit, txformat.BabylonTag(tagAsBytes)) + err = zckeeper.VerifyEpochSubmitted(rawCkpt, txsInfo, btcHeaders, powLimit, tagAsBytes) require.NoError(t, err) }) } diff --git a/x/zoneconcierge/keeper/proof_tx_in_block.go b/x/zoneconcierge/keeper/proof_tx_in_block.go index 89b309926..ee6373647 100644 --- a/x/zoneconcierge/keeper/proof_tx_in_block.go +++ b/x/zoneconcierge/keeper/proof_tx_in_block.go @@ -1,21 +1,21 @@ package keeper import ( + "context" "crypto/sha256" "fmt" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmtypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) ProveTxInBlock(ctx sdk.Context, txHash []byte) (*tmproto.TxProof, error) { +func (k Keeper) ProveTxInBlock(ctx context.Context, txHash []byte) (*tmproto.TxProof, error) { if len(txHash) != sha256.Size { return nil, fmt.Errorf("invalid txHash length: expected: %d, actual: %d", sha256.Size, len(txHash)) } // query the tx with inclusion proof - resTx, err := k.tmClient.Tx(ctx, txHash, true) + resTx, err := k.cmtClient.Tx(ctx, txHash, true) if err != nil { return nil, err } diff --git a/x/zoneconcierge/keeper/proof_tx_in_block_test.go b/x/zoneconcierge/keeper/proof_tx_in_block_test.go deleted file mode 100644 index 36d88ce6e..000000000 --- a/x/zoneconcierge/keeper/proof_tx_in_block_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package keeper_test - -import ( - "fmt" - "testing" - - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/testutil/network" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/spf13/pflag" - "github.com/stretchr/testify/require" - - "github.com/babylonchain/babylon/app" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" -) - -func TestProveTxInBlock(t *testing.T) { - // setup virtual network - min := network.MinimumAppConfig() - cfg, _ := network.DefaultConfigWithAppConfig(min) - encodingCfg := app.GetEncodingConfig() - cfg.InterfaceRegistry = encodingCfg.InterfaceRegistry - cfg.TxConfig = encodingCfg.TxConfig - cfg.NumValidators = 2 - cfg.RPCAddress = "tcp://0.0.0.0:26657" // TODO: parameterise this - testNetwork, err := network.New(t, t.TempDir(), cfg) - require.NoError(t, err) - defer testNetwork.Cleanup() - - // enter block 1 so that gentxs take effect and validators have tokens - err = testNetwork.WaitForNextBlock() - require.NoError(t, err) - - _, babylonChain, _, babylonApp := SetupTest(t) - zcKeeper := babylonApp.ZoneConciergeKeeper - - ctx := babylonChain.GetContext() - - // construct client context - val := testNetwork.Validators[0] - val.ClientCtx.FromAddress = val.Address - val.ClientCtx.FeePayer = val.Address - val.ClientCtx.FeeGranter = val.Address - require.NotEmpty(t, val.Address, val.ValAddress) - fs := pflag.NewFlagSet("", pflag.ContinueOnError) - fs.String(flags.FlagFees, "", "Fees to pay along with transaction; eg: 10ubbn") - fee := fmt.Sprintf("100%s", sdk.DefaultBondDenom) - err = fs.Set(flags.FlagFees, fee) - require.NoError(t, err) - - // construct a simple msg - coins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))) - msg := banktypes.NewMsgSend(val.Address, testNetwork.Validators[1].Address, coins) - - txf, err := tx.NewFactoryCLI(val.ClientCtx, fs) - - require.NoError(t, err) - // construct a tx that includes this msg - txf = txf. - WithTxConfig(val.ClientCtx.TxConfig). - WithAccountRetriever(val.ClientCtx.AccountRetriever) - - txf, err = txf.Prepare(val.ClientCtx) - require.NoError(t, err) - txb, err := txf.BuildUnsignedTx(msg) - require.NoError(t, err) - keys, err := val.ClientCtx.Keyring.List() - require.NoError(t, err) - err = tx.Sign(txf.WithKeybase(val.ClientCtx.Keyring), keys[0].Name, txb, true) - require.NoError(t, err) - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txb.GetTx()) - require.NoError(t, err) - - // submit the tx to Tendermint - resp, err := val.RPCClient.BroadcastTxSync(ctx, txBytes) - require.NoError(t, err) - txHash := resp.Hash - - err = testNetwork.WaitForNextBlock() - require.NoError(t, err) - err = testNetwork.WaitForNextBlock() - require.NoError(t, err) - - proof, err := zcKeeper.ProveTxInBlock(ctx, txHash) - require.NoError(t, err) - - err = zckeeper.VerifyTxInBlock(txHash, proof) - require.NoError(t, err) -} diff --git a/x/zoneconcierge/keeper/query_kvstore.go b/x/zoneconcierge/keeper/query_kvstore.go index bdebccee1..ad01a3260 100644 --- a/x/zoneconcierge/keeper/query_kvstore.go +++ b/x/zoneconcierge/keeper/query_kvstore.go @@ -1,12 +1,12 @@ package keeper import ( + storetypes "cosmossdk.io/store/types" "fmt" - abci "github.com/cometbft/cometbft/abci/types" + "cosmossdk.io/store/rootmulti" "github.com/cometbft/cometbft/crypto/merkle" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - "github.com/cosmos/cosmos-sdk/store/rootmulti" ) // QueryStore queries a KV pair in the KVStore, where @@ -25,12 +25,15 @@ func (k Keeper) QueryStore(moduleStoreKey string, key []byte, queryHeight int64) path := fmt.Sprintf("/%s/key", moduleStoreKey) // query the KV with Merkle proof - resp := k.storeQuerier.Query(abci.RequestQuery{ + resp, err := k.storeQuerier.Query(&storetypes.RequestQuery{ Path: path, Data: key, Height: queryHeight - 1, // NOTE: the inclusion proof corresponds to the NEXT header Prove: true, }) + if err != nil { + return nil, nil, nil, err + } if resp.Code != 0 { return nil, nil, nil, fmt.Errorf("query (with path %s) failed with response: %v", path, resp) } diff --git a/x/zoneconcierge/keeper/query_kvstore_test.go b/x/zoneconcierge/keeper/query_kvstore_test.go deleted file mode 100644 index 7134afadf..000000000 --- a/x/zoneconcierge/keeper/query_kvstore_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package keeper_test - -import ( - "testing" - - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestQueryStore(t *testing.T) { - _, babylonChain, _, babylonApp := SetupTest(t) - zcKeeper := babylonApp.ZoneConciergeKeeper - - babylonChain.NextBlock() - babylonChain.NextBlock() - - ctx := babylonChain.GetContext() - - epochQueryKey := append(epochingtypes.EpochInfoKey, sdk.Uint64ToBigEndian(1)...) - // NOTE: QueryStore will use ctx.BlockHeight() - 1 as the height for ABCI query - // This is because the ABCI query will return the inclusion proof corresponding - // to the NEXT block rather than the given block. - key, value, proof, err := zcKeeper.QueryStore(epochingtypes.StoreKey, epochQueryKey, ctx.BlockHeight()) - - require.NoError(t, err) - require.NotNil(t, proof) - require.Equal(t, epochQueryKey, key) - - err = zckeeper.VerifyStore(ctx.BlockHeader().AppHash, epochingtypes.StoreKey, key, value, proof) - require.NoError(t, err) -} diff --git a/x/zoneconcierge/module.go b/x/zoneconcierge/module.go index 26ffaf051..20ab32932 100644 --- a/x/zoneconcierge/module.go +++ b/x/zoneconcierge/module.go @@ -2,6 +2,7 @@ package zoneconcierge import ( "context" + "cosmossdk.io/core/appmodule" "encoding/json" "fmt" @@ -18,13 +19,15 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - _ porttypes.IBCModule = IBCModule{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ porttypes.IBCModule = IBCModule{} ) // ---------------------------------------------------------------------------- @@ -124,14 +127,12 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} // InitGenesis performs the module's genesis initialization. It returns no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. @@ -144,11 +145,19 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock contains the logic that is automatically triggered at the beginning of each block -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, am.keeper, req) +func (am AppModule) BeginBlock(ctx context.Context) error { + return BeginBlocker(ctx, am.keeper) } // EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx context.Context) ([]abci.ValidatorUpdate, error) { return EndBlocker(ctx, am.keeper) } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() { // marker +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() { // marker +} diff --git a/x/zoneconcierge/module_ibc.go b/x/zoneconcierge/module_ibc.go index 074cbc9ad..da3b86048 100644 --- a/x/zoneconcierge/module_ibc.go +++ b/x/zoneconcierge/module_ibc.go @@ -6,11 +6,11 @@ import ( "github.com/babylonchain/babylon/x/zoneconcierge/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ) type IBCModule struct { diff --git a/x/zoneconcierge/module_ibc_packet_test.go b/x/zoneconcierge/module_ibc_packet_test.go deleted file mode 100644 index 790ed1cbf..000000000 --- a/x/zoneconcierge/module_ibc_packet_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package zoneconcierge_test - -import ( - "encoding/json" - "math/rand" - - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - - "github.com/babylonchain/babylon/app" - zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" -) - -// SetupTest creates a coordinator with 2 test chains. -func (suite *ZoneConciergeTestSuite) SetupTestForIBCPackets() { - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - // replace the first test chain with a Babylon chain - ibctesting.DefaultTestingAppInit = func() (ibctesting.TestingApp, map[string]json.RawMessage) { - babylonApp := app.Setup(suite.T(), false) - suite.zcKeeper = babylonApp.ZoneConciergeKeeper - encCdc := app.GetEncodingConfig() - genesis := app.NewDefaultGenesisState(encCdc.Marshaler) - return babylonApp, genesis - } - babylonChainID := ibctesting.GetChainID(1) - suite.coordinator.Chains[babylonChainID] = ibctesting.NewTestChain(suite.T(), suite.coordinator, babylonChainID) - - suite.babylonChain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.czChain = suite.coordinator.GetChain(ibctesting.GetChainID(2)) - - // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) - suite.coordinator.CommitNBlocks(suite.babylonChain, 2) - suite.coordinator.CommitNBlocks(suite.czChain, 2) -} - -func (suite *ZoneConciergeTestSuite) TestSetChannel() { - suite.SetupTestForIBCPackets() - - path := ibctesting.NewPath(suite.babylonChain, suite.czChain) - - // overwrite the channel config - channelCfg := &ibctesting.ChannelConfig{ - PortID: zctypes.PortID, - Version: zctypes.Version, - Order: zctypes.Ordering, - } - path.EndpointA.ChannelConfig = channelCfg - path.EndpointB.ChannelConfig = channelCfg - - // create client and connections on both chains - suite.coordinator.SetupConnections(path) - - // check for channel to be created on chainA - _, found := suite.babylonChain.App.GetIBCKeeper().ChannelKeeper.GetChannel(suite.babylonChain.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - suite.False(found) - - // establish channel - path.SetChannelOrdered() - suite.coordinator.CreateChannels(path) - - storedChannel, found := suite.babylonChain.App.GetIBCKeeper().ChannelKeeper.GetChannel(suite.babylonChain.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - expectedCounterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) - - // assert channel states and properties - suite.True(found) - suite.Equal(channeltypes.OPEN, storedChannel.State) - suite.Equal(channeltypes.ORDERED, storedChannel.Ordering) - suite.Equal(expectedCounterparty, storedChannel.Counterparty) - - err := path.EndpointA.UpdateClient() - suite.Require().NoError(err) - err = path.EndpointB.UpdateClient() - suite.Require().NoError(err) - - numTests := 10 - for k := 0; k < numTests; k++ { // test suite does not support fuzz tests so we simulate it here - // retrieve the send sequence number in Babylon - nextSeqSend, found := suite.babylonChain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.babylonChain.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - suite.True(found) - - // commit some blocks - numBlocks := rand.Intn(10) - for i := 0; i < numBlocks; i++ { - suite.coordinator.CommitBlock(suite.babylonChain) - } - - // retrieve the send sequence number in Babylon again - newNextSeqSend, found := suite.babylonChain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.babylonChain.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) - suite.True(found) - - // Assert the gap between two sequence numbers to be zero - // No packet is supposed to be sent during these blocks. - // IBC packet (i.e., BTC timestamp) is sent only upon newly finalised epoch - suite.Equal(newNextSeqSend, nextSeqSend) - - // update clients to ensure no panic happens - err = path.EndpointA.UpdateClient() - suite.Require().NoError(err) - err = path.EndpointB.UpdateClient() - suite.Require().NoError(err) - } - -} diff --git a/x/zoneconcierge/module_simulation.go b/x/zoneconcierge/module_simulation.go index 7bb84f9a4..795437f50 100644 --- a/x/zoneconcierge/module_simulation.go +++ b/x/zoneconcierge/module_simulation.go @@ -3,12 +3,10 @@ package zoneconcierge import ( simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - simappparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/testutil/sample" zoneconciergesimulation "github.com/babylonchain/babylon/x/zoneconcierge/simulation" "github.com/babylonchain/babylon/x/zoneconcierge/types" "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/simulation" ) @@ -17,7 +15,6 @@ import ( var ( _ = sample.AccAddress _ = zoneconciergesimulation.FindAccount - _ = simappparams.StakePerAccount _ = simulation.MsgEntryKind _ = baseapp.Paramspace ) @@ -40,7 +37,7 @@ func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedP } // RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} +func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} // WeightedOperations returns the all the gov module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { diff --git a/x/zoneconcierge/module_test.go b/x/zoneconcierge/module_test.go deleted file mode 100644 index c3442e614..000000000 --- a/x/zoneconcierge/module_test.go +++ /dev/null @@ -1,429 +0,0 @@ -package zoneconcierge_test - -import ( - "encoding/json" - "fmt" - "math/rand" - "testing" - "time" - - tmbytes "github.com/cometbft/cometbft/libs/bytes" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmtypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/cosmos-sdk/codec" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - clientkeeper "github.com/cosmos/ibc-go/v7/modules/core/02-client/keeper" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - ibctestingmock "github.com/cosmos/ibc-go/v7/testing/mock" - "github.com/cosmos/ibc-go/v7/testing/simapp" - "github.com/stretchr/testify/suite" - - "github.com/babylonchain/babylon/app" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" -) - -// ZoneConciergeTestSuite provides a test suite for IBC functionalities in ZoneConcierge -// (adapted from https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/core/02-client/keeper/keeper_test.go) -type ZoneConciergeTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - // testing chains used for convenience and readability - babylonChain *ibctesting.TestChain - czChain *ibctesting.TestChain - - // System states of the simulated Babylon chain - cdc codec.Codec - ctx sdk.Context - keeper clientkeeper.Keeper - zcKeeper zckeeper.Keeper - consensusState *ibctmtypes.ConsensusState - header *ibctmtypes.Header - valSet *tmtypes.ValidatorSet - valSetHash tmbytes.HexBytes - privVal tmtypes.PrivValidator - now time.Time - past time.Time - signers map[string]tmtypes.PrivValidator -} - -func TestZoneConciergeTestSuite(t *testing.T) { - suite.Run(t, new(ZoneConciergeTestSuite)) -} - -func (suite *ZoneConciergeTestSuite) SetupTest() { - // set up 2 Test chains with default constructors - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - // replace the first test chain with a Babylon chain - ibctesting.DefaultTestingAppInit = func() (ibctesting.TestingApp, map[string]json.RawMessage) { - babylonApp := app.Setup(suite.T(), false) - suite.zcKeeper = babylonApp.ZoneConciergeKeeper - encCdc := app.GetEncodingConfig() - return babylonApp, app.NewDefaultGenesisState(encCdc.Marshaler) - } - babylonChainID := ibctesting.GetChainID(1) - suite.coordinator.Chains[babylonChainID] = ibctesting.NewTestChain(suite.T(), suite.coordinator, babylonChainID) - - suite.babylonChain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.czChain = suite.coordinator.GetChain(ibctesting.GetChainID(2)) - - suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) - suite.past = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) - now2 := suite.now.Add(time.Hour) - app := simapp.Setup(false) - - babylonChainHeight := uint64(5) // TODO: find out why it's 5 (any value > 0 is okay) - suite.cdc = app.AppCodec() - suite.ctx = app.BaseApp.NewContext(false, tmproto.Header{Height: int64(babylonChainHeight), ChainID: babylonChainID, Time: now2}) - suite.keeper = app.IBCKeeper.ClientKeeper - suite.privVal = ibctestingmock.NewPV() - - pubKey, err := suite.privVal.GetPubKey() - suite.Require().NoError(err) - - testClientHeightMinus1 := clienttypes.NewHeight(0, babylonChainHeight-1) - - validator := tmtypes.NewValidator(pubKey, 1) - suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - suite.valSetHash = suite.valSet.Hash() - suite.signers = make(map[string]tmtypes.PrivValidator, 1) - suite.signers[validator.Address.String()] = suite.privVal - - testClientHeight := clienttypes.NewHeight(0, babylonChainHeight) - testChainID := ibctesting.GetChainID(2) - suite.header = suite.babylonChain.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeightMinus1, now2, suite.valSet, suite.valSet, suite.valSet, suite.signers) - suite.consensusState = ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.valSetHash) - - var validators stakingtypes.Validators - for i := 1; i < 11; i++ { - privVal := ibctestingmock.NewPV() - tmPk, err := privVal.GetPubKey() - suite.Require().NoError(err) - pk, err := cryptocodec.FromTmPubKeyInterface(tmPk) - suite.Require().NoError(err) - val, err := stakingtypes.NewValidator(sdk.ValAddress(pk.Address()), pk, stakingtypes.Description{}) - suite.Require().NoError(err) - - val.Status = stakingtypes.Bonded - val.Tokens = sdk.NewInt(rand.Int63()) - validators = append(validators, val) - - hi := stakingtypes.NewHistoricalInfo(suite.ctx.BlockHeader(), validators, sdk.DefaultPowerReduction) - app.StakingKeeper.SetHistoricalInfo(suite.ctx, int64(i), &hi) - } -} - -// TestUpdateClientTendermint provides tests on verifying different headers from the IBC light client -// (https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/core/02-client/keeper/client_test.go) -func (suite *ZoneConciergeTestSuite) TestUpdateClientTendermint() { - var ( - path *ibctesting.Path - updateHeader *ibctmtypes.Header - ) - - // Must create header creation functions since suite.header gets recreated on each test case - createFutureUpdateFn := func(trustedHeight clienttypes.Height) *ibctmtypes.Header { - header, err := suite.babylonChain.ConstructUpdateTMClientHeaderWithTrustedHeight(path.EndpointB.Chain, path.EndpointA.ClientID, trustedHeight) - suite.Require().NoError(err) - return header - } - createPastUpdateFn := func(fillHeight, trustedHeight clienttypes.Height) *ibctmtypes.Header { - consState, found := suite.babylonChain.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.babylonChain.GetContext(), path.EndpointA.ClientID, trustedHeight) - suite.Require().True(found) - - return suite.czChain.CreateTMClientHeader(suite.czChain.ChainID, int64(fillHeight.RevisionHeight), trustedHeight, consState.(*ibctmtypes.ConsensusState).Timestamp.Add(time.Second*5), - suite.czChain.Vals, suite.czChain.Vals, suite.czChain.Vals, suite.czChain.Signers) - } - - cases := []struct { - name string - malleate func() - expPass bool - expDishonestMajority bool - }{ - {"valid update", func() { - clientState := path.EndpointA.GetClientState().(*ibctmtypes.ClientState) - trustHeight := clientState.GetLatestHeight().(clienttypes.Height) - - // store intermediate consensus state to check that trustedHeight does not need to be highest consensus state before header height - err := path.EndpointA.UpdateClient() - suite.Require().NoError(err) - - updateHeader = createFutureUpdateFn(trustHeight) - }, true, false}, - {"valid past update", func() { - clientState := path.EndpointA.GetClientState() - trustedHeight := clientState.GetLatestHeight().(clienttypes.Height) - - currHeight := suite.czChain.CurrentHeader.Height - fillHeight := clienttypes.NewHeight(clientState.GetLatestHeight().GetRevisionNumber(), uint64(currHeight)) - - // commit a couple blocks to allow client to fill in gaps - suite.coordinator.CommitBlock(suite.czChain) // this height is not filled in yet - suite.coordinator.CommitBlock(suite.czChain) // this height is filled in by the update below - - err := path.EndpointA.UpdateClient() - suite.Require().NoError(err) - - // ensure fill height not set - _, found := suite.babylonChain.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.babylonChain.GetContext(), path.EndpointA.ClientID, fillHeight) - suite.Require().False(found) - - // updateHeader will fill in consensus state between prevConsState and suite.consState - // clientState should not be updated - updateHeader = createPastUpdateFn(fillHeight, trustedHeight) - }, true, false}, - {"valid duplicate update", func() { - clientID := path.EndpointA.ClientID - - height1 := clienttypes.NewHeight(1, 1) - - // store previous consensus state - prevConsState := &ibctmtypes.ConsensusState{ - Timestamp: suite.past, - NextValidatorsHash: suite.czChain.Vals.Hash(), - } - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, height1, prevConsState) - - height5 := clienttypes.NewHeight(1, 5) - // store next consensus state to check that trustedHeight does not need to be hightest consensus state before header height - nextConsState := &ibctmtypes.ConsensusState{ - Timestamp: suite.past.Add(time.Minute), - NextValidatorsHash: suite.czChain.Vals.Hash(), - } - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, height5, nextConsState) - - height3 := clienttypes.NewHeight(1, 3) - // updateHeader will fill in consensus state between prevConsState and suite.consState - // clientState should not be updated - updateHeader = createPastUpdateFn(height3, height1) - // set updateHeader's consensus state in store to create duplicate UpdateClient scenario - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, updateHeader.GetHeight(), updateHeader.ConsensusState()) - }, true, false}, - {"misbehaviour in dishonest majority CZ: conflicting header", func() { - clientID := path.EndpointA.ClientID - - height1 := clienttypes.NewHeight(1, 1) - // store previous consensus state - prevConsState := &ibctmtypes.ConsensusState{ - Timestamp: suite.past, - NextValidatorsHash: suite.czChain.Vals.Hash(), - } - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, height1, prevConsState) - - height5 := clienttypes.NewHeight(1, 5) - // store next consensus state to check that trustedHeight does not need to be hightest consensus state before header height - nextConsState := &ibctmtypes.ConsensusState{ - Timestamp: suite.past.Add(time.Minute), - NextValidatorsHash: suite.czChain.Vals.Hash(), - } - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, height5, nextConsState) - - height3 := clienttypes.NewHeight(1, 3) - // updateHeader will fill in consensus state between prevConsState and suite.consState - // clientState should not be updated - updateHeader = createPastUpdateFn(height3, height1) - // set conflicting consensus state in store to create misbehaviour scenario - conflictConsState := updateHeader.ConsensusState() - conflictConsState.Root = commitmenttypes.NewMerkleRoot([]byte("conflicting apphash")) - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, updateHeader.GetHeight(), conflictConsState) - }, false, true}, // Babylon modification: fork headers will be recorded in the fork index - {"misbehaviour in dishonest majority CZ: monotonic time violation", func() { - clientState := path.EndpointA.GetClientState().(*ibctmtypes.ClientState) - clientID := path.EndpointA.ClientID - trustedHeight := clientState.GetLatestHeight().(clienttypes.Height) - - // store intermediate consensus state at a time greater than updateHeader time - // this will break time monotonicity - incrementedClientHeight := clientState.GetLatestHeight().Increment().(clienttypes.Height) - intermediateConsState := &ibctmtypes.ConsensusState{ - Timestamp: suite.coordinator.CurrentTime.Add(2 * time.Hour), - NextValidatorsHash: suite.czChain.Vals.Hash(), - } - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.babylonChain.GetContext(), clientID, incrementedClientHeight, intermediateConsState) - // set iteration key - clientStore := suite.keeper.ClientStore(suite.ctx, clientID) - ibctmtypes.SetIterationKey(clientStore, incrementedClientHeight) - - clientState.LatestHeight = incrementedClientHeight - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.babylonChain.GetContext(), clientID, clientState) - - updateHeader = createFutureUpdateFn(trustedHeight) - }, false, true}, // Babylon modification: non-monotonic headers will be recorded in the fork index - {"client state not found", func() { - updateHeader = createFutureUpdateFn(path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height)) - - path.EndpointA.ClientID = ibctesting.InvalidID - }, false, false}, - {"consensus state not found", func() { - clientState := path.EndpointA.GetClientState() - tmClient, ok := clientState.(*ibctmtypes.ClientState) - suite.Require().True(ok) - tmClient.LatestHeight = tmClient.LatestHeight.Increment().(clienttypes.Height) - - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.babylonChain.GetContext(), path.EndpointA.ClientID, clientState) - updateHeader = createFutureUpdateFn(clientState.GetLatestHeight().(clienttypes.Height)) - }, false, false}, - {"client is not active", func() { - clientState := path.EndpointA.GetClientState().(*ibctmtypes.ClientState) - clientState.FrozenHeight = clienttypes.NewHeight(0, 1) - suite.babylonChain.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.babylonChain.GetContext(), path.EndpointA.ClientID, clientState) - updateHeader = createFutureUpdateFn(clientState.GetLatestHeight().(clienttypes.Height)) - }, false, false}, - {"invalid header", func() { - updateHeader = createFutureUpdateFn(path.EndpointA.GetClientState().GetLatestHeight().(clienttypes.Height)) - updateHeader.TrustedHeight = updateHeader.TrustedHeight.Increment().(clienttypes.Height) - }, false, false}, - } - - for _, tc := range cases { - suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() - path = ibctesting.NewPath(suite.babylonChain, suite.czChain) - suite.coordinator.SetupClients(path) - - tc.malleate() - - var clientState exported.ClientState - if tc.expPass { - clientState = path.EndpointA.GetClientState() - } - - msg, err := clienttypes.NewMsgUpdateClient(path.EndpointA.ClientID, updateHeader, path.EndpointA.Chain.SenderAccount.GetAddress().String()) - suite.Require().NoError(err) - - // define SendMsgsOverride such that SendMsgs does not make expectation on whether - // the tx is successful or not. Here ZoneConcierge only cares about whether headers - // are indexed as expected - suite.babylonChain.SendMsgsOverride = func(msgs ...sdk.Msg) (*sdk.Result, error) { - chain := suite.babylonChain - // ensure the chain has the latest time - chain.Coordinator.UpdateTimeForChain(chain) - - // generate a signed mock tx - tx, err := simtestutil.GenSignedMockTx( - rand.New(rand.NewSource(time.Now().UnixNano())), - chain.TxConfig, - msgs, - sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, - simtestutil.DefaultGenTxGas, - chain.ChainID, - []uint64{chain.SenderAccount.GetAccountNumber()}, - []uint64{chain.SenderAccount.GetSequence()}, - chain.SenderPrivKey, - ) - if err != nil { - return nil, err - } - - // Simulate a sending a transaction - _, res, err := chain.App.GetBaseApp().SimDeliver(chain.TxConfig.TxEncoder(), tx) - if err != nil { - return nil, err - } - - // NextBlock calls app.Commit() - chain.NextBlock() - - // increment sequence for successful transaction execution - err = chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) - if err != nil { - return nil, err - } - - // increment time - chain.Coordinator.IncrementTime() - - return res, nil - } - // send message! - _, err = suite.babylonChain.SendMsgs(msg) - - if tc.expPass { - suite.Require().NoError(err, err) - - newClientState := path.EndpointA.GetClientState() - - // Babylon check: Babylon's IBC light client should never freeze - suite.Require().True(newClientState.(*ibctmtypes.ClientState).FrozenHeight.IsZero(), "Babylon's IBC light client should never freeze") - - expConsensusState := &ibctmtypes.ConsensusState{ - Timestamp: updateHeader.GetTime(), - Root: commitmenttypes.NewMerkleRoot(updateHeader.Header.GetAppHash()), - NextValidatorsHash: updateHeader.Header.NextValidatorsHash, - } - - consensusState, found := suite.babylonChain.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.babylonChain.GetContext(), path.EndpointA.ClientID, updateHeader.GetHeight()) - suite.Require().True(found) - - // Determine if clientState should be updated or not - if updateHeader.GetHeight().GT(clientState.GetLatestHeight()) { - // Header Height is greater than clientState latest Height, clientState should be updated with header.GetHeight() - suite.Require().Equal(updateHeader.GetHeight(), newClientState.GetLatestHeight(), "clientstate height did not update") - } else { - // Update will add past consensus state, clientState should not be updated at all - suite.Require().Equal(clientState.GetLatestHeight(), newClientState.GetLatestHeight(), "client state height updated for past header") - } - - suite.Require().NoError(err) - suite.Require().Equal(expConsensusState, consensusState, "consensus state should have been updated on case %s", tc.name) - - /* Extra Babylon checks */ - ctx := suite.babylonChain.GetContext() - czChainID := suite.czChain.ChainID - updateHeaderHeight := uint64(updateHeader.Header.Height) - - // updateHeader should be correctly recorded in chain info indexer - if tc.name != "valid past update" { // we exclude the case of past update since chain info indexer does not record past update - // updateHeader should be correctly recorded in canonical chain indexer - expUpdateHeader, err := suite.zcKeeper.GetHeader(ctx, czChainID, updateHeaderHeight) - suite.Require().NoError(err) - suite.Require().Equal(expUpdateHeader.Hash, updateHeader.Header.LastCommitHash) - suite.Require().Equal(expUpdateHeader.Height, updateHeaderHeight) - // updateHeader should be correctly recorded in chain info indexer - chainInfo, err := suite.zcKeeper.GetChainInfo(ctx, czChainID) - suite.Require().NoError(err) - suite.Require().Equal(chainInfo.LatestHeader.Hash, updateHeader.Header.LastCommitHash) - suite.Require().Equal(chainInfo.LatestHeader.Height, updateHeaderHeight) - } else { - // there should be no header in updateHeaderHeight - _, err := suite.zcKeeper.GetHeader(ctx, czChainID, updateHeaderHeight) - suite.Require().Error(err) - // the latest header in chain info indexer should be the last header - chainInfo, err := suite.zcKeeper.GetChainInfo(ctx, czChainID) - suite.Require().NoError(err) - suite.Require().Equal(chainInfo.LatestHeader.Hash, suite.czChain.LastHeader.Header.LastCommitHash) - suite.Require().Equal(chainInfo.LatestHeader.Height, uint64(suite.czChain.LastHeader.Header.Height)) - } - } else { - if tc.expDishonestMajority { - /* Extra Babylon checks */ - ctx := suite.babylonChain.GetContext() - czChainID := suite.czChain.ChainID - updateHeaderHeight := uint64(updateHeader.Header.Height) - // updateHeader should be correctly recorded in fork indexer - expForks := suite.zcKeeper.GetForks(ctx, czChainID, updateHeaderHeight) - suite.Require().Equal(1, len(expForks.Headers)) - suite.Require().Equal(expForks.Headers[0].Hash, updateHeader.Header.LastCommitHash) - suite.Require().Equal(expForks.Headers[0].Height, updateHeaderHeight) - // updateHeader should be correctly recorded in chain info indexer - chainInfo, err := suite.zcKeeper.GetChainInfo(ctx, czChainID) - suite.Require().NoError(err) - suite.Require().Equal(1, len(chainInfo.LatestForks.Headers)) - suite.Require().Equal(chainInfo.LatestForks.Headers[0].Hash, updateHeader.Header.LastCommitHash) - suite.Require().Equal(chainInfo.LatestForks.Headers[0].Height, updateHeaderHeight) - } else { - suite.Require().Error(err) - } - } - }) - } -} diff --git a/x/zoneconcierge/types/codec.go b/x/zoneconcierge/types/codec.go index be4f24671..c88ab8585 100644 --- a/x/zoneconcierge/types/codec.go +++ b/x/zoneconcierge/types/codec.go @@ -7,11 +7,9 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { -} +func RegisterCodec(_ *codec.LegacyAmino) {} func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { - msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/zoneconcierge/types/errors.go b/x/zoneconcierge/types/errors.go index 88e178199..f37cae374 100644 --- a/x/zoneconcierge/types/errors.go +++ b/x/zoneconcierge/types/errors.go @@ -1,28 +1,20 @@ package types -// DONTCOVER - import ( errorsmod "cosmossdk.io/errors" ) // x/zoneconcierge module sentinel errors var ( - ErrSample = errorsmod.Register(ModuleName, 1100, "sample error") - ErrInvalidPacketTimeout = errorsmod.Register(ModuleName, 1101, "invalid packet timeout") - ErrInvalidVersion = errorsmod.Register(ModuleName, 1102, "invalid version") - ErrHeaderNotFound = errorsmod.Register(ModuleName, 1103, "no header exists at this height") - ErrInvalidHeader = errorsmod.Register(ModuleName, 1104, "input header is invalid") - ErrNoValidAncestorHeader = errorsmod.Register(ModuleName, 1105, "no valid ancestor for this header") - ErrForkNotFound = errorsmod.Register(ModuleName, 1106, "cannot find fork") - ErrInvalidForks = errorsmod.Register(ModuleName, 1107, "input forks is invalid") - ErrChainInfoNotFound = errorsmod.Register(ModuleName, 1108, "no chain info exists") - ErrEpochChainInfoNotFound = errorsmod.Register(ModuleName, 1109, "no chain info exists at this epoch") - ErrEpochHeadersNotFound = errorsmod.Register(ModuleName, 1110, "no timestamped header exists at this epoch") - ErrFinalizedEpochNotFound = errorsmod.Register(ModuleName, 1111, "cannot find a finalized epoch") - ErrInvalidProofEpochSealed = errorsmod.Register(ModuleName, 1112, "invalid ProofEpochSealed") - ErrInvalidMerkleProof = errorsmod.Register(ModuleName, 1113, "invalid Merkle inclusion proof") - ErrInvalidChainInfo = errorsmod.Register(ModuleName, 1114, "invalid chain info") - ErrFinalizingBTCTipNotFound = errorsmod.Register(ModuleName, 1115, "cannot find a finalizing BTC tip") - ErrInvalidChainIDs = errorsmod.Register(ModuleName, 1116, "chain ids contain duplicates or empty strings") + ErrInvalidVersion = errorsmod.Register(ModuleName, 1101, "invalid version") + ErrHeaderNotFound = errorsmod.Register(ModuleName, 1102, "no header exists at this height") + ErrInvalidHeader = errorsmod.Register(ModuleName, 1103, "input header is invalid") + ErrChainInfoNotFound = errorsmod.Register(ModuleName, 1104, "no chain info exists") + ErrEpochChainInfoNotFound = errorsmod.Register(ModuleName, 1105, "no chain info exists at this epoch") + ErrEpochHeadersNotFound = errorsmod.Register(ModuleName, 1106, "no timestamped header exists at this epoch") + ErrFinalizedEpochNotFound = errorsmod.Register(ModuleName, 1107, "cannot find a finalized epoch") + ErrInvalidProofEpochSealed = errorsmod.Register(ModuleName, 1108, "invalid ProofEpochSealed") + ErrInvalidMerkleProof = errorsmod.Register(ModuleName, 1109, "invalid Merkle inclusion proof") + ErrInvalidChainInfo = errorsmod.Register(ModuleName, 1110, "invalid chain info") + ErrInvalidChainIDs = errorsmod.Register(ModuleName, 1111, "chain ids contain duplicates or empty strings") ) diff --git a/x/zoneconcierge/types/events_ibc.go b/x/zoneconcierge/types/events_ibc.go index fd4e2bba8..07b499e5a 100644 --- a/x/zoneconcierge/types/events_ibc.go +++ b/x/zoneconcierge/types/events_ibc.go @@ -2,10 +2,8 @@ package types // IBC events const ( - EventTypeAck = "acknowledgement" - EventTypeTimeout = "timeout" + EventTypeAck = "acknowledgement" AttributeKeyAckSuccess = "success" - AttributeKeyAck = "acknowledgement" AttributeKeyAckError = "error" ) diff --git a/x/zoneconcierge/types/expected_keepers.go b/x/zoneconcierge/types/expected_keepers.go index 082a4570d..b618d3abe 100644 --- a/x/zoneconcierge/types/expected_keepers.go +++ b/x/zoneconcierge/types/expected_keepers.go @@ -1,10 +1,10 @@ package types import ( - context "context" + "context" bbn "github.com/babylonchain/babylon/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" @@ -13,26 +13,25 @@ import ( tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" ctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ) // AccountKeeper defines the contract required for account APIs. type AccountKeeper interface { GetModuleAddress(name string) sdk.AccAddress - GetModuleAccount(ctx sdk.Context, name string) types.ModuleAccountI + GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI } // BankKeeper defines the expected bank keeper type BankKeeper interface { - SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error BlockedAddr(addr sdk.AccAddress) bool } @@ -82,32 +81,32 @@ type ScopedKeeper interface { } type BTCLightClientKeeper interface { - GetTipInfo(ctx sdk.Context) *btclctypes.BTCHeaderInfo - GetMainChainFrom(ctx sdk.Context, startHeight uint64) []*btclctypes.BTCHeaderInfo - GetMainChainUpTo(ctx sdk.Context, depth uint64) []*btclctypes.BTCHeaderInfo - GetHeaderByHash(ctx sdk.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo + GetTipInfo(ctx context.Context) *btclctypes.BTCHeaderInfo + GetMainChainFrom(ctx context.Context, startHeight uint64) []*btclctypes.BTCHeaderInfo + GetMainChainUpTo(ctx context.Context, depth uint64) []*btclctypes.BTCHeaderInfo + GetHeaderByHash(ctx context.Context, hash *bbn.BTCHeaderHashBytes) *btclctypes.BTCHeaderInfo } type BtcCheckpointKeeper interface { - GetParams(ctx sdk.Context) (p btcctypes.Params) - GetEpochData(ctx sdk.Context, e uint64) *btcctypes.EpochData - GetBestSubmission(ctx sdk.Context, e uint64) (btcctypes.BtcStatus, *btcctypes.SubmissionKey, error) - GetSubmissionData(ctx sdk.Context, sk btcctypes.SubmissionKey) *btcctypes.SubmissionData - GetEpochBestSubmissionBtcInfo(ctx sdk.Context, ed *btcctypes.EpochData) *btcctypes.SubmissionBtcInfo + GetParams(ctx context.Context) (p btcctypes.Params) + GetEpochData(ctx context.Context, e uint64) *btcctypes.EpochData + GetBestSubmission(ctx context.Context, e uint64) (btcctypes.BtcStatus, *btcctypes.SubmissionKey, error) + GetSubmissionData(ctx context.Context, sk btcctypes.SubmissionKey) *btcctypes.SubmissionData + GetEpochBestSubmissionBtcInfo(ctx context.Context, ed *btcctypes.EpochData) *btcctypes.SubmissionBtcInfo } type CheckpointingKeeper interface { - GetBLSPubKeySet(ctx sdk.Context, epochNumber uint64) ([]*checkpointingtypes.ValidatorWithBlsKey, error) - GetRawCheckpoint(ctx sdk.Context, epochNumber uint64) (*checkpointingtypes.RawCheckpointWithMeta, error) + GetBLSPubKeySet(ctx context.Context, epochNumber uint64) ([]*checkpointingtypes.ValidatorWithBlsKey, error) + GetRawCheckpoint(ctx context.Context, epochNumber uint64) (*checkpointingtypes.RawCheckpointWithMeta, error) } type EpochingKeeper interface { - GetHistoricalEpoch(ctx sdk.Context, epochNumber uint64) (*epochingtypes.Epoch, error) - GetEpoch(ctx sdk.Context) *epochingtypes.Epoch - ProveAppHashInEpoch(ctx sdk.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) + GetHistoricalEpoch(ctx context.Context, epochNumber uint64) (*epochingtypes.Epoch, error) + GetEpoch(ctx context.Context) *epochingtypes.Epoch + ProveAppHashInEpoch(ctx context.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) } -// TMClient is a Tendermint client that allows to query tx inclusion proofs -type TMClient interface { +// CometClient is a Comet client that allows to query tx inclusion proofs +type CometClient interface { Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) } diff --git a/x/zoneconcierge/types/genesis.go b/x/zoneconcierge/types/genesis.go index 37b63a085..1bf65e10d 100644 --- a/x/zoneconcierge/types/genesis.go +++ b/x/zoneconcierge/types/genesis.go @@ -1,12 +1,9 @@ package types import ( - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ) -// DefaultIndex is the default global index -const DefaultIndex uint64 = 1 - // DefaultGenesis returns the default genesis state func DefaultGenesis() *GenesisState { return &GenesisState{ diff --git a/x/zoneconcierge/types/keys.go b/x/zoneconcierge/types/keys.go index ce46c87db..029d42b77 100644 --- a/x/zoneconcierge/types/keys.go +++ b/x/zoneconcierge/types/keys.go @@ -1,7 +1,7 @@ package types import ( - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" ) const ( diff --git a/x/zoneconcierge/types/mocked_keepers.go b/x/zoneconcierge/types/mocked_keepers.go index 2453d64ff..ae09ce756 100644 --- a/x/zoneconcierge/types/mocked_keepers.go +++ b/x/zoneconcierge/types/mocked_keepers.go @@ -16,12 +16,11 @@ import ( crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" coretypes "github.com/cometbft/cometbft/rpc/core/types" types4 "github.com/cosmos/cosmos-sdk/types" - types5 "github.com/cosmos/cosmos-sdk/x/auth/types" - types6 "github.com/cosmos/cosmos-sdk/x/capability/types" - types7 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - types8 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - types9 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - exported "github.com/cosmos/ibc-go/v7/modules/core/exported" + types5 "github.com/cosmos/ibc-go/modules/capability/types" + types6 "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + types7 "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + types8 "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + exported "github.com/cosmos/ibc-go/v8/modules/core/exported" gomock "github.com/golang/mock/gomock" ) @@ -49,10 +48,10 @@ func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { } // GetModuleAccount mocks base method. -func (m *MockAccountKeeper) GetModuleAccount(ctx types4.Context, name string) types5.ModuleAccountI { +func (m *MockAccountKeeper) GetModuleAccount(ctx context.Context, name string) types4.ModuleAccountI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModuleAccount", ctx, name) - ret0, _ := ret[0].(types5.ModuleAccountI) + ret0, _ := ret[0].(types4.ModuleAccountI) return ret0 } @@ -114,7 +113,7 @@ func (mr *MockBankKeeperMockRecorder) BlockedAddr(addr interface{}) *gomock.Call } // BurnCoins mocks base method. -func (m *MockBankKeeper) BurnCoins(ctx types4.Context, moduleName string, amt types4.Coins) error { +func (m *MockBankKeeper) BurnCoins(ctx context.Context, moduleName string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BurnCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -128,7 +127,7 @@ func (mr *MockBankKeeperMockRecorder) BurnCoins(ctx, moduleName, amt interface{} } // MintCoins mocks base method. -func (m *MockBankKeeper) MintCoins(ctx types4.Context, moduleName string, amt types4.Coins) error { +func (m *MockBankKeeper) MintCoins(ctx context.Context, moduleName string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MintCoins", ctx, moduleName, amt) ret0, _ := ret[0].(error) @@ -142,7 +141,7 @@ func (mr *MockBankKeeperMockRecorder) MintCoins(ctx, moduleName, amt interface{} } // SendCoins mocks base method. -func (m *MockBankKeeper) SendCoins(ctx types4.Context, fromAddr, toAddr types4.AccAddress, amt types4.Coins) error { +func (m *MockBankKeeper) SendCoins(ctx context.Context, fromAddr, toAddr types4.AccAddress, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) ret0, _ := ret[0].(error) @@ -156,7 +155,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt inter } // SendCoinsFromAccountToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx types4.Context, senderAddr types4.AccAddress, recipientModule string, amt types4.Coins) error { +func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx context.Context, senderAddr types4.AccAddress, recipientModule string, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) ret0, _ := ret[0].(error) @@ -170,7 +169,7 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAd } // SendCoinsFromModuleToAccount mocks base method. -func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx types4.Context, senderModule string, recipientAddr types4.AccAddress, amt types4.Coins) error { +func (m *MockBankKeeper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types4.AccAddress, amt types4.Coins) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) ret0, _ := ret[0].(error) @@ -207,7 +206,7 @@ func (m *MockICS4Wrapper) EXPECT() *MockICS4WrapperMockRecorder { } // SendPacket mocks base method. -func (m *MockICS4Wrapper) SendPacket(ctx types4.Context, channelCap *types6.Capability, sourcePort, sourceChannel string, timeoutHeight types7.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { +func (m *MockICS4Wrapper) SendPacket(ctx types4.Context, channelCap *types5.Capability, sourcePort, sourceChannel string, timeoutHeight types6.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendPacket", ctx, channelCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) ret0, _ := ret[0].(uint64) @@ -245,10 +244,10 @@ func (m *MockChannelKeeper) EXPECT() *MockChannelKeeperMockRecorder { } // GetAllChannels mocks base method. -func (m *MockChannelKeeper) GetAllChannels(ctx types4.Context) []types9.IdentifiedChannel { +func (m *MockChannelKeeper) GetAllChannels(ctx types4.Context) []types8.IdentifiedChannel { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllChannels", ctx) - ret0, _ := ret[0].([]types9.IdentifiedChannel) + ret0, _ := ret[0].([]types8.IdentifiedChannel) return ret0 } @@ -259,10 +258,10 @@ func (mr *MockChannelKeeperMockRecorder) GetAllChannels(ctx interface{}) *gomock } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types4.Context, srcPort, srcChan string) (types9.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types4.Context, srcPort, srcChan string) (types8.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types9.Channel) + ret0, _ := ret[0].(types8.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -327,19 +326,31 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } -// GetClientConsensusState mocks base method. -func (m *MockClientKeeper) GetClientConsensusState(ctx types4.Context, clientID string) (exported.ConsensusState, bool) { +// GetClientState mocks base method. +func (m *MockClientKeeper) GetClientState(ctx types4.Context, clientID string) (exported.ClientState, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetClientConsensusState", ctx, clientID) - ret0, _ := ret[0].(exported.ConsensusState) + ret := m.ctrl.Call(m, "GetClientState", ctx, clientID) + ret0, _ := ret[0].(exported.ClientState) ret1, _ := ret[1].(bool) return ret0, ret1 } -// GetClientConsensusState indicates an expected call of GetClientConsensusState. -func (mr *MockClientKeeperMockRecorder) GetClientConsensusState(ctx, clientID interface{}) *gomock.Call { +// GetClientState indicates an expected call of GetClientState. +func (mr *MockClientKeeperMockRecorder) GetClientState(ctx, clientID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientConsensusState", reflect.TypeOf((*MockClientKeeper)(nil).GetClientConsensusState), ctx, clientID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientState", reflect.TypeOf((*MockClientKeeper)(nil).GetClientState), ctx, clientID) +} + +// SetClientState mocks base method. +func (m *MockClientKeeper) SetClientState(ctx types4.Context, clientID string, clientState exported.ClientState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetClientState", ctx, clientID, clientState) +} + +// SetClientState indicates an expected call of SetClientState. +func (mr *MockClientKeeperMockRecorder) SetClientState(ctx, clientID, clientState interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientState", reflect.TypeOf((*MockClientKeeper)(nil).SetClientState), ctx, clientID, clientState) } // MockConnectionKeeper is a mock of ConnectionKeeper interface. @@ -366,10 +377,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types4.Context, connectionID string) (types8.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types4.Context, connectionID string) (types7.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types8.ConnectionEnd) + ret0, _ := ret[0].(types7.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -404,10 +415,10 @@ func (m *MockPortKeeper) EXPECT() *MockPortKeeperMockRecorder { } // BindPort mocks base method. -func (m *MockPortKeeper) BindPort(ctx types4.Context, portID string) *types6.Capability { +func (m *MockPortKeeper) BindPort(ctx types4.Context, portID string) *types5.Capability { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BindPort", ctx, portID) - ret0, _ := ret[0].(*types6.Capability) + ret0, _ := ret[0].(*types5.Capability) return ret0 } @@ -441,7 +452,7 @@ func (m *MockScopedKeeper) EXPECT() *MockScopedKeeperMockRecorder { } // AuthenticateCapability mocks base method. -func (m *MockScopedKeeper) AuthenticateCapability(ctx types4.Context, cap *types6.Capability, name string) bool { +func (m *MockScopedKeeper) AuthenticateCapability(ctx types4.Context, cap *types5.Capability, name string) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AuthenticateCapability", ctx, cap, name) ret0, _ := ret[0].(bool) @@ -455,7 +466,7 @@ func (mr *MockScopedKeeperMockRecorder) AuthenticateCapability(ctx, cap, name in } // ClaimCapability mocks base method. -func (m *MockScopedKeeper) ClaimCapability(ctx types4.Context, cap *types6.Capability, name string) error { +func (m *MockScopedKeeper) ClaimCapability(ctx types4.Context, cap *types5.Capability, name string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ClaimCapability", ctx, cap, name) ret0, _ := ret[0].(error) @@ -469,10 +480,10 @@ func (mr *MockScopedKeeperMockRecorder) ClaimCapability(ctx, cap, name interface } // GetCapability mocks base method. -func (m *MockScopedKeeper) GetCapability(ctx types4.Context, name string) (*types6.Capability, bool) { +func (m *MockScopedKeeper) GetCapability(ctx types4.Context, name string) (*types5.Capability, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCapability", ctx, name) - ret0, _ := ret[0].(*types6.Capability) + ret0, _ := ret[0].(*types5.Capability) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -484,11 +495,11 @@ func (mr *MockScopedKeeperMockRecorder) GetCapability(ctx, name interface{}) *go } // LookupModules mocks base method. -func (m *MockScopedKeeper) LookupModules(ctx types4.Context, name string) ([]string, *types6.Capability, error) { +func (m *MockScopedKeeper) LookupModules(ctx types4.Context, name string) ([]string, *types5.Capability, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LookupModules", ctx, name) ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(*types6.Capability) + ret1, _ := ret[1].(*types5.Capability) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -523,7 +534,7 @@ func (m *MockBTCLightClientKeeper) EXPECT() *MockBTCLightClientKeeperMockRecorde } // GetHeaderByHash mocks base method. -func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx types4.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetHeaderByHash(ctx context.Context, hash *types.BTCHeaderHashBytes) *types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHeaderByHash", ctx, hash) ret0, _ := ret[0].(*types1.BTCHeaderInfo) @@ -537,7 +548,7 @@ func (mr *MockBTCLightClientKeeperMockRecorder) GetHeaderByHash(ctx, hash interf } // GetMainChainFrom mocks base method. -func (m *MockBTCLightClientKeeper) GetMainChainFrom(ctx types4.Context, startHeight uint64) []*types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetMainChainFrom(ctx context.Context, startHeight uint64) []*types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMainChainFrom", ctx, startHeight) ret0, _ := ret[0].([]*types1.BTCHeaderInfo) @@ -551,7 +562,7 @@ func (mr *MockBTCLightClientKeeperMockRecorder) GetMainChainFrom(ctx, startHeigh } // GetMainChainUpTo mocks base method. -func (m *MockBTCLightClientKeeper) GetMainChainUpTo(ctx types4.Context, depth uint64) []*types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetMainChainUpTo(ctx context.Context, depth uint64) []*types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMainChainUpTo", ctx, depth) ret0, _ := ret[0].([]*types1.BTCHeaderInfo) @@ -565,7 +576,7 @@ func (mr *MockBTCLightClientKeeperMockRecorder) GetMainChainUpTo(ctx, depth inte } // GetTipInfo mocks base method. -func (m *MockBTCLightClientKeeper) GetTipInfo(ctx types4.Context) *types1.BTCHeaderInfo { +func (m *MockBTCLightClientKeeper) GetTipInfo(ctx context.Context) *types1.BTCHeaderInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTipInfo", ctx) ret0, _ := ret[0].(*types1.BTCHeaderInfo) @@ -602,7 +613,7 @@ func (m *MockBtcCheckpointKeeper) EXPECT() *MockBtcCheckpointKeeperMockRecorder } // GetBestSubmission mocks base method. -func (m *MockBtcCheckpointKeeper) GetBestSubmission(ctx types4.Context, e uint64) (types0.BtcStatus, *types0.SubmissionKey, error) { +func (m *MockBtcCheckpointKeeper) GetBestSubmission(ctx context.Context, e uint64) (types0.BtcStatus, *types0.SubmissionKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBestSubmission", ctx, e) ret0, _ := ret[0].(types0.BtcStatus) @@ -618,7 +629,7 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetBestSubmission(ctx, e interfac } // GetEpochBestSubmissionBtcInfo mocks base method. -func (m *MockBtcCheckpointKeeper) GetEpochBestSubmissionBtcInfo(ctx types4.Context, ed *types0.EpochData) *types0.SubmissionBtcInfo { +func (m *MockBtcCheckpointKeeper) GetEpochBestSubmissionBtcInfo(ctx context.Context, ed *types0.EpochData) *types0.SubmissionBtcInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochBestSubmissionBtcInfo", ctx, ed) ret0, _ := ret[0].(*types0.SubmissionBtcInfo) @@ -632,7 +643,7 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetEpochBestSubmissionBtcInfo(ctx } // GetEpochData mocks base method. -func (m *MockBtcCheckpointKeeper) GetEpochData(ctx types4.Context, e uint64) *types0.EpochData { +func (m *MockBtcCheckpointKeeper) GetEpochData(ctx context.Context, e uint64) *types0.EpochData { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpochData", ctx, e) ret0, _ := ret[0].(*types0.EpochData) @@ -646,7 +657,7 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetEpochData(ctx, e interface{}) } // GetParams mocks base method. -func (m *MockBtcCheckpointKeeper) GetParams(ctx types4.Context) types0.Params { +func (m *MockBtcCheckpointKeeper) GetParams(ctx context.Context) types0.Params { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetParams", ctx) ret0, _ := ret[0].(types0.Params) @@ -660,7 +671,7 @@ func (mr *MockBtcCheckpointKeeperMockRecorder) GetParams(ctx interface{}) *gomoc } // GetSubmissionData mocks base method. -func (m *MockBtcCheckpointKeeper) GetSubmissionData(ctx types4.Context, sk types0.SubmissionKey) *types0.SubmissionData { +func (m *MockBtcCheckpointKeeper) GetSubmissionData(ctx context.Context, sk types0.SubmissionKey) *types0.SubmissionData { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSubmissionData", ctx, sk) ret0, _ := ret[0].(*types0.SubmissionData) @@ -697,7 +708,7 @@ func (m *MockCheckpointingKeeper) EXPECT() *MockCheckpointingKeeperMockRecorder } // GetBLSPubKeySet mocks base method. -func (m *MockCheckpointingKeeper) GetBLSPubKeySet(ctx types4.Context, epochNumber uint64) ([]*types2.ValidatorWithBlsKey, error) { +func (m *MockCheckpointingKeeper) GetBLSPubKeySet(ctx context.Context, epochNumber uint64) ([]*types2.ValidatorWithBlsKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBLSPubKeySet", ctx, epochNumber) ret0, _ := ret[0].([]*types2.ValidatorWithBlsKey) @@ -712,7 +723,7 @@ func (mr *MockCheckpointingKeeperMockRecorder) GetBLSPubKeySet(ctx, epochNumber } // GetRawCheckpoint mocks base method. -func (m *MockCheckpointingKeeper) GetRawCheckpoint(ctx types4.Context, epochNumber uint64) (*types2.RawCheckpointWithMeta, error) { +func (m *MockCheckpointingKeeper) GetRawCheckpoint(ctx context.Context, epochNumber uint64) (*types2.RawCheckpointWithMeta, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRawCheckpoint", ctx, epochNumber) ret0, _ := ret[0].(*types2.RawCheckpointWithMeta) @@ -750,7 +761,7 @@ func (m *MockEpochingKeeper) EXPECT() *MockEpochingKeeperMockRecorder { } // GetEpoch mocks base method. -func (m *MockEpochingKeeper) GetEpoch(ctx types4.Context) *types3.Epoch { +func (m *MockEpochingKeeper) GetEpoch(ctx context.Context) *types3.Epoch { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEpoch", ctx) ret0, _ := ret[0].(*types3.Epoch) @@ -764,7 +775,7 @@ func (mr *MockEpochingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call } // GetHistoricalEpoch mocks base method. -func (m *MockEpochingKeeper) GetHistoricalEpoch(ctx types4.Context, epochNumber uint64) (*types3.Epoch, error) { +func (m *MockEpochingKeeper) GetHistoricalEpoch(ctx context.Context, epochNumber uint64) (*types3.Epoch, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHistoricalEpoch", ctx, epochNumber) ret0, _ := ret[0].(*types3.Epoch) @@ -779,7 +790,7 @@ func (mr *MockEpochingKeeperMockRecorder) GetHistoricalEpoch(ctx, epochNumber in } // ProveAppHashInEpoch mocks base method. -func (m *MockEpochingKeeper) ProveAppHashInEpoch(ctx types4.Context, height, epochNumber uint64) (*crypto.Proof, error) { +func (m *MockEpochingKeeper) ProveAppHashInEpoch(ctx context.Context, height, epochNumber uint64) (*crypto.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ProveAppHashInEpoch", ctx, height, epochNumber) ret0, _ := ret[0].(*crypto.Proof) @@ -793,31 +804,31 @@ func (mr *MockEpochingKeeperMockRecorder) ProveAppHashInEpoch(ctx, height, epoch return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProveAppHashInEpoch", reflect.TypeOf((*MockEpochingKeeper)(nil).ProveAppHashInEpoch), ctx, height, epochNumber) } -// MockTMClient is a mock of TMClient interface. -type MockTMClient struct { +// MockCometClient is a mock of CometClient interface. +type MockCometClient struct { ctrl *gomock.Controller - recorder *MockTMClientMockRecorder + recorder *MockCometClientMockRecorder } -// MockTMClientMockRecorder is the mock recorder for MockTMClient. -type MockTMClientMockRecorder struct { - mock *MockTMClient +// MockCometClientMockRecorder is the mock recorder for MockCometClient. +type MockCometClientMockRecorder struct { + mock *MockCometClient } -// NewMockTMClient creates a new mock instance. -func NewMockTMClient(ctrl *gomock.Controller) *MockTMClient { - mock := &MockTMClient{ctrl: ctrl} - mock.recorder = &MockTMClientMockRecorder{mock} +// NewMockCometClient creates a new mock instance. +func NewMockCometClient(ctrl *gomock.Controller) *MockCometClient { + mock := &MockCometClient{ctrl: ctrl} + mock.recorder = &MockCometClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTMClient) EXPECT() *MockTMClientMockRecorder { +func (m *MockCometClient) EXPECT() *MockCometClientMockRecorder { return m.recorder } // Tx mocks base method. -func (m *MockTMClient) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) { +func (m *MockCometClient) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Tx", ctx, hash, prove) ret0, _ := ret[0].(*coretypes.ResultTx) @@ -826,7 +837,7 @@ func (m *MockTMClient) Tx(ctx context.Context, hash []byte, prove bool) (*corety } // Tx indicates an expected call of Tx. -func (mr *MockTMClientMockRecorder) Tx(ctx, hash, prove interface{}) *gomock.Call { +func (mr *MockCometClientMockRecorder) Tx(ctx, hash, prove interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tx", reflect.TypeOf((*MockTMClient)(nil).Tx), ctx, hash, prove) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tx", reflect.TypeOf((*MockCometClient)(nil).Tx), ctx, hash, prove) } diff --git a/x/zoneconcierge/types/packet.pb.go b/x/zoneconcierge/types/packet.pb.go index d2f44c32e..6e123e938 100644 --- a/x/zoneconcierge/types/packet.pb.go +++ b/x/zoneconcierge/types/packet.pb.go @@ -122,7 +122,7 @@ type BTCTimestamp struct { // btc_submission_key is position of two BTC txs that include the raw checkpoint of this epoch BtcSubmissionKey *types3.SubmissionKey `protobuf:"bytes,5,opt,name=btc_submission_key,json=btcSubmissionKey,proto3" json:"btc_submission_key,omitempty"` // - // Proofs that the header is finalized + //Proofs that the header is finalized Proof *ProofFinalizedChainInfo `protobuf:"bytes,6,opt,name=proof,proto3" json:"proof,omitempty"` } diff --git a/x/zoneconcierge/types/tx.pb.go b/x/zoneconcierge/types/tx.pb.go index 111da71a1..032e66afa 100644 --- a/x/zoneconcierge/types/tx.pb.go +++ b/x/zoneconcierge/types/tx.pb.go @@ -135,7 +135,7 @@ func init() { func init() { proto.RegisterFile("babylon/zoneconcierge/v1/tx.proto", fileDescriptor_35e2112d987e4e18) } var fileDescriptor_35e2112d987e4e18 = []byte{ - // 324 bytes of a gzipped FileDescriptorProto + // 331 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4c, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0xaf, 0xca, 0xcf, 0x4b, 0x4d, 0xce, 0xcf, 0x4b, 0xce, 0x4c, 0x2d, 0x4a, 0x4f, 0xd5, 0x2f, 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x80, @@ -149,14 +149,14 @@ var fileDescriptor_35e2112d987e4e18 = []byte{ 0x29, 0xca, 0xcc, 0x4b, 0x0f, 0x42, 0x28, 0x15, 0xb2, 0xe3, 0x62, 0x83, 0x98, 0x2d, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0xa4, 0xa0, 0x87, 0xcb, 0x9f, 0x7a, 0x10, 0x9b, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xea, 0xb2, 0xe2, 0x6b, 0x7a, 0xbe, 0x41, 0x0b, 0x61, 0x9e, 0x92, 0x24, - 0x97, 0x38, 0x9a, 0xd3, 0x82, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x8d, 0x8a, 0xb9, 0x98, + 0x97, 0x38, 0x9a, 0xd3, 0x82, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x8d, 0xaa, 0xb8, 0x98, 0x7d, 0x8b, 0xd3, 0x85, 0x72, 0xb8, 0x78, 0x50, 0x5c, 0xae, 0x89, 0xdb, 0x46, 0x34, 0x93, 0xa4, - 0x0c, 0x89, 0x56, 0x0a, 0xb3, 0xd4, 0xc9, 0xff, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, - 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, - 0x18, 0xa2, 0x4c, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0xa1, 0xc6, - 0x26, 0x67, 0x24, 0x66, 0xe6, 0xc1, 0x38, 0xfa, 0x15, 0x68, 0xd1, 0x50, 0x52, 0x59, 0x90, 0x5a, - 0x9c, 0xc4, 0x06, 0x8e, 0x03, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5a, 0xdf, 0x0c, 0x09, - 0x33, 0x02, 0x00, 0x00, + 0x0c, 0x89, 0x56, 0x0a, 0xb3, 0x54, 0x8a, 0xb5, 0xe1, 0xf9, 0x06, 0x2d, 0x46, 0x27, 0xff, 0x13, + 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, + 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, + 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, 0x9a, 0x9e, 0x9c, 0x91, 0x98, 0x99, 0x07, 0xe3, 0xe8, 0x57, + 0xa0, 0xc5, 0x46, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0x38, 0x2a, 0x8c, 0x01, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x05, 0xf5, 0x03, 0x87, 0x3a, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/zoneconcierge/types/zoneconcierge.go b/x/zoneconcierge/types/zoneconcierge.go index 230715dd0..9fe7f95a5 100644 --- a/x/zoneconcierge/types/zoneconcierge.go +++ b/x/zoneconcierge/types/zoneconcierge.go @@ -42,7 +42,7 @@ func (ih *IndexedHeader) Equal(ih2 *IndexedHeader) bool { return false } else if ih.Height != ih2.Height { return false - } else if !bytes.Equal(ih.BabylonHeader.LastCommitHash, ih2.BabylonHeader.LastCommitHash) { + } else if !bytes.Equal(ih.BabylonHeader.AppHash, ih2.BabylonHeader.AppHash) { return false } else if ih.BabylonEpoch != ih2.BabylonEpoch { return false From 9cf49a9a38e441ca95d170fa519cf6e9dd1f9d7e Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 22 Nov 2023 16:39:38 +0400 Subject: [PATCH 108/202] fix: Use EncodeAddress instead of String for converting BTC addresses (#116) --- test/e2e/btc_staking_e2e_test.go | 4 ++-- x/btcstaking/keeper/grpc_query_test.go | 8 ++++---- x/btcstaking/keeper/incentive_test.go | 2 +- x/btcstaking/keeper/msg_server_test.go | 16 ++++++++-------- x/btcstaking/keeper/voting_power_table_test.go | 8 ++++---- x/btcstaking/types/btc_slashing_tx_test.go | 2 +- x/btcstaking/types/btcstaking_test.go | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 3a7259f81..9e21df8dc 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -121,7 +121,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { 1, stakingTimeBlocks, stakingValue, - params.SlashingAddress, changeAddress.String(), + params.SlashingAddress, changeAddress.EncodeAddress(), params.SlashingRate, ) @@ -465,7 +465,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { wire.NewOutPoint(stakingTxChainHash, uint32(activeDel.StakingOutputIdx)), initialization.BabylonBtcFinalizationPeriod+1, stakingValue-fee, - params.SlashingAddress, changeAddress.String(), + params.SlashingAddress, changeAddress.EncodeAddress(), params.SlashingRate, ) s.NoError(err) diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index fcc3ae25a..5bef99a2a 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -214,7 +214,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, ) @@ -320,7 +320,7 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, ) @@ -500,7 +500,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, 10000, slashingRate, ) @@ -606,7 +606,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, ) diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 595b9e4e3..9f2853bea 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -73,7 +73,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, ) diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index bbae6cc4a..6655283b2 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -100,7 +100,7 @@ func getCovenantInfo(t *testing.T, err = bsKeeper.SetParams(sdkCtx, types.Params{ CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, CovenantQuorum: 1, - SlashingAddress: slashingAddress.String(), + SlashingAddress: slashingAddress.EncodeAddress(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), @@ -423,7 +423,7 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { net, validatorPK, covenantPK, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, 1000, ) @@ -464,7 +464,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { err = bsKeeper.SetParams(ctx, types.Params{ CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, CovenantQuorum: 1, - SlashingAddress: slashingAddress.String(), + SlashingAddress: slashingAddress.EncodeAddress(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), @@ -494,7 +494,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { 1, stakingTimeBlocks, stakingValue, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, ) // get msgTx @@ -579,7 +579,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { net, validatorPK, covenantPK, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, 1000, ) @@ -598,7 +598,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { delSK, validatorPK, covenantPK, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, ) @@ -643,7 +643,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { net, validatorPK, covenantPK, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, 1000, ) @@ -662,7 +662,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { delSK, validatorPK, covenantPK, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), bsKeeper.GetParams(ctx).SlashingRate, ) diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 966a0a0c0..d86aa4e04 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -70,7 +70,7 @@ func FuzzVotingPowerTable(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, ) @@ -238,7 +238,7 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, ) @@ -342,7 +342,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, ) @@ -390,7 +390,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { delSK, []*btcec.PrivateKey{covenantSK}, 1, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, ) diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index f280a47a7..2e2330d29 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -54,7 +54,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { 1, stakingTimeBlocks, stakingValue, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), slashingRate, ) diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index a0ae8fee4..cfc2a0ba0 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -49,7 +49,7 @@ func FuzzStakingTx(f *testing.F) { 1, stakingTimeBlocks, stakingValue, - slashingAddress.String(), changeAddress.String(), + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), slashingRate, ) require.NoError(t, err) From c31e470a8b0dff36e6ea26e47d3f151e6ffc7ff3 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 23 Nov 2023 14:37:44 +1100 Subject: [PATCH 109/202] btcstaking: accomodate message formats and handlers for new staking tx (#117) --- go.mod | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 23 +- proto/babylon/btcstaking/v1/tx.proto | 35 +- .../babylon/checkpointing/v1/checkpoint.proto | 2 +- proto/babylon/epoching/v1/epoching.proto | 2 +- proto/babylon/finality/v1/finality.proto | 12 +- proto/babylon/finality/v1/tx.proto | 4 +- .../zoneconcierge/v1/zoneconcierge.proto | 6 +- test/e2e/btc_staking_e2e_test.go | 15 +- .../configurer/chain/commands_btcstaking.go | 18 +- x/btcstaking/client/cli/tx.go | 45 +-- x/btcstaking/keeper/btc_delegators.go | 14 +- x/btcstaking/keeper/grpc_query.go | 8 +- x/btcstaking/keeper/grpc_query_test.go | 9 +- x/btcstaking/keeper/incentive.go | 4 +- x/btcstaking/keeper/msg_server.go | 73 ++-- x/btcstaking/keeper/msg_server_test.go | 24 +- x/btcstaking/keeper/voting_power_table.go | 4 +- x/btcstaking/types/btc_delegations.go | 4 +- x/btcstaking/types/btcstaking.go | 46 ++- x/btcstaking/types/btcstaking.pb.go | 258 +++++++------ x/btcstaking/types/btcstaking_test.go | 5 +- x/btcstaking/types/errors.go | 25 +- x/btcstaking/types/incentive.go | 4 +- x/btcstaking/types/msg.go | 18 +- x/btcstaking/types/params.go | 9 + x/btcstaking/types/tx.pb.go | 344 ++++++------------ x/checkpointing/client/cli/tx.go | 9 +- x/checkpointing/keeper/msg_server_test.go | 2 +- x/checkpointing/types/checkpoint.pb.go | 2 +- x/checkpointing/types/utils.go | 2 +- x/epoching/keeper/epochs.go | 3 +- x/epoching/types/epoching.pb.go | 2 +- x/finality/client/cli/tx.go | 10 +- x/finality/types/finality.pb.go | 69 ++-- x/finality/types/tx.pb.go | 86 ++--- x/incentive/types/params.pb.go | 2 +- x/zoneconcierge/keeper/proof_epoch_sealed.go | 12 +- .../keeper/proof_epoch_sealed_test.go | 2 +- x/zoneconcierge/types/zoneconcierge.pb.go | 6 +- 40 files changed, 577 insertions(+), 643 deletions(-) diff --git a/go.mod b/go.mod index 63d5dbaef..62bbabfd8 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( cosmossdk.io/api v0.7.2 cosmossdk.io/client/v2 v2.0.0-beta.1 cosmossdk.io/core v0.11.0 + cosmossdk.io/depinject v1.0.0-alpha.4 cosmossdk.io/errors v1.0.0 cosmossdk.io/log v1.2.1 cosmossdk.io/math v1.2.0 @@ -134,7 +135,6 @@ require ( cloud.google.com/go/iam v1.1.3 // indirect cloud.google.com/go/storage v1.30.1 // indirect cosmossdk.io/collections v0.4.0 // indirect - cosmossdk.io/depinject v1.0.0-alpha.4 // indirect cosmossdk.io/x/nft v0.1.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/DataDog/zstd v1.5.5 // indirect diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 7853a808b..6759352e7 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -93,6 +93,7 @@ message BTCDelegation { // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples + // over each restaked BTC validator (i.e., a matrix of tuples) bytes covenant_sig = 12 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // if this object is present it menans that staker requested undelegation, and whole @@ -121,14 +122,12 @@ message BTCUndelegation { // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon - // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples + // TODO: change to a matrix of (covenant PK, covenant adaptor signature) tuples bytes covenant_slashing_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // covenant_unbonding_sig is the signature on the unbonding tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon and after - // validator sig will be provided by validator - // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - bytes covenant_unbonding_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // covenant_unbonding_sig_list is the list of signatures on the unbonding tx + // by covenant members + // It must be provided after processing undelagate message by Babylon + repeated SignatureInfo covenant_unbonding_sig_list = 6; } @@ -139,11 +138,11 @@ message BTCUndelegationInfo { // than staking output. bytes unbonding_tx = 1; - // covenant_unbonding_sig is the signature on the unbonding tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // it will be nil if covenant didn't sign it yet - // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - bytes covenant_unbonding_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // covenant_unbonding_sig_list is the list of signatures on the unbonding tx + // by covenant members + // It must be provided after processing undelagate message by Babylon and after + // validator sig will be provided by validator + repeated SignatureInfo covenant_unbonding_sig_list = 2; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 66a028e2f..3f0d96634 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -65,8 +65,8 @@ message MsgCreateBTCDelegation { cosmos.crypto.secp256k1.PubKey babylon_pk = 2; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // staker_btc_pk is the Bitcoin secp256k1 PK of the BTC staker - bytes staker_btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // btc_pk is the Bitcoin secp256k1 PK of the BTC delegator + bytes btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one // validator pk it means that delegation is re-staked repeated bytes val_btc_pk_list = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; @@ -116,18 +116,15 @@ message MsgAddCovenantSig { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // pk is the BTC public key of the covenant member + bytes pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - string staking_tx_hash = 4; + // It uniquely identifies a BTC delegation + string staking_tx_hash = 3; // sig is the signature of the covenant // the signature follows encoding in BIP-340 spec - bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // TODO: change to adaptor signature + bytes sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddCovenantSigResponse {} @@ -156,22 +153,18 @@ message MsgAddCovenantUnbondingSigs { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - bytes val_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - bytes del_pk = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // pk is the BTC public key of the covenant member + bytes pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - string staking_tx_hash = 4; + string staking_tx_hash = 3; // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec - bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes unbonding_tx_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon // the signature follows encoding in BIP-340 spec - bytes slashing_unbonding_tx_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - + // TODO: change to adaptor signature + bytes slashing_unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddCovenantUnbondingSigsResponse {} diff --git a/proto/babylon/checkpointing/v1/checkpoint.proto b/proto/babylon/checkpointing/v1/checkpoint.proto index 019904a88..200487498 100644 --- a/proto/babylon/checkpointing/v1/checkpoint.proto +++ b/proto/babylon/checkpointing/v1/checkpoint.proto @@ -80,7 +80,7 @@ message BlsSig { // epoch_num defines the epoch number that the BLS sig is signed on uint64 epoch_num = 1; - // last_commit_hash defines the 'AppHash' that the BLS sig is signed on + // app_hash defines the 'AppHash' that the BLS sig is signed on bytes app_hash = 2 [ (gogoproto.customtype) = "AppHash" ]; bytes bls_sig = 3 [ (gogoproto.customtype) = diff --git a/proto/babylon/epoching/v1/epoching.proto b/proto/babylon/epoching/v1/epoching.proto index 9b56c0bca..66c7faf51 100644 --- a/proto/babylon/epoching/v1/epoching.proto +++ b/proto/babylon/epoching/v1/epoching.proto @@ -23,7 +23,7 @@ message Epoch { // It will be used for proving a block is in an epoch bytes app_hash_root = 5; // sealer_header is the 2nd header of the next epoch - // This validator set has generated a BLS multisig on `last_commit_hash` of + // This validator set has generated a BLS multisig on `app_hash` of // the sealer header tendermint.types.Header sealer_header = 6; } diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index 2886f04a9..e03a89ba9 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -9,8 +9,8 @@ import "gogoproto/gogo.proto"; message IndexedBlock { // height is the height of the block uint64 height = 1; - // last_commit_hash is the last_commit_hash of the block - bytes last_commit_hash = 2; + // app_hash is the AppHash of the block + bytes app_hash = 2; // finalized indicates whether the IndexedBlock is finalised by 2/3 // BTC validators or not bool finalized = 3; @@ -25,10 +25,10 @@ message Evidence { uint64 block_height = 2; // pub_rand is the public randomness the BTC validator has committed to bytes pub_rand = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; - // canonical_last_commit_hash is the last_commit_hash of the canonical block - bytes canonical_last_commit_hash = 4; - // fork_last_commit_hash is the last_commit_hash of the fork block - bytes fork_last_commit_hash = 5; + // canonical_app_hash is the AppHash of the canonical block + bytes canonical_app_hash = 4; + // fork_app_hash is the AppHash of the fork block + bytes fork_app_hash = 5; // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index d81e7ed45..c3f714c87 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -30,8 +30,8 @@ message MsgAddFinalitySig { bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // block_height is the height of the voted block uint64 block_height = 3; - // block_last_commit_hash is the last_commit_hash of the voted block - bytes block_last_commit_hash = 4; + // block_app_hash is the AppHash of the voted block + bytes block_app_hash = 4; // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` diff --git a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto index 0c24a491b..ae05c3490 100644 --- a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto +++ b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto @@ -94,14 +94,14 @@ message FinalizedChainInfo { // - Metadata of this epoch, which includes the sealer header // - Raw checkpoint of this epoch // The verifier can perform the following verification rules: -// - The raw checkpoint's `last_commit_hash` is same as in the sealer header +// - The raw checkpoint's `app_hash` is same as in the sealer header // - More than 1/3 (in voting power) validators in the validator set of this -// epoch have signed `last_commit_hash` of the sealer header +// epoch have signed `app_hash` of the sealer header // - The epoch medatata is committed to the `app_hash` of the sealer header // - The validator set is committed to the `app_hash` of the sealer header message ProofEpochSealed { // validator_set is the validator set of the sealed epoch - // This validator set has generated a BLS multisig on `last_commit_hash` of + // This validator set has generated a BLS multisig on `app_hash` of // the sealer header repeated babylon.checkpointing.v1.ValidatorWithBlsKey validator_set = 1; // proof_epoch_info is the Merkle proof that the epoch's metadata is committed diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 9e21df8dc..2730ab1f2 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -238,7 +238,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { net, ) s.NoError(err) - nonValidatorNode.AddCovenantSig(btcVal.BtcPk, bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, covenantSig) + nonValidatorNode.AddCovenantSig(¶ms.CovenantPks[0], stakingTxHash, covenantSig) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() @@ -264,8 +264,8 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { s.NoError(err) activeBTCVals := nonValidatorNode.QueryActiveBTCValidatorsAtHeight(activatedHeight) s.Len(activeBTCVals, 1) - s.Equal(activeBTCVals[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) - s.Equal(activeBTCVals[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod)) + s.Equal(activeBTCVals[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) + s.Equal(activeBTCVals[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) } // Test2CommitPublicRandomnessAndSubmitFinalitySignature is an end-to-end @@ -519,7 +519,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegation := delegatorDelegations.Dels[0] s.NotNil(delegation.BtcUndelegation) - s.Nil(delegation.BtcUndelegation.CovenantUnbondingSig) + s.Empty(delegation.BtcUndelegation.CovenantUnbondingSigList) s.Nil(delegation.BtcUndelegation.CovenantSlashingSig) // params for covenantPk and slashing address @@ -593,8 +593,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { ) s.NoError(err) nonValidatorNode.AddCovenantUnbondingSigs( - btcVal.BtcPk, - bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), stakingTxHash, &covenantUnbondingSig, covenantSlashingSig) + ¶ms.CovenantPks[0], stakingTxHash, &covenantUnbondingSig, covenantSlashingSig) nonValidatorNode.WaitForNextBlock() nonValidatorNode.WaitForNextBlock() @@ -603,12 +602,12 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { s.Len(allDelegationsWithSigs, 1) delegationWithSigs := allDelegationsWithSigs[0].Dels[0] s.NotNil(delegationWithSigs.BtcUndelegation) - s.NotNil(delegationWithSigs.BtcUndelegation.CovenantUnbondingSig) + s.NotEmpty(delegationWithSigs.BtcUndelegation.CovenantUnbondingSigList) s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSig) btcTip, err := nonValidatorNode.QueryTip() s.NoError(err) s.Equal( bstypes.BTCDelegationStatus_UNBONDED, - delegationWithSigs.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod), + delegationWithSigs.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum), ) } diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 68ebbc441..1f112ed6d 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -2,9 +2,10 @@ package chain import ( "encoding/hex" + "strconv" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" - "strconv" "cosmossdk.io/math" sdkmath "cosmossdk.io/math" @@ -80,14 +81,13 @@ func (n *NodeConfig) CreateBTCDelegation( n.LogActionF("successfully created BTC delegation") } -func (n *NodeConfig) AddCovenantSig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { +func (n *NodeConfig) AddCovenantSig(covPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { n.LogActionF("adding covenant signature") - valPKHex := valPK.MarshalHex() - delPKHex := delPK.MarshalHex() + covPKHex := covPK.MarshalHex() sigHex := sig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", covPKHex, stakingTxHash, sigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added covenant sig") @@ -181,19 +181,17 @@ func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bb } func (n *NodeConfig) AddCovenantUnbondingSigs( - valPK *bbn.BIP340PubKey, - delPK *bbn.BIP340PubKey, + covPK *bbn.BIP340PubKey, stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, slashUnbondingTxSig *bbn.BIP340Signature) { n.LogActionF("adding validator signature") - valPKHex := valPK.MarshalHex() - delPKHex := delPK.MarshalHex() + covPKHex := covPK.MarshalHex() unbondingTxSigHex := unbondingTxSig.ToHexStr() slashUnbondingTxSigHex := slashUnbondingTxSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", valPKHex, delPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", covPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added covenant unbonding sigs") diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 6dedadee8..24518d4bb 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -251,7 +251,7 @@ func NewCreateBTCDelegationCmd() *cobra.Command { msg := types.MsgCreateBTCDelegation{ Signer: clientCtx.FromAddress.String(), BabylonPk: &babylonPK, - StakerBtcPk: btcPK, + BtcPk: btcPK, ValBtcPkList: []bbn.BIP340PubKey{*valPK}, Pop: pop, StakingTime: uint32(stakingTime), @@ -272,8 +272,8 @@ func NewCreateBTCDelegationCmd() *cobra.Command { func NewAddCovenantSigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-covenant-sig [val_pk] [del_pk] [staking_tx_hash] [sig]", - Args: cobra.ExactArgs(4), + Use: "add-covenant-sig [covenant_pk] [staking_tx_hash] [sig]", + Args: cobra.ExactArgs(3), Short: "Add a covenant signature", Long: strings.TrimSpace( `Add a covenant signature.`, // TODO: example @@ -284,31 +284,23 @@ func NewAddCovenantSigCmd() *cobra.Command { return err } - // get validator PK - valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) - if err != nil { - return err - } - - // get delegator PK - delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { return err } // get staking tx hash - stakingTxHash := args[2] + stakingTxHash := args[1] // get covenant sigature - sig, err := bbn.NewBIP340SignatureFromHex(args[3]) + sig, err := bbn.NewBIP340SignatureFromHex(args[2]) if err != nil { return err } msg := types.MsgAddCovenantSig{ Signer: clientCtx.FromAddress.String(), - ValPk: valPK, - DelPk: delPK, + Pk: covPK, StakingTxHash: stakingTxHash, Sig: sig, } @@ -387,8 +379,8 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { func NewAddCovenantUnbondingSigsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-covenant-unbonding-sigs [val_pk] [del_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", - Args: cobra.ExactArgs(5), + Use: "add-covenant-unbonding-sigs [covenant_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", + Args: cobra.ExactArgs(4), Short: "Add covenant signatures for unbonding tx and slash unbonding tx", RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -396,37 +388,30 @@ func NewAddCovenantUnbondingSigsCmd() *cobra.Command { return err } - // get validator PK - valPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) - if err != nil { - return err - } - - // get delegator PK - delPK, err := bbn.NewBIP340PubKeyFromHex(args[1]) + // get covenant PK + covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { return err } // get staking tx hash - stakingTxHash := args[2] + stakingTxHash := args[1] // get covenant sigature for unbonding tx - unbondingSig, err := bbn.NewBIP340SignatureFromHex(args[3]) + unbondingSig, err := bbn.NewBIP340SignatureFromHex(args[2]) if err != nil { return err } // get covenant sigature for slash unbonding tx - slashUnbondingSig, err := bbn.NewBIP340SignatureFromHex(args[4]) + slashUnbondingSig, err := bbn.NewBIP340SignatureFromHex(args[3]) if err != nil { return err } msg := types.MsgAddCovenantUnbondingSigs{ Signer: clientCtx.FromAddress.String(), - ValPk: valPK, - DelPk: delPK, + Pk: covPK, StakingTxHash: stakingTxHash, UnbondingTxSig: unbondingSig, SlashingUnbondingTxSig: slashUnbondingSig, diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index f3db189c9..e6a62b6d2 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" @@ -78,7 +79,11 @@ func (k Keeper) updateBTCDelegation( // AddCovenantSigToBTCDelegation adds a given covenant sig to a BTC delegation // with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddCovenantSigToBTCDelegation(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey, stakingTxHash string, covenantSig *bbn.BIP340Signature) error { +func (k Keeper) AddCovenantSigToBTCDelegation( + ctx context.Context, + stakingTxHash string, + covenantSig *bbn.BIP340Signature, +) error { addCovenantSig := func(btcDel *types.BTCDelegation) error { if btcDel.CovenantSig != nil { return fmt.Errorf("the BTC delegation with staking tx hash %s already has a covenant signature", stakingTxHash) @@ -109,7 +114,7 @@ func (k Keeper) AddUndelegationToBTCDelegation( func (k Keeper) AddCovenantSigsToUndelegation( ctx context.Context, stakingTxHash string, - unbondingTxSig *bbn.BIP340Signature, + unbondingTxSigInfo *types.SignatureInfo, slashUnbondingTxSig *bbn.BIP340Signature, ) error { addCovenantSigs := func(btcDel *types.BTCDelegation) error { @@ -117,11 +122,12 @@ func (k Keeper) AddCovenantSigsToUndelegation( return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) } - if btcDel.BtcUndelegation.CovenantUnbondingSig != nil || btcDel.BtcUndelegation.CovenantSlashingSig != nil { + // TODO: ensure the given CovenantUnbondingSig does not exist in the BTC undelegation yet + if btcDel.BtcUndelegation.CovenantUnbondingSigList != nil || btcDel.BtcUndelegation.CovenantSlashingSig != nil { return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid covenant signatures", stakingTxHash) } - btcDel.BtcUndelegation.CovenantUnbondingSig = unbondingTxSig + btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingTxSigInfo) btcDel.BtcUndelegation.CovenantSlashingSig = slashUnbondingTxSig return nil } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index e56466594..578f73218 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -74,6 +74,7 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio } sdkCtx := sdk.UnwrapSDKContext(ctx) + covenantQuorum := k.GetParams(ctx).CovenantQuorum // get current BTC height btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) @@ -90,7 +91,7 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio k.cdc.MustUnmarshal(value, &btcDel) // hit if the queried status is ANY or matches the BTC delegation status - if req.Status == types.BTCDelegationStatus_ANY || btcDel.GetStatus(btcTipHeight, wValue) == req.Status { + if req.Status == types.BTCDelegationStatus_ANY || btcDel.GetStatus(btcTipHeight, wValue, covenantQuorum) == req.Status { if accumulate { btcDels = append(btcDels, &btcDel) } @@ -276,14 +277,15 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation isActive := btcDel.GetStatus( currentTip.Height, currentWValue, + k.GetParams(ctx).CovenantQuorum, ) == types.BTCDelegationStatus_ACTIVE // get its undelegation info var undelegationInfo *types.BTCUndelegationInfo if btcDel.BtcUndelegation != nil { undelegationInfo = &types.BTCUndelegationInfo{ - UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, - CovenantUnbondingSig: btcDel.BtcUndelegation.CovenantUnbondingSig, + UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, + CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, } } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 5bef99a2a..36cb33223 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -1,11 +1,12 @@ package keeper_test import ( - sdkmath "cosmossdk.io/math" "errors" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -281,7 +282,8 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) + covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + covBTCPK := bbn.NewBIP340PubKeyFromBTCPK(covenantPK) require.NoError(t, err) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) @@ -332,7 +334,8 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { if datagen.RandomInt(r, 2) == 1 { // these BTC delegations are unbonded - btcDel.BtcUndelegation.CovenantUnbondingSig = btcDel.CovenantSig + unbondingSigInfo := types.NewSignatureInfo(covBTCPK, btcDel.CovenantSig) + btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingSigInfo) btcDel.BtcUndelegation.CovenantSlashingSig = btcDel.CovenantSig } else { // these BTC delegations are unbonding diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go index e69a55a3c..af8408a0f 100644 --- a/x/btcstaking/keeper/incentive.go +++ b/x/btcstaking/keeper/incentive.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -11,6 +12,7 @@ import ( ) func (k Keeper) RecordRewardDistCache(ctx context.Context) { + covenantQuorum := k.GetParams(ctx).CovenantQuorum // get BTC tip height and w, which are necessary for determining a BTC // delegation's voting power btcTipHeight, err := k.GetCurrentBTCHeight(ctx) @@ -57,7 +59,7 @@ func (k Keeper) RecordRewardDistCache(ctx context.Context) { panic(err) // only programming error is possible } btcDel := k.getBTCDelegation(ctx, *stakingTxHash) - btcValDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue) + btcValDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue, covenantQuorum) } } btcDelIter.Close() diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index d5e436f7d..1ee135520 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -187,7 +187,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout // 1. verify proof of possession - if err := req.Pop.Verify(req.BabylonPk, req.StakerBtcPk, ms.btcNet); err != nil { + if err := req.Pop.Verify(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { return nil, types.ErrInvalidProofOfPossession.Wrapf("error while validating proof of posession: %v", err) } @@ -241,7 +241,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } si, err := btcstaking.BuildStakingInfo( - req.StakerBtcPk.MustToBTCPK(), + req.BtcPk.MustToBTCPK(), validatorKeys, covenantKeys, params.CovenantQuorum, @@ -324,7 +324,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre stakingOutput.PkScript, stakingOutput.Value, slashingPathInfo.RevealedLeaf.Script, - req.StakerBtcPk.MustToBTCPK(), + req.BtcPk.MustToBTCPK(), req.DelegatorSig, ) @@ -338,7 +338,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // and 2) it receives a covenant signature newBTCDel := &types.BTCDelegation{ BabylonPk: req.BabylonPk, - BtcPk: req.StakerBtcPk, + BtcPk: req.BtcPk, Pop: req.Pop, ValBtcPkList: req.ValBtcPkList, StartHeight: startHeight, @@ -411,7 +411,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // 6. Check delegation state. Only active delegations can be unbonded. btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := del.GetStatus(btcTip.Height, wValue) + status := del.GetStatus(btcTip.Height, wValue, params.CovenantQuorum) if status != types.BTCDelegationStatus_ACTIVE { return nil, types.ErrInvalidDelegationState.Wrapf("current status: %v, want: %s", status.String(), types.BTCDelegationStatus_ACTIVE.String()) @@ -515,8 +515,8 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // Jurry needs to provide two sigs: // - one for unbonding tx // - one for slashing tx of unbonding tx - CovenantSlashingSig: nil, - CovenantUnbondingSig: nil, + CovenantSlashingSig: nil, + CovenantUnbondingSigList: nil, } if err := ms.AddUndelegationToBTCDelegation( @@ -543,36 +543,37 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // AddCovenantSig adds a signature from covenant to a BTC delegation func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCovenantSig) (*types.MsgAddCovenantSigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + covenantQuorum := ms.GetParams(ctx).CovenantQuorum // ensure BTC delegation exists btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) - if err != nil { return nil, err } - if btcDel.HasCovenantSig() { - return nil, types.ErrDuplicatedCovenantSig - } stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) stakingOutput := stakingTx.TxOut[stakingOutputIdx] - // TODO: covenant PK will be a field in the message. Then the verification will be - // that the given covenant PK has to be one of the covenant PKs in the parameter - covenantPK, err := ms.GetParams(ctx).CovenantPks[0].ToBTCPK() - if err != nil { - // failing to cast a verified covenant PK a programming error - panic(fmt.Errorf("failed to cast a verified covenant public key")) + // ensure that the given covenant PK is in the parameter + if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { + return nil, types.ErrInvalidCovenantPK } - spendInfo, err := ms.stakingInfoFromDelegation(ctx, btcDel) + // ensure the BTC delegation hasn't reached a quorum yet + if btcDel.HasCovenantQuorum(covenantQuorum) { + return nil, types.ErrCovenantQuorumAlreadyReached + } + // ensure that this covenant member has not signed the delegation yet + if btcDel.IsSignedByCovMember(req.Pk) { + return nil, types.ErrDuplicatedCovenantSig + } + spendInfo, err := ms.stakingInfoFromDelegation(ctx, btcDel) if err != nil { panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) } slashingPathInfo, err := spendInfo.SlashingPathSpendInfo() - if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error @@ -584,7 +585,7 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven stakingOutput.PkScript, stakingOutput.Value, slashingPathInfo.RevealedLeaf.Script, - covenantPK, + req.Pk.MustToBTCPK(), req.Sig, ) @@ -593,7 +594,7 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven } // all good, add signature to BTC delegation and set it back to KVStore - if err := ms.AddCovenantSigToBTCDelegation(ctx, req.ValPk, req.DelPk, req.StakingTxHash, req.Sig); err != nil { + if err := ms.AddCovenantSigToBTCDelegation(ctx, req.StakingTxHash, req.Sig); err != nil { panic("failed to set BTC delegation that has passed verification") } @@ -607,9 +608,11 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven func (ms msgServer) AddCovenantUnbondingSigs( goCtx context.Context, - req *types.MsgAddCovenantUnbondingSigs) (*types.MsgAddCovenantUnbondingSigsResponse, error) { + req *types.MsgAddCovenantUnbondingSigs, +) (*types.MsgAddCovenantUnbondingSigsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + covenantQuorum := ms.GetParams(ctx).CovenantQuorum // 1. Check that delegation even exists for provided params btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) @@ -618,15 +621,19 @@ func (ms msgServer) AddCovenantUnbondingSigs( } btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := btcDel.GetStatus(btcTip.Height, wValue) + status := btcDel.GetStatus(btcTip.Height, wValue, covenantQuorum) // 2. Check that we are in proper status if status != types.BTCDelegationStatus_UNBONDING { return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) } - // 3. Check that we did not recevie covenant signature yet - if btcDel.BtcUndelegation.HasCovenantSigs() { + // 3. Check that we did not receive a quorum number of covenant signatures yet + if btcDel.BtcUndelegation.HasCovenantQuorum(covenantQuorum) { + return nil, types.ErrCovenantQuorumAlreadyReached + } + // ensure that this covenant member hasn't signed yet + if btcDel.BtcUndelegation.IsSignedByCovMember(req.Pk) { return nil, types.ErrDuplicatedCovenantSig } @@ -634,12 +641,9 @@ func (ms msgServer) AddCovenantUnbondingSigs( stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) stakingOutput := stakingTx.TxOut[stakingOutputIdx] - // TODO: covenant PK will be a field in the message. Then the verification will be - // that the given covenant PK has to be one of the covenant PKs in the parameter - covenantPK, err := ms.GetParams(ctx).CovenantPks[0].ToBTCPK() - if err != nil { - // failing to cast a verified covenant PK is a programming error - panic(fmt.Errorf("failed to cast a verified covenant public key")) + // ensure that the given covenant PK is in the parameter + if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { + return nil, types.ErrInvalidCovenantPK } unbondingTxMsg, err := types.ParseBtcTx(btcDel.BtcUndelegation.UnbondingTx) @@ -665,7 +669,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( stakingOutput.PkScript, stakingOutput.Value, unbondingPathInfo.RevealedLeaf.Script, - covenantPK, + req.Pk.MustToBTCPK(), *req.UnbondingTxSig, ); err != nil { return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) @@ -693,7 +697,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( unbondingOutput.PkScript, unbondingOutput.Value, slashingPathInfo.RevealedLeaf.Script, - covenantPK, + req.Pk.MustToBTCPK(), req.SlashingUnbondingTxSig, ) if err != nil { @@ -701,10 +705,11 @@ func (ms msgServer) AddCovenantUnbondingSigs( } // all good, add signature to BTC undelegation and set it back to KVStore + unbondingSigInfo := types.NewSignatureInfo(req.Pk, req.UnbondingTxSig) if err := ms.AddCovenantSigsToUndelegation( ctx, req.StakingTxHash, - req.UnbondingTxSig, + unbondingSigInfo, req.SlashingUnbondingTxSig); err != nil { panic("failed to set BTC delegation that has passed verification") } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 6655283b2..d4c38ca9a 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -2,12 +2,13 @@ package keeper_test import ( "context" - sdkmath "cosmossdk.io/math" "errors" "math/rand" "testing" "time" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" @@ -212,7 +213,7 @@ func createDelegation( msgCreateBTCDel := &types.MsgCreateBTCDelegation{ Signer: signer, BabylonPk: delBabylonPK.(*secp256k1.PubKey), - StakerBtcPk: stPk, + BtcPk: stPk, ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, Pop: pop, StakingTime: uint32(stakingTimeBlocks), @@ -269,10 +270,8 @@ func createCovenantSig( ) require.NoError(t, err) msgAddCovenantSig := &types.MsgAddCovenantSig{ - Signer: msgCreateBTCDel.Signer, - // TODO: this will be removed after all - ValPk: &delegation.ValBtcPkList[0], - DelPk: delegation.BtcPk, + Signer: msgCreateBTCDel.Signer, + Pk: bbn.NewBIP340PubKeyFromBTCPK(cPk), StakingTxHash: stakingTxHash, Sig: covenantSig, } @@ -285,7 +284,7 @@ func createCovenantSig( actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) require.NoError(t, err) require.Equal(t, actualDelWithCovenantSig.CovenantSig.MustMarshal(), covenantSig.MustMarshal()) - require.True(t, actualDelWithCovenantSig.HasCovenantSig()) + require.True(t, actualDelWithCovenantSig.HasCovenantQuorum(bsKeeper.GetParams(sdkCtx).CovenantQuorum)) } func getDelegationAndCheckValues( @@ -309,7 +308,7 @@ func getDelegationAndCheckValues( err = actualDel.ValidateBasic() require.NoError(t, err) // delegation is not activated by covenant yet - require.False(t, actualDel.HasCovenantSig()) + require.False(t, actualDel.HasCovenantQuorum(bsKeeper.GetParams(sdkCtx).CovenantQuorum)) return actualDel } @@ -537,7 +536,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { Signer: signer, BabylonPk: delBabylonPK.(*secp256k1.PubKey), ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, - StakerBtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), + BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), Pop: pop, StakingTime: uint32(stakingTimeBlocks), StakingValue: stakingValue, @@ -610,7 +609,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSigList) }) } @@ -730,8 +729,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ Signer: datagen.GenRandomAccount().Address, - ValPk: &del.ValBtcPkList[0], - DelPk: del.BtcPk, + Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), StakingTxHash: stakingTxHash, UnbondingTxSig: &covenantUnbondingSig, SlashingUnbondingTxSig: slashUnbondingTxSignatureCovenant, @@ -745,6 +743,6 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSig) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList) }) } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 9eaf340f2..a9b326b24 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" @@ -15,6 +16,7 @@ import ( // and saves the power table to KVStore // triggered upon each EndBlock func (k Keeper) RecordVotingPowerTable(ctx context.Context) { + covenantQuorum := k.GetParams(ctx).CovenantQuorum // tip of Babylon and Bitcoin babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) btcTipHeight, err := k.GetCurrentBTCHeight(ctx) @@ -58,7 +60,7 @@ func (k Keeper) RecordVotingPowerTable(ctx context.Context) { if err != nil { panic(err) // only programming error is possible } - valPower += btcDels.VotingPower(btcTipHeight, wValue) + valPower += btcDels.VotingPower(btcTipHeight, wValue, covenantQuorum) } btcDelIter.Close() diff --git a/x/btcstaking/types/btc_delegations.go b/x/btcstaking/types/btc_delegations.go index 3f5e20136..0fe8b3f3f 100644 --- a/x/btcstaking/types/btc_delegations.go +++ b/x/btcstaking/types/btc_delegations.go @@ -36,10 +36,10 @@ func (i *BTCDelegatorDelegationIndex) Add(stakingTxHash chainhash.Hash) error { } // VotingPower calculates the total voting power of all BTC delegations -func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64) uint64 { +func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { power := uint64(0) for _, del := range dels.Dels { - power += del.VotingPower(btcHeight, w) + power += del.VotingPower(btcHeight, w, covenantQuorum) } return power } diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 5b2fc5380..207cbcda1 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -88,16 +88,35 @@ func (d *BTCDelegation) ValidateBasic() error { } // HasCovenantSig returns whether a BTC delegation has a covenant signature -func (d *BTCDelegation) HasCovenantSig() bool { +func (d *BTCDelegation) HasCovenantQuorum(quorum uint32) bool { + // TODO: accomodate covenant committee for CovenantSig return d.CovenantSig != nil } -func (ud *BTCUndelegation) HasCovenantSigs() bool { - return ud.CovenantSlashingSig != nil && ud.CovenantUnbondingSig != nil +// IsSignedByCovMember checks whether the given covenant PK has signed the delegation +func (d *BTCDelegation) IsSignedByCovMember(covPK *bbn.BIP340PubKey) bool { + // TODO: accomodate covenant committee for CovenantSig + return d.CovenantSig != nil +} + +func (ud *BTCUndelegation) HasCovenantQuorum(quorum uint32) bool { + // TODO: accomodate covenant committee for CovenantSlashingSig + return ud.CovenantSlashingSig != nil && len(ud.CovenantUnbondingSigList) >= int(quorum) +} + +// IsSignedByCovMember checks whether the given covenant PK has signed the undelegation +func (ud *BTCUndelegation) IsSignedByCovMember(covPK *bbn.BIP340PubKey) bool { + // TODO: accomodate covenant committee for CovenantSlashingSig + for _, sigInfo := range ud.CovenantUnbondingSigList { + if sigInfo.Pk.Equals(covPK) { + return true + } + } + return false } -func (ud *BTCUndelegation) HasAllSignatures() bool { - return ud.HasCovenantSigs() +func (ud *BTCUndelegation) HasAllSignatures(covenantQuorum uint32) bool { + return ud.HasCovenantQuorum(covenantQuorum) } // GetStatus returns the status of the BTC Delegation based on a BTC height and a w value @@ -108,9 +127,9 @@ func (ud *BTCUndelegation) HasAllSignatures() bool { // Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig // Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig // Expired: Delegation timelock -func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatus { +func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64, covenantQuorum uint32) BTCDelegationStatus { if d.BtcUndelegation != nil { - if d.BtcUndelegation.HasAllSignatures() { + if d.BtcUndelegation.HasAllSignatures(covenantQuorum) { return BTCDelegationStatus_UNBONDED } // If we received an undelegation but is still does not have all required signature, @@ -124,7 +143,7 @@ func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatu } if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { - if d.HasCovenantSig() { + if d.HasCovenantQuorum(covenantQuorum) { return BTCDelegationStatus_ACTIVE } else { return BTCDelegationStatus_PENDING @@ -136,8 +155,8 @@ func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64) BTCDelegationStatu // VotingPower returns the voting power of the BTC delegation at a given BTC height // and a given w value. // The BTC delegation d has voting power iff it is active. -func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64) uint64 { - if d.GetStatus(btcHeight, w) != BTCDelegationStatus_ACTIVE { +func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { + if d.GetStatus(btcHeight, w, covenantQuorum) != BTCDelegationStatus_ACTIVE { return 0 } return d.GetTotalSat() @@ -195,3 +214,10 @@ func ExistsDup(btcPKs []bbn.BIP340PubKey) bool { return false } + +func NewSignatureInfo(pk *bbn.BIP340PubKey, sig *bbn.BIP340Signature) *SignatureInfo { + return &SignatureInfo{ + Pk: pk, + Sig: sig, + } +} diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index c3ab1dae6..d5b82866f 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -288,6 +288,7 @@ type BTCDelegation struct { // by the covenant (i.e., SK corresponding to covenant_pk in params) // It will be a part of the witness for the staking tx output. // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples + // over each restaked BTC validator (i.e., a matrix of tuples) CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,12,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` // if this object is present it menans that staker requested undelegation, and whole // delegation is being undelegated. @@ -404,14 +405,12 @@ type BTCUndelegation struct { // covenant_slashing_sig is the signature on the slashing tx // by the covenant (i.e., SK corresponding to covenant_pk in params) // It must be provided after processing undelagate message by Babylon - // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples + // TODO: change to a matrix of (covenant PK, covenant adaptor signature) tuples CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` - // covenant_unbonding_sig is the signature on the unbonding tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon and after - // validator sig will be provided by validator - // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` + // covenant_unbonding_sig_list is the list of signatures on the unbonding tx + // by covenant members + // It must be provided after processing undelagate message by Babylon + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,6,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } func (m *BTCUndelegation) Reset() { *m = BTCUndelegation{} } @@ -461,17 +460,24 @@ func (m *BTCUndelegation) GetUnbondingTime() uint32 { return 0 } +func (m *BTCUndelegation) GetCovenantUnbondingSigList() []*SignatureInfo { + if m != nil { + return m.CovenantUnbondingSigList + } + return nil +} + // BTCUndelegationInfo provides all necessary info about the undeleagation type BTCUndelegationInfo struct { // unbonding_tx is the transaction which will transfer the funds from staking // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // covenant_unbonding_sig is the signature on the unbonding tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // it will be nil if covenant didn't sign it yet - // TODO: change to a set of (covenant PK, covenant Schnorr signature) tuples - CovenantUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,2,opt,name=covenant_unbonding_sig,json=covenantUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_unbonding_sig,omitempty"` + // covenant_unbonding_sig_list is the list of signatures on the unbonding tx + // by covenant members + // It must be provided after processing undelagate message by Babylon and after + // validator sig will be provided by validator + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,2,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -514,6 +520,13 @@ func (m *BTCUndelegationInfo) GetUnbondingTx() []byte { return nil } +func (m *BTCUndelegationInfo) GetCovenantUnbondingSigList() []*SignatureInfo { + if m != nil { + return m.CovenantUnbondingSigList + } + return nil +} + // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` @@ -660,73 +673,74 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1046 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdd, 0x6e, 0x1b, 0x45, - 0x14, 0xce, 0xda, 0x8e, 0x5b, 0x1f, 0xdb, 0xc4, 0x9d, 0xa6, 0xc1, 0x24, 0xc2, 0x36, 0xa6, 0x54, - 0x16, 0xa2, 0x6b, 0x92, 0xfe, 0x08, 0xb8, 0x40, 0xaa, 0xe3, 0x40, 0xad, 0xe6, 0xc7, 0xac, 0x9d, - 0x22, 0x10, 0xb0, 0x9a, 0xdd, 0x9d, 0xac, 0x57, 0xb6, 0x77, 0x56, 0x9e, 0xb1, 0xb1, 0xef, 0x79, - 0x00, 0x1e, 0x82, 0x1b, 0xb8, 0xe6, 0x21, 0xb8, 0xac, 0xe0, 0x06, 0xe5, 0x22, 0xaa, 0x92, 0x17, - 0x41, 0x3b, 0x3b, 0xeb, 0xdd, 0x84, 0x04, 0x5a, 0x1c, 0xee, 0x3c, 0x73, 0xce, 0xf9, 0xce, 0x77, - 0xbe, 0xef, 0x64, 0xb2, 0x70, 0xcf, 0xc0, 0xc6, 0x6c, 0x40, 0xdd, 0xba, 0xc1, 0x4d, 0xc6, 0x71, - 0xdf, 0x71, 0xed, 0xfa, 0x64, 0x33, 0x76, 0x52, 0xbd, 0x11, 0xe5, 0x14, 0xdd, 0x91, 0x79, 0x6a, - 0x2c, 0x32, 0xd9, 0x5c, 0x5f, 0xb5, 0xa9, 0x4d, 0x45, 0x46, 0xdd, 0xff, 0x15, 0x24, 0xaf, 0xbf, - 0x65, 0x52, 0x36, 0xa4, 0x4c, 0x0f, 0x02, 0xc1, 0x41, 0x86, 0xaa, 0xc1, 0xa9, 0x6e, 0x8e, 0x66, - 0x1e, 0xa7, 0x75, 0x46, 0x4c, 0x6f, 0xeb, 0xd1, 0xe3, 0xfe, 0x66, 0xbd, 0x4f, 0x66, 0x61, 0xce, - 0x5d, 0x99, 0x13, 0xf1, 0x31, 0x08, 0xc7, 0x9b, 0xf5, 0x73, 0x8c, 0xd6, 0xcb, 0x97, 0x33, 0xf7, - 0xa8, 0x17, 0x24, 0x54, 0xff, 0x48, 0x42, 0xae, 0xd1, 0xdd, 0x7e, 0x8e, 0x07, 0x8e, 0x85, 0x39, - 0x1d, 0xa1, 0x1d, 0xc8, 0x5a, 0x84, 0x99, 0x23, 0xc7, 0xe3, 0x0e, 0x75, 0x8b, 0x4a, 0x45, 0xa9, - 0x65, 0xb7, 0xde, 0x55, 0x25, 0xbf, 0x68, 0x2a, 0xd1, 0x4d, 0x6d, 0x46, 0xa9, 0x5a, 0xbc, 0x0e, - 0xed, 0x01, 0x98, 0x74, 0x38, 0x74, 0x18, 0xf3, 0x51, 0x12, 0x15, 0xa5, 0x96, 0x69, 0xdc, 0x3f, - 0x3e, 0x29, 0x6f, 0x04, 0x40, 0xcc, 0xea, 0xab, 0x0e, 0xad, 0x0f, 0x31, 0xef, 0xa9, 0xbb, 0xc4, - 0xc6, 0xe6, 0xac, 0x49, 0xcc, 0xdf, 0x7f, 0xbd, 0x0f, 0xb2, 0x4f, 0x93, 0x98, 0x5a, 0x0c, 0x00, - 0x7d, 0x0a, 0x20, 0x27, 0xd1, 0xbd, 0x7e, 0x31, 0x29, 0x48, 0x95, 0x43, 0x52, 0x81, 0x4c, 0xea, - 0x5c, 0x26, 0xb5, 0x3d, 0x36, 0x9e, 0x91, 0x99, 0x96, 0x91, 0x25, 0xed, 0x3e, 0xda, 0x83, 0xb4, - 0xc1, 0x4d, 0xbf, 0x36, 0x55, 0x51, 0x6a, 0xb9, 0xc6, 0xe3, 0xe3, 0x93, 0xf2, 0x96, 0xed, 0xf0, - 0xde, 0xd8, 0x50, 0x4d, 0x3a, 0xac, 0xcb, 0x4c, 0xb3, 0x87, 0x1d, 0x37, 0x3c, 0xd4, 0xf9, 0xcc, - 0x23, 0x4c, 0x6d, 0xb4, 0xda, 0x0f, 0x1e, 0x7e, 0x28, 0x21, 0x97, 0x0d, 0x6e, 0xb6, 0xfb, 0xe8, - 0x13, 0x48, 0x7a, 0xd4, 0x2b, 0x2e, 0x0b, 0x1e, 0x35, 0xf5, 0x52, 0xdb, 0xd5, 0xf6, 0x88, 0xd2, - 0xa3, 0x83, 0xa3, 0x36, 0x65, 0x8c, 0x88, 0x29, 0x34, 0xbf, 0x08, 0x3d, 0x84, 0x35, 0x36, 0xc0, - 0xac, 0x47, 0x2c, 0x3d, 0x1c, 0xa9, 0x47, 0x1c, 0xbb, 0xc7, 0x8b, 0xe9, 0x8a, 0x52, 0x4b, 0x69, - 0xab, 0x32, 0xda, 0x08, 0x82, 0x4f, 0x45, 0x0c, 0x7d, 0x00, 0x68, 0x5e, 0xc5, 0xcd, 0xb0, 0xe2, - 0x86, 0xa8, 0x28, 0x84, 0x15, 0xdc, 0x0c, 0xb2, 0xab, 0x3f, 0x24, 0x60, 0x35, 0xee, 0xea, 0x97, - 0x0e, 0xef, 0xed, 0x11, 0x8e, 0x63, 0x3a, 0x28, 0xd7, 0xa1, 0xc3, 0x1a, 0xa4, 0x25, 0x93, 0x84, - 0x60, 0x22, 0x4f, 0xe8, 0x1d, 0xc8, 0x4d, 0x28, 0x77, 0x5c, 0x5b, 0xf7, 0xe8, 0xf7, 0x64, 0x24, - 0x0c, 0x4b, 0x69, 0xd9, 0xe0, 0xae, 0xed, 0x5f, 0xfd, 0x83, 0x0c, 0xa9, 0xd7, 0x96, 0x61, 0xf9, - 0x0a, 0x19, 0x7e, 0x49, 0x43, 0xbe, 0xd1, 0xdd, 0x6e, 0x92, 0x01, 0xb1, 0x31, 0xff, 0xfb, 0x1e, - 0x29, 0x0b, 0xec, 0x51, 0xe2, 0x1a, 0xf7, 0x28, 0xf9, 0x5f, 0xf6, 0xe8, 0x5b, 0x58, 0x99, 0xe0, - 0x81, 0x1e, 0xd0, 0xd1, 0x07, 0x0e, 0xf3, 0x95, 0x4b, 0x2e, 0xc0, 0x29, 0x37, 0xc1, 0x83, 0x86, - 0x4f, 0x6b, 0xd7, 0x61, 0xc2, 0x42, 0xc6, 0xf1, 0x88, 0x9f, 0xd7, 0x38, 0x2b, 0xee, 0xa4, 0x19, - 0x6f, 0x03, 0x10, 0xd7, 0x3a, 0xbf, 0xbd, 0x19, 0xe2, 0x5a, 0x32, 0xbc, 0x01, 0x19, 0x4e, 0x39, - 0x1e, 0xe8, 0x0c, 0x87, 0x9b, 0x7a, 0x53, 0x5c, 0x74, 0xb0, 0xa8, 0x95, 0x23, 0xea, 0x7c, 0x5a, - 0xbc, 0xe9, 0x8b, 0xa9, 0x65, 0xe4, 0x4d, 0x77, 0x2a, 0x7c, 0x96, 0x61, 0x3a, 0xe6, 0xde, 0x98, - 0xeb, 0x8e, 0x35, 0x2d, 0x66, 0x2a, 0x4a, 0x2d, 0xaf, 0x15, 0x64, 0xe4, 0x40, 0x04, 0x5a, 0xd6, - 0x14, 0x6d, 0x41, 0x56, 0x78, 0x2f, 0xd1, 0x40, 0x58, 0x73, 0xeb, 0xf8, 0xa4, 0xec, 0xbb, 0xdf, - 0x91, 0x91, 0xee, 0x54, 0x03, 0x36, 0xff, 0x8d, 0xbe, 0x83, 0xbc, 0x15, 0xec, 0x05, 0x1d, 0xe9, - 0xcc, 0xb1, 0x8b, 0x59, 0x51, 0xf5, 0xf1, 0xf1, 0x49, 0xf9, 0xd1, 0xeb, 0x88, 0xd7, 0x71, 0x6c, - 0x17, 0xf3, 0xf1, 0x88, 0x68, 0xb9, 0x39, 0x5e, 0xc7, 0xb1, 0xd1, 0x37, 0x90, 0x33, 0xe9, 0x84, - 0xb8, 0xd8, 0xe5, 0x02, 0x3e, 0xb7, 0x28, 0x7c, 0x36, 0x84, 0xf3, 0xd1, 0xbf, 0x80, 0x82, 0x6f, - 0xfc, 0xd8, 0xb5, 0xe6, 0xbb, 0x5d, 0xcc, 0x8b, 0x2d, 0xba, 0x77, 0xc5, 0x16, 0x35, 0xba, 0xdb, - 0x87, 0xb1, 0x6c, 0x6d, 0xc5, 0xe0, 0x66, 0xfc, 0xa2, 0xfa, 0x32, 0x09, 0x2b, 0x17, 0x92, 0xfc, - 0x25, 0x18, 0xbb, 0x06, 0x75, 0x2d, 0xa9, 0xac, 0x78, 0x34, 0xb4, 0xec, 0xfc, 0xae, 0x3b, 0x45, - 0xef, 0xc1, 0x1b, 0xb1, 0x14, 0x67, 0x48, 0xc4, 0x5f, 0x46, 0x5e, 0xcb, 0x47, 0x49, 0xce, 0x90, - 0x5c, 0xb4, 0x28, 0xf9, 0x2a, 0x16, 0x51, 0x58, 0x8b, 0x59, 0x14, 0x56, 0xfb, 0x62, 0xa6, 0x16, - 0x15, 0x73, 0x35, 0xf2, 0x4a, 0xe2, 0xfa, 0xaa, 0x0e, 0xe1, 0x4e, 0xe4, 0x59, 0xbc, 0xdf, 0xf2, - 0xa2, 0xfd, 0x6e, 0xcf, 0xcd, 0x8b, 0xb5, 0xa3, 0xb0, 0x36, 0x6f, 0x17, 0x69, 0xe8, 0xf7, 0x4b, - 0x2f, 0x3c, 0x5f, 0x08, 0x7c, 0x18, 0xe2, 0x76, 0x1c, 0xbb, 0xfa, 0xb3, 0x02, 0xb7, 0x2f, 0x58, - 0xdc, 0x72, 0x8f, 0xe8, 0xab, 0xd8, 0x7c, 0x35, 0xd7, 0xc4, 0xff, 0xc3, 0xb5, 0x03, 0x6f, 0x46, - 0x4f, 0x37, 0x1d, 0x45, 0x6f, 0x38, 0x43, 0x1f, 0x41, 0xca, 0x22, 0x03, 0x56, 0x54, 0x2a, 0xc9, - 0x5a, 0x76, 0xeb, 0xee, 0xd5, 0x0b, 0x1f, 0x15, 0x69, 0xa2, 0xa2, 0xba, 0x0f, 0x1b, 0x97, 0x83, - 0xb6, 0x5c, 0x8b, 0x4c, 0x51, 0x1d, 0x56, 0xa3, 0x47, 0x49, 0xef, 0x61, 0xd6, 0x0b, 0xde, 0x55, - 0xbf, 0x51, 0x4e, 0xbb, 0x35, 0x7f, 0x9e, 0x9e, 0x62, 0xd6, 0xf3, 0x1f, 0xc9, 0xea, 0x4f, 0x0a, - 0xe4, 0xe7, 0x83, 0x08, 0x29, 0x3f, 0x83, 0xc4, 0xc2, 0xff, 0x5c, 0x13, 0x5e, 0x1f, 0x3d, 0x83, - 0xe4, 0xb5, 0x88, 0xeb, 0xa3, 0xbc, 0xdf, 0x15, 0xb6, 0x47, 0xd3, 0x76, 0x38, 0xe6, 0x63, 0x86, - 0xb2, 0x70, 0xa3, 0xbd, 0xb3, 0xdf, 0x6c, 0xed, 0x7f, 0x5e, 0x58, 0x42, 0x00, 0xe9, 0x27, 0xdb, - 0xdd, 0xd6, 0xf3, 0x9d, 0x82, 0x82, 0xf2, 0x90, 0x39, 0xdc, 0x6f, 0x1c, 0x04, 0xa1, 0x04, 0xca, - 0xc1, 0xcd, 0xe0, 0xb8, 0xd3, 0x2c, 0x24, 0xd1, 0x0d, 0x48, 0x3e, 0xd9, 0xff, 0xaa, 0x90, 0x6a, - 0xec, 0xfe, 0x76, 0x5a, 0x52, 0x5e, 0x9c, 0x96, 0x94, 0x97, 0xa7, 0x25, 0xe5, 0xc7, 0xb3, 0xd2, - 0xd2, 0x8b, 0xb3, 0xd2, 0xd2, 0x9f, 0x67, 0xa5, 0xa5, 0xaf, 0xff, 0x75, 0xe8, 0x69, 0xfc, 0x7b, - 0x54, 0x10, 0x37, 0xd2, 0xe2, 0x7b, 0xf4, 0xc1, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf9, 0x12, - 0x09, 0xab, 0x6c, 0x0b, 0x00, 0x00, + // 1060 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xe3, 0x44, + 0x17, 0xae, 0x93, 0x34, 0xbb, 0x39, 0x49, 0xde, 0x66, 0x67, 0xbb, 0x7d, 0x43, 0x2b, 0x92, 0x10, + 0x96, 0x55, 0x84, 0x58, 0x87, 0x76, 0x3f, 0x04, 0x5c, 0x20, 0x6d, 0x9a, 0xc2, 0x46, 0xdb, 0x8f, + 0xe0, 0xa4, 0x8b, 0x40, 0x80, 0x35, 0xb6, 0xa7, 0x8e, 0x95, 0xc4, 0x63, 0x65, 0x26, 0x21, 0xb9, + 0xe7, 0x07, 0xf0, 0x07, 0xb8, 0xe3, 0x8a, 0x6b, 0x7e, 0x04, 0x97, 0x2b, 0xe0, 0x02, 0xf5, 0xa2, + 0x42, 0xed, 0x1f, 0x41, 0x1e, 0x8f, 0x63, 0xb7, 0xb4, 0xfb, 0x41, 0x7a, 0xd7, 0x99, 0x73, 0xce, + 0x73, 0x9e, 0xf3, 0x3c, 0xc7, 0xd3, 0xc0, 0x3d, 0x03, 0x1b, 0xb3, 0x01, 0x75, 0xeb, 0x06, 0x37, + 0x19, 0xc7, 0x7d, 0xc7, 0xb5, 0xeb, 0x93, 0xcd, 0xd8, 0x49, 0xf5, 0x46, 0x94, 0x53, 0x74, 0x47, + 0xe6, 0xa9, 0xb1, 0xc8, 0x64, 0x73, 0x7d, 0xd5, 0xa6, 0x36, 0x15, 0x19, 0x75, 0xff, 0xaf, 0x20, + 0x79, 0xfd, 0x2d, 0x93, 0xb2, 0x21, 0x65, 0x7a, 0x10, 0x08, 0x0e, 0x32, 0x54, 0x0d, 0x4e, 0x75, + 0x73, 0x34, 0xf3, 0x38, 0xad, 0x33, 0x62, 0x7a, 0x5b, 0x8f, 0x1e, 0xf7, 0x37, 0xeb, 0x7d, 0x32, + 0x0b, 0x73, 0xee, 0xca, 0x9c, 0x88, 0x8f, 0x41, 0x38, 0xde, 0xac, 0x9f, 0x63, 0xb4, 0x5e, 0xbe, + 0x9c, 0xb9, 0x47, 0xbd, 0x20, 0xa1, 0xfa, 0x47, 0x12, 0x72, 0x8d, 0xee, 0xf6, 0x73, 0x3c, 0x70, + 0x2c, 0xcc, 0xe9, 0x08, 0xed, 0x40, 0xd6, 0x22, 0xcc, 0x1c, 0x39, 0x1e, 0x77, 0xa8, 0x5b, 0x54, + 0x2a, 0x4a, 0x2d, 0xbb, 0xf5, 0xae, 0x2a, 0xf9, 0x45, 0x53, 0x89, 0x6e, 0x6a, 0x33, 0x4a, 0xd5, + 0xe2, 0x75, 0x68, 0x0f, 0xc0, 0xa4, 0xc3, 0xa1, 0xc3, 0x98, 0x8f, 0x92, 0xa8, 0x28, 0xb5, 0x4c, + 0xe3, 0xfe, 0xf1, 0x49, 0x79, 0x23, 0x00, 0x62, 0x56, 0x5f, 0x75, 0x68, 0x7d, 0x88, 0x79, 0x4f, + 0xdd, 0x25, 0x36, 0x36, 0x67, 0x4d, 0x62, 0xfe, 0xfe, 0xeb, 0x7d, 0x90, 0x7d, 0x9a, 0xc4, 0xd4, + 0x62, 0x00, 0xe8, 0x53, 0x00, 0x39, 0x89, 0xee, 0xf5, 0x8b, 0x49, 0x41, 0xaa, 0x1c, 0x92, 0x0a, + 0x64, 0x52, 0xe7, 0x32, 0xa9, 0xed, 0xb1, 0xf1, 0x8c, 0xcc, 0xb4, 0x8c, 0x2c, 0x69, 0xf7, 0xd1, + 0x1e, 0xa4, 0x0d, 0x6e, 0xfa, 0xb5, 0xa9, 0x8a, 0x52, 0xcb, 0x35, 0x1e, 0x1f, 0x9f, 0x94, 0xb7, + 0x6c, 0x87, 0xf7, 0xc6, 0x86, 0x6a, 0xd2, 0x61, 0x5d, 0x66, 0x9a, 0x3d, 0xec, 0xb8, 0xe1, 0xa1, + 0xce, 0x67, 0x1e, 0x61, 0x6a, 0xa3, 0xd5, 0x7e, 0xf0, 0xf0, 0x43, 0x09, 0xb9, 0x6c, 0x70, 0xb3, + 0xdd, 0x47, 0x9f, 0x40, 0xd2, 0xa3, 0x5e, 0x71, 0x59, 0xf0, 0xa8, 0xa9, 0x97, 0xda, 0xae, 0xb6, + 0x47, 0x94, 0x1e, 0x1d, 0x1c, 0xb5, 0x29, 0x63, 0x44, 0x4c, 0xa1, 0xf9, 0x45, 0xe8, 0x21, 0xac, + 0xb1, 0x01, 0x66, 0x3d, 0x62, 0xe9, 0xe1, 0x48, 0x3d, 0xe2, 0xd8, 0x3d, 0x5e, 0x4c, 0x57, 0x94, + 0x5a, 0x4a, 0x5b, 0x95, 0xd1, 0x46, 0x10, 0x7c, 0x2a, 0x62, 0xe8, 0x03, 0x40, 0xf3, 0x2a, 0x6e, + 0x86, 0x15, 0x37, 0x44, 0x45, 0x21, 0xac, 0xe0, 0x66, 0x90, 0x5d, 0xfd, 0x21, 0x01, 0xab, 0x71, + 0x57, 0xbf, 0x74, 0x78, 0x6f, 0x8f, 0x70, 0x1c, 0xd3, 0x41, 0xb9, 0x0e, 0x1d, 0xd6, 0x20, 0x2d, + 0x99, 0x24, 0x04, 0x13, 0x79, 0x42, 0xef, 0x40, 0x6e, 0x42, 0xb9, 0xe3, 0xda, 0xba, 0x47, 0xbf, + 0x27, 0x23, 0x61, 0x58, 0x4a, 0xcb, 0x06, 0x77, 0x6d, 0xff, 0xea, 0x25, 0x32, 0xa4, 0xde, 0x58, + 0x86, 0xe5, 0x2b, 0x64, 0xf8, 0x25, 0x0d, 0xf9, 0x46, 0x77, 0xbb, 0x49, 0x06, 0xc4, 0xc6, 0xfc, + 0xdf, 0x7b, 0xa4, 0x2c, 0xb0, 0x47, 0x89, 0x6b, 0xdc, 0xa3, 0xe4, 0x7f, 0xd9, 0xa3, 0x6f, 0x61, + 0x65, 0x82, 0x07, 0x7a, 0x40, 0x47, 0x1f, 0x38, 0xcc, 0x57, 0x2e, 0xb9, 0x00, 0xa7, 0xdc, 0x04, + 0x0f, 0x1a, 0x3e, 0xad, 0x5d, 0x87, 0x09, 0x0b, 0x19, 0xc7, 0x23, 0x7e, 0x5e, 0xe3, 0xac, 0xb8, + 0x93, 0x66, 0xbc, 0x0d, 0x40, 0x5c, 0xeb, 0xfc, 0xf6, 0x66, 0x88, 0x6b, 0xc9, 0xf0, 0x06, 0x64, + 0x38, 0xe5, 0x78, 0xa0, 0x33, 0x1c, 0x6e, 0xea, 0x4d, 0x71, 0xd1, 0xc1, 0xa2, 0x56, 0x8e, 0xa8, + 0xf3, 0x69, 0xf1, 0xa6, 0x2f, 0xa6, 0x96, 0x91, 0x37, 0xdd, 0xa9, 0xf0, 0x59, 0x86, 0xe9, 0x98, + 0x7b, 0x63, 0xae, 0x3b, 0xd6, 0xb4, 0x98, 0xa9, 0x28, 0xb5, 0xbc, 0x56, 0x90, 0x91, 0x03, 0x11, + 0x68, 0x59, 0x53, 0xb4, 0x05, 0x59, 0xe1, 0xbd, 0x44, 0x03, 0x61, 0xcd, 0xad, 0xe3, 0x93, 0xb2, + 0xef, 0x7e, 0x47, 0x46, 0xba, 0x53, 0x0d, 0xd8, 0xfc, 0x6f, 0xf4, 0x1d, 0xe4, 0xad, 0x60, 0x2f, + 0xe8, 0x48, 0x67, 0x8e, 0x5d, 0xcc, 0x8a, 0xaa, 0x8f, 0x8f, 0x4f, 0xca, 0x8f, 0xde, 0x44, 0xbc, + 0x8e, 0x63, 0xbb, 0x98, 0x8f, 0x47, 0x44, 0xcb, 0xcd, 0xf1, 0x3a, 0x8e, 0x8d, 0xbe, 0x81, 0x9c, + 0x49, 0x27, 0xc4, 0xc5, 0x2e, 0x17, 0xf0, 0xb9, 0x45, 0xe1, 0xb3, 0x21, 0x9c, 0x8f, 0xfe, 0x05, + 0x14, 0x7c, 0xe3, 0xc7, 0xae, 0x35, 0xdf, 0xed, 0x62, 0x5e, 0x6c, 0xd1, 0xbd, 0x2b, 0xb6, 0xa8, + 0xd1, 0xdd, 0x3e, 0x8c, 0x65, 0x6b, 0x2b, 0x06, 0x37, 0xe3, 0x17, 0xd5, 0x3f, 0x93, 0xb0, 0x72, + 0x21, 0xc9, 0x5f, 0x82, 0xb1, 0x6b, 0x50, 0xd7, 0x92, 0xca, 0x8a, 0x47, 0x43, 0xcb, 0xce, 0xef, + 0xba, 0x53, 0xf4, 0x1e, 0xfc, 0x2f, 0x96, 0xe2, 0x0c, 0x89, 0xf8, 0x32, 0xf2, 0x5a, 0x3e, 0x4a, + 0x72, 0x86, 0xe4, 0xa2, 0x45, 0xc9, 0xd7, 0xb1, 0x88, 0xc2, 0x5a, 0xcc, 0xa2, 0xb0, 0xda, 0x17, + 0x33, 0xb5, 0xa8, 0x98, 0xab, 0x91, 0x57, 0x12, 0xd7, 0x57, 0x75, 0x08, 0x77, 0x22, 0xcf, 0xe2, + 0xfd, 0x96, 0x17, 0xed, 0x77, 0x7b, 0x6e, 0x5e, 0xac, 0x9d, 0x09, 0x1b, 0xf3, 0x76, 0x91, 0x86, + 0xcc, 0xb1, 0x83, 0xaf, 0x39, 0x5d, 0x49, 0xd6, 0xb2, 0x5b, 0x77, 0xaf, 0xf0, 0x73, 0x8e, 0xdd, + 0x72, 0x8f, 0xa8, 0x56, 0x0c, 0x81, 0x0e, 0x43, 0x9c, 0x8e, 0x63, 0xfb, 0xdf, 0x71, 0xf5, 0x27, + 0x05, 0x6e, 0x5f, 0xb0, 0xd5, 0xaf, 0x78, 0x1d, 0x6b, 0x5f, 0xc1, 0x2f, 0x71, 0x2d, 0xfc, 0x3a, + 0xf0, 0xff, 0xe8, 0x89, 0xa6, 0xa3, 0xe8, 0xad, 0x66, 0xe8, 0x23, 0x48, 0x59, 0x64, 0xc0, 0x8a, + 0xca, 0x4b, 0x1b, 0x9d, 0x7b, 0xe0, 0x35, 0x51, 0x51, 0xdd, 0x87, 0x8d, 0xcb, 0x41, 0x5b, 0xae, + 0x45, 0xa6, 0xa8, 0x0e, 0xab, 0xd1, 0xe3, 0xa3, 0xf7, 0x30, 0xeb, 0x05, 0x13, 0xf9, 0x8d, 0x72, + 0xda, 0xad, 0xf9, 0x33, 0xf4, 0x14, 0xb3, 0x9e, 0x20, 0xf9, 0xb3, 0x02, 0xf9, 0x73, 0x03, 0xa1, + 0xcf, 0x20, 0xb1, 0xf0, 0x3f, 0xd1, 0x84, 0xd7, 0x47, 0xcf, 0x20, 0xe9, 0x2f, 0x58, 0x62, 0xd1, + 0x05, 0xf3, 0x51, 0xde, 0xef, 0x0a, 0xab, 0xa3, 0x69, 0x3b, 0x1c, 0xf3, 0x31, 0x43, 0x59, 0xb8, + 0xd1, 0xde, 0xd9, 0x6f, 0xb6, 0xf6, 0x3f, 0x2f, 0x2c, 0x21, 0x80, 0xf4, 0x93, 0xed, 0x6e, 0xeb, + 0xf9, 0x4e, 0x41, 0x41, 0x79, 0xc8, 0x1c, 0xee, 0x37, 0x0e, 0x82, 0x50, 0x02, 0xe5, 0xe0, 0x66, + 0x70, 0xdc, 0x69, 0x16, 0x92, 0xe8, 0x06, 0x24, 0x9f, 0xec, 0x7f, 0x55, 0x48, 0x35, 0x76, 0x7f, + 0x3b, 0x2d, 0x29, 0x2f, 0x4e, 0x4b, 0xca, 0xdf, 0xa7, 0x25, 0xe5, 0xc7, 0xb3, 0xd2, 0xd2, 0x8b, + 0xb3, 0xd2, 0xd2, 0x5f, 0x67, 0xa5, 0xa5, 0xaf, 0x5f, 0x39, 0xf4, 0x34, 0xfe, 0xbb, 0x53, 0x10, + 0x37, 0xd2, 0xe2, 0x77, 0xe7, 0x83, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x99, 0xd6, 0x9d, + 0x54, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1045,17 +1059,19 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.CovenantUnbondingSig != nil { - { - size := m.CovenantUnbondingSig.Size() - i -= size - if _, err := m.CovenantUnbondingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.CovenantUnbondingSigList) > 0 { + for iNdEx := len(m.CovenantUnbondingSigList) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantUnbondingSigList[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 } - i-- - dAtA[i] = 0x32 } if m.CovenantSlashingSig != nil { { @@ -1128,17 +1144,19 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.CovenantUnbondingSig != nil { - { - size := m.CovenantUnbondingSig.Size() - i -= size - if _, err := m.CovenantUnbondingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.CovenantUnbondingSigList) > 0 { + for iNdEx := len(m.CovenantUnbondingSigList) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantUnbondingSigList[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 } if len(m.UnbondingTx) > 0 { i -= len(m.UnbondingTx) @@ -1421,9 +1439,11 @@ func (m *BTCUndelegation) Size() (n int) { l = m.CovenantSlashingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.CovenantUnbondingSig != nil { - l = m.CovenantUnbondingSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) + if len(m.CovenantUnbondingSigList) > 0 { + for _, e := range m.CovenantUnbondingSigList { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } return n } @@ -1438,9 +1458,11 @@ func (m *BTCUndelegationInfo) Size() (n int) { if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } - if m.CovenantUnbondingSig != nil { - l = m.CovenantUnbondingSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) + if len(m.CovenantUnbondingSigList) > 0 { + for _, e := range m.CovenantUnbondingSigList { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } return n } @@ -2558,9 +2580,9 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2570,24 +2592,23 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantUnbondingSig = &v - if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantUnbondingSigList = append(m.CovenantUnbondingSigList, &SignatureInfo{}) + if err := m.CovenantUnbondingSigList[len(m.CovenantUnbondingSigList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2677,9 +2698,9 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2689,24 +2710,23 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantUnbondingSig = &v - if err := m.CovenantUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantUnbondingSigList = append(m.CovenantUnbondingSigList, &SignatureInfo{}) + if err := m.CovenantUnbondingSigList[len(m.CovenantUnbondingSigList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index cfc2a0ba0..785c79ad8 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -1,10 +1,11 @@ package types_test import ( - sdkmath "cosmossdk.io/math" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -84,7 +85,7 @@ func FuzzBTCDelegation(f *testing.F) { // test expected voting power hasVotingPower := hasCovenantSig && btcDel.StartHeight <= btcHeight && btcHeight+w <= btcDel.EndHeight - actualVotingPower := btcDel.VotingPower(btcHeight, w) + actualVotingPower := btcDel.VotingPower(btcHeight, w, 1) if hasVotingPower { require.Equal(t, btcDel.TotalSat, actualVotingPower) } else { diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 3a1f641fa..bc8287ddc 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -17,16 +17,17 @@ var ( ErrInvalidCovenantPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong covenant PK") ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1109, "the BTC staking tx is not valid") ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1110, "the BTC slashing tx is not valid") - ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received covenant signature") - ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1112, "the covenant signature is not valid") - ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1113, "commission cannot be less than min rate") - ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1114, "Unexpected delegation state") - ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1115, "the BTC unbonding tx is not valid") - ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1116, "the BTC undelegation has already received validator signature") - ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1117, "the validator signature is not valid") - ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1118, "the BTC undelegation did not receive validator signature yet") - ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1119, "the reward distribution cache is not found") - ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1120, "the validator list is empty") - ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1121, "the proof of possession is not valid") - ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1122, "the staking request contains duplicated validator public key") + ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received this covenant signature") + ErrCovenantQuorumAlreadyReached = errorsmod.Register(ModuleName, 1112, "the BTC delegation has already received a quorum number of covenant signatures") + ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1113, "the covenant signature is not valid") + ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1114, "commission cannot be less than min rate") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1115, "Unexpected delegation state") + ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1116, "the BTC unbonding tx is not valid") + ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1117, "the BTC undelegation has already received validator signature") + ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1118, "the validator signature is not valid") + ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1119, "the BTC undelegation did not receive validator signature yet") + ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1120, "the reward distribution cache is not found") + ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1121, "the validator list is empty") + ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1122, "the proof of possession is not valid") + ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1123, "the staking request contains duplicated validator public key") ) diff --git a/x/btcstaking/types/incentive.go b/x/btcstaking/types/incentive.go index f6787e7a3..92a4df6f7 100644 --- a/x/btcstaking/types/incentive.go +++ b/x/btcstaking/types/incentive.go @@ -54,10 +54,10 @@ func (v *BTCValDistInfo) GetAddress() sdk.AccAddress { return sdk.AccAddress(v.BabylonPk.Address()) } -func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wValue uint64) { +func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wValue uint64, covenantQuorum uint32) { btcDelDistInfo := &BTCDelDistInfo{ BabylonPk: btcDel.BabylonPk, - VotingPower: btcDel.VotingPower(btcHeight, wValue), + VotingPower: btcDel.VotingPower(btcHeight, wValue, covenantQuorum), } if btcDelDistInfo.VotingPower > 0 { diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 217a9c559..6104f6dcf 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -91,8 +91,8 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.Pop == nil { return fmt.Errorf("empty proof of possession") } - if m.StakerBtcPk == nil { - return fmt.Errorf("empty staker BTC public key") + if m.BtcPk == nil { + return fmt.Errorf("empty delegator BTC public key") } if m.StakingTx == nil { return fmt.Errorf("empty staking tx info") @@ -127,11 +127,8 @@ func (m *MsgAddCovenantSig) GetSigners() []sdk.AccAddress { } func (m *MsgAddCovenantSig) ValidateBasic() error { - if m.ValPk == nil { - return fmt.Errorf("empty BTC validator public key") - } - if m.DelPk == nil { - return fmt.Errorf("empty BTC delegation public key") + if m.Pk == nil { + return fmt.Errorf("empty BTC covenant public key") } if m.Sig == nil { return fmt.Errorf("empty covenant signature") @@ -187,11 +184,8 @@ func (m *MsgAddCovenantUnbondingSigs) GetSigners() []sdk.AccAddress { } func (m *MsgAddCovenantUnbondingSigs) ValidateBasic() error { - if m.ValPk == nil { - return fmt.Errorf("empty BTC validator public key") - } - if m.DelPk == nil { - return fmt.Errorf("empty BTC delegation public key") + if m.Pk == nil { + return fmt.Errorf("empty BTC covenant public key") } if m.UnbondingTxSig == nil { return fmt.Errorf("empty covenant signature") diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 6b36c964c..604838fc7 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -125,3 +125,12 @@ func (p Params) String() string { out, _ := yaml.Marshal(p) return string(out) } + +func (p Params) HasCovenantPK(pk *bbn.BIP340PubKey) bool { + for _, pk2 := range p.CovenantPks { + if pk2.Equals(pk) { + return true + } + } + return false +} diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index a5555ac5b..cb9709897 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -156,8 +156,8 @@ type MsgCreateBTCDelegation struct { BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` - // staker_btc_pk is the Bitcoin secp256k1 PK of the BTC staker - StakerBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=staker_btc_pk,json=stakerBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"staker_btc_pk,omitempty"` + // btc_pk is the Bitcoin secp256k1 PK of the BTC delegator + BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one // validator pk it means that delegation is re-staked ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` @@ -408,18 +408,15 @@ var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo // MsgAddCovenantSig is the message for handling a signature from covenant type MsgAddCovenantSig struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // pk is the BTC public key of the covenant member + Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // It uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // sig is the signature of the covenant // the signature follows encoding in BIP-340 spec - Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` + // TODO: change to adaptor signature + Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` } func (m *MsgAddCovenantSig) Reset() { *m = MsgAddCovenantSig{} } @@ -606,21 +603,18 @@ var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo // MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant type MsgAddCovenantUnbondingSigs struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // val_pk is the Bitcoin secp256k1 PK of the BTC validator - // the PK follows encoding in BIP-340 spec - ValPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_pk,json=valPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_pk,omitempty"` - // del_pk is the Bitcoin secp256k1 PK of the BTC delegation - // the PK follows encoding in BIP-340 spec - DelPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,3,opt,name=del_pk,json=delPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"del_pk,omitempty"` + // pk is the BTC public key of the covenant member + Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` // staking_tx_hash is the hash of the staking tx. // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,4,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec - UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` + UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon // the signature follows encoding in BIP-340 spec - SlashingUnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=slashing_unbonding_tx_sig,json=slashingUnbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_unbonding_tx_sig,omitempty"` + // TODO: change to adaptor signature + SlashingUnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=slashing_unbonding_tx_sig,json=slashingUnbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_unbonding_tx_sig,omitempty"` } func (m *MsgAddCovenantUnbondingSigs) Reset() { *m = MsgAddCovenantUnbondingSigs{} } @@ -725,81 +719,79 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1169 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x57, 0x4f, 0x6f, 0x1a, 0x47, - 0x14, 0x37, 0xc6, 0xd0, 0xf2, 0x00, 0x3b, 0xd9, 0x3a, 0x0e, 0xc6, 0x0a, 0x10, 0xdc, 0x24, 0x34, - 0x8a, 0x97, 0x98, 0xc4, 0x96, 0xea, 0x4a, 0x95, 0x82, 0x5d, 0x29, 0x51, 0x8c, 0x8a, 0x16, 0x9c, - 0x43, 0xa4, 0x16, 0x0d, 0xbb, 0xe3, 0x65, 0x05, 0xec, 0xac, 0x76, 0x06, 0x04, 0xea, 0xa5, 0xea, - 0xb1, 0xa7, 0x9e, 0x7a, 0xa8, 0xd4, 0xef, 0x90, 0x43, 0x3e, 0x44, 0xa4, 0x1e, 0x9a, 0xe6, 0x54, - 0xf9, 0x60, 0x55, 0xf6, 0x21, 0x5f, 0xa3, 0xda, 0xdd, 0xd9, 0x7f, 0x36, 0xb4, 0xb8, 0xf4, 0x94, - 0x1b, 0xb3, 0xf3, 0x7b, 0xbf, 0xf7, 0xde, 0xef, 0xfd, 0x61, 0x17, 0x72, 0x6d, 0xd4, 0x1e, 0xf7, - 0x88, 0x5e, 0x6e, 0x33, 0x99, 0x32, 0xd4, 0xd5, 0x74, 0xb5, 0x3c, 0xdc, 0x2e, 0xb3, 0x91, 0x68, - 0x98, 0x84, 0x11, 0xe1, 0x06, 0xbf, 0x17, 0xfd, 0x7b, 0x71, 0xb8, 0x9d, 0x5d, 0x55, 0x89, 0x4a, - 0x6c, 0x44, 0xd9, 0xfa, 0xe5, 0x80, 0xb3, 0xeb, 0x32, 0xa1, 0x7d, 0x42, 0x5b, 0xce, 0x85, 0x73, - 0xe0, 0x57, 0x37, 0x9d, 0x53, 0xb9, 0x4f, 0x6d, 0xfe, 0x3e, 0x55, 0xf9, 0x45, 0x91, 0x5f, 0xc8, - 0xe6, 0xd8, 0x60, 0xa4, 0x4c, 0xb1, 0x6c, 0x54, 0x76, 0x76, 0xbb, 0xdb, 0xe5, 0x2e, 0x1e, 0xbb, - 0xc6, 0xc5, 0xc9, 0x41, 0x1a, 0xc8, 0x44, 0x7d, 0x17, 0xf3, 0x20, 0x80, 0x91, 0x3b, 0x58, 0xee, - 0x1a, 0x44, 0xd3, 0x99, 0x05, 0x0b, 0x3d, 0xe0, 0xe8, 0x4f, 0xb9, 0x57, 0x9f, 0xad, 0x8d, 0x19, - 0xda, 0x76, 0xcf, 0x1c, 0x95, 0x9f, 0xe2, 0x97, 0x18, 0x0e, 0xa0, 0xf8, 0x6b, 0x14, 0x6e, 0xd4, - 0xa8, 0xba, 0x6f, 0x62, 0xc4, 0x70, 0xb5, 0xb9, 0xff, 0x02, 0xf5, 0x34, 0x05, 0x31, 0x62, 0x0a, - 0x6b, 0x10, 0xa7, 0x9a, 0xaa, 0x63, 0x33, 0x13, 0x29, 0x44, 0x4a, 0x09, 0x89, 0x9f, 0x84, 0xaf, - 0x20, 0xa9, 0x60, 0x2a, 0x9b, 0x9a, 0xc1, 0x34, 0xa2, 0x67, 0x16, 0x0b, 0x91, 0x52, 0xb2, 0xb2, - 0x29, 0x72, 0xad, 0x7c, 0x85, 0xed, 0x70, 0xc4, 0x03, 0x1f, 0x2a, 0x05, 0xed, 0x84, 0x1a, 0x80, - 0x4c, 0xfa, 0x7d, 0x8d, 0x52, 0x8b, 0x25, 0x6a, 0xb9, 0xa8, 0x6e, 0x9d, 0x9c, 0xe6, 0x37, 0x1c, - 0x22, 0xaa, 0x74, 0x45, 0x8d, 0x94, 0xfb, 0x88, 0x75, 0xc4, 0x43, 0xac, 0x22, 0x79, 0x7c, 0x80, - 0xe5, 0x77, 0xaf, 0xb7, 0x80, 0xfb, 0x39, 0xc0, 0xb2, 0x14, 0x20, 0x10, 0xbe, 0x04, 0xe0, 0xa9, - 0xb6, 0x8c, 0x6e, 0x66, 0xc9, 0x0e, 0x2a, 0xef, 0x06, 0xe5, 0x54, 0x46, 0xf4, 0x2a, 0x23, 0xd6, - 0x07, 0xed, 0xe7, 0x78, 0x2c, 0x25, 0xb8, 0x49, 0xbd, 0x2b, 0xd4, 0x20, 0xde, 0x66, 0xb2, 0x65, - 0x1b, 0x2b, 0x44, 0x4a, 0xa9, 0xea, 0xee, 0xc9, 0x69, 0xbe, 0xa2, 0x6a, 0xac, 0x33, 0x68, 0x8b, - 0x32, 0xe9, 0x97, 0x39, 0x52, 0xee, 0x20, 0x4d, 0x77, 0x0f, 0x65, 0x36, 0x36, 0x30, 0x15, 0xab, - 0xcf, 0xea, 0x8f, 0x1e, 0x3f, 0xe4, 0x94, 0xb1, 0x36, 0x93, 0xeb, 0x5d, 0x61, 0x0f, 0xa2, 0x06, - 0x31, 0x32, 0x71, 0x3b, 0x8e, 0x92, 0x38, 0xb1, 0x05, 0xc5, 0xba, 0x49, 0xc8, 0xf1, 0xd7, 0xc7, - 0x75, 0x42, 0x29, 0xb6, 0xb3, 0x90, 0x2c, 0xa3, 0xbd, 0xe4, 0x0f, 0xef, 0x5f, 0xdd, 0xe7, 0x6a, - 0x17, 0xf3, 0x70, 0x6b, 0x62, 0x79, 0x24, 0x4c, 0x0d, 0xa2, 0x53, 0x5c, 0xfc, 0x25, 0x06, 0x6b, - 0x41, 0xc4, 0x01, 0xee, 0x61, 0x15, 0xd9, 0x12, 0x4f, 0xab, 0x60, 0x58, 0xab, 0xc5, 0x2b, 0x6b, - 0xc5, 0x93, 0x8b, 0xfe, 0x87, 0xe4, 0x84, 0x97, 0x90, 0xb6, 0x40, 0xd8, 0x6c, 0x71, 0xb9, 0x97, - 0xe6, 0x92, 0x3b, 0xe9, 0x90, 0x55, 0x6d, 0xd1, 0xbf, 0x81, 0x95, 0x21, 0xea, 0x71, 0xe2, 0x56, - 0x4f, 0xa3, 0x2c, 0x13, 0x2b, 0x44, 0xe7, 0x60, 0x4f, 0x0d, 0x51, 0xcf, 0xa6, 0x3e, 0xd4, 0x28, - 0x13, 0x6e, 0x43, 0x8a, 0xe7, 0xd7, 0x62, 0x5a, 0x1f, 0xdb, 0xc5, 0x4d, 0x3b, 0x11, 0x68, 0xba, - 0xda, 0xd4, 0xfa, 0x58, 0xd8, 0x74, 0xb2, 0xb3, 0x20, 0x43, 0xd4, 0x1b, 0xe0, 0xcc, 0x47, 0x85, - 0x48, 0x29, 0x2a, 0xb9, 0x76, 0x2f, 0xac, 0x67, 0xc2, 0x53, 0x00, 0x8f, 0x67, 0x94, 0xf9, 0xd8, - 0x56, 0xf1, 0xb3, 0xa0, 0x8a, 0x81, 0x59, 0x1f, 0x6e, 0x8b, 0x4d, 0x13, 0xe9, 0x14, 0xc9, 0x56, - 0x45, 0x9f, 0xe9, 0xc7, 0x44, 0x4a, 0xb8, 0x0e, 0x47, 0x42, 0x05, 0x92, 0xb4, 0x87, 0x68, 0x87, - 0x53, 0x25, 0x6c, 0x29, 0xaf, 0x9f, 0x9c, 0xe6, 0xd3, 0xd5, 0xe6, 0x7e, 0x83, 0xdf, 0x34, 0x47, - 0x12, 0x50, 0xef, 0xb7, 0xf0, 0x2d, 0xa4, 0x15, 0xa7, 0x45, 0x88, 0xd9, 0xa2, 0x9a, 0x9a, 0x01, - 0xdb, 0xea, 0xf3, 0x93, 0xd3, 0xfc, 0xce, 0x55, 0x24, 0x6a, 0x68, 0xaa, 0x8e, 0xd8, 0xc0, 0xc4, - 0x52, 0xca, 0xe3, 0x6b, 0x68, 0x6a, 0xb8, 0x7b, 0x0b, 0x90, 0x9b, 0xdc, 0x9b, 0x5e, 0xfb, 0xfe, - 0xbe, 0x08, 0xd7, 0x6a, 0x54, 0xad, 0x36, 0xf7, 0x8f, 0x74, 0xce, 0x83, 0xa7, 0x36, 0xee, 0x6d, - 0x48, 0x0d, 0xf4, 0x36, 0xd1, 0x15, 0x9e, 0xb0, 0xd5, 0xba, 0x29, 0x29, 0xe9, 0x3d, 0x6b, 0x8e, - 0x84, 0x3b, 0xb0, 0x1c, 0x80, 0x58, 0x65, 0x8a, 0xda, 0x65, 0x4a, 0xfb, 0x20, 0xab, 0x50, 0xf7, - 0x60, 0xc5, 0x87, 0x39, 0xa5, 0x5a, 0xb2, 0x4b, 0xe5, 0x5b, 0x3b, 0xc5, 0xba, 0x20, 0x71, 0x6c, - 0x16, 0x89, 0x09, 0xac, 0x05, 0x24, 0x76, 0xad, 0x2d, 0xad, 0xe3, 0xf3, 0x6a, 0xbd, 0xea, 0x6b, - 0xcd, 0x79, 0x2f, 0x69, 0x9e, 0x85, 0xcc, 0x45, 0x41, 0x3d, 0xb5, 0x7f, 0x5b, 0x84, 0xeb, 0x35, - 0xaa, 0x3e, 0x51, 0x94, 0x7d, 0x32, 0xc4, 0x3a, 0xd2, 0x59, 0x43, 0x53, 0xa7, 0xca, 0x5d, 0x83, - 0xb8, 0x35, 0x4f, 0x7c, 0x47, 0xcc, 0xb1, 0x13, 0x87, 0xa8, 0xe7, 0xac, 0x58, 0x05, 0xdb, 0x74, - 0xd1, 0xf9, 0xe8, 0x14, 0x6c, 0xd1, 0xdd, 0x85, 0x15, 0x7f, 0x8c, 0x5a, 0x1d, 0x44, 0x3b, 0x76, - 0x09, 0x13, 0x52, 0xda, 0x1b, 0x90, 0xa7, 0x88, 0x76, 0x84, 0xe7, 0x10, 0xb5, 0xa4, 0x8f, 0xcd, - 0x2b, 0xbd, 0xc5, 0x12, 0x56, 0x7a, 0x03, 0xd6, 0x2f, 0x89, 0xe9, 0x49, 0xfd, 0x73, 0x04, 0x56, - 0x6a, 0x54, 0x3d, 0x32, 0x14, 0xc4, 0x70, 0xdd, 0xfe, 0x9f, 0x17, 0x76, 0x21, 0x81, 0x06, 0xac, - 0x43, 0x4c, 0x8d, 0x8d, 0x1d, 0xad, 0xab, 0x99, 0x77, 0xaf, 0xb7, 0x56, 0xf9, 0xea, 0x7d, 0xa2, - 0x28, 0x26, 0xa6, 0xb4, 0xc1, 0x4c, 0x4d, 0x57, 0x25, 0x1f, 0x2a, 0x7c, 0x01, 0x71, 0xe7, 0x4d, - 0x81, 0x2f, 0xeb, 0x5b, 0xd3, 0x76, 0xae, 0x0d, 0xaa, 0x2e, 0xbd, 0x39, 0xcd, 0x2f, 0x48, 0xdc, - 0x64, 0x6f, 0xd9, 0x0a, 0xd9, 0x27, 0x2b, 0xae, 0xc3, 0xcd, 0x0b, 0x71, 0x79, 0x31, 0x9f, 0x47, - 0x61, 0x23, 0x9c, 0xd1, 0x91, 0x3b, 0x0d, 0x0d, 0x4d, 0xa5, 0x1f, 0x78, 0xa3, 0xc8, 0x70, 0x2d, - 0xb8, 0x5d, 0x5a, 0xff, 0x4b, 0xd7, 0x2c, 0x07, 0x96, 0x93, 0x35, 0x6b, 0x0c, 0xd6, 0xbd, 0x8d, - 0x70, 0xc9, 0xdb, 0xdc, 0xeb, 0x61, 0xcd, 0xe5, 0x3e, 0x0a, 0x79, 0x0d, 0xb7, 0xed, 0x1d, 0xd8, - 0xfc, 0x87, 0x22, 0xbb, 0xcd, 0x50, 0xf9, 0x23, 0x06, 0xd1, 0x1a, 0x55, 0x85, 0x11, 0x08, 0x13, - 0xde, 0x0e, 0x1f, 0x4c, 0x69, 0xc1, 0x89, 0x2f, 0x2b, 0xd9, 0xc7, 0x57, 0x41, 0xbb, 0x11, 0x08, - 0xdf, 0xc1, 0x27, 0x93, 0x5e, 0x6b, 0xb6, 0x66, 0x20, 0xf3, 0xe1, 0xd9, 0x9d, 0x2b, 0xc1, 0x3d, - 0xe7, 0x1a, 0xa4, 0xc3, 0x7f, 0x4a, 0xf7, 0xa6, 0xf3, 0x84, 0x80, 0xd9, 0xf2, 0x8c, 0x40, 0xcf, - 0x55, 0x0f, 0x96, 0x2f, 0x6c, 0xe4, 0xd2, 0x74, 0x8a, 0x30, 0x32, 0xfb, 0x70, 0x56, 0xa4, 0xe7, - 0xed, 0xc7, 0x08, 0x64, 0xa6, 0x4e, 0x78, 0x65, 0x26, 0xba, 0x90, 0x4d, 0x76, 0xef, 0xea, 0x36, - 0x5e, 0x30, 0xc7, 0x90, 0x0a, 0x6d, 0xc8, 0xbb, 0xd3, 0xb9, 0x82, 0xb8, 0xac, 0x38, 0x1b, 0xce, - 0xf5, 0x93, 0x8d, 0x7d, 0xff, 0xfe, 0xd5, 0xfd, 0x48, 0xf5, 0xf0, 0xcd, 0x59, 0x2e, 0xf2, 0xf6, - 0x2c, 0x17, 0xf9, 0xeb, 0x2c, 0x17, 0xf9, 0xe9, 0x3c, 0xb7, 0xf0, 0xf6, 0x3c, 0xb7, 0xf0, 0xe7, - 0x79, 0x6e, 0xe1, 0xe5, 0xbf, 0xee, 0x97, 0x51, 0xf0, 0x13, 0xca, 0x9e, 0xbe, 0x76, 0xdc, 0xfe, - 0x84, 0x7a, 0xf4, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x1a, 0xb9, 0xe0, 0x82, 0x0e, 0x00, - 0x00, + // 1145 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0x4f, 0x6f, 0x1a, 0xc7, + 0x1b, 0xf6, 0x1a, 0xc3, 0xef, 0xe7, 0x17, 0xb0, 0x93, 0xad, 0xe3, 0x60, 0xac, 0x00, 0xc1, 0x4d, + 0x42, 0xa3, 0x78, 0x37, 0x26, 0xb1, 0xa5, 0xba, 0x52, 0xa5, 0x60, 0xb7, 0x4a, 0x14, 0xa3, 0xa2, + 0x05, 0xe7, 0x50, 0xa9, 0x45, 0xc3, 0x32, 0x5e, 0x56, 0xc0, 0xce, 0x6a, 0x67, 0x40, 0xa0, 0x5e, + 0xaa, 0x1e, 0x2b, 0x55, 0xea, 0xa9, 0xb7, 0x7e, 0x87, 0x1c, 0xf2, 0x21, 0x72, 0x6b, 0x9a, 0x53, + 0xe5, 0x83, 0x55, 0xd9, 0x87, 0x7c, 0x82, 0xde, 0xab, 0xdd, 0x9d, 0xfd, 0x67, 0x43, 0x8b, 0xe3, + 0x1c, 0x7a, 0x63, 0x76, 0x9e, 0xf7, 0x79, 0xdf, 0x79, 0x9e, 0x77, 0xde, 0x65, 0x21, 0xd7, 0x42, + 0xad, 0x71, 0x8f, 0x18, 0x72, 0x8b, 0xa9, 0x94, 0xa1, 0xae, 0x6e, 0x68, 0xf2, 0x70, 0x4b, 0x66, + 0x23, 0xc9, 0xb4, 0x08, 0x23, 0xe2, 0x0d, 0xbe, 0x2f, 0x05, 0xfb, 0xd2, 0x70, 0x2b, 0xbb, 0xa2, + 0x11, 0x8d, 0x38, 0x08, 0xd9, 0xfe, 0xe5, 0x82, 0xb3, 0x6b, 0x2a, 0xa1, 0x7d, 0x42, 0x9b, 0xee, + 0x86, 0xbb, 0xe0, 0x5b, 0x37, 0xdd, 0x95, 0xdc, 0xa7, 0x0e, 0x7f, 0x9f, 0x6a, 0x7c, 0xa3, 0xc8, + 0x37, 0x54, 0x6b, 0x6c, 0x32, 0x22, 0x53, 0xac, 0x9a, 0xe5, 0xed, 0x9d, 0xee, 0x96, 0xdc, 0xc5, + 0x63, 0x2f, 0xb8, 0x38, 0xb9, 0x48, 0x13, 0x59, 0xa8, 0xef, 0x61, 0x1e, 0x84, 0x30, 0x6a, 0x07, + 0xab, 0x5d, 0x93, 0xe8, 0x06, 0xb3, 0x61, 0x91, 0x07, 0x1c, 0xfd, 0x31, 0xcf, 0x1a, 0xb0, 0xb5, + 0x30, 0x43, 0x5b, 0xde, 0x9a, 0xa3, 0xf2, 0x53, 0xf2, 0x12, 0xd3, 0x05, 0x14, 0x7f, 0x8d, 0xc1, + 0x8d, 0x2a, 0xd5, 0xf6, 0x2c, 0x8c, 0x18, 0xae, 0x34, 0xf6, 0x5e, 0xa0, 0x9e, 0xde, 0x46, 0x8c, + 0x58, 0xe2, 0x2a, 0x24, 0xa8, 0xae, 0x19, 0xd8, 0xca, 0x08, 0x05, 0xa1, 0xb4, 0xa8, 0xf0, 0x95, + 0xf8, 0x05, 0x24, 0xdb, 0x98, 0xaa, 0x96, 0x6e, 0x32, 0x9d, 0x18, 0x99, 0xf9, 0x82, 0x50, 0x4a, + 0x96, 0x37, 0x24, 0xae, 0x55, 0xa0, 0xb0, 0x53, 0x8e, 0xb4, 0x1f, 0x40, 0x95, 0x70, 0x9c, 0x58, + 0x05, 0x50, 0x49, 0xbf, 0xaf, 0x53, 0x6a, 0xb3, 0xc4, 0xec, 0x14, 0x95, 0xcd, 0xe3, 0x93, 0xfc, + 0xba, 0x4b, 0x44, 0xdb, 0x5d, 0x49, 0x27, 0x72, 0x1f, 0xb1, 0x8e, 0x74, 0x80, 0x35, 0xa4, 0x8e, + 0xf7, 0xb1, 0xfa, 0xf6, 0xd5, 0x26, 0xf0, 0x3c, 0xfb, 0x58, 0x55, 0x42, 0x04, 0xe2, 0xe7, 0x00, + 0xfc, 0xa8, 0x4d, 0xb3, 0x9b, 0x59, 0x70, 0x8a, 0xca, 0x7b, 0x45, 0xb9, 0xce, 0x48, 0xbe, 0x33, + 0x52, 0x6d, 0xd0, 0x7a, 0x8e, 0xc7, 0xca, 0x22, 0x0f, 0xa9, 0x75, 0xc5, 0x2a, 0x24, 0x5a, 0x4c, + 0xb5, 0x63, 0xe3, 0x05, 0xa1, 0x94, 0xaa, 0xec, 0x1c, 0x9f, 0xe4, 0xcb, 0x9a, 0xce, 0x3a, 0x83, + 0x96, 0xa4, 0x92, 0xbe, 0xcc, 0x91, 0x6a, 0x07, 0xe9, 0x86, 0xb7, 0x90, 0xd9, 0xd8, 0xc4, 0x54, + 0xaa, 0x3c, 0xab, 0x3d, 0x7a, 0xfc, 0x90, 0x53, 0xc6, 0x5b, 0x4c, 0xad, 0x75, 0xc5, 0x5d, 0x88, + 0x99, 0xc4, 0xcc, 0x24, 0x9c, 0x3a, 0x4a, 0xd2, 0xc4, 0x16, 0x94, 0x6a, 0x16, 0x21, 0x47, 0x5f, + 0x1d, 0xd5, 0x08, 0xa5, 0xd8, 0x39, 0x85, 0x62, 0x07, 0xed, 0x26, 0x7f, 0x78, 0xf7, 0xf2, 0x3e, + 0x57, 0xbb, 0x98, 0x87, 0x5b, 0x13, 0xed, 0x51, 0x30, 0x35, 0x89, 0x41, 0x71, 0xf1, 0xa7, 0x38, + 0xac, 0x86, 0x11, 0xfb, 0xb8, 0x87, 0x35, 0xe4, 0x48, 0x3c, 0xcd, 0xc1, 0xa8, 0x56, 0xf3, 0x97, + 0xd6, 0x8a, 0x1f, 0x2e, 0xf6, 0x1e, 0x87, 0x0b, 0xe9, 0xbc, 0xf0, 0x21, 0x74, 0xfe, 0x06, 0x96, + 0x87, 0xa8, 0xd7, 0x74, 0x29, 0x9b, 0x3d, 0x9d, 0xb2, 0x4c, 0xbc, 0x10, 0xbb, 0x02, 0x6f, 0x6a, + 0x88, 0x7a, 0x15, 0x9b, 0xfa, 0x40, 0xa7, 0x4c, 0xbc, 0x0d, 0x29, 0x7e, 0xa4, 0x26, 0xd3, 0xfb, + 0xd8, 0xf1, 0x33, 0xad, 0x24, 0xf9, 0xb3, 0x86, 0xde, 0xc7, 0xe2, 0x06, 0xa4, 0x3d, 0xc8, 0x10, + 0xf5, 0x06, 0x38, 0xf3, 0xbf, 0x82, 0x50, 0x8a, 0x29, 0x5e, 0xdc, 0x0b, 0xfb, 0x99, 0xf8, 0x14, + 0xc0, 0xe7, 0x19, 0x65, 0xfe, 0xef, 0x08, 0xf7, 0x49, 0x58, 0xb8, 0xd0, 0xf5, 0x1e, 0x6e, 0x49, + 0x0d, 0x0b, 0x19, 0x14, 0xa9, 0xb6, 0x89, 0xcf, 0x8c, 0x23, 0xa2, 0x2c, 0x7a, 0x09, 0x47, 0x62, + 0x19, 0x92, 0xb4, 0x87, 0x68, 0x87, 0x53, 0x2d, 0x3a, 0x22, 0x5e, 0x3f, 0x3e, 0xc9, 0xa7, 0x2b, + 0x8d, 0xbd, 0x3a, 0xdf, 0x69, 0x8c, 0x14, 0xa0, 0xfe, 0x6f, 0xf1, 0x5b, 0x48, 0xb7, 0xdd, 0xae, + 0x20, 0x56, 0x93, 0xea, 0x5a, 0x06, 0x9c, 0xa8, 0x4f, 0x8f, 0x4f, 0xf2, 0xdb, 0x97, 0x91, 0xa8, + 0xae, 0x6b, 0x06, 0x62, 0x03, 0x0b, 0x2b, 0x29, 0x9f, 0xaf, 0xae, 0x6b, 0xd1, 0x86, 0x2d, 0x40, + 0x6e, 0x72, 0x3b, 0xfa, 0x1d, 0xfb, 0xdb, 0x3c, 0x5c, 0xab, 0x52, 0xad, 0xd2, 0xd8, 0x3b, 0x34, + 0x38, 0x0f, 0x9e, 0xda, 0xab, 0xb7, 0x21, 0x35, 0x30, 0x5a, 0xc4, 0x68, 0xf3, 0x03, 0xdb, 0xdd, + 0x9a, 0x52, 0x92, 0xfe, 0xb3, 0xc6, 0x48, 0xbc, 0x03, 0x4b, 0x21, 0x88, 0x6d, 0x53, 0xcc, 0xb1, + 0x29, 0x1d, 0x80, 0x6c, 0xa3, 0xee, 0xc1, 0x72, 0x00, 0x73, 0xad, 0x5a, 0x70, 0xac, 0x0a, 0xa2, + 0x5d, 0xb3, 0xce, 0x49, 0x1c, 0x9f, 0x45, 0x62, 0x02, 0xab, 0x21, 0x89, 0xbd, 0x68, 0x5b, 0xeb, + 0xc4, 0x55, 0xb5, 0x5e, 0x09, 0xb4, 0xe6, 0xbc, 0x17, 0x34, 0xcf, 0x42, 0xe6, 0xbc, 0xa0, 0xbe, + 0xda, 0x7f, 0x09, 0x70, 0xbd, 0x4a, 0xb5, 0x27, 0xed, 0xf6, 0x1e, 0x19, 0x62, 0x03, 0x19, 0xac, + 0xae, 0x6b, 0x53, 0xe5, 0xfe, 0x12, 0xe6, 0xf9, 0x48, 0x78, 0xff, 0x2b, 0x34, 0x6f, 0x76, 0xc5, + 0xbb, 0xb0, 0x1c, 0x34, 0x7c, 0xb3, 0x83, 0x68, 0xc7, 0x1d, 0xf1, 0x4a, 0xda, 0x6f, 0xe5, 0xa7, + 0x88, 0x76, 0xc4, 0xe7, 0x10, 0xb3, 0x45, 0x5a, 0xb8, 0xaa, 0x48, 0x36, 0x4b, 0x54, 0x93, 0x75, + 0x58, 0xbb, 0x70, 0x6c, 0x5f, 0x94, 0x5f, 0x04, 0x58, 0xae, 0x52, 0xed, 0xd0, 0x6c, 0x23, 0x86, + 0x6b, 0xce, 0x4b, 0x58, 0xdc, 0x81, 0x45, 0x34, 0x60, 0x1d, 0x62, 0xe9, 0x6c, 0xec, 0xaa, 0x52, + 0xc9, 0xbc, 0x7d, 0xb5, 0xb9, 0xc2, 0xe7, 0xe2, 0x93, 0x76, 0xdb, 0xc2, 0x94, 0xd6, 0x99, 0xa5, + 0x1b, 0x9a, 0x12, 0x40, 0xc5, 0xcf, 0x20, 0xe1, 0xbe, 0xc6, 0xf9, 0x24, 0xbd, 0x35, 0x6d, 0x20, + 0x3a, 0xa0, 0xca, 0xc2, 0xeb, 0x93, 0xfc, 0x9c, 0xc2, 0x43, 0x76, 0x97, 0xec, 0x92, 0x03, 0xb2, + 0xe2, 0x1a, 0xdc, 0x3c, 0x57, 0x57, 0x30, 0xe8, 0x63, 0xb0, 0x1e, 0x3d, 0xd1, 0xa1, 0xd7, 0xb7, + 0x75, 0x5d, 0xa3, 0xff, 0x19, 0x4b, 0x55, 0xb8, 0x16, 0xbe, 0xb1, 0xcd, 0x0f, 0xe2, 0xef, 0x52, + 0xe8, 0xc2, 0xdb, 0xfd, 0xcb, 0x60, 0xcd, 0xbf, 0x65, 0x17, 0xb2, 0xc5, 0xaf, 0x9a, 0x6d, 0xd5, + 0xe3, 0x3e, 0x8c, 0x64, 0x8d, 0x36, 0xd8, 0x1d, 0xd8, 0xf8, 0x07, 0x3b, 0x3c, 0xdb, 0xca, 0xbf, + 0xc7, 0x21, 0x56, 0xa5, 0x9a, 0x38, 0x02, 0x71, 0xc2, 0x9f, 0xac, 0x07, 0x53, 0x9a, 0x65, 0xe2, + 0x3b, 0x3f, 0xfb, 0xf8, 0x32, 0x68, 0xaf, 0x02, 0xf1, 0x3b, 0xf8, 0x68, 0xd2, 0xbf, 0x83, 0xcd, + 0x19, 0xc8, 0x02, 0x78, 0x76, 0xfb, 0x52, 0x70, 0x3f, 0xb9, 0x0e, 0xe9, 0xe8, 0xa0, 0xbf, 0x37, + 0x9d, 0x27, 0x02, 0xcc, 0xca, 0x33, 0x02, 0xfd, 0x54, 0x3d, 0x58, 0x3a, 0x37, 0xe5, 0x4a, 0xd3, + 0x29, 0xa2, 0xc8, 0xec, 0xc3, 0x59, 0x91, 0x7e, 0xb6, 0x1f, 0x05, 0xc8, 0x4c, 0xbd, 0x8b, 0xe5, + 0x99, 0xe8, 0x22, 0x31, 0xd9, 0xdd, 0xcb, 0xc7, 0xf8, 0xc5, 0x1c, 0x41, 0x2a, 0x32, 0xcb, 0xee, + 0x4e, 0xe7, 0x0a, 0xe3, 0xb2, 0xd2, 0x6c, 0x38, 0x2f, 0x4f, 0x36, 0xfe, 0xfd, 0xbb, 0x97, 0xf7, + 0x85, 0xca, 0xc1, 0xeb, 0xd3, 0x9c, 0xf0, 0xe6, 0x34, 0x27, 0xfc, 0x79, 0x9a, 0x13, 0x7e, 0x3e, + 0xcb, 0xcd, 0xbd, 0x39, 0xcb, 0xcd, 0xfd, 0x71, 0x96, 0x9b, 0xfb, 0xfa, 0x5f, 0x87, 0xcb, 0x28, + 0xfc, 0x25, 0xe2, 0xdc, 0xbe, 0x56, 0xc2, 0xf9, 0x12, 0x79, 0xf4, 0x77, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x05, 0x25, 0x56, 0xb9, 0xc9, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1271,11 +1263,11 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) dAtA[i] = 0x2a } } - if m.StakerBtcPk != nil { + if m.BtcPk != nil { { - size := m.StakerBtcPk.Size() + size := m.BtcPk.Size() i -= size - if _, err := m.StakerBtcPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1464,32 +1456,20 @@ func (m *MsgAddCovenantSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x22 } if len(m.StakingTxHash) > 0 { i -= len(m.StakingTxHash) copy(dAtA[i:], m.StakingTxHash) i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) i-- - dAtA[i] = 0x22 - } - if m.DelPk != nil { - { - size := m.DelPk.Size() - i -= size - if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0x1a } - if m.ValPk != nil { + if m.Pk != nil { { - size := m.ValPk.Size() + size := m.Pk.Size() i -= size - if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.Pk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1623,7 +1603,7 @@ func (m *MsgAddCovenantUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, er i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 + dAtA[i] = 0x2a } if m.UnbondingTxSig != nil { { @@ -1635,32 +1615,20 @@ func (m *MsgAddCovenantUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, er i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x22 } if len(m.StakingTxHash) > 0 { i -= len(m.StakingTxHash) copy(dAtA[i:], m.StakingTxHash) i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) i-- - dAtA[i] = 0x22 - } - if m.DelPk != nil { - { - size := m.DelPk.Size() - i -= size - if _, err := m.DelPk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- dAtA[i] = 0x1a } - if m.ValPk != nil { + if m.Pk != nil { { - size := m.ValPk.Size() + size := m.Pk.Size() i -= size - if _, err := m.ValPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.Pk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1772,8 +1740,8 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovTx(uint64(l)) } - if m.StakerBtcPk != nil { - l = m.StakerBtcPk.Size() + if m.BtcPk != nil { + l = m.BtcPk.Size() n += 1 + l + sovTx(uint64(l)) } if len(m.ValBtcPkList) > 0 { @@ -1862,12 +1830,8 @@ func (m *MsgAddCovenantSig) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.ValPk != nil { - l = m.ValPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.DelPk != nil { - l = m.DelPk.Size() + if m.Pk != nil { + l = m.Pk.Size() n += 1 + l + sovTx(uint64(l)) } l = len(m.StakingTxHash) @@ -1924,12 +1888,8 @@ func (m *MsgAddCovenantUnbondingSigs) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.ValPk != nil { - l = m.ValPk.Size() - n += 1 + l + sovTx(uint64(l)) - } - if m.DelPk != nil { - l = m.DelPk.Size() + if m.Pk != nil { + l = m.Pk.Size() n += 1 + l + sovTx(uint64(l)) } l = len(m.StakingTxHash) @@ -2408,7 +2368,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakerBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2436,8 +2396,8 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.StakerBtcPk = &v - if err := m.StakerBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.BtcPk = &v + if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3028,7 +2988,7 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3056,47 +3016,12 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValPk = &v - if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Pk = &v + if err := m.Pk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.DelPk = &v - if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) } @@ -3128,7 +3053,7 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { } m.StakingTxHash = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) } @@ -3462,7 +3387,7 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3490,47 +3415,12 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValPk = &v - if err := m.ValPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Pk = &v + if err := m.Pk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelPk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.DelPk = &v - if err := m.DelPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) } @@ -3562,7 +3452,7 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { } m.StakingTxHash = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) } @@ -3597,7 +3487,7 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 6: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSig", wireType) } diff --git a/x/checkpointing/client/cli/tx.go b/x/checkpointing/client/cli/tx.go index 7dc4aba4f..bfcdd7eff 100644 --- a/x/checkpointing/client/cli/tx.go +++ b/x/checkpointing/client/cli/tx.go @@ -1,15 +1,16 @@ package cli import ( - "cosmossdk.io/core/address" "fmt" - appparams "github.com/babylonchain/babylon/app/params" - authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "os" "path/filepath" "strconv" "strings" + "cosmossdk.io/core/address" + appparams "github.com/babylonchain/babylon/app/params" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + "github.com/babylonchain/babylon/crypto/bls12381" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -40,7 +41,7 @@ func GetTxCmd() *cobra.Command { func CmdTxAddBlsSig() *cobra.Command { cmd := &cobra.Command{ - Use: "submit [epoch_number] [last_commit_hash] [bls_sig] [signer address]", + Use: "submit [epoch_number] [app_hash] [bls_sig] [signer address]", Short: "submit a BLS signature", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index 58041b52e..e75423a4e 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -336,7 +336,7 @@ func FuzzAddBlsSig_CkptNotExist(f *testing.F) { } // FuzzAddBlsSig_WrongAppHash tests adding BLS signatures via MsgAddBlsSig -// in a scenario where the signature is signed over wrong last_commit_hash +// in a scenario where the signature is signed over wrong app_hash // 4. a BLS signature is rejected if the signature is invalid func FuzzAddBlsSig_WrongAppHash(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 4) diff --git a/x/checkpointing/types/checkpoint.pb.go b/x/checkpointing/types/checkpoint.pb.go index 3ba6609ef..ee8a7af89 100644 --- a/x/checkpointing/types/checkpoint.pb.go +++ b/x/checkpointing/types/checkpoint.pb.go @@ -276,7 +276,7 @@ func (m *CheckpointStateUpdate) GetBlockTime() *time.Time { type BlsSig struct { // epoch_num defines the epoch number that the BLS sig is signed on EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` - // last_commit_hash defines the 'AppHash' that the BLS sig is signed on + // app_hash defines the 'AppHash' that the BLS sig is signed on AppHash *AppHash `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3,customtype=AppHash" json:"app_hash,omitempty"` BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` // can't find cosmos_proto.scalar when compiling due to cosmos v0.45.4 does diff --git a/x/checkpointing/types/utils.go b/x/checkpointing/types/utils.go index d1a6c80b3..a7594b96b 100644 --- a/x/checkpointing/types/utils.go +++ b/x/checkpointing/types/utils.go @@ -36,7 +36,7 @@ func (m RawCheckpoint) HashStr() string { } // SignedMsg is the message corresponding to the BLS sig in this raw checkpoint -// Its value should be (epoch_number || last_commit_hash) +// Its value should be (epoch_number || app_hash) func (m RawCheckpoint) SignedMsg() []byte { return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.AppHash...) } diff --git a/x/epoching/keeper/epochs.go b/x/epoching/keeper/epochs.go index 567ad1909..89dae1049 100644 --- a/x/epoching/keeper/epochs.go +++ b/x/epoching/keeper/epochs.go @@ -2,6 +2,7 @@ package keeper import ( "context" + errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" @@ -105,7 +106,7 @@ func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx context.Context) error { // RecordSealerHeaderForPrevEpoch records the sealer header for the previous epoch, // where the sealer header of an epoch is the 2nd header of the next epoch -// This validator set of the epoch has generated a BLS multisig on `last_commit_hash` of the sealer header +// This validator set of the epoch has generated a BLS multisig on `app_hash` of the sealer header func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch { // get the sealer header epoch := k.GetEpoch(ctx) diff --git a/x/epoching/types/epoching.pb.go b/x/epoching/types/epoching.pb.go index ffa316a79..a09931f41 100644 --- a/x/epoching/types/epoching.pb.go +++ b/x/epoching/types/epoching.pb.go @@ -84,7 +84,7 @@ type Epoch struct { // It will be used for proving a block is in an epoch AppHashRoot []byte `protobuf:"bytes,5,opt,name=app_hash_root,json=appHashRoot,proto3" json:"app_hash_root,omitempty"` // sealer_header is the 2nd header of the next epoch - // This validator set has generated a BLS multisig on `last_commit_hash` of + // This validator set has generated a BLS multisig on `app_hash` of // the sealer header SealerHeader *types.Header `protobuf:"bytes,6,opt,name=sealer_header,json=sealerHeader,proto3" json:"sealer_header,omitempty"` } diff --git a/x/finality/client/cli/tx.go b/x/finality/client/cli/tx.go index b7c4d8aca..7419d0fa8 100644 --- a/x/finality/client/cli/tx.go +++ b/x/finality/client/cli/tx.go @@ -94,7 +94,7 @@ func NewCommitPubRandListCmd() *cobra.Command { func NewAddFinalitySigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-finality-sig [val_btc_pk] [block_height] [block_last_commit_hash] [finality_sig]", + Use: "add-finality-sig [val_btc_pk] [block_height] [block_app_hash] [finality_sig]", Args: cobra.ExactArgs(4), Short: "Add a finality signature", Long: strings.TrimSpace( @@ -131,11 +131,11 @@ func NewAddFinalitySigCmd() *cobra.Command { } msg := types.MsgAddFinalitySig{ - Signer: clientCtx.FromAddress.String(), - ValBtcPk: valBTCPK, - BlockHeight: blockHeight, + Signer: clientCtx.FromAddress.String(), + ValBtcPk: valBTCPK, + BlockHeight: blockHeight, BlockAppHash: blockLch, - FinalitySig: finalitySig, + FinalitySig: finalitySig, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index 7fc2fbd68..c510d0d8d 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -28,8 +28,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type IndexedBlock struct { // height is the height of the block Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` - // last_commit_hash is the last_commit_hash of the block - AppHash []byte `protobuf:"bytes,2,opt,name=last_commit_hash,json=appHash,proto3" json:"last_commit_hash,omitempty"` + // app_hash is the AppHash of the block + AppHash []byte `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` // finalized indicates whether the IndexedBlock is finalised by 2/3 // BTC validators or not Finalized bool `protobuf:"varint,3,opt,name=finalized,proto3" json:"finalized,omitempty"` @@ -98,10 +98,10 @@ type Evidence struct { BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` // pub_rand is the public randomness the BTC validator has committed to PubRand *github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,3,opt,name=pub_rand,json=pubRand,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand,omitempty"` - // canonical_last_commit_hash is the last_commit_hash of the canonical block - CanonicalAppHash []byte `protobuf:"bytes,4,opt,name=canonical_last_commit_hash,json=canonicalAppHash,proto3" json:"canonical_last_commit_hash,omitempty"` - // fork_last_commit_hash is the last_commit_hash of the fork block - ForkAppHash []byte `protobuf:"bytes,5,opt,name=fork_last_commit_hash,json=forkAppHash,proto3" json:"fork_last_commit_hash,omitempty"` + // canonical_app_hash is the AppHash of the canonical block + CanonicalAppHash []byte `protobuf:"bytes,4,opt,name=canonical_app_hash,json=canonicalAppHash,proto3" json:"canonical_app_hash,omitempty"` + // fork_app_hash is the AppHash of the fork block + ForkAppHash []byte `protobuf:"bytes,5,opt,name=fork_app_hash,json=forkAppHash,proto3" json:"fork_app_hash,omitempty"` // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` @@ -176,35 +176,34 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 440 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x6f, 0xd3, 0x30, - 0x14, 0xc7, 0x9b, 0x51, 0xba, 0x62, 0x22, 0x7e, 0x98, 0x31, 0xaa, 0x09, 0x65, 0xa5, 0xa7, 0x9e, - 0x9a, 0x95, 0x4d, 0x08, 0x89, 0x5b, 0xd0, 0xd0, 0x06, 0x48, 0x54, 0xc9, 0x4e, 0x5c, 0x2c, 0xdb, - 0xf1, 0x62, 0xab, 0xa9, 0x1d, 0x25, 0x4e, 0xb4, 0xf0, 0x57, 0xf0, 0x17, 0xf0, 0xf7, 0x70, 0xdc, - 0x11, 0xed, 0x30, 0xa1, 0xf6, 0x1f, 0x41, 0x71, 0xb2, 0x94, 0xb1, 0x03, 0x88, 0xdd, 0x9e, 0xbf, - 0x7e, 0xef, 0x7d, 0xde, 0xf7, 0x25, 0x06, 0x23, 0x82, 0x49, 0x19, 0x2b, 0xe9, 0x9e, 0x0a, 0x89, - 0x63, 0xa1, 0x4b, 0xb7, 0x98, 0xb6, 0xf1, 0x24, 0x49, 0x95, 0x56, 0xf0, 0x49, 0x93, 0x33, 0x69, - 0xf5, 0x62, 0xba, 0xb3, 0x15, 0xa9, 0x48, 0x99, 0x7b, 0xb7, 0x8a, 0xea, 0xd4, 0x91, 0x04, 0xf6, - 0xb1, 0x0c, 0xd9, 0x19, 0x0b, 0xbd, 0x58, 0xd1, 0x39, 0xdc, 0x06, 0x3d, 0xce, 0x44, 0xc4, 0xf5, - 0xc0, 0x1a, 0x5a, 0xe3, 0xae, 0xdf, 0x9c, 0xe0, 0x18, 0x3c, 0x8a, 0x71, 0xa6, 0x11, 0x55, 0x8b, - 0x85, 0xd0, 0x88, 0xe3, 0x8c, 0x0f, 0x36, 0x86, 0xd6, 0xd8, 0xf6, 0x1f, 0x54, 0xfa, 0x5b, 0x23, - 0x1f, 0xe1, 0x8c, 0xc3, 0xe7, 0xe0, 0x5e, 0x8d, 0xfd, 0xc2, 0xc2, 0xc1, 0x9d, 0xa1, 0x35, 0xee, - 0xfb, 0x6b, 0x61, 0xf4, 0xad, 0x0b, 0xfa, 0x87, 0x85, 0x08, 0x99, 0xa4, 0x0c, 0x9e, 0x00, 0x50, - 0xe0, 0x18, 0x11, 0x4d, 0x51, 0x32, 0x37, 0x40, 0xdb, 0x7b, 0x75, 0x71, 0xb9, 0xfb, 0x32, 0x12, - 0x9a, 0xe7, 0x64, 0x42, 0xd5, 0xc2, 0x6d, 0xac, 0x50, 0x8e, 0x85, 0xbc, 0x3a, 0xb8, 0xba, 0x4c, - 0x58, 0x36, 0xf1, 0x8e, 0x67, 0xfb, 0x07, 0x7b, 0xb3, 0x9c, 0x7c, 0x60, 0xa5, 0xdf, 0x2f, 0x70, - 0xec, 0x69, 0x3a, 0x9b, 0xc3, 0x17, 0xc0, 0x26, 0x95, 0x17, 0xd4, 0x18, 0xd9, 0x30, 0x46, 0xee, - 0x1b, 0xed, 0xa8, 0x76, 0x13, 0x80, 0x7e, 0x92, 0x13, 0x94, 0x62, 0x59, 0x8f, 0x68, 0x7b, 0xaf, - 0x2f, 0x2e, 0x77, 0x0f, 0xfe, 0x0d, 0x1b, 0x50, 0x2e, 0x55, 0x9a, 0xce, 0x72, 0xe2, 0x63, 0x19, - 0xfa, 0x9b, 0x49, 0x1d, 0xc0, 0x37, 0x60, 0x87, 0x62, 0xa9, 0xa4, 0xa0, 0x38, 0x46, 0x37, 0x96, - 0xd5, 0x35, 0xcb, 0x7a, 0xd6, 0x66, 0x7c, 0xbc, 0xbe, 0xb5, 0x29, 0x78, 0x7a, 0xaa, 0xd2, 0xf9, - 0xcd, 0xba, 0xbb, 0xa6, 0x0e, 0x56, 0x97, 0x7f, 0x94, 0x48, 0xb0, 0xbd, 0xe6, 0x5d, 0x7d, 0x69, - 0x94, 0x89, 0x68, 0xd0, 0xfb, 0x4f, 0x4b, 0x87, 0x9f, 0x4e, 0x82, 0x40, 0x44, 0xfe, 0x56, 0xdb, - 0xf7, 0x5d, 0xd3, 0x36, 0x10, 0x11, 0x0c, 0xc1, 0x63, 0x33, 0xe2, 0x35, 0xd4, 0xe6, 0x2d, 0x51, - 0x0f, 0xab, 0x96, 0xbf, 0x51, 0xbc, 0xf7, 0xdf, 0x97, 0x8e, 0x75, 0xbe, 0x74, 0xac, 0x9f, 0x4b, - 0xc7, 0xfa, 0xba, 0x72, 0x3a, 0xe7, 0x2b, 0xa7, 0xf3, 0x63, 0xe5, 0x74, 0x3e, 0xef, 0xfd, 0x0d, - 0x70, 0xb6, 0x7e, 0x13, 0x86, 0x45, 0x7a, 0xe6, 0x1f, 0xdf, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, - 0x5e, 0x45, 0x73, 0x06, 0x34, 0x03, 0x00, 0x00, + // 425 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0x4f, 0x6f, 0xd3, 0x30, + 0x18, 0x87, 0x9b, 0x51, 0xda, 0xe0, 0x05, 0x01, 0x66, 0x9a, 0x0a, 0x42, 0x59, 0xe9, 0xa9, 0x07, + 0xd4, 0x6c, 0x6c, 0x42, 0x5c, 0x89, 0x34, 0xb4, 0xc1, 0x81, 0x2a, 0xd9, 0x89, 0x8b, 0x65, 0x3b, + 0x5e, 0x6c, 0x35, 0xd8, 0x56, 0xfe, 0x69, 0xe1, 0xc4, 0x47, 0xe0, 0x63, 0x71, 0xdc, 0x11, 0xed, + 0x30, 0xa1, 0xf6, 0x8b, 0xa0, 0x38, 0x69, 0x02, 0x27, 0x10, 0xdc, 0x5e, 0xbf, 0xfe, 0xe9, 0x7d, + 0x1e, 0xbd, 0x36, 0x98, 0x11, 0x4c, 0xaa, 0x44, 0x49, 0xef, 0x52, 0x48, 0x9c, 0x88, 0xbc, 0xf2, + 0xca, 0xa3, 0xae, 0x5e, 0xe8, 0x54, 0xe5, 0x0a, 0x3e, 0x6e, 0x33, 0x8b, 0xae, 0x5f, 0x1e, 0x3d, + 0xdd, 0x8b, 0x55, 0xac, 0xcc, 0xbd, 0x57, 0x57, 0x4d, 0x74, 0x86, 0x80, 0x73, 0x2e, 0x23, 0x76, + 0xc5, 0x22, 0x3f, 0x51, 0x74, 0x05, 0xf7, 0xc1, 0x88, 0x33, 0x11, 0xf3, 0x7c, 0x62, 0x4d, 0xad, + 0xf9, 0x30, 0x68, 0x4f, 0xf0, 0x09, 0xb0, 0xb1, 0xd6, 0x88, 0xe3, 0x8c, 0x4f, 0x76, 0xa6, 0xd6, + 0xdc, 0x09, 0xc6, 0x58, 0xeb, 0x33, 0x9c, 0x71, 0xf8, 0x0c, 0xdc, 0x6b, 0x38, 0x9f, 0x59, 0x34, + 0xb9, 0x33, 0xb5, 0xe6, 0x76, 0xd0, 0x37, 0x66, 0x5f, 0x86, 0xc0, 0x3e, 0x2d, 0x45, 0xc4, 0x24, + 0x65, 0xf0, 0x02, 0x80, 0x12, 0x27, 0x88, 0xe4, 0x14, 0xe9, 0x95, 0x21, 0x38, 0xfe, 0xab, 0x9b, + 0xdb, 0x83, 0x97, 0xb1, 0xc8, 0x79, 0x41, 0x16, 0x54, 0x7d, 0xf2, 0x5a, 0x77, 0xca, 0xb1, 0x90, + 0xdb, 0x83, 0x97, 0x57, 0x9a, 0x65, 0x0b, 0xff, 0x7c, 0x79, 0x7c, 0x72, 0xb8, 0x2c, 0xc8, 0x7b, + 0x56, 0x05, 0x76, 0x89, 0x13, 0x3f, 0xa7, 0xcb, 0x15, 0x7c, 0x0e, 0x1c, 0x52, 0xcb, 0xa3, 0xd6, + 0x7c, 0xc7, 0x98, 0xef, 0x9a, 0xde, 0x59, 0xa3, 0x1f, 0x02, 0x5b, 0x17, 0x04, 0xa5, 0x58, 0x36, + 0x8a, 0x8e, 0xff, 0xfa, 0xe6, 0xf6, 0xe0, 0xe4, 0xef, 0xb0, 0x21, 0xe5, 0x52, 0xa5, 0xe9, 0xb2, + 0x20, 0x01, 0x96, 0x51, 0x30, 0xd6, 0x4d, 0x01, 0x5f, 0x00, 0x48, 0xb1, 0x54, 0x52, 0x50, 0x9c, + 0xa0, 0x6e, 0x3b, 0x43, 0xb3, 0x9d, 0x87, 0xdd, 0xcd, 0x9b, 0x76, 0x4d, 0x33, 0x70, 0xff, 0x52, + 0xa5, 0xab, 0x3e, 0x78, 0xd7, 0x04, 0x77, 0xeb, 0xe6, 0x36, 0x23, 0xc1, 0x7e, 0x3f, 0x71, 0xfb, + 0x78, 0x28, 0x13, 0xf1, 0x64, 0xf4, 0x8f, 0xd2, 0xa7, 0x1f, 0x2e, 0xc2, 0x50, 0xc4, 0xc1, 0x5e, + 0x37, 0xf7, 0x6d, 0x3b, 0x36, 0x14, 0x31, 0x8c, 0xc0, 0x23, 0xe3, 0xf4, 0x1b, 0x6a, 0xfc, 0x9f, + 0xa8, 0x07, 0xf5, 0xc8, 0x5f, 0x28, 0xfe, 0xbb, 0x6f, 0x6b, 0xd7, 0xba, 0x5e, 0xbb, 0xd6, 0x8f, + 0xb5, 0x6b, 0x7d, 0xdd, 0xb8, 0x83, 0xeb, 0x8d, 0x3b, 0xf8, 0xbe, 0x71, 0x07, 0x1f, 0x0f, 0xff, + 0x04, 0xb8, 0xea, 0xbf, 0xb9, 0x61, 0x91, 0x91, 0xf9, 0xb6, 0xc7, 0x3f, 0x03, 0x00, 0x00, 0xff, + 0xff, 0x34, 0x61, 0xea, 0x06, 0x07, 0x03, 0x00, 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index 1b1e87ff5..259739472 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -38,8 +38,8 @@ type MsgAddFinalitySig struct { ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` // block_height is the height of the voted block BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // block_last_commit_hash is the last_commit_hash of the voted block - BlockAppHash []byte `protobuf:"bytes,4,opt,name=block_last_commit_hash,json=blockAppHash,proto3" json:"block_last_commit_hash,omitempty"` + // block_app_hash is the AppHash of the voted block + BlockAppHash []byte `protobuf:"bytes,4,opt,name=block_app_hash,json=blockAppHash,proto3" json:"block_app_hash,omitempty"` // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` @@ -348,48 +348,48 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_2dd6da066b6baf1d) } var fileDescriptor_2dd6da066b6baf1d = []byte{ - // 653 bytes of a gzipped FileDescriptorProto + // 644 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x4f, 0x4f, 0x13, 0x41, - 0x14, 0xef, 0xb6, 0x85, 0xc8, 0xb4, 0x62, 0x58, 0x08, 0x2c, 0x85, 0x6c, 0x6b, 0x63, 0x0c, 0x12, - 0xdd, 0xe5, 0x9f, 0x44, 0xb8, 0x51, 0xa3, 0x41, 0xa1, 0xb1, 0xd9, 0xe2, 0x45, 0x4d, 0x36, 0xb3, - 0x7f, 0x98, 0x9d, 0xb0, 0xbb, 0xb3, 0xee, 0x4c, 0x1b, 0x7a, 0x33, 0x7e, 0x02, 0x0f, 0x7e, 0x10, - 0x0e, 0x7e, 0x03, 0x2f, 0x5c, 0x4c, 0x88, 0x27, 0xc3, 0xa1, 0x1a, 0x38, 0xf0, 0x35, 0x4c, 0x77, - 0x67, 0x29, 0xb4, 0x25, 0x56, 0x0f, 0xde, 0x76, 0xe6, 0xf7, 0xdb, 0xf7, 0x7e, 0xef, 0xf7, 0xde, - 0x1b, 0x30, 0x6f, 0x40, 0xa3, 0xe5, 0x12, 0x5f, 0xdd, 0xc7, 0x3e, 0x74, 0x31, 0x6b, 0xa9, 0xcd, - 0x65, 0x95, 0x1d, 0x2a, 0x41, 0x48, 0x18, 0x11, 0x27, 0x39, 0xaa, 0x24, 0xa8, 0xd2, 0x5c, 0x2e, - 0x4c, 0x21, 0x82, 0x48, 0x84, 0xab, 0x9d, 0xaf, 0x98, 0x5a, 0x98, 0x35, 0x09, 0xf5, 0x08, 0xd5, - 0x63, 0x20, 0x3e, 0x70, 0x68, 0x26, 0x3e, 0xa9, 0x1e, 0x45, 0x9d, 0xe8, 0x1e, 0x45, 0x1c, 0x28, - 0x0d, 0x4a, 0x1e, 0xc0, 0x10, 0x7a, 0xfc, 0xd7, 0xf2, 0xd7, 0x34, 0x98, 0xa8, 0x52, 0xb4, 0x65, - 0x59, 0xcf, 0x39, 0xa5, 0x8e, 0x91, 0x38, 0x0d, 0x46, 0x29, 0x46, 0xbe, 0x1d, 0x4a, 0x42, 0x49, - 0x58, 0x18, 0xd3, 0xf8, 0x49, 0xdc, 0x03, 0xa0, 0x09, 0x5d, 0xdd, 0x60, 0xa6, 0x1e, 0x1c, 0x48, - 0xe9, 0x92, 0xb0, 0x90, 0xaf, 0xac, 0x9f, 0xb6, 0x8b, 0x2b, 0x08, 0x33, 0xa7, 0x61, 0x28, 0x26, - 0xf1, 0x54, 0x9e, 0xd2, 0x74, 0x20, 0xf6, 0x93, 0x83, 0xca, 0x5a, 0x81, 0x4d, 0x95, 0xca, 0x8b, - 0xda, 0xea, 0xda, 0x52, 0xad, 0x61, 0xec, 0xd8, 0x2d, 0xed, 0x56, 0x13, 0xba, 0x15, 0x66, 0xd6, - 0x0e, 0xc4, 0xbb, 0x20, 0x6f, 0xb8, 0xc4, 0x3c, 0xd0, 0x1d, 0x1b, 0x23, 0x87, 0x49, 0x99, 0x92, - 0xb0, 0x90, 0xd5, 0x72, 0xd1, 0xdd, 0x76, 0x74, 0x25, 0xae, 0x82, 0xe9, 0x98, 0xe2, 0x42, 0xca, - 0x74, 0x93, 0x78, 0x1e, 0x66, 0xba, 0x03, 0xa9, 0x23, 0x65, 0x3b, 0x22, 0xb4, 0xc9, 0x08, 0xdd, - 0x85, 0x94, 0x3d, 0x8d, 0xb0, 0x6d, 0x48, 0x1d, 0xf1, 0x2d, 0xc8, 0x27, 0x75, 0xeb, 0x14, 0x23, - 0x69, 0x24, 0xd2, 0xfb, 0xe4, 0xb4, 0x5d, 0x5c, 0x1b, 0x4e, 0x6f, 0xdd, 0x74, 0x7c, 0x12, 0x86, - 0xcf, 0x5e, 0xed, 0xd5, 0xeb, 0x18, 0x69, 0xb9, 0xfd, 0xae, 0x45, 0x9b, 0xb9, 0x8f, 0x17, 0x47, - 0x8b, 0xdc, 0x97, 0xf2, 0x1c, 0x98, 0xed, 0x33, 0x51, 0xb3, 0x69, 0x40, 0x7c, 0x6a, 0x97, 0x7f, - 0xa6, 0xc1, 0x54, 0x95, 0xa2, 0x58, 0x58, 0xad, 0x61, 0x68, 0xd0, 0xb7, 0x76, 0x31, 0x65, 0xff, - 0xdf, 0x65, 0xca, 0x60, 0xc8, 0x7a, 0x5c, 0x8e, 0xee, 0xb8, 0xcb, 0xef, 0xc0, 0xed, 0xa0, 0x61, - 0xe8, 0x21, 0xf4, 0x2d, 0xdd, 0xc5, 0x94, 0x49, 0xd9, 0x52, 0xe6, 0x9f, 0x1c, 0xe3, 0x55, 0x6a, - 0xb9, 0xe0, 0x4a, 0xb9, 0x3b, 0x20, 0xd3, 0xed, 0xc2, 0xc6, 0x69, 0xbb, 0xf8, 0xf8, 0x6f, 0xea, - 0xa9, 0x63, 0xe4, 0x43, 0xd6, 0x08, 0x6d, 0xad, 0x13, 0xe5, 0xba, 0xfd, 0x32, 0x98, 0x1f, 0x64, - 0xf0, 0x65, 0x07, 0x3e, 0x0b, 0xe0, 0x4e, 0x95, 0xa2, 0xd7, 0x81, 0x05, 0x99, 0x5d, 0x8b, 0xc6, - 0x5f, 0x5c, 0x07, 0x63, 0xb0, 0xc1, 0x1c, 0x12, 0x62, 0xd6, 0x8a, 0xfd, 0xaf, 0x48, 0xdf, 0xbf, - 0x3c, 0x9a, 0xe2, 0x8b, 0xb5, 0x65, 0x59, 0xa1, 0x4d, 0x69, 0x9d, 0x85, 0xd8, 0x47, 0x5a, 0x97, - 0x2a, 0x6e, 0x80, 0xd1, 0x78, 0x81, 0xa2, 0xc6, 0xe4, 0x56, 0xe6, 0x94, 0x01, 0x2b, 0xac, 0xc4, - 0x49, 0x2a, 0xd9, 0xe3, 0x76, 0x31, 0xa5, 0xf1, 0x1f, 0x36, 0xc7, 0x3b, 0x9a, 0xbb, 0xa1, 0xca, - 0xb3, 0x60, 0xa6, 0x47, 0x55, 0xa2, 0x78, 0xe5, 0x5b, 0x1a, 0x64, 0xaa, 0x14, 0x89, 0x0e, 0x18, - 0xef, 0x59, 0xcd, 0xfb, 0x03, 0xf3, 0xf5, 0x4d, 0x5f, 0x41, 0x19, 0x8e, 0x97, 0x64, 0x14, 0xdf, - 0x83, 0x89, 0xfe, 0x09, 0x7d, 0x70, 0x53, 0x90, 0x3e, 0x6a, 0x61, 0x79, 0x68, 0xea, 0x65, 0x4a, - 0x03, 0xe4, 0xaf, 0xb5, 0xe4, 0xde, 0x4d, 0x21, 0xae, 0xb2, 0x0a, 0x0f, 0x87, 0x61, 0x25, 0x39, - 0x0a, 0x23, 0x1f, 0x2e, 0x8e, 0x16, 0x85, 0xca, 0xcb, 0xe3, 0x33, 0x59, 0x38, 0x39, 0x93, 0x85, - 0x5f, 0x67, 0xb2, 0xf0, 0xe9, 0x5c, 0x4e, 0x9d, 0x9c, 0xcb, 0xa9, 0x1f, 0xe7, 0x72, 0xea, 0xcd, - 0xd2, 0x9f, 0x86, 0xf0, 0xb0, 0xfb, 0x78, 0x46, 0xf3, 0x68, 0x8c, 0x46, 0x2f, 0xe7, 0xea, 0xef, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xdf, 0xe0, 0xeb, 0xda, 0x05, 0x00, 0x00, + 0x14, 0xef, 0xb6, 0x85, 0xc8, 0xb4, 0x62, 0x58, 0x89, 0x2c, 0x85, 0x6c, 0x6b, 0x43, 0x0c, 0x12, + 0xdd, 0xe5, 0x9f, 0x44, 0xb8, 0x51, 0xa3, 0x41, 0xb1, 0xb1, 0xd9, 0xe2, 0x45, 0x4d, 0x36, 0xb3, + 0x7f, 0x98, 0x9d, 0xd0, 0xdd, 0x19, 0x77, 0xa6, 0x0d, 0xbd, 0x19, 0x3f, 0x81, 0x07, 0x3f, 0x08, + 0x07, 0x3e, 0x04, 0x17, 0x13, 0xe2, 0xc9, 0x70, 0xa8, 0x06, 0x0e, 0x7c, 0x0d, 0xd3, 0xdd, 0x59, + 0x0a, 0xb4, 0xc4, 0xea, 0xc1, 0xdb, 0xbe, 0x79, 0xbf, 0xf7, 0x7e, 0xef, 0xfd, 0xde, 0x7b, 0x0b, + 0x66, 0x2d, 0x68, 0xb5, 0x1b, 0x24, 0xd0, 0x77, 0x71, 0x00, 0x1b, 0x98, 0xb7, 0xf5, 0xd6, 0x92, + 0xce, 0xf7, 0x35, 0x1a, 0x12, 0x4e, 0xe4, 0xbb, 0xc2, 0xab, 0x25, 0x5e, 0xad, 0xb5, 0x54, 0x98, + 0x44, 0x04, 0x91, 0xc8, 0xaf, 0x77, 0xbf, 0x62, 0x68, 0x61, 0xda, 0x26, 0xcc, 0x27, 0xcc, 0x8c, + 0x1d, 0xb1, 0x21, 0x5c, 0x53, 0xb1, 0xa5, 0xfb, 0x0c, 0x75, 0xb3, 0xfb, 0x0c, 0x09, 0x47, 0x69, + 0x10, 0x39, 0x85, 0x21, 0xf4, 0x45, 0x68, 0xf9, 0x30, 0x0d, 0x26, 0xaa, 0x0c, 0x6d, 0x3a, 0xce, + 0x0b, 0x01, 0xa9, 0x63, 0x24, 0xdf, 0x03, 0xa3, 0x0c, 0xa3, 0xc0, 0x0d, 0x15, 0xa9, 0x24, 0xcd, + 0x8f, 0x19, 0xc2, 0x92, 0x77, 0x00, 0x68, 0xc1, 0x86, 0x69, 0x71, 0xdb, 0xa4, 0x7b, 0x4a, 0xba, + 0x24, 0xcd, 0xe7, 0x2b, 0x6b, 0x27, 0x9d, 0xe2, 0x32, 0xc2, 0xdc, 0x6b, 0x5a, 0x9a, 0x4d, 0x7c, + 0x5d, 0x50, 0xda, 0x1e, 0xc4, 0x41, 0x62, 0xe8, 0xbc, 0x4d, 0x5d, 0xa6, 0x55, 0x5e, 0xd6, 0x56, + 0x56, 0x17, 0x6b, 0x4d, 0x6b, 0xdb, 0x6d, 0x1b, 0xb7, 0x5a, 0xb0, 0x51, 0xe1, 0x76, 0x6d, 0x4f, + 0xbe, 0x0f, 0xf2, 0x56, 0x83, 0xd8, 0x7b, 0xa6, 0xe7, 0x62, 0xe4, 0x71, 0x25, 0x53, 0x92, 0xe6, + 0xb3, 0x46, 0x2e, 0x7a, 0xdb, 0x8a, 0x9e, 0xe4, 0x39, 0x30, 0x1e, 0x43, 0x20, 0xa5, 0xa6, 0x07, + 0x99, 0xa7, 0x64, 0xbb, 0xe4, 0x46, 0x1c, 0xb8, 0x49, 0xe9, 0x16, 0x64, 0x9e, 0xfc, 0x1e, 0xe4, + 0x93, 0x46, 0x4d, 0x86, 0x91, 0x32, 0x12, 0x15, 0xf8, 0xf4, 0xa4, 0x53, 0x5c, 0x1d, 0xae, 0xc0, + 0xba, 0xed, 0x05, 0x24, 0x0c, 0x9f, 0xbf, 0xd9, 0xa9, 0xd7, 0x31, 0x32, 0x72, 0xbb, 0x3d, 0x4d, + 0x36, 0x72, 0x9f, 0xcf, 0x0f, 0x16, 0x84, 0x10, 0xe5, 0x19, 0x30, 0xdd, 0xa7, 0x9a, 0xe1, 0x32, + 0x4a, 0x02, 0xe6, 0x96, 0x7f, 0xa6, 0xc1, 0x64, 0x95, 0xa1, 0x67, 0xc4, 0xf7, 0x31, 0xaf, 0x35, + 0x2d, 0x03, 0x06, 0xce, 0x6b, 0xcc, 0xf8, 0xff, 0x97, 0x95, 0x71, 0x18, 0xf2, 0x6b, 0xb2, 0x46, + 0x6f, 0x42, 0xd6, 0x0f, 0xe0, 0x36, 0x6d, 0x5a, 0x66, 0x08, 0x03, 0xc7, 0x6c, 0x60, 0xc6, 0x95, + 0x6c, 0x29, 0xf3, 0x4f, 0x8a, 0x89, 0x2e, 0x8d, 0x1c, 0xbd, 0xd4, 0xee, 0x36, 0xc8, 0xf4, 0xa6, + 0xb0, 0x7e, 0xd2, 0x29, 0x3e, 0xf9, 0x9b, 0x7e, 0xea, 0x18, 0x05, 0x90, 0x37, 0x43, 0xd7, 0xe8, + 0x66, 0xb9, 0x2a, 0xbf, 0x0a, 0x66, 0x07, 0x09, 0x7c, 0x31, 0x81, 0xaf, 0x12, 0xb8, 0x53, 0x65, + 0xe8, 0x2d, 0x75, 0x20, 0x77, 0x6b, 0xd1, 0xbe, 0xcb, 0x6b, 0x60, 0x0c, 0x36, 0xb9, 0x47, 0x42, + 0xcc, 0xdb, 0xb1, 0xfe, 0x15, 0xe5, 0xfb, 0xe1, 0xe3, 0x49, 0x71, 0x49, 0x9b, 0x8e, 0x13, 0xba, + 0x8c, 0xd5, 0x79, 0x88, 0x03, 0x64, 0xf4, 0xa0, 0xf2, 0x3a, 0x18, 0x8d, 0x2f, 0x26, 0x1a, 0x4c, + 0x6e, 0x79, 0x46, 0x1b, 0x70, 0xb3, 0x5a, 0x4c, 0x52, 0xc9, 0x1e, 0x75, 0x8a, 0x29, 0x43, 0x04, + 0x6c, 0x8c, 0x77, 0x6b, 0xee, 0xa5, 0x2a, 0x4f, 0x83, 0xa9, 0x6b, 0x55, 0x25, 0x15, 0x2f, 0x7f, + 0x4b, 0x83, 0x4c, 0x95, 0x21, 0xd9, 0x03, 0xe3, 0xd7, 0x6e, 0xf1, 0xc1, 0x40, 0xbe, 0xbe, 0xed, + 0x2b, 0x68, 0xc3, 0xe1, 0x12, 0x46, 0xf9, 0x23, 0x98, 0xe8, 0xdf, 0xd0, 0x87, 0x37, 0x25, 0xe9, + 0x83, 0x16, 0x96, 0x86, 0x86, 0x5e, 0x50, 0x5a, 0x20, 0x7f, 0x65, 0x24, 0x73, 0x37, 0xa5, 0xb8, + 0x8c, 0x2a, 0x3c, 0x1a, 0x06, 0x95, 0x70, 0x14, 0x46, 0x3e, 0x9d, 0x1f, 0x2c, 0x48, 0x95, 0x57, + 0x47, 0xa7, 0xaa, 0x74, 0x7c, 0xaa, 0x4a, 0xbf, 0x4e, 0x55, 0xe9, 0xcb, 0x99, 0x9a, 0x3a, 0x3e, + 0x53, 0x53, 0x3f, 0xce, 0xd4, 0xd4, 0xbb, 0xc5, 0x3f, 0x2d, 0xe1, 0x7e, 0xef, 0x6f, 0x19, 0xed, + 0xa3, 0x35, 0x1a, 0xfd, 0x2a, 0x57, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x72, 0x38, 0x11, + 0xcb, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/incentive/types/params.pb.go b/x/incentive/types/params.pb.go index 4b9fff8b7..fac26d98a 100644 --- a/x/incentive/types/params.pb.go +++ b/x/incentive/types/params.pb.go @@ -27,7 +27,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module, including portions of rewards // distributed to each type of stakeholder. Note that sum of the portions should -// be strictly less than 1 so that the rest will go to Tendermint validators/delegations +// be strictly less than 1 so that the rest will go to Comet validators/delegations // adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto type Params struct { // submitter_portion is the portion of rewards that goes to submitter diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed.go b/x/zoneconcierge/keeper/proof_epoch_sealed.go index f1a35fc3d..33baa22c4 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed.go @@ -46,8 +46,8 @@ func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, err // ProveEpochSealed proves an epoch has been sealed, i.e., // - the epoch's validator set has a valid multisig over the sealer header -// - the epoch's validator set is committed to the sealer header's last_commit_hash -// - the epoch's metadata is committed to the sealer header's last_commit_hash +// - the epoch's validator set is committed to the sealer header's app_hash +// - the epoch's metadata is committed to the sealer header's app_hash func (k Keeper) ProveEpochSealed(ctx context.Context, epochNumber uint64) (*types.ProofEpochSealed, error) { var ( proof = &types.ProofEpochSealed{} @@ -84,8 +84,8 @@ func (k Keeper) ProveEpochSealed(ctx context.Context, epochNumber uint64) (*type // VerifyEpochSealed verifies that the given `epoch` is sealed by the `rawCkpt` by using the given `proof` // The verification rules include: // - basic sanity checks -// - The raw checkpoint's last_commit_hash is same as in the header of the sealer epoch -// - More than 1/3 (in voting power) validators in the validator set of this epoch have signed last_commit_hash of the sealer epoch +// - The raw checkpoint's app_hash is same as in the header of the sealer epoch +// - More than 1/3 (in voting power) validators in the validator set of this epoch have signed app_hash of the sealer epoch // - The epoch medatata is committed to the app_hash of the sealer epoch // - The validator set is committed to the app_hash of the sealer epoch func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.RawCheckpoint, proof *types.ProofEpochSealed) error { @@ -112,7 +112,7 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R return fmt.Errorf("epoch.EpochNumber (%d) is not equal to rawCkpt.EpochNum (%d)", epoch.EpochNumber, rawCkpt.EpochNum) } - // ensure the raw checkpoint's last_commit_hash is same as in the header of the sealer header + // ensure the raw checkpoint's app_hash is same as in the header of the sealer header // NOTE: since this proof is assembled by a Babylon node who has verified the checkpoint, // the two appHash values should always be the same, otherwise this Babylon node is malicious. // This is different from the checkpoint verification rules in checkpointing, @@ -124,7 +124,7 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R } /* - Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || last_commit_hash) in the raw checkpoint + Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || app_hash) in the raw checkpoint */ valSet := checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} // filter validator set that contributes to the signature diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go index c0acbcc57..1d838f5c0 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go @@ -68,7 +68,7 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { BlsMultiSig: nil, } - // let the subset generate a BLS multisig over sealer header's last_commit_hash + // let the subset generate a BLS multisig over sealer header's app_hash multiSig, err := signBLSWithBitmap(blsSKs, bm, rawCkpt.SignedMsg()) require.NoError(t, err) // assign multiSig to rawCkpt diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index 59f6f8009..e4270f746 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -372,14 +372,14 @@ func (m *FinalizedChainInfo) GetProof() *ProofFinalizedChainInfo { // - Metadata of this epoch, which includes the sealer header // - Raw checkpoint of this epoch // The verifier can perform the following verification rules: -// - The raw checkpoint's `last_commit_hash` is same as in the sealer header +// - The raw checkpoint's `app_hash` is same as in the sealer header // - More than 1/3 (in voting power) validators in the validator set of this -// epoch have signed `last_commit_hash` of the sealer header +// epoch have signed `app_hash` of the sealer header // - The epoch medatata is committed to the `app_hash` of the sealer header // - The validator set is committed to the `app_hash` of the sealer header type ProofEpochSealed struct { // validator_set is the validator set of the sealed epoch - // This validator set has generated a BLS multisig on `last_commit_hash` of + // This validator set has generated a BLS multisig on `app_hash` of // the sealer header ValidatorSet []*types2.ValidatorWithBlsKey `protobuf:"bytes,1,rep,name=validator_set,json=validatorSet,proto3" json:"validator_set,omitempty"` // proof_epoch_info is the Merkle proof that the epoch's metadata is committed From 72406d08a4114e54404b03df7d93a3aa1d13e9ed Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Fri, 24 Nov 2023 13:26:07 +0400 Subject: [PATCH 110/202] fix: Include unbonding time in stored struct (#119) --- x/btcstaking/keeper/msg_server.go | 1 + x/btcstaking/keeper/msg_server_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 1ee135520..bb75b3d72 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -517,6 +517,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // - one for slashing tx of unbonding tx CovenantSlashingSig: nil, CovenantUnbondingSigList: nil, + UnbondingTime: req.UnbondingTime, } if err := ms.AddUndelegationToBTCDelegation( diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index d4c38ca9a..e7d65a6c2 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -608,6 +608,7 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, undelegateMsg.UnbondingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) + require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTime, undelegateMsg.UnbondingTime) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSigList) }) From 43f0930e38cd861a8e2ba060172c64839b6d0941 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 24 Nov 2023 21:15:10 +1100 Subject: [PATCH 111/202] chore: fix pk hash generation for tests (#121) --- testutil/datagen/btc_address.go | 13 ++++++++----- x/btcstaking/keeper/grpc_query_test.go | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/testutil/datagen/btc_address.go b/testutil/datagen/btc_address.go index 9b8b76f78..4b8606cc8 100644 --- a/testutil/datagen/btc_address.go +++ b/testutil/datagen/btc_address.go @@ -1,20 +1,23 @@ package datagen import ( + "io" "math/rand" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" + "golang.org/x/crypto/ripemd160" //nolint:all ) -func GenRandomPkScript(r *rand.Rand) []byte { - // NOTE: this generates non-standard pkscript - return GenRandomByteArray(r, 20) +func GenRandomPkHash(r *rand.Rand) []byte { + md := ripemd160.New() + io.WriteString(md, GenRandomHexStr(r, 20)) //nolint:errcheck + return md.Sum(nil) } func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (btcutil.Address, error) { - addr, err := btcutil.NewAddressPubKeyHash(GenRandomByteArray(r, 20), net) + addr, err := btcutil.NewAddressPubKeyHash(GenRandomPkHash(r), net) if err != nil { return nil, err } @@ -22,7 +25,7 @@ func GenRandomBTCAddress(r *rand.Rand, net *chaincfg.Params) (btcutil.Address, e } func GenRandomPubKeyHashScript(r *rand.Rand, net *chaincfg.Params) ([]byte, error) { - addr, err := btcutil.NewAddressPubKeyHash(GenRandomByteArray(r, 20), net) + addr, err := btcutil.NewAddressPubKeyHash(GenRandomPkHash(r), net) if err != nil { return nil, err } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 36cb33223..e3309271c 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -563,7 +563,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { } func FuzzBTCValidatorDelegations(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 100) + datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) ctrl := gomock.NewController(t) From 857053e6eea7388a18e0a71a7cadfa1f5796a7f6 Mon Sep 17 00:00:00 2001 From: Cirrus Gai Date: Mon, 27 Nov 2023 22:31:34 +0800 Subject: [PATCH 112/202] Handle adaptor sig from covenant (#118) --- btcstaking/btc_staking.go | 18 +- btcstaking/staking.go | 120 ++++- crypto/schnorr-adaptor-signature/keys.go | 15 + crypto/schnorr-adaptor-signature/sig.go | 70 ++- crypto/schnorr-adaptor-signature/sig_test.go | 20 +- .../schnorr-adaptor-signature/sign_utils.go | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 35 +- proto/babylon/btcstaking/v1/tx.proto | 19 +- test/e2e/btc_staking_e2e_test.go | 47 +- .../configurer/chain/commands_btcstaking.go | 16 +- testutil/datagen/btcstaking.go | 30 +- x/btcstaking/client/cli/tx.go | 36 +- x/btcstaking/keeper/btc_delegators.go | 49 +- x/btcstaking/keeper/grpc_query_test.go | 23 +- x/btcstaking/keeper/msg_server.go | 168 +++--- x/btcstaking/keeper/msg_server_test.go | 60 ++- x/btcstaking/types/btc_slashing_tx.go | 70 ++- x/btcstaking/types/btc_slashing_tx_test.go | 27 +- x/btcstaking/types/btcstaking.go | 100 +++- x/btcstaking/types/btcstaking.pb.go | 494 +++++++++++++----- x/btcstaking/types/btcstaking_test.go | 23 +- x/btcstaking/types/errors.go | 44 +- x/btcstaking/types/msg.go | 7 +- x/btcstaking/types/tx.pb.go | 249 ++++----- 24 files changed, 1211 insertions(+), 531 deletions(-) diff --git a/btcstaking/btc_staking.go b/btcstaking/btc_staking.go index 017afc29f..40402c5f1 100644 --- a/btcstaking/btc_staking.go +++ b/btcstaking/btc_staking.go @@ -215,10 +215,10 @@ func BuildStakingInfo( return nil, err } - var unbodningPaths [][]byte - unbodningPaths = append(unbodningPaths, babylonScripts.timeLockPathScript) - unbodningPaths = append(unbodningPaths, babylonScripts.unbondingPathScript) - unbodningPaths = append(unbodningPaths, babylonScripts.slashingPathScript) + var unbondingPaths [][]byte + unbondingPaths = append(unbondingPaths, babylonScripts.timeLockPathScript) + unbondingPaths = append(unbondingPaths, babylonScripts.unbondingPathScript) + unbondingPaths = append(unbondingPaths, babylonScripts.slashingPathScript) timeLockLeafHash := txscript.NewBaseTapLeaf(babylonScripts.timeLockPathScript).TapHash() unbondingPathLeafHash := txscript.NewBaseTapLeaf(babylonScripts.unbondingPathScript).TapHash() @@ -226,7 +226,7 @@ func BuildStakingInfo( sh, err := newTaprootScriptHolder( &unspendableKeyPathKey, - unbodningPaths, + unbondingPaths, ) if err != nil { @@ -295,16 +295,16 @@ func BuildUnbondingInfo( return nil, err } - var unbodningPaths [][]byte - unbodningPaths = append(unbodningPaths, babylonScripts.timeLockPathScript) - unbodningPaths = append(unbodningPaths, babylonScripts.slashingPathScript) + var unbondingPaths [][]byte + unbondingPaths = append(unbondingPaths, babylonScripts.timeLockPathScript) + unbondingPaths = append(unbondingPaths, babylonScripts.slashingPathScript) timeLockLeafHash := txscript.NewBaseTapLeaf(babylonScripts.timeLockPathScript).TapHash() slashingLeafHash := txscript.NewBaseTapLeaf(babylonScripts.slashingPathScript).TapHash() sh, err := newTaprootScriptHolder( &unspendableKeyPathKey, - unbodningPaths, + unbondingPaths, ) if err != nil { diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 55a6921a3..04fc4947a 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -13,6 +13,8 @@ import ( "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" ) const ( @@ -541,34 +543,87 @@ func SignTxWithOneScriptSpendInputStrict( fundingOutputIdx uint32, signedScriptPath []byte, privKey *btcec.PrivateKey, - net *chaincfg.Params, ) (*schnorr.Signature, error) { + if err := checkTxBeforeSigning(txToSign, fundingTx, fundingOutputIdx); err != nil { + return nil, fmt.Errorf("invalid tx: %w", err) + } + + fundingOutput := fundingTx.TxOut[fundingOutputIdx] + + return SignTxWithOneScriptSpendInputFromScript(txToSign, fundingOutput, privKey, signedScriptPath) +} + +// EncSignTxWithOneScriptSpendInputStrict is encrypted version of +// SignTxWithOneScriptSpendInputStrict with the output to be encrypted +// by an encryption key (adaptor signature) +func EncSignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, + privKey *btcec.PrivateKey, + encKey *asig.EncryptionKey, +) (*asig.AdaptorSignature, error) { + + if err := checkTxBeforeSigning(txToSign, fundingTx, fundingOutputIdx); err != nil { + return nil, fmt.Errorf("invalid tx: %w", err) + } + + fundingOutput := fundingTx.TxOut[fundingOutputIdx] + + tapLeaf := txscript.NewBaseTapLeaf(signedScriptPath) + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + fundingOutput.PkScript, + fundingOutput.Value, + ) + + sigHashes := txscript.NewTxSigHashes(txToSign, inputFetcher) + + sigHash, err := txscript.CalcTapscriptSignaturehash( + sigHashes, + txscript.SigHashDefault, + txToSign, + 0, + inputFetcher, + tapLeaf) + if err != nil { + return nil, err + } + + adaptorSig, err := asig.EncSign(privKey, encKey, sigHash) + if err != nil { + return nil, err + } + + return adaptorSig, nil +} + +func checkTxBeforeSigning(txToSign *wire.MsgTx, fundingTx *wire.MsgTx, fundingOutputIdx uint32) error { if txToSign == nil { - return nil, fmt.Errorf("tx to sign must not be nil") + return fmt.Errorf("tx to sign must not be nil") } if len(txToSign.TxIn) != 1 { - return nil, fmt.Errorf("tx to sign must have exactly one input") + return fmt.Errorf("tx to sign must have exactly one input") } if fundingOutputIdx >= uint32(len(fundingTx.TxOut)) { - return nil, fmt.Errorf("invalid funding output index %d, tx has %d outputs", fundingOutputIdx, len(fundingTx.TxOut)) + return fmt.Errorf("invalid funding output index %d, tx has %d outputs", fundingOutputIdx, len(fundingTx.TxOut)) } fundingTxHash := fundingTx.TxHash() if !txToSign.TxIn[0].PreviousOutPoint.Hash.IsEqual(&fundingTxHash) { - return nil, fmt.Errorf("txToSign must input point to fundingTx") + return fmt.Errorf("txToSign must input point to fundingTx") } - if txToSign.TxIn[0].PreviousOutPoint.Index != uint32(fundingOutputIdx) { - return nil, fmt.Errorf("txToSign inpunt index must point to output with provided script") + if txToSign.TxIn[0].PreviousOutPoint.Index != fundingOutputIdx { + return fmt.Errorf("txToSign inpunt index must point to output with provided script") } - fundingOutput := fundingTx.TxOut[fundingOutputIdx] - - return SignTxWithOneScriptSpendInputFromScript(txToSign, fundingOutput, privKey, signedScriptPath) + return nil } // VerifyTransactionSigWithOutput verifies that: @@ -652,6 +707,51 @@ func VerifyTransactionSigWithOutputData( return nil } +// EncVerifyTransactionSigWithOutputData verifies that: +// - provided transaction has exactly one input +// - provided signature is valid adaptor signature +// - provided signature is signing whole provided transaction (SigHashDefault) +func EncVerifyTransactionSigWithOutputData( + transaction *wire.MsgTx, + fundingOutputPkScript []byte, + fundingOutputValue int64, + script []byte, + pubKey *btcec.PublicKey, + encKey *asig.EncryptionKey, + signature *asig.AdaptorSignature, +) error { + if transaction == nil { + return fmt.Errorf("tx to verify not be nil") + } + + if len(transaction.TxIn) != 1 { + return fmt.Errorf("tx to sign must have exactly one input") + } + + if pubKey == nil { + return fmt.Errorf("public key must not be nil") + } + + tapLeaf := txscript.NewBaseTapLeaf(script) + + inputFetcher := txscript.NewCannedPrevOutputFetcher( + fundingOutputPkScript, + fundingOutputValue, + ) + + sigHashes := txscript.NewTxSigHashes(transaction, inputFetcher) + + sigHash, err := txscript.CalcTapscriptSignaturehash( + sigHashes, txscript.SigHashDefault, transaction, 0, inputFetcher, tapLeaf, + ) + + if err != nil { + return err + } + + return signature.EncVerify(pubKey, encKey, sigHash) +} + // IsSlashingRateValid checks if the given slashing rate is between the valid range i.e., (0,1) with a precision of at most 2 decimal places. func IsSlashingRateValid(slashingRate sdkmath.LegacyDec) bool { // Check if the slashing rate is between 0 and 1 diff --git a/crypto/schnorr-adaptor-signature/keys.go b/crypto/schnorr-adaptor-signature/keys.go index 795fa7f84..177a6156d 100644 --- a/crypto/schnorr-adaptor-signature/keys.go +++ b/crypto/schnorr-adaptor-signature/keys.go @@ -18,6 +18,14 @@ func NewDecyptionKeyFromModNScalar(scalar *btcec.ModNScalar) (*DecryptionKey, er return nil, fmt.Errorf("the given scalar is zero") } + // enforce using a scalar corresponding to an even encryption key + var ekPoint btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(scalar, &ekPoint) + ekPoint.ToAffine() + if ekPoint.Y.IsOdd() { + scalar = scalar.Negate() + } + return &DecryptionKey{*scalar}, nil } @@ -73,6 +81,13 @@ func NewEncryptionKeyFromJacobianPoint(point *btcec.JacobianPoint) (*EncryptionK affinePoint.ToAffine() } + // enforce affinePoint to be an even point + // this is needed since we cannot predict whether the given + // point or public key is odd or even + if affinePoint.Y.IsOdd() { + affinePoint.Y.Negate(1).Normalize() + } + return &EncryptionKey{affinePoint}, nil } diff --git a/crypto/schnorr-adaptor-signature/sig.go b/crypto/schnorr-adaptor-signature/sig.go index 3ea28483f..09cebcfd8 100644 --- a/crypto/schnorr-adaptor-signature/sig.go +++ b/crypto/schnorr-adaptor-signature/sig.go @@ -1,10 +1,12 @@ package schnorr_adaptor_signature import ( + "bytes" + "encoding/hex" "fmt" "github.com/btcsuite/btcd/btcec/v2" - schnorr "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -90,8 +92,12 @@ func (sig *AdaptorSignature) Recover(decryptedSchnorrSig *schnorr.Signature) *De return &DecryptionKey{*t} } -func (sig *AdaptorSignature) ToBytes() []byte { - asigBytes := []byte{} +// Marshal is to implement proto interface +func (sig *AdaptorSignature) Marshal() ([]byte, error) { + if sig == nil { + return nil, nil + } + var asigBytes []byte // append r rBytes := btcec.JacobianToByteSlice(sig.r) asigBytes = append(asigBytes, rBytes...) @@ -104,7 +110,54 @@ func (sig *AdaptorSignature) ToBytes() []byte { } else { asigBytes = append(asigBytes, 0x00) } - return asigBytes + return asigBytes, nil +} + +func (sig *AdaptorSignature) MustMarshal() []byte { + if sig == nil { + return nil + } + bz, err := sig.Marshal() + if err != nil { + panic(err) + } + + return bz +} + +func (sig *AdaptorSignature) MarshalHex() string { + return hex.EncodeToString(sig.MustMarshal()) +} + +// Size is to implement proto interface +func (sig *AdaptorSignature) Size() int { + return AdaptorSignatureSize +} + +// MarshalTo is to implement proto interface +func (sig *AdaptorSignature) MarshalTo(data []byte) (int, error) { + bz, err := sig.Marshal() + if err != nil { + return 0, err + } + copy(data, bz) + return len(data), nil +} + +// Unmarshal is to implement proto interface +func (sig *AdaptorSignature) Unmarshal(data []byte) error { + adaptorSig, err := NewAdaptorSignatureFromBytes(data) + if err != nil { + return err + } + + *sig = *adaptorSig + + return nil +} + +func (sig *AdaptorSignature) Equals(sig2 AdaptorSignature) bool { + return bytes.Equal(sig.MustMarshal(), sig2.MustMarshal()) } // EncSign generates an adaptor signature by using the given secret key, @@ -177,3 +230,12 @@ func NewAdaptorSignatureFromBytes(asigBytes []byte) (*AdaptorSignature, error) { return newAdaptorSignature(&r, &sHat, needNegation), nil } + +// NewAdaptorSignatureFromHex parses the given hex string to an adaptor signature +func NewAdaptorSignatureFromHex(asigHex string) (*AdaptorSignature, error) { + asigBytes, err := hex.DecodeString(asigHex) + if err != nil { + return nil, err + } + return NewAdaptorSignatureFromBytes(asigBytes) +} diff --git a/crypto/schnorr-adaptor-signature/sig_test.go b/crypto/schnorr-adaptor-signature/sig_test.go index d5a443fe7..34ee6cd49 100644 --- a/crypto/schnorr-adaptor-signature/sig_test.go +++ b/crypto/schnorr-adaptor-signature/sig_test.go @@ -3,10 +3,11 @@ package schnorr_adaptor_signature_test import ( "testing" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/stretchr/testify/require" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" ) func FuzzEncSignAndEncVerify(f *testing.F) { @@ -134,9 +135,20 @@ func FuzzSerializeAdaptorSig(f *testing.F) { require.NoError(t, err) // roundtrip for serialising/deserialising adaptor signature - adaptorSigBytes := adaptorSig.ToBytes() - actualAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(adaptorSigBytes) + adaptorSigBytes, err := adaptorSig.Marshal() + require.NoError(t, err) + var unmarshalledSig asig.AdaptorSignature + err = unmarshalledSig.Unmarshal(adaptorSigBytes) + require.NoError(t, err) + require.True(t, adaptorSig.Equals(unmarshalledSig)) + + fromBytesSig, err := asig.NewAdaptorSignatureFromBytes(adaptorSigBytes) + require.NoError(t, err) + require.True(t, adaptorSig.Equals(*fromBytesSig)) + + sigHex := adaptorSig.MarshalHex() + fromHexSig, err := asig.NewAdaptorSignatureFromHex(sigHex) require.NoError(t, err) - require.Equal(t, adaptorSig, actualAdaptorSig) + require.True(t, adaptorSig.Equals(*fromHexSig)) }) } diff --git a/crypto/schnorr-adaptor-signature/sign_utils.go b/crypto/schnorr-adaptor-signature/sign_utils.go index ee61e9e29..e45dc4caf 100644 --- a/crypto/schnorr-adaptor-signature/sign_utils.go +++ b/crypto/schnorr-adaptor-signature/sign_utils.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" - schnorr "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" ) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 6759352e7..2365775ab 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -89,14 +89,11 @@ message BTCDelegation { // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. bytes delegator_sig = 11 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // covenant_sig is the signature signature on the slashing tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) + // covenant_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member // It will be a part of the witness for the staking tx output. - // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - // over each restaked BTC validator (i.e., a matrix of tuples) - bytes covenant_sig = 12 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - - // if this object is present it menans that staker requested undelegation, and whole + repeated CovenantAdaptorSignatures covenant_sigs = 12; + // if this object is present it means that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object @@ -111,19 +108,18 @@ message BTCUndelegation { bytes unbonding_tx = 1; // unbonding_time describes how long the funds will be locked in the unbonding output uint32 unbonding_time = 2; - // slashing_tx is the slashing tx for unbodning transactions + // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). - // It will be a part of the witness for the unbodning tx output. + // It will be a part of the witness for the unbonding tx output. bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // covenant_slashing_sig is the signature on the slashing tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon - // TODO: change to a matrix of (covenant PK, covenant adaptor signature) tuples - bytes covenant_slashing_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member + // It will be a part of the witness for the staking tx output. + repeated CovenantAdaptorSignatures covenant_slashing_sigs = 5; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon @@ -140,8 +136,6 @@ message BTCUndelegationInfo { // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - // It must be provided after processing undelagate message by Babylon and after - // validator sig will be provided by validator repeated SignatureInfo covenant_unbonding_sig_list = 2; } @@ -184,3 +178,12 @@ message SignatureInfo { bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; bytes sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } + +// CovenantAdaptorSignatures is a list adaptor signatures signed by the +// covenant with different validator's public keys as encryption keys +message CovenantAdaptorSignatures { + // cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature + bytes cov_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC validator's public key + repeated bytes adaptor_sigs = 2; +} diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 3f0d96634..b8564c08f 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -26,7 +26,7 @@ service Msg { rpc AddCovenantSig(MsgAddCovenantSig) returns (MsgAddCovenantSigResponse); // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbodning tx submitted to babylon by staker + // - slashing tx corresponding to unbonding tx submitted to babylon by staker rpc AddCovenantUnbondingSigs(MsgAddCovenantUnbondingSigs) returns (MsgAddCovenantUnbondingSigsResponse); // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); @@ -121,10 +121,10 @@ message MsgAddCovenantSig { // staking_tx_hash is the hash of the staking tx. // It uniquely identifies a BTC delegation string staking_tx_hash = 3; - // sig is the signature of the covenant - // the signature follows encoding in BIP-340 spec - // TODO: change to adaptor signature - bytes sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // sigs is a list of adaptor signatures of the covenant + // the order of sigs should respect the order of validators + // of the corresponding delegation + repeated bytes sigs = 4; } // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddCovenantSigResponse {} @@ -161,10 +161,11 @@ message MsgAddCovenantUnbondingSigs { // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec bytes unbonding_tx_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon - // the signature follows encoding in BIP-340 spec - // TODO: change to adaptor signature - bytes slashing_unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant + // on slashing tx corresponding to unbonding tx submitted to babylon + // the order of sigs should respect the order of validators + // of the corresponding delegation + repeated bytes slashing_unbonding_tx_sigs = 5; } // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig message MsgAddCovenantUnbondingSigsResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 2730ab1f2..c5f4e08cc 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -5,8 +5,18 @@ import ( "math/rand" "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/crypto/eots" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonchain/babylon/test/e2e/configurer" "github.com/babylonchain/babylon/test/e2e/initialization" "github.com/babylonchain/babylon/test/e2e/util" @@ -16,14 +26,6 @@ import ( bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ftypes "github.com/babylonchain/babylon/x/finality/types" itypes "github.com/babylonchain/babylon/x/incentive/types" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" ) var ( @@ -136,7 +138,6 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { 0, stakingSlashingPathInfo.RevealedLeaf.Script, delBTCSK, - net, ) s.NoError(err) @@ -173,7 +174,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { pendingDels := pendingDelSet[0] s.Len(pendingDels.Dels, 1) s.Equal(delBTCPK.SerializeCompressed()[1:], pendingDels.Dels[0].BtcPk.MustToBTCPK().SerializeCompressed()[1:]) - s.Nil(pendingDels.Dels[0].CovenantSig) + s.Len(pendingDels.Dels[0].CovenantSigs, 0) // check delegation delegation := nonValidatorNode.QueryBtcDelegation(stakingTxHash) @@ -194,7 +195,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { pendingDels := pendingDelsSet[0] s.Len(pendingDels.Dels, 1) pendingDel := pendingDels.Dels[0] - s.Nil(pendingDel.CovenantSig) + s.Len(pendingDel.CovenantSigs, 0) slashingTx := pendingDel.SlashingTx stakingTx := pendingDel.StakingTx @@ -230,12 +231,15 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { /* generate and insert new covenant signature, in order to activate the BTC delegation */ - covenantSig, err := slashingTx.Sign( + // TODO add multiple covenant sigs + encKey, err := asig.NewEncryptionKeyFromBTCPK(validatorBTCPKs[0]) + s.NoError(err) + covenantSig, err := slashingTx.EncSign( stakingMsgTx, pendingDel.StakingOutputIdx, stakingSlashingPathInfo.RevealedLeaf.Script, covenantSK, - net, + encKey, ) s.NoError(err) nonValidatorNode.AddCovenantSig(¶ms.CovenantPks[0], stakingTxHash, covenantSig) @@ -250,7 +254,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) activeDel := activeDels.Dels[0] - s.NotNil(activeDel.CovenantSig) + s.NotNil(activeDel.CovenantSigs) // wait for a block so that above txs take effect and the voting power table // is updated in the next block's BeginBlock @@ -432,7 +436,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) activeDel := activeDels.Dels[0] - s.NotNil(activeDel.CovenantSig) + s.NotNil(activeDel.CovenantSigs) // params for covenantPk and slashing address params := nonValidatorNode.QueryBTCStakingParams() @@ -480,7 +484,6 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { 0, unbondingTxSlashingPathInfo.RevealedLeaf.Script, delBTCSK, - net, ) s.NoError(err) @@ -520,7 +523,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { s.NotNil(delegation.BtcUndelegation) s.Empty(delegation.BtcUndelegation.CovenantUnbondingSigList) - s.Nil(delegation.BtcUndelegation.CovenantSlashingSig) + s.Len(delegation.BtcUndelegation.CovenantSlashingSigs, 0) // params for covenantPk and slashing address params := nonValidatorNode.QueryBTCStakingParams() @@ -574,7 +577,6 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegation.StakingOutputIdx, stakingTxUnbondigPathInfo.RevealedLeaf.Script, covenantSK, - net, ) s.NoError(err) @@ -584,12 +586,15 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() s.NoError(err) - covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.Sign( + // TODO accomodate multiple slashing sigs + encKey, err := asig.NewEncryptionKeyFromBTCPK(validatorBTCPKs[0]) + s.NoError(err) + covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.EncSign( unbondingTx, 0, unbondingTxSlashingPath.RevealedLeaf.Script, covenantSK, - net, + encKey, ) s.NoError(err) nonValidatorNode.AddCovenantUnbondingSigs( @@ -603,7 +608,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { delegationWithSigs := allDelegationsWithSigs[0].Dels[0] s.NotNil(delegationWithSigs.BtcUndelegation) s.NotEmpty(delegationWithSigs.BtcUndelegation.CovenantUnbondingSigList) - s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSig) + s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSigs) btcTip, err := nonValidatorNode.QueryTip() s.NoError(err) s.Equal( diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 1f112ed6d..422071d4b 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -9,11 +9,13 @@ import ( "cosmossdk.io/math" sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/stretchr/testify/require" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/stretchr/testify/require" ) func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *math.LegacyDec) { @@ -81,11 +83,12 @@ func (n *NodeConfig) CreateBTCDelegation( n.LogActionF("successfully created BTC delegation") } -func (n *NodeConfig) AddCovenantSig(covPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { +// TODO accomodate multiple adaptor sigs +func (n *NodeConfig) AddCovenantSig(covPK *bbn.BIP340PubKey, stakingTxHash string, sig *asig.AdaptorSignature) { n.LogActionF("adding covenant signature") covPKHex := covPK.MarshalHex() - sigHex := sig.ToHexStr() + sigHex := sig.MarshalHex() cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", covPKHex, stakingTxHash, sigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) @@ -180,16 +183,17 @@ func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bb n.LogActionF("successfully added validator unbonding sig") } +// TODO accomodate multiple slashing sigs func (n *NodeConfig) AddCovenantUnbondingSigs( covPK *bbn.BIP340PubKey, stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, - slashUnbondingTxSig *bbn.BIP340Signature) { + slashUnbondingTxSig *asig.AdaptorSignature) { n.LogActionF("adding validator signature") covPKHex := covPK.MarshalHex() unbondingTxSigHex := unbondingTxSig.ToHexStr() - slashUnbondingTxSigHex := slashUnbondingTxSig.ToHexStr() + slashUnbondingTxSigHex := slashUnbondingTxSig.MarshalHex() cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", covPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 7a6f5a54b..af55278ab 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -1,14 +1,12 @@ package datagen import ( - sdkmath "cosmossdk.io/math" "fmt" "math/rand" "testing" - "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" - bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -18,6 +16,11 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { @@ -129,15 +132,16 @@ func GenRandomBTCDelegation( // covenant sig and delegator sig stakingMsgTx := testingInfo.StakingTx // TODO: covenant multisig - covenantSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, covenantSKs[0], net) - if err != nil { - return nil, err - } - delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, delSK, net) - if err != nil { - return nil, err + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPKs[0]) + require.NoError(t, err) + covenantSig, err := testingInfo.SlashingTx.EncSign(stakingMsgTx, 0, script, covenantSKs[0], encKey) + require.NoError(t, err) + covenantSigInfo := &bstypes.CovenantAdaptorSignatures{ + CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantBTCPKs[0]), + AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, } - + delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, delSK) + require.NoError(t, err) serializedStaking, err := bstypes.SerializeBtcTx(testingInfo.StakingTx) require.NoError(t, err) @@ -151,7 +155,7 @@ func GenRandomBTCDelegation( TotalSat: totalSat, StakingOutputIdx: 0, DelegatorSig: delegatorSig, - CovenantSig: covenantSig, + CovenantSigs: []*bstypes.CovenantAdaptorSignatures{covenantSigInfo}, StakingTx: serializedStaking, SlashingTx: testingInfo.SlashingTx, }, nil diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 24518d4bb..977aa3c47 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -7,9 +7,6 @@ import ( "strings" sdkmath "cosmossdk.io/math" - bbn "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcutil" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -17,6 +14,11 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/spf13/cobra" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + "github.com/babylonchain/babylon/x/btcstaking/types" ) const ( @@ -286,23 +288,24 @@ func NewAddCovenantSigCmd() *cobra.Command { covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { - return err + return fmt.Errorf("invalid public key: %w", err) } // get staking tx hash stakingTxHash := args[1] - // get covenant sigature - sig, err := bbn.NewBIP340SignatureFromHex(args[2]) + // TODO add multiple adaptor sigs from cli + // get adaptor sig + sig, err := asig.NewAdaptorSignatureFromHex(args[2]) if err != nil { - return err + return fmt.Errorf("invalid covenant signature: %w", err) } msg := types.MsgAddCovenantSig{ Signer: clientCtx.FromAddress.String(), Pk: covPK, StakingTxHash: stakingTxHash, - Sig: sig, + Sigs: [][]byte{sig.MustMarshal()}, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -403,18 +406,19 @@ func NewAddCovenantUnbondingSigsCmd() *cobra.Command { return err } - // get covenant sigature for slash unbonding tx - slashUnbondingSig, err := bbn.NewBIP340SignatureFromHex(args[3]) + // TODO add multiple adaptor sigs from cli + // get adaptor sig + slashingSig, err := asig.NewAdaptorSignatureFromHex(args[3]) if err != nil { - return err + return fmt.Errorf("invalid covenant signature: %w", err) } msg := types.MsgAddCovenantUnbondingSigs{ - Signer: clientCtx.FromAddress.String(), - Pk: covPK, - StakingTxHash: stakingTxHash, - UnbondingTxSig: unbondingSig, - SlashingUnbondingTxSig: slashUnbondingSig, + Signer: clientCtx.FromAddress.String(), + Pk: covPK, + StakingTxHash: stakingTxHash, + UnbondingTxSig: unbondingSig, + SlashingUnbondingTxSigs: [][]byte{slashingSig.MustMarshal()}, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index e6a62b6d2..301422e25 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -7,9 +7,11 @@ import ( "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" + "github.com/btcsuite/btcd/chaincfg/chainhash" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" ) // AddBTCDelegation indexes the given BTC delegation in the BTC delegator store, and saves @@ -77,19 +79,25 @@ func (k Keeper) updateBTCDelegation( return nil } -// AddCovenantSigToBTCDelegation adds a given covenant sig to a BTC delegation +// AddCovenantSigsToBTCDelegation adds a given covenant sig to a BTC delegation // with the given (val PK, del PK, staking tx hash) tuple -func (k Keeper) AddCovenantSigToBTCDelegation( +func (k Keeper) AddCovenantSigsToBTCDelegation( ctx context.Context, stakingTxHash string, - covenantSig *bbn.BIP340Signature, + covenantSigs [][]byte, + covPk *bbn.BIP340PubKey, + quorum uint32, ) error { - addCovenantSig := func(btcDel *types.BTCDelegation) error { - if btcDel.CovenantSig != nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s already has a covenant signature", stakingTxHash) + adaptorSigs := make([]asig.AdaptorSignature, 0, len(covenantSigs)) + for _, s := range covenantSigs { + as, err := asig.NewAdaptorSignatureFromBytes(s) + if err != nil { + return err } - btcDel.CovenantSig = covenantSig - return nil + adaptorSigs = append(adaptorSigs, *as) + } + addCovenantSig := func(btcDel *types.BTCDelegation) error { + return btcDel.AddCovenantSigs(covPk, adaptorSigs, quorum) } return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSig) @@ -114,22 +122,25 @@ func (k Keeper) AddUndelegationToBTCDelegation( func (k Keeper) AddCovenantSigsToUndelegation( ctx context.Context, stakingTxHash string, - unbondingTxSigInfo *types.SignatureInfo, - slashUnbondingTxSig *bbn.BIP340Signature, + covPk *bbn.BIP340PubKey, + unbondingTxSigInfo *bbn.BIP340Signature, + slashUnbondingTxSigs [][]byte, + quorum uint32, ) error { + adaptorSigs := make([]asig.AdaptorSignature, 0, len(slashUnbondingTxSigs)) + for _, s := range slashUnbondingTxSigs { + as, err := asig.NewAdaptorSignatureFromBytes(s) + if err != nil { + return err + } + adaptorSigs = append(adaptorSigs, *as) + } addCovenantSigs := func(btcDel *types.BTCDelegation) error { if btcDel.BtcUndelegation == nil { return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) } - // TODO: ensure the given CovenantUnbondingSig does not exist in the BTC undelegation yet - if btcDel.BtcUndelegation.CovenantUnbondingSigList != nil || btcDel.BtcUndelegation.CovenantSlashingSig != nil { - return fmt.Errorf("the BTC undelegation for staking tx hash %s already has valid covenant signatures", stakingTxHash) - } - - btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingTxSigInfo) - btcDel.BtcUndelegation.CovenantSlashingSig = slashUnbondingTxSig - return nil + return btcDel.BtcUndelegation.AddCovenantSigs(covPk, unbondingTxSigInfo, adaptorSigs, quorum) } return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSigs) diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index e3309271c..c5bedf149 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -6,19 +6,21 @@ import ( "testing" sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/babylonchain/babylon/testutil/datagen" - testkeeper "github.com/babylonchain/babylon/testutil/keeper" - bbn "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/testutil/datagen" + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/types" ) func FuzzActivatedHeight(f *testing.F) { @@ -222,7 +224,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { // remove covenant sig in random BTC delegations to make them inactive - btcDel.CovenantSig = nil + btcDel.CovenantSigs = nil pendingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel } err = keeper.AddBTCDelegation(ctx, btcDel) @@ -334,9 +336,12 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { if datagen.RandomInt(r, 2) == 1 { // these BTC delegations are unbonded - unbondingSigInfo := types.NewSignatureInfo(covBTCPK, btcDel.CovenantSig) + sig, err := schnorr.Sign(covenantSK, datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + unbondingSig := bbn.NewBIP340SignatureFromBTCSig(sig) + unbondingSigInfo := types.NewSignatureInfo(covBTCPK, &unbondingSig) btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingSigInfo) - btcDel.BtcUndelegation.CovenantSlashingSig = btcDel.CovenantSig + btcDel.BtcUndelegation.CovenantSlashingSigs = btcDel.CovenantSigs } else { // these BTC delegations are unbonding unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index bb75b3d72..4e947a3b3 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -6,8 +6,6 @@ import ( "math" errorsmod "cosmossdk.io/errors" - "github.com/babylonchain/babylon/btcstaking" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -16,6 +14,10 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/babylonchain/babylon/x/btcstaking/types" ) type msgServer struct { @@ -348,7 +350,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre StakingOutputIdx: stakingOutputIdx, SlashingTx: req.SlashingTx, DelegatorSig: req.DelegatorSig, - CovenantSig: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant + CovenantSigs: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant BtcUndelegation: nil, } if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { @@ -378,23 +380,23 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele unbondingMsgTx, err := types.ParseBtcTx(req.UnbondingTx) if err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + return nil, types.ErrInvalidUnbondingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } // 2. basic stateless checks for unbonding tx if err := btcstaking.IsSimpleTransfer(unbondingMsgTx); err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) + return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } // 3. Check unbonding time (staking time from unbonding tx) is larger than finalization time - // Unbodning time must be strictly larger that babylon finalization time. + // Unbonding time must be strictly larger that babylon finalization time. if uint64(req.UnbondingTime) <= wValue { - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time %d must be larger than finalization time %d", req.UnbondingTime, wValue) + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than finalization time %d", req.UnbondingTime, wValue) } // 4. Check unbonding time is lower than max uint16 if uint64(req.UnbondingTime) > math.MaxUint16 { - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding time %d must be lower than %d", req.UnbondingTime, math.MaxUint16) + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be lower than %d", req.UnbondingTime, math.MaxUint16) } // retrieve staking tx hash from unbonding tx, at this point we know that unbonding tx is a simple transfer with @@ -442,13 +444,13 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele ) if err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) + return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } unbondingOutputIdx, err := types.GetOutputIdx(unbondingMsgTx, si.UnbondingOutput) if err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx does not contain expected unbonding output") + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not contain expected unbonding output") } // 8. Check that slashing tx and unbonding tx are valid and consistent @@ -462,7 +464,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele ms.btcNet, ) if err != nil { - return nil, types.ErrInvalidUnbodningTx.Wrapf("err: %v", err) + return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } // 9. Check staker signature against slashing path of the unbonding tx @@ -495,7 +497,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // we only check index of the staking output, as we already retrieved delegation // by stakingTxHash computed from unbonding tx input if unbondingTxFundingOutpoint.Index != uint32(stakingOutputIndex) { - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx does not point to staking tx staking output") + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not point to staking tx staking output") } if unbondingMsgTx.TxOut[0].Value >= stakingTxMsg.TxOut[stakingOutputIndex].Value { @@ -504,7 +506,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // burden on staker to choose right fee. // Unbonding tx should not be replaceable at babylon level (and by extension on btc level), as this would // allow staker to spam the network with unbonding txs, which would force covenant and validator to send signatures. - return nil, types.ErrInvalidUnbodningTx.Wrapf("unbonding tx fee must be larger that 0") + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx fee must be larger that 0") } ud := types.BTCUndelegation{ @@ -512,10 +514,10 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele SlashingTx: req.SlashingTx, DelegatorSlashingSig: req.DelegatorSlashingSig, // following objects needs to be filled by covenant and validator - // Jurry needs to provide two sigs: - // - one for unbonding tx - // - one for slashing tx of unbonding tx - CovenantSlashingSig: nil, + // covenant emulators need to provide two sigs: + // - one for unbonding tx (schnorr sig) + // - one for validator of the slashing tx of unbonding tx (adaptor sig) + CovenantSlashingSigs: nil, CovenantUnbondingSigList: nil, UnbondingTime: req.UnbondingTime, } @@ -555,18 +557,21 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) stakingOutput := stakingTx.TxOut[stakingOutputIdx] - // ensure that the given covenant PK is in the parameter - if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { - return nil, types.ErrInvalidCovenantPK + // Note: we assume the order of adaptor sigs is matched to the + // order of validators in the delegation + // TODO ensure the order, currently, we only have one validator + // one covenant emulator + numAdaptorSig := len(req.Sigs) + numVals := len(btcDel.ValBtcPkList) + if numAdaptorSig != numVals { + return nil, types.ErrInvalidCovenantSig.Wrapf( + "number of covenant signatures: %d, number of validators being staked to: %d", + numAdaptorSig, numVals) } - // ensure the BTC delegation hasn't reached a quorum yet - if btcDel.HasCovenantQuorum(covenantQuorum) { - return nil, types.ErrCovenantQuorumAlreadyReached - } - // ensure that this covenant member has not signed the delegation yet - if btcDel.IsSignedByCovMember(req.Pk) { - return nil, types.ErrDuplicatedCovenantSig + // ensure that the given covenant PK is in the parameter + if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { + return nil, types.ErrInvalidCovenantPK.Wrapf("covenant pk: %s", req.Pk.MarshalHex()) } spendInfo, err := ms.stakingInfoFromDelegation(ctx, btcDel) @@ -581,21 +586,24 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven panic(err) } - // verify signature w.r.t. covenant PK and signature - err = btcDel.SlashingTx.VerifySignature( - stakingOutput.PkScript, - stakingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, - req.Pk.MustToBTCPK(), - req.Sig, - ) - - if err != nil { - return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) + // verify each covenant adaptor signature with the corresponding validator public key + for i, sig := range req.Sigs { + err := verifySlashingTxAdaptorSig( + btcDel.SlashingTx, + stakingOutput.PkScript, + stakingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, + req.Pk.MustToBTCPK(), + btcDel.ValBtcPkList[i].MustToBTCPK(), + sig, + ) + if err != nil { + return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) + } } - // all good, add signature to BTC delegation and set it back to KVStore - if err := ms.AddCovenantSigToBTCDelegation(ctx, req.StakingTxHash, req.Sig); err != nil { + // all good, add signatures to BTC delegation and set it back to KVStore + if err := ms.AddCovenantSigsToBTCDelegation(ctx, req.StakingTxHash, req.Sigs, req.Pk, covenantQuorum); err != nil { panic("failed to set BTC delegation that has passed verification") } @@ -607,6 +615,34 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven return &types.MsgAddCovenantSigResponse{}, nil } +func verifySlashingTxAdaptorSig( + slashingTx *types.BTCSlashingTx, + stakingPkScript []byte, + stakingAmount int64, + stakingScript []byte, + pk *btcec.PublicKey, + valPk *btcec.PublicKey, + sig []byte) error { + adaptorSig, err := asig.NewAdaptorSignatureFromBytes(sig) + if err != nil { + return err + } + + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPk) + if err != nil { + return err + } + + return slashingTx.EncVerifyAdaptorSignature( + stakingPkScript, + stakingAmount, + stakingScript, + pk, + encKey, + adaptorSig, + ) +} + func (ms msgServer) AddCovenantUnbondingSigs( goCtx context.Context, req *types.MsgAddCovenantUnbondingSigs, @@ -629,16 +665,21 @@ func (ms msgServer) AddCovenantUnbondingSigs( return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) } - // 3. Check that we did not receive a quorum number of covenant signatures yet - if btcDel.BtcUndelegation.HasCovenantQuorum(covenantQuorum) { - return nil, types.ErrCovenantQuorumAlreadyReached - } - // ensure that this covenant member hasn't signed yet - if btcDel.BtcUndelegation.IsSignedByCovMember(req.Pk) { - return nil, types.ErrDuplicatedCovenantSig + // 3. Check that the number of covenant sigs and number of the + // validators are matched + // Note: we assume the order of adaptor sigs is matched to the + // order of validators in the delegation + // TODO ensure the order, currently, we only have one validator + // one covenant emulator + numAdaptorSig := len(req.SlashingUnbondingTxSigs) + numVals := len(btcDel.ValBtcPkList) + if numAdaptorSig != numVals { + return nil, types.ErrInvalidCovenantSig.Wrapf( + "number of covenant signatures: %d, number of validators being staked to: %d", + numAdaptorSig, numVals) } - // 4. Verify signature of unbodning tx against staking tx output + // 4. Verify signature of unbonding tx against staking tx output stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) stakingOutput := stakingTx.TxOut[stakingOutputIdx] @@ -694,24 +735,31 @@ func (ms msgServer) AddCovenantUnbondingSigs( panic(err) } - err = btcDel.BtcUndelegation.SlashingTx.VerifySignature( - unbondingOutput.PkScript, - unbondingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, - req.Pk.MustToBTCPK(), - req.SlashingUnbondingTxSig, - ) - if err != nil { - return nil, types.ErrUnbodningInvalidValidatorSig.Wrap(err.Error()) + // verify each covenant adaptor signature with the corresponding validator public key + for i, sig := range req.SlashingUnbondingTxSigs { + err := verifySlashingTxAdaptorSig( + btcDel.BtcUndelegation.SlashingTx, + unbondingOutput.PkScript, + unbondingOutput.Value, + slashingPathInfo.RevealedLeaf.Script, + req.Pk.MustToBTCPK(), + btcDel.ValBtcPkList[i].MustToBTCPK(), + sig, + ) + if err != nil { + return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) + } } // all good, add signature to BTC undelegation and set it back to KVStore - unbondingSigInfo := types.NewSignatureInfo(req.Pk, req.UnbondingTxSig) if err := ms.AddCovenantSigsToUndelegation( ctx, req.StakingTxHash, - unbondingSigInfo, - req.SlashingUnbondingTxSig); err != nil { + req.Pk, + req.UnbondingTxSig, + req.SlashingUnbondingTxSigs, + covenantQuorum, + ); err != nil { panic("failed to set BTC delegation that has passed verification") } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index e7d65a6c2..11b61dff4 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "bytes" "context" "errors" "math/rand" @@ -9,14 +10,6 @@ import ( sdkmath "cosmossdk.io/math" - "github.com/babylonchain/babylon/btcstaking" - "github.com/babylonchain/babylon/testutil/datagen" - keepertest "github.com/babylonchain/babylon/testutil/keeper" - bbn "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/babylonchain/babylon/x/btcstaking/keeper" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -26,6 +19,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/babylonchain/babylon/testutil/datagen" + keepertest "github.com/babylonchain/babylon/testutil/keeper" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/babylonchain/babylon/x/btcstaking/keeper" + "github.com/babylonchain/babylon/x/btcstaking/types" ) func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { @@ -202,7 +205,6 @@ func createDelegation( 0, slashignSpendInfo.RevealedLeaf.Script, delSK, - net, ) require.NoError(t, err) @@ -261,19 +263,21 @@ func createCovenantSig( slashingPathInfo, err := info.SlashingPathSpendInfo() require.NoError(t, err) - covenantSig, err := msgCreateBTCDel.SlashingTx.Sign( + encKey, err := asig.NewEncryptionKeyFromBTCPK(vPK) + require.NoError(t, err) + covenantSig, err := msgCreateBTCDel.SlashingTx.EncSign( stakingTx, 0, slashingPathInfo.RevealedLeaf.Script, covenantSK, - net, + encKey, ) require.NoError(t, err) msgAddCovenantSig := &types.MsgAddCovenantSig{ Signer: msgCreateBTCDel.Signer, Pk: bbn.NewBIP340PubKeyFromBTCPK(cPk), StakingTxHash: stakingTxHash, - Sig: covenantSig, + Sigs: [][]byte{covenantSig.MustMarshal()}, } _, err = ms.AddCovenantSig(goCtx, msgAddCovenantSig) require.NoError(t, err) @@ -283,7 +287,7 @@ func createCovenantSig( */ actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) require.NoError(t, err) - require.Equal(t, actualDelWithCovenantSig.CovenantSig.MustMarshal(), covenantSig.MustMarshal()) + require.Equal(t, actualDelWithCovenantSig.CovenantSigs[0].AdaptorSigs[0], covenantSig.MustMarshal()) require.True(t, actualDelWithCovenantSig.HasCovenantQuorum(bsKeeper.GetParams(sdkCtx).CovenantQuorum)) } @@ -362,7 +366,6 @@ func createUndelegation( 0, unbondingSlashingPathInfo.RevealedLeaf.Script, delSK, - net, ) require.NoError(t, err) @@ -527,7 +530,6 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { 0, slashingPathInfo.RevealedLeaf.Script, delSK, - net, ) require.NoError(t, err) @@ -608,8 +610,8 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, undelegateMsg.UnbondingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) + require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSigs) require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTime, undelegateMsg.UnbondingTime) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSig) require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSigList) }) } @@ -698,13 +700,12 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { del.StakingOutputIdx, stakingUnbondingPathInfo.RevealedLeaf.Script, covenantSK, - net, ) covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(unbondingTxSignatureCovenant) require.NoError(t, err) - // slash unbodning tx spends unbonding tx + // slash unbonding tx spends unbonding tx unbondingInfo, err := btcstaking.BuildUnbondingInfo( del.BtcPk.MustToBTCPK(), []*btcec.PublicKey{validatorPK}, @@ -719,21 +720,23 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() require.NoError(t, err) - slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.Sign( + enckey, err := asig.NewEncryptionKeyFromBTCPK(validatorPK) + require.NoError(t, err) + slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.EncSign( unbondingTx, 0, unbondingSlashingPathInfo.RevealedLeaf.Script, covenantSK, - net, + enckey, ) require.NoError(t, err) covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ - Signer: datagen.GenRandomAccount().Address, - Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - StakingTxHash: stakingTxHash, - UnbondingTxSig: &covenantUnbondingSig, - SlashingUnbondingTxSig: slashUnbondingTxSignatureCovenant, + Signer: datagen.GenRandomAccount().Address, + Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + StakingTxHash: stakingTxHash, + UnbondingTxSig: &covenantUnbondingSig, + SlashingUnbondingTxSigs: [][]byte{slashUnbondingTxSignatureCovenant.MustMarshal()}, } btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) @@ -743,7 +746,12 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSig) + require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList) + require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList, 1) + require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs, 1) + require.True(t, bytes.Equal(delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs[0].CovPk.MustMarshal(), + bbn.NewBIP340PubKeyFromBTCPK(covenantPK).MustMarshal())) + require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1) }) } diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 98e298e24..f935ae2e3 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -2,15 +2,17 @@ package types import ( "bytes" - sdkmath "cosmossdk.io/math" "encoding/hex" - "github.com/babylonchain/babylon/btcstaking" - bbn "github.com/babylonchain/babylon/types" + sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/babylonchain/babylon/types" ) type BTCSlashingTx []byte @@ -105,13 +107,13 @@ func (tx *BTCSlashingTx) Validate( ) } -// Sign generates a signature on the slashing tx signed by staker, validator or covenant +// Sign generates a signature on the slashing tx func (tx *BTCSlashingTx) Sign( stakingMsgTx *wire.MsgTx, spendOutputIndex uint32, scriptPath []byte, sk *btcec.PrivateKey, - net *chaincfg.Params) (*bbn.BIP340Signature, error) { +) (*types.BIP340Signature, error) { msgTx, err := tx.ToMsgTx() if err != nil { return nil, err @@ -122,12 +124,11 @@ func (tx *BTCSlashingTx) Sign( spendOutputIndex, scriptPath, sk, - net, ) if err != nil { return nil, err } - sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) + sig := types.NewBIP340SignatureFromBTCSig(schnorrSig) return &sig, nil } @@ -137,7 +138,7 @@ func (tx *BTCSlashingTx) VerifySignature( stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, - sig *bbn.BIP340Signature) error { + sig *types.BIP340Signature) error { msgTx, err := tx.ToMsgTx() if err != nil { return err @@ -151,3 +152,56 @@ func (tx *BTCSlashingTx) VerifySignature( *sig, ) } + +// EncSign generates an adaptor signature on the slashing tx with validator's +// public key as encryption key +func (tx *BTCSlashingTx) EncSign( + stakingMsgTx *wire.MsgTx, + spendOutputIndex uint32, + scriptPath []byte, + sk *btcec.PrivateKey, + encKey *asig.EncryptionKey, +) (*asig.AdaptorSignature, error) { + msgTx, err := tx.ToMsgTx() + if err != nil { + return nil, err + } + adaptorSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( + msgTx, + stakingMsgTx, + spendOutputIndex, + scriptPath, + sk, + encKey, + ) + if err != nil { + return nil, err + } + + return adaptorSig, nil +} + +// EncVerifyAdaptorSignature verifies an adaptor signature on the slashing tx +// with the validator's public key as encryption key +func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( + stakingPkScript []byte, + stakingAmount int64, + stakingScript []byte, + pk *btcec.PublicKey, + encKey *asig.EncryptionKey, + sig *asig.AdaptorSignature, +) error { + msgTx, err := tx.ToMsgTx() + if err != nil { + return err + } + return btcstaking.EncVerifyTransactionSigWithOutputData( + msgTx, + stakingPkScript, + stakingAmount, + stakingScript, + pk, + encKey, + sig, + ) +} diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 2e2330d29..883190da2 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -1,17 +1,20 @@ package types_test import ( - sdkmath "cosmossdk.io/math" "math/rand" "testing" - "github.com/babylonchain/babylon/btcstaking" - btctest "github.com/babylonchain/babylon/testutil/bitcoin" - "github.com/babylonchain/babylon/testutil/datagen" + sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + btctest "github.com/babylonchain/babylon/testutil/bitcoin" + "github.com/babylonchain/babylon/testutil/datagen" ) func FuzzSlashingTxWithWitness(f *testing.F) { @@ -67,11 +70,13 @@ func FuzzSlashingTxWithWitness(f *testing.F) { slashingScript := slashingScriptInfo.RevealedLeaf.Script // sign slashing tx - valSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, valSK, net) + valSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, valSK) + require.NoError(t, err) + delSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, delSK) require.NoError(t, err) - delSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, delSK, net) + enckey, err := asig.NewEncryptionKeyFromBTCPK(valPK) require.NoError(t, err) - covenantSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, covenantSK, net) + covenantSig, err := slashingTx.EncSign(stakingMsgTx, 0, slashingScript, covenantSK, enckey) require.NoError(t, err) // verify signatures first @@ -79,17 +84,19 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, delPK, delSig) require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, covenantPK, covenantSig) + err = slashingTx.EncVerifyAdaptorSignature(stakingPkScript, stakingValue, slashingScript, covenantPK, enckey, covenantSig) require.NoError(t, err) stakerSigBytes := delSig.MustMarshal() validatorSigBytes := valSig.MustMarshal() - covSigBytes := covenantSig.MustMarshal() + decryptKey, err := asig.NewDecyptionKeyFromBTCSK(valSK) + require.NoError(t, err) + covSigDecryptedBytes := covenantSig.Decrypt(decryptKey).Serialize() // TODO: use comittee witness, err := btcstaking.CreateBabylonWitness( [][]byte{ - covSigBytes, + covSigDecryptedBytes, validatorSigBytes, stakerSigBytes, }, diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 207cbcda1..7838d61a0 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -4,8 +4,10 @@ import ( "fmt" "sort" - bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/chaincfg/chainhash" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" ) func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, error) { @@ -87,26 +89,54 @@ func (d *BTCDelegation) ValidateBasic() error { return nil } -// HasCovenantSig returns whether a BTC delegation has a covenant signature +// HasCovenantQuorum returns whether a BTC delegation has sufficient sigs +// from Covenant members to make a quorum func (d *BTCDelegation) HasCovenantQuorum(quorum uint32) bool { - // TODO: accomodate covenant committee for CovenantSig - return d.CovenantSig != nil + return uint32(len(d.CovenantSigs)) >= quorum } // IsSignedByCovMember checks whether the given covenant PK has signed the delegation -func (d *BTCDelegation) IsSignedByCovMember(covPK *bbn.BIP340PubKey) bool { - // TODO: accomodate covenant committee for CovenantSig - return d.CovenantSig != nil +func (d *BTCDelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { + for _, sigInfo := range d.CovenantSigs { + if covPk.Equals(sigInfo.CovPk) { + return true + } + } + + return false +} + +func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.AdaptorSignature, quorum uint32) error { + // we can ignore the covenant sig if quorum is already reached + if d.HasCovenantQuorum(quorum) { + return nil + } + // ensure that this covenant member has not signed the delegation yet + if d.IsSignedByCovMember(covPk) { + return ErrDuplicatedCovenantSig + } + + adaptorSigs := make([][]byte, 0, len(sigs)) + for _, s := range sigs { + adaptorSigs = append(adaptorSigs, s.MustMarshal()) + } + covSigs := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} + + d.CovenantSigs = append(d.CovenantSigs, covSigs) + + return nil +} + +func (ud *BTCUndelegation) HasCovenantQuorumOnSlashing(quorum uint32) bool { + return len(ud.CovenantUnbondingSigList) >= int(quorum) } -func (ud *BTCUndelegation) HasCovenantQuorum(quorum uint32) bool { - // TODO: accomodate covenant committee for CovenantSlashingSig - return ud.CovenantSlashingSig != nil && len(ud.CovenantUnbondingSigList) >= int(quorum) +func (ud *BTCUndelegation) HasCovenantQuorumOnUnbonding(quorum uint32) bool { + return len(ud.CovenantUnbondingSigList) >= int(quorum) } -// IsSignedByCovMember checks whether the given covenant PK has signed the undelegation -func (ud *BTCUndelegation) IsSignedByCovMember(covPK *bbn.BIP340PubKey) bool { - // TODO: accomodate covenant committee for CovenantSlashingSig +// IsSignedByCovMemberOnUnbonding checks whether the given covenant PK has signed the unbonding tx +func (ud *BTCUndelegation) IsSignedByCovMemberOnUnbonding(covPK *bbn.BIP340PubKey) bool { for _, sigInfo := range ud.CovenantUnbondingSigList { if sigInfo.Pk.Equals(covPK) { return true @@ -115,8 +145,50 @@ func (ud *BTCUndelegation) IsSignedByCovMember(covPK *bbn.BIP340PubKey) bool { return false } +// IsSignedByCovMemberOnSlashing checks whether the given covenant PK has signed the slashing tx +func (ud *BTCUndelegation) IsSignedByCovMemberOnSlashing(covPK *bbn.BIP340PubKey) bool { + for _, sigInfo := range ud.CovenantSlashingSigs { + if sigInfo.CovPk.Equals(covPK) { + return true + } + } + return false +} + +func (ud *BTCUndelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { + return ud.IsSignedByCovMemberOnUnbonding(covPk) && ud.IsSignedByCovMemberOnSlashing(covPk) +} + func (ud *BTCUndelegation) HasAllSignatures(covenantQuorum uint32) bool { - return ud.HasCovenantQuorum(covenantQuorum) + return ud.HasCovenantQuorumOnUnbonding(covenantQuorum) && ud.HasCovenantQuorumOnSlashing(covenantQuorum) +} + +func (ud *BTCUndelegation) AddCovenantSigs( + covPk *bbn.BIP340PubKey, + unbondingSig *bbn.BIP340Signature, + slashingSigs []asig.AdaptorSignature, + quorum uint32, +) error { + // we can ignore the covenant slashing sig if quorum is already reached + if ud.HasAllSignatures(quorum) { + return nil + } + + if ud.IsSignedByCovMember(covPk) { + return ErrDuplicatedCovenantSig + } + + covUnbondingSigInfo := &SignatureInfo{Pk: covPk, Sig: unbondingSig} + ud.CovenantUnbondingSigList = append(ud.CovenantUnbondingSigList, covUnbondingSigInfo) + + adaptorSigs := make([][]byte, 0, len(slashingSigs)) + for _, s := range slashingSigs { + adaptorSigs = append(adaptorSigs, s.MustMarshal()) + } + slashingSigsInfo := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} + ud.CovenantSlashingSigs = append(ud.CovenantSlashingSigs, slashingSigsInfo) + + return nil } // GetStatus returns the status of the BTC Delegation based on a BTC height and a w value diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index d5b82866f..a1ad77207 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -284,13 +284,11 @@ type BTCDelegation struct { // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,11,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` - // covenant_sig is the signature signature on the slashing tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) + // covenant_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member // It will be a part of the witness for the staking tx output. - // TODO: change to a set of (covenant PK, covenant adaptor signature) tuples - // over each restaked BTC validator (i.e., a matrix of tuples) - CovenantSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,12,opt,name=covenant_sig,json=covenantSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_sig,omitempty"` - // if this object is present it menans that staker requested undelegation, and whole + CovenantSigs []*CovenantAdaptorSignatures `protobuf:"bytes,12,rep,name=covenant_sigs,json=covenantSigs,proto3" json:"covenant_sigs,omitempty"` + // if this object is present it means that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object @@ -379,6 +377,13 @@ func (m *BTCDelegation) GetStakingOutputIdx() uint32 { return 0 } +func (m *BTCDelegation) GetCovenantSigs() []*CovenantAdaptorSignatures { + if m != nil { + return m.CovenantSigs + } + return nil +} + func (m *BTCDelegation) GetBtcUndelegation() *BTCUndelegation { if m != nil { return m.BtcUndelegation @@ -394,19 +399,18 @@ type BTCUndelegation struct { UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // unbonding_time describes how long the funds will be locked in the unbonding output UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` - // slashing_tx is the slashing tx for unbodning transactions + // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). - // It will be a part of the witness for the unbodning tx output. + // It will be a part of the witness for the unbonding tx output. DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` - // covenant_slashing_sig is the signature on the slashing tx - // by the covenant (i.e., SK corresponding to covenant_pk in params) - // It must be provided after processing undelagate message by Babylon - // TODO: change to a matrix of (covenant PK, covenant adaptor signature) tuples - CovenantSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=covenant_slashing_sig,json=covenantSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"covenant_slashing_sig,omitempty"` + // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member + // It will be a part of the witness for the staking tx output. + CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,5,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon @@ -460,6 +464,13 @@ func (m *BTCUndelegation) GetUnbondingTime() uint32 { return 0 } +func (m *BTCUndelegation) GetCovenantSlashingSigs() []*CovenantAdaptorSignatures { + if m != nil { + return m.CovenantSlashingSigs + } + return nil +} + func (m *BTCUndelegation) GetCovenantUnbondingSigList() []*SignatureInfo { if m != nil { return m.CovenantUnbondingSigList @@ -475,8 +486,6 @@ type BTCUndelegationInfo struct { UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - // It must be provided after processing undelagate message by Babylon and after - // validator sig will be provided by validator CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,2,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } @@ -656,6 +665,55 @@ func (m *SignatureInfo) XXX_DiscardUnknown() { var xxx_messageInfo_SignatureInfo proto.InternalMessageInfo +// CovenantAdaptorSignatures is a list adaptor signatures signed by the +// covenant with different validator's public keys as encryption keys +type CovenantAdaptorSignatures struct { + // cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature + CovPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=cov_pk,json=covPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"cov_pk,omitempty"` + // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC validator's public key + AdaptorSigs [][]byte `protobuf:"bytes,2,rep,name=adaptor_sigs,json=adaptorSigs,proto3" json:"adaptor_sigs,omitempty"` +} + +func (m *CovenantAdaptorSignatures) Reset() { *m = CovenantAdaptorSignatures{} } +func (m *CovenantAdaptorSignatures) String() string { return proto.CompactTextString(m) } +func (*CovenantAdaptorSignatures) ProtoMessage() {} +func (*CovenantAdaptorSignatures) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{8} +} +func (m *CovenantAdaptorSignatures) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CovenantAdaptorSignatures) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CovenantAdaptorSignatures.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CovenantAdaptorSignatures) XXX_Merge(src proto.Message) { + xxx_messageInfo_CovenantAdaptorSignatures.Merge(m, src) +} +func (m *CovenantAdaptorSignatures) XXX_Size() int { + return m.Size() +} +func (m *CovenantAdaptorSignatures) XXX_DiscardUnknown() { + xxx_messageInfo_CovenantAdaptorSignatures.DiscardUnknown(m) +} + +var xxx_messageInfo_CovenantAdaptorSignatures proto.InternalMessageInfo + +func (m *CovenantAdaptorSignatures) GetAdaptorSigs() [][]byte { + if m != nil { + return m.AdaptorSigs + } + return nil +} + func init() { proto.RegisterEnum("babylon.btcstaking.v1.BTCDelegationStatus", BTCDelegationStatus_name, BTCDelegationStatus_value) proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") @@ -666,6 +724,7 @@ func init() { proto.RegisterType((*BTCDelegatorDelegations)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegations") proto.RegisterType((*BTCDelegatorDelegationIndex)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegationIndex") proto.RegisterType((*SignatureInfo)(nil), "babylon.btcstaking.v1.SignatureInfo") + proto.RegisterType((*CovenantAdaptorSignatures)(nil), "babylon.btcstaking.v1.CovenantAdaptorSignatures") } func init() { @@ -673,74 +732,77 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1060 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xe3, 0x44, - 0x17, 0xae, 0x93, 0x34, 0xbb, 0x39, 0x49, 0xde, 0x66, 0x67, 0xbb, 0x7d, 0x43, 0x2b, 0x92, 0x10, - 0x96, 0x55, 0x84, 0x58, 0x87, 0x76, 0x3f, 0x04, 0x5c, 0x20, 0x6d, 0x9a, 0xc2, 0x46, 0xdb, 0x8f, - 0xe0, 0xa4, 0x8b, 0x40, 0x80, 0x35, 0xb6, 0xa7, 0x8e, 0x95, 0xc4, 0x63, 0x65, 0x26, 0x21, 0xb9, - 0xe7, 0x07, 0xf0, 0x07, 0xb8, 0xe3, 0x8a, 0x6b, 0x7e, 0x04, 0x97, 0x2b, 0xe0, 0x02, 0xf5, 0xa2, - 0x42, 0xed, 0x1f, 0x41, 0x1e, 0x8f, 0x63, 0xb7, 0xb4, 0xfb, 0x41, 0x7a, 0xd7, 0x99, 0x73, 0xce, - 0x73, 0x9e, 0xf3, 0x3c, 0xc7, 0xd3, 0xc0, 0x3d, 0x03, 0x1b, 0xb3, 0x01, 0x75, 0xeb, 0x06, 0x37, - 0x19, 0xc7, 0x7d, 0xc7, 0xb5, 0xeb, 0x93, 0xcd, 0xd8, 0x49, 0xf5, 0x46, 0x94, 0x53, 0x74, 0x47, - 0xe6, 0xa9, 0xb1, 0xc8, 0x64, 0x73, 0x7d, 0xd5, 0xa6, 0x36, 0x15, 0x19, 0x75, 0xff, 0xaf, 0x20, - 0x79, 0xfd, 0x2d, 0x93, 0xb2, 0x21, 0x65, 0x7a, 0x10, 0x08, 0x0e, 0x32, 0x54, 0x0d, 0x4e, 0x75, - 0x73, 0x34, 0xf3, 0x38, 0xad, 0x33, 0x62, 0x7a, 0x5b, 0x8f, 0x1e, 0xf7, 0x37, 0xeb, 0x7d, 0x32, - 0x0b, 0x73, 0xee, 0xca, 0x9c, 0x88, 0x8f, 0x41, 0x38, 0xde, 0xac, 0x9f, 0x63, 0xb4, 0x5e, 0xbe, - 0x9c, 0xb9, 0x47, 0xbd, 0x20, 0xa1, 0xfa, 0x47, 0x12, 0x72, 0x8d, 0xee, 0xf6, 0x73, 0x3c, 0x70, - 0x2c, 0xcc, 0xe9, 0x08, 0xed, 0x40, 0xd6, 0x22, 0xcc, 0x1c, 0x39, 0x1e, 0x77, 0xa8, 0x5b, 0x54, - 0x2a, 0x4a, 0x2d, 0xbb, 0xf5, 0xae, 0x2a, 0xf9, 0x45, 0x53, 0x89, 0x6e, 0x6a, 0x33, 0x4a, 0xd5, - 0xe2, 0x75, 0x68, 0x0f, 0xc0, 0xa4, 0xc3, 0xa1, 0xc3, 0x98, 0x8f, 0x92, 0xa8, 0x28, 0xb5, 0x4c, - 0xe3, 0xfe, 0xf1, 0x49, 0x79, 0x23, 0x00, 0x62, 0x56, 0x5f, 0x75, 0x68, 0x7d, 0x88, 0x79, 0x4f, - 0xdd, 0x25, 0x36, 0x36, 0x67, 0x4d, 0x62, 0xfe, 0xfe, 0xeb, 0x7d, 0x90, 0x7d, 0x9a, 0xc4, 0xd4, - 0x62, 0x00, 0xe8, 0x53, 0x00, 0x39, 0x89, 0xee, 0xf5, 0x8b, 0x49, 0x41, 0xaa, 0x1c, 0x92, 0x0a, - 0x64, 0x52, 0xe7, 0x32, 0xa9, 0xed, 0xb1, 0xf1, 0x8c, 0xcc, 0xb4, 0x8c, 0x2c, 0x69, 0xf7, 0xd1, - 0x1e, 0xa4, 0x0d, 0x6e, 0xfa, 0xb5, 0xa9, 0x8a, 0x52, 0xcb, 0x35, 0x1e, 0x1f, 0x9f, 0x94, 0xb7, - 0x6c, 0x87, 0xf7, 0xc6, 0x86, 0x6a, 0xd2, 0x61, 0x5d, 0x66, 0x9a, 0x3d, 0xec, 0xb8, 0xe1, 0xa1, - 0xce, 0x67, 0x1e, 0x61, 0x6a, 0xa3, 0xd5, 0x7e, 0xf0, 0xf0, 0x43, 0x09, 0xb9, 0x6c, 0x70, 0xb3, - 0xdd, 0x47, 0x9f, 0x40, 0xd2, 0xa3, 0x5e, 0x71, 0x59, 0xf0, 0xa8, 0xa9, 0x97, 0xda, 0xae, 0xb6, - 0x47, 0x94, 0x1e, 0x1d, 0x1c, 0xb5, 0x29, 0x63, 0x44, 0x4c, 0xa1, 0xf9, 0x45, 0xe8, 0x21, 0xac, - 0xb1, 0x01, 0x66, 0x3d, 0x62, 0xe9, 0xe1, 0x48, 0x3d, 0xe2, 0xd8, 0x3d, 0x5e, 0x4c, 0x57, 0x94, - 0x5a, 0x4a, 0x5b, 0x95, 0xd1, 0x46, 0x10, 0x7c, 0x2a, 0x62, 0xe8, 0x03, 0x40, 0xf3, 0x2a, 0x6e, - 0x86, 0x15, 0x37, 0x44, 0x45, 0x21, 0xac, 0xe0, 0x66, 0x90, 0x5d, 0xfd, 0x21, 0x01, 0xab, 0x71, - 0x57, 0xbf, 0x74, 0x78, 0x6f, 0x8f, 0x70, 0x1c, 0xd3, 0x41, 0xb9, 0x0e, 0x1d, 0xd6, 0x20, 0x2d, - 0x99, 0x24, 0x04, 0x13, 0x79, 0x42, 0xef, 0x40, 0x6e, 0x42, 0xb9, 0xe3, 0xda, 0xba, 0x47, 0xbf, - 0x27, 0x23, 0x61, 0x58, 0x4a, 0xcb, 0x06, 0x77, 0x6d, 0xff, 0xea, 0x25, 0x32, 0xa4, 0xde, 0x58, - 0x86, 0xe5, 0x2b, 0x64, 0xf8, 0x25, 0x0d, 0xf9, 0x46, 0x77, 0xbb, 0x49, 0x06, 0xc4, 0xc6, 0xfc, - 0xdf, 0x7b, 0xa4, 0x2c, 0xb0, 0x47, 0x89, 0x6b, 0xdc, 0xa3, 0xe4, 0x7f, 0xd9, 0xa3, 0x6f, 0x61, - 0x65, 0x82, 0x07, 0x7a, 0x40, 0x47, 0x1f, 0x38, 0xcc, 0x57, 0x2e, 0xb9, 0x00, 0xa7, 0xdc, 0x04, - 0x0f, 0x1a, 0x3e, 0xad, 0x5d, 0x87, 0x09, 0x0b, 0x19, 0xc7, 0x23, 0x7e, 0x5e, 0xe3, 0xac, 0xb8, - 0x93, 0x66, 0xbc, 0x0d, 0x40, 0x5c, 0xeb, 0xfc, 0xf6, 0x66, 0x88, 0x6b, 0xc9, 0xf0, 0x06, 0x64, - 0x38, 0xe5, 0x78, 0xa0, 0x33, 0x1c, 0x6e, 0xea, 0x4d, 0x71, 0xd1, 0xc1, 0xa2, 0x56, 0x8e, 0xa8, - 0xf3, 0x69, 0xf1, 0xa6, 0x2f, 0xa6, 0x96, 0x91, 0x37, 0xdd, 0xa9, 0xf0, 0x59, 0x86, 0xe9, 0x98, - 0x7b, 0x63, 0xae, 0x3b, 0xd6, 0xb4, 0x98, 0xa9, 0x28, 0xb5, 0xbc, 0x56, 0x90, 0x91, 0x03, 0x11, - 0x68, 0x59, 0x53, 0xb4, 0x05, 0x59, 0xe1, 0xbd, 0x44, 0x03, 0x61, 0xcd, 0xad, 0xe3, 0x93, 0xb2, - 0xef, 0x7e, 0x47, 0x46, 0xba, 0x53, 0x0d, 0xd8, 0xfc, 0x6f, 0xf4, 0x1d, 0xe4, 0xad, 0x60, 0x2f, - 0xe8, 0x48, 0x67, 0x8e, 0x5d, 0xcc, 0x8a, 0xaa, 0x8f, 0x8f, 0x4f, 0xca, 0x8f, 0xde, 0x44, 0xbc, - 0x8e, 0x63, 0xbb, 0x98, 0x8f, 0x47, 0x44, 0xcb, 0xcd, 0xf1, 0x3a, 0x8e, 0x8d, 0xbe, 0x81, 0x9c, - 0x49, 0x27, 0xc4, 0xc5, 0x2e, 0x17, 0xf0, 0xb9, 0x45, 0xe1, 0xb3, 0x21, 0x9c, 0x8f, 0xfe, 0x05, - 0x14, 0x7c, 0xe3, 0xc7, 0xae, 0x35, 0xdf, 0xed, 0x62, 0x5e, 0x6c, 0xd1, 0xbd, 0x2b, 0xb6, 0xa8, - 0xd1, 0xdd, 0x3e, 0x8c, 0x65, 0x6b, 0x2b, 0x06, 0x37, 0xe3, 0x17, 0xd5, 0x3f, 0x93, 0xb0, 0x72, - 0x21, 0xc9, 0x5f, 0x82, 0xb1, 0x6b, 0x50, 0xd7, 0x92, 0xca, 0x8a, 0x47, 0x43, 0xcb, 0xce, 0xef, - 0xba, 0x53, 0xf4, 0x1e, 0xfc, 0x2f, 0x96, 0xe2, 0x0c, 0x89, 0xf8, 0x32, 0xf2, 0x5a, 0x3e, 0x4a, - 0x72, 0x86, 0xe4, 0xa2, 0x45, 0xc9, 0xd7, 0xb1, 0x88, 0xc2, 0x5a, 0xcc, 0xa2, 0xb0, 0xda, 0x17, - 0x33, 0xb5, 0xa8, 0x98, 0xab, 0x91, 0x57, 0x12, 0xd7, 0x57, 0x75, 0x08, 0x77, 0x22, 0xcf, 0xe2, - 0xfd, 0x96, 0x17, 0xed, 0x77, 0x7b, 0x6e, 0x5e, 0xac, 0x9d, 0x09, 0x1b, 0xf3, 0x76, 0x91, 0x86, - 0xcc, 0xb1, 0x83, 0xaf, 0x39, 0x5d, 0x49, 0xd6, 0xb2, 0x5b, 0x77, 0xaf, 0xf0, 0x73, 0x8e, 0xdd, - 0x72, 0x8f, 0xa8, 0x56, 0x0c, 0x81, 0x0e, 0x43, 0x9c, 0x8e, 0x63, 0xfb, 0xdf, 0x71, 0xf5, 0x27, - 0x05, 0x6e, 0x5f, 0xb0, 0xd5, 0xaf, 0x78, 0x1d, 0x6b, 0x5f, 0xc1, 0x2f, 0x71, 0x2d, 0xfc, 0x3a, - 0xf0, 0xff, 0xe8, 0x89, 0xa6, 0xa3, 0xe8, 0xad, 0x66, 0xe8, 0x23, 0x48, 0x59, 0x64, 0xc0, 0x8a, - 0xca, 0x4b, 0x1b, 0x9d, 0x7b, 0xe0, 0x35, 0x51, 0x51, 0xdd, 0x87, 0x8d, 0xcb, 0x41, 0x5b, 0xae, - 0x45, 0xa6, 0xa8, 0x0e, 0xab, 0xd1, 0xe3, 0xa3, 0xf7, 0x30, 0xeb, 0x05, 0x13, 0xf9, 0x8d, 0x72, - 0xda, 0xad, 0xf9, 0x33, 0xf4, 0x14, 0xb3, 0x9e, 0x20, 0xf9, 0xb3, 0x02, 0xf9, 0x73, 0x03, 0xa1, - 0xcf, 0x20, 0xb1, 0xf0, 0x3f, 0xd1, 0x84, 0xd7, 0x47, 0xcf, 0x20, 0xe9, 0x2f, 0x58, 0x62, 0xd1, - 0x05, 0xf3, 0x51, 0xde, 0xef, 0x0a, 0xab, 0xa3, 0x69, 0x3b, 0x1c, 0xf3, 0x31, 0x43, 0x59, 0xb8, - 0xd1, 0xde, 0xd9, 0x6f, 0xb6, 0xf6, 0x3f, 0x2f, 0x2c, 0x21, 0x80, 0xf4, 0x93, 0xed, 0x6e, 0xeb, - 0xf9, 0x4e, 0x41, 0x41, 0x79, 0xc8, 0x1c, 0xee, 0x37, 0x0e, 0x82, 0x50, 0x02, 0xe5, 0xe0, 0x66, - 0x70, 0xdc, 0x69, 0x16, 0x92, 0xe8, 0x06, 0x24, 0x9f, 0xec, 0x7f, 0x55, 0x48, 0x35, 0x76, 0x7f, - 0x3b, 0x2d, 0x29, 0x2f, 0x4e, 0x4b, 0xca, 0xdf, 0xa7, 0x25, 0xe5, 0xc7, 0xb3, 0xd2, 0xd2, 0x8b, - 0xb3, 0xd2, 0xd2, 0x5f, 0x67, 0xa5, 0xa5, 0xaf, 0x5f, 0x39, 0xf4, 0x34, 0xfe, 0xbb, 0x53, 0x10, - 0x37, 0xd2, 0xe2, 0x77, 0xe7, 0x83, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x99, 0xd6, 0x9d, - 0x54, 0x0b, 0x00, 0x00, + // 1111 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0xae, 0x93, 0x34, 0x6d, 0x4e, 0x12, 0x9a, 0x1d, 0x42, 0xf1, 0xb6, 0x22, 0x09, 0x61, 0x59, + 0x45, 0x88, 0x75, 0xb6, 0xdd, 0x1f, 0x01, 0x17, 0x48, 0x4d, 0x53, 0xd8, 0x68, 0xfb, 0x13, 0x9c, + 0x74, 0x11, 0x48, 0x10, 0x4d, 0xec, 0xa9, 0x63, 0x25, 0xf1, 0x58, 0x99, 0x49, 0x48, 0xee, 0xb9, + 0x45, 0xe2, 0x05, 0xb8, 0x82, 0x47, 0xe0, 0x21, 0xb8, 0x41, 0x5a, 0xc1, 0x0d, 0xea, 0x45, 0x85, + 0xda, 0x17, 0x41, 0x1e, 0x4f, 0x6c, 0xb7, 0xb4, 0xfb, 0xd7, 0xde, 0x65, 0xce, 0xcf, 0x77, 0xce, + 0xf9, 0xbe, 0x33, 0xe3, 0xc0, 0xdd, 0x2e, 0xee, 0xce, 0x06, 0xd4, 0xa9, 0x76, 0xb9, 0xc1, 0x38, + 0xee, 0xdb, 0x8e, 0x55, 0x9d, 0x6c, 0x44, 0x4e, 0x9a, 0x3b, 0xa2, 0x9c, 0xa2, 0x77, 0x64, 0x9c, + 0x16, 0xf1, 0x4c, 0x36, 0xd6, 0xf2, 0x16, 0xb5, 0xa8, 0x88, 0xa8, 0x7a, 0xbf, 0xfc, 0xe0, 0xb5, + 0xdb, 0x06, 0x65, 0x43, 0xca, 0x3a, 0xbe, 0xc3, 0x3f, 0x48, 0x57, 0xd9, 0x3f, 0x55, 0x8d, 0xd1, + 0xcc, 0xe5, 0xb4, 0xca, 0x88, 0xe1, 0x6e, 0x3e, 0x7a, 0xdc, 0xdf, 0xa8, 0xf6, 0xc9, 0x6c, 0x1e, + 0x73, 0x47, 0xc6, 0x84, 0xfd, 0x74, 0x09, 0xc7, 0x1b, 0xd5, 0x73, 0x1d, 0xad, 0x15, 0x2f, 0xef, + 0xdc, 0xa5, 0xae, 0x1f, 0x50, 0xfe, 0x3b, 0x0e, 0x99, 0x5a, 0x7b, 0xfb, 0x19, 0x1e, 0xd8, 0x26, + 0xe6, 0x74, 0x84, 0x76, 0x20, 0x6d, 0x12, 0x66, 0x8c, 0x6c, 0x97, 0xdb, 0xd4, 0x51, 0x95, 0x92, + 0x52, 0x49, 0x6f, 0x7e, 0xa0, 0xc9, 0xfe, 0xc2, 0xa9, 0x44, 0x35, 0xad, 0x1e, 0x86, 0xea, 0xd1, + 0x3c, 0xb4, 0x07, 0x60, 0xd0, 0xe1, 0xd0, 0x66, 0xcc, 0x43, 0x89, 0x95, 0x94, 0x4a, 0xaa, 0x76, + 0xef, 0xf8, 0xa4, 0xb8, 0xee, 0x03, 0x31, 0xb3, 0xaf, 0xd9, 0xb4, 0x3a, 0xc4, 0xbc, 0xa7, 0xed, + 0x12, 0x0b, 0x1b, 0xb3, 0x3a, 0x31, 0xfe, 0xfa, 0xfd, 0x1e, 0xc8, 0x3a, 0x75, 0x62, 0xe8, 0x11, + 0x00, 0xf4, 0x39, 0x80, 0x9c, 0xa4, 0xe3, 0xf6, 0xd5, 0xb8, 0x68, 0xaa, 0x38, 0x6f, 0xca, 0xa7, + 0x49, 0x0b, 0x68, 0xd2, 0x9a, 0xe3, 0xee, 0x53, 0x32, 0xd3, 0x53, 0x32, 0xa5, 0xd9, 0x47, 0x7b, + 0x90, 0xec, 0x72, 0xc3, 0xcb, 0x4d, 0x94, 0x94, 0x4a, 0xa6, 0xf6, 0xf8, 0xf8, 0xa4, 0xb8, 0x69, + 0xd9, 0xbc, 0x37, 0xee, 0x6a, 0x06, 0x1d, 0x56, 0x65, 0xa4, 0xd1, 0xc3, 0xb6, 0x33, 0x3f, 0x54, + 0xf9, 0xcc, 0x25, 0x4c, 0xab, 0x35, 0x9a, 0x0f, 0x1e, 0xde, 0x97, 0x90, 0x8b, 0x5d, 0x6e, 0x34, + 0xfb, 0xe8, 0x33, 0x88, 0xbb, 0xd4, 0x55, 0x17, 0x45, 0x1f, 0x15, 0xed, 0x52, 0xd9, 0xb5, 0xe6, + 0x88, 0xd2, 0xa3, 0x83, 0xa3, 0x26, 0x65, 0x8c, 0x88, 0x29, 0x74, 0x2f, 0x09, 0x3d, 0x84, 0x55, + 0x36, 0xc0, 0xac, 0x47, 0xcc, 0xce, 0x7c, 0xa4, 0x1e, 0xb1, 0xad, 0x1e, 0x57, 0x93, 0x25, 0xa5, + 0x92, 0xd0, 0xf3, 0xd2, 0x5b, 0xf3, 0x9d, 0x4f, 0x84, 0x0f, 0x7d, 0x0c, 0x28, 0xc8, 0xe2, 0xc6, + 0x3c, 0x63, 0x49, 0x64, 0xe4, 0xe6, 0x19, 0xdc, 0xf0, 0xa3, 0xcb, 0x3f, 0xc6, 0x20, 0x1f, 0x55, + 0xf5, 0x6b, 0x9b, 0xf7, 0xf6, 0x08, 0xc7, 0x11, 0x1e, 0x94, 0x9b, 0xe0, 0x61, 0x15, 0x92, 0xb2, + 0x93, 0x98, 0xe8, 0x44, 0x9e, 0xd0, 0xfb, 0x90, 0x99, 0x50, 0x6e, 0x3b, 0x56, 0xc7, 0xa5, 0x3f, + 0x90, 0x91, 0x10, 0x2c, 0xa1, 0xa7, 0x7d, 0x5b, 0xd3, 0x33, 0xbd, 0x80, 0x86, 0xc4, 0x6b, 0xd3, + 0xb0, 0x78, 0x05, 0x0d, 0xbf, 0x26, 0x21, 0x5b, 0x6b, 0x6f, 0xd7, 0xc9, 0x80, 0x58, 0x98, 0xff, + 0x7f, 0x8f, 0x94, 0x6b, 0xec, 0x51, 0xec, 0x06, 0xf7, 0x28, 0xfe, 0x26, 0x7b, 0xf4, 0x1d, 0xac, + 0x4c, 0xf0, 0xa0, 0xe3, 0xb7, 0xd3, 0x19, 0xd8, 0xcc, 0x63, 0x2e, 0x7e, 0x8d, 0x9e, 0x32, 0x13, + 0x3c, 0xa8, 0x79, 0x6d, 0xed, 0xda, 0x4c, 0x48, 0xc8, 0x38, 0x1e, 0xf1, 0xf3, 0x1c, 0xa7, 0x85, + 0x4d, 0x8a, 0xf1, 0x1e, 0x00, 0x71, 0xcc, 0xf3, 0xdb, 0x9b, 0x22, 0x8e, 0x29, 0xdd, 0xeb, 0x90, + 0xe2, 0x94, 0xe3, 0x41, 0x87, 0xe1, 0xf9, 0xa6, 0x2e, 0x0b, 0x43, 0x0b, 0x8b, 0x5c, 0x39, 0x62, + 0x87, 0x4f, 0xd5, 0x65, 0x8f, 0x4c, 0x3d, 0x25, 0x2d, 0xed, 0xa9, 0xd0, 0x59, 0xba, 0xe9, 0x98, + 0xbb, 0x63, 0xde, 0xb1, 0xcd, 0xa9, 0x9a, 0x2a, 0x29, 0x95, 0xac, 0x9e, 0x93, 0x9e, 0x03, 0xe1, + 0x68, 0x98, 0x53, 0xb4, 0x09, 0x69, 0xa1, 0xbd, 0x44, 0x03, 0x21, 0xcd, 0xad, 0xe3, 0x93, 0xa2, + 0xa7, 0x7e, 0x4b, 0x7a, 0xda, 0x53, 0x1d, 0x58, 0xf0, 0x1b, 0x7d, 0x0f, 0x59, 0xd3, 0xdf, 0x0b, + 0x3a, 0xea, 0x30, 0xdb, 0x52, 0xd3, 0x22, 0xeb, 0xd3, 0xe3, 0x93, 0xe2, 0xa3, 0xd7, 0x21, 0xaf, + 0x65, 0x5b, 0x0e, 0xe6, 0xe3, 0x11, 0xd1, 0x33, 0x01, 0x5e, 0xcb, 0xb6, 0xd0, 0x21, 0x64, 0x0d, + 0x3a, 0x21, 0x0e, 0x76, 0xb8, 0x07, 0xcf, 0xd4, 0x4c, 0x29, 0x5e, 0x49, 0x6f, 0xde, 0xbf, 0x42, + 0xe4, 0x6d, 0x19, 0xbb, 0x65, 0x62, 0xd7, 0x47, 0xf0, 0x51, 0x99, 0x9e, 0x99, 0xc3, 0xb4, 0x6c, + 0x8b, 0xa1, 0xaf, 0x20, 0xe7, 0x29, 0x3e, 0x76, 0xcc, 0x60, 0xa9, 0xd5, 0xac, 0x58, 0x9f, 0xbb, + 0x57, 0x20, 0xd7, 0xda, 0xdb, 0x87, 0x91, 0x68, 0x7d, 0xa5, 0xcb, 0x8d, 0xa8, 0xa1, 0xfc, 0x67, + 0x1c, 0x56, 0x2e, 0x04, 0x79, 0xea, 0x8f, 0x9d, 0x2e, 0x75, 0x4c, 0x49, 0xa9, 0x78, 0x2d, 0xf4, + 0x74, 0x60, 0x6b, 0x4f, 0xd1, 0x87, 0xf0, 0x56, 0x24, 0xc4, 0x1e, 0x12, 0x71, 0x25, 0xb2, 0x7a, + 0x36, 0x0c, 0xb2, 0x87, 0xe4, 0xa2, 0x36, 0xf1, 0x57, 0xd1, 0x86, 0xc2, 0x6a, 0x44, 0x9b, 0x79, + 0xb6, 0x27, 0x52, 0xe2, 0xba, 0x22, 0xe5, 0x43, 0x91, 0x24, 0xae, 0x27, 0xd6, 0x11, 0xac, 0x86, + 0x62, 0x45, 0xea, 0x31, 0x75, 0xf1, 0x0d, 0x55, 0xcb, 0x07, 0xaa, 0x85, 0x65, 0x18, 0x32, 0x60, + 0x3d, 0xa8, 0x13, 0x92, 0xc7, 0x6c, 0xcb, 0xbf, 0xbf, 0x49, 0x51, 0xec, 0xce, 0x15, 0xc5, 0x02, + 0xf4, 0x86, 0x73, 0x44, 0x75, 0x75, 0x0e, 0x74, 0x38, 0xc7, 0x69, 0xd9, 0x96, 0x77, 0x73, 0xcb, + 0xbf, 0x28, 0xf0, 0xf6, 0x05, 0x3d, 0xbd, 0x8c, 0x57, 0xd1, 0xf4, 0x25, 0xfd, 0xc5, 0x6e, 0xa4, + 0xbf, 0x16, 0xbc, 0x1b, 0x3e, 0xca, 0x74, 0x14, 0xbe, 0xce, 0x0c, 0x7d, 0x02, 0x09, 0x93, 0x0c, + 0x98, 0xaa, 0xbc, 0xb0, 0xd0, 0xb9, 0x27, 0x5d, 0x17, 0x19, 0xe5, 0x7d, 0x58, 0xbf, 0x1c, 0xb4, + 0xe1, 0x98, 0x64, 0x8a, 0xaa, 0x90, 0x0f, 0x9f, 0x9b, 0x4e, 0x0f, 0xb3, 0x9e, 0x3f, 0x91, 0x57, + 0x28, 0xa3, 0xdf, 0x0a, 0x1e, 0x9e, 0x27, 0x98, 0xf5, 0x44, 0x93, 0xbf, 0x29, 0x90, 0x3d, 0x37, + 0x10, 0xfa, 0x02, 0x62, 0xd7, 0xfe, 0x6c, 0xc6, 0xdc, 0x3e, 0x7a, 0x0a, 0x71, 0x6f, 0x93, 0x63, + 0xd7, 0xdd, 0x64, 0x0f, 0xa5, 0xfc, 0x93, 0x02, 0xb7, 0xaf, 0x5c, 0x42, 0xef, 0x6b, 0x65, 0xd0, + 0xc9, 0x0d, 0x7c, 0xed, 0x0d, 0x3a, 0x69, 0xf6, 0xbd, 0x05, 0xc2, 0x7e, 0x0d, 0xff, 0x6e, 0xc4, + 0x04, 0x79, 0x69, 0x1c, 0xd4, 0x65, 0x1f, 0xb5, 0xc5, 0xea, 0x85, 0xec, 0xb7, 0x38, 0xe6, 0x63, + 0x86, 0xd2, 0xb0, 0xd4, 0xdc, 0xd9, 0xaf, 0x37, 0xf6, 0xbf, 0xcc, 0x2d, 0x20, 0x80, 0xe4, 0xd6, + 0x76, 0xbb, 0xf1, 0x6c, 0x27, 0xa7, 0xa0, 0x2c, 0xa4, 0x0e, 0xf7, 0x6b, 0x07, 0xbe, 0x2b, 0x86, + 0x32, 0xb0, 0xec, 0x1f, 0x77, 0xea, 0xb9, 0x38, 0x5a, 0x82, 0xf8, 0xd6, 0xfe, 0x37, 0xb9, 0x44, + 0x6d, 0xf7, 0x8f, 0xd3, 0x82, 0xf2, 0xfc, 0xb4, 0xa0, 0xfc, 0x7b, 0x5a, 0x50, 0x7e, 0x3e, 0x2b, + 0x2c, 0x3c, 0x3f, 0x2b, 0x2c, 0xfc, 0x73, 0x56, 0x58, 0xf8, 0xf6, 0xa5, 0xd3, 0x4c, 0xa3, 0xff, + 0x7c, 0xc5, 0x68, 0xdd, 0xa4, 0xf8, 0xe7, 0xfb, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5c, + 0x49, 0x1e, 0x9d, 0xd6, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -923,17 +985,19 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x6a } - if m.CovenantSig != nil { - { - size := m.CovenantSig.Size() - i -= size - if _, err := m.CovenantSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.CovenantSigs) > 0 { + for iNdEx := len(m.CovenantSigs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantSigs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x62 } - i-- - dAtA[i] = 0x62 } if m.DelegatorSig != nil { { @@ -1073,17 +1137,19 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x32 } } - if m.CovenantSlashingSig != nil { - { - size := m.CovenantSlashingSig.Size() - i -= size - if _, err := m.CovenantSlashingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err + if len(m.CovenantSlashingSigs) > 0 { + for iNdEx := len(m.CovenantSlashingSigs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantSlashingSigs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } - i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a } - i-- - dAtA[i] = 0x2a } if m.DelegatorSlashingSig != nil { { @@ -1284,6 +1350,50 @@ func (m *SignatureInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *CovenantAdaptorSignatures) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CovenantAdaptorSignatures) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CovenantAdaptorSignatures) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AdaptorSigs) > 0 { + for iNdEx := len(m.AdaptorSigs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AdaptorSigs[iNdEx]) + copy(dAtA[i:], m.AdaptorSigs[iNdEx]) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.AdaptorSigs[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.CovPk != nil { + { + size := m.CovPk.Size() + i -= size + if _, err := m.CovPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { offset -= sovBtcstaking(v) base := offset @@ -1403,9 +1513,11 @@ func (m *BTCDelegation) Size() (n int) { l = m.DelegatorSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.CovenantSig != nil { - l = m.CovenantSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) + if len(m.CovenantSigs) > 0 { + for _, e := range m.CovenantSigs { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } if m.BtcUndelegation != nil { l = m.BtcUndelegation.Size() @@ -1435,9 +1547,11 @@ func (m *BTCUndelegation) Size() (n int) { l = m.DelegatorSlashingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if m.CovenantSlashingSig != nil { - l = m.CovenantSlashingSig.Size() - n += 1 + l + sovBtcstaking(uint64(l)) + if len(m.CovenantSlashingSigs) > 0 { + for _, e := range m.CovenantSlashingSigs { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } } if len(m.CovenantUnbondingSigList) > 0 { for _, e := range m.CovenantUnbondingSigList { @@ -1514,6 +1628,25 @@ func (m *SignatureInfo) Size() (n int) { return n } +func (m *CovenantAdaptorSignatures) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CovPk != nil { + l = m.CovPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + if len(m.AdaptorSigs) > 0 { + for _, b := range m.AdaptorSigs { + l = len(b) + n += 1 + l + sovBtcstaking(uint64(l)) + } + } + return n +} + func sovBtcstaking(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2301,9 +2434,9 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSigs", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2313,24 +2446,23 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantSig = &v - if err := m.CovenantSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantSigs = append(m.CovenantSigs, &CovenantAdaptorSignatures{}) + if err := m.CovenantSigs[len(m.CovenantSigs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2545,9 +2677,9 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSigs", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtcstaking @@ -2557,24 +2689,23 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthBtcstaking } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthBtcstaking } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.CovenantSlashingSig = &v - if err := m.CovenantSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.CovenantSlashingSigs = append(m.CovenantSlashingSigs, &CovenantAdaptorSignatures{}) + if err := m.CovenantSlashingSigs[len(m.CovenantSlashingSigs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3037,6 +3168,123 @@ func (m *SignatureInfo) Unmarshal(dAtA []byte) error { } return nil } +func (m *CovenantAdaptorSignatures) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CovenantAdaptorSignatures: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CovenantAdaptorSignatures: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CovPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.CovPk = &v + if err := m.CovPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AdaptorSigs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AdaptorSigs = append(m.AdaptorSigs, make([]byte, postIndex-iNdEx)) + copy(m.AdaptorSigs[len(m.AdaptorSigs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBtcstaking(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 785c79ad8..e297eaa39 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -6,12 +6,13 @@ import ( sdkmath "cosmossdk.io/math" - "github.com/babylonchain/babylon/testutil/datagen" - bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/require" + + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/x/btcstaking/types" ) func FuzzStakingTx(f *testing.F) { @@ -71,8 +72,20 @@ func FuzzBTCDelegation(f *testing.F) { // randomise covenant sig hasCovenantSig := datagen.RandomInt(r, 2) == 0 if hasCovenantSig { - covenantSig := bbn.BIP340Signature([]byte{1, 2, 3}) - btcDel.CovenantSig = &covenantSig + encKey, _, err := asig.GenKeyPair() + require.NoError(t, err) + covenantSK, _ := btcec.PrivKeyFromBytes( + []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + ) + covenantSig, err := asig.EncSign(covenantSK, encKey, datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + covPk, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + covSigInfo := &types.CovenantAdaptorSignatures{ + CovPk: covPk, + AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, + } + btcDel.CovenantSigs = []*types.CovenantAdaptorSignatures{covSigInfo} } // randomise start height and end height diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index bc8287ddc..518d131af 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,28 +6,24 @@ import ( // x/btcstaking module sentinel errors var ( - ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") - ErrBTCDelegatorNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegator is not found") - ErrBTCDelegationNotFound = errorsmod.Register(ModuleName, 1102, "the BTC delegation is not found") - ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been registered") - ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the BTC validator has already been slashed") - ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1105, "the BTC staking protocol is not activated yet") - ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1106, "the BTC height is not found") - ErrReusedStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is already used") - ErrInvalidCovenantPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong covenant PK") - ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1109, "the BTC staking tx is not valid") - ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1110, "the BTC slashing tx is not valid") - ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received this covenant signature") - ErrCovenantQuorumAlreadyReached = errorsmod.Register(ModuleName, 1112, "the BTC delegation has already received a quorum number of covenant signatures") - ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1113, "the covenant signature is not valid") - ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1114, "commission cannot be less than min rate") - ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1115, "Unexpected delegation state") - ErrInvalidUnbodningTx = errorsmod.Register(ModuleName, 1116, "the BTC unbonding tx is not valid") - ErrUnbondingDuplicatedValidatorSig = errorsmod.Register(ModuleName, 1117, "the BTC undelegation has already received validator signature") - ErrUnbodningInvalidValidatorSig = errorsmod.Register(ModuleName, 1118, "the validator signature is not valid") - ErrUnbondingUnexpectedValidatorSig = errorsmod.Register(ModuleName, 1119, "the BTC undelegation did not receive validator signature yet") - ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1120, "the reward distribution cache is not found") - ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1121, "the validator list is empty") - ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1122, "the proof of possession is not valid") - ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1123, "the staking request contains duplicated validator public key") + ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrBTCDelegatorNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegator is not found") + ErrBTCDelegationNotFound = errorsmod.Register(ModuleName, 1102, "the BTC delegation is not found") + ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been registered") + ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the BTC validator has already been slashed") + ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1105, "the BTC staking protocol is not activated yet") + ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1106, "the BTC height is not found") + ErrReusedStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is already used") + ErrInvalidCovenantPK = errorsmod.Register(ModuleName, 1108, "the BTC staking tx specifies a wrong covenant PK") + ErrInvalidStakingTx = errorsmod.Register(ModuleName, 1109, "the BTC staking tx is not valid") + ErrInvalidSlashingTx = errorsmod.Register(ModuleName, 1110, "the BTC slashing tx is not valid") + ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received this covenant signature") + ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1112, "the covenant signature is not valid") + ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1113, "commission cannot be less than min rate") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1114, "Unexpected delegation state") + ErrInvalidUnbondingTx = errorsmod.Register(ModuleName, 1115, "the BTC unbonding tx is not valid") + ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1116, "the reward distribution cache is not found") + ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1117, "the validator list is empty") + ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1118, "the proof of possession is not valid") + ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1119, "the staking request contains duplicated validator public key") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 6104f6dcf..84a6839d9 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -4,6 +4,7 @@ import ( "fmt" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/btcstaking" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -130,7 +131,7 @@ func (m *MsgAddCovenantSig) ValidateBasic() error { if m.Pk == nil { return fmt.Errorf("empty BTC covenant public key") } - if m.Sig == nil { + if m.Sigs == nil { return fmt.Errorf("empty covenant signature") } if len(m.StakingTxHash) != chainhash.MaxHashStringSize { @@ -142,7 +143,7 @@ func (m *MsgAddCovenantSig) ValidateBasic() error { func (m *MsgBTCUndelegate) ValidateBasic() error { if m.UnbondingTx == nil { - return fmt.Errorf("empty unbodning tx") + return fmt.Errorf("empty unbonding tx") } if m.SlashingTx == nil { return fmt.Errorf("empty slashing tx") @@ -190,7 +191,7 @@ func (m *MsgAddCovenantUnbondingSigs) ValidateBasic() error { if m.UnbondingTxSig == nil { return fmt.Errorf("empty covenant signature") } - if m.SlashingUnbondingTxSig == nil { + if m.SlashingUnbondingTxSigs == nil { return fmt.Errorf("empty covenant signature") } if len(m.StakingTxHash) != chainhash.MaxHashStringSize { diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index cb9709897..c0afcdf69 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -413,10 +413,10 @@ type MsgAddCovenantSig struct { // staking_tx_hash is the hash of the staking tx. // It uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // sig is the signature of the covenant - // the signature follows encoding in BIP-340 spec - // TODO: change to adaptor signature - Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` + // sigs is a list of adaptor signatures of the covenant + // the order of sigs should respect the order of validators + // of the corresponding delegation + Sigs [][]byte `protobuf:"bytes,4,rep,name=sigs,proto3" json:"sigs,omitempty"` } func (m *MsgAddCovenantSig) Reset() { *m = MsgAddCovenantSig{} } @@ -466,6 +466,13 @@ func (m *MsgAddCovenantSig) GetStakingTxHash() string { return "" } +func (m *MsgAddCovenantSig) GetSigs() [][]byte { + if m != nil { + return m.Sigs + } + return nil +} + // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig type MsgAddCovenantSigResponse struct { } @@ -611,10 +618,11 @@ type MsgAddCovenantUnbondingSigs struct { // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon // the signature follows encoding in BIP-340 spec UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` - // slashing_unbonding_tx_sig is the signature of the covenant on slashing tx corresponding to unbodning tx submitted to babylon - // the signature follows encoding in BIP-340 spec - // TODO: change to adaptor signature - SlashingUnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=slashing_unbonding_tx_sig,json=slashingUnbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"slashing_unbonding_tx_sig,omitempty"` + // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant + // on slashing tx corresponding to unbonding tx submitted to babylon + // the order of sigs should respect the order of validators + // of the corresponding delegation + SlashingUnbondingTxSigs [][]byte `protobuf:"bytes,5,rep,name=slashing_unbonding_tx_sigs,json=slashingUnbondingTxSigs,proto3" json:"slashing_unbonding_tx_sigs,omitempty"` } func (m *MsgAddCovenantUnbondingSigs) Reset() { *m = MsgAddCovenantUnbondingSigs{} } @@ -664,6 +672,13 @@ func (m *MsgAddCovenantUnbondingSigs) GetStakingTxHash() string { return "" } +func (m *MsgAddCovenantUnbondingSigs) GetSlashingUnbondingTxSigs() [][]byte { + if m != nil { + return m.SlashingUnbondingTxSigs + } + return nil +} + // MsgAddCovenantSigResponse is the response for MsgAddCovenantSig type MsgAddCovenantUnbondingSigsResponse struct { } @@ -719,79 +734,79 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1145 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0x4f, 0x6f, 0x1a, 0xc7, - 0x1b, 0xf6, 0x1a, 0xc3, 0xef, 0xe7, 0x17, 0xb0, 0x93, 0xad, 0xe3, 0x60, 0xac, 0x00, 0xc1, 0x4d, - 0x42, 0xa3, 0x78, 0x37, 0x26, 0xb1, 0xa5, 0xba, 0x52, 0xa5, 0x60, 0xb7, 0x4a, 0x14, 0xa3, 0xa2, - 0x05, 0xe7, 0x50, 0xa9, 0x45, 0xc3, 0x32, 0x5e, 0x56, 0xc0, 0xce, 0x6a, 0x67, 0x40, 0xa0, 0x5e, - 0xaa, 0x1e, 0x2b, 0x55, 0xea, 0xa9, 0xb7, 0x7e, 0x87, 0x1c, 0xf2, 0x21, 0x72, 0x6b, 0x9a, 0x53, - 0xe5, 0x83, 0x55, 0xd9, 0x87, 0x7c, 0x82, 0xde, 0xab, 0xdd, 0x9d, 0xfd, 0x67, 0x43, 0x8b, 0xe3, - 0x1c, 0x7a, 0x63, 0x76, 0x9e, 0xf7, 0x79, 0xdf, 0x79, 0x9e, 0x77, 0xde, 0x65, 0x21, 0xd7, 0x42, - 0xad, 0x71, 0x8f, 0x18, 0x72, 0x8b, 0xa9, 0x94, 0xa1, 0xae, 0x6e, 0x68, 0xf2, 0x70, 0x4b, 0x66, - 0x23, 0xc9, 0xb4, 0x08, 0x23, 0xe2, 0x0d, 0xbe, 0x2f, 0x05, 0xfb, 0xd2, 0x70, 0x2b, 0xbb, 0xa2, - 0x11, 0x8d, 0x38, 0x08, 0xd9, 0xfe, 0xe5, 0x82, 0xb3, 0x6b, 0x2a, 0xa1, 0x7d, 0x42, 0x9b, 0xee, - 0x86, 0xbb, 0xe0, 0x5b, 0x37, 0xdd, 0x95, 0xdc, 0xa7, 0x0e, 0x7f, 0x9f, 0x6a, 0x7c, 0xa3, 0xc8, - 0x37, 0x54, 0x6b, 0x6c, 0x32, 0x22, 0x53, 0xac, 0x9a, 0xe5, 0xed, 0x9d, 0xee, 0x96, 0xdc, 0xc5, - 0x63, 0x2f, 0xb8, 0x38, 0xb9, 0x48, 0x13, 0x59, 0xa8, 0xef, 0x61, 0x1e, 0x84, 0x30, 0x6a, 0x07, - 0xab, 0x5d, 0x93, 0xe8, 0x06, 0xb3, 0x61, 0x91, 0x07, 0x1c, 0xfd, 0x31, 0xcf, 0x1a, 0xb0, 0xb5, - 0x30, 0x43, 0x5b, 0xde, 0x9a, 0xa3, 0xf2, 0x53, 0xf2, 0x12, 0xd3, 0x05, 0x14, 0x7f, 0x8d, 0xc1, - 0x8d, 0x2a, 0xd5, 0xf6, 0x2c, 0x8c, 0x18, 0xae, 0x34, 0xf6, 0x5e, 0xa0, 0x9e, 0xde, 0x46, 0x8c, - 0x58, 0xe2, 0x2a, 0x24, 0xa8, 0xae, 0x19, 0xd8, 0xca, 0x08, 0x05, 0xa1, 0xb4, 0xa8, 0xf0, 0x95, - 0xf8, 0x05, 0x24, 0xdb, 0x98, 0xaa, 0x96, 0x6e, 0x32, 0x9d, 0x18, 0x99, 0xf9, 0x82, 0x50, 0x4a, - 0x96, 0x37, 0x24, 0xae, 0x55, 0xa0, 0xb0, 0x53, 0x8e, 0xb4, 0x1f, 0x40, 0x95, 0x70, 0x9c, 0x58, - 0x05, 0x50, 0x49, 0xbf, 0xaf, 0x53, 0x6a, 0xb3, 0xc4, 0xec, 0x14, 0x95, 0xcd, 0xe3, 0x93, 0xfc, - 0xba, 0x4b, 0x44, 0xdb, 0x5d, 0x49, 0x27, 0x72, 0x1f, 0xb1, 0x8e, 0x74, 0x80, 0x35, 0xa4, 0x8e, - 0xf7, 0xb1, 0xfa, 0xf6, 0xd5, 0x26, 0xf0, 0x3c, 0xfb, 0x58, 0x55, 0x42, 0x04, 0xe2, 0xe7, 0x00, - 0xfc, 0xa8, 0x4d, 0xb3, 0x9b, 0x59, 0x70, 0x8a, 0xca, 0x7b, 0x45, 0xb9, 0xce, 0x48, 0xbe, 0x33, - 0x52, 0x6d, 0xd0, 0x7a, 0x8e, 0xc7, 0xca, 0x22, 0x0f, 0xa9, 0x75, 0xc5, 0x2a, 0x24, 0x5a, 0x4c, - 0xb5, 0x63, 0xe3, 0x05, 0xa1, 0x94, 0xaa, 0xec, 0x1c, 0x9f, 0xe4, 0xcb, 0x9a, 0xce, 0x3a, 0x83, - 0x96, 0xa4, 0x92, 0xbe, 0xcc, 0x91, 0x6a, 0x07, 0xe9, 0x86, 0xb7, 0x90, 0xd9, 0xd8, 0xc4, 0x54, - 0xaa, 0x3c, 0xab, 0x3d, 0x7a, 0xfc, 0x90, 0x53, 0xc6, 0x5b, 0x4c, 0xad, 0x75, 0xc5, 0x5d, 0x88, - 0x99, 0xc4, 0xcc, 0x24, 0x9c, 0x3a, 0x4a, 0xd2, 0xc4, 0x16, 0x94, 0x6a, 0x16, 0x21, 0x47, 0x5f, - 0x1d, 0xd5, 0x08, 0xa5, 0xd8, 0x39, 0x85, 0x62, 0x07, 0xed, 0x26, 0x7f, 0x78, 0xf7, 0xf2, 0x3e, - 0x57, 0xbb, 0x98, 0x87, 0x5b, 0x13, 0xed, 0x51, 0x30, 0x35, 0x89, 0x41, 0x71, 0xf1, 0xa7, 0x38, - 0xac, 0x86, 0x11, 0xfb, 0xb8, 0x87, 0x35, 0xe4, 0x48, 0x3c, 0xcd, 0xc1, 0xa8, 0x56, 0xf3, 0x97, - 0xd6, 0x8a, 0x1f, 0x2e, 0xf6, 0x1e, 0x87, 0x0b, 0xe9, 0xbc, 0xf0, 0x21, 0x74, 0xfe, 0x06, 0x96, - 0x87, 0xa8, 0xd7, 0x74, 0x29, 0x9b, 0x3d, 0x9d, 0xb2, 0x4c, 0xbc, 0x10, 0xbb, 0x02, 0x6f, 0x6a, - 0x88, 0x7a, 0x15, 0x9b, 0xfa, 0x40, 0xa7, 0x4c, 0xbc, 0x0d, 0x29, 0x7e, 0xa4, 0x26, 0xd3, 0xfb, - 0xd8, 0xf1, 0x33, 0xad, 0x24, 0xf9, 0xb3, 0x86, 0xde, 0xc7, 0xe2, 0x06, 0xa4, 0x3d, 0xc8, 0x10, - 0xf5, 0x06, 0x38, 0xf3, 0xbf, 0x82, 0x50, 0x8a, 0x29, 0x5e, 0xdc, 0x0b, 0xfb, 0x99, 0xf8, 0x14, - 0xc0, 0xe7, 0x19, 0x65, 0xfe, 0xef, 0x08, 0xf7, 0x49, 0x58, 0xb8, 0xd0, 0xf5, 0x1e, 0x6e, 0x49, - 0x0d, 0x0b, 0x19, 0x14, 0xa9, 0xb6, 0x89, 0xcf, 0x8c, 0x23, 0xa2, 0x2c, 0x7a, 0x09, 0x47, 0x62, - 0x19, 0x92, 0xb4, 0x87, 0x68, 0x87, 0x53, 0x2d, 0x3a, 0x22, 0x5e, 0x3f, 0x3e, 0xc9, 0xa7, 0x2b, - 0x8d, 0xbd, 0x3a, 0xdf, 0x69, 0x8c, 0x14, 0xa0, 0xfe, 0x6f, 0xf1, 0x5b, 0x48, 0xb7, 0xdd, 0xae, - 0x20, 0x56, 0x93, 0xea, 0x5a, 0x06, 0x9c, 0xa8, 0x4f, 0x8f, 0x4f, 0xf2, 0xdb, 0x97, 0x91, 0xa8, - 0xae, 0x6b, 0x06, 0x62, 0x03, 0x0b, 0x2b, 0x29, 0x9f, 0xaf, 0xae, 0x6b, 0xd1, 0x86, 0x2d, 0x40, - 0x6e, 0x72, 0x3b, 0xfa, 0x1d, 0xfb, 0xdb, 0x3c, 0x5c, 0xab, 0x52, 0xad, 0xd2, 0xd8, 0x3b, 0x34, - 0x38, 0x0f, 0x9e, 0xda, 0xab, 0xb7, 0x21, 0x35, 0x30, 0x5a, 0xc4, 0x68, 0xf3, 0x03, 0xdb, 0xdd, - 0x9a, 0x52, 0x92, 0xfe, 0xb3, 0xc6, 0x48, 0xbc, 0x03, 0x4b, 0x21, 0x88, 0x6d, 0x53, 0xcc, 0xb1, - 0x29, 0x1d, 0x80, 0x6c, 0xa3, 0xee, 0xc1, 0x72, 0x00, 0x73, 0xad, 0x5a, 0x70, 0xac, 0x0a, 0xa2, - 0x5d, 0xb3, 0xce, 0x49, 0x1c, 0x9f, 0x45, 0x62, 0x02, 0xab, 0x21, 0x89, 0xbd, 0x68, 0x5b, 0xeb, - 0xc4, 0x55, 0xb5, 0x5e, 0x09, 0xb4, 0xe6, 0xbc, 0x17, 0x34, 0xcf, 0x42, 0xe6, 0xbc, 0xa0, 0xbe, - 0xda, 0x7f, 0x09, 0x70, 0xbd, 0x4a, 0xb5, 0x27, 0xed, 0xf6, 0x1e, 0x19, 0x62, 0x03, 0x19, 0xac, - 0xae, 0x6b, 0x53, 0xe5, 0xfe, 0x12, 0xe6, 0xf9, 0x48, 0x78, 0xff, 0x2b, 0x34, 0x6f, 0x76, 0xc5, - 0xbb, 0xb0, 0x1c, 0x34, 0x7c, 0xb3, 0x83, 0x68, 0xc7, 0x1d, 0xf1, 0x4a, 0xda, 0x6f, 0xe5, 0xa7, - 0x88, 0x76, 0xc4, 0xe7, 0x10, 0xb3, 0x45, 0x5a, 0xb8, 0xaa, 0x48, 0x36, 0x4b, 0x54, 0x93, 0x75, - 0x58, 0xbb, 0x70, 0x6c, 0x5f, 0x94, 0x5f, 0x04, 0x58, 0xae, 0x52, 0xed, 0xd0, 0x6c, 0x23, 0x86, - 0x6b, 0xce, 0x4b, 0x58, 0xdc, 0x81, 0x45, 0x34, 0x60, 0x1d, 0x62, 0xe9, 0x6c, 0xec, 0xaa, 0x52, - 0xc9, 0xbc, 0x7d, 0xb5, 0xb9, 0xc2, 0xe7, 0xe2, 0x93, 0x76, 0xdb, 0xc2, 0x94, 0xd6, 0x99, 0xa5, - 0x1b, 0x9a, 0x12, 0x40, 0xc5, 0xcf, 0x20, 0xe1, 0xbe, 0xc6, 0xf9, 0x24, 0xbd, 0x35, 0x6d, 0x20, - 0x3a, 0xa0, 0xca, 0xc2, 0xeb, 0x93, 0xfc, 0x9c, 0xc2, 0x43, 0x76, 0x97, 0xec, 0x92, 0x03, 0xb2, - 0xe2, 0x1a, 0xdc, 0x3c, 0x57, 0x57, 0x30, 0xe8, 0x63, 0xb0, 0x1e, 0x3d, 0xd1, 0xa1, 0xd7, 0xb7, - 0x75, 0x5d, 0xa3, 0xff, 0x19, 0x4b, 0x55, 0xb8, 0x16, 0xbe, 0xb1, 0xcd, 0x0f, 0xe2, 0xef, 0x52, - 0xe8, 0xc2, 0xdb, 0xfd, 0xcb, 0x60, 0xcd, 0xbf, 0x65, 0x17, 0xb2, 0xc5, 0xaf, 0x9a, 0x6d, 0xd5, - 0xe3, 0x3e, 0x8c, 0x64, 0x8d, 0x36, 0xd8, 0x1d, 0xd8, 0xf8, 0x07, 0x3b, 0x3c, 0xdb, 0xca, 0xbf, - 0xc7, 0x21, 0x56, 0xa5, 0x9a, 0x38, 0x02, 0x71, 0xc2, 0x9f, 0xac, 0x07, 0x53, 0x9a, 0x65, 0xe2, - 0x3b, 0x3f, 0xfb, 0xf8, 0x32, 0x68, 0xaf, 0x02, 0xf1, 0x3b, 0xf8, 0x68, 0xd2, 0xbf, 0x83, 0xcd, - 0x19, 0xc8, 0x02, 0x78, 0x76, 0xfb, 0x52, 0x70, 0x3f, 0xb9, 0x0e, 0xe9, 0xe8, 0xa0, 0xbf, 0x37, - 0x9d, 0x27, 0x02, 0xcc, 0xca, 0x33, 0x02, 0xfd, 0x54, 0x3d, 0x58, 0x3a, 0x37, 0xe5, 0x4a, 0xd3, - 0x29, 0xa2, 0xc8, 0xec, 0xc3, 0x59, 0x91, 0x7e, 0xb6, 0x1f, 0x05, 0xc8, 0x4c, 0xbd, 0x8b, 0xe5, - 0x99, 0xe8, 0x22, 0x31, 0xd9, 0xdd, 0xcb, 0xc7, 0xf8, 0xc5, 0x1c, 0x41, 0x2a, 0x32, 0xcb, 0xee, - 0x4e, 0xe7, 0x0a, 0xe3, 0xb2, 0xd2, 0x6c, 0x38, 0x2f, 0x4f, 0x36, 0xfe, 0xfd, 0xbb, 0x97, 0xf7, - 0x85, 0xca, 0xc1, 0xeb, 0xd3, 0x9c, 0xf0, 0xe6, 0x34, 0x27, 0xfc, 0x79, 0x9a, 0x13, 0x7e, 0x3e, - 0xcb, 0xcd, 0xbd, 0x39, 0xcb, 0xcd, 0xfd, 0x71, 0x96, 0x9b, 0xfb, 0xfa, 0x5f, 0x87, 0xcb, 0x28, - 0xfc, 0x25, 0xe2, 0xdc, 0xbe, 0x56, 0xc2, 0xf9, 0x12, 0x79, 0xf4, 0x77, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x05, 0x25, 0x56, 0xb9, 0xc9, 0x0d, 0x00, 0x00, + // 1149 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0x4d, 0x6f, 0x1a, 0x57, + 0x17, 0xf6, 0xf0, 0xf5, 0xbe, 0x3e, 0x80, 0x9d, 0x4c, 0x93, 0x78, 0x8c, 0x15, 0x20, 0xb8, 0x49, + 0x68, 0x14, 0x0f, 0x31, 0x89, 0x2d, 0xd5, 0x91, 0x2a, 0x05, 0xbb, 0x55, 0xa2, 0x1a, 0x15, 0x0d, + 0x38, 0x8b, 0x4a, 0x2d, 0xba, 0x0c, 0xd7, 0xc3, 0x08, 0x98, 0x3b, 0x9a, 0x7b, 0x41, 0xa0, 0x6e, + 0xaa, 0x2e, 0x2b, 0x55, 0xea, 0xaa, 0xbb, 0xfe, 0x87, 0x2c, 0xb2, 0xec, 0x0f, 0xc8, 0xae, 0x69, + 0xd4, 0x45, 0xe5, 0x85, 0x55, 0xd9, 0x8b, 0xfc, 0x8d, 0x6a, 0x66, 0xee, 0x7c, 0x40, 0xa0, 0xc5, + 0x71, 0x17, 0xdd, 0xcd, 0x9d, 0xfb, 0x9c, 0xe7, 0x9c, 0xf3, 0x9c, 0x8f, 0x01, 0xc8, 0xb6, 0x50, + 0x6b, 0xdc, 0x23, 0x46, 0xa9, 0xc5, 0x54, 0xca, 0x50, 0x57, 0x37, 0xb4, 0xd2, 0x70, 0xbb, 0xc4, + 0x46, 0xb2, 0x69, 0x11, 0x46, 0xc4, 0xeb, 0xfc, 0x5e, 0x0e, 0xee, 0xe5, 0xe1, 0x76, 0xe6, 0x9a, + 0x46, 0x34, 0xe2, 0x20, 0x4a, 0xf6, 0x93, 0x0b, 0xce, 0xac, 0xab, 0x84, 0xf6, 0x09, 0x6d, 0xba, + 0x17, 0xee, 0x81, 0x5f, 0xad, 0xb9, 0xa7, 0x52, 0x9f, 0x3a, 0xfc, 0x7d, 0xaa, 0xf1, 0x8b, 0x02, + 0xbf, 0x50, 0xad, 0xb1, 0xc9, 0x48, 0x89, 0x62, 0xd5, 0x2c, 0xef, 0xec, 0x76, 0xb7, 0x4b, 0x5d, + 0x3c, 0xf6, 0x8c, 0x0b, 0xb3, 0x83, 0x34, 0x91, 0x85, 0xfa, 0x1e, 0xe6, 0x7e, 0x08, 0xa3, 0x76, + 0xb0, 0xda, 0x35, 0x89, 0x6e, 0x30, 0x1b, 0x36, 0xf1, 0x82, 0xa3, 0x3f, 0xe4, 0x5e, 0x03, 0xb6, + 0x16, 0x66, 0x68, 0xdb, 0x3b, 0x73, 0x54, 0x6e, 0x8e, 0x5f, 0x62, 0xba, 0x80, 0xc2, 0xcf, 0x51, + 0xb8, 0x5e, 0xa5, 0xda, 0xbe, 0x85, 0x11, 0xc3, 0x95, 0xc6, 0xfe, 0x73, 0xd4, 0xd3, 0xdb, 0x88, + 0x11, 0x4b, 0xbc, 0x01, 0x09, 0xaa, 0x6b, 0x06, 0xb6, 0x24, 0x21, 0x2f, 0x14, 0x97, 0x15, 0x7e, + 0x12, 0x3f, 0x85, 0x64, 0x1b, 0x53, 0xd5, 0xd2, 0x4d, 0xa6, 0x13, 0x43, 0x8a, 0xe4, 0x85, 0x62, + 0xb2, 0xbc, 0x29, 0x73, 0xad, 0x02, 0x85, 0x9d, 0x70, 0xe4, 0x83, 0x00, 0xaa, 0x84, 0xed, 0xc4, + 0x2a, 0x80, 0x4a, 0xfa, 0x7d, 0x9d, 0x52, 0x9b, 0x25, 0x6a, 0xbb, 0xa8, 0x6c, 0x9d, 0x9c, 0xe6, + 0x36, 0x5c, 0x22, 0xda, 0xee, 0xca, 0x3a, 0x29, 0xf5, 0x11, 0xeb, 0xc8, 0x87, 0x58, 0x43, 0xea, + 0xf8, 0x00, 0xab, 0x6f, 0x5e, 0x6e, 0x01, 0xf7, 0x73, 0x80, 0x55, 0x25, 0x44, 0x20, 0x7e, 0x02, + 0xc0, 0x53, 0x6d, 0x9a, 0x5d, 0x29, 0xe6, 0x04, 0x95, 0xf3, 0x82, 0x72, 0x2b, 0x23, 0xfb, 0x95, + 0x91, 0x6b, 0x83, 0xd6, 0xe7, 0x78, 0xac, 0x2c, 0x73, 0x93, 0x5a, 0x57, 0xac, 0x42, 0xa2, 0xc5, + 0x54, 0xdb, 0x36, 0x9e, 0x17, 0x8a, 0xa9, 0xca, 0xee, 0xc9, 0x69, 0xae, 0xac, 0xe9, 0xac, 0x33, + 0x68, 0xc9, 0x2a, 0xe9, 0x97, 0x38, 0x52, 0xed, 0x20, 0xdd, 0xf0, 0x0e, 0x25, 0x36, 0x36, 0x31, + 0x95, 0x2b, 0xcf, 0x6a, 0x0f, 0x1f, 0x3d, 0xe0, 0x94, 0xf1, 0x16, 0x53, 0x6b, 0x5d, 0x71, 0x0f, + 0xa2, 0x26, 0x31, 0xa5, 0x84, 0x13, 0x47, 0x51, 0x9e, 0xd9, 0x82, 0x72, 0xcd, 0x22, 0xe4, 0xf8, + 0x8b, 0xe3, 0x1a, 0xa1, 0x14, 0x3b, 0x59, 0x28, 0xb6, 0xd1, 0x5e, 0xf2, 0xbb, 0xb7, 0x2f, 0xee, + 0x71, 0xb5, 0x0b, 0x39, 0xb8, 0x39, 0xb3, 0x3c, 0x0a, 0xa6, 0x26, 0x31, 0x28, 0x2e, 0xfc, 0x10, + 0x87, 0x1b, 0x61, 0xc4, 0x01, 0xee, 0x61, 0x0d, 0x39, 0x12, 0xcf, 0xab, 0xe0, 0xa4, 0x56, 0x91, + 0x0b, 0x6b, 0xc5, 0x93, 0x8b, 0xbe, 0x47, 0x72, 0x21, 0x9d, 0x63, 0xff, 0x86, 0xce, 0x5f, 0xc1, + 0xea, 0x10, 0xf5, 0x9a, 0x2e, 0x65, 0xb3, 0xa7, 0x53, 0x26, 0xc5, 0xf3, 0xd1, 0x4b, 0xf0, 0xa6, + 0x86, 0xa8, 0x57, 0xb1, 0xa9, 0x0f, 0x75, 0xca, 0xc4, 0x5b, 0x90, 0xe2, 0x29, 0x35, 0x99, 0xde, + 0xc7, 0x4e, 0x3d, 0xd3, 0x4a, 0x92, 0xbf, 0x6b, 0xe8, 0x7d, 0x2c, 0x6e, 0x42, 0xda, 0x83, 0x0c, + 0x51, 0x6f, 0x80, 0xa5, 0xff, 0xe5, 0x85, 0x62, 0x54, 0xf1, 0xec, 0x9e, 0xdb, 0xef, 0xc4, 0xa7, + 0x00, 0x3e, 0xcf, 0x48, 0xfa, 0xbf, 0x23, 0xdc, 0x47, 0x61, 0xe1, 0x42, 0xe3, 0x3d, 0xdc, 0x96, + 0x1b, 0x16, 0x32, 0x28, 0x52, 0xed, 0x22, 0x3e, 0x33, 0x8e, 0x89, 0xb2, 0xec, 0x39, 0x1c, 0x89, + 0x65, 0x48, 0xd2, 0x1e, 0xa2, 0x1d, 0x4e, 0xb5, 0xec, 0x88, 0x78, 0xf5, 0xe4, 0x34, 0x97, 0xae, + 0x34, 0xf6, 0xeb, 0xfc, 0xa6, 0x31, 0x52, 0x80, 0xfa, 0xcf, 0xe2, 0xd7, 0x90, 0x6e, 0xbb, 0x5d, + 0x41, 0xac, 0x26, 0xd5, 0x35, 0x09, 0x1c, 0xab, 0x8f, 0x4f, 0x4e, 0x73, 0x3b, 0x17, 0x91, 0xa8, + 0xae, 0x6b, 0x06, 0x62, 0x03, 0x0b, 0x2b, 0x29, 0x9f, 0xaf, 0xae, 0x6b, 0x93, 0x0d, 0x9b, 0x87, + 0xec, 0xec, 0x76, 0xf4, 0x3b, 0xf6, 0xd7, 0x08, 0x5c, 0xa9, 0x52, 0xad, 0xd2, 0xd8, 0x3f, 0x32, + 0x38, 0x0f, 0x9e, 0xdb, 0xab, 0xb7, 0x20, 0x35, 0x30, 0x5a, 0xc4, 0x68, 0xf3, 0x84, 0xed, 0x6e, + 0x4d, 0x29, 0x49, 0xff, 0x5d, 0x63, 0x24, 0xde, 0x86, 0x95, 0x10, 0xc4, 0x2e, 0x53, 0xd4, 0x29, + 0x53, 0x3a, 0x00, 0xd9, 0x85, 0xba, 0x0b, 0xab, 0x01, 0xcc, 0x2d, 0x55, 0xcc, 0x29, 0x55, 0x60, + 0xed, 0x16, 0x6b, 0x4a, 0xe2, 0xf8, 0x22, 0x12, 0x13, 0xb8, 0x11, 0x92, 0xd8, 0xb3, 0xb6, 0xb5, + 0x4e, 0x5c, 0x56, 0xeb, 0x6b, 0x81, 0xd6, 0x9c, 0xf7, 0x1d, 0xcd, 0x33, 0x20, 0x4d, 0x0b, 0xea, + 0xab, 0xfd, 0x8b, 0x00, 0x57, 0xab, 0x54, 0x7b, 0xd2, 0x6e, 0xef, 0x93, 0x21, 0x36, 0x90, 0xc1, + 0xea, 0xba, 0x36, 0x57, 0xee, 0xcf, 0x20, 0xc2, 0x57, 0xc2, 0xfb, 0x8f, 0x50, 0xc4, 0xec, 0x8a, + 0x77, 0x60, 0x35, 0x68, 0xf8, 0x66, 0x07, 0xd1, 0x8e, 0xbb, 0xe2, 0x95, 0xb4, 0xdf, 0xca, 0x4f, + 0x11, 0xed, 0x88, 0x22, 0xc4, 0xa8, 0xae, 0x51, 0x29, 0x66, 0x0f, 0xad, 0xe2, 0x3c, 0x4f, 0xa6, + 0xb6, 0x01, 0xeb, 0xef, 0x44, 0xef, 0xe7, 0xf6, 0x93, 0x00, 0xab, 0x55, 0xaa, 0x1d, 0x99, 0x6d, + 0xc4, 0x70, 0xcd, 0xf9, 0x96, 0x8a, 0xbb, 0xb0, 0x8c, 0x06, 0xac, 0x43, 0x2c, 0x9d, 0x8d, 0xdd, + 0xe4, 0x2a, 0xd2, 0x9b, 0x97, 0x5b, 0xd7, 0xf8, 0x7a, 0x7b, 0xd2, 0x6e, 0x5b, 0x98, 0xd2, 0x3a, + 0xb3, 0x74, 0x43, 0x53, 0x02, 0xa8, 0xf8, 0x18, 0x12, 0xee, 0xd7, 0x98, 0x2f, 0xc4, 0x9b, 0xf3, + 0xf6, 0x9a, 0x03, 0xaa, 0xc4, 0x5e, 0x9d, 0xe6, 0x96, 0x14, 0x6e, 0xb2, 0xb7, 0x62, 0x87, 0x1c, + 0x90, 0x15, 0xd6, 0x61, 0x6d, 0x2a, 0x2e, 0x3f, 0xe6, 0xdf, 0x23, 0xb0, 0x31, 0x99, 0xd1, 0x91, + 0xd7, 0x7e, 0x75, 0x5d, 0xa3, 0xff, 0x99, 0xca, 0xa8, 0x70, 0x25, 0x3c, 0x78, 0x4e, 0x2f, 0xc7, + 0x2e, 0xdb, 0xcb, 0x2b, 0xa1, 0xb9, 0xb5, 0xdb, 0xf0, 0x31, 0x64, 0xfc, 0x61, 0x99, 0xf6, 0x46, + 0xdd, 0x4d, 0xae, 0xac, 0x79, 0x88, 0xa3, 0x09, 0xdb, 0xa9, 0x3e, 0xb9, 0x0d, 0x9b, 0x7f, 0xa3, + 0xaa, 0xa7, 0x7e, 0xf9, 0xb7, 0x38, 0x44, 0xab, 0x54, 0x13, 0x47, 0x20, 0xce, 0xf8, 0xc9, 0x73, + 0x7f, 0x4e, 0xcd, 0x67, 0x7e, 0x81, 0x33, 0x8f, 0x2e, 0x82, 0xf6, 0x22, 0x10, 0xbf, 0x81, 0x0f, + 0x66, 0x7d, 0xab, 0xb7, 0x16, 0x20, 0x0b, 0xe0, 0x99, 0x9d, 0x0b, 0xc1, 0x7d, 0xe7, 0x3a, 0xa4, + 0x27, 0xd7, 0xee, 0xdd, 0xf9, 0x3c, 0x13, 0xc0, 0x4c, 0x69, 0x41, 0xa0, 0xef, 0xaa, 0x07, 0x2b, + 0x53, 0x3b, 0xa7, 0x38, 0x9f, 0x62, 0x12, 0x99, 0x79, 0xb0, 0x28, 0xd2, 0xf7, 0xf6, 0xbd, 0x00, + 0xd2, 0xdc, 0x91, 0x2a, 0x2f, 0x44, 0x37, 0x61, 0x93, 0xd9, 0xbb, 0xb8, 0x8d, 0x1f, 0xcc, 0x31, + 0xa4, 0x26, 0x56, 0xd2, 0x9d, 0xf9, 0x5c, 0x61, 0x5c, 0x46, 0x5e, 0x0c, 0xe7, 0xf9, 0xc9, 0xc4, + 0xbf, 0x7d, 0xfb, 0xe2, 0x9e, 0x50, 0x39, 0x7c, 0x75, 0x96, 0x15, 0x5e, 0x9f, 0x65, 0x85, 0x3f, + 0xcf, 0xb2, 0xc2, 0x8f, 0xe7, 0xd9, 0xa5, 0xd7, 0xe7, 0xd9, 0xa5, 0x3f, 0xce, 0xb3, 0x4b, 0x5f, + 0xfe, 0xe3, 0x8e, 0x18, 0x85, 0xff, 0x17, 0x38, 0x23, 0xdb, 0x4a, 0x38, 0xff, 0x0b, 0x1e, 0xfe, + 0x15, 0x00, 0x00, 0xff, 0xff, 0x22, 0x62, 0x6f, 0x70, 0x57, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -816,7 +831,7 @@ type MsgClient interface { AddCovenantSig(ctx context.Context, in *MsgAddCovenantSig, opts ...grpc.CallOption) (*MsgAddCovenantSigResponse, error) // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbodning tx submitted to babylon by staker + // - slashing tx corresponding to unbonding tx submitted to babylon by staker AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) @@ -896,7 +911,7 @@ type MsgServer interface { AddCovenantSig(context.Context, *MsgAddCovenantSig) (*MsgAddCovenantSigResponse, error) // AddCovenantUnbondingSigs handles two signatures from covenant for: // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbodning tx submitted to babylon by staker + // - slashing tx corresponding to unbonding tx submitted to babylon by staker AddCovenantUnbondingSigs(context.Context, *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) @@ -1446,17 +1461,14 @@ func (m *MsgAddCovenantSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Sig != nil { - { - size := m.Sig.Size() - i -= size - if _, err := m.Sig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) + if len(m.Sigs) > 0 { + for iNdEx := len(m.Sigs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Sigs[iNdEx]) + copy(dAtA[i:], m.Sigs[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sigs[iNdEx]))) + i-- + dAtA[i] = 0x22 } - i-- - dAtA[i] = 0x22 } if len(m.StakingTxHash) > 0 { i -= len(m.StakingTxHash) @@ -1593,17 +1605,14 @@ func (m *MsgAddCovenantUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, er _ = i var l int _ = l - if m.SlashingUnbondingTxSig != nil { - { - size := m.SlashingUnbondingTxSig.Size() - i -= size - if _, err := m.SlashingUnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) + if len(m.SlashingUnbondingTxSigs) > 0 { + for iNdEx := len(m.SlashingUnbondingTxSigs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.SlashingUnbondingTxSigs[iNdEx]) + copy(dAtA[i:], m.SlashingUnbondingTxSigs[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.SlashingUnbondingTxSigs[iNdEx]))) + i-- + dAtA[i] = 0x2a } - i-- - dAtA[i] = 0x2a } if m.UnbondingTxSig != nil { { @@ -1838,9 +1847,11 @@ func (m *MsgAddCovenantSig) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.Sig != nil { - l = m.Sig.Size() - n += 1 + l + sovTx(uint64(l)) + if len(m.Sigs) > 0 { + for _, b := range m.Sigs { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } } return n } @@ -1900,9 +1911,11 @@ func (m *MsgAddCovenantUnbondingSigs) Size() (n int) { l = m.UnbondingTxSig.Size() n += 1 + l + sovTx(uint64(l)) } - if m.SlashingUnbondingTxSig != nil { - l = m.SlashingUnbondingTxSig.Size() - n += 1 + l + sovTx(uint64(l)) + if len(m.SlashingUnbondingTxSigs) > 0 { + for _, b := range m.SlashingUnbondingTxSigs { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } } return n } @@ -3055,7 +3068,7 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Sigs", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3082,11 +3095,8 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.Sig = &v - if err := m.Sig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Sigs = append(m.Sigs, make([]byte, postIndex-iNdEx)) + copy(m.Sigs[len(m.Sigs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -3489,7 +3499,7 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSigs", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3516,11 +3526,8 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.SlashingUnbondingTxSig = &v - if err := m.SlashingUnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.SlashingUnbondingTxSigs = append(m.SlashingUnbondingTxSigs, make([]byte, postIndex-iNdEx)) + copy(m.SlashingUnbondingTxSigs[len(m.SlashingUnbondingTxSigs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex From 98a554989e81d26fd6b345c9ed7d48372a0da803 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 28 Nov 2023 10:55:51 +1100 Subject: [PATCH 113/202] chore: cleaning up btcstaking tooling library (#122) --- btcstaking/{scripts.go => scripts_utils.go} | 29 ++- btcstaking/staking.go | 105 ++++------ btcstaking/staking_test.go | 34 ++- btcstaking/staking_utils.go | 219 -------------------- btcstaking/{btc_staking.go => types.go} | 171 ++++++++++++++- go.mod | 2 +- x/btcstaking/types/types.go | 9 - 7 files changed, 253 insertions(+), 316 deletions(-) rename btcstaking/{scripts.go => scripts_utils.go} (79%) delete mode 100644 btcstaking/staking_utils.go rename btcstaking/{btc_staking.go => types.go} (63%) diff --git a/btcstaking/scripts.go b/btcstaking/scripts_utils.go similarity index 79% rename from btcstaking/scripts.go rename to btcstaking/scripts_utils.go index 41f7eac7c..e9e2a2928 100644 --- a/btcstaking/scripts.go +++ b/btcstaking/scripts_utils.go @@ -3,15 +3,16 @@ package btcstaking import ( "bytes" "fmt" + "sort" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" ) -// private helper to build multisig script +// private helper to assemble multisig script // SCRIPT: OP_CHEKCSIG OP_CHECKSIGADD OP_CHECKSIGADD ... OP_CHECKSIGADD OP_GREATERTHANOREQUAL OP_VERIFY -func buildMultiSigScript( +func assembleMultiSigScript( pubkeys []*btcec.PublicKey, threshold uint32, withVerify bool, @@ -36,6 +37,18 @@ func buildMultiSigScript( return builder.Script() } +// sortKeys takes a set of schnorr public keys and returns a new slice that is +// a copy of the keys sorted in lexicographical order bytes on the x-only +// pubkey serialization. +func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey { + sort.SliceStable(keys, func(i, j int) bool { + keyIBytes := schnorr.SerializePubKey(keys[i]) + keyJBytes := schnorr.SerializePubKey(keys[j]) + return bytes.Compare(keyIBytes, keyJBytes) == -1 + }) + return keys +} + // prepareKeys prepares keys to be used in multisig script // Validates: // - whether there are at lest 2 keys @@ -57,11 +70,11 @@ func prepareKeysForMultisigScript(keys []*btcec.PublicKey) ([]*btcec.PublicKey, return sortedKeys, nil } -// BuildMultiSigScript creates multisig script with given keys and signer threshold to +// buildMultiSigScript creates multisig script with given keys and signer threshold to // successfully execute script // it validates whether provided keys are unique and the threshold is not greater than number of keys // If there is only one key provided it will return single key sig script -func BuildMultiSigScript( +func buildMultiSigScript( keys []*btcec.PublicKey, threshold uint32, withVerify bool, @@ -76,7 +89,7 @@ func BuildMultiSigScript( if len(keys) == 1 { // if we have only one key we can use single key sig script - return BuildSingleKeySigScript(keys[0], withVerify) + return buildSingleKeySigScript(keys[0], withVerify) } sortedKeys, err := prepareKeysForMultisigScript(keys) @@ -85,12 +98,12 @@ func BuildMultiSigScript( return nil, err } - return buildMultiSigScript(sortedKeys, threshold, withVerify) + return assembleMultiSigScript(sortedKeys, threshold, withVerify) } // Only holder of private key for given pubKey can spend after relative lock time // SCRIPT: OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY -func BuildTimeLockScript( +func buildTimeLockScript( pubKey *btcec.PublicKey, lockTime uint16, ) ([]byte, error) { @@ -104,7 +117,7 @@ func BuildTimeLockScript( // Only holder of private key for given pubKey can spend // SCRIPT: OP_CHECKSIGVERIFY -func BuildSingleKeySigScript( +func buildSingleKeySigScript( pubKey *btcec.PublicKey, withVerify bool, ) ([]byte, error) { diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 04fc4947a..22bd297f5 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -2,7 +2,6 @@ package btcstaking import ( "bytes" - "encoding/hex" "fmt" sdkmath "cosmossdk.io/math" @@ -17,63 +16,6 @@ import ( asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" ) -const ( - - // Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs - // using it as internal public key efectively disables taproot key spends - unspendableKeyPath = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" -) - -var ( - unspendableKeyPathKey = unspendableKeyPathInternalPubKeyInternal(unspendableKeyPath) -) - -func unspendableKeyPathInternalPubKeyInternal(keyHex string) btcec.PublicKey { - keyBytes, err := hex.DecodeString(keyHex) - - if err != nil { - panic(fmt.Sprintf("unexpected error: %v", err)) - } - - // We are using btcec here, as key is 33 byte compressed format. - pubKey, err := btcec.ParsePubKey(keyBytes) - - if err != nil { - panic(fmt.Sprintf("unexpected error: %v", err)) - } - return *pubKey -} - -// StakingScriptData is a struct that holds data parsed from staking script -type StakingScriptData struct { - StakerKey *btcec.PublicKey - ValidatorKey *btcec.PublicKey - CovenantKey *btcec.PublicKey - StakingTime uint16 -} - -func NewStakingScriptData( - stakerKey, - validatorKey, - covenantKey *btcec.PublicKey, - stakingTime uint16) (*StakingScriptData, error) { - - if stakerKey == nil || validatorKey == nil || covenantKey == nil { - return nil, fmt.Errorf("staker, validator and covenant keys cannot be nil") - } - - return &StakingScriptData{ - StakerKey: stakerKey, - ValidatorKey: validatorKey, - CovenantKey: covenantKey, - StakingTime: stakingTime, - }, nil -} - -func UnspendableKeyPathInternalPubKey() btcec.PublicKey { - return unspendableKeyPathKey -} - // BuildSlashingTxFromOutpoint builds a valid slashing transaction by creating a new Bitcoin transaction that slashes a portion // of staked funds and directs them to a specified slashing address. The transaction also includes a change output sent back to // the specified change address. The slashing rate determines the proportion of staked funds to be slashed. @@ -752,19 +694,44 @@ func EncVerifyTransactionSigWithOutputData( return signature.EncVerify(pubKey, encKey, sigHash) } -// IsSlashingRateValid checks if the given slashing rate is between the valid range i.e., (0,1) with a precision of at most 2 decimal places. -func IsSlashingRateValid(slashingRate sdkmath.LegacyDec) bool { - // Check if the slashing rate is between 0 and 1 - if slashingRate.LTE(sdkmath.LegacyZeroDec()) || slashingRate.GTE(sdkmath.LegacyOneDec()) { - return false +// CreateBabylonWitness creates babylon compatible witness, as babylon scripts +// have witness with the same shape +// - first come signatures +// - then whole revealed script +// - then control block +func CreateBabylonWitness( + signatures [][]byte, + si *SpendInfo, +) (wire.TxWitness, error) { + numSignatures := len(signatures) + + if numSignatures == 0 { + return nil, fmt.Errorf("cannot build witness without signatures") + } + + if si == nil { + return nil, fmt.Errorf("cannot build witness without spend info") + } + + controlBlockBytes, err := si.ControlBlock.ToBytes() + + if err != nil { + return nil, err } - // Multiply by 100 to move the decimal places and check if precision is at most 2 decimal places - multipliedRate := slashingRate.Mul(sdkmath.LegacyNewDec(100)) + // witness stack has: + // all signatures + // whole revealed script + // control block + witnessStack := wire.TxWitness(make([][]byte, numSignatures+2)) + + for i, sig := range signatures { + sc := sig + witnessStack[i] = sc + } - // Truncate the rate to remove decimal places - truncatedRate := multipliedRate.TruncateDec() + witnessStack[numSignatures] = si.RevealedLeaf.Script + witnessStack[numSignatures+1] = controlBlockBytes - // Check if the truncated rate is equal to the original rate - return multipliedRate.Equal(truncatedRate) + return witnessStack, nil } diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index c3eacb655..d1b01111b 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -1,11 +1,13 @@ package btcstaking_test import ( - sdkmath "cosmossdk.io/math" + "fmt" "math" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/testutil/datagen" "github.com/btcsuite/btcd/btcec/v2" @@ -16,7 +18,33 @@ import ( "github.com/stretchr/testify/require" ) -func genValidStakingScriptData(_ *testing.T, r *rand.Rand) *btcstaking.StakingScriptData { +// StakingScriptData is a struct that holds data parsed from staking script +type StakingScriptData struct { + StakerKey *btcec.PublicKey + ValidatorKey *btcec.PublicKey + CovenantKey *btcec.PublicKey + StakingTime uint16 +} + +func NewStakingScriptData( + stakerKey, + validatorKey, + covenantKey *btcec.PublicKey, + stakingTime uint16) (*StakingScriptData, error) { + + if stakerKey == nil || validatorKey == nil || covenantKey == nil { + return nil, fmt.Errorf("staker, validator and covenant keys cannot be nil") + } + + return &StakingScriptData{ + StakerKey: stakerKey, + ValidatorKey: validatorKey, + CovenantKey: covenantKey, + StakingTime: stakingTime, + }, nil +} + +func genValidStakingScriptData(_ *testing.T, r *rand.Rand) *StakingScriptData { stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) covenantPrivKeyBytes := datagen.GenRandomByteArray(r, 32) @@ -26,7 +54,7 @@ func genValidStakingScriptData(_ *testing.T, r *rand.Rand) *btcstaking.StakingSc _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) _, covenantPublicKey := btcec.PrivKeyFromBytes(covenantPrivKeyBytes) - sd, _ := btcstaking.NewStakingScriptData(stakerPublicKey, validatorPublicKey, covenantPublicKey, stakingTime) + sd, _ := NewStakingScriptData(stakerPublicKey, validatorPublicKey, covenantPublicKey, stakingTime) return sd } diff --git a/btcstaking/staking_utils.go b/btcstaking/staking_utils.go deleted file mode 100644 index 4c7ea0e12..000000000 --- a/btcstaking/staking_utils.go +++ /dev/null @@ -1,219 +0,0 @@ -package btcstaking - -import ( - "bytes" - "fmt" - "sort" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" -) - -// key sorting code copied from musig2 impl in btcd: https://github.com/btcsuite/btcd/blob/master/btcec/schnorr/musig2/keys.go -type sortableKeys []*btcec.PublicKey - -// Less reports whether the element with index i must sort before the element -// with index j. -func (s sortableKeys) Less(i, j int) bool { - // TODO(roasbeef): more efficient way to compare... - keyIBytes := schnorr.SerializePubKey(s[i]) - keyJBytes := schnorr.SerializePubKey(s[j]) - - return bytes.Compare(keyIBytes, keyJBytes) == -1 -} - -// Swap swaps the elements with indexes i and j. -func (s sortableKeys) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Len is the number of elements in the collection. -func (s sortableKeys) Len() int { - return len(s) -} - -// sortKeys takes a set of schnorr public keys and returns a new slice that is -// a copy of the keys sorted in lexicographical order bytes on the x-only -// pubkey serialization. -func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey { - keySet := sortableKeys(keys) - if sort.IsSorted(keySet) { - return keys - } - - sort.Sort(keySet) - return keySet -} - -type SignatureInfo struct { - SignerPubKey *btcec.PublicKey - Signature []byte -} - -func NewSignatureInfo( - signerPubKey *btcec.PublicKey, - signature []byte, -) *SignatureInfo { - return &SignatureInfo{ - SignerPubKey: signerPubKey, - Signature: signature, - } -} - -type sortableSigInfo []*SignatureInfo - -// Less reports whether the element with index i must sort before the element -// with index j. -func (s sortableSigInfo) Less(i, j int) bool { - // TODO(roasbeef): more efficient way to compare... - keyIBytes := schnorr.SerializePubKey(s[i].SignerPubKey) - keyJBytes := schnorr.SerializePubKey(s[j].SignerPubKey) - - return bytes.Compare(keyIBytes, keyJBytes) == 1 -} - -// Swap swaps the elements with indexes i and j. -func (s sortableSigInfo) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Len is the number of elements in the collection. -func (s sortableSigInfo) Len() int { - return len(s) -} - -// Helper function to sort all signatures in reverse lexicographical order of signing public keys -// this way signatures are ready to be used in multisig witness with corresponding public keys -func SortSignatureInfo(infos []*SignatureInfo) []*SignatureInfo { - keySet := sortableSigInfo(infos) - if sort.IsSorted(keySet) { - return infos - } - - sort.Sort(keySet) - return keySet -} - -func SpendInfoFromRevealedScript( - revealedScript []byte, - internalKey *btcec.PublicKey, - tree *txscript.IndexedTapScriptTree) (*SpendInfo, error) { - - revealedLeaf := txscript.NewBaseTapLeaf(revealedScript) - leafHash := revealedLeaf.TapHash() - - scriptIdx, ok := tree.LeafProofIndex[leafHash] - - if !ok { - return nil, fmt.Errorf("script not found in script tree") - } - - merkleProof := tree.LeafMerkleProofs[scriptIdx] - - return &SpendInfo{ - ControlBlock: merkleProof.ToControlBlock(internalKey), - RevealedLeaf: revealedLeaf, - }, nil -} - -func NewTaprootTreeFromScripts( - scripts [][]byte, -) *txscript.IndexedTapScriptTree { - var tapLeafs []txscript.TapLeaf - for _, script := range scripts { - scr := script - tapLeafs = append(tapLeafs, txscript.NewBaseTapLeaf(scr)) - } - return txscript.AssembleTaprootScriptTree(tapLeafs...) -} - -func DeriveTaprootAddress( - tapScriptTree *txscript.IndexedTapScriptTree, - internalPubKey *btcec.PublicKey, - net *chaincfg.Params) (*btcutil.AddressTaproot, error) { - - tapScriptRootHash := tapScriptTree.RootNode.TapHash() - - outputKey := txscript.ComputeTaprootOutputKey( - internalPubKey, tapScriptRootHash[:], - ) - - address, err := btcutil.NewAddressTaproot( - schnorr.SerializePubKey(outputKey), net) - - if err != nil { - return nil, fmt.Errorf("error encoding Taproot address: %v", err) - } - - return address, nil -} - -func DeriveTaprootPkScript( - tapScriptTree *txscript.IndexedTapScriptTree, - internalPubKey *btcec.PublicKey, - net *chaincfg.Params, -) ([]byte, error) { - taprootAddress, err := DeriveTaprootAddress( - tapScriptTree, - &unspendableKeyPathKey, - net, - ) - - if err != nil { - return nil, err - } - - taprootPkScript, err := txscript.PayToAddrScript(taprootAddress) - - if err != nil { - return nil, err - } - - return taprootPkScript, nil -} - -// CreateBabylonWitness creates babylon compatible witness, as babylon scripts -// has witness with the same shape -// - first come signatures -// - then whole revealed script -// - then control block -func CreateBabylonWitness( - signatures [][]byte, - si *SpendInfo, -) (wire.TxWitness, error) { - numSignatures := len(signatures) - - if numSignatures == 0 { - return nil, fmt.Errorf("cannot build witness without signatures") - } - - if si == nil { - return nil, fmt.Errorf("cannot build witness without spend info") - } - - controlBlockBytes, err := si.ControlBlock.ToBytes() - - if err != nil { - return nil, err - } - - // witness stack has: - // all signatures - // whole revealed script - // control block - witnessStack := wire.TxWitness(make([][]byte, numSignatures+2)) - - for i, sig := range signatures { - sc := sig - witnessStack[i] = sc - } - - witnessStack[numSignatures] = si.RevealedLeaf.Script - witnessStack[numSignatures+1] = controlBlockBytes - - return witnessStack, nil -} diff --git a/btcstaking/btc_staking.go b/btcstaking/types.go similarity index 63% rename from btcstaking/btc_staking.go rename to btcstaking/types.go index 40402c5f1..5bf8d9c38 100644 --- a/btcstaking/btc_staking.go +++ b/btcstaking/types.go @@ -1,9 +1,14 @@ package btcstaking import ( + "bytes" + "encoding/hex" "fmt" + "sort" + sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -11,6 +16,92 @@ import ( "github.com/btcsuite/btcd/wire" ) +const ( + // Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + // using it as internal public key efectively disables taproot key spends + unspendableKeyPath = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +) + +var ( + unspendableKeyPathKey = unspendableKeyPathInternalPubKeyInternal(unspendableKeyPath) +) + +func unspendableKeyPathInternalPubKeyInternal(keyHex string) btcec.PublicKey { + keyBytes, err := hex.DecodeString(keyHex) + + if err != nil { + panic(fmt.Sprintf("unexpected error: %v", err)) + } + + // We are using btcec here, as key is 33 byte compressed format. + pubKey, err := btcec.ParsePubKey(keyBytes) + + if err != nil { + panic(fmt.Sprintf("unexpected error: %v", err)) + } + return *pubKey +} + +func unspendableKeyPathInternalPubKey() btcec.PublicKey { + return unspendableKeyPathKey +} + +func NewTaprootTreeFromScripts( + scripts [][]byte, +) *txscript.IndexedTapScriptTree { + var tapLeafs []txscript.TapLeaf + for _, script := range scripts { + scr := script + tapLeafs = append(tapLeafs, txscript.NewBaseTapLeaf(scr)) + } + return txscript.AssembleTaprootScriptTree(tapLeafs...) +} + +func DeriveTaprootAddress( + tapScriptTree *txscript.IndexedTapScriptTree, + internalPubKey *btcec.PublicKey, + net *chaincfg.Params) (*btcutil.AddressTaproot, error) { + + tapScriptRootHash := tapScriptTree.RootNode.TapHash() + + outputKey := txscript.ComputeTaprootOutputKey( + internalPubKey, tapScriptRootHash[:], + ) + + address, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(outputKey), net) + + if err != nil { + return nil, fmt.Errorf("error encoding Taproot address: %v", err) + } + + return address, nil +} + +func DeriveTaprootPkScript( + tapScriptTree *txscript.IndexedTapScriptTree, + internalPubKey *btcec.PublicKey, + net *chaincfg.Params, +) ([]byte, error) { + taprootAddress, err := DeriveTaprootAddress( + tapScriptTree, + &unspendableKeyPathKey, + net, + ) + + if err != nil { + return nil, err + } + + taprootPkScript, err := txscript.PayToAddrScript(taprootAddress) + + if err != nil { + return nil, err + } + + return taprootPkScript, nil +} + type taprootScriptHolder struct { internalPubKey *btcec.PublicKey scriptTree *txscript.IndexedTapScriptTree @@ -83,6 +174,33 @@ func (t *taprootScriptHolder) taprootPkScript(net *chaincfg.Params) ([]byte, err ) } +type SignatureInfo struct { + SignerPubKey *btcec.PublicKey + Signature []byte +} + +func NewSignatureInfo( + signerPubKey *btcec.PublicKey, + signature []byte, +) *SignatureInfo { + return &SignatureInfo{ + SignerPubKey: signerPubKey, + Signature: signature, + } +} + +// Helper function to sort all signatures in reverse lexicographical order of signing public keys +// this way signatures are ready to be used in multisig witness with corresponding public keys +func SortSignatureInfo(infos []*SignatureInfo) []*SignatureInfo { + sort.SliceStable(infos, func(i, j int) bool { + keyIBytes := schnorr.SerializePubKey(infos[i].SignerPubKey) + keyJBytes := schnorr.SerializePubKey(infos[j].SignerPubKey) + return bytes.Compare(keyIBytes, keyJBytes) == 1 + }) + + return infos +} + // Package responsible for different kinds of btc scripts used by babylon // Staking script has 3 spending paths: // 1. Staker can spend after relative time lock - staking @@ -105,6 +223,28 @@ type SpendInfo struct { RevealedLeaf txscript.TapLeaf } +func SpendInfoFromRevealedScript( + revealedScript []byte, + internalKey *btcec.PublicKey, + tree *txscript.IndexedTapScriptTree) (*SpendInfo, error) { + + revealedLeaf := txscript.NewBaseTapLeaf(revealedScript) + leafHash := revealedLeaf.TapHash() + + scriptIdx, ok := tree.LeafProofIndex[leafHash] + + if !ok { + return nil, fmt.Errorf("script not found in script tree") + } + + merkleProof := tree.LeafMerkleProofs[scriptIdx] + + return &SpendInfo{ + ControlBlock: merkleProof.ToControlBlock(internalKey), + RevealedLeaf: revealedLeaf, + }, nil +} + func aggregateScripts(scripts ...[]byte) []byte { if len(scripts) == 0 { return []byte{} @@ -118,7 +258,7 @@ func aggregateScripts(scripts ...[]byte) []byte { return finalScript } -// babylonScriptPaths is and aggregate of all possible babylon script paths +// babylonScriptPaths contains all possible babylon script paths // not every babylon output will contain all of those paths type babylonScriptPaths struct { timeLockPathScript []byte @@ -137,13 +277,13 @@ func newBabylonScriptPaths( return nil, fmt.Errorf("staker key is nil") } - timeLockPathScript, err := BuildTimeLockScript(stakerKey, lockTime) + timeLockPathScript, err := buildTimeLockScript(stakerKey, lockTime) if err != nil { return nil, err } - covenantMultisigScript, err := BuildMultiSigScript( + covenantMultisigScript, err := buildMultiSigScript( covenantKeys, covenantThreshold, // covenant multisig is always last in script so we do not run verify and leave @@ -156,13 +296,13 @@ func newBabylonScriptPaths( return nil, err } - stakerSigScript, err := BuildSingleKeySigScript(stakerKey, true) + stakerSigScript, err := buildSingleKeySigScript(stakerKey, true) if err != nil { return nil, err } - validatorSigScript, err := BuildMultiSigScript( + validatorSigScript, err := buildMultiSigScript( validatorKeys, // we always require only one validator to sign 1, @@ -201,7 +341,7 @@ func BuildStakingInfo( stakingAmount btcutil.Amount, net *chaincfg.Params, ) (*StakingInfo, error) { - unspendableKeyPathKey := UnspendableKeyPathInternalPubKey() + unspendableKeyPathKey := unspendableKeyPathInternalPubKey() babylonScripts, err := newBabylonScriptPaths( stakerKey, @@ -281,7 +421,7 @@ func BuildUnbondingInfo( unbondingAmount btcutil.Amount, net *chaincfg.Params, ) (*UnbondingInfo, error) { - unspendableKeyPathKey := UnspendableKeyPathInternalPubKey() + unspendableKeyPathKey := unspendableKeyPathInternalPubKey() babylonScripts, err := newBabylonScriptPaths( stakerKey, @@ -334,3 +474,20 @@ func (i *UnbondingInfo) TimeLockPathSpendInfo() (*SpendInfo, error) { func (i *UnbondingInfo) SlashingPathSpendInfo() (*SpendInfo, error) { return i.scriptHolder.scriptSpendInfoByName(i.slashingPathLeafHash) } + +// IsSlashingRateValid checks if the given slashing rate is between the valid range i.e., (0,1) with a precision of at most 2 decimal places. +func IsSlashingRateValid(slashingRate sdkmath.LegacyDec) bool { + // Check if the slashing rate is between 0 and 1 + if slashingRate.LTE(sdkmath.LegacyZeroDec()) || slashingRate.GTE(sdkmath.LegacyOneDec()) { + return false + } + + // Multiply by 100 to move the decimal places and check if precision is at most 2 decimal places + multipliedRate := slashingRate.Mul(sdkmath.LegacyNewDec(100)) + + // Truncate the rate to remove decimal places + truncatedRate := multipliedRate.TruncateDec() + + // Check if the truncated rate is equal to the original rate + return multipliedRate.Equal(truncatedRate) +} diff --git a/go.mod b/go.mod index 62bbabfd8..5dd4cfceb 100644 --- a/go.mod +++ b/go.mod @@ -120,7 +120,7 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.2 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.14.0 golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go index fac4aab24..6f3138b58 100644 --- a/x/btcstaking/types/types.go +++ b/x/btcstaking/types/types.go @@ -6,7 +6,6 @@ import ( "fmt" "math" - "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/wire" ) @@ -17,14 +16,6 @@ type PublicKeyInfo struct { CovenantKey *bbn.BIP340PubKey } -func KeyDataFromScript(scriptData *btcstaking.StakingScriptData) *PublicKeyInfo { - return &PublicKeyInfo{ - StakerKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.StakerKey), - ValidatorKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.ValidatorKey), - CovenantKey: bbn.NewBIP340PubKeyFromBTCPK(scriptData.CovenantKey), - } -} - func ParseBtcTx(txBytes []byte) (*wire.MsgTx, error) { var msgTx wire.MsgTx rbuf := bytes.NewReader(txBytes) From 4a58fbecd77e448591d94ebcce51535b7910ca44 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 29 Nov 2023 05:55:53 +0100 Subject: [PATCH 114/202] Add unbonding time to unbonding info (#133) --- proto/babylon/btcstaking/v1/btcstaking.proto | 4 +- x/btcstaking/keeper/grpc_query.go | 1 + x/btcstaking/types/btcstaking.pb.go | 174 +++++++++++-------- 3 files changed, 109 insertions(+), 70 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 2365775ab..681f58d38 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -134,9 +134,11 @@ message BTCUndelegationInfo { // than staking output. bytes unbonding_tx = 1; + // unbonding_time describes how long the funds will be locked in the unbonding output + uint32 unbonding_time = 2; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - repeated SignatureInfo covenant_unbonding_sig_list = 2; + repeated SignatureInfo covenant_unbonding_sig_list = 3; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 578f73218..d98144c25 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -285,6 +285,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation if btcDel.BtcUndelegation != nil { undelegationInfo = &types.BTCUndelegationInfo{ UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, + UnbondingTime: btcDel.BtcUndelegation.UnbondingTime, CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, } } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index a1ad77207..00ee07a55 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -484,9 +484,11 @@ type BTCUndelegationInfo struct { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // unbonding_time describes how long the funds will be locked in the unbonding output + UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,2,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,3,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -529,6 +531,13 @@ func (m *BTCUndelegationInfo) GetUnbondingTx() []byte { return nil } +func (m *BTCUndelegationInfo) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + func (m *BTCUndelegationInfo) GetCovenantUnbondingSigList() []*SignatureInfo { if m != nil { return m.CovenantUnbondingSigList @@ -734,75 +743,75 @@ func init() { var fileDescriptor_3851ae95ccfaf7db = []byte{ // 1111 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0xae, 0x93, 0x34, 0x6d, 0x4e, 0x12, 0x9a, 0x1d, 0x42, 0xf1, 0xb6, 0x22, 0x09, 0x61, 0x59, + 0x14, 0xae, 0xe3, 0x34, 0x6d, 0x4e, 0x12, 0x9a, 0x1d, 0x42, 0xf1, 0xb6, 0x22, 0x09, 0x61, 0x59, 0x45, 0x88, 0x75, 0xb6, 0xdd, 0x1f, 0x01, 0x17, 0x48, 0x4d, 0x53, 0xd8, 0x68, 0xfb, 0x13, 0x9c, 0x74, 0x11, 0x48, 0x10, 0x4d, 0xec, 0xa9, 0x63, 0x25, 0xf1, 0x58, 0x99, 0x49, 0x48, 0xee, 0xb9, - 0x45, 0xe2, 0x05, 0xb8, 0x82, 0x47, 0xe0, 0x21, 0xb8, 0x41, 0x5a, 0xc1, 0x0d, 0xea, 0x45, 0x85, - 0xda, 0x17, 0x41, 0x1e, 0x4f, 0x6c, 0xb7, 0xb4, 0xfb, 0xd7, 0xde, 0x65, 0xce, 0xcf, 0x77, 0xce, - 0xf9, 0xbe, 0x33, 0xe3, 0xc0, 0xdd, 0x2e, 0xee, 0xce, 0x06, 0xd4, 0xa9, 0x76, 0xb9, 0xc1, 0x38, - 0xee, 0xdb, 0x8e, 0x55, 0x9d, 0x6c, 0x44, 0x4e, 0x9a, 0x3b, 0xa2, 0x9c, 0xa2, 0x77, 0x64, 0x9c, - 0x16, 0xf1, 0x4c, 0x36, 0xd6, 0xf2, 0x16, 0xb5, 0xa8, 0x88, 0xa8, 0x7a, 0xbf, 0xfc, 0xe0, 0xb5, - 0xdb, 0x06, 0x65, 0x43, 0xca, 0x3a, 0xbe, 0xc3, 0x3f, 0x48, 0x57, 0xd9, 0x3f, 0x55, 0x8d, 0xd1, - 0xcc, 0xe5, 0xb4, 0xca, 0x88, 0xe1, 0x6e, 0x3e, 0x7a, 0xdc, 0xdf, 0xa8, 0xf6, 0xc9, 0x6c, 0x1e, - 0x73, 0x47, 0xc6, 0x84, 0xfd, 0x74, 0x09, 0xc7, 0x1b, 0xd5, 0x73, 0x1d, 0xad, 0x15, 0x2f, 0xef, - 0xdc, 0xa5, 0xae, 0x1f, 0x50, 0xfe, 0x3b, 0x0e, 0x99, 0x5a, 0x7b, 0xfb, 0x19, 0x1e, 0xd8, 0x26, - 0xe6, 0x74, 0x84, 0x76, 0x20, 0x6d, 0x12, 0x66, 0x8c, 0x6c, 0x97, 0xdb, 0xd4, 0x51, 0x95, 0x92, - 0x52, 0x49, 0x6f, 0x7e, 0xa0, 0xc9, 0xfe, 0xc2, 0xa9, 0x44, 0x35, 0xad, 0x1e, 0x86, 0xea, 0xd1, - 0x3c, 0xb4, 0x07, 0x60, 0xd0, 0xe1, 0xd0, 0x66, 0xcc, 0x43, 0x89, 0x95, 0x94, 0x4a, 0xaa, 0x76, - 0xef, 0xf8, 0xa4, 0xb8, 0xee, 0x03, 0x31, 0xb3, 0xaf, 0xd9, 0xb4, 0x3a, 0xc4, 0xbc, 0xa7, 0xed, - 0x12, 0x0b, 0x1b, 0xb3, 0x3a, 0x31, 0xfe, 0xfa, 0xfd, 0x1e, 0xc8, 0x3a, 0x75, 0x62, 0xe8, 0x11, - 0x00, 0xf4, 0x39, 0x80, 0x9c, 0xa4, 0xe3, 0xf6, 0xd5, 0xb8, 0x68, 0xaa, 0x38, 0x6f, 0xca, 0xa7, - 0x49, 0x0b, 0x68, 0xd2, 0x9a, 0xe3, 0xee, 0x53, 0x32, 0xd3, 0x53, 0x32, 0xa5, 0xd9, 0x47, 0x7b, - 0x90, 0xec, 0x72, 0xc3, 0xcb, 0x4d, 0x94, 0x94, 0x4a, 0xa6, 0xf6, 0xf8, 0xf8, 0xa4, 0xb8, 0x69, - 0xd9, 0xbc, 0x37, 0xee, 0x6a, 0x06, 0x1d, 0x56, 0x65, 0xa4, 0xd1, 0xc3, 0xb6, 0x33, 0x3f, 0x54, - 0xf9, 0xcc, 0x25, 0x4c, 0xab, 0x35, 0x9a, 0x0f, 0x1e, 0xde, 0x97, 0x90, 0x8b, 0x5d, 0x6e, 0x34, - 0xfb, 0xe8, 0x33, 0x88, 0xbb, 0xd4, 0x55, 0x17, 0x45, 0x1f, 0x15, 0xed, 0x52, 0xd9, 0xb5, 0xe6, - 0x88, 0xd2, 0xa3, 0x83, 0xa3, 0x26, 0x65, 0x8c, 0x88, 0x29, 0x74, 0x2f, 0x09, 0x3d, 0x84, 0x55, - 0x36, 0xc0, 0xac, 0x47, 0xcc, 0xce, 0x7c, 0xa4, 0x1e, 0xb1, 0xad, 0x1e, 0x57, 0x93, 0x25, 0xa5, - 0x92, 0xd0, 0xf3, 0xd2, 0x5b, 0xf3, 0x9d, 0x4f, 0x84, 0x0f, 0x7d, 0x0c, 0x28, 0xc8, 0xe2, 0xc6, - 0x3c, 0x63, 0x49, 0x64, 0xe4, 0xe6, 0x19, 0xdc, 0xf0, 0xa3, 0xcb, 0x3f, 0xc6, 0x20, 0x1f, 0x55, - 0xf5, 0x6b, 0x9b, 0xf7, 0xf6, 0x08, 0xc7, 0x11, 0x1e, 0x94, 0x9b, 0xe0, 0x61, 0x15, 0x92, 0xb2, - 0x93, 0x98, 0xe8, 0x44, 0x9e, 0xd0, 0xfb, 0x90, 0x99, 0x50, 0x6e, 0x3b, 0x56, 0xc7, 0xa5, 0x3f, - 0x90, 0x91, 0x10, 0x2c, 0xa1, 0xa7, 0x7d, 0x5b, 0xd3, 0x33, 0xbd, 0x80, 0x86, 0xc4, 0x6b, 0xd3, - 0xb0, 0x78, 0x05, 0x0d, 0xbf, 0x26, 0x21, 0x5b, 0x6b, 0x6f, 0xd7, 0xc9, 0x80, 0x58, 0x98, 0xff, - 0x7f, 0x8f, 0x94, 0x6b, 0xec, 0x51, 0xec, 0x06, 0xf7, 0x28, 0xfe, 0x26, 0x7b, 0xf4, 0x1d, 0xac, - 0x4c, 0xf0, 0xa0, 0xe3, 0xb7, 0xd3, 0x19, 0xd8, 0xcc, 0x63, 0x2e, 0x7e, 0x8d, 0x9e, 0x32, 0x13, - 0x3c, 0xa8, 0x79, 0x6d, 0xed, 0xda, 0x4c, 0x48, 0xc8, 0x38, 0x1e, 0xf1, 0xf3, 0x1c, 0xa7, 0x85, - 0x4d, 0x8a, 0xf1, 0x1e, 0x00, 0x71, 0xcc, 0xf3, 0xdb, 0x9b, 0x22, 0x8e, 0x29, 0xdd, 0xeb, 0x90, - 0xe2, 0x94, 0xe3, 0x41, 0x87, 0xe1, 0xf9, 0xa6, 0x2e, 0x0b, 0x43, 0x0b, 0x8b, 0x5c, 0x39, 0x62, - 0x87, 0x4f, 0xd5, 0x65, 0x8f, 0x4c, 0x3d, 0x25, 0x2d, 0xed, 0xa9, 0xd0, 0x59, 0xba, 0xe9, 0x98, - 0xbb, 0x63, 0xde, 0xb1, 0xcd, 0xa9, 0x9a, 0x2a, 0x29, 0x95, 0xac, 0x9e, 0x93, 0x9e, 0x03, 0xe1, - 0x68, 0x98, 0x53, 0xb4, 0x09, 0x69, 0xa1, 0xbd, 0x44, 0x03, 0x21, 0xcd, 0xad, 0xe3, 0x93, 0xa2, - 0xa7, 0x7e, 0x4b, 0x7a, 0xda, 0x53, 0x1d, 0x58, 0xf0, 0x1b, 0x7d, 0x0f, 0x59, 0xd3, 0xdf, 0x0b, - 0x3a, 0xea, 0x30, 0xdb, 0x52, 0xd3, 0x22, 0xeb, 0xd3, 0xe3, 0x93, 0xe2, 0xa3, 0xd7, 0x21, 0xaf, - 0x65, 0x5b, 0x0e, 0xe6, 0xe3, 0x11, 0xd1, 0x33, 0x01, 0x5e, 0xcb, 0xb6, 0xd0, 0x21, 0x64, 0x0d, - 0x3a, 0x21, 0x0e, 0x76, 0xb8, 0x07, 0xcf, 0xd4, 0x4c, 0x29, 0x5e, 0x49, 0x6f, 0xde, 0xbf, 0x42, - 0xe4, 0x6d, 0x19, 0xbb, 0x65, 0x62, 0xd7, 0x47, 0xf0, 0x51, 0x99, 0x9e, 0x99, 0xc3, 0xb4, 0x6c, - 0x8b, 0xa1, 0xaf, 0x20, 0xe7, 0x29, 0x3e, 0x76, 0xcc, 0x60, 0xa9, 0xd5, 0xac, 0x58, 0x9f, 0xbb, - 0x57, 0x20, 0xd7, 0xda, 0xdb, 0x87, 0x91, 0x68, 0x7d, 0xa5, 0xcb, 0x8d, 0xa8, 0xa1, 0xfc, 0x67, - 0x1c, 0x56, 0x2e, 0x04, 0x79, 0xea, 0x8f, 0x9d, 0x2e, 0x75, 0x4c, 0x49, 0xa9, 0x78, 0x2d, 0xf4, - 0x74, 0x60, 0x6b, 0x4f, 0xd1, 0x87, 0xf0, 0x56, 0x24, 0xc4, 0x1e, 0x12, 0x71, 0x25, 0xb2, 0x7a, - 0x36, 0x0c, 0xb2, 0x87, 0xe4, 0xa2, 0x36, 0xf1, 0x57, 0xd1, 0x86, 0xc2, 0x6a, 0x44, 0x9b, 0x79, - 0xb6, 0x27, 0x52, 0xe2, 0xba, 0x22, 0xe5, 0x43, 0x91, 0x24, 0xae, 0x27, 0xd6, 0x11, 0xac, 0x86, - 0x62, 0x45, 0xea, 0x31, 0x75, 0xf1, 0x0d, 0x55, 0xcb, 0x07, 0xaa, 0x85, 0x65, 0x18, 0x32, 0x60, - 0x3d, 0xa8, 0x13, 0x92, 0xc7, 0x6c, 0xcb, 0xbf, 0xbf, 0x49, 0x51, 0xec, 0xce, 0x15, 0xc5, 0x02, - 0xf4, 0x86, 0x73, 0x44, 0x75, 0x75, 0x0e, 0x74, 0x38, 0xc7, 0x69, 0xd9, 0x96, 0x77, 0x73, 0xcb, - 0xbf, 0x28, 0xf0, 0xf6, 0x05, 0x3d, 0xbd, 0x8c, 0x57, 0xd1, 0xf4, 0x25, 0xfd, 0xc5, 0x6e, 0xa4, - 0xbf, 0x16, 0xbc, 0x1b, 0x3e, 0xca, 0x74, 0x14, 0xbe, 0xce, 0x0c, 0x7d, 0x02, 0x09, 0x93, 0x0c, - 0x98, 0xaa, 0xbc, 0xb0, 0xd0, 0xb9, 0x27, 0x5d, 0x17, 0x19, 0xe5, 0x7d, 0x58, 0xbf, 0x1c, 0xb4, - 0xe1, 0x98, 0x64, 0x8a, 0xaa, 0x90, 0x0f, 0x9f, 0x9b, 0x4e, 0x0f, 0xb3, 0x9e, 0x3f, 0x91, 0x57, - 0x28, 0xa3, 0xdf, 0x0a, 0x1e, 0x9e, 0x27, 0x98, 0xf5, 0x44, 0x93, 0xbf, 0x29, 0x90, 0x3d, 0x37, - 0x10, 0xfa, 0x02, 0x62, 0xd7, 0xfe, 0x6c, 0xc6, 0xdc, 0x3e, 0x7a, 0x0a, 0x71, 0x6f, 0x93, 0x63, - 0xd7, 0xdd, 0x64, 0x0f, 0xa5, 0xfc, 0x93, 0x02, 0xb7, 0xaf, 0x5c, 0x42, 0xef, 0x6b, 0x65, 0xd0, - 0xc9, 0x0d, 0x7c, 0xed, 0x0d, 0x3a, 0x69, 0xf6, 0xbd, 0x05, 0xc2, 0x7e, 0x0d, 0xff, 0x6e, 0xc4, - 0x04, 0x79, 0x69, 0x1c, 0xd4, 0x65, 0x1f, 0xb5, 0xc5, 0xea, 0x85, 0xec, 0xb7, 0x38, 0xe6, 0x63, - 0x86, 0xd2, 0xb0, 0xd4, 0xdc, 0xd9, 0xaf, 0x37, 0xf6, 0xbf, 0xcc, 0x2d, 0x20, 0x80, 0xe4, 0xd6, - 0x76, 0xbb, 0xf1, 0x6c, 0x27, 0xa7, 0xa0, 0x2c, 0xa4, 0x0e, 0xf7, 0x6b, 0x07, 0xbe, 0x2b, 0x86, - 0x32, 0xb0, 0xec, 0x1f, 0x77, 0xea, 0xb9, 0x38, 0x5a, 0x82, 0xf8, 0xd6, 0xfe, 0x37, 0xb9, 0x44, - 0x6d, 0xf7, 0x8f, 0xd3, 0x82, 0xf2, 0xfc, 0xb4, 0xa0, 0xfc, 0x7b, 0x5a, 0x50, 0x7e, 0x3e, 0x2b, - 0x2c, 0x3c, 0x3f, 0x2b, 0x2c, 0xfc, 0x73, 0x56, 0x58, 0xf8, 0xf6, 0xa5, 0xd3, 0x4c, 0xa3, 0xff, - 0x7c, 0xc5, 0x68, 0xdd, 0xa4, 0xf8, 0xe7, 0xfb, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5c, - 0x49, 0x1e, 0x9d, 0xd6, 0x0b, 0x00, 0x00, + 0x45, 0xe2, 0x1d, 0xe0, 0x11, 0x78, 0x04, 0x2e, 0xb8, 0x41, 0x5a, 0xc1, 0x0d, 0xea, 0x45, 0x85, + 0xda, 0x17, 0x41, 0x1e, 0x4f, 0x62, 0xb7, 0xb4, 0xfb, 0xd7, 0xde, 0x65, 0xce, 0xcf, 0x77, 0xce, + 0xf9, 0xbe, 0x33, 0x13, 0xc3, 0xdd, 0x0e, 0xee, 0x4c, 0xfb, 0xd4, 0xad, 0x74, 0xb8, 0xc9, 0x38, + 0xee, 0x39, 0xae, 0x5d, 0x19, 0x6f, 0x44, 0x4e, 0xba, 0x37, 0xa4, 0x9c, 0xa2, 0x77, 0x64, 0x9c, + 0x1e, 0xf1, 0x8c, 0x37, 0xd6, 0x72, 0x36, 0xb5, 0xa9, 0x88, 0xa8, 0xf8, 0xbf, 0x82, 0xe0, 0xb5, + 0xdb, 0x26, 0x65, 0x03, 0xca, 0xda, 0x81, 0x23, 0x38, 0x48, 0x57, 0x29, 0x38, 0x55, 0xcc, 0xe1, + 0xd4, 0xe3, 0xb4, 0xc2, 0x88, 0xe9, 0x6d, 0x3e, 0x7a, 0xdc, 0xdb, 0xa8, 0xf4, 0xc8, 0x74, 0x16, + 0x73, 0x47, 0xc6, 0x84, 0xfd, 0x74, 0x08, 0xc7, 0x1b, 0x95, 0x73, 0x1d, 0xad, 0x15, 0x2e, 0xef, + 0xdc, 0xa3, 0x5e, 0x10, 0x50, 0xfa, 0x5b, 0x85, 0x74, 0xb5, 0xb5, 0xfd, 0x0c, 0xf7, 0x1d, 0x0b, + 0x73, 0x3a, 0x44, 0x3b, 0x90, 0xb2, 0x08, 0x33, 0x87, 0x8e, 0xc7, 0x1d, 0xea, 0x6a, 0x4a, 0x51, + 0x29, 0xa7, 0x36, 0x3f, 0xd0, 0x65, 0x7f, 0xe1, 0x54, 0xa2, 0x9a, 0x5e, 0x0b, 0x43, 0x8d, 0x68, + 0x1e, 0xda, 0x03, 0x30, 0xe9, 0x60, 0xe0, 0x30, 0xe6, 0xa3, 0xc4, 0x8a, 0x4a, 0x39, 0x59, 0xbd, + 0x77, 0x7c, 0x52, 0x58, 0x0f, 0x80, 0x98, 0xd5, 0xd3, 0x1d, 0x5a, 0x19, 0x60, 0xde, 0xd5, 0x77, + 0x89, 0x8d, 0xcd, 0x69, 0x8d, 0x98, 0x7f, 0xfd, 0x76, 0x0f, 0x64, 0x9d, 0x1a, 0x31, 0x8d, 0x08, + 0x00, 0xfa, 0x1c, 0x40, 0x4e, 0xd2, 0xf6, 0x7a, 0x9a, 0x2a, 0x9a, 0x2a, 0xcc, 0x9a, 0x0a, 0x68, + 0xd2, 0xe7, 0x34, 0xe9, 0x8d, 0x51, 0xe7, 0x29, 0x99, 0x1a, 0x49, 0x99, 0xd2, 0xe8, 0xa1, 0x3d, + 0x48, 0x74, 0xb8, 0xe9, 0xe7, 0xc6, 0x8b, 0x4a, 0x39, 0x5d, 0x7d, 0x7c, 0x7c, 0x52, 0xd8, 0xb4, + 0x1d, 0xde, 0x1d, 0x75, 0x74, 0x93, 0x0e, 0x2a, 0x32, 0xd2, 0xec, 0x62, 0xc7, 0x9d, 0x1d, 0x2a, + 0x7c, 0xea, 0x11, 0xa6, 0x57, 0xeb, 0x8d, 0x07, 0x0f, 0xef, 0x4b, 0xc8, 0xc5, 0x0e, 0x37, 0x1b, + 0x3d, 0xf4, 0x19, 0xa8, 0x1e, 0xf5, 0xb4, 0x45, 0xd1, 0x47, 0x59, 0xbf, 0x54, 0x76, 0xbd, 0x31, + 0xa4, 0xf4, 0xe8, 0xe0, 0xa8, 0x41, 0x19, 0x23, 0x62, 0x0a, 0xc3, 0x4f, 0x42, 0x0f, 0x61, 0x95, + 0xf5, 0x31, 0xeb, 0x12, 0xab, 0x3d, 0x1b, 0xa9, 0x4b, 0x1c, 0xbb, 0xcb, 0xb5, 0x44, 0x51, 0x29, + 0xc7, 0x8d, 0x9c, 0xf4, 0x56, 0x03, 0xe7, 0x13, 0xe1, 0x43, 0x1f, 0x03, 0x9a, 0x67, 0x71, 0x73, + 0x96, 0xb1, 0x24, 0x32, 0xb2, 0xb3, 0x0c, 0x6e, 0x06, 0xd1, 0xa5, 0x1f, 0x63, 0x90, 0x8b, 0xaa, + 0xfa, 0xb5, 0xc3, 0xbb, 0x7b, 0x84, 0xe3, 0x08, 0x0f, 0xca, 0x4d, 0xf0, 0xb0, 0x0a, 0x09, 0xd9, + 0x49, 0x4c, 0x74, 0x22, 0x4f, 0xe8, 0x7d, 0x48, 0x8f, 0x29, 0x77, 0x5c, 0xbb, 0xed, 0xd1, 0x1f, + 0xc8, 0x50, 0x08, 0x16, 0x37, 0x52, 0x81, 0xad, 0xe1, 0x9b, 0x5e, 0x40, 0x43, 0xfc, 0xb5, 0x69, + 0x58, 0xbc, 0x82, 0x86, 0x5f, 0x12, 0x90, 0xa9, 0xb6, 0xb6, 0x6b, 0xa4, 0x4f, 0x6c, 0xcc, 0xff, + 0xbf, 0x47, 0xca, 0x35, 0xf6, 0x28, 0x76, 0x83, 0x7b, 0xa4, 0xbe, 0xc9, 0x1e, 0x7d, 0x07, 0x2b, + 0x63, 0xdc, 0x6f, 0x07, 0xed, 0xb4, 0xfb, 0x0e, 0xf3, 0x99, 0x53, 0xaf, 0xd1, 0x53, 0x7a, 0x8c, + 0xfb, 0x55, 0xbf, 0xad, 0x5d, 0x87, 0x09, 0x09, 0x19, 0xc7, 0x43, 0x7e, 0x9e, 0xe3, 0x94, 0xb0, + 0x49, 0x31, 0xde, 0x03, 0x20, 0xae, 0x75, 0x7e, 0x7b, 0x93, 0xc4, 0xb5, 0xa4, 0x7b, 0x1d, 0x92, + 0x9c, 0x72, 0xdc, 0x6f, 0x33, 0x3c, 0xdb, 0xd4, 0x65, 0x61, 0x68, 0x62, 0x91, 0x2b, 0x47, 0x6c, + 0xf3, 0x89, 0xb6, 0xec, 0x93, 0x69, 0x24, 0xa5, 0xa5, 0x35, 0x11, 0x3a, 0x4b, 0x37, 0x1d, 0x71, + 0x6f, 0xc4, 0xdb, 0x8e, 0x35, 0xd1, 0x92, 0x45, 0xa5, 0x9c, 0x31, 0xb2, 0xd2, 0x73, 0x20, 0x1c, + 0x75, 0x6b, 0x82, 0x36, 0x21, 0x25, 0xb4, 0x97, 0x68, 0x20, 0xa4, 0xb9, 0x75, 0x7c, 0x52, 0xf0, + 0xd5, 0x6f, 0x4a, 0x4f, 0x6b, 0x62, 0x00, 0x9b, 0xff, 0x46, 0xdf, 0x43, 0xc6, 0x0a, 0xf6, 0x82, + 0x0e, 0xdb, 0xcc, 0xb1, 0xb5, 0x94, 0xc8, 0xfa, 0xf4, 0xf8, 0xa4, 0xf0, 0xe8, 0x75, 0xc8, 0x6b, + 0x3a, 0xb6, 0x8b, 0xf9, 0x68, 0x48, 0x8c, 0xf4, 0x1c, 0xaf, 0xe9, 0xd8, 0xe8, 0x10, 0x32, 0x26, + 0x1d, 0x13, 0x17, 0xbb, 0xdc, 0x87, 0x67, 0x5a, 0xba, 0xa8, 0x96, 0x53, 0x9b, 0xf7, 0xaf, 0x10, + 0x79, 0x5b, 0xc6, 0x6e, 0x59, 0xd8, 0x0b, 0x10, 0x02, 0x54, 0x66, 0xa4, 0x67, 0x30, 0x4d, 0xc7, + 0x66, 0xe8, 0x2b, 0xc8, 0xfa, 0x8a, 0x8f, 0x5c, 0x6b, 0xbe, 0xd4, 0x5a, 0x46, 0xac, 0xcf, 0xdd, + 0x2b, 0x90, 0xab, 0xad, 0xed, 0xc3, 0x48, 0xb4, 0xb1, 0xd2, 0xe1, 0x66, 0xd4, 0x50, 0xfa, 0x53, + 0x85, 0x95, 0x0b, 0x41, 0xbe, 0xfa, 0x23, 0xb7, 0x43, 0x5d, 0x4b, 0x52, 0x2a, 0x5e, 0x0b, 0x23, + 0x35, 0xb7, 0xb5, 0x26, 0xe8, 0x43, 0x78, 0x2b, 0x12, 0xe2, 0x0c, 0x88, 0xb8, 0x12, 0x19, 0x23, + 0x13, 0x06, 0x39, 0x03, 0x72, 0x51, 0x1b, 0xf5, 0x55, 0xb4, 0xa1, 0xb0, 0x1a, 0xd1, 0x66, 0x96, + 0xed, 0x8b, 0x14, 0xbf, 0xae, 0x48, 0xb9, 0x50, 0x24, 0x89, 0xeb, 0x8b, 0x75, 0x04, 0xab, 0xa1, + 0x58, 0x91, 0x7a, 0x4c, 0x5b, 0x7c, 0x43, 0xd5, 0x72, 0x73, 0xd5, 0xc2, 0x32, 0x0c, 0x99, 0xb0, + 0x3e, 0xaf, 0x13, 0x92, 0xc7, 0x1c, 0x3b, 0xb8, 0xbf, 0x09, 0x51, 0xec, 0xce, 0x15, 0xc5, 0xe6, + 0xe8, 0x75, 0xf7, 0x88, 0x1a, 0xda, 0x0c, 0xe8, 0x70, 0x86, 0xd3, 0x74, 0x6c, 0xff, 0xe6, 0x96, + 0x7e, 0x57, 0xe0, 0xed, 0x0b, 0x7a, 0xfa, 0x19, 0x37, 0xa8, 0xe9, 0x4b, 0xc6, 0x50, 0x6f, 0x64, + 0x8c, 0x26, 0xbc, 0x1b, 0xbe, 0xdd, 0x74, 0x18, 0x3e, 0xe2, 0x0c, 0x7d, 0x02, 0x71, 0x8b, 0xf4, + 0x99, 0xa6, 0xbc, 0xb0, 0xd0, 0xb9, 0x97, 0xdf, 0x10, 0x19, 0xa5, 0x7d, 0x58, 0xbf, 0x1c, 0xb4, + 0xee, 0x5a, 0x64, 0x82, 0x2a, 0x90, 0x0b, 0x5f, 0xa5, 0x76, 0x17, 0xb3, 0x6e, 0x30, 0x91, 0x5f, + 0x28, 0x6d, 0xdc, 0x9a, 0xbf, 0x4f, 0x4f, 0x30, 0xeb, 0x8a, 0x26, 0x7f, 0x55, 0x20, 0x73, 0x6e, + 0x20, 0xf4, 0x05, 0xc4, 0xae, 0xfd, 0xef, 0x1a, 0xf3, 0x7a, 0xe8, 0x29, 0xa8, 0xfe, 0xc2, 0xc7, + 0xae, 0xbb, 0xf0, 0x3e, 0x4a, 0xe9, 0x27, 0x05, 0x6e, 0x5f, 0xb9, 0xab, 0xfe, 0x9f, 0x9a, 0x49, + 0xc7, 0x37, 0xf0, 0x51, 0x60, 0xd2, 0x71, 0xa3, 0xe7, 0xef, 0x19, 0x0e, 0x6a, 0x04, 0x57, 0x28, + 0x26, 0xc8, 0x4b, 0xe1, 0x79, 0x5d, 0xf6, 0x51, 0x4b, 0x6c, 0x68, 0xc8, 0x7e, 0x93, 0x63, 0x3e, + 0x62, 0x28, 0x05, 0x4b, 0x8d, 0x9d, 0xfd, 0x5a, 0x7d, 0xff, 0xcb, 0xec, 0x02, 0x02, 0x48, 0x6c, + 0x6d, 0xb7, 0xea, 0xcf, 0x76, 0xb2, 0x0a, 0xca, 0x40, 0xf2, 0x70, 0xbf, 0x7a, 0x10, 0xb8, 0x62, + 0x28, 0x0d, 0xcb, 0xc1, 0x71, 0xa7, 0x96, 0x55, 0xd1, 0x12, 0xa8, 0x5b, 0xfb, 0xdf, 0x64, 0xe3, + 0xd5, 0xdd, 0x3f, 0x4e, 0xf3, 0xca, 0xf3, 0xd3, 0xbc, 0xf2, 0xef, 0x69, 0x5e, 0xf9, 0xf9, 0x2c, + 0xbf, 0xf0, 0xfc, 0x2c, 0xbf, 0xf0, 0xcf, 0x59, 0x7e, 0xe1, 0xdb, 0x97, 0x4e, 0x33, 0x89, 0x7e, + 0x20, 0x8b, 0xd1, 0x3a, 0x09, 0xf1, 0x81, 0xfc, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, + 0x7f, 0xe0, 0x9e, 0xfd, 0x0b, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1221,9 +1230,14 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a } } + if m.UnbondingTime != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x10 + } if len(m.UnbondingTx) > 0 { i -= len(m.UnbondingTx) copy(dAtA[i:], m.UnbondingTx) @@ -1572,6 +1586,9 @@ func (m *BTCUndelegationInfo) Size() (n int) { if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } + if m.UnbondingTime != 0 { + n += 1 + sovBtcstaking(uint64(m.UnbondingTime)) + } if len(m.CovenantUnbondingSigList) > 0 { for _, e := range m.CovenantUnbondingSigList { l = e.Size() @@ -2828,6 +2845,25 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) + } + m.UnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } From 8ad04811f959a458236fd91862422fee00325416 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 29 Nov 2023 17:49:56 +1100 Subject: [PATCH 115/202] btcstaking: utilities for building slashing tx witness (#132) --- btcstaking/btcstaking_test.go | 70 +--- btcstaking/staking.go | 12 +- btcstaking/types.go | 18 + test/e2e/btc_staking_e2e_test.go | 22 +- .../configurer/chain/commands_btcstaking.go | 2 +- testutil/bitcoin/utils.go | 19 + testutil/datagen/btcstaking.go | 14 +- types/btc_schnorr_pk.go | 20 + types/btcutils.go | 45 +++ x/btcstaking/client/cli/tx.go | 2 +- x/btcstaking/keeper/btc_delegators.go | 2 +- x/btcstaking/keeper/msg_server.go | 136 ++----- x/btcstaking/keeper/msg_server_test.go | 38 +- x/btcstaking/types/btc_delegation.go | 371 ++++++++++++++++++ ...staking_test.go => btc_delegation_test.go} | 143 ++++--- x/btcstaking/types/btc_delegations.go | 45 --- x/btcstaking/types/btc_slashing_tx.go | 59 ++- x/btcstaking/types/btc_slashing_tx_test.go | 52 +-- x/btcstaking/types/btc_undelegation.go | 74 ++++ x/btcstaking/types/btc_undelegation_test.go | 131 +++++++ x/btcstaking/types/btcstaking.go | 222 ----------- x/btcstaking/types/msg.go | 3 +- x/btcstaking/types/params.go | 8 + x/btcstaking/types/types.go | 71 ---- 24 files changed, 938 insertions(+), 641 deletions(-) create mode 100644 x/btcstaking/types/btc_delegation.go rename x/btcstaking/types/{btcstaking_test.go => btc_delegation_test.go} (55%) delete mode 100644 x/btcstaking/types/btc_delegations.go create mode 100644 x/btcstaking/types/btc_undelegation.go create mode 100644 x/btcstaking/types/btc_undelegation_test.go delete mode 100644 x/btcstaking/types/types.go diff --git a/btcstaking/btcstaking_test.go b/btcstaking/btcstaking_test.go index 993c8b1b9..862d8501f 100644 --- a/btcstaking/btcstaking_test.go +++ b/btcstaking/btcstaking_test.go @@ -132,22 +132,17 @@ func TestSpendingTimeLockPath(t *testing.T) { require.NoError(t, err) - witness, err := btcstaking.CreateBabylonWitness( - [][]byte{sig.Serialize()}, - si, - ) + witness, err := si.CreateWitness([][]byte{sig.Serialize()}) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, - ) + prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( - stakingInfo.StakingOutput.PkScript, + stakingInfo.GetPkScript(), spendStakeTx, 0, txscript.StandardVerifyFlags, nil, txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, prevOutputFetcher, @@ -255,20 +250,15 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { var witnessSignatures [][]byte witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err := si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, - ) + prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( - stakingInfo.StakingOutput.PkScript, + stakingInfo.GetPkScript(), spendStakeTx, 0, txscript.StandardVerifyFlags, nil, txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, prevOutputFetcher, @@ -300,10 +290,7 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err := si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -375,20 +362,15 @@ func TestSpendingUnbondingPathSingleKeyCovenant(t *testing.T) { var witnessSignatures [][]byte witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err := si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, - ) + prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( - stakingInfo.StakingOutput.PkScript, + stakingInfo.GetPkScript(), spendStakeTx, 0, txscript.StandardVerifyFlags, nil, txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, prevOutputFetcher, @@ -455,19 +437,14 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { var witnessSignatures [][]byte witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err := si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, - ) + prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( - stakingInfo.StakingOutput.PkScript, + stakingInfo.GetPkScript(), spendStakeTx, 0, txscript.StandardVerifyFlags, nil, txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, prevOutputFetcher, @@ -489,10 +466,7 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, validatorSig.Serialize()) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err = btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err = si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -558,19 +532,14 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) var witnessSignatures [][]byte witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err := si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, - ) + prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( - stakingInfo.StakingOutput.PkScript, + stakingInfo.GetPkScript(), spendStakeTx, 0, txscript.StandardVerifyFlags, nil, txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, prevOutputFetcher, @@ -597,10 +566,7 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) witnessSignatures = append(witnessSignatures, covenantSigantures...) witnessSignatures = append(witnessSignatures, validatorsSignatures...) witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err = btcstaking.CreateBabylonWitness( - witnessSignatures, - si, - ) + witness, err = si.CreateWitness(witnessSignatures) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 22bd297f5..4b7f6dabd 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -694,15 +694,13 @@ func EncVerifyTransactionSigWithOutputData( return signature.EncVerify(pubKey, encKey, sigHash) } -// CreateBabylonWitness creates babylon compatible witness, as babylon scripts -// have witness with the same shape +// CreateWitness creates witness for spending the tx corresponding to +// the spend info with the given stack of signatures +// The returned witness stack follows the structure below: // - first come signatures // - then whole revealed script // - then control block -func CreateBabylonWitness( - signatures [][]byte, - si *SpendInfo, -) (wire.TxWitness, error) { +func (si *SpendInfo) CreateWitness(signatures [][]byte) (wire.TxWitness, error) { numSignatures := len(signatures) if numSignatures == 0 { @@ -730,7 +728,7 @@ func CreateBabylonWitness( witnessStack[i] = sc } - witnessStack[numSignatures] = si.RevealedLeaf.Script + witnessStack[numSignatures] = si.GetPkScriptPath() witnessStack[numSignatures+1] = controlBlockBytes return witnessStack, nil diff --git a/btcstaking/types.go b/btcstaking/types.go index 5bf8d9c38..06d95176c 100644 --- a/btcstaking/types.go +++ b/btcstaking/types.go @@ -214,6 +214,18 @@ type StakingInfo struct { slashingPathLeafHash chainhash.Hash } +// GetPkScript returns the full staking taproot pkscript in the corresponding staking tx +func (sti *StakingInfo) GetPkScript() []byte { + return sti.StakingOutput.PkScript +} + +// GetOutputFetcher returns the fetcher of the staking tx's output +func (sti *StakingInfo) GetOutputFetcher() *txscript.CannedPrevOutputFetcher { + return txscript.NewCannedPrevOutputFetcher( + sti.GetPkScript(), sti.StakingOutput.Value, + ) +} + // SpendInfo contains information necessary to create witness for given script type SpendInfo struct { // Control block contains merkle proof of inclusion of revealed script path @@ -223,6 +235,12 @@ type SpendInfo struct { RevealedLeaf txscript.TapLeaf } +// GetPkScriptPath returns the path of the taproot pkscript corresponding +// to the triggered spending condition of the tx associated with the SpendInfo +func (si *SpendInfo) GetPkScriptPath() []byte { + return si.RevealedLeaf.Script +} + func SpendInfoFromRevealedScript( revealedScript []byte, internalKey *btcec.PublicKey, diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index c5f4e08cc..c43cc3de2 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -113,7 +113,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.NoError(err) // generate staking tx and slashing tx stakingTimeBlocks := uint16(math.MaxUint16) - testStakingInfo := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, s.T(), net, @@ -136,7 +136,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { delegatorSig, err := testStakingInfo.SlashingTx.Sign( stakingMsgTx, 0, - stakingSlashingPathInfo.RevealedLeaf.Script, + stakingSlashingPathInfo.GetPkScriptPath(), delBTCSK, ) s.NoError(err) @@ -199,7 +199,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { slashingTx := pendingDel.SlashingTx stakingTx := pendingDel.StakingTx - stakingMsgTx, err := bstypes.ParseBtcTx(stakingTx) + stakingMsgTx, err := bbn.NewBTCTxFromBytes(stakingTx) s.NoError(err) stakingTxHash := stakingMsgTx.TxHash().String() @@ -237,7 +237,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { covenantSig, err := slashingTx.EncSign( stakingMsgTx, pendingDel.StakingOutputIdx, - stakingSlashingPathInfo.RevealedLeaf.Script, + stakingSlashingPathInfo.GetPkScriptPath(), covenantSK, encKey, ) @@ -451,14 +451,14 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) } - stakingMsgTx, err := bstypes.ParseBtcTx(activeDel.StakingTx) + stakingMsgTx, err := bbn.NewBTCTxFromBytes(activeDel.StakingTx) s.NoError(err) stakingTxHash := stakingMsgTx.TxHash().String() stakingTxChainHash, err := chainhash.NewHashFromStr(stakingTxHash) s.NoError(err) fee := int64(1000) - testUnbondingInfo := datagen.GenBTCUnbondingSlashingTx( + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( r, s.T(), net, @@ -482,7 +482,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { slashingTxSig, err := testUnbondingInfo.SlashingTx.Sign( unbondingTxMsg, 0, - unbondingTxSlashingPathInfo.RevealedLeaf.Script, + unbondingTxSlashingPathInfo.GetPkScriptPath(), delBTCSK, ) s.NoError(err) @@ -549,9 +549,9 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { ) s.NoError(err) - unbondingTx, err := bstypes.ParseBtcTx(delegation.BtcUndelegation.UnbondingTx) + unbondingTx, err := bbn.NewBTCTxFromBytes(delegation.BtcUndelegation.UnbondingTx) s.NoError(err) - stakingTx, err := bstypes.ParseBtcTx(delegation.StakingTx) + stakingTx, err := bbn.NewBTCTxFromBytes(delegation.StakingTx) s.NoError(err) stakingTxHash := stakingTx.TxHash().String() @@ -575,7 +575,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { unbondingTx, stakingTx, delegation.StakingOutputIdx, - stakingTxUnbondigPathInfo.RevealedLeaf.Script, + stakingTxUnbondigPathInfo.GetPkScriptPath(), covenantSK, ) s.NoError(err) @@ -592,7 +592,7 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.EncSign( unbondingTx, 0, - unbondingTxSlashingPath.RevealedLeaf.Script, + unbondingTxSlashingPath.GetPkScriptPath(), covenantSK, encKey, ) diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 422071d4b..25c7d1bad 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -152,7 +152,7 @@ func (n *NodeConfig) CreateBTCUndelegation( delegatorSig *bbn.BIP340Signature) { n.LogActionF("creating BTC undelegation") - txBytes, err := bstypes.SerializeBtcTx(unbondingTx) + txBytes, err := bbn.SerializeBTCTx(unbondingTx) require.NoError(n.t, err) // get staking tx hex unbondingTxHex := hex.EncodeToString(txBytes) diff --git a/testutil/bitcoin/utils.go b/testutil/bitcoin/utils.go index 07f6c9999..3654519dc 100644 --- a/testutil/bitcoin/utils.go +++ b/testutil/bitcoin/utils.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) @@ -70,3 +71,21 @@ func AssertEngineExecution(t *testing.T, testNum int, valid bool, t.Log(debugBuf.String()) t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) } + +// AssertSlashingTxExecution asserts that the given tx has a valid witness for spending the given funding output +func AssertSlashingTxExecution(t *testing.T, fundingOutput *wire.TxOut, txWithWitness *wire.MsgTx) { + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(fundingOutput.PkScript, fundingOutput.Value) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + fundingOutput.PkScript, + txWithWitness, + 0, + txscript.StandardVerifyFlags, + nil, + txscript.NewTxSigHashes(txWithWitness, prevOutputFetcher), + fundingOutput.Value, + prevOutputFetcher, + ) + } + AssertEngineExecution(t, 0, true, newEngine) +} diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index af55278ab..4a748e59d 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -111,7 +111,7 @@ func GenRandomBTCDelegation( return nil, err } // staking/slashing tx - testingInfo := GenBTCStakingSlashingTx( + testingInfo := GenBTCStakingSlashingInfo( r, t, net, @@ -127,7 +127,7 @@ func GenRandomBTCDelegation( slashingPathSpendInfo, err := testingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - script := slashingPathSpendInfo.RevealedLeaf.Script + script := slashingPathSpendInfo.GetPkScriptPath() // covenant sig and delegator sig stakingMsgTx := testingInfo.StakingTx @@ -142,7 +142,7 @@ func GenRandomBTCDelegation( } delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, delSK) require.NoError(t, err) - serializedStaking, err := bstypes.SerializeBtcTx(testingInfo.StakingTx) + serializedStaking, err := bbn.SerializeBTCTx(testingInfo.StakingTx) require.NoError(t, err) return &bstypes.BTCDelegation{ @@ -173,7 +173,7 @@ type TestUnbondingSlashingInfo struct { UnbondingInfo *btcstaking.UnbondingInfo } -func GenBTCStakingSlashingTxWithOutPoint( +func GenBTCStakingSlashingInfoWithOutPoint( r *rand.Rand, t *testing.T, btcNet *chaincfg.Params, @@ -235,7 +235,7 @@ func GenBTCStakingSlashingTxWithOutPoint( } } -func GenBTCStakingSlashingTx( +func GenBTCStakingSlashingInfo( r *rand.Rand, t *testing.T, btcNet *chaincfg.Params, @@ -251,7 +251,7 @@ func GenBTCStakingSlashingTx( // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) outPoint := &spend.prevOut - return GenBTCStakingSlashingTxWithOutPoint( + return GenBTCStakingSlashingInfoWithOutPoint( r, t, btcNet, @@ -266,7 +266,7 @@ func GenBTCStakingSlashingTx( slashingRate) } -func GenBTCUnbondingSlashingTx( +func GenBTCUnbondingSlashingInfo( r *rand.Rand, t *testing.T, btcNet *chaincfg.Params, diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index 4745fce45..06d24d3a5 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -114,3 +114,23 @@ func (pk *BIP340PubKey) UnmarshalJSON(bz []byte) error { func (pk *BIP340PubKey) Equals(pk2 *BIP340PubKey) bool { return bytes.Equal(*pk, *pk2) } + +func NewBTCPKsFromBIP340PKs(pks []BIP340PubKey) ([]*btcec.PublicKey, error) { + btcPks := make([]*btcec.PublicKey, 0, len(pks)) + for _, pk := range pks { + btcPk, err := pk.ToBTCPK() + if err != nil { + return nil, err + } + btcPks = append(btcPks, btcPk) + } + return btcPks, nil +} + +func NewBIP340PKsFromBTCPKs(btcPKs []*btcec.PublicKey) []BIP340PubKey { + pks := make([]BIP340PubKey, 0, len(btcPKs)) + for _, btcPK := range btcPKs { + pks = append(pks, *NewBIP340PubKeyFromBTCPK(btcPK)) + } + return pks +} diff --git a/types/btcutils.go b/types/btcutils.go index cb43f8848..a9a8fca66 100644 --- a/types/btcutils.go +++ b/types/btcutils.go @@ -1,6 +1,8 @@ package types import ( + "bytes" + "encoding/hex" "errors" "fmt" "math/big" @@ -49,3 +51,46 @@ func GetMaxDifficulty() big.Int { } return *maxDifficulty } + +func NewBTCTxFromBytes(txBytes []byte) (*wire.MsgTx, error) { + var msgTx wire.MsgTx + rbuf := bytes.NewReader(txBytes) + if err := msgTx.Deserialize(rbuf); err != nil { + return nil, err + } + + return &msgTx, nil +} + +func NewBTCTxFromHex(txHex string) (*wire.MsgTx, []byte, error) { + txBytes, err := hex.DecodeString(txHex) + if err != nil { + return nil, nil, err + } + + parsed, err := NewBTCTxFromBytes(txBytes) + + if err != nil { + return nil, nil, err + } + + return parsed, txBytes, nil +} + +func SerializeBTCTx(tx *wire.MsgTx) ([]byte, error) { + var txBuf bytes.Buffer + if err := tx.Serialize(&txBuf); err != nil { + return nil, err + } + return txBuf.Bytes(), nil +} + +func GetOutputIdxInBTCTx(tx *wire.MsgTx, output *wire.TxOut) (uint32, error) { + for i, txOut := range tx.TxOut { + if bytes.Equal(txOut.PkScript, output.PkScript) && txOut.Value == output.Value { + return uint32(i), nil + } + } + + return 0, fmt.Errorf("output not found") +} diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 977aa3c47..3e042d343 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -332,7 +332,7 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { } // get staking tx - _, unbondingTxBytes, err := types.ParseBtcTxFromHex(args[0]) + _, unbondingTxBytes, err := bbn.NewBTCTxFromHex(args[0]) if err != nil { return err } diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index 301422e25..5ea5c4e7d 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -80,7 +80,7 @@ func (k Keeper) updateBTCDelegation( } // AddCovenantSigsToBTCDelegation adds a given covenant sig to a BTC delegation -// with the given (val PK, del PK, staking tx hash) tuple +// with the given staking tx hash func (k Keeper) AddCovenantSigsToBTCDelegation( ctx context.Context, stakingTxHash string, diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 4e947a3b3..7ef9f1705 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -17,6 +17,7 @@ import ( "github.com/babylonchain/babylon/btcstaking" asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" ) @@ -32,16 +33,8 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} -func mustGetSlashingAddress(params *types.Params, btcParams *chaincfg.Params) btcutil.Address { - slashingAddr, err := btcutil.DecodeAddress(params.SlashingAddress, btcParams) - if err != nil { - panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) - } - return slashingAddr -} - func mustGetStakingTxInfo(del *types.BTCDelegation, params *chaincfg.Params) (*wire.MsgTx, uint32) { - stakingTxMsg, err := types.ParseBtcTx(del.StakingTx) + stakingTxMsg, err := bbn.NewBTCTxFromBytes(del.StakingTx) if err != nil { // failing to get staking output info from a verified staking tx is a programming error @@ -50,84 +43,6 @@ func mustGetStakingTxInfo(del *types.BTCDelegation, params *chaincfg.Params) (*w return stakingTxMsg, del.StakingOutputIdx } -func (ms msgServer) stakingInfoFromDelegation(ctx sdk.Context, del *types.BTCDelegation) (*btcstaking.StakingInfo, error) { - params := ms.GetParams(ctx) - - var validatorKeys []*btcec.PublicKey - for _, valBTCPK := range del.ValBtcPkList { - validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) - } - - var covenantKeys []*btcec.PublicKey - for _, covenantPK := range params.CovenantPks { - covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) - } - - stakerPk := del.BtcPk.MustToBTCPK() - - si, err := btcstaking.BuildStakingInfo( - stakerPk, - validatorKeys, - covenantKeys, - params.CovenantQuorum, - del.GetStakingTime(), - btcutil.Amount(del.TotalSat), - ms.btcNet, - ) - - if err != nil { - return nil, err - } - - return si, nil -} - -func (ms msgServer) mustStakingInfoFromDelegation( - ctx sdk.Context, del *types.BTCDelegation) *btcstaking.StakingInfo { - si, err := ms.stakingInfoFromDelegation(ctx, del) - if err != nil { - panic(err) - } - return si -} - -func (ms msgServer) mustUnbondingInfo( - ctx sdk.Context, - del *types.BTCDelegation, - unbondingTime uint16, - unbondingValue btcutil.Amount, -) *btcstaking.UnbondingInfo { - params := ms.GetParams(ctx) - - var validatorKeys []*btcec.PublicKey - for _, valBTCPK := range del.ValBtcPkList { - validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) - } - - var covenantKeys []*btcec.PublicKey - for _, covenantPK := range params.CovenantPks { - covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) - } - - stakerPk := del.BtcPk.MustToBTCPK() - - si, err := btcstaking.BuildUnbondingInfo( - stakerPk, - validatorKeys, - covenantKeys, - params.CovenantQuorum, - unbondingTime, - unbondingValue, - ms.btcNet, - ) - - if err != nil { - panic("failed to build unbonding info") - } - - return si -} - // UpdateParams updates the params func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if ms.authority != req.Authority { @@ -211,7 +126,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } // 5. Parse staking tx - stakingMsgTx, err := types.ParseBtcTx(req.StakingTx.Transaction) + stakingMsgTx, err := bbn.NewBTCTxFromBytes(req.StakingTx.Transaction) if err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("cannot be parsed: %v", err) @@ -256,7 +171,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidStakingTx.Wrapf("err: %v", err) } - stakingOutputIdx, err := types.GetOutputIdx(stakingMsgTx, si.StakingOutput) + stakingOutputIdx, err := bbn.GetOutputIdxInBTCTx(stakingMsgTx, si.StakingOutput) if err != nil { return nil, types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output") @@ -325,7 +240,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre err = req.SlashingTx.VerifySignature( stakingOutput.PkScript, stakingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), req.BtcPk.MustToBTCPK(), req.DelegatorSig, ) @@ -369,7 +284,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) params := ms.GetParams(ctx) - slashingAddress := mustGetSlashingAddress(¶ms, ms.btcNet) + slashingAddress := params.MustGetSlashingAddress(ms.btcNet) wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout // 1. deserialize provided transactions @@ -378,7 +293,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - unbondingMsgTx, err := types.ParseBtcTx(req.UnbondingTx) + unbondingMsgTx, err := bbn.NewBTCTxFromBytes(req.UnbondingTx) if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } @@ -447,7 +362,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } - unbondingOutputIdx, err := types.GetOutputIdx(unbondingMsgTx, si.UnbondingOutput) + unbondingOutputIdx, err := bbn.GetOutputIdxInBTCTx(unbondingMsgTx, si.UnbondingOutput) if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not contain expected unbonding output") @@ -481,7 +396,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele err = req.SlashingTx.VerifySignature( unbondingOutput.PkScript, unbondingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), del.BtcPk.MustToBTCPK(), req.DelegatorSlashingSig, ) @@ -570,11 +485,12 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven } // ensure that the given covenant PK is in the parameter - if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { + params := ms.GetParams(ctx) + if !params.HasCovenantPK(req.Pk) { return nil, types.ErrInvalidCovenantPK.Wrapf("covenant pk: %s", req.Pk.MarshalHex()) } - spendInfo, err := ms.stakingInfoFromDelegation(ctx, btcDel) + spendInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) if err != nil { panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) } @@ -592,7 +508,7 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven btcDel.SlashingTx, stakingOutput.PkScript, stakingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), btcDel.ValBtcPkList[i].MustToBTCPK(), sig, @@ -684,11 +600,12 @@ func (ms msgServer) AddCovenantUnbondingSigs( stakingOutput := stakingTx.TxOut[stakingOutputIdx] // ensure that the given covenant PK is in the parameter - if !ms.GetParams(ctx).HasCovenantPK(req.Pk) { + params := ms.GetParams(ctx) + if !params.HasCovenantPK(req.Pk) { return nil, types.ErrInvalidCovenantPK } - unbondingTxMsg, err := types.ParseBtcTx(btcDel.BtcUndelegation.UnbondingTx) + unbondingTxMsg, err := bbn.NewBTCTxFromBytes(btcDel.BtcUndelegation.UnbondingTx) if err != nil { panic(fmt.Errorf("failed to parse unbonding tx from existing delegation with hash %s : %v", req.StakingTxHash, err)) @@ -696,7 +613,10 @@ func (ms msgServer) AddCovenantUnbondingSigs( unbondingTxHash := unbondingTxMsg.TxHash().String() - stakingOutputSpendInfo := ms.mustStakingInfoFromDelegation(ctx, btcDel) + stakingOutputSpendInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) + if err != nil { + panic(err) + } unbondingPathInfo, err := stakingOutputSpendInfo.UnbondingPathSpendInfo() @@ -710,7 +630,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( unbondingTxMsg, stakingOutput.PkScript, stakingOutput.Value, - unbondingPathInfo.RevealedLeaf.Script, + unbondingPathInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), *req.UnbondingTxSig, ); err != nil { @@ -720,14 +640,12 @@ func (ms msgServer) AddCovenantUnbondingSigs( // 5. Verify signature of slashing tx against unbonding tx output // unbonding tx always have only one output unbondingOutput := unbondingTxMsg.TxOut[0] - unbondingOutputSpendInfo := ms.mustUnbondingInfo( - ctx, - btcDel, - uint16(btcDel.BtcUndelegation.UnbondingTime), - btcutil.Amount(unbondingTxMsg.TxOut[0].Value), - ) + unbondingInfo, err := btcDel.GetUnbondingInfo(¶ms, ms.btcNet) + if err != nil { + panic(err) + } - slashingPathInfo, err := unbondingOutputSpendInfo.SlashingPathSpendInfo() + slashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() if err != nil { // our unbonding info was constructed by using BuildStakingInfo constructor, so if @@ -741,7 +659,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( btcDel.BtcUndelegation.SlashingTx, unbondingOutput.PkScript, unbondingOutput.Value, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), btcDel.ValBtcPkList[i].MustToBTCPK(), sig, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 11b61dff4..639f91f65 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -13,7 +13,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" @@ -157,7 +156,7 @@ func createDelegation( stakingTimeBlocks := stakingTime stakingValue := int64(2 * 10e8) - testStakingInfo := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, t, net, @@ -185,7 +184,7 @@ func createDelegation( prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, testStakingInfo.StakingTx) btcHeader := btcHeaderWithProof.HeaderBytes - serializedStakingTx, err := types.SerializeBtcTx(testStakingInfo.StakingTx) + serializedStakingTx, err := bbn.SerializeBTCTx(testStakingInfo.StakingTx) require.NoError(t, err) txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, btcHeaderWithProof.SpvProof.MerkleNodes) @@ -203,7 +202,7 @@ func createDelegation( delegatorSig, err := testStakingInfo.SlashingTx.Sign( testStakingInfo.StakingTx, 0, - slashignSpendInfo.RevealedLeaf.Script, + slashignSpendInfo.GetPkScriptPath(), delSK, ) require.NoError(t, err) @@ -241,7 +240,7 @@ func createCovenantSig( msgCreateBTCDel *types.MsgCreateBTCDelegation, delegation *types.BTCDelegation, ) { - stakingTx, err := types.ParseBtcTx(delegation.StakingTx) + stakingTx, err := bbn.NewBTCTxFromBytes(delegation.StakingTx) require.NoError(t, err) stakingTxHash := stakingTx.TxHash().String() @@ -268,7 +267,7 @@ func createCovenantSig( covenantSig, err := msgCreateBTCDel.SlashingTx.EncSign( stakingTx, 0, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), covenantSK, encKey, ) @@ -324,14 +323,13 @@ func createUndelegation( net *chaincfg.Params, btclcKeeper *types.MockBTCLightClientKeeper, actualDel *types.BTCDelegation, - stakingTxHash string, delSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, covenantPK *btcec.PublicKey, slashingAddress, changeAddress string, slashingRate sdkmath.LegacyDec, ) *types.MsgBTCUndelegate { - stkTxHash, err := chainhash.NewHashFromStr(stakingTxHash) + stkTxHash, err := actualDel.GetStakingTxHash() require.NoError(t, err) stkOutputIdx := uint32(0) defaultParams := btcctypes.DefaultParams() @@ -339,7 +337,7 @@ func createUndelegation( unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 unbondingValue := int64(actualDel.TotalSat) - 1000 - testUnbondingInfo := datagen.GenBTCUnbondingSlashingTx( + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( r, t, net, @@ -347,7 +345,7 @@ func createUndelegation( []*btcec.PublicKey{validatorPK}, []*btcec.PublicKey{covenantPK}, 1, - wire.NewOutPoint(stkTxHash, stkOutputIdx), + wire.NewOutPoint(&stkTxHash, stkOutputIdx), unbondingTime, unbondingValue, slashingAddress, changeAddress, @@ -364,12 +362,12 @@ func createUndelegation( sig, err := testUnbondingInfo.SlashingTx.Sign( unbondingTxMsg, 0, - unbondingSlashingPathInfo.RevealedLeaf.Script, + unbondingSlashingPathInfo.GetPkScriptPath(), delSK, ) require.NoError(t, err) - serializedUnbondingTx, err := types.SerializeBtcTx(testUnbondingInfo.UnbondingTx) + serializedUnbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) require.NoError(t, err) msg := &types.MsgBTCUndelegate{ @@ -486,7 +484,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { require.NoError(t, err) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) - testStakingInfo := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, t, net, @@ -501,7 +499,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { ) // get msgTx stakingMsgTx := testStakingInfo.StakingTx - serializedStakingTx, err := types.SerializeBtcTx(stakingMsgTx) + serializedStakingTx, err := bbn.SerializeBTCTx(stakingMsgTx) require.NoError(t, err) // random signer signer := datagen.GenRandomAccount().Address @@ -528,7 +526,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { delegatorSig, err := testStakingInfo.SlashingTx.Sign( stakingMsgTx, 0, - slashingPathInfo.RevealedLeaf.Script, + slashingPathInfo.GetPkScriptPath(), delSK, ) require.NoError(t, err) @@ -595,7 +593,6 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { net, btclcKeeper, actualDel, - stakingTxHash, delSK, validatorPK, covenantPK, @@ -660,7 +657,6 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { net, btclcKeeper, actualDel, - stakingTxHash, delSK, validatorPK, covenantPK, @@ -672,10 +668,10 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { require.NoError(t, err) require.NotNil(t, del.BtcUndelegation) - stakingTx, err := types.ParseBtcTx(del.StakingTx) + stakingTx, err := bbn.NewBTCTxFromBytes(del.StakingTx) require.NoError(t, err) - unbondingTx, err := types.ParseBtcTx(del.BtcUndelegation.UnbondingTx) + unbondingTx, err := bbn.NewBTCTxFromBytes(del.BtcUndelegation.UnbondingTx) require.NoError(t, err) // Check sending covenant signatures @@ -698,7 +694,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { unbondingTx, stakingTx, del.StakingOutputIdx, - stakingUnbondingPathInfo.RevealedLeaf.Script, + stakingUnbondingPathInfo.GetPkScriptPath(), covenantSK, ) @@ -725,7 +721,7 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.EncSign( unbondingTx, 0, - unbondingSlashingPathInfo.RevealedLeaf.Script, + unbondingSlashingPathInfo.GetPkScriptPath(), covenantSK, enckey, ) diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go new file mode 100644 index 000000000..7b20d50d6 --- /dev/null +++ b/x/btcstaking/types/btc_delegation.go @@ -0,0 +1,371 @@ +package types + +import ( + "bytes" + "fmt" + math "math" + + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, error) { + switch statusStr { + case "pending": + return BTCDelegationStatus_PENDING, nil + case "active": + return BTCDelegationStatus_ACTIVE, nil + case "unbonding": + return BTCDelegationStatus_UNBONDING, nil + case "unbonded": + return BTCDelegationStatus_UNBONDED, nil + case "any": + return BTCDelegationStatus_ANY, nil + default: + return -1, fmt.Errorf("invalid status string; should be one of {pending, active, unbonding, unbonded, any}") + } +} + +func (d *BTCDelegation) GetStakingTime() uint16 { + diff := d.EndHeight - d.StartHeight + + if diff > math.MaxUint16 { + // In valid delegation, EndHeight is always greater than StartHeight and it is always uint16 value + panic("invalid delegation in database") + } + + return uint16(diff) +} + +// GetStatus returns the status of the BTC Delegation based on a BTC height and a w value +// TODO: Given that we only accept delegations that can be activated immediately, +// we can only have expired delegations. If we accept optimistic submissions, +// we could also have delegations that are in the waiting, so we need an extra status. +// This is covered by expired for now as it is the default value. +// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig +// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig +// Expired: Delegation timelock +func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64, covenantQuorum uint32) BTCDelegationStatus { + if d.BtcUndelegation != nil { + if d.BtcUndelegation.HasAllSignatures(covenantQuorum) { + return BTCDelegationStatus_UNBONDED + } + // If we received an undelegation but is still does not have all required signature, + // delegation receives UNBONING status. + // Voting power from this delegation is removed from the total voting power and now we + // are waiting for signatures from validator and covenant for delegation to become expired. + // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain + // we may consider adding unbonding time on Babylon chain later to avoid situation where + // we can lose to much voting power in to short time. + return BTCDelegationStatus_UNBONDING + } + + if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { + if d.HasCovenantQuorum(covenantQuorum) { + return BTCDelegationStatus_ACTIVE + } else { + return BTCDelegationStatus_PENDING + } + } + return BTCDelegationStatus_UNBONDED +} + +// VotingPower returns the voting power of the BTC delegation at a given BTC height +// and a given w value. +// The BTC delegation d has voting power iff it is active. +func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { + if d.GetStatus(btcHeight, w, covenantQuorum) != BTCDelegationStatus_ACTIVE { + return 0 + } + return d.GetTotalSat() +} + +func (d *BTCDelegation) GetStakingTxHash() (chainhash.Hash, error) { + parsed, err := bbn.NewBTCTxFromBytes(d.StakingTx) + + if err != nil { + return chainhash.Hash{}, err + } + + return parsed.TxHash(), nil +} + +func (d *BTCDelegation) MustGetStakingTxHash() chainhash.Hash { + txHash, err := d.GetStakingTxHash() + + if err != nil { + panic(err) + } + + return txHash +} + +func (d *BTCDelegation) ValidateBasic() error { + if d.BabylonPk == nil { + return fmt.Errorf("empty Babylon public key") + } + if d.BtcPk == nil { + return fmt.Errorf("empty BTC public key") + } + if d.Pop == nil { + return fmt.Errorf("empty proof of possession") + } + if len(d.ValBtcPkList) == 0 { + return fmt.Errorf("empty list of BTC validator PKs") + } + if ExistsDup(d.ValBtcPkList) { + return fmt.Errorf("list of BTC validator PKs has duplication") + } + if d.StakingTx == nil { + return fmt.Errorf("empty staking tx") + } + if d.SlashingTx == nil { + return fmt.Errorf("empty slashing tx") + } + if d.DelegatorSig == nil { + return fmt.Errorf("empty delegator signature") + } + + // ensure staking tx is correctly formatted + if _, err := bbn.NewBTCTxFromBytes(d.StakingTx); err != nil { + return err + } + if err := d.Pop.ValidateBasic(); err != nil { + return err + } + + return nil +} + +// HasCovenantQuorum returns whether a BTC delegation has sufficient sigs +// from Covenant members to make a quorum +func (d *BTCDelegation) HasCovenantQuorum(quorum uint32) bool { + return uint32(len(d.CovenantSigs)) >= quorum +} + +// IsSignedByCovMember checks whether the given covenant PK has signed the delegation +func (d *BTCDelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { + for _, sigInfo := range d.CovenantSigs { + if covPk.Equals(sigInfo.CovPk) { + return true + } + } + + return false +} + +// AddCovenantSigs adds signatures on the slashing tx from the given +// covenant, where each signature is an adaptor signature encrypted by +// each BTC validator's PK this BTC delegation restakes to +func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.AdaptorSignature, quorum uint32) error { + // we can ignore the covenant sig if quorum is already reached + if d.HasCovenantQuorum(quorum) { + return nil + } + // ensure that this covenant member has not signed the delegation yet + if d.IsSignedByCovMember(covPk) { + return ErrDuplicatedCovenantSig + } + + adaptorSigs := make([][]byte, 0, len(sigs)) + for _, s := range sigs { + adaptorSigs = append(adaptorSigs, s.MustMarshal()) + } + covSigs := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} + + d.CovenantSigs = append(d.CovenantSigs, covSigs) + + return nil +} + +// GetStakingInfo returns the staking info of the BTC delegation +// the staking info can be used for constructing witness of slashing tx +// with access to a BTC validator's SK +func (d *BTCDelegation) GetStakingInfo(bsParams *Params, btcNet *chaincfg.Params) (*btcstaking.StakingInfo, error) { + valBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.ValBtcPkList) + if err != nil { + return nil, fmt.Errorf("failed to convert validator pks to BTC pks %v", err) + } + covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) + if err != nil { + return nil, fmt.Errorf("failed to convert covenant pks to BTC pks %v", err) + } + stakingInfo, err := btcstaking.BuildStakingInfo( + d.BtcPk.MustToBTCPK(), + valBtcPkList, + covenantBtcPkList, + bsParams.CovenantQuorum, + d.GetStakingTime(), + btcutil.Amount(d.TotalSat), + btcNet, + ) + if err != nil { + return nil, fmt.Errorf("could not create BTC staking info: %v", err) + } + return stakingInfo, nil +} + +// GetUnbondingInfo returns the unbonding info of the BTC delegation +// the unbonding info can be used for constructing witness of unbonding slashing +// tx with access to a BTC validator's SK +func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Params) (*btcstaking.UnbondingInfo, error) { + valBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.ValBtcPkList) + if err != nil { + return nil, fmt.Errorf("failed to convert validator pks to BTC pks: %v", err) + } + + covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) + if err != nil { + return nil, fmt.Errorf("failed to convert covenant pks to BTC pks: %v", err) + } + unbondingTx, err := bbn.NewBTCTxFromBytes(d.BtcUndelegation.UnbondingTx) + if err != nil { + return nil, fmt.Errorf("failed to parse unbonding transaction: %v", err) + } + + unbondingInfo, err := btcstaking.BuildUnbondingInfo( + d.BtcPk.MustToBTCPK(), + valBtcPkList, + covenantBtcPkList, + bsParams.CovenantQuorum, + uint16(d.BtcUndelegation.GetUnbondingTime()), + btcutil.Amount(unbondingTx.TxOut[0].Value), + btcNet, + ) + if err != nil { + return nil, fmt.Errorf("could not create BTC staking info: %v", err) + } + + return unbondingInfo, nil +} + +func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, valSK *btcec.PrivateKey) (*wire.MsgTx, error) { + stakingMsgTx, err := bbn.NewBTCTxFromBytes(d.StakingTx) + if err != nil { + return nil, fmt.Errorf("failed to convert a Babylon staking tx to wire.MsgTx: %w", err) + } + + // get staking info + stakingInfo, err := d.GetStakingInfo(bsParams, btcNet) + if err != nil { + return nil, fmt.Errorf("could not create BTC staking info: %v", err) + } + slashingSpendInfo, err := stakingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, fmt.Errorf("could not get slashing spend info: %v", err) + } + + // TODO: work with restaking + // TODO: work with covenant committee + covAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(d.CovenantSigs[0].AdaptorSigs[0]) + if err != nil { + return nil, fmt.Errorf("failed to decode a covenant adaptor signature: %w", err) + } + + // assemble witness for slashing tx + slashingMsgTxWithWitness, err := d.SlashingTx.BuildSlashingTxWithWitness( + valSK, + stakingMsgTx, + d.StakingOutputIdx, + d.DelegatorSig, + covAdaptorSig, + slashingSpendInfo, + ) + if err != nil { + return nil, fmt.Errorf( + "failed to build witness for BTC delegation of %s under BTC validator %s: %v", + d.BtcPk.MarshalHex(), + bbn.NewBIP340PubKeyFromBTCPK(valSK.PubKey()).MarshalHex(), + err, + ) + } + + return slashingMsgTxWithWitness, nil +} + +func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, valSK *btcec.PrivateKey) (*wire.MsgTx, error) { + unbondingMsgTx, err := bbn.NewBTCTxFromBytes(d.BtcUndelegation.UnbondingTx) + if err != nil { + return nil, fmt.Errorf("failed to convert a Babylon unbonding tx to wire.MsgTx: %w", err) + } + + // get unbonding info + unbondingInfo, err := d.GetUnbondingInfo(bsParams, btcNet) + if err != nil { + return nil, fmt.Errorf("could not create BTC unbonding info: %v", err) + } + slashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, fmt.Errorf("could not get unbonding slashing spend info: %v", err) + } + + // TODO: work with restaking + // TODO: work with covenant committee + covAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(d.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs[0]) + if err != nil { + return nil, fmt.Errorf("failed to decode a covenant adaptor signature: %w", err) + } + + // assemble witness for unbonding slashing tx + slashingMsgTxWithWitness, err := d.BtcUndelegation.SlashingTx.BuildSlashingTxWithWitness( + valSK, + unbondingMsgTx, + 0, + d.BtcUndelegation.DelegatorSlashingSig, + covAdaptorSig, // TODO: accomodate covenant committee + slashingSpendInfo, + ) + if err != nil { + return nil, fmt.Errorf( + "failed to build witness for unbonding BTC delegation %s under BTC validator %s: %v", + d.BtcPk.MarshalHex(), + bbn.NewBIP340PubKeyFromBTCPK(valSK.PubKey()).MarshalHex(), + err, + ) + } + + return slashingMsgTxWithWitness, nil +} + +func NewBTCDelegatorDelegationIndex() *BTCDelegatorDelegationIndex { + return &BTCDelegatorDelegationIndex{ + StakingTxHashList: [][]byte{}, + } +} + +func (i *BTCDelegatorDelegationIndex) Has(stakingTxHash chainhash.Hash) bool { + for _, hash := range i.StakingTxHashList { + if bytes.Equal(stakingTxHash[:], hash) { + return true + } + } + return false +} + +func (i *BTCDelegatorDelegationIndex) Add(stakingTxHash chainhash.Hash) error { + // ensure staking tx hash is not duplicated + for _, hash := range i.StakingTxHashList { + if bytes.Equal(stakingTxHash[:], hash) { + return fmt.Errorf("the given stakingTxHash %s is duplicated", stakingTxHash.String()) + } + } + // add + i.StakingTxHashList = append(i.StakingTxHashList, stakingTxHash[:]) + + return nil +} + +// VotingPower calculates the total voting power of all BTC delegations +func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { + power := uint64(0) + for _, del := range dels.Dels { + power += del.VotingPower(btcHeight, w, covenantQuorum) + } + return power +} diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btc_delegation_test.go similarity index 55% rename from x/btcstaking/types/btcstaking_test.go rename to x/btcstaking/types/btc_delegation_test.go index e297eaa39..2c80bc961 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -5,60 +5,17 @@ import ( "testing" sdkmath "cosmossdk.io/math" - + bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/require" asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" "github.com/babylonchain/babylon/x/btcstaking/types" ) -func FuzzStakingTx(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - net := &chaincfg.SimNetParams - - stakerSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - - stakingTimeBlocks := uint16(5) - stakingValue := int64(2 * 10e8) - slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) - // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. - // NOTE - if the rate is higher or lower, it may produce slashing or change outputs - // with value below the dust threshold, causing test failure. - // Our goal is not to test failure due to such extreme cases here; - // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - testInfo := datagen.GenBTCStakingSlashingTx( - r, - t, - net, - stakerSK, - []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, - stakingTimeBlocks, - stakingValue, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - slashingRate, - ) - require.NoError(t, err) - require.Equal(t, testInfo.StakingInfo.StakingOutput.Value, stakingValue) - }) -} - func FuzzBTCDelegation(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 100) @@ -106,3 +63,99 @@ func FuzzBTCDelegation(f *testing.F) { } }) } + +func FuzzBTCDelegation_SlashingTx(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams + + delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) + + valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + valPKList := []*btcec.PublicKey{valPK} + + covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + covPKList := []*btcec.PublicKey{covenantPK} + + stakingTimeBlocks := uint16(5) + stakingValue := int64(2 * 10e8) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. + // NOTE - if the rate is higher or lower, it may produce slashing or change outputs + // with value below the dust threshold, causing test failure. + // Our goal is not to test failure due to such extreme cases here; + // this is already covered in FuzzGeneratingValidStakingSlashingTx + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + testInfo := datagen.GenBTCStakingSlashingInfo( + r, + t, + net, + delSK, + valPKList, + covPKList, + 1, + stakingTimeBlocks, + stakingValue, + slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingRate, + ) + require.NoError(t, err) + + stakingTxBytes, err := bbn.SerializeBTCTx(testInfo.StakingTx) + require.NoError(t, err) + + // spend info of the slashing tx + slashingSpendInfo, err := testInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + // delegator signs the slashing tx + delSig, err := testInfo.SlashingTx.Sign(testInfo.StakingTx, 0, slashingSpendInfo.GetPkScriptPath(), delSK) + require.NoError(t, err) + // covenant signs (using adaptor signature) the slashing tx + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + require.NoError(t, err) + covenantSig, err := testInfo.SlashingTx.EncSign(testInfo.StakingTx, 0, slashingSpendInfo.GetPkScriptPath(), covenantSK, encKey) + require.NoError(t, err) + covenantSigs := &types.CovenantAdaptorSignatures{ + CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, + } + + // construct the BTC delegation with everything + btcDel := &types.BTCDelegation{ + BabylonPk: nil, // not relevant here + BtcPk: delBTCPK, + Pop: nil, // not relevant here + ValBtcPkList: bbn.NewBIP340PKsFromBTCPKs(valPKList), + StartHeight: 1000, // not relevant here + EndHeight: uint64(1000 + stakingTimeBlocks), + TotalSat: uint64(stakingValue), + StakingTx: stakingTxBytes, + StakingOutputIdx: 0, + SlashingTx: testInfo.SlashingTx, + DelegatorSig: delSig, + CovenantSigs: []*types.CovenantAdaptorSignatures{covenantSigs}, + } + + bsParams := &types.Params{ + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covPKList), + CovenantQuorum: 1, + } + btcNet := &chaincfg.SimNetParams + + // build slashing tx with witness for spending the staking tx + slashingTxWithWitness, err := btcDel.BuildSlashingTxWithWitness(bsParams, btcNet, valSK) + require.NoError(t, err) + + // assert execution + btctest.AssertSlashingTxExecution(t, testInfo.StakingInfo.StakingOutput, slashingTxWithWitness) + }) +} diff --git a/x/btcstaking/types/btc_delegations.go b/x/btcstaking/types/btc_delegations.go deleted file mode 100644 index 0fe8b3f3f..000000000 --- a/x/btcstaking/types/btc_delegations.go +++ /dev/null @@ -1,45 +0,0 @@ -package types - -import ( - "bytes" - "fmt" - - "github.com/btcsuite/btcd/chaincfg/chainhash" -) - -func NewBTCDelegatorDelegationIndex() *BTCDelegatorDelegationIndex { - return &BTCDelegatorDelegationIndex{ - StakingTxHashList: [][]byte{}, - } -} - -func (i *BTCDelegatorDelegationIndex) Has(stakingTxHash chainhash.Hash) bool { - for _, hash := range i.StakingTxHashList { - if bytes.Equal(stakingTxHash[:], hash) { - return true - } - } - return false -} - -func (i *BTCDelegatorDelegationIndex) Add(stakingTxHash chainhash.Hash) error { - // ensure staking tx hash is not duplicated - for _, hash := range i.StakingTxHashList { - if bytes.Equal(stakingTxHash[:], hash) { - return fmt.Errorf("the given stakingTxHash %s is duplicated", stakingTxHash.String()) - } - } - // add - i.StakingTxHashList = append(i.StakingTxHashList, stakingTxHash[:]) - - return nil -} - -// VotingPower calculates the total voting power of all BTC delegations -func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { - power := uint64(0) - for _, del := range dels.Dels { - power += del.VotingPower(btcHeight, w, covenantQuorum) - } - return power -} diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index f935ae2e3..33a0df8c1 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -3,6 +3,7 @@ package types import ( "bytes" "encoding/hex" + "fmt" sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" @@ -12,7 +13,7 @@ import ( "github.com/babylonchain/babylon/btcstaking" asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" - "github.com/babylonchain/babylon/types" + bbn "github.com/babylonchain/babylon/types" ) type BTCSlashingTx []byte @@ -82,7 +83,7 @@ func (tx *BTCSlashingTx) ToHexStr() string { } func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { - return ParseBtcTx(*tx) + return bbn.NewBTCTxFromBytes(*tx) } func (tx *BTCSlashingTx) Validate( @@ -113,7 +114,7 @@ func (tx *BTCSlashingTx) Sign( spendOutputIndex uint32, scriptPath []byte, sk *btcec.PrivateKey, -) (*types.BIP340Signature, error) { +) (*bbn.BIP340Signature, error) { msgTx, err := tx.ToMsgTx() if err != nil { return nil, err @@ -128,7 +129,7 @@ func (tx *BTCSlashingTx) Sign( if err != nil { return nil, err } - sig := types.NewBIP340SignatureFromBTCSig(schnorrSig) + sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) return &sig, nil } @@ -138,7 +139,7 @@ func (tx *BTCSlashingTx) VerifySignature( stakingAmount int64, stakingScript []byte, pk *btcec.PublicKey, - sig *types.BIP340Signature) error { + sig *bbn.BIP340Signature) error { msgTx, err := tx.ToMsgTx() if err != nil { return err @@ -205,3 +206,51 @@ func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( sig, ) } + +func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( + valSK *btcec.PrivateKey, + fundingMsgTx *wire.MsgTx, + outputIdx uint32, + delegatorSig *bbn.BIP340Signature, + covenantSig *asig.AdaptorSignature, + slashingPathSpendInfo *btcstaking.SpendInfo, +) (*wire.MsgTx, error) { + valSig, err := tx.Sign(fundingMsgTx, outputIdx, slashingPathSpendInfo.GetPkScriptPath(), valSK) + if err != nil { + return nil, fmt.Errorf("failed to sign slashing tx for the BTC validator: %w", err) + } + + stakerSigBytes := delegatorSig.MustMarshal() + validatorSigBytes := valSig.MustMarshal() + + // decrypt covenant adaptor signature to Schnorr signature using validator's SK, + // then marshal + // TODO: work with restaking + // TODO: use covenant committee + decKey, err := asig.NewDecyptionKeyFromBTCSK(valSK) + if err != nil { + return nil, fmt.Errorf("failed to get decryption key from BTC SK: %w", err) + } + covSig := covenantSig.Decrypt(decKey) + covSigBytes := bbn.NewBIP340SignatureFromBTCSig(covSig).MustMarshal() + + // construct witness + witness, err := slashingPathSpendInfo.CreateWitness( + [][]byte{ + covSigBytes, + validatorSigBytes, + stakerSigBytes, + }, + ) + if err != nil { + return nil, err + } + // add witness to slashing tx + slashingMsgTxWithWitness, err := tx.ToMsgTx() + if err != nil { + return nil, err + } + slashingMsgTxWithWitness.TxIn[0].Witness = witness + + return slashingMsgTxWithWitness, nil +} diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 883190da2..0e6ff6b53 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -5,16 +5,12 @@ import ( "testing" sdkmath "cosmossdk.io/math" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/stretchr/testify/require" - - "github.com/babylonchain/babylon/btcstaking" asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/stretchr/testify/require" ) func FuzzSlashingTxWithWitness(f *testing.F) { @@ -47,7 +43,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) // generate staking/slashing tx - testStakingInfo := datagen.GenBTCStakingSlashingTx( + testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, t, net, @@ -63,11 +59,11 @@ func FuzzSlashingTxWithWitness(f *testing.F) { slashingTx := testStakingInfo.SlashingTx stakingMsgTx := testStakingInfo.StakingTx - stakingPkScript := testStakingInfo.StakingInfo.StakingOutput.PkScript + stakingPkScript := testStakingInfo.StakingInfo.GetPkScript() - slashingScriptInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - slashingScript := slashingScriptInfo.RevealedLeaf.Script + slashingScript := slashingSpendInfo.GetPkScriptPath() // sign slashing tx valSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, valSK) @@ -87,39 +83,11 @@ func FuzzSlashingTxWithWitness(f *testing.F) { err = slashingTx.EncVerifyAdaptorSignature(stakingPkScript, stakingValue, slashingScript, covenantPK, enckey, covenantSig) require.NoError(t, err) - stakerSigBytes := delSig.MustMarshal() - validatorSigBytes := valSig.MustMarshal() - decryptKey, err := asig.NewDecyptionKeyFromBTCSK(valSK) - require.NoError(t, err) - covSigDecryptedBytes := covenantSig.Decrypt(decryptKey).Serialize() - - // TODO: use comittee - witness, err := btcstaking.CreateBabylonWitness( - [][]byte{ - covSigDecryptedBytes, - validatorSigBytes, - stakerSigBytes, - }, - slashingScriptInfo, - ) + // create slashing tx with witness + slashingMsgTxWithWitness, err := slashingTx.BuildSlashingTxWithWitness(valSK, stakingMsgTx, 0, delSig, covenantSig, slashingSpendInfo) require.NoError(t, err) - slashingMsgTxWithWitness, err := slashingTx.ToMsgTx() - require.NoError(t, err) - - slashingMsgTxWithWitness.TxIn[0].Witness = witness // verify slashing tx with witness - prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( - stakingPkScript, stakingValue, - ) - newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine( - stakingPkScript, - slashingMsgTxWithWitness, 0, txscript.StandardVerifyFlags, nil, - txscript.NewTxSigHashes(slashingMsgTxWithWitness, prevOutputFetcher), stakingValue, - prevOutputFetcher, - ) - } - btctest.AssertEngineExecution(t, 0, true, newEngine) + btctest.AssertSlashingTxExecution(t, testStakingInfo.StakingInfo.StakingOutput, slashingMsgTxWithWitness) }) } diff --git a/x/btcstaking/types/btc_undelegation.go b/x/btcstaking/types/btc_undelegation.go new file mode 100644 index 000000000..6363a79c4 --- /dev/null +++ b/x/btcstaking/types/btc_undelegation.go @@ -0,0 +1,74 @@ +package types + +import ( + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" +) + +func (ud *BTCUndelegation) HasCovenantQuorumOnSlashing(quorum uint32) bool { + return len(ud.CovenantUnbondingSigList) >= int(quorum) +} + +func (ud *BTCUndelegation) HasCovenantQuorumOnUnbonding(quorum uint32) bool { + return len(ud.CovenantUnbondingSigList) >= int(quorum) +} + +// IsSignedByCovMemberOnUnbonding checks whether the given covenant PK has signed the unbonding tx +func (ud *BTCUndelegation) IsSignedByCovMemberOnUnbonding(covPK *bbn.BIP340PubKey) bool { + for _, sigInfo := range ud.CovenantUnbondingSigList { + if sigInfo.Pk.Equals(covPK) { + return true + } + } + return false +} + +// IsSignedByCovMemberOnSlashing checks whether the given covenant PK has signed the slashing tx +func (ud *BTCUndelegation) IsSignedByCovMemberOnSlashing(covPK *bbn.BIP340PubKey) bool { + for _, sigInfo := range ud.CovenantSlashingSigs { + if sigInfo.CovPk.Equals(covPK) { + return true + } + } + return false +} + +func (ud *BTCUndelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { + return ud.IsSignedByCovMemberOnUnbonding(covPk) && ud.IsSignedByCovMemberOnSlashing(covPk) +} + +func (ud *BTCUndelegation) HasAllSignatures(covenantQuorum uint32) bool { + return ud.HasCovenantQuorumOnUnbonding(covenantQuorum) && ud.HasCovenantQuorumOnSlashing(covenantQuorum) +} + +// AddCovenantSigs adds a Schnorr signature on the unbonding tx, and +// a list of adaptor signatures on the unbonding slashing tx, each encrypted +// by a BTC validator's PK this BTC delegation restakes to, from the given +// covenant +func (ud *BTCUndelegation) AddCovenantSigs( + covPk *bbn.BIP340PubKey, + unbondingSig *bbn.BIP340Signature, + slashingSigs []asig.AdaptorSignature, + quorum uint32, +) error { + // we can ignore the covenant slashing sig if quorum is already reached + if ud.HasAllSignatures(quorum) { + return nil + } + + if ud.IsSignedByCovMember(covPk) { + return ErrDuplicatedCovenantSig + } + + covUnbondingSigInfo := &SignatureInfo{Pk: covPk, Sig: unbondingSig} + ud.CovenantUnbondingSigList = append(ud.CovenantUnbondingSigList, covUnbondingSigInfo) + + adaptorSigs := make([][]byte, 0, len(slashingSigs)) + for _, s := range slashingSigs { + adaptorSigs = append(adaptorSigs, s.MustMarshal()) + } + slashingSigsInfo := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} + ud.CovenantSlashingSigs = append(ud.CovenantSlashingSigs, slashingSigsInfo) + + return nil +} diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go new file mode 100644 index 000000000..884b6896a --- /dev/null +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -0,0 +1,131 @@ +package types_test + +import ( + "math/rand" + "testing" + + sdkmath "cosmossdk.io/math" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + btctest "github.com/babylonchain/babylon/testutil/bitcoin" + "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +func FuzzBTCUndelegation_SlashingTx(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + net := &chaincfg.SimNetParams + + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + valPKList := []*btcec.PublicKey{valPK} + + covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + covPKList := []*btcec.PublicKey{covenantPK} + + stakingTimeBlocks := uint16(5) + stakingValue := int64(2 * 10e8) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) + require.NoError(t, err) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) + require.NoError(t, err) + + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + + // construct the BTC delegation with everything + btcDel, err := datagen.GenRandomBTCDelegation( + r, + t, + bbn.NewBIP340PKsFromBTCPKs(valPKList), + delSK, + []*btcec.PrivateKey{covenantSK}, + 1, + slashingAddress.EncodeAddress(), + changeAddress.EncodeAddress(), + 1000, + uint64(1000+stakingTimeBlocks), + uint64(stakingValue), + slashingRate, + ) + require.NoError(t, err) + + stakingTxHash := btcDel.MustGetStakingTxHash() + unbondingTime := uint16(100) + 1 + unbondingValue := stakingValue - 1000 + + testInfo := datagen.GenBTCUnbondingSlashingInfo( + r, + t, + net, + delSK, + valPKList, + covPKList, + 1, + wire.NewOutPoint(&stakingTxHash, 0), + unbondingTime, + unbondingValue, + slashingAddress.EncodeAddress(), + changeAddress.EncodeAddress(), + slashingRate, + ) + require.NoError(t, err) + + unbondingTxBytes, err := bbn.SerializeBTCTx(testInfo.UnbondingTx) + require.NoError(t, err) + + // spend info of the unbonding slashing tx + unbondingSlashingSpendInfo, err := testInfo.UnbondingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + // delegator signs the unbonding slashing tx + delSig, err := testInfo.SlashingTx.Sign( + testInfo.UnbondingTx, + 0, + unbondingSlashingSpendInfo.GetPkScriptPath(), + delSK, + ) + require.NoError(t, err) + // covenant signs (using adaptor signature) the slashing tx + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + require.NoError(t, err) + covenantSig, err := testInfo.SlashingTx.EncSign(testInfo.UnbondingTx, 0, unbondingSlashingSpendInfo.GetPkScriptPath(), covenantSK, encKey) + require.NoError(t, err) + covenantSigs := &types.CovenantAdaptorSignatures{ + CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), + AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, + } + + btcDel.BtcUndelegation = &types.BTCUndelegation{ + UnbondingTx: unbondingTxBytes, + UnbondingTime: 100 + 1, + SlashingTx: testInfo.SlashingTx, + DelegatorSlashingSig: delSig, + CovenantSlashingSigs: []*types.CovenantAdaptorSignatures{covenantSigs}, + CovenantUnbondingSigList: nil, // not relevant here + } + + bsParams := &types.Params{ + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covPKList), + CovenantQuorum: 1, + } + btcNet := &chaincfg.SimNetParams + + // build slashing tx with witness for spending the unbonding tx + unbondingSlashingTxWithWitness, err := btcDel.BuildUnbondingSlashingTxWithWitness(bsParams, btcNet, valSK) + require.NoError(t, err) + + // assert the execution + btctest.AssertSlashingTxExecution(t, testInfo.UnbondingInfo.UnbondingOutput, unbondingSlashingTxWithWitness) + }) +} diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7838d61a0..a920fb791 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -4,29 +4,9 @@ import ( "fmt" "sort" - "github.com/btcsuite/btcd/chaincfg/chainhash" - - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" ) -func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, error) { - switch statusStr { - case "pending": - return BTCDelegationStatus_PENDING, nil - case "active": - return BTCDelegationStatus_ACTIVE, nil - case "unbonding": - return BTCDelegationStatus_UNBONDING, nil - case "unbonded": - return BTCDelegationStatus_UNBONDED, nil - case "any": - return BTCDelegationStatus_ANY, nil - default: - return -1, fmt.Errorf("invalid status string; should be one of {pending, active, unbonding, unbonded, any}") - } -} - func (v *BTCValidator) IsSlashed() bool { return v.SlashedBabylonHeight > 0 } @@ -52,208 +32,6 @@ func (v *BTCValidator) ValidateBasic() error { return nil } -func (d *BTCDelegation) ValidateBasic() error { - if d.BabylonPk == nil { - return fmt.Errorf("empty Babylon public key") - } - if d.BtcPk == nil { - return fmt.Errorf("empty BTC public key") - } - if d.Pop == nil { - return fmt.Errorf("empty proof of possession") - } - if len(d.ValBtcPkList) == 0 { - return fmt.Errorf("empty list of BTC validator PKs") - } - if ExistsDup(d.ValBtcPkList) { - return fmt.Errorf("list of BTC validator PKs has duplication") - } - if d.StakingTx == nil { - return fmt.Errorf("empty staking tx") - } - if d.SlashingTx == nil { - return fmt.Errorf("empty slashing tx") - } - if d.DelegatorSig == nil { - return fmt.Errorf("empty delegator signature") - } - - // ensure staking tx is correctly formatted - if _, err := ParseBtcTx(d.StakingTx); err != nil { - return err - } - if err := d.Pop.ValidateBasic(); err != nil { - return err - } - - return nil -} - -// HasCovenantQuorum returns whether a BTC delegation has sufficient sigs -// from Covenant members to make a quorum -func (d *BTCDelegation) HasCovenantQuorum(quorum uint32) bool { - return uint32(len(d.CovenantSigs)) >= quorum -} - -// IsSignedByCovMember checks whether the given covenant PK has signed the delegation -func (d *BTCDelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { - for _, sigInfo := range d.CovenantSigs { - if covPk.Equals(sigInfo.CovPk) { - return true - } - } - - return false -} - -func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.AdaptorSignature, quorum uint32) error { - // we can ignore the covenant sig if quorum is already reached - if d.HasCovenantQuorum(quorum) { - return nil - } - // ensure that this covenant member has not signed the delegation yet - if d.IsSignedByCovMember(covPk) { - return ErrDuplicatedCovenantSig - } - - adaptorSigs := make([][]byte, 0, len(sigs)) - for _, s := range sigs { - adaptorSigs = append(adaptorSigs, s.MustMarshal()) - } - covSigs := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} - - d.CovenantSigs = append(d.CovenantSigs, covSigs) - - return nil -} - -func (ud *BTCUndelegation) HasCovenantQuorumOnSlashing(quorum uint32) bool { - return len(ud.CovenantUnbondingSigList) >= int(quorum) -} - -func (ud *BTCUndelegation) HasCovenantQuorumOnUnbonding(quorum uint32) bool { - return len(ud.CovenantUnbondingSigList) >= int(quorum) -} - -// IsSignedByCovMemberOnUnbonding checks whether the given covenant PK has signed the unbonding tx -func (ud *BTCUndelegation) IsSignedByCovMemberOnUnbonding(covPK *bbn.BIP340PubKey) bool { - for _, sigInfo := range ud.CovenantUnbondingSigList { - if sigInfo.Pk.Equals(covPK) { - return true - } - } - return false -} - -// IsSignedByCovMemberOnSlashing checks whether the given covenant PK has signed the slashing tx -func (ud *BTCUndelegation) IsSignedByCovMemberOnSlashing(covPK *bbn.BIP340PubKey) bool { - for _, sigInfo := range ud.CovenantSlashingSigs { - if sigInfo.CovPk.Equals(covPK) { - return true - } - } - return false -} - -func (ud *BTCUndelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { - return ud.IsSignedByCovMemberOnUnbonding(covPk) && ud.IsSignedByCovMemberOnSlashing(covPk) -} - -func (ud *BTCUndelegation) HasAllSignatures(covenantQuorum uint32) bool { - return ud.HasCovenantQuorumOnUnbonding(covenantQuorum) && ud.HasCovenantQuorumOnSlashing(covenantQuorum) -} - -func (ud *BTCUndelegation) AddCovenantSigs( - covPk *bbn.BIP340PubKey, - unbondingSig *bbn.BIP340Signature, - slashingSigs []asig.AdaptorSignature, - quorum uint32, -) error { - // we can ignore the covenant slashing sig if quorum is already reached - if ud.HasAllSignatures(quorum) { - return nil - } - - if ud.IsSignedByCovMember(covPk) { - return ErrDuplicatedCovenantSig - } - - covUnbondingSigInfo := &SignatureInfo{Pk: covPk, Sig: unbondingSig} - ud.CovenantUnbondingSigList = append(ud.CovenantUnbondingSigList, covUnbondingSigInfo) - - adaptorSigs := make([][]byte, 0, len(slashingSigs)) - for _, s := range slashingSigs { - adaptorSigs = append(adaptorSigs, s.MustMarshal()) - } - slashingSigsInfo := &CovenantAdaptorSignatures{CovPk: covPk, AdaptorSigs: adaptorSigs} - ud.CovenantSlashingSigs = append(ud.CovenantSlashingSigs, slashingSigsInfo) - - return nil -} - -// GetStatus returns the status of the BTC Delegation based on a BTC height and a w value -// TODO: Given that we only accept delegations that can be activated immediately, -// we can only have expired delegations. If we accept optimistic submissions, -// we could also have delegations that are in the waiting, so we need an extra status. -// This is covered by expired for now as it is the default value. -// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig -// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig -// Expired: Delegation timelock -func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64, covenantQuorum uint32) BTCDelegationStatus { - if d.BtcUndelegation != nil { - if d.BtcUndelegation.HasAllSignatures(covenantQuorum) { - return BTCDelegationStatus_UNBONDED - } - // If we received an undelegation but is still does not have all required signature, - // delegation receives UNBONING status. - // Voting power from this delegation is removed from the total voting power and now we - // are waiting for signatures from validator and covenant for delegation to become expired. - // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain - // we may consider adding unbonding time on Babylon chain later to avoid situation where - // we can lose to much voting power in to short time. - return BTCDelegationStatus_UNBONDING - } - - if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { - if d.HasCovenantQuorum(covenantQuorum) { - return BTCDelegationStatus_ACTIVE - } else { - return BTCDelegationStatus_PENDING - } - } - return BTCDelegationStatus_UNBONDED -} - -// VotingPower returns the voting power of the BTC delegation at a given BTC height -// and a given w value. -// The BTC delegation d has voting power iff it is active. -func (d *BTCDelegation) VotingPower(btcHeight uint64, w uint64, covenantQuorum uint32) uint64 { - if d.GetStatus(btcHeight, w, covenantQuorum) != BTCDelegationStatus_ACTIVE { - return 0 - } - return d.GetTotalSat() -} - -func (d *BTCDelegation) GetStakingTxHash() (chainhash.Hash, error) { - parsed, err := ParseBtcTx(d.StakingTx) - - if err != nil { - return chainhash.Hash{}, err - } - - return parsed.TxHash(), nil -} - -func (d *BTCDelegation) MustGetStakingTxHash() chainhash.Hash { - txHash, err := d.GetStakingTxHash() - - if err != nil { - panic(err) - } - - return txHash -} - // FilterTopNBTCValidators returns the top n validators based on VotingPower. func FilterTopNBTCValidators(validators []*BTCValidatorWithMeta, n uint32) []*BTCValidatorWithMeta { numVals := uint32(len(validators)) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 84a6839d9..ece606e84 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -6,6 +6,7 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" @@ -155,7 +156,7 @@ func (m *MsgBTCUndelegate) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } - unbondingTxMsg, err := ParseBtcTx(m.UnbondingTx) + unbondingTxMsg, err := bbn.NewBTCTxFromBytes(m.UnbondingTx) if err != nil { return err diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 604838fc7..f7b19811f 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -134,3 +134,11 @@ func (p Params) HasCovenantPK(pk *bbn.BIP340PubKey) bool { } return false } + +func (p Params) MustGetSlashingAddress(btcParams *chaincfg.Params) btcutil.Address { + slashingAddr, err := btcutil.DecodeAddress(p.SlashingAddress, btcParams) + if err != nil { + panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) + } + return slashingAddr +} diff --git a/x/btcstaking/types/types.go b/x/btcstaking/types/types.go deleted file mode 100644 index 6f3138b58..000000000 --- a/x/btcstaking/types/types.go +++ /dev/null @@ -1,71 +0,0 @@ -package types - -import ( - "bytes" - "encoding/hex" - "fmt" - "math" - - bbn "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/wire" -) - -type PublicKeyInfo struct { - StakerKey *bbn.BIP340PubKey - ValidatorKey *bbn.BIP340PubKey - CovenantKey *bbn.BIP340PubKey -} - -func ParseBtcTx(txBytes []byte) (*wire.MsgTx, error) { - var msgTx wire.MsgTx - rbuf := bytes.NewReader(txBytes) - if err := msgTx.Deserialize(rbuf); err != nil { - return nil, err - } - - return &msgTx, nil -} - -func SerializeBtcTx(tx *wire.MsgTx) ([]byte, error) { - var txBuf bytes.Buffer - if err := tx.Serialize(&txBuf); err != nil { - return nil, err - } - return txBuf.Bytes(), nil -} - -func ParseBtcTxFromHex(txHex string) (*wire.MsgTx, []byte, error) { - txBytes, err := hex.DecodeString(txHex) - if err != nil { - return nil, nil, err - } - - parsed, err := ParseBtcTx(txBytes) - - if err != nil { - return nil, nil, err - } - - return parsed, txBytes, nil -} - -func GetOutputIdx(tx *wire.MsgTx, output *wire.TxOut) (uint32, error) { - for i, txOut := range tx.TxOut { - if bytes.Equal(txOut.PkScript, output.PkScript) && txOut.Value == output.Value { - return uint32(i), nil - } - } - - return 0, fmt.Errorf("output not found") -} - -func (del *BTCDelegation) GetStakingTime() uint16 { - diff := del.EndHeight - del.StartHeight - - if diff > math.MaxUint16 { - // In valid delegation, EndHeight is always greater than StartHeight and it is always uint16 value - panic("invalid delegation in database") - } - - return uint16(diff) -} From eb5073070b4314ef16fa4497f6ea02de0deb327d Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 4 Dec 2023 12:48:26 +1100 Subject: [PATCH 116/202] btcstaking: generalise witness generation for covenant committee (#134) Co-authored-by: KonradStaniec --- btcstaking/btcstaking_test.go | 159 +++-- btcstaking/staking.go | 40 -- btcstaking/types.go | 54 +- btcstaking/witness_utils.go | 120 ++++ test/e2e/btc_staking_e2e_test.go | 90 +-- .../configurer/chain/commands_btcstaking.go | 22 +- testutil/bitcoin/utils.go | 12 +- testutil/datagen/btc_schnorr.go | 29 + testutil/datagen/btcstaking.go | 71 ++- types/btc_schnorr_pk.go | 16 +- types/btc_schnorr_sig.go | 8 + x/btcstaking/client/cli/tx.go | 36 +- x/btcstaking/keeper/grpc_query_test.go | 43 +- x/btcstaking/keeper/incentive_test.go | 11 +- x/btcstaking/keeper/msg_server.go | 71 +-- x/btcstaking/keeper/msg_server_test.go | 583 ++++++++---------- .../keeper/voting_power_table_test.go | 29 +- x/btcstaking/types/btc_delegation.go | 14 +- x/btcstaking/types/btc_delegation_test.go | 24 +- x/btcstaking/types/btc_slashing_tx.go | 37 +- x/btcstaking/types/btc_slashing_tx_test.go | 47 +- x/btcstaking/types/btc_undelegation_test.go | 38 +- x/btcstaking/types/btcstaking.go | 42 ++ x/btcstaking/types/params.go | 29 +- 24 files changed, 874 insertions(+), 751 deletions(-) create mode 100644 btcstaking/witness_utils.go diff --git a/btcstaking/btcstaking_test.go b/btcstaking/btcstaking_test.go index 862d8501f..86722c9e9 100644 --- a/btcstaking/btcstaking_test.go +++ b/btcstaking/btcstaking_test.go @@ -1,13 +1,16 @@ package btcstaking_test import ( + "bytes" "math/rand" + "sort" "testing" "time" "github.com/babylonchain/babylon/btcstaking" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" @@ -132,8 +135,7 @@ func TestSpendingTimeLockPath(t *testing.T) { require.NoError(t, err) - witness, err := si.CreateWitness([][]byte{sig.Serialize()}) - + witness, err := si.CreateTimeLockPathWitness(sig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -151,6 +153,35 @@ func TestSpendingTimeLockPath(t *testing.T) { btctest.AssertEngineExecution(t, 0, true, newEngine) } +type SignatureInfo struct { + SignerPubKey *btcec.PublicKey + Signature *schnorr.Signature +} + +func NewSignatureInfo( + signerPubKey *btcec.PublicKey, + signature *schnorr.Signature, +) *SignatureInfo { + return &SignatureInfo{ + SignerPubKey: signerPubKey, + Signature: signature, + } +} + +// Helper function to sort all signatures in reverse lexicographical order of signing public keys +// this way signatures are ready to be used in multisig witness with corresponding public keys +func sortSignatureInfo(infos []*SignatureInfo) []*SignatureInfo { + sortedInfos := make([]*SignatureInfo, len(infos)) + copy(sortedInfos, infos) + sort.SliceStable(sortedInfos, func(i, j int) bool { + keyIBytes := schnorr.SerializePubKey(sortedInfos[i].SignerPubKey) + keyJBytes := schnorr.SerializePubKey(sortedInfos[j].SignerPubKey) + return bytes.Compare(keyIBytes, keyJBytes) == 1 + }) + + return sortedInfos +} + // generate list of signatures in valid order func GenerateSignatures( t *testing.T, @@ -158,9 +189,9 @@ func GenerateSignatures( tx *wire.MsgTx, stakingOutput *wire.TxOut, leaf txscript.TapLeaf, -) [][]byte { +) []*schnorr.Signature { - var si []*btcstaking.SignatureInfo + var si []*SignatureInfo for _, key := range keys { pubKey := key.PubKey() @@ -171,17 +202,17 @@ func GenerateSignatures( leaf, ) require.NoError(t, err) - info := btcstaking.NewSignatureInfo( + info := NewSignatureInfo( pubKey, - sig.Serialize(), + sig, ) si = append(si, info) } // sort signatures by public key - sortedSigInfo := btcstaking.SortSignatureInfo(si) + sortedSigInfo := sortSignatureInfo(si) - var sigs [][]byte = make([][]byte, len(sortedSigInfo)) + var sigs []*schnorr.Signature = make([]*schnorr.Signature, len(sortedSigInfo)) for i, sigInfo := range sortedSigInfo { sig := sigInfo @@ -247,10 +278,7 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { stakingInfo.StakingOutput, si.RevealedLeaf, ) - var witnessSignatures [][]byte - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := si.CreateWitness(witnessSignatures) + witness, err := si.CreateUnbondingPathWitness(covenantSigantures, stakerSig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -269,9 +297,6 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { numOfCovenantMembers := len(scenario.CovenantKeys) // with each loop iteration we remove one key from the list of signatures for i := 0; i < numOfCovenantMembers; i++ { - // reset signatures - witnessSignatures = [][]byte{} - numOfRemovedSignatures := i + 1 covenantSigantures := GenerateSignatures( @@ -285,12 +310,10 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { for j := 0; j <= i; j++ { // NOTE: Number provides signatures must match number of public keys in the script, // if we are missing some signatures those must be set to empty signature in witness - covenantSigantures[j] = []byte{} + covenantSigantures[j] = nil } - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := si.CreateWitness(witnessSignatures) + witness, err := si.CreateUnbondingPathWitness(covenantSigantures, stakerSig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -359,10 +382,7 @@ func TestSpendingUnbondingPathSingleKeyCovenant(t *testing.T) { stakingInfo.StakingOutput, si.RevealedLeaf, ) - var witnessSignatures [][]byte - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := si.CreateWitness(witnessSignatures) + witness, err := si.CreateUnbondingPathWitness(covenantSigantures, stakerSig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness @@ -418,6 +438,7 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { si, err := stakingInfo.SlashingPathSpendInfo() require.NoError(t, err) + // generate staker signature, covenant signatures, and validator signature stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, @@ -425,8 +446,6 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { si.RevealedLeaf, ) require.NoError(t, err) - - // Case without validator signature covenantSigantures := GenerateSignatures( t, scenario.CovenantKeys, @@ -434,27 +453,6 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { stakingInfo.StakingOutput, si.RevealedLeaf, ) - var witnessSignatures [][]byte - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := si.CreateWitness(witnessSignatures) - require.NoError(t, err) - spendStakeTx.TxIn[0].Witness = witness - - prevOutputFetcher := stakingInfo.GetOutputFetcher() - newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine( - stakingInfo.GetPkScript(), - spendStakeTx, 0, txscript.StandardVerifyFlags, nil, - txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, - prevOutputFetcher, - ) - } - // we expect it will fail because we are missing validator signature - btctest.AssertEngineExecution(t, 0, false, newEngine) - - // Retry with the same values but now with validator signature present - witnessSignatures = [][]byte{} validatorSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, @@ -463,21 +461,31 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { ) require.NoError(t, err) - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, validatorSig.Serialize()) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err = si.CreateWitness(witnessSignatures) + witness, err := si.CreateSlashingPathWitness( + covenantSigantures, + []*schnorr.Signature{validatorSig}, + stakerSig, + ) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness // now as we have validator signature execution should succeed + prevOutputFetcher := stakingInfo.GetOutputFetcher() + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.GetPkScript(), + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } btctest.AssertEngineExecution(t, 0, true, newEngine) } func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - // we are having here 3/5 covenant threshold sig, and we are restaking to 2 validators + // we have 3 out of 5 covenant committee, and we are restaking to 2 validators scenario := GenerateTestScenario( r, t, @@ -513,6 +521,7 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) si, err := stakingInfo.SlashingPathSpendInfo() require.NoError(t, err) + // generate staker signature, covenant signatures, and validator signature stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, @@ -521,7 +530,7 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) ) require.NoError(t, err) - // Case without validator signature + // only use 3 out of 5 covenant signatures covenantSigantures := GenerateSignatures( t, scenario.CovenantKeys, @@ -529,28 +538,12 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) stakingInfo.StakingOutput, si.RevealedLeaf, ) - var witnessSignatures [][]byte - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err := si.CreateWitness(witnessSignatures) - require.NoError(t, err) - spendStakeTx.TxIn[0].Witness = witness - - prevOutputFetcher := stakingInfo.GetOutputFetcher() - newEngine := func() (*txscript.Engine, error) { - return txscript.NewEngine( - stakingInfo.GetPkScript(), - spendStakeTx, 0, txscript.StandardVerifyFlags, nil, - txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, - prevOutputFetcher, - ) - } - // we expect it will fail because we are missing validators signature - btctest.AssertEngineExecution(t, 0, false, newEngine) - - // Retry with the same values but now with validator signature present - witnessSignatures = [][]byte{} + covenantSigantures[0] = nil + covenantSigantures[1] = nil + // only use one of the validator signatures + // script should still be valid as we require only one validator signature + // to be present validatorsSignatures := GenerateSignatures( t, scenario.ValidatorKeys, @@ -558,18 +551,20 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) stakingInfo.StakingOutput, si.RevealedLeaf, ) + validatorsSignatures[0] = nil - // make one signature empty, script still should be valid as we require only one validator signature - // to be present - validatorsSignatures[0] = []byte{} - - witnessSignatures = append(witnessSignatures, covenantSigantures...) - witnessSignatures = append(witnessSignatures, validatorsSignatures...) - witnessSignatures = append(witnessSignatures, stakerSig.Serialize()) - witness, err = si.CreateWitness(witnessSignatures) + witness, err := si.CreateSlashingPathWitness(covenantSigantures, validatorsSignatures, stakerSig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - // now as we have validator signature execution should succeed + prevOutputFetcher := stakingInfo.GetOutputFetcher() + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + stakingInfo.GetPkScript(), + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), stakingInfo.StakingOutput.Value, + prevOutputFetcher, + ) + } btctest.AssertEngineExecution(t, 0, true, newEngine) } diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 4b7f6dabd..08864309a 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -693,43 +693,3 @@ func EncVerifyTransactionSigWithOutputData( return signature.EncVerify(pubKey, encKey, sigHash) } - -// CreateWitness creates witness for spending the tx corresponding to -// the spend info with the given stack of signatures -// The returned witness stack follows the structure below: -// - first come signatures -// - then whole revealed script -// - then control block -func (si *SpendInfo) CreateWitness(signatures [][]byte) (wire.TxWitness, error) { - numSignatures := len(signatures) - - if numSignatures == 0 { - return nil, fmt.Errorf("cannot build witness without signatures") - } - - if si == nil { - return nil, fmt.Errorf("cannot build witness without spend info") - } - - controlBlockBytes, err := si.ControlBlock.ToBytes() - - if err != nil { - return nil, err - } - - // witness stack has: - // all signatures - // whole revealed script - // control block - witnessStack := wire.TxWitness(make([][]byte, numSignatures+2)) - - for i, sig := range signatures { - sc := sig - witnessStack[i] = sc - } - - witnessStack[numSignatures] = si.GetPkScriptPath() - witnessStack[numSignatures+1] = controlBlockBytes - - return witnessStack, nil -} diff --git a/btcstaking/types.go b/btcstaking/types.go index 06d95176c..d1857900d 100644 --- a/btcstaking/types.go +++ b/btcstaking/types.go @@ -1,10 +1,8 @@ package btcstaking import ( - "bytes" "encoding/hex" "fmt" - "sort" sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" @@ -174,33 +172,6 @@ func (t *taprootScriptHolder) taprootPkScript(net *chaincfg.Params) ([]byte, err ) } -type SignatureInfo struct { - SignerPubKey *btcec.PublicKey - Signature []byte -} - -func NewSignatureInfo( - signerPubKey *btcec.PublicKey, - signature []byte, -) *SignatureInfo { - return &SignatureInfo{ - SignerPubKey: signerPubKey, - Signature: signature, - } -} - -// Helper function to sort all signatures in reverse lexicographical order of signing public keys -// this way signatures are ready to be used in multisig witness with corresponding public keys -func SortSignatureInfo(infos []*SignatureInfo) []*SignatureInfo { - sort.SliceStable(infos, func(i, j int) bool { - keyIBytes := schnorr.SerializePubKey(infos[i].SignerPubKey) - keyJBytes := schnorr.SerializePubKey(infos[j].SignerPubKey) - return bytes.Compare(keyIBytes, keyJBytes) == 1 - }) - - return infos -} - // Package responsible for different kinds of btc scripts used by babylon // Staking script has 3 spending paths: // 1. Staker can spend after relative time lock - staking @@ -279,16 +250,25 @@ func aggregateScripts(scripts ...[]byte) []byte { // babylonScriptPaths contains all possible babylon script paths // not every babylon output will contain all of those paths type babylonScriptPaths struct { - timeLockPathScript []byte + // timeLockPathScript is the script path for normal unbonding + // OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY + timeLockPathScript []byte + // unbondingPathScript is the script path for on-demand early unbonding + // OP_CHECKSIGVERIFY + // OP_CHECKSIG ... OP_CHECKSIGADD M OP_GREATERTHANOREQUAL OP_VERIFY unbondingPathScript []byte - slashingPathScript []byte + // slashingPathScript is the script path for slashing + // OP_CHECKSIGVERIFY + // OP_CHECKSIG ... OP_CHECKSIGADD M OP_GREATERTHANOREQUAL OP_VERIFY + // OP_CHECKSIG ... OP_CHECKSIGADD 1 OP_GREATERTHANOREQUAL OP_VERIFY + slashingPathScript []byte } func newBabylonScriptPaths( stakerKey *btcec.PublicKey, validatorKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, lockTime uint16, ) (*babylonScriptPaths, error) { if stakerKey == nil { @@ -303,7 +283,7 @@ func newBabylonScriptPaths( covenantMultisigScript, err := buildMultiSigScript( covenantKeys, - covenantThreshold, + covenantQuorum, // covenant multisig is always last in script so we do not run verify and leave // last value on the stack. If we do not leave at least one element on the stack // script will always error @@ -354,7 +334,7 @@ func BuildStakingInfo( stakerKey *btcec.PublicKey, validatorKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, stakingTime uint16, stakingAmount btcutil.Amount, net *chaincfg.Params, @@ -365,7 +345,7 @@ func BuildStakingInfo( stakerKey, validatorKeys, covenantKeys, - covenantThreshold, + covenantQuorum, stakingTime, ) @@ -434,7 +414,7 @@ func BuildUnbondingInfo( stakerKey *btcec.PublicKey, validatorKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, unbondingTime uint16, unbondingAmount btcutil.Amount, net *chaincfg.Params, @@ -445,7 +425,7 @@ func BuildUnbondingInfo( stakerKey, validatorKeys, covenantKeys, - covenantThreshold, + covenantQuorum, unbondingTime, ) diff --git a/btcstaking/witness_utils.go b/btcstaking/witness_utils.go new file mode 100644 index 000000000..87a8d2f1c --- /dev/null +++ b/btcstaking/witness_utils.go @@ -0,0 +1,120 @@ +package btcstaking + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/wire" +) + +func (si *SpendInfo) CreateTimeLockPathWitness(delegatorSig *schnorr.Signature) (wire.TxWitness, error) { + if si == nil { + panic("cannot build witness without spend info") + } + if delegatorSig == nil { + return nil, fmt.Errorf("delegator signature should not be nil") + } + return CreateWitness(si, [][]byte{delegatorSig.Serialize()}) +} + +func (si *SpendInfo) CreateUnbondingPathWitness(covenantSigs []*schnorr.Signature, delegatorSig *schnorr.Signature) (wire.TxWitness, error) { + if si == nil { + panic("cannot build witness without spend info") + } + + var witnessStack [][]byte + + // add covenant signatures to witness stack + // NOTE: only a quorum number of covenant signatures needs to be non-nil + if len(covenantSigs) == 0 { + return nil, fmt.Errorf("covenant signatures should not be empty") + } + for _, covSig := range covenantSigs { + if covSig == nil { + witnessStack = append(witnessStack, []byte{}) + } else { + witnessStack = append(witnessStack, covSig.Serialize()) + } + } + + // add delegator signature to witness stack + if delegatorSig == nil { + return nil, fmt.Errorf("delegator signature should not be nil") + } + witnessStack = append(witnessStack, delegatorSig.Serialize()) + + return CreateWitness(si, witnessStack) +} + +func (si *SpendInfo) CreateSlashingPathWitness(covenantSigs []*schnorr.Signature, validatorSigs []*schnorr.Signature, delegatorSig *schnorr.Signature) (wire.TxWitness, error) { + if si == nil { + panic("cannot build witness without spend info") + } + + var witnessStack [][]byte + + // add covenant signatures to witness stack + // NOTE: only a quorum number of covenant signatures needs to be non-nil + if len(covenantSigs) == 0 { + return nil, fmt.Errorf("covenant signatures should not be empty") + } + for _, covSig := range covenantSigs { + if covSig == nil { + witnessStack = append(witnessStack, []byte{}) + } else { + witnessStack = append(witnessStack, covSig.Serialize()) + } + } + + // add validator signatures to witness stack + // NOTE: only 1 of the validator signatures needs to be non-nil + if len(validatorSigs) == 0 { + return nil, fmt.Errorf("validator signatures should not be empty") + } + for _, valSig := range validatorSigs { + if valSig == nil { + witnessStack = append(witnessStack, []byte{}) + } else { + witnessStack = append(witnessStack, valSig.Serialize()) + } + } + + // add delegator signature to witness stack + if delegatorSig == nil { + return nil, fmt.Errorf("delegator signature should not be nil") + } + witnessStack = append(witnessStack, delegatorSig.Serialize()) + + return CreateWitness(si, witnessStack) +} + +// createWitness creates witness for spending the tx corresponding to +// the given spend info with the given stack of signatures +// The returned witness stack follows the structure below: +// - first come signatures +// - then whole revealed script +// - then control block +func CreateWitness(si *SpendInfo, signatures [][]byte) (wire.TxWitness, error) { + numSignatures := len(signatures) + + controlBlockBytes, err := si.ControlBlock.ToBytes() + if err != nil { + return nil, err + } + + // witness stack has: + // all signatures + // whole revealed script + // control block + witnessStack := wire.TxWitness(make([][]byte, numSignatures+2)) + + for i, sig := range signatures { + sc := sig + witnessStack[i] = sc + } + + witnessStack[numSignatures] = si.GetPkScriptPath() + witnessStack[numSignatures+1] = controlBlockBytes + + return witnessStack, nil +} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index c43cc3de2..51f675add 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -37,9 +37,7 @@ var ( // BTC delegation delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) // covenant - covenantSK, _ = btcec.PrivKeyFromBytes( - []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - ) + covenantSKs, _, covenantQuorum = bstypes.DefaultCovenantCommittee() stakingValue = int64(2 * 10e8) @@ -120,7 +118,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { delBTCSK, []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, covenantBTCPKs, - 1, + covenantQuorum, stakingTimeBlocks, stakingValue, params.SlashingAddress, changeAddress.EncodeAddress(), @@ -209,10 +207,8 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) } - validatorBTCPKs := []*btcec.PublicKey{} - for _, valPK := range pendingDel.ValBtcPkList { - validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) - } + validatorBTCPKs, err := bbn.NewBTCPKsFromBIP340PKs(pendingDel.ValBtcPkList) + s.NoError(err) stakingInfo, err := btcstaking.BuildStakingInfo( pendingDel.BtcPk.MustToBTCPK(), @@ -231,18 +227,18 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { /* generate and insert new covenant signature, in order to activate the BTC delegation */ - // TODO add multiple covenant sigs - encKey, err := asig.NewEncryptionKeyFromBTCPK(validatorBTCPKs[0]) - s.NoError(err) - covenantSig, err := slashingTx.EncSign( + covenantSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + validatorBTCPKs, stakingMsgTx, - pendingDel.StakingOutputIdx, stakingSlashingPathInfo.GetPkScriptPath(), - covenantSK, - encKey, + slashingTx, ) s.NoError(err) - nonValidatorNode.AddCovenantSig(¶ms.CovenantPks[0], stakingTxHash, covenantSig) + s.GreaterOrEqual(uint32(len(covenantSigs)), covenantQuorum) + for i := 0; i < int(covenantQuorum); i++ { + nonValidatorNode.AddCovenantSigs(covenantSigs[i].CovPk, stakingTxHash, covenantSigs[i].AdaptorSigs) + } // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() @@ -571,34 +567,42 @@ func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { stakingTxUnbondigPathInfo, err := stakingInfo.UnbondingPathSpendInfo() s.NoError(err) - covenantUnbondingSignature, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - unbondingTx, - stakingTx, - delegation.StakingOutputIdx, - stakingTxUnbondigPathInfo.GetPkScriptPath(), - covenantSK, - ) - s.NoError(err) - - covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingSignature) - - // Next covenant signature on unbonding slashing tx - unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() - s.NoError(err) + for i := 0; i < int(covenantQuorum); i++ { + covenantUnbondingSignature, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + delegation.StakingOutputIdx, + stakingTxUnbondigPathInfo.GetPkScriptPath(), + covenantSKs[i], + ) + s.NoError(err) + + covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingSignature) + + // Next covenant signature on unbonding slashing tx + unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() + s.NoError(err) + + // slashing signatures, each encrypted by a restaked BTC validator's PK + covenantSlashingSigs := []*asig.AdaptorSignature{} + for _, valPK := range validatorBTCPKs { + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + s.NoError(err) + covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.EncSign( + unbondingTx, + 0, + unbondingTxSlashingPath.GetPkScriptPath(), + covenantSKs[i], + encKey, + ) + s.NoError(err) + covenantSlashingSigs = append(covenantSlashingSigs, covenantSlashingSig) + } + + nonValidatorNode.AddCovenantUnbondingSigs( + ¶ms.CovenantPks[i], stakingTxHash, &covenantUnbondingSig, covenantSlashingSigs) + } - // TODO accomodate multiple slashing sigs - encKey, err := asig.NewEncryptionKeyFromBTCPK(validatorBTCPKs[0]) - s.NoError(err) - covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.EncSign( - unbondingTx, - 0, - unbondingTxSlashingPath.GetPkScriptPath(), - covenantSK, - encKey, - ) - s.NoError(err) - nonValidatorNode.AddCovenantUnbondingSigs( - ¶ms.CovenantPks[0], stakingTxHash, &covenantUnbondingSig, covenantSlashingSig) nonValidatorNode.WaitForNextBlock() nonValidatorNode.WaitForNextBlock() diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 25c7d1bad..98bba8bd9 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -83,17 +83,19 @@ func (n *NodeConfig) CreateBTCDelegation( n.LogActionF("successfully created BTC delegation") } -// TODO accomodate multiple adaptor sigs -func (n *NodeConfig) AddCovenantSig(covPK *bbn.BIP340PubKey, stakingTxHash string, sig *asig.AdaptorSignature) { +func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash string, sigs [][]byte) { n.LogActionF("adding covenant signature") covPKHex := covPK.MarshalHex() - sigHex := sig.MarshalHex() - cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", covPKHex, stakingTxHash, sigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", covPKHex, stakingTxHash} + for _, sig := range sigs { + cmd = append(cmd, hex.EncodeToString(sig)) + } + cmd = append(cmd, "--from=val") _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - n.LogActionF("successfully added covenant sig") + n.LogActionF("successfully added covenant sigatures") } func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand, sig *bbn.BIP340Signature) { @@ -183,19 +185,21 @@ func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bb n.LogActionF("successfully added validator unbonding sig") } -// TODO accomodate multiple slashing sigs func (n *NodeConfig) AddCovenantUnbondingSigs( covPK *bbn.BIP340PubKey, stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, - slashUnbondingTxSig *asig.AdaptorSignature) { + slashUnbondingTxSigs []*asig.AdaptorSignature) { n.LogActionF("adding validator signature") covPKHex := covPK.MarshalHex() unbondingTxSigHex := unbondingTxSig.ToHexStr() - slashUnbondingTxSigHex := slashUnbondingTxSig.MarshalHex() - cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", covPKHex, stakingTxHash, unbondingTxSigHex, slashUnbondingTxSigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-unbonding-sigs", covPKHex, stakingTxHash, unbondingTxSigHex} + for _, sig := range slashUnbondingTxSigs { + cmd = append(cmd, sig.MarshalHex()) + } + cmd = append(cmd, "--from=val") _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added covenant unbonding sigs") diff --git a/testutil/bitcoin/utils.go b/testutil/bitcoin/utils.go index 3654519dc..505a1c1d1 100644 --- a/testutil/bitcoin/utils.go +++ b/testutil/bitcoin/utils.go @@ -48,16 +48,16 @@ func AssertEngineExecution(t *testing.T, testNum int, valid bool, done, err = vm.Step() if err != nil && valid { t.Log(debugBuf.String()) - t.Fatalf("spend test case #%v failed, spend "+ - "should be valid: %v", testNum, err) + t.Fatalf("spend test case #%v failed at (%v), spend "+ + "should be valid: %v", testNum, dis, err) } else if err == nil && !valid && done { t.Log(debugBuf.String()) - t.Fatalf("spend test case #%v succeed, spend "+ - "should be invalid: %v", testNum, err) + t.Fatalf("spend test case #%v succeed at (%v), spend "+ + "should be invalid: %v", testNum, dis, err) } - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + debugBuf.WriteString(fmt.Sprintf("Stack: %v\n", vm.GetStack())) + debugBuf.WriteString(fmt.Sprintf("AltStack: %v\n\n", vm.GetAltStack())) } // If we get to this point the unexpected case was not reached diff --git a/testutil/datagen/btc_schnorr.go b/testutil/datagen/btc_schnorr.go index 63bed5eb4..1eec8d047 100644 --- a/testutil/datagen/btc_schnorr.go +++ b/testutil/datagen/btc_schnorr.go @@ -16,6 +16,20 @@ func GenRandomBTCKeyPair(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, err return sk, sk.PubKey(), nil } +func GenRandomBTCKeyPairs(r *rand.Rand, n int) ([]*btcec.PrivateKey, []*btcec.PublicKey, error) { + sks, pks := []*btcec.PrivateKey{}, []*btcec.PublicKey{} + for i := 0; i < n; i++ { + sk, pk, err := GenRandomBTCKeyPair(r) + if err != nil { + return nil, nil, err + } + sks = append(sks, sk) + pks = append(pks, pk) + } + + return sks, pks, nil +} + func GenRandomBIP340PubKey(r *rand.Rand) (*bbn.BIP340PubKey, error) { sk, err := secp256k1.GeneratePrivateKeyFromRand(r) if err != nil { @@ -25,3 +39,18 @@ func GenRandomBIP340PubKey(r *rand.Rand) (*bbn.BIP340PubKey, error) { btcPK := bbn.NewBIP340PubKeyFromBTCPK(pk) return btcPK, nil } + +// GenCovenantCommittee generates a covenant committee +// with random number of members and quorum size +func GenCovenantCommittee(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec.PublicKey, uint32) { + committeeSize := RandomInt(r, 5) + 5 + quorumSize := uint32(committeeSize/2 + 1) + sks, pks := []*btcec.PrivateKey{}, []*btcec.PublicKey{} + for i := uint64(0); i < committeeSize; i++ { + skBytes := GenRandomByteArray(r, 32) + sk, pk := btcec.PrivKeyFromBytes(skBytes) + sks = append(sks, sk) + pks = append(pks, pk) + } + return sks, pks, quorumSize +} diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 4a748e59d..48fa2f01a 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -32,6 +32,36 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { return GenRandomBTCValidatorWithBTCSK(r, btcSK) } +func GenCovenantAdaptorSigs( + covenantSKs []*btcec.PrivateKey, + valPKs []*btcec.PublicKey, + fundingTx *wire.MsgTx, + pkScriptPath []byte, + slashingTx *bstypes.BTCSlashingTx, +) ([]*bstypes.CovenantAdaptorSignatures, error) { + covenantSigs := []*bstypes.CovenantAdaptorSignatures{} + for _, covenantSK := range covenantSKs { + covMemberSigs := &bstypes.CovenantAdaptorSignatures{ + CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantSK.PubKey()), + AdaptorSigs: [][]byte{}, + } + for _, valPK := range valPKs { + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + if err != nil { + return nil, err + } + covenantSig, err := slashingTx.EncSign(fundingTx, 0, pkScriptPath, covenantSK, encKey) + if err != nil { + return nil, err + } + covMemberSigs.AdaptorSigs = append(covMemberSigs.AdaptorSigs, covenantSig.MustMarshal()) + } + covenantSigs = append(covenantSigs, covMemberSigs) + } + + return covenantSigs, nil +} + func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { bbnSK, _, err := GenRandomSecp256k1KeyPair(r) if err != nil { @@ -73,7 +103,7 @@ func GenRandomBTCDelegation( valBTCPKs []bbn.BIP340PubKey, delSK *btcec.PrivateKey, covenantSKs []*btcec.PrivateKey, - covenantThreshold uint32, + covenantQuorum uint32, slashingAddress, changeAddress string, startHeight, endHeight, totalSat uint64, slashingRate sdkmath.LegacyDec, @@ -118,7 +148,7 @@ func GenRandomBTCDelegation( delSK, valPKs, covenantBTCPKs, - covenantThreshold, + covenantQuorum, uint16(endHeight-startHeight), int64(totalSat), slashingAddress, changeAddress, @@ -127,22 +157,19 @@ func GenRandomBTCDelegation( slashingPathSpendInfo, err := testingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - script := slashingPathSpendInfo.GetPkScriptPath() + pkScriptPath := slashingPathSpendInfo.GetPkScriptPath() - // covenant sig and delegator sig stakingMsgTx := testingInfo.StakingTx - // TODO: covenant multisig - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPKs[0]) - require.NoError(t, err) - covenantSig, err := testingInfo.SlashingTx.EncSign(stakingMsgTx, 0, script, covenantSKs[0], encKey) + + // covenant sigs + covenantSigs, err := GenCovenantAdaptorSigs(covenantSKs, valPKs, stakingMsgTx, pkScriptPath, testingInfo.SlashingTx) require.NoError(t, err) - covenantSigInfo := &bstypes.CovenantAdaptorSignatures{ - CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantBTCPKs[0]), - AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, - } - delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, script, delSK) + + // delegator sig + delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, pkScriptPath, delSK) require.NoError(t, err) - serializedStaking, err := bbn.SerializeBTCTx(testingInfo.StakingTx) + + serializedStakingTx, err := bbn.SerializeBTCTx(testingInfo.StakingTx) require.NoError(t, err) return &bstypes.BTCDelegation{ @@ -155,8 +182,8 @@ func GenRandomBTCDelegation( TotalSat: totalSat, StakingOutputIdx: 0, DelegatorSig: delegatorSig, - CovenantSigs: []*bstypes.CovenantAdaptorSignatures{covenantSigInfo}, - StakingTx: serializedStaking, + CovenantSigs: covenantSigs, + StakingTx: serializedStakingTx, SlashingTx: testingInfo.SlashingTx, }, nil } @@ -181,7 +208,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, @@ -192,7 +219,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( stakerSK.PubKey(), validatorPKs, covenantPKs, - covenantThreshold, + covenantQuorum, stakingTimeBlocks, btcutil.Amount(stakingValue), btcNet, @@ -242,7 +269,7 @@ func GenBTCStakingSlashingInfo( stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, slashingAddress, changeAddress string, @@ -259,7 +286,7 @@ func GenBTCStakingSlashingInfo( stakerSK, validatorPKs, covenantPKs, - covenantThreshold, + covenantQuorum, stakingTimeBlocks, stakingValue, slashingAddress, changeAddress, @@ -273,7 +300,7 @@ func GenBTCUnbondingSlashingInfo( stakerSK *btcec.PrivateKey, validatorPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, - covenantThreshold uint32, + covenantQuorum uint32, stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, @@ -285,7 +312,7 @@ func GenBTCUnbondingSlashingInfo( stakerSK.PubKey(), validatorPKs, covenantPKs, - covenantThreshold, + covenantQuorum, stakingTimeBlocks, btcutil.Amount(stakingValue), btcNet, diff --git a/types/btc_schnorr_pk.go b/types/btc_schnorr_pk.go index 06d24d3a5..345e9f42d 100644 --- a/types/btc_schnorr_pk.go +++ b/types/btc_schnorr_pk.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "sort" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -118,11 +119,11 @@ func (pk *BIP340PubKey) Equals(pk2 *BIP340PubKey) bool { func NewBTCPKsFromBIP340PKs(pks []BIP340PubKey) ([]*btcec.PublicKey, error) { btcPks := make([]*btcec.PublicKey, 0, len(pks)) for _, pk := range pks { - btcPk, err := pk.ToBTCPK() + btcPK, err := pk.ToBTCPK() if err != nil { return nil, err } - btcPks = append(btcPks, btcPk) + btcPks = append(btcPks, btcPK) } return btcPks, nil } @@ -134,3 +135,14 @@ func NewBIP340PKsFromBTCPKs(btcPKs []*btcec.PublicKey) []BIP340PubKey { } return pks } + +func SortBIP340PKs(keys []BIP340PubKey) []BIP340PubKey { + sortedPKs := make([]BIP340PubKey, len(keys)) + copy(sortedPKs, keys) + sort.SliceStable(sortedPKs, func(i, j int) bool { + keyIBytes := sortedPKs[i].MustMarshal() + keyJBytes := sortedPKs[j].MustMarshal() + return bytes.Compare(keyIBytes, keyJBytes) == 1 + }) + return sortedPKs +} diff --git a/types/btc_schnorr_sig.go b/types/btc_schnorr_sig.go index eee896b7c..82c4263bb 100644 --- a/types/btc_schnorr_sig.go +++ b/types/btc_schnorr_sig.go @@ -34,6 +34,14 @@ func (sig BIP340Signature) ToBTCSig() (*schnorr.Signature, error) { return schnorr.ParseSignature(sig) } +func (sig BIP340Signature) MustToBTCSig() *schnorr.Signature { + btcSig, err := schnorr.ParseSignature(sig) + if err != nil { + panic(err) + } + return btcSig +} + func (sig BIP340Signature) Size() int { return len(sig.MustMarshal()) } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 3e042d343..6f571dea4 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -274,8 +274,8 @@ func NewCreateBTCDelegationCmd() *cobra.Command { func NewAddCovenantSigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-covenant-sig [covenant_pk] [staking_tx_hash] [sig]", - Args: cobra.ExactArgs(3), + Use: "add-covenant-sig [covenant_pk] [staking_tx_hash] [sig1] [sig2] ...", + Args: cobra.MinimumNArgs(3), Short: "Add a covenant signature", Long: strings.TrimSpace( `Add a covenant signature.`, // TODO: example @@ -294,18 +294,20 @@ func NewAddCovenantSigCmd() *cobra.Command { // get staking tx hash stakingTxHash := args[1] - // TODO add multiple adaptor sigs from cli - // get adaptor sig - sig, err := asig.NewAdaptorSignatureFromHex(args[2]) - if err != nil { - return fmt.Errorf("invalid covenant signature: %w", err) + sigs := [][]byte{} + for _, sigHex := range args[2:] { + sig, err := asig.NewAdaptorSignatureFromHex(sigHex) + if err != nil { + return fmt.Errorf("invalid covenant signature: %w", err) + } + sigs = append(sigs, sig.MustMarshal()) } msg := types.MsgAddCovenantSig{ Signer: clientCtx.FromAddress.String(), Pk: covPK, StakingTxHash: stakingTxHash, - Sigs: [][]byte{sig.MustMarshal()}, + Sigs: sigs, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -382,8 +384,8 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { func NewAddCovenantUnbondingSigsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-covenant-unbonding-sigs [covenant_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig]", - Args: cobra.ExactArgs(4), + Use: "add-covenant-unbonding-sigs [covenant_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig1] [slashing_unbonding_tx_sig2] ...", + Args: cobra.MinimumNArgs(4), Short: "Add covenant signatures for unbonding tx and slash unbonding tx", RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -406,11 +408,13 @@ func NewAddCovenantUnbondingSigsCmd() *cobra.Command { return err } - // TODO add multiple adaptor sigs from cli - // get adaptor sig - slashingSig, err := asig.NewAdaptorSignatureFromHex(args[3]) - if err != nil { - return fmt.Errorf("invalid covenant signature: %w", err) + slashingSigs := [][]byte{} + for _, sigHex := range args[3:] { + slashingSig, err := asig.NewAdaptorSignatureFromHex(sigHex) + if err != nil { + return fmt.Errorf("invalid covenant signature: %w", err) + } + slashingSigs = append(slashingSigs, slashingSig.MustMarshal()) } msg := types.MsgAddCovenantUnbondingSigs{ @@ -418,7 +422,7 @@ func NewAddCovenantUnbondingSigsCmd() *cobra.Command { Pk: covPK, StakingTxHash: stakingTxHash, UnbondingTxSig: unbondingSig, - SlashingUnbondingTxSigs: [][]byte{slashingSig.MustMarshal()}, + SlashingUnbondingTxSigs: slashingSigs, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index c5bedf149..ee239e56d 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -8,7 +8,6 @@ import ( sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" @@ -178,8 +177,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -215,8 +213,8 @@ func FuzzPendingBTCDelegations(f *testing.F) { t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, @@ -284,9 +282,8 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) - covBTCPK := bbn.NewBIP340PubKeyFromBTCPK(covenantPK) - require.NoError(t, err) + covenantSKs, covenantPKs, covenantQuorum := types.DefaultCovenantCommittee() + covBTCPKs := bbn.NewBIP340PKsFromBTCPKs(covenantPKs) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -322,8 +319,8 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, @@ -336,11 +333,13 @@ func FuzzUnbondingBTCDelegations(f *testing.F) { if datagen.RandomInt(r, 2) == 1 { // these BTC delegations are unbonded - sig, err := schnorr.Sign(covenantSK, datagen.GenRandomByteArray(r, 32)) - require.NoError(t, err) - unbondingSig := bbn.NewBIP340SignatureFromBTCSig(sig) - unbondingSigInfo := types.NewSignatureInfo(covBTCPK, &unbondingSig) - btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingSigInfo) + for i := range covenantSKs { + sig, err := schnorr.Sign(covenantSKs[i], datagen.GenRandomByteArray(r, 32)) + require.NoError(t, err) + unbondingSig := bbn.NewBIP340SignatureFromBTCSig(sig) + unbondingSigInfo := types.NewSignatureInfo(&covBTCPKs[i], &unbondingSig) + btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingSigInfo) + } btcDel.BtcUndelegation.CovenantSlashingSigs = btcDel.CovenantSigs } else { // these BTC delegations are unbonding @@ -465,8 +464,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -506,8 +504,8 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { t, []bbn.BIP340PubKey{*valBTCPK}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, 10000, slashingRate, @@ -581,8 +579,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -612,8 +609,8 @@ func FuzzBTCValidatorDelegations(f *testing.F) { t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 9f2853bea..eacc367f8 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -1,17 +1,17 @@ package keeper_test import ( - sdkmath "cosmossdk.io/math" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -32,8 +32,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -71,8 +70,8 @@ func FuzzRecordRewardDistCache(f *testing.F) { t, []bbn.BIP340PubKey{*btcVal.BtcPk}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 7ef9f1705..aedc9a365 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -8,8 +8,6 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "google.golang.org/grpc/codes" @@ -33,16 +31,6 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} -func mustGetStakingTxInfo(del *types.BTCDelegation, params *chaincfg.Params) (*wire.MsgTx, uint32) { - stakingTxMsg, err := bbn.NewBTCTxFromBytes(del.StakingTx) - - if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed deserialize staking tx from db")) - } - return stakingTxMsg, del.StakingOutputIdx -} - // UpdateParams updates the params func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if ms.authority != req.Authority { @@ -157,7 +145,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) } - si, err := btcstaking.BuildStakingInfo( + stakingInfo, err := btcstaking.BuildStakingInfo( req.BtcPk.MustToBTCPK(), validatorKeys, covenantKeys, @@ -171,7 +159,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidStakingTx.Wrapf("err: %v", err) } - stakingOutputIdx, err := bbn.GetOutputIdxInBTCTx(stakingMsgTx, si.StakingOutput) + stakingOutputIdx, err := bbn.GetOutputIdxInBTCTx(stakingMsgTx, stakingInfo.StakingOutput) if err != nil { return nil, types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output") @@ -231,7 +219,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre stakingOutput := stakingMsgTx.TxOut[stakingOutputIdx] // 13. verify delegator sig against slashing path of the script - slashingPathInfo, err := si.SlashingPathSpendInfo() + slashingSpendInfo, err := stakingInfo.SlashingPathSpendInfo() if err != nil { panic(fmt.Errorf("failed to construct slashing path from the staking tx: %w", err)) @@ -240,7 +228,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre err = req.SlashingTx.VerifySignature( stakingOutput.PkScript, stakingOutput.Value, - slashingPathInfo.GetPkScriptPath(), + slashingSpendInfo.GetPkScriptPath(), req.BtcPk.MustToBTCPK(), req.DelegatorSig, ) @@ -348,7 +336,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) } - si, err := btcstaking.BuildUnbondingInfo( + unbondingInfo, err := btcstaking.BuildUnbondingInfo( del.BtcPk.MustToBTCPK(), validatorKeys, covenantKeys, @@ -362,7 +350,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } - unbondingOutputIdx, err := bbn.GetOutputIdxInBTCTx(unbondingMsgTx, si.UnbondingOutput) + unbondingOutputIdx, err := bbn.GetOutputIdxInBTCTx(unbondingMsgTx, unbondingInfo.UnbondingOutput) if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not contain expected unbonding output") @@ -385,7 +373,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // 9. Check staker signature against slashing path of the unbonding tx unbondingOutput := unbondingMsgTx.TxOut[unbondingOutputIdx] - slashingPathInfo, err := si.SlashingPathSpendInfo() + slashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if @@ -396,7 +384,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele err = req.SlashingTx.VerifySignature( unbondingOutput.PkScript, unbondingOutput.Value, - slashingPathInfo.GetPkScriptPath(), + slashingSpendInfo.GetPkScriptPath(), del.BtcPk.MustToBTCPK(), req.DelegatorSlashingSig, ) @@ -407,15 +395,19 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // 8. Check unbonding tx against staking tx. // - that input points to the staking tx, staking output // - fee is larger than 0 - stakingTxMsg, stakingOutputIndex := mustGetStakingTxInfo(del, ms.btcNet) + stakingTxMsg, err := bbn.NewBTCTxFromBytes(del.StakingTx) + if err != nil { + // failing to get staking output info from a verified staking tx is a programming error + panic(fmt.Errorf("failed deserialize staking tx from db")) + } // we only check index of the staking output, as we already retrieved delegation // by stakingTxHash computed from unbonding tx input - if unbondingTxFundingOutpoint.Index != uint32(stakingOutputIndex) { + if unbondingTxFundingOutpoint.Index != uint32(del.StakingOutputIdx) { return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not point to staking tx staking output") } - if unbondingMsgTx.TxOut[0].Value >= stakingTxMsg.TxOut[stakingOutputIndex].Value { + if unbondingMsgTx.TxOut[0].Value >= stakingTxMsg.TxOut[del.StakingOutputIdx].Value { // Note: we do not enfore any minimum fee for unbonding tx, we only require that it is larger than 0 // Given that unbonding tx must not be replacable and we do not allow sending it second time, it places // burden on staker to choose right fee. @@ -469,9 +461,6 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven return nil, err } - stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) - stakingOutput := stakingTx.TxOut[stakingOutputIdx] - // Note: we assume the order of adaptor sigs is matched to the // order of validators in the delegation // TODO ensure the order, currently, we only have one validator @@ -490,12 +479,12 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven return nil, types.ErrInvalidCovenantPK.Wrapf("covenant pk: %s", req.Pk.MarshalHex()) } - spendInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) + stakingInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) if err != nil { panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) } - slashingPathInfo, err := spendInfo.SlashingPathSpendInfo() + slashingSpendInfo, err := stakingInfo.SlashingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error @@ -506,9 +495,9 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven for i, sig := range req.Sigs { err := verifySlashingTxAdaptorSig( btcDel.SlashingTx, - stakingOutput.PkScript, - stakingOutput.Value, - slashingPathInfo.GetPkScriptPath(), + stakingInfo.StakingOutput.PkScript, + stakingInfo.StakingOutput.Value, + slashingSpendInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), btcDel.ValBtcPkList[i].MustToBTCPK(), sig, @@ -595,10 +584,6 @@ func (ms msgServer) AddCovenantUnbondingSigs( numAdaptorSig, numVals) } - // 4. Verify signature of unbonding tx against staking tx output - stakingTx, stakingOutputIdx := mustGetStakingTxInfo(btcDel, ms.btcNet) - stakingOutput := stakingTx.TxOut[stakingOutputIdx] - // ensure that the given covenant PK is in the parameter params := ms.GetParams(ctx) if !params.HasCovenantPK(req.Pk) { @@ -613,24 +598,24 @@ func (ms msgServer) AddCovenantUnbondingSigs( unbondingTxHash := unbondingTxMsg.TxHash().String() - stakingOutputSpendInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) + stakingInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) if err != nil { panic(err) } - unbondingPathInfo, err := stakingOutputSpendInfo.UnbondingPathSpendInfo() - + unbondingSpendInfo, err := stakingInfo.UnbondingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error panic(err) } + // 4. Verify signature of unbonding tx against staking tx output if err := btcstaking.VerifyTransactionSigWithOutputData( unbondingTxMsg, - stakingOutput.PkScript, - stakingOutput.Value, - unbondingPathInfo.GetPkScriptPath(), + stakingInfo.StakingOutput.PkScript, + stakingInfo.StakingOutput.Value, + unbondingSpendInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), *req.UnbondingTxSig, ); err != nil { @@ -645,7 +630,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( panic(err) } - slashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + slashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() if err != nil { // our unbonding info was constructed by using BuildStakingInfo constructor, so if @@ -659,7 +644,7 @@ func (ms msgServer) AddCovenantUnbondingSigs( btcDel.BtcUndelegation.SlashingTx, unbondingOutput.PkScript, unbondingOutput.Value, - slashingPathInfo.GetPkScriptPath(), + slashingSpendInfo.GetPkScriptPath(), req.Pk.MustToBTCPK(), btcDel.ValBtcPkList[i].MustToBTCPK(), sig, diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 639f91f65..b6c1a7511 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "bytes" "context" "errors" "math/rand" @@ -15,7 +14,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -30,100 +28,60 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/types" ) -func setupMsgServer(t testing.TB) (*keeper.Keeper, types.MsgServer, context.Context) { - k, ctx := keepertest.BTCStakingKeeper(t, nil, nil) - return k, keeper.NewMsgServerImpl(*k), ctx -} +type Helper struct { + t *testing.T -func TestMsgServer(t *testing.T) { - _, ms, ctx := setupMsgServer(t) - require.NotNil(t, ms) - require.NotNil(t, ctx) + Ctx context.Context + BTCStakingKeeper *keeper.Keeper + BTCLightClientKeeper *types.MockBTCLightClientKeeper + BTCCheckpointKeeper *types.MockBtcCheckpointKeeper + MsgServer types.MsgServer + Net *chaincfg.Params } -func FuzzMsgCreateBTCValidator(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - keeper, ms, goCtx := setupMsgServer(t) - ctx := sdk.UnwrapSDKContext(goCtx) - - // generate new BTC validators - btcVals := []*types.BTCValidator{} - for i := 0; i < int(datagen.RandomInt(r, 10)); i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) - require.NoError(t, err) - msg := &types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - Description: btcVal.Description, - Commission: btcVal.Commission, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, - } - _, err = ms.CreateBTCValidator(goCtx, msg) - require.NoError(t, err) - - btcVals = append(btcVals, btcVal) - } - // assert these validators exist in KVStore - for _, btcVal := range btcVals { - btcPK := *btcVal.BtcPk - require.True(t, keeper.HasBTCValidator(ctx, btcPK)) - } +func NewHelper(t *testing.T, btclcKeeper *types.MockBTCLightClientKeeper, btccKeeper *types.MockBtcCheckpointKeeper) *Helper { + k, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + msgSrvr := keeper.NewMsgServerImpl(*k) + + return &Helper{ + t: t, + Ctx: ctx, + BTCStakingKeeper: k, + BTCLightClientKeeper: btclcKeeper, + BTCCheckpointKeeper: btccKeeper, + MsgServer: msgSrvr, + Net: &chaincfg.SimNetParams, + } +} - // duplicated BTC validators should not pass - for _, btcVal2 := range btcVals { - msg := &types.MsgCreateBTCValidator{ - Signer: datagen.GenRandomAccount().Address, - Description: btcVal2.Description, - Commission: btcVal2.Commission, - BabylonPk: btcVal2.BabylonPk, - BtcPk: btcVal2.BtcPk, - Pop: btcVal2.Pop, - } - _, err := ms.CreateBTCValidator(goCtx, msg) - require.Error(t, err) - } - }) +func (h *Helper) NoError(err error) { + require.NoError(h.t, err) } -func getCovenantInfo(t *testing.T, - r *rand.Rand, - goCtx context.Context, - ms types.MsgServer, - net *chaincfg.Params, - bsKeeper *keeper.Keeper, - sdkCtx sdk.Context) (*btcec.PrivateKey, *btcec.PublicKey, btcutil.Address) { - covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - slashingAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) - err = bsKeeper.SetParams(sdkCtx, types.Params{ - CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, - CovenantQuorum: 1, +func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec.PublicKey) { + // TODO: randomise covenant committee and quorum? + covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) + h.NoError(err) + slashingAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + h.NoError(err) + err = h.BTCStakingKeeper.SetParams(h.Ctx, types.Params{ + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: 3, SlashingAddress: slashingAddress.EncodeAddress(), MinSlashingTxFeeSat: 10, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveBtcValidators: 100, }) - require.NoError(t, err) - return covenantSK, covenantPK, slashingAddress - + h.NoError(err) + return covenantSKs, covenantPKs } -func createValidator( - t *testing.T, - r *rand.Rand, - goCtx context.Context, - ms types.MsgServer, -) (*btcec.PrivateKey, *btcec.PublicKey, *types.BTCValidator) { +func (h *Helper) CreateValidator(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, *types.BTCValidator) { validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + h.NoError(err) btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) - require.NoError(t, err) + h.NoError(err) msgNewVal := types.MsgCreateBTCValidator{ Signer: datagen.GenRandomAccount().Address, Description: btcVal.Description, @@ -132,80 +90,75 @@ func createValidator( BtcPk: btcVal.BtcPk, Pop: btcVal.Pop, } - _, err = ms.CreateBTCValidator(goCtx, &msgNewVal) - require.NoError(t, err) + _, err = h.MsgServer.CreateBTCValidator(h.Ctx, &msgNewVal) + h.NoError(err) return validatorSK, validatorPK, btcVal } -func createDelegation( - t *testing.T, +func (h *Helper) CreateDelegation( r *rand.Rand, - goCtx context.Context, - ms types.MsgServer, - btccKeeper *types.MockBtcCheckpointKeeper, - btclcKeeper *types.MockBTCLightClientKeeper, - net *chaincfg.Params, validatorPK *btcec.PublicKey, - covenantPK *btcec.PublicKey, - slashingAddress, changeAddress string, - slashingRate sdkmath.LegacyDec, + changeAddress string, stakingTime uint16, ) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + h.NoError(err) stakingTimeBlocks := stakingTime stakingValue := int64(2 * 10e8) - + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) + h.NoError(err) testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, - t, - net, + h.t, + h.Net, delSK, []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covPKs, + bsParams.CovenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress, changeAddress, - slashingRate, + bsParams.SlashingAddress, + changeAddress, + bsParams.SlashingRate, ) - require.NoError(t, err) + h.NoError(err) stakingTxHash := testStakingInfo.StakingTx.TxHash().String() // random signer signer := datagen.GenRandomAccount().Address // random Babylon SK delBabylonSK, delBabylonPK, err := datagen.GenRandomSecp256k1KeyPair(r) - require.NoError(t, err) + h.NoError(err) // PoP pop, err := types.NewPoP(delBabylonSK, delSK) - require.NoError(t, err) + h.NoError(err) // generate staking tx info prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, testStakingInfo.StakingTx) btcHeader := btcHeaderWithProof.HeaderBytes serializedStakingTx, err := bbn.SerializeBTCTx(testStakingInfo.StakingTx) - require.NoError(t, err) + h.NoError(err) txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, btcHeaderWithProof.SpvProof.MerkleNodes) // mock for testing k-deep stuff - btccKeeper.EXPECT().GetPowLimit().Return(net.PowLimit).AnyTimes() - btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - btclcKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) + h.BTCCheckpointKeeper.EXPECT().GetPowLimit().Return(h.Net.PowLimit).AnyTimes() + h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + h.BTCLightClientKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) - slashignSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() - require.NoError(t, err) + slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + h.NoError(err) // generate proper delegator sig delegatorSig, err := testStakingInfo.SlashingTx.Sign( testStakingInfo.StakingTx, 0, - slashignSpendInfo.GetPkScriptPath(), + slashingSpendInfo.GetPkScriptPath(), delSK, ) - require.NoError(t, err) + h.NoError(err) stakerPk := delSK.PubKey() stPk := bbn.NewBIP340PubKeyFromBTCPK(stakerPk) @@ -223,141 +176,133 @@ func createDelegation( SlashingTx: testStakingInfo.SlashingTx, DelegatorSig: delegatorSig, } - _, err = ms.CreateBTCDelegation(goCtx, msgCreateBTCDel) - require.NoError(t, err) + _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) + h.NoError(err) return stakingTxHash, delSK, delPK, msgCreateBTCDel } -func createCovenantSig( - t *testing.T, +func (h *Helper) CreateCovenantSigs( r *rand.Rand, - goCtx context.Context, - ms types.MsgServer, - bsKeeper *keeper.Keeper, - sdkCtx sdk.Context, - net *chaincfg.Params, - covenantSK *btcec.PrivateKey, + covenantSKs []*btcec.PrivateKey, msgCreateBTCDel *types.MsgCreateBTCDelegation, delegation *types.BTCDelegation, ) { stakingTx, err := bbn.NewBTCTxFromBytes(delegation.StakingTx) - require.NoError(t, err) + h.NoError(err) stakingTxHash := stakingTx.TxHash().String() - cPk := covenantSK.PubKey() + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + cPks, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) + h.NoError(err) - vPK := delegation.ValBtcPkList[0].MustToBTCPK() + vPKs, err := bbn.NewBTCPKsFromBIP340PKs(delegation.ValBtcPkList) + h.NoError(err) info, err := btcstaking.BuildStakingInfo( delegation.BtcPk.MustToBTCPK(), - []*btcec.PublicKey{vPK}, - []*btcec.PublicKey{cPk}, - 1, + vPKs, + cPks, + bsParams.CovenantQuorum, delegation.GetStakingTime(), btcutil.Amount(delegation.TotalSat), - net, + h.Net, ) + h.NoError(err) - require.NoError(t, err) slashingPathInfo, err := info.SlashingPathSpendInfo() - require.NoError(t, err) + h.NoError(err) - encKey, err := asig.NewEncryptionKeyFromBTCPK(vPK) - require.NoError(t, err) - covenantSig, err := msgCreateBTCDel.SlashingTx.EncSign( + // generate all covenant signatures from all covenant members + covenantSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + vPKs, stakingTx, - 0, slashingPathInfo.GetPkScriptPath(), - covenantSK, - encKey, + msgCreateBTCDel.SlashingTx, ) - require.NoError(t, err) - msgAddCovenantSig := &types.MsgAddCovenantSig{ - Signer: msgCreateBTCDel.Signer, - Pk: bbn.NewBIP340PubKeyFromBTCPK(cPk), - StakingTxHash: stakingTxHash, - Sigs: [][]byte{covenantSig.MustMarshal()}, + h.NoError(err) + + // each covenant member submits signatures + for i := 0; i < len(covenantSigs); i++ { + msgAddCovenantSig := &types.MsgAddCovenantSig{ + Signer: msgCreateBTCDel.Signer, + Pk: covenantSigs[i].CovPk, + StakingTxHash: stakingTxHash, + Sigs: covenantSigs[i].AdaptorSigs, + } + _, err = h.MsgServer.AddCovenantSig(h.Ctx, msgAddCovenantSig) + h.NoError(err) } - _, err = ms.AddCovenantSig(goCtx, msgAddCovenantSig) - require.NoError(t, err) /* ensure covenant sig is added successfully */ - actualDelWithCovenantSig, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) - require.NoError(t, err) - require.Equal(t, actualDelWithCovenantSig.CovenantSigs[0].AdaptorSigs[0], covenantSig.MustMarshal()) - require.True(t, actualDelWithCovenantSig.HasCovenantQuorum(bsKeeper.GetParams(sdkCtx).CovenantQuorum)) + actualDelWithCovenantSig, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.Equal(h.t, len(actualDelWithCovenantSig.CovenantSigs), int(bsParams.CovenantQuorum)) // TODO: fix + require.True(h.t, actualDelWithCovenantSig.HasCovenantQuorum(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) } -func getDelegationAndCheckValues( - t *testing.T, +func (h *Helper) GetDelegationAndCheckValues( r *rand.Rand, - ms types.MsgServer, - bsKeeper *keeper.Keeper, - sdkCtx sdk.Context, msgCreateBTCDel *types.MsgCreateBTCDelegation, validatorPK *btcec.PublicKey, delegatorPK *btcec.PublicKey, stakingTxHash string, ) *types.BTCDelegation { - actualDel, err := bsKeeper.GetBTCDelegation(sdkCtx, stakingTxHash) - require.NoError(t, err) - require.Equal(t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) - require.Equal(t, msgCreateBTCDel.Pop, actualDel.Pop) - require.Equal(t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx) - require.Equal(t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.Equal(h.t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) + require.Equal(h.t, msgCreateBTCDel.Pop, actualDel.Pop) + require.Equal(h.t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx) + require.Equal(h.t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) // ensure the BTC delegation in DB is correctly formatted err = actualDel.ValidateBasic() - require.NoError(t, err) + h.NoError(err) // delegation is not activated by covenant yet - require.False(t, actualDel.HasCovenantQuorum(bsKeeper.GetParams(sdkCtx).CovenantQuorum)) + require.False(h.t, actualDel.HasCovenantQuorum(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) return actualDel } -func createUndelegation( - t *testing.T, +func (h *Helper) CreateUndelegation( r *rand.Rand, - goCtx context.Context, - ms types.MsgServer, - net *chaincfg.Params, - btclcKeeper *types.MockBTCLightClientKeeper, actualDel *types.BTCDelegation, delSK *btcec.PrivateKey, validatorPK *btcec.PublicKey, - covenantPK *btcec.PublicKey, - slashingAddress, changeAddress string, - slashingRate sdkmath.LegacyDec, + changeAddress string, ) *types.MsgBTCUndelegate { stkTxHash, err := actualDel.GetStakingTxHash() - require.NoError(t, err) + h.NoError(err) stkOutputIdx := uint32(0) defaultParams := btcctypes.DefaultParams() unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 unbondingValue := int64(actualDel.TotalSat) - 1000 - + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) + h.NoError(err) testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( r, - t, - net, + h.t, + h.Net, delSK, []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covPKs, + bsParams.CovenantQuorum, wire.NewOutPoint(&stkTxHash, stkOutputIdx), unbondingTime, unbondingValue, - slashingAddress, changeAddress, - slashingRate, + bsParams.SlashingAddress, + changeAddress, + bsParams.SlashingRate, ) - require.NoError(t, err) + h.NoError(err) // random signer signer := datagen.GenRandomAccount().Address unbondingTxMsg := testUnbondingInfo.UnbondingTx unbondingSlashingPathInfo, err := testUnbondingInfo.UnbondingInfo.SlashingPathSpendInfo() - require.NoError(t, err) + h.NoError(err) sig, err := testUnbondingInfo.SlashingTx.Sign( unbondingTxMsg, @@ -365,10 +310,10 @@ func createUndelegation( unbondingSlashingPathInfo.GetPkScriptPath(), delSK, ) - require.NoError(t, err) + h.NoError(err) serializedUnbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) - require.NoError(t, err) + h.NoError(err) msg := &types.MsgBTCUndelegate{ Signer: signer, @@ -378,12 +323,59 @@ func createUndelegation( SlashingTx: testUnbondingInfo.SlashingTx, DelegatorSlashingSig: sig, } - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.BTCUndelegate(goCtx, msg) - require.NoError(t, err) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) + _, err = h.MsgServer.BTCUndelegate(h.Ctx, msg) + h.NoError(err) return msg } +func FuzzMsgCreateBTCValidator(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + h := NewHelper(t, nil, nil) + + // generate new BTC validators + btcVals := []*types.BTCValidator{} + for i := 0; i < int(datagen.RandomInt(r, 10)); i++ { + btcVal, err := datagen.GenRandomBTCValidator(r) + require.NoError(t, err) + msg := &types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + Description: btcVal.Description, + Commission: btcVal.Commission, + BabylonPk: btcVal.BabylonPk, + BtcPk: btcVal.BtcPk, + Pop: btcVal.Pop, + } + _, err = h.MsgServer.CreateBTCValidator(h.Ctx, msg) + require.NoError(t, err) + + btcVals = append(btcVals, btcVal) + } + // assert these validators exist in KVStore + for _, btcVal := range btcVals { + btcPK := *btcVal.BtcPk + require.True(t, h.BTCStakingKeeper.HasBTCValidator(h.Ctx, btcPK)) + } + + // duplicated BTC validators should not pass + for _, btcVal2 := range btcVals { + msg := &types.MsgCreateBTCValidator{ + Signer: datagen.GenRandomAccount().Address, + Description: btcVal2.Description, + Commission: btcVal2.Commission, + BabylonPk: btcVal2.BabylonPk, + BtcPk: btcVal2.BtcPk, + Pop: btcVal2.Pop, + } + _, err := h.MsgServer.CreateBTCValidator(h.Ctx, msg) + require.Error(t, err) + } + }) +} + func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) @@ -396,11 +388,10 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - ms := keeper.NewMsgServerImpl(*bsKeeper) + h := NewHelper(t, btclcKeeper, btccKeeper) - // set covenant PK to params - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) + // set covenant PKs to params + covenantSKs, _ := h.GenAndApplyParams(r) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) @@ -408,23 +399,15 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { /* generate and insert new BTC validator */ - _, validatorPK, _ := createValidator(t, r, ctx, ms) + _, validatorPK, _ := h.CreateValidator(r) /* generate and insert new BTC delegation */ - stakingTxHash, _, delPK, msgCreateBTCDel := createDelegation( - t, + stakingTxHash, _, delPK, msgCreateBTCDel := h.CreateDelegation( r, - ctx, - ms, - btccKeeper, - btclcKeeper, - net, validatorPK, - covenantPK, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + changeAddress.EncodeAddress(), 1000, ) @@ -432,12 +415,12 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { verify the new BTC delegation */ // check existence - actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) /* generate and insert new covenant signature */ - createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) }) } @@ -451,26 +434,14 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - ms := keeper.NewMsgServerImpl(*bsKeeper) + h := NewHelper(t, btclcKeeper, btccKeeper) // set covenant PK to params - _, covenantPK, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - slashingAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) + _, covenantPKs := h.GenAndApplyParams(r) + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - err = bsKeeper.SetParams(ctx, types.Params{ - CovenantPks: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(covenantPK)}, - CovenantQuorum: 1, - SlashingAddress: slashingAddress.EncodeAddress(), - MinSlashingTxFeeSat: 10, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), - SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), - MaxActiveBtcValidators: 100, - }) - require.NoError(t, err) // We only generate a validator, but not insert it into KVStore. So later // insertion of delegation should fail. @@ -490,12 +461,13 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { net, delSK, []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covenantPKs, + bsParams.CovenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + bsParams.SlashingAddress, + changeAddress.EncodeAddress(), + bsParams.SlashingRate, ) // get msgTx stakingMsgTx := testStakingInfo.StakingTx @@ -544,7 +516,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { SlashingTx: testStakingInfo.SlashingTx, DelegatorSig: delegatorSig, } - _, err = ms.CreateBTCDelegation(ctx, msgCreateBTCDel) + _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) require.Error(t, err) require.True(t, errors.Is(err, types.ErrBTCValNotFound)) } @@ -561,46 +533,30 @@ func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - ms := keeper.NewMsgServerImpl(*bsKeeper) + h := NewHelper(t, btclcKeeper, btccKeeper) - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) + covenantSKs, _ := h.GenAndApplyParams(r) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - _, validatorPK, _ := createValidator(t, r, ctx, ms) - stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( - t, + _, validatorPK, _ := h.CreateValidator(r) + stakingTxHash, delSK, delPK, msgCreateBTCDel := h.CreateDelegation( r, - ctx, - ms, - btccKeeper, - btclcKeeper, - net, validatorPK, - covenantPK, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + changeAddress.EncodeAddress(), 1000, ) - actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) - undelegateMsg := createUndelegation( - t, + undelegateMsg := h.CreateUndelegation( r, - ctx, - ms, - net, - btclcKeeper, actualDel, delSK, validatorPK, - covenantPK, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + changeAddress.EncodeAddress(), ) - actualDelegationWithUnbonding, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) + actualDelegationWithUnbonding, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) @@ -625,46 +581,32 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { // mock BTC light client and BTC checkpoint modules btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - bsKeeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - ms := keeper.NewMsgServerImpl(*bsKeeper) + h := NewHelper(t, btclcKeeper, btccKeeper) + + covenantSKs, covenantPKs := h.GenAndApplyParams(r) + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - covenantSK, covenantPK, slashingAddress := getCovenantInfo(t, r, ctx, ms, net, bsKeeper, ctx) changeAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - _, validatorPK, _ := createValidator(t, r, ctx, ms) - stakingTxHash, delSK, delPK, msgCreateBTCDel := createDelegation( - t, + _, validatorPK, _ := h.CreateValidator(r) + stakingTxHash, delSK, delPK, msgCreateBTCDel := h.CreateDelegation( r, - ctx, - ms, - btccKeeper, - btclcKeeper, - net, validatorPK, - covenantPK, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + changeAddress.EncodeAddress(), 1000, ) - actualDel := getDelegationAndCheckValues(t, r, ms, bsKeeper, ctx, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - createCovenantSig(t, r, ctx, ms, bsKeeper, ctx, net, covenantSK, msgCreateBTCDel, actualDel) + actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) - undelegateMsg := createUndelegation( - t, + undelegateMsg := h.CreateUndelegation( r, - ctx, - ms, - net, - btclcKeeper, actualDel, delSK, validatorPK, - covenantPK, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - bsKeeper.GetParams(ctx).SlashingRate, + changeAddress.EncodeAddress(), ) - del, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) + del, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, del.BtcUndelegation) @@ -679,34 +621,23 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { stakingInfo, err := btcstaking.BuildStakingInfo( del.BtcPk.MustToBTCPK(), []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covenantPKs, + bsParams.CovenantQuorum, uint16(del.GetStakingTime()), btcutil.Amount(del.TotalSat), net, ) require.NoError(t, err) - stakingUnbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() - require.NoError(t, err) - - unbondingTxSignatureCovenant, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - unbondingTx, - stakingTx, - del.StakingOutputIdx, - stakingUnbondingPathInfo.GetPkScriptPath(), - covenantSK, - ) - - covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(unbondingTxSignatureCovenant) + unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() require.NoError(t, err) // slash unbonding tx spends unbonding tx unbondingInfo, err := btcstaking.BuildUnbondingInfo( del.BtcPk.MustToBTCPK(), []*btcec.PublicKey{validatorPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covenantPKs, + bsParams.CovenantQuorum, uint16(del.BtcUndelegation.GetUnbondingTime()), btcutil.Amount(unbondingTx.TxOut[0].Value), net, @@ -718,36 +649,58 @@ func FuzzAddCovenantSigToUnbonding(f *testing.F) { enckey, err := asig.NewEncryptionKeyFromBTCPK(validatorPK) require.NoError(t, err) - slashUnbondingTxSignatureCovenant, err := undelegateMsg.SlashingTx.EncSign( - unbondingTx, - 0, - unbondingSlashingPathInfo.GetPkScriptPath(), - covenantSK, - enckey, - ) - require.NoError(t, err) - covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ - Signer: datagen.GenRandomAccount().Address, - Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - StakingTxHash: stakingTxHash, - UnbondingTxSig: &covenantUnbondingSig, - SlashingUnbondingTxSigs: [][]byte{slashUnbondingTxSignatureCovenant.MustMarshal()}, - } + // submit covenant signatures for each covenant member + for i := 0; i < int(bsParams.CovenantQuorum); i++ { + covenantUnbondingBTCSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + del.StakingOutputIdx, + unbondingPathInfo.GetPkScriptPath(), + covenantSKs[i], + ) + + covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingBTCSig) + require.NoError(t, err) - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = ms.AddCovenantUnbondingSigs(ctx, &covenantSigsMsg) - require.NoError(t, err) + slashUnbondingTxSig, err := undelegateMsg.SlashingTx.EncSign( + unbondingTx, + 0, + unbondingSlashingPathInfo.GetPkScriptPath(), + covenantSKs[i], + enckey, + ) + require.NoError(t, err) + + covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ + Signer: datagen.GenRandomAccount().Address, + Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantSKs[i].PubKey()), + StakingTxHash: stakingTxHash, + UnbondingTxSig: &covenantUnbondingSig, + SlashingUnbondingTxSigs: [][]byte{slashUnbondingTxSig.MustMarshal()}, + } + + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) + _, err = h.MsgServer.AddCovenantUnbondingSigs(h.Ctx, &covenantSigsMsg) + require.NoError(t, err) + } - delWithUnbondingSigs, err := bsKeeper.GetBTCDelegation(ctx, stakingTxHash) + delWithUnbondingSigs, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) require.NoError(t, err) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs) require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList) - require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList, 1) - require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs, 1) - require.True(t, bytes.Equal(delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs[0].CovPk.MustMarshal(), - bbn.NewBIP340PubKeyFromBTCPK(covenantPK).MustMarshal())) + require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList, int(bsParams.CovenantQuorum)) + require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs, int(bsParams.CovenantQuorum)) require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1) + + covPKMap := map[string]struct{}{} + for _, covPK := range covenantPKs { + covPKMap[bbn.NewBIP340PubKeyFromBTCPK(covPK).MarshalHex()] = struct{}{} + } + for _, actualCovSigs := range delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs { + _, ok := covPKMap[actualCovSigs.CovPk.MarshalHex()] + require.True(t, ok) + } }) } diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index d86aa4e04..be0148915 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -1,17 +1,17 @@ package keeper_test import ( - sdkmath "cosmossdk.io/math" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -32,8 +32,7 @@ func FuzzVotingPowerTable(f *testing.F) { keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -68,8 +67,8 @@ func FuzzVotingPowerTable(f *testing.F) { t, []bbn.BIP340PubKey{*btcVals[i].BtcPk}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, @@ -204,8 +203,7 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -236,8 +234,8 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { t, []bbn.BIP340PubKey{*valBTCPK}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -308,8 +306,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { keeper, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) // covenant and slashing addr - covenantSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) + covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) @@ -340,8 +337,8 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { t, []bbn.BIP340PubKey{*valBTCPK}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, @@ -388,8 +385,8 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { t, []bbn.BIP340PubKey{*activatedValBTCPK}, delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index 7b20d50d6..ce883796b 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -262,10 +262,9 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha } // TODO: work with restaking - // TODO: work with covenant committee - covAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(d.CovenantSigs[0].AdaptorSigs[0]) + covAdaptorSigs, err := GetOrderedCovenantSignatures(0, d.CovenantSigs, bsParams) if err != nil { - return nil, fmt.Errorf("failed to decode a covenant adaptor signature: %w", err) + return nil, fmt.Errorf("failed to get ordered covenant adaptor signatures: %w", err) } // assemble witness for slashing tx @@ -274,7 +273,7 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha stakingMsgTx, d.StakingOutputIdx, d.DelegatorSig, - covAdaptorSig, + covAdaptorSigs, slashingSpendInfo, ) if err != nil { @@ -306,10 +305,9 @@ func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, bt } // TODO: work with restaking - // TODO: work with covenant committee - covAdaptorSig, err := asig.NewAdaptorSignatureFromBytes(d.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs[0]) + covAdaptorSigs, err := GetOrderedCovenantSignatures(0, d.BtcUndelegation.CovenantSlashingSigs, bsParams) if err != nil { - return nil, fmt.Errorf("failed to decode a covenant adaptor signature: %w", err) + return nil, fmt.Errorf("failed to get ordered covenant adaptor signatures: %w", err) } // assemble witness for unbonding slashing tx @@ -318,7 +316,7 @@ func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, bt unbondingMsgTx, 0, d.BtcUndelegation.DelegatorSlashingSig, - covAdaptorSig, // TODO: accomodate covenant committee + covAdaptorSigs, slashingSpendInfo, ) if err != nil { diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index 2c80bc961..2158aad73 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -79,9 +79,10 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { require.NoError(t, err) valPKList := []*btcec.PublicKey{valPK} - covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + // (3, 5) covenant committee + covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) require.NoError(t, err) - covPKList := []*btcec.PublicKey{covenantPK} + covenantQuorum := uint32(3) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) @@ -101,8 +102,8 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { net, delSK, valPKList, - covPKList, - 1, + covenantPKs, + covenantQuorum, stakingTimeBlocks, stakingValue, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), @@ -120,14 +121,9 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { delSig, err := testInfo.SlashingTx.Sign(testInfo.StakingTx, 0, slashingSpendInfo.GetPkScriptPath(), delSK) require.NoError(t, err) // covenant signs (using adaptor signature) the slashing tx - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + covenantSigs, err := datagen.GenCovenantAdaptorSigs(covenantSKs, []*btcec.PublicKey{valPK}, testInfo.StakingTx, slashingSpendInfo.GetPkScriptPath(), testInfo.SlashingTx) require.NoError(t, err) - covenantSig, err := testInfo.SlashingTx.EncSign(testInfo.StakingTx, 0, slashingSpendInfo.GetPkScriptPath(), covenantSK, encKey) - require.NoError(t, err) - covenantSigs := &types.CovenantAdaptorSignatures{ - CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, - } + covenantSigs = covenantSigs[2:] // discard 2 out of 5 signatures // construct the BTC delegation with everything btcDel := &types.BTCDelegation{ @@ -142,12 +138,12 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { StakingOutputIdx: 0, SlashingTx: testInfo.SlashingTx, DelegatorSig: delSig, - CovenantSigs: []*types.CovenantAdaptorSignatures{covenantSigs}, + CovenantSigs: covenantSigs, } bsParams := &types.Params{ - CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covPKList), - CovenantQuorum: 1, + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: covenantQuorum, } btcNet := &chaincfg.SimNetParams diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 33a0df8c1..77f3728ed 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -7,6 +7,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" @@ -137,7 +138,7 @@ func (tx *BTCSlashingTx) Sign( func (tx *BTCSlashingTx) VerifySignature( stakingPkScript []byte, stakingAmount int64, - stakingScript []byte, + slashingPkScriptPath []byte, pk *btcec.PublicKey, sig *bbn.BIP340Signature) error { msgTx, err := tx.ToMsgTx() @@ -148,7 +149,7 @@ func (tx *BTCSlashingTx) VerifySignature( msgTx, stakingPkScript, stakingAmount, - stakingScript, + slashingPkScriptPath, pk, *sig, ) @@ -187,7 +188,7 @@ func (tx *BTCSlashingTx) EncSign( func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( stakingPkScript []byte, stakingAmount int64, - stakingScript []byte, + slashingPkScriptPath []byte, pk *btcec.PublicKey, encKey *asig.EncryptionKey, sig *asig.AdaptorSignature, @@ -200,7 +201,7 @@ func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( msgTx, stakingPkScript, stakingAmount, - stakingScript, + slashingPkScriptPath, pk, encKey, sig, @@ -212,7 +213,7 @@ func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( fundingMsgTx *wire.MsgTx, outputIdx uint32, delegatorSig *bbn.BIP340Signature, - covenantSig *asig.AdaptorSignature, + covenantSigs []*asig.AdaptorSignature, slashingPathSpendInfo *btcstaking.SpendInfo, ) (*wire.MsgTx, error) { valSig, err := tx.Sign(fundingMsgTx, outputIdx, slashingPathSpendInfo.GetPkScriptPath(), valSK) @@ -220,27 +221,27 @@ func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( return nil, fmt.Errorf("failed to sign slashing tx for the BTC validator: %w", err) } - stakerSigBytes := delegatorSig.MustMarshal() - validatorSigBytes := valSig.MustMarshal() - // decrypt covenant adaptor signature to Schnorr signature using validator's SK, // then marshal - // TODO: work with restaking - // TODO: use covenant committee decKey, err := asig.NewDecyptionKeyFromBTCSK(valSK) if err != nil { return nil, fmt.Errorf("failed to get decryption key from BTC SK: %w", err) } - covSig := covenantSig.Decrypt(decKey) - covSigBytes := bbn.NewBIP340SignatureFromBTCSig(covSig).MustMarshal() + + var covSigs []*schnorr.Signature + for _, covenantSig := range covenantSigs { + if covenantSig != nil { + covSigs = append(covSigs, covenantSig.Decrypt(decKey)) + } else { + covSigs = append(covSigs, nil) + } + } // construct witness - witness, err := slashingPathSpendInfo.CreateWitness( - [][]byte{ - covSigBytes, - validatorSigBytes, - stakerSigBytes, - }, + witness, err := slashingPathSpendInfo.CreateSlashingPathWitness( + covSigs, + []*schnorr.Signature{valSig.MustToBTCSig()}, // TODO: work with restaking + delegatorSig.MustToBTCSig(), ) if err != nil { return nil, err diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index 0e6ff6b53..f3a225cc2 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -5,9 +5,10 @@ import ( "testing" sdkmath "cosmossdk.io/math" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/require" @@ -35,12 +36,17 @@ func FuzzSlashingTxWithWitness(f *testing.F) { // Our goal is not to test failure due to such extreme cases here; // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + + // TODO: test restaking valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + + delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + + covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) require.NoError(t, err) + covenantQuorum := uint32(3) // generate staking/slashing tx testStakingInfo := datagen.GenBTCStakingSlashingInfo( @@ -49,8 +55,8 @@ func FuzzSlashingTxWithWitness(f *testing.F) { net, delSK, []*btcec.PublicKey{valPK}, - []*btcec.PublicKey{covenantPK}, - 1, + covenantPKs, + covenantQuorum, stakingTimeBlocks, stakingValue, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), @@ -59,32 +65,33 @@ func FuzzSlashingTxWithWitness(f *testing.F) { slashingTx := testStakingInfo.SlashingTx stakingMsgTx := testStakingInfo.StakingTx - stakingPkScript := testStakingInfo.StakingInfo.GetPkScript() slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - slashingScript := slashingSpendInfo.GetPkScriptPath() + slashingPkScriptPath := slashingSpendInfo.GetPkScriptPath() // sign slashing tx - valSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, valSK) - require.NoError(t, err) - delSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingScript, delSK) - require.NoError(t, err) - enckey, err := asig.NewEncryptionKeyFromBTCPK(valPK) - require.NoError(t, err) - covenantSig, err := slashingTx.EncSign(stakingMsgTx, 0, slashingScript, covenantSK, enckey) + delSig, err := slashingTx.Sign(stakingMsgTx, 0, slashingPkScriptPath, delSK) require.NoError(t, err) - // verify signatures first - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, valPK, valSig) - require.NoError(t, err) - err = slashingTx.VerifySignature(stakingPkScript, stakingValue, slashingScript, delPK, delSig) + covenantSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + []*btcec.PublicKey{valPK}, + stakingMsgTx, + slashingPkScriptPath, + slashingTx, + ) require.NoError(t, err) - err = slashingTx.EncVerifyAdaptorSignature(stakingPkScript, stakingValue, slashingScript, covenantPK, enckey, covenantSig) + + bsParams := types.Params{ + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: covenantQuorum, + } + covSigs, err := types.GetOrderedCovenantSignatures(0, covenantSigs, &bsParams) require.NoError(t, err) // create slashing tx with witness - slashingMsgTxWithWitness, err := slashingTx.BuildSlashingTxWithWitness(valSK, stakingMsgTx, 0, delSig, covenantSig, slashingSpendInfo) + slashingMsgTxWithWitness, err := slashingTx.BuildSlashingTxWithWitness(valSK, stakingMsgTx, 0, delSig, covSigs, slashingSpendInfo) require.NoError(t, err) // verify slashing tx with witness diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go index 884b6896a..e84f8a319 100644 --- a/x/btcstaking/types/btc_undelegation_test.go +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -5,7 +5,6 @@ import ( "testing" sdkmath "cosmossdk.io/math" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" btctest "github.com/babylonchain/babylon/testutil/bitcoin" "github.com/babylonchain/babylon/testutil/datagen" bbn "github.com/babylonchain/babylon/types" @@ -30,9 +29,10 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { require.NoError(t, err) valPKList := []*btcec.PublicKey{valPK} - covenantSK, covenantPK, err := datagen.GenRandomBTCKeyPair(r) + // (3, 5) covenant committee + covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) require.NoError(t, err) - covPKList := []*btcec.PublicKey{covenantPK} + covenantQuorum := uint32(3) stakingTimeBlocks := uint16(5) stakingValue := int64(2 * 10e8) @@ -49,8 +49,8 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { t, bbn.NewBIP340PKsFromBTCPKs(valPKList), delSK, - []*btcec.PrivateKey{covenantSK}, - 1, + covenantSKs, + covenantQuorum, slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), 1000, @@ -70,8 +70,8 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { net, delSK, valPKList, - covPKList, - 1, + covenantPKs, + covenantQuorum, wire.NewOutPoint(&stakingTxHash, 0), unbondingTime, unbondingValue, @@ -96,33 +96,33 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { delSK, ) require.NoError(t, err) + // covenant signs (using adaptor signature) the slashing tx - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) - require.NoError(t, err) - covenantSig, err := testInfo.SlashingTx.EncSign(testInfo.UnbondingTx, 0, unbondingSlashingSpendInfo.GetPkScriptPath(), covenantSK, encKey) + covenantSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + []*btcec.PublicKey{valPK}, + testInfo.UnbondingTx, + unbondingSlashingSpendInfo.GetPkScriptPath(), + testInfo.SlashingTx, + ) require.NoError(t, err) - covenantSigs := &types.CovenantAdaptorSignatures{ - CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantPK), - AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, - } btcDel.BtcUndelegation = &types.BTCUndelegation{ UnbondingTx: unbondingTxBytes, UnbondingTime: 100 + 1, SlashingTx: testInfo.SlashingTx, DelegatorSlashingSig: delSig, - CovenantSlashingSigs: []*types.CovenantAdaptorSignatures{covenantSigs}, + CovenantSlashingSigs: covenantSigs, CovenantUnbondingSigList: nil, // not relevant here } bsParams := &types.Params{ - CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covPKList), - CovenantQuorum: 1, + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: covenantQuorum, } - btcNet := &chaincfg.SimNetParams // build slashing tx with witness for spending the unbonding tx - unbondingSlashingTxWithWitness, err := btcDel.BuildUnbondingSlashingTxWithWitness(bsParams, btcNet, valSK) + unbondingSlashingTxWithWitness, err := btcDel.BuildUnbondingSlashingTxWithWitness(bsParams, net, valSK) require.NoError(t, err) // assert the execution diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index a920fb791..7f9b80bb8 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" ) @@ -71,3 +72,44 @@ func NewSignatureInfo(pk *bbn.BIP340PubKey, sig *bbn.BIP340Signature) *Signature Sig: sig, } } + +// GetOrderedCovenantSignatures returns the ordered covenant adaptor signatures +// encrypted by the BTC validator's PK at the given index from the given list of +// covenant signatures +// the order of covenant adaptor signatures will follow the reverse lexicographical order +// of signing public keys, in order to be used as tx witness +func GetOrderedCovenantSignatures(valIdx int, covSigsList []*CovenantAdaptorSignatures, params *Params) ([]*asig.AdaptorSignature, error) { + // construct the map where key is the covenant PK and value is this + // covenant member's adaptor signature encrypted by the given validator's PK + covSigsMap := map[string]*asig.AdaptorSignature{} + for _, covSigs := range covSigsList { + // find the adaptor signature at the corresponding BTC validator's index + if valIdx >= len(covSigs.AdaptorSigs) { + return nil, fmt.Errorf("validator index is out of the scope") + } + covSigBytes := covSigs.AdaptorSigs[valIdx] + // decode the adaptor signature bytes + covSig, err := asig.NewAdaptorSignatureFromBytes(covSigBytes) + if err != nil { + return nil, err + } + // append to map + covSigsMap[covSigs.CovPk.MarshalHex()] = covSig + } + + // sort covenant PKs in reverse reverse lexicographical order + orderedCovenantPKs := bbn.SortBIP340PKs(params.CovenantPks) + + // get ordered list of covenant signatures w.r.t. the order of sorted covenant PKs + // Note that only a quorum number of covenant signatures needs to be provided + orderedCovSigs := []*asig.AdaptorSignature{} + for _, covPK := range orderedCovenantPKs { + if covSig, ok := covSigsMap[covPK.MarshalHex()]; ok { + orderedCovSigs = append(orderedCovSigs, covSig) + } else { + orderedCovSigs = append(orderedCovSigs, nil) + } + } + + return orderedCovSigs, nil +} diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index f7b19811f..f4b5a0ea1 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/cometbft/cometbft/crypto/tmhash" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -19,12 +20,17 @@ const ( var _ paramtypes.ParamSet = (*Params)(nil) -// TODO: default values for multisig covenant -func defaultCovenantPks() []bbn.BIP340PubKey { - // 32 bytes - skBytes := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - _, defaultPK := btcec.PrivKeyFromBytes(skBytes) - return []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(defaultPK)} +// DefaultCovenantCommittee deterministically generates a covenant committee +// with 5 members and quorum size of 3 +func DefaultCovenantCommittee() ([]*btcec.PrivateKey, []*btcec.PublicKey, uint32) { + sks, pks := []*btcec.PrivateKey{}, []*btcec.PublicKey{} + for i := uint8(0); i < 5; i++ { + skBytes := tmhash.Sum([]byte{i}) + sk, pk := btcec.PrivKeyFromBytes(skBytes) + sks = append(sks, sk) + pks = append(pks, pk) + } + return sks, pks, 3 } func defaultSlashingAddress() string { @@ -44,9 +50,10 @@ func ParamKeyTable() paramtypes.KeyTable { // DefaultParams returns a default set of parameters func DefaultParams() Params { + _, pks, quorum := DefaultCovenantCommittee() return Params{ - CovenantPks: defaultCovenantPks(), // TODO: default values for multisig covenant - CovenantQuorum: 1, // TODO: default values for multisig covenant + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(pks), + CovenantQuorum: quorum, SlashingAddress: defaultSlashingAddress(), MinSlashingTxFeeSat: 1000, MinCommissionRate: sdkmath.LegacyZeroDec(), @@ -97,10 +104,8 @@ func (p Params) Validate() error { if p.CovenantQuorum == 0 { return fmt.Errorf("covenant quorum size has to be positive") } - if p.CovenantQuorum*3 <= uint32(len(p.CovenantPks))*2 { - // NOTE: we assume covenant member can be adversarial, including - // equivocation, so >2/3 quorum is needed - return fmt.Errorf("covenant quorum size has to be more than 2/3 of the covenant committee size") + if p.CovenantQuorum*2 <= uint32(len(p.CovenantPks))*1 { + return fmt.Errorf("covenant quorum size has to be more than 1/2 of the covenant committee size") } if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { return err From 84fe48e80015b30cab8967a936fbd01438f25926 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Mon, 4 Dec 2023 12:10:36 +0400 Subject: [PATCH 117/202] feat: Add new btc staking parameters to genesis CLI args (#136) --- cmd/babylond/cmd/flags.go | 21 ++++++++++++++++----- cmd/babylond/cmd/genesis.go | 24 +++++++++++++++--------- cmd/babylond/cmd/testnet.go | 5 +++-- x/btcstaking/types/params.go | 21 ++++++++++++++++++++- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 00a35a91f..08befd304 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -2,6 +2,7 @@ package cmd import ( "cosmossdk.io/math" + "strings" "time" babylonApp "github.com/babylonchain/babylon/app" @@ -27,7 +28,9 @@ const ( flagBlocksPerYear = "blocks-per-year" flagGenesisTime = "genesis-time" flagBlockGasLimit = "block-gas-limit" - flagCovenantPk = "covenant-pk" // TODO: multisig covenant + flagCovenantPks = "covenant-pks" + flagCovenantQuorum = "covenant-quorum" + flagMaxActiveBTCValidators = "max-active-btc-validators" flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" flagSlashingRate = "slashing-rate" @@ -51,10 +54,12 @@ type GenesisCLIArgs struct { BlocksPerYear uint64 GenesisTime time.Time BlockGasLimit int64 - CovenantPK string + CovenantPKs []string + CovenantQuorum uint32 SlashingAddress string MinSlashingTransactionFeeSat int64 SlashingRate math.LegacyDec + MaxActiveBTCValidators uint32 MinPubRand uint64 MinCommissionRate math.LegacyDec } @@ -74,11 +79,13 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") // btcstaking args - cmd.Flags().String(flagCovenantPk, btcstypes.DefaultParams().CovenantPks[0].MarshalHex(), "Bitcoin staking covenant public key") + cmd.Flags().String(flagCovenantPks, strings.Join(btcstypes.DefaultParams().CovenantPksHex(), ","), "Bitcoin staking covenant public keys, comma separated") + cmd.Flags().Uint32(flagCovenantQuorum, btcstypes.DefaultParams().CovenantQuorum, "Bitcoin staking covenant quorum") cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") cmd.Flags().String(flagSlashingRate, "0.1", "Bitcoin staking slashing rate") + cmd.Flags().Uint32(flagMaxActiveBTCValidators, 100, "Bitcoin staking maximum active BTC validators") // finality args cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") // inflation args @@ -102,11 +109,13 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { epochInterval, _ := cmd.Flags().GetUint64(flagEpochInterval) baseBtcHeaderHex, _ := cmd.Flags().GetString(flagBaseBtcHeaderHex) baseBtcHeaderHeight, _ := cmd.Flags().GetUint64(flagBaseBtcHeaderHeight) - covenantPk, _ := cmd.Flags().GetString(flagCovenantPk) + covenantPks, _ := cmd.Flags().GetString(flagCovenantPks) + covenantQuorum, _ := cmd.Flags().GetUint32(flagCovenantQuorum) slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) slashingRate, _ := cmd.Flags().GetString(flagSlashingRate) + maxActiveBTCValidators, _ := cmd.Flags().GetUint32(flagMaxActiveBTCValidators) minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) @@ -131,11 +140,13 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { EpochInterval: epochInterval, BaseBtcHeaderHeight: baseBtcHeaderHeight, BaseBtcHeaderHex: baseBtcHeaderHex, - CovenantPK: covenantPk, + CovenantPKs: strings.Split(covenantPks, ","), + CovenantQuorum: covenantQuorum, SlashingAddress: slashingAddress, MinSlashingTransactionFeeSat: minSlashingFee, MinCommissionRate: math.LegacyMustNewDecFromStr(minCommissionRate), SlashingRate: math.LegacyMustNewDecFromStr(slashingRate), + MaxActiveBTCValidators: maxActiveBTCValidators, MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index b4177c1b0..2bee1fe00 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -69,9 +69,9 @@ Example: genesisParams = TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPK, + genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, + genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveBTCValidators, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) @@ -231,8 +231,9 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, - baseBtcHeaderHeight uint64, covenantPk string, slashingAddress string, minSlashingFee int64, - minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, minPubRand uint64, inflationRateChange float64, + baseBtcHeaderHeight uint64, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, + minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveBTCValidators uint32, + minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -306,16 +307,21 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtclightclientBaseBtcHeader = *baseBtcHeaderInfo genParams.BtcstakingParams = btcstakingtypes.DefaultParams() - covenantPK, err := bbn.NewBIP340PubKeyFromHex(covenantPk) - if err != nil { - panic(err) + covenantPKsBIP340 := make([]bbn.BIP340PubKey, 0, len(covenantPKs)) + for _, pkHex := range covenantPKs { + pk, err := bbn.NewBIP340PubKeyFromHex(pkHex) + if err != nil { + panic(err) + } + covenantPKsBIP340 = append(covenantPKsBIP340, *pk) } - genParams.BtcstakingParams.CovenantPks = []bbn.BIP340PubKey{*covenantPK} - genParams.BtcstakingParams.CovenantQuorum = 1 // TODO: multisig covenant + genParams.BtcstakingParams.CovenantPks = covenantPKsBIP340 + genParams.BtcstakingParams.CovenantQuorum = covenantQuorum genParams.BtcstakingParams.SlashingAddress = slashingAddress genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee genParams.BtcstakingParams.MinCommissionRate = minCommissionRate genParams.BtcstakingParams.SlashingRate = slashingRate + genParams.BtcstakingParams.MaxActiveBtcValidators = maxActiveBTCValidators if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index b4707b458..0210279d3 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -98,8 +98,9 @@ Example: genesisParams := TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, - genesisCliArgs.CovenantPK, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MinPubRand, + genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, + genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, + genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveBTCValidators, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index f4b5a0ea1..5eab880d9 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -99,14 +99,25 @@ func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { return nil } +// validateCovenantPks checks whether the covenants list contains any duplicates +func validateCovenantPks(covenantPks []bbn.BIP340PubKey) error { + if ExistsDup(covenantPks) { + return fmt.Errorf("duplicate covenant key") + } + return nil +} + // Validate validates the set of params func (p Params) Validate() error { if p.CovenantQuorum == 0 { return fmt.Errorf("covenant quorum size has to be positive") } - if p.CovenantQuorum*2 <= uint32(len(p.CovenantPks))*1 { + if p.CovenantQuorum*2 <= uint32(len(p.CovenantPks)) { return fmt.Errorf("covenant quorum size has to be more than 1/2 of the covenant committee size") } + if err := validateCovenantPks(p.CovenantPks); err != nil { + return err + } if err := validateMinSlashingTxFeeSat(p.MinSlashingTxFeeSat); err != nil { return err } @@ -147,3 +158,11 @@ func (p Params) MustGetSlashingAddress(btcParams *chaincfg.Params) btcutil.Addre } return slashingAddr } + +func (p Params) CovenantPksHex() []string { + covPksHex := make([]string, 0, len(p.CovenantPks)) + for _, pk := range p.CovenantPks { + covPksHex = append(covPksHex, pk.MarshalHex()) + } + return covPksHex +} From d9b6629dcf13c79b0fb57f1d90477f85d8728ea0 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Tue, 5 Dec 2023 08:57:26 +0400 Subject: [PATCH 118/202] fix: Do not remove the ./config directory when initiating a priv signer (#137) --- app/test_helpers.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/test_helpers.go b/app/test_helpers.go index 2e65ab15a..a9e1bc895 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -8,13 +8,13 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/server" + "github.com/docker/docker/pkg/ioutils" "math/rand" + "os" "testing" "time" "cosmossdk.io/math" - cmtconfig "github.com/cometbft/cometbft/config" - tmjson "github.com/cometbft/cometbft/libs/json" "cosmossdk.io/log" @@ -253,10 +253,16 @@ func SetupPrivSigner() (*PrivSigner, error) { if err != nil { return nil, err } - nodeCfg := cmtconfig.DefaultConfig() encodingCfg := appparams.DefaultEncodingConfig() - privSigner, _ := InitPrivSigner(client.Context{}, ".", kr, "", encodingCfg) - privSigner.WrappedPV.Clean(nodeCfg.PrivValidatorKeyFile(), nodeCfg.PrivValidatorStateFile()) + // Create a temporary node directory + nodeDir, err := ioutils.TempDir("", "tmp-signer") + if err != nil { + return nil, err + } + defer func() { + _ = os.RemoveAll(nodeDir) + }() + privSigner, _ := InitPrivSigner(client.Context{}, nodeDir, kr, "", encodingCfg) return privSigner, nil } From acd8ccf9730571e1b4c1a9d6b0fa9d9384882687 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Tue, 5 Dec 2023 12:19:59 +0400 Subject: [PATCH 119/202] fix: Do not allow commission that is more than 100% (#138) --- x/btcstaking/keeper/msg_server.go | 5 +++++ x/btcstaking/types/errors.go | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index aedc9a365..1a82ebb13 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -2,6 +2,7 @@ package keeper import ( "context" + sdkmath "cosmossdk.io/math" "fmt" "math" @@ -60,6 +61,10 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea return nil, types.ErrCommissionLTMinRate.Wrapf("cannot set validator commission to less than minimum rate of %s", ms.MinCommissionRate(ctx)) } + if req.Commission.GT(sdkmath.LegacyOneDec()) { + return nil, types.ErrCommissionGTMaxRate + } + // ensure BTC validator does not exist before if ms.HasBTCValidator(ctx, *req.BtcPk) { return nil, types.ErrDuplicatedBTCVal diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 518d131af..418ac8d30 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -20,10 +20,11 @@ var ( ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1111, "the BTC delegation has already received this covenant signature") ErrInvalidCovenantSig = errorsmod.Register(ModuleName, 1112, "the covenant signature is not valid") ErrCommissionLTMinRate = errorsmod.Register(ModuleName, 1113, "commission cannot be less than min rate") - ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1114, "Unexpected delegation state") - ErrInvalidUnbondingTx = errorsmod.Register(ModuleName, 1115, "the BTC unbonding tx is not valid") - ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1116, "the reward distribution cache is not found") - ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1117, "the validator list is empty") - ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1118, "the proof of possession is not valid") - ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1119, "the staking request contains duplicated validator public key") + ErrCommissionGTMaxRate = errorsmod.Register(ModuleName, 1114, "commission cannot be more than one") + ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1115, "Unexpected delegation state") + ErrInvalidUnbondingTx = errorsmod.Register(ModuleName, 1116, "the BTC unbonding tx is not valid") + ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1117, "the reward distribution cache is not found") + ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1118, "the validator list is empty") + ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1119, "the proof of possession is not valid") + ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1120, "the staking request contains duplicated validator public key") ) From 83d72c602dadc72fe325d0a514c57eacd20dfcf8 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 6 Dec 2023 19:39:33 +0400 Subject: [PATCH 120/202] fix: Use correct acount address codec (#139) --- app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index e043db380..b2d8f74c2 100644 --- a/app/app.go +++ b/app/app.go @@ -1239,7 +1239,7 @@ func (app *BabylonApp) AutoCliOpts() autocli.AppOptions { return autocli.AppOptions{ Modules: modules, ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), - AddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), + AddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixAccAddr), ValidatorAddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), ConsensusAddressCodec: authcodec.NewBech32Codec(appparams.Bech32PrefixConsAddr), } From 1dfc9c88278dbd67268c8545424c9fc05020f080 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 7 Dec 2023 07:54:01 +1100 Subject: [PATCH 121/202] btcstaking: presigning unbonding tx (#135) --- go.mod | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 35 +- proto/babylon/btcstaking/v1/tx.proto | 103 +- test/e2e/btc_staking_e2e_test.go | 277 +--- .../configurer/chain/commands_btcstaking.go | 99 +- .../configurer/chain/queries_btcstaking.go | 22 +- testutil/datagen/btc_transaction.go | 10 +- testutil/datagen/btcstaking.go | 192 ++- testutil/datagen/covenant.go | 59 + testutil/datagen/finality.go | 3 +- testutil/datagen/raw_checkpoint.go | 7 +- types/btc_schnorr_sig.go | 5 +- types/btc_schnorr_sig_test.go | 2 +- x/btcstaking/client/cli/tx.go | 241 +-- x/btcstaking/client/cli/utils.go | 49 + x/btcstaking/keeper/btc_delegators.go | 64 +- x/btcstaking/keeper/grpc_query_test.go | 115 -- x/btcstaking/keeper/msg_server.go | 532 +++--- x/btcstaking/keeper/msg_server_test.go | 540 +++--- .../keeper/voting_power_table_test.go | 11 +- x/btcstaking/types/btc_delegation.go | 90 +- x/btcstaking/types/btc_delegation_test.go | 3 + x/btcstaking/types/btc_slashing_tx.go | 3 +- x/btcstaking/types/btc_undelegation.go | 7 +- x/btcstaking/types/btc_undelegation_test.go | 17 +- x/btcstaking/types/btcstaking.pb.go | 251 +-- x/btcstaking/types/codec.go | 8 +- x/btcstaking/types/errors.go | 1 + x/btcstaking/types/msg.go | 99 +- x/btcstaking/types/tx.pb.go | 1445 +++++++---------- 30 files changed, 1862 insertions(+), 2430 deletions(-) create mode 100644 testutil/datagen/covenant.go create mode 100644 x/btcstaking/client/cli/utils.go diff --git a/go.mod b/go.mod index 5dd4cfceb..843c075d4 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.0.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + github.com/docker/docker v23.0.1+incompatible github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/hashicorp/go-metrics v0.5.1 @@ -161,7 +162,6 @@ require ( github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v23.0.1+incompatible // indirect - github.com/docker/docker v23.0.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/dot v1.6.0 // indirect diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 681f58d38..0fddb4e6e 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -112,18 +112,24 @@ message BTCUndelegation { // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + // delegator_unbonding_sig is the signature on the unbonding tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It effectively proves that the delegator wants to unbond and thus + // Babylon will consider this BTC delegation unbonded. Delegator's BTC + // on Bitcoin will be unbonded after timelock + bytes delegator_unbonding_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbonding tx output. - bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_slashing_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx // by each covenant member // It will be a part of the witness for the staking tx output. - repeated CovenantAdaptorSignatures covenant_slashing_sigs = 5; + repeated CovenantAdaptorSignatures covenant_slashing_sigs = 6; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon - repeated SignatureInfo covenant_unbonding_sig_list = 6; + repeated SignatureInfo covenant_unbonding_sig_list = 7; } @@ -151,28 +157,21 @@ message BTCDelegatorDelegationIndex { repeated bytes staking_tx_hash_list = 1; } -// BTCDelegationStatus is the status of a delegation. There are two possible state -// transition paths: -// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path when timelock of staking -// transaction expires. -// 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through -// MsgBTCUndelegate message. +// BTCDelegationStatus is the status of a delegation. The state transition path is +// PENDING -> ACTIVE -> UNBONDED with two possibilities: +// 1. the typical path when timelock of staking transaction expires. +// 2. the path when staker requests early undelegation through MsgBTCUndelegate message. enum BTCDelegationStatus { - // PENDING defines a delegation that is waiting for a covenant signature to become active. + // PENDING defines a delegation that is waiting for covenant signatures to become active. PENDING = 0; // ACTIVE defines a delegation that has voting power ACTIVE = 1; - // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx - // from staker, but not yet received signatures from validator and covenant. - // Delegation in this state already lost its voting power. - UNBONDING = 2; // UNBONDED defines a delegation no longer has voting power: // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx and then receiving signatures from validator and covenant for this - // unbonding tx. - UNBONDED = 3; + // - or receiving unbonding tx with signatures from staker and covenant committee + UNBONDED = 2; // ANY is any of the above status - ANY = 4; + ANY = 3; } // SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index b8564c08f..4cbad4fb8 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -13,6 +13,7 @@ import "babylon/btcstaking/v1/pop.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; // Msg defines the Msg service. +// TODO: handle unbonding tx with full witness service Msg { option (cosmos.msg.v1.service) = true; @@ -20,16 +21,12 @@ service Msg { rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); // CreateBTCDelegation creates a new BTC delegation rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); - // BtcUndelegate undelegates funds from exsitng btc delegation - rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); - // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation - rpc AddCovenantSig(MsgAddCovenantSig) returns (MsgAddCovenantSigResponse); - // AddCovenantUnbondingSigs handles two signatures from covenant for: - // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbonding tx submitted to babylon by staker - rpc AddCovenantUnbondingSigs(MsgAddCovenantUnbondingSigs) returns (MsgAddCovenantUnbondingSigsResponse); + // AddCovenantSigs handles signatures from a covenant member + rpc AddCovenantSigs(MsgAddCovenantSigs) returns (MsgAddCovenantSigsResponse); // UpdateParams updates the btcstaking module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); + // BTCUndelegate handles a signature on unbonding tx from its delegator + rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); } // MsgCreateBTCValidator is the message for creating a BTC validator @@ -79,40 +76,32 @@ message MsgCreateBTCDelegation { // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; - // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from covenant and validator in // order to be spendable. - bytes delegator_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; -} -// MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation -message MsgCreateBTCDelegationResponse {} + bytes delegator_slashing_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; -// MsgBTCUndelegate is the message undelegating existing and active delegation -message MsgBTCUndelegate { - option (cosmos.msg.v1.signer) = "signer"; - - string signer = 1; + /// fields related to on-demand unbonding // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output - bytes unbonding_tx = 2; + bytes unbonding_tx = 11; // unbonding_time is the time lock used in unbonding transaction - uint32 unbonding_time = 3; + uint32 unbonding_time = 12; // unbonding_value is amount of satoshis locked in unbonding output. // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding - int64 unbonding_value = 4; - // slashing_tx is the slashing tx which slash unbonding contract + int64 unbonding_value = 13; + // unbonding_slashing_tx is the slashing tx which slash unbonding contract // Note that the tx itself does not contain signatures, which are off-chain. - bytes slashing_tx = 5 [ (gogoproto.customtype) = "BTCSlashingTx" ]; - // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). - bytes delegator_slashing_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes unbonding_slashing_tx = 14 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + // delegator_unbonding_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + bytes delegator_unbonding_slashing_sig = 15 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } +// MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation +message MsgCreateBTCDelegationResponse {} -// MsgBtcUndelegateResponse is the response for MsgBtcUndelegate -message MsgBTCUndelegateResponse {} - -// MsgAddCovenantSig is the message for handling a signature from covenant -message MsgAddCovenantSig { +// MsgAddCovenantSigs is the message for handling signatures from a covenant member +message MsgAddCovenantSigs { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; @@ -124,10 +113,36 @@ message MsgAddCovenantSig { // sigs is a list of adaptor signatures of the covenant // the order of sigs should respect the order of validators // of the corresponding delegation - repeated bytes sigs = 4; + repeated bytes slashing_tx_sigs = 4; + // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon + // the signature follows encoding in BIP-340 spec + bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant + // on slashing tx corresponding to unbonding tx submitted to babylon + // the order of sigs should respect the order of validators + // of the corresponding delegation + repeated bytes slashing_unbonding_tx_sigs = 6; } -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -message MsgAddCovenantSigResponse {} +// MsgAddCovenantSigsResponse is the response for MsgAddCovenantSigs +message MsgAddCovenantSigsResponse {} + + +// MsgBTCUndelegate is the message for handling signature on unbonding tx +// from its delegator. This signature effectively proves that the delegator +// wants to unbond this BTC delegation +message MsgBTCUndelegate { + option (cosmos.msg.v1.signer) = "signer"; + + string signer = 1; + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + string staking_tx_hash = 2; + // unbonding_tx_sig is the signature of the staker on the unbonding tx submitted to babylon + // the signature follows encoding in BIP-340 spec + bytes unbonding_tx_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; +} +// MsgBTCUndelegateResponse is the response for MsgBTCUndelegate +message MsgBTCUndelegateResponse {} // MsgUpdateParams defines a message for updating btcstaking module parameters. message MsgUpdateParams { @@ -147,25 +162,3 @@ message MsgUpdateParams { // MsgUpdateParamsResponse is the response to the MsgUpdateParams message. message MsgUpdateParamsResponse {} - -// MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant -message MsgAddCovenantUnbondingSigs { - option (cosmos.msg.v1.signer) = "signer"; - - string signer = 1; - // pk is the BTC public key of the covenant member - bytes pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - string staking_tx_hash = 3; - // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon - // the signature follows encoding in BIP-340 spec - bytes unbonding_tx_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant - // on slashing tx corresponding to unbonding tx submitted to babylon - // the order of sigs should respect the order of validators - // of the corresponding delegation - repeated bytes slashing_unbonding_tx_sigs = 5; -} -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -message MsgAddCovenantUnbondingSigsResponse {} diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 51f675add..332d00027 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -8,15 +8,12 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" - "github.com/babylonchain/babylon/btcstaking" "github.com/babylonchain/babylon/crypto/eots" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonchain/babylon/test/e2e/configurer" "github.com/babylonchain/babylon/test/e2e/initialization" "github.com/babylonchain/babylon/test/e2e/util" @@ -133,7 +130,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { // generate proper delegator sig delegatorSig, err := testStakingInfo.SlashingTx.Sign( stakingMsgTx, - 0, + datagen.StakingOutIdx, stakingSlashingPathInfo.GetPkScriptPath(), delBTCSK, ) @@ -150,6 +147,27 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { } stakingTxInfo := btcctypes.NewTransactionInfoFromSpvProof(blockWithStakingTx.SpvProof) + // generate BTC undelegation stuff + stkTxHash := testStakingInfo.StakingTx.TxHash() + unbondingTime := initialization.BabylonBtcFinalizationPeriod + 1 + unbondingValue := stakingValue - datagen.UnbondingTxFee // TODO: parameterise fee + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( + r, + s.T(), + net, + delBTCSK, + []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + covenantBTCPKs, + covenantQuorum, + wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), + uint16(unbondingTime), + unbondingValue, + params.SlashingAddress, changeAddress.EncodeAddress(), + params.SlashingRate, + ) + delUnbondingSlashingSig, err := testUnbondingInfo.GenDelSlashingTxSig(delBTCSK) + s.NoError(err) + // submit the message for creating BTC delegation nonValidatorNode.CreateBTCDelegation( delBabylonSK.PubKey().(*secp256k1.PubKey), @@ -161,6 +179,11 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { btcutil.Amount(stakingValue), testStakingInfo.SlashingTx, delegatorSig, + testUnbondingInfo.UnbondingTx, + testUnbondingInfo.SlashingTx, + uint16(unbondingTime), + btcutil.Amount(unbondingValue), + delUnbondingSlashingSig, ) // wait for a block so that above txs take effect @@ -202,23 +225,11 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { stakingTxHash := stakingMsgTx.TxHash().String() params := nonValidatorNode.QueryBTCStakingParams() - covenantBTCPKs := []*btcec.PublicKey{} - for _, covenantPK := range params.CovenantPks { - covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) - } validatorBTCPKs, err := bbn.NewBTCPKsFromBIP340PKs(pendingDel.ValBtcPkList) s.NoError(err) - stakingInfo, err := btcstaking.BuildStakingInfo( - pendingDel.BtcPk.MustToBTCPK(), - validatorBTCPKs, - covenantBTCPKs, - params.CovenantQuorum, - pendingDel.GetStakingTime(), - btcutil.Amount(pendingDel.TotalSat), - net, - ) + stakingInfo, err := pendingDel.GetStakingInfo(params, net) s.NoError(err) stakingSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() @@ -227,7 +238,8 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { /* generate and insert new covenant signature, in order to activate the BTC delegation */ - covenantSigs, err := datagen.GenCovenantAdaptorSigs( + // covenant signatures on slashing tx + covenantSlashingSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, validatorBTCPKs, stakingMsgTx, @@ -235,22 +247,58 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { slashingTx, ) s.NoError(err) - s.GreaterOrEqual(uint32(len(covenantSigs)), covenantQuorum) + + // cov Schnorr sigs on unbonding signature + unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() + s.NoError(err) + unbondingTx, err := bbn.NewBTCTxFromBytes(pendingDel.BtcUndelegation.UnbondingTx) + s.NoError(err) + + covUnbondingSigs, err := datagen.GenCovenantUnbondingSigs( + covenantSKs, + stakingMsgTx, + pendingDel.StakingOutputIdx, + unbondingPathInfo.GetPkScriptPath(), + unbondingTx, + ) + s.NoError(err) + + unbondingInfo, err := pendingDel.GetUnbondingInfo(params, net) + s.NoError(err) + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + s.NoError(err) + covenantUnbondingSlashingSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + validatorBTCPKs, + unbondingTx, + unbondingSlashingPathInfo.GetPkScriptPath(), + pendingDel.BtcUndelegation.SlashingTx, + ) + s.NoError(err) + for i := 0; i < int(covenantQuorum); i++ { - nonValidatorNode.AddCovenantSigs(covenantSigs[i].CovPk, stakingTxHash, covenantSigs[i].AdaptorSigs) + nonValidatorNode.AddCovenantSigs( + covenantSlashingSigs[i].CovPk, + stakingTxHash, + covenantSlashingSigs[i].AdaptorSigs, + bbn.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), + covenantUnbondingSlashingSigs[i].AdaptorSigs, + ) + // wait for a block so that above txs take effect + nonValidatorNode.WaitForNextBlock() } // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() nonValidatorNode.WaitForNextBlock() - // ensure the BTC delegation has covenant sig now + // ensure the BTC delegation has covenant sigs now activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) s.Len(activeDelsSet, 1) activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) activeDel := activeDels.Dels[0] - s.NotNil(activeDel.CovenantSigs) + s.True(activeDel.HasCovenantQuorums(covenantQuorum)) // wait for a block so that above txs take effect and the voting power table // is updated in the next block's BeginBlock @@ -434,189 +482,22 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { activeDel := activeDels.Dels[0] s.NotNil(activeDel.CovenantSigs) - // params for covenantPk and slashing address - params := nonValidatorNode.QueryBTCStakingParams() - // get covenant BTC PKs - covenantBTCPKs := []*btcec.PublicKey{} - for _, covenantPK := range params.CovenantPks { - covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) - } - - validatorBTCPKs := []*btcec.PublicKey{} - for _, valPK := range activeDel.ValBtcPkList { - validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) - } - + // staking tx hash stakingMsgTx, err := bbn.NewBTCTxFromBytes(activeDel.StakingTx) s.NoError(err) - stakingTxHash := stakingMsgTx.TxHash().String() - stakingTxChainHash, err := chainhash.NewHashFromStr(stakingTxHash) - s.NoError(err) - - fee := int64(1000) - testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( - r, - s.T(), - net, - delBTCSK, - validatorBTCPKs, - covenantBTCPKs, - params.CovenantQuorum, - wire.NewOutPoint(stakingTxChainHash, uint32(activeDel.StakingOutputIdx)), - initialization.BabylonBtcFinalizationPeriod+1, - stakingValue-fee, - params.SlashingAddress, changeAddress.EncodeAddress(), - params.SlashingRate, - ) - s.NoError(err) - - unbondingTxMsg := testUnbondingInfo.UnbondingTx - - unbondingTxSlashingPathInfo, err := testUnbondingInfo.UnbondingInfo.SlashingPathSpendInfo() - s.NoError(err) + stakingTxHash := stakingMsgTx.TxHash() - slashingTxSig, err := testUnbondingInfo.SlashingTx.Sign( - unbondingTxMsg, - 0, - unbondingTxSlashingPathInfo.GetPkScriptPath(), - delBTCSK, - ) + // delegator signs unbonding tx + params := nonValidatorNode.QueryBTCStakingParams() + delUnbondingSig, err := activeDel.SignUnbondingTx(params, net, delBTCSK) s.NoError(err) // submit the message for creating BTC undelegation - nonValidatorNode.CreateBTCUndelegation( - unbondingTxMsg, - testUnbondingInfo.SlashingTx, - initialization.BabylonBtcFinalizationPeriod+1, - btcutil.Amount(stakingValue-fee), - slashingTxSig, - ) + nonValidatorNode.BTCUndelegate(&stakingTxHash, delUnbondingSig) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - valDelegations := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) - s.Len(valDelegations, 1) - s.Len(valDelegations[0].Dels, 1) - delegation := valDelegations[0].Dels[0] - s.NotNil(delegation.BtcUndelegation) -} - -// Test6SubmitStakerUnbonding is an end-to-end test for covenant and validator submitting signatures -// for unbonding transaction -func (s *BTCStakingTestSuite) Test6SubmitUnbondingSignatures() { - chainA := s.configurer.GetChainConfig(0) - chainA.WaitUntilHeight(1) - nonValidatorNode, err := chainA.GetNodeAtIndex(2) - s.NoError(err) - // wait for a block so that above txs take effect - nonValidatorNode.WaitForNextBlock() - - allDelegations := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) - s.Len(allDelegations, 1) - delegatorDelegations := allDelegations[0] - s.Len(delegatorDelegations.Dels, 1) - delegation := delegatorDelegations.Dels[0] - - s.NotNil(delegation.BtcUndelegation) - s.Empty(delegation.BtcUndelegation.CovenantUnbondingSigList) - s.Len(delegation.BtcUndelegation.CovenantSlashingSigs, 0) - - // params for covenantPk and slashing address - params := nonValidatorNode.QueryBTCStakingParams() - // get covenant BTC PKs - covenantBTCPKs := []*btcec.PublicKey{} - for _, covenantPK := range params.CovenantPks { - covenantBTCPKs = append(covenantBTCPKs, covenantPK.MustToBTCPK()) - } - - validatorBTCPKs := []*btcec.PublicKey{} - for _, valPK := range delegation.ValBtcPkList { - validatorBTCPKs = append(validatorBTCPKs, valPK.MustToBTCPK()) - } - - stakingInfo, err := btcstaking.BuildStakingInfo( - delegation.BtcPk.MustToBTCPK(), - validatorBTCPKs, - covenantBTCPKs, - params.CovenantQuorum, - delegation.GetStakingTime(), - btcutil.Amount(delegation.TotalSat), - net, - ) - s.NoError(err) - - unbondingTx, err := bbn.NewBTCTxFromBytes(delegation.BtcUndelegation.UnbondingTx) - s.NoError(err) - stakingTx, err := bbn.NewBTCTxFromBytes(delegation.StakingTx) - s.NoError(err) - stakingTxHash := stakingTx.TxHash().String() - - unbondingInfo, err := btcstaking.BuildUnbondingInfo( - delegation.BtcPk.MustToBTCPK(), - validatorBTCPKs, - covenantBTCPKs, - params.CovenantQuorum, - uint16(delegation.BtcUndelegation.UnbondingTime), - btcutil.Amount(unbondingTx.TxOut[0].Value), - net, - ) - s.NoError(err) - - // Next send covenant signatures. - // First covenant signature on unbonding tx - stakingTxUnbondigPathInfo, err := stakingInfo.UnbondingPathSpendInfo() - s.NoError(err) - - for i := 0; i < int(covenantQuorum); i++ { - covenantUnbondingSignature, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - unbondingTx, - stakingTx, - delegation.StakingOutputIdx, - stakingTxUnbondigPathInfo.GetPkScriptPath(), - covenantSKs[i], - ) - s.NoError(err) - - covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingSignature) - - // Next covenant signature on unbonding slashing tx - unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() - s.NoError(err) - - // slashing signatures, each encrypted by a restaked BTC validator's PK - covenantSlashingSigs := []*asig.AdaptorSignature{} - for _, valPK := range validatorBTCPKs { - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) - s.NoError(err) - covenantSlashingSig, err := delegation.BtcUndelegation.SlashingTx.EncSign( - unbondingTx, - 0, - unbondingTxSlashingPath.GetPkScriptPath(), - covenantSKs[i], - encKey, - ) - s.NoError(err) - covenantSlashingSigs = append(covenantSlashingSigs, covenantSlashingSig) - } - - nonValidatorNode.AddCovenantUnbondingSigs( - ¶ms.CovenantPks[i], stakingTxHash, &covenantUnbondingSig, covenantSlashingSigs) - } - - nonValidatorNode.WaitForNextBlock() - nonValidatorNode.WaitForNextBlock() - - // Check all signatures are properly registered - allDelegationsWithSigs := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) - s.Len(allDelegationsWithSigs, 1) - delegationWithSigs := allDelegationsWithSigs[0].Dels[0] - s.NotNil(delegationWithSigs.BtcUndelegation) - s.NotEmpty(delegationWithSigs.BtcUndelegation.CovenantUnbondingSigList) - s.NotNil(delegationWithSigs.BtcUndelegation.CovenantSlashingSigs) - btcTip, err := nonValidatorNode.QueryTip() - s.NoError(err) - s.Equal( - bstypes.BTCDelegationStatus_UNBONDED, - delegationWithSigs.GetStatus(btcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum), - ) + unbondedDels := nonValidatorNode.QueryUnbondedDelegations() + s.Len(unbondedDels, 1) + s.Equal(stakingTxHash, unbondedDels[0].MustGetStakingTxHash()) } diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 98bba8bd9..505bc47db 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -3,11 +3,13 @@ package chain import ( "encoding/hex" "strconv" + "strings" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "cosmossdk.io/math" sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/stretchr/testify/require" @@ -18,7 +20,7 @@ import ( bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) -func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *math.LegacyDec) { +func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *sdkmath.LegacyDec) { n.LogActionF("creating BTC validator") // get babylon PK hex @@ -49,6 +51,11 @@ func (n *NodeConfig) CreateBTCDelegation( stakingValue btcutil.Amount, slashingTx *bstypes.BTCSlashingTx, delegatorSig *bbn.BIP340Signature, + unbondingTx *wire.MsgTx, + unbondingSlashingTx *bstypes.BTCSlashingTx, + unbondingTime uint16, + unbondingValue btcutil.Amount, + delUnbondingSlashingSig *bbn.BIP340Signature, ) { n.LogActionF("creating BTC delegation") @@ -77,22 +84,50 @@ func (n *NodeConfig) CreateBTCDelegation( // get delegator sig hex delegatorSigHex := delegatorSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, btcPkHex, popHex, stakingTxInfoHex, valPKHex, stakingTimeString, stakingValueString, slashingTxHex, delegatorSigHex, "--from=val"} + // on-demand unbonding related + unbondingTxBytes, err := bbn.SerializeBTCTx(unbondingTx) + require.NoError(n.t, err) + unbondingTxHex := hex.EncodeToString(unbondingTxBytes) + unbondingSlashingTxHex := unbondingSlashingTx.ToHexStr() + unbondingTimeStr := sdkmath.NewUint(uint64(unbondingTime)).String() + unbondingValueStr := sdkmath.NewInt(int64(unbondingValue)).String() + delUnbondingSlashingSigHex := delUnbondingSlashingSig.ToHexStr() + + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, btcPkHex, popHex, stakingTxInfoHex, valPKHex, stakingTimeString, stakingValueString, slashingTxHex, delegatorSigHex, unbondingTxHex, unbondingSlashingTxHex, unbondingTimeStr, unbondingValueStr, delUnbondingSlashingSigHex, "--from=val"} _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully created BTC delegation") } -func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash string, sigs [][]byte) { +func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash string, slashingSigs [][]byte, unbondingSig *bbn.BIP340Signature, unbondingSlashingSigs [][]byte) { n.LogActionF("adding covenant signature") covPKHex := covPK.MarshalHex() - cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sig", covPKHex, stakingTxHash} - for _, sig := range sigs { - cmd = append(cmd, hex.EncodeToString(sig)) + cmd := []string{"babylond", "tx", "btcstaking", "add-covenant-sigs", covPKHex, stakingTxHash} + + // slashing signatures + slashingSigStrList := []string{} + for _, sig := range slashingSigs { + slashingSigStrList = append(slashingSigStrList, hex.EncodeToString(sig)) } + slashingSigStr := strings.Join(slashingSigStrList, ",") + cmd = append(cmd, slashingSigStr) + + // on-demand unbonding stuff + cmd = append(cmd, unbondingSig.ToHexStr()) + unbondingSlashingSigStrList := []string{} + for _, sig := range unbondingSlashingSigs { + unbondingSlashingSigStrList = append(unbondingSlashingSigStrList, hex.EncodeToString(sig)) + } + unbondingSlashingSigStr := strings.Join(unbondingSlashingSigStrList, ",") + cmd = append(cmd, unbondingSlashingSigStr) + + // used key cmd = append(cmd, "--from=val") + // gas + cmd = append(cmd, "--gas=auto", "--gas-prices=1ubbn", "--gas-adjustment=1.3") + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added covenant sigatures") @@ -146,45 +181,6 @@ func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint n.LogActionF("successfully added finality signature") } -func (n *NodeConfig) CreateBTCUndelegation( - unbondingTx *wire.MsgTx, - slashingTx *bstypes.BTCSlashingTx, - unbondingTimeBlocks uint16, - unbondingValue btcutil.Amount, - delegatorSig *bbn.BIP340Signature) { - n.LogActionF("creating BTC undelegation") - - txBytes, err := bbn.SerializeBTCTx(unbondingTx) - require.NoError(n.t, err) - // get staking tx hex - unbondingTxHex := hex.EncodeToString(txBytes) - // get slashing tx hex - slashingTxHex := slashingTx.ToHexStr() - // get delegator sig hex - delegatorSigHex := delegatorSig.ToHexStr() - - unbondingTimeStr := sdkmath.NewUint(uint64(unbondingTimeBlocks)).String() - unbondingValueStr := sdkmath.NewInt(int64(unbondingValue)).String() - - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-undelegation", unbondingTxHex, slashingTxHex, unbondingTimeStr, unbondingValueStr, delegatorSigHex, "--from=val"} - _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) - require.NoError(n.t, err) - n.LogActionF("successfully created BTC delegation") -} - -func (n *NodeConfig) AddValidatorUnbondingSig(valPK *bbn.BIP340PubKey, delPK *bbn.BIP340PubKey, stakingTxHash string, sig *bbn.BIP340Signature) { - n.LogActionF("adding validator signature") - - valPKHex := valPK.MarshalHex() - delPKHex := delPK.MarshalHex() - sigHex := sig.ToHexStr() - - cmd := []string{"babylond", "tx", "btcstaking", "add-validator-unbonding-sig", valPKHex, delPKHex, stakingTxHash, sigHex, "--from=val"} - _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) - require.NoError(n.t, err) - n.LogActionF("successfully added validator unbonding sig") -} - func (n *NodeConfig) AddCovenantUnbondingSigs( covPK *bbn.BIP340PubKey, stakingTxHash string, @@ -204,3 +200,14 @@ func (n *NodeConfig) AddCovenantUnbondingSigs( require.NoError(n.t, err) n.LogActionF("successfully added covenant unbonding sigs") } + +func (n *NodeConfig) BTCUndelegate(stakingTxHash *chainhash.Hash, delUnbondingSig *schnorr.Signature) { + n.LogActionF("undelegate by using signature on unbonding tx from delegator") + + sigHex := bbn.NewBIP340SignatureFromBTCSig(delUnbondingSig).ToHexStr() + cmd := []string{"babylond", "tx", "btcstaking", "btc-undelegate", stakingTxHash.String(), sigHex, "--from=val"} + + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully added signature on unbonding tx from delegator") +} diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index a9bf61a0b..38963560e 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -57,29 +57,29 @@ func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BT return resp.BtcDelegatorDelegations } -func (n *NodeConfig) QueryUnbondingDelegations() []*bstypes.BTCDelegation { - queryParams := url.Values{} - queryParams.Add("status", fmt.Sprintf("%d", bstypes.BTCDelegationStatus_UNBONDING)) - bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/btc_delegations", queryParams) +func (n *NodeConfig) QueryBtcDelegation(stakingTxHash string) *bstypes.QueryBTCDelegationResponse { + path := fmt.Sprintf("/babylon/btcstaking/v1/btc_delegations/%s", stakingTxHash) + bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryBTCDelegationsResponse + var resp bstypes.QueryBTCDelegationResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) - return resp.BtcDelegations + return &resp } -func (n *NodeConfig) QueryBtcDelegation(stakingTxHash string) *bstypes.QueryBTCDelegationResponse { - path := fmt.Sprintf("/babylon/btcstaking/v1/btc_delegations/%s", stakingTxHash) - bz, err := n.QueryGRPCGateway(path, url.Values{}) +func (n *NodeConfig) QueryUnbondedDelegations() []*bstypes.BTCDelegation { + queryParams := url.Values{} + queryParams.Add("status", fmt.Sprintf("%d", bstypes.BTCDelegationStatus_UNBONDED)) + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/btc_delegations", queryParams) require.NoError(n.t, err) - var resp bstypes.QueryBTCDelegationResponse + var resp bstypes.QueryBTCDelegationsResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) - return &resp + return resp.BtcDelegations } func (n *NodeConfig) QueryActivatedHeight() uint64 { diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index e8d1a8648..5b04b2f02 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -10,8 +10,6 @@ import ( "time" txformat "github.com/babylonchain/babylon/btctxformatter" - - "github.com/babylonchain/babylon/btctxformatter" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/btcsuite/btcd/blockchain" @@ -367,7 +365,7 @@ func GenRandomTx(r *rand.Rand) *wire.MsgTx { return tx } -func GenRandomBabylonTxPair(r *rand.Rand) ([]*wire.MsgTx, *btctxformatter.RawBtcCheckpoint) { +func GenRandomBabylonTxPair(r *rand.Rand) ([]*wire.MsgTx, *txformat.RawBtcCheckpoint) { txs := []*wire.MsgTx{GenRandomTx(r), GenRandomTx(r)} builder := txscript.NewScriptBuilder() @@ -375,9 +373,9 @@ func GenRandomBabylonTxPair(r *rand.Rand) ([]*wire.MsgTx, *btctxformatter.RawBtc rawBTCCkpt := GetRandomRawBtcCheckpoint(r) tag := GenRandomByteArray(r, 4) // encode raw checkpoint to two halves - firstHalf, secondHalf, err := btctxformatter.EncodeCheckpointData( - btctxformatter.BabylonTag(tag), - btctxformatter.CurrentVersion, + firstHalf, secondHalf, err := txformat.EncodeCheckpointData( + txformat.BabylonTag(tag), + txformat.CurrentVersion, rawBTCCkpt, ) if err != nil { diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 48fa2f01a..2d5a80bb5 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -18,11 +18,15 @@ import ( "github.com/stretchr/testify/require" "github.com/babylonchain/babylon/btcstaking" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) +const ( + StakingOutIdx = uint32(0) + UnbondingTxFee = int64(1000) +) + func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { // key pairs btcSK, _, err := GenRandomBTCKeyPair(r) @@ -32,36 +36,6 @@ func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { return GenRandomBTCValidatorWithBTCSK(r, btcSK) } -func GenCovenantAdaptorSigs( - covenantSKs []*btcec.PrivateKey, - valPKs []*btcec.PublicKey, - fundingTx *wire.MsgTx, - pkScriptPath []byte, - slashingTx *bstypes.BTCSlashingTx, -) ([]*bstypes.CovenantAdaptorSignatures, error) { - covenantSigs := []*bstypes.CovenantAdaptorSignatures{} - for _, covenantSK := range covenantSKs { - covMemberSigs := &bstypes.CovenantAdaptorSignatures{ - CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantSK.PubKey()), - AdaptorSigs: [][]byte{}, - } - for _, valPK := range valPKs { - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) - if err != nil { - return nil, err - } - covenantSig, err := slashingTx.EncSign(fundingTx, 0, pkScriptPath, covenantSK, encKey) - if err != nil { - return nil, err - } - covMemberSigs.AdaptorSigs = append(covMemberSigs.AdaptorSigs, covenantSig.MustMarshal()) - } - covenantSigs = append(covenantSigs, covMemberSigs) - } - - return covenantSigs, nil -} - func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { bbnSK, _, err := GenRandomSecp256k1KeyPair(r) if err != nil { @@ -97,6 +71,7 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe }, nil } +// TODO: accomodate presign unbonding flow func GenRandomBTCDelegation( r *rand.Rand, t *testing.T, @@ -141,7 +116,7 @@ func GenRandomBTCDelegation( return nil, err } // staking/slashing tx - testingInfo := GenBTCStakingSlashingInfo( + stakingSlashingInfo := GenBTCStakingSlashingInfo( r, t, net, @@ -155,24 +130,34 @@ func GenRandomBTCDelegation( slashingRate, ) - slashingPathSpendInfo, err := testingInfo.StakingInfo.SlashingPathSpendInfo() + slashingPathSpendInfo, err := stakingSlashingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - pkScriptPath := slashingPathSpendInfo.GetPkScriptPath() - stakingMsgTx := testingInfo.StakingTx + stakingMsgTx := stakingSlashingInfo.StakingTx - // covenant sigs - covenantSigs, err := GenCovenantAdaptorSigs(covenantSKs, valPKs, stakingMsgTx, pkScriptPath, testingInfo.SlashingTx) + // delegator sig + delegatorSig, err := stakingSlashingInfo.SlashingTx.Sign( + stakingMsgTx, + StakingOutIdx, + slashingPathSpendInfo.GetPkScriptPath(), + delSK, + ) require.NoError(t, err) - // delegator sig - delegatorSig, err := testingInfo.SlashingTx.Sign(stakingMsgTx, 0, pkScriptPath, delSK) + // covenant sigs + covenantSigs, err := GenCovenantAdaptorSigs( + covenantSKs, + valPKs, + stakingMsgTx, + slashingPathSpendInfo.GetPkScriptPath(), + stakingSlashingInfo.SlashingTx, + ) require.NoError(t, err) - serializedStakingTx, err := bbn.SerializeBTCTx(testingInfo.StakingTx) + serializedStakingTx, err := bbn.SerializeBTCTx(stakingSlashingInfo.StakingTx) require.NoError(t, err) - return &bstypes.BTCDelegation{ + del := &bstypes.BTCDelegation{ BabylonPk: secp256k1PK, BtcPk: delBTCPK, Pop: pop, @@ -180,12 +165,66 @@ func GenRandomBTCDelegation( StartHeight: startHeight, EndHeight: endHeight, TotalSat: totalSat, - StakingOutputIdx: 0, + StakingOutputIdx: StakingOutIdx, DelegatorSig: delegatorSig, CovenantSigs: covenantSigs, StakingTx: serializedStakingTx, - SlashingTx: testingInfo.SlashingTx, - }, nil + SlashingTx: stakingSlashingInfo.SlashingTx, + } + + /* + construct BTC undelegation + */ + + // construct unbonding info + stkTxHash := stakingSlashingInfo.StakingTx.TxHash() + unbondingValue := totalSat - uint64(UnbondingTxFee) + w := uint16(100) // TODO: parameterise w + unbondingSlashingInfo := GenBTCUnbondingSlashingInfo( + r, + t, + net, + delSK, + valPKs, + covenantBTCPKs, + covenantQuorum, + wire.NewOutPoint(&stkTxHash, StakingOutIdx), + w+1, + int64(unbondingValue), + slashingAddress, changeAddress, + slashingRate, + ) + + unbondingTxBytes, err := bbn.SerializeBTCTx(unbondingSlashingInfo.UnbondingTx) + require.NoError(t, err) + delSlashingTxSig, err := unbondingSlashingInfo.GenDelSlashingTxSig(delSK) + require.NoError(t, err) + del.BtcUndelegation = &bstypes.BTCUndelegation{ + UnbondingTx: unbondingTxBytes, + UnbondingTime: uint32(w + 1), + SlashingTx: unbondingSlashingInfo.SlashingTx, + DelegatorSlashingSig: delSlashingTxSig, + } + + /* + covenant signs BTC undelegation + */ + + unbondingPathSpendInfo, err := stakingSlashingInfo.StakingInfo.UnbondingPathSpendInfo() + require.NoError(t, err) + + covUnbondingSlashingSigs, covUnbondingSigs, err := unbondingSlashingInfo.GenCovenantSigs( + covenantSKs, + valPKs, + stakingMsgTx, + unbondingPathSpendInfo.GetPkScriptPath(), + ) + require.NoError(t, err) + + del.BtcUndelegation.CovenantSlashingSigs = covUnbondingSlashingSigs + del.BtcUndelegation.CovenantUnbondingSigList = covUnbondingSigs + + return del, nil } type TestStakingSlashingInfo struct { @@ -246,7 +285,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( require.NoError(t, err) slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, - 0, + StakingOutIdx, slashingAddrBtc, changeAddrBtc, 2000, slashingRate, @@ -276,7 +315,7 @@ func GenBTCStakingSlashingInfo( slashingRate sdkmath.LegacyDec, ) *TestStakingSlashingInfo { // an arbitrary input - spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+1000)) + spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+UnbondingTxFee)) outPoint := &spend.prevOut return GenBTCStakingSlashingInfoWithOutPoint( r, @@ -332,7 +371,7 @@ func GenBTCUnbondingSlashingInfo( require.NoError(t, err) slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, - 0, + StakingOutIdx, slashingAddrBtc, changeAddrBtc, 2000, slashingRate, @@ -347,3 +386,62 @@ func GenBTCUnbondingSlashingInfo( UnbondingInfo: unbondingInfo, } } + +func (info *TestUnbondingSlashingInfo) GenDelSlashingTxSig(sk *btcec.PrivateKey) (*bbn.BIP340Signature, error) { + unbondingTxMsg := info.UnbondingTx + unbondingTxSlashingPathInfo, err := info.UnbondingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, err + } + slashingTxSig, err := info.SlashingTx.Sign( + unbondingTxMsg, + StakingOutIdx, + unbondingTxSlashingPathInfo.GetPkScriptPath(), + sk, + ) + if err != nil { + return nil, err + } + return slashingTxSig, nil +} + +func (info *TestUnbondingSlashingInfo) GenCovenantSigs( + covSKs []*btcec.PrivateKey, + valPKs []*btcec.PublicKey, + stakingTx *wire.MsgTx, + unbondingPkScriptPath []byte, +) ([]*bstypes.CovenantAdaptorSignatures, []*bstypes.SignatureInfo, error) { + unbondingSlashingPathInfo, err := info.UnbondingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, nil, err + } + + covUnbondingSlashingSigs, err := GenCovenantAdaptorSigs( + covSKs, + valPKs, + info.UnbondingTx, + unbondingSlashingPathInfo.GetPkScriptPath(), + info.SlashingTx, + ) + if err != nil { + return nil, nil, err + } + covUnbondingSigs, err := GenCovenantUnbondingSigs( + covSKs, + stakingTx, + StakingOutIdx, + unbondingPkScriptPath, + info.UnbondingTx, + ) + if err != nil { + return nil, nil, err + } + covUnbondingSigList := []*bstypes.SignatureInfo{} + for i := range covUnbondingSigs { + covUnbondingSigList = append(covUnbondingSigList, &bstypes.SignatureInfo{ + Pk: bbn.NewBIP340PubKeyFromBTCPK(covSKs[i].PubKey()), + Sig: bbn.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), + }) + } + return covUnbondingSlashingSigs, covUnbondingSigList, nil +} diff --git a/testutil/datagen/covenant.go b/testutil/datagen/covenant.go new file mode 100644 index 000000000..ae2fd835b --- /dev/null +++ b/testutil/datagen/covenant.go @@ -0,0 +1,59 @@ +package datagen + +import ( + "github.com/babylonchain/babylon/btcstaking" + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" + bbn "github.com/babylonchain/babylon/types" + bstypes "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/wire" +) + +func GenCovenantAdaptorSigs( + covenantSKs []*btcec.PrivateKey, + valPKs []*btcec.PublicKey, + fundingTx *wire.MsgTx, + pkScriptPath []byte, + slashingTx *bstypes.BTCSlashingTx, +) ([]*bstypes.CovenantAdaptorSignatures, error) { + covenantSigs := []*bstypes.CovenantAdaptorSignatures{} + for _, covenantSK := range covenantSKs { + covMemberSigs := &bstypes.CovenantAdaptorSignatures{ + CovPk: bbn.NewBIP340PubKeyFromBTCPK(covenantSK.PubKey()), + AdaptorSigs: [][]byte{}, + } + for _, valPK := range valPKs { + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPK) + if err != nil { + return nil, err + } + covenantSig, err := slashingTx.EncSign(fundingTx, 0, pkScriptPath, covenantSK, encKey) + if err != nil { + return nil, err + } + covMemberSigs.AdaptorSigs = append(covMemberSigs.AdaptorSigs, covenantSig.MustMarshal()) + } + covenantSigs = append(covenantSigs, covMemberSigs) + } + + return covenantSigs, nil +} + +func GenCovenantUnbondingSigs(covenantSKs []*btcec.PrivateKey, stakingTx *wire.MsgTx, stakingOutIdx uint32, unbondingPkScriptPath []byte, unbondingTx *wire.MsgTx) ([]*schnorr.Signature, error) { + sigs := []*schnorr.Signature{} + for i := range covenantSKs { + sig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + stakingOutIdx, + unbondingPkScriptPath, + covenantSKs[i], + ) + if err != nil { + return nil, err + } + sigs = append(sigs, sig) + } + return sigs, nil +} diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index 11cf971cc..f4cb4dd7a 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -46,8 +46,7 @@ func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeig if err != nil { return nil, nil, err } - sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) - msg.Sig = &sig + msg.Sig = bbn.NewBIP340SignatureFromBTCSig(schnorrSig) return srList, msg, nil } diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index 8e76b28ac..b59a0c2cb 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -5,7 +5,6 @@ import ( "github.com/boljen/go-bitmap" - "github.com/babylonchain/babylon/btctxformatter" txformat "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/x/checkpointing/types" @@ -25,13 +24,13 @@ func GenRandomBitmap(r *rand.Rand) (bitmap.Bitmap, int) { return bm, numSubset } -func GetRandomRawBtcCheckpoint(r *rand.Rand) *btctxformatter.RawBtcCheckpoint { +func GetRandomRawBtcCheckpoint(r *rand.Rand) *txformat.RawBtcCheckpoint { rawCkpt := GenRandomRawCheckpoint(r) - return &btctxformatter.RawBtcCheckpoint{ + return &txformat.RawBtcCheckpoint{ Epoch: rawCkpt.EpochNum, AppHash: *rawCkpt.AppHash, BitMap: rawCkpt.Bitmap, - SubmitterAddress: GenRandomByteArray(r, btctxformatter.AddressLength), + SubmitterAddress: GenRandomByteArray(r, txformat.AddressLength), BlsSig: rawCkpt.BlsMultiSig.Bytes(), } } diff --git a/types/btc_schnorr_sig.go b/types/btc_schnorr_sig.go index 82c4263bb..58342323f 100644 --- a/types/btc_schnorr_sig.go +++ b/types/btc_schnorr_sig.go @@ -25,9 +25,10 @@ func NewBIP340SignatureFromHex(sigHex string) (*BIP340Signature, error) { return NewBIP340Signature(sigBytes) } -func NewBIP340SignatureFromBTCSig(btcSig *schnorr.Signature) BIP340Signature { +func NewBIP340SignatureFromBTCSig(btcSig *schnorr.Signature) *BIP340Signature { sigBytes := btcSig.Serialize() - return BIP340Signature(sigBytes) + sig := BIP340Signature(sigBytes) + return &sig } func (sig BIP340Signature) ToBTCSig() (*schnorr.Signature, error) { diff --git a/types/btc_schnorr_sig_test.go b/types/btc_schnorr_sig_test.go index e25e4a43b..c8a7f2ad8 100644 --- a/types/btc_schnorr_sig_test.go +++ b/types/btc_schnorr_sig_test.go @@ -36,6 +36,6 @@ func FuzzBIP340Signature(f *testing.F) { require.NoError(t, err) err = sig2.Unmarshal(sigBytes) require.NoError(t, err) - require.Equal(t, sig, sig2) + require.Equal(t, *sig, sig2) }) } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index 6f571dea4..df640e9ac 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -3,11 +3,9 @@ package cli import ( "encoding/hex" "fmt" - "math" "strings" sdkmath "cosmossdk.io/math" - "github.com/btcsuite/btcd/btcutil" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -43,9 +41,8 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand( NewCreateBTCValidatorCmd(), NewCreateBTCDelegationCmd(), - NewAddCovenantSigCmd(), - NewCreateBTCUndelegationCmd(), - NewAddCovenantUnbondingSigsCmd(), + NewAddCovenantSigsCmd(), + NewBTCUndelegateCmd(), ) return cmd @@ -135,50 +132,10 @@ func NewCreateBTCValidatorCmd() *cobra.Command { return cmd } -func parseLockTime(str string) (uint16, error) { - num, ok := sdkmath.NewIntFromString(str) - - if !ok { - return 0, fmt.Errorf("invalid staking time: %s", str) - } - - if !num.IsUint64() { - return 0, fmt.Errorf("staking time is not valid uint") - } - - asUint64 := num.Uint64() - - if asUint64 > math.MaxUint16 { - return 0, fmt.Errorf("staking time is too large. Max is %d", math.MaxUint16) - } - - return uint16(asUint64), nil -} - -func parseBtcAmount(str string) (btcutil.Amount, error) { - num, ok := sdkmath.NewIntFromString(str) - - if !ok { - return 0, fmt.Errorf("invalid staking value: %s", str) - } - - if num.IsNegative() { - return 0, fmt.Errorf("staking value is negative") - } - - if !num.IsInt64() { - return 0, fmt.Errorf("staking value is not valid uint") - } - - asInt64 := num.Int64() - - return btcutil.Amount(asInt64), nil -} - func NewCreateBTCDelegationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-delegation [babylon_pk] [btc_pk] [pop] [staking_tx_info] [val_pk] [staking_time] [staking_value] [slashing_tx] [delegator_sig]", - Args: cobra.ExactArgs(9), + Use: "create-btc-delegation [babylon_pk] [btc_pk] [pop] [staking_tx_info] [val_pk] [staking_time] [staking_value] [slashing_tx] [delegator_slashing_sig] [unbonding_tx] [unbonding_slashing_tx] [unbonding_time] [unbonding_value] [delegator_unbonding_slashing_sig]", + Args: cobra.ExactArgs(14), Short: "Create a BTC delegation", Long: strings.TrimSpace( `Create a BTC delegation.`, // TODO: example @@ -227,13 +184,11 @@ func NewCreateBTCDelegationCmd() *cobra.Command { // get staking time stakingTime, err := parseLockTime(args[5]) - if err != nil { return err } stakingValue, err := parseBtcAmount(args[6]) - if err != nil { return err } @@ -244,70 +199,57 @@ func NewCreateBTCDelegationCmd() *cobra.Command { return err } - // get delegator sig - delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[8]) + // get delegator sig on slashing tx + delegatorSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[8]) if err != nil { return err } - msg := types.MsgCreateBTCDelegation{ - Signer: clientCtx.FromAddress.String(), - BabylonPk: &babylonPK, - BtcPk: btcPK, - ValBtcPkList: []bbn.BIP340PubKey{*valPK}, - Pop: pop, - StakingTime: uint32(stakingTime), - StakingValue: int64(stakingValue), - StakingTx: stakingTxInfo, - SlashingTx: slashingTx, - DelegatorSig: delegatorSig, + // get unbonding tx + _, unbondingTxBytes, err := bbn.NewBTCTxFromHex(args[9]) + if err != nil { + return err } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - -func NewAddCovenantSigCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "add-covenant-sig [covenant_pk] [staking_tx_hash] [sig1] [sig2] ...", - Args: cobra.MinimumNArgs(3), - Short: "Add a covenant signature", - Long: strings.TrimSpace( - `Add a covenant signature.`, // TODO: example - ), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) + // get unbonding slashing tx + unbondingSlashingTx, err := types.NewBTCSlashingTxFromHex(args[10]) if err != nil { return err } - covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + // get staking time + unbondingTime, err := parseLockTime(args[11]) if err != nil { - return fmt.Errorf("invalid public key: %w", err) + return err } - // get staking tx hash - stakingTxHash := args[1] + unbondingValue, err := parseBtcAmount(args[12]) + if err != nil { + return err + } - sigs := [][]byte{} - for _, sigHex := range args[2:] { - sig, err := asig.NewAdaptorSignatureFromHex(sigHex) - if err != nil { - return fmt.Errorf("invalid covenant signature: %w", err) - } - sigs = append(sigs, sig.MustMarshal()) + // get delegator sig on unbonding slashing tx + delegatorUnbondingSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[13]) + if err != nil { + return err } - msg := types.MsgAddCovenantSig{ - Signer: clientCtx.FromAddress.String(), - Pk: covPK, - StakingTxHash: stakingTxHash, - Sigs: sigs, + msg := types.MsgCreateBTCDelegation{ + Signer: clientCtx.FromAddress.String(), + BabylonPk: &babylonPK, + BtcPk: btcPK, + ValBtcPkList: []bbn.BIP340PubKey{*valPK}, + Pop: pop, + StakingTime: uint32(stakingTime), + StakingValue: int64(stakingValue), + StakingTx: stakingTxInfo, + SlashingTx: slashingTx, + DelegatorSlashingSig: delegatorSlashingSig, + UnbondingTx: unbondingTxBytes, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: int64(unbondingValue), + UnbondingSlashingTx: unbondingSlashingTx, + DelegatorUnbondingSlashingSig: delegatorUnbondingSlashingSig, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -319,13 +261,13 @@ func NewAddCovenantSigCmd() *cobra.Command { return cmd } -func NewCreateBTCUndelegationCmd() *cobra.Command { +func NewAddCovenantSigsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-undelegation [unbonding_tx] [slashing_tx] [unbonding_time] [unbonding_value] [delegator_sig]", + Use: "add-covenant-sigs [covenant_pk] [staking_tx_hash] [slashing_tx_sig1],[slashing_tx_sig2],... [unbonding_tx_sig] [slashing_unbonding_tx_sig1],[slashing_unbonding_tx_sig2],...", Args: cobra.ExactArgs(5), - Short: "Create a BTC undelegation", + Short: "Add a covenant signature", Long: strings.TrimSpace( - `Create a BTC undelegation.`, // TODO: example + `Add a covenant signature.`, // TODO: example ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -333,44 +275,47 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { return err } - // get staking tx - _, unbondingTxBytes, err := bbn.NewBTCTxFromHex(args[0]) - if err != nil { - return err - } - - // get slashing tx - slashingTx, err := types.NewBTCSlashingTxFromHex(args[1]) + covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { - return err + return fmt.Errorf("invalid public key: %w", err) } - // get staking time - unbondingTime, err := parseLockTime(args[2]) + // get staking tx hash + stakingTxHash := args[1] - if err != nil { - return err + // parse slashing tx sigs + slashingTxSigs := [][]byte{} + for _, sigHex := range strings.Split(args[2], ",") { + sig, err := asig.NewAdaptorSignatureFromHex(sigHex) + if err != nil { + return fmt.Errorf("invalid covenant signature: %w", err) + } + slashingTxSigs = append(slashingTxSigs, sig.MustMarshal()) } - unbondingValue, err := parseBtcAmount(args[3]) - + // get covenant signature for unbonding tx + unbondingTxSig, err := bbn.NewBIP340SignatureFromHex(args[3]) if err != nil { return err } - // get delegator sig - delegatorSig, err := bbn.NewBIP340SignatureFromHex(args[4]) - if err != nil { - return err + // parse unbonding slashing tx sigs + unbondingSlashingSigs := [][]byte{} + for _, sigHex := range strings.Split(args[4], ",") { + slashingSig, err := asig.NewAdaptorSignatureFromHex(sigHex) + if err != nil { + return fmt.Errorf("invalid covenant signature: %w", err) + } + unbondingSlashingSigs = append(unbondingSlashingSigs, slashingSig.MustMarshal()) } - msg := types.MsgBTCUndelegate{ - Signer: clientCtx.FromAddress.String(), - UnbondingTx: unbondingTxBytes, - UnbondingTime: uint32(unbondingTime), - UnbondingValue: int64(unbondingValue), - SlashingTx: slashingTx, - DelegatorSlashingSig: delegatorSig, + msg := types.MsgAddCovenantSigs{ + Signer: clientCtx.FromAddress.String(), + Pk: covPK, + StakingTxHash: stakingTxHash, + SlashingTxSigs: slashingTxSigs, + UnbondingTxSig: unbondingTxSig, + SlashingUnbondingTxSigs: unbondingSlashingSigs, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -382,47 +327,33 @@ func NewCreateBTCUndelegationCmd() *cobra.Command { return cmd } -func NewAddCovenantUnbondingSigsCmd() *cobra.Command { +func NewBTCUndelegateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-covenant-unbonding-sigs [covenant_pk] [staking_tx_hash] [unbonding_tx_sg] [slashing_unbonding_tx_sig1] [slashing_unbonding_tx_sig2] ...", - Args: cobra.MinimumNArgs(4), - Short: "Add covenant signatures for unbonding tx and slash unbonding tx", + Use: "btc-undelegate [staking_tx_hash] [unbonding_tx_sig]", + Args: cobra.ExactArgs(2), + Short: "Add a signature on the unbonding tx of a BTC delegation identified by a given staking tx hash. ", + Long: strings.TrimSpace( + `Add a signature on the unbonding tx of a BTC delegation identified by a given staking tx hash signed by the delegator. The signature proves that delegator wants to unbond, and Babylon will consider the BTC delegation unbonded.`, // TODO: example + ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } - // get covenant PK - covPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) - if err != nil { - return err - } - // get staking tx hash - stakingTxHash := args[1] + stakingTxHash := args[0] - // get covenant sigature for unbonding tx - unbondingSig, err := bbn.NewBIP340SignatureFromHex(args[2]) + // get delegator signature for unbonding tx + unbondingTxSig, err := bbn.NewBIP340SignatureFromHex(args[1]) if err != nil { return err } - slashingSigs := [][]byte{} - for _, sigHex := range args[3:] { - slashingSig, err := asig.NewAdaptorSignatureFromHex(sigHex) - if err != nil { - return fmt.Errorf("invalid covenant signature: %w", err) - } - slashingSigs = append(slashingSigs, slashingSig.MustMarshal()) - } - - msg := types.MsgAddCovenantUnbondingSigs{ - Signer: clientCtx.FromAddress.String(), - Pk: covPK, - StakingTxHash: stakingTxHash, - UnbondingTxSig: unbondingSig, - SlashingUnbondingTxSigs: slashingSigs, + msg := types.MsgBTCUndelegate{ + Signer: clientCtx.FromAddress.String(), + StakingTxHash: stakingTxHash, + UnbondingTxSig: unbondingTxSig, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/btcstaking/client/cli/utils.go b/x/btcstaking/client/cli/utils.go new file mode 100644 index 000000000..cec621388 --- /dev/null +++ b/x/btcstaking/client/cli/utils.go @@ -0,0 +1,49 @@ +package cli + +import ( + "fmt" + "math" + + sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/btcutil" +) + +func parseLockTime(str string) (uint16, error) { + num, ok := sdkmath.NewIntFromString(str) + + if !ok { + return 0, fmt.Errorf("invalid staking time: %s", str) + } + + if !num.IsUint64() { + return 0, fmt.Errorf("staking time is not valid uint") + } + + asUint64 := num.Uint64() + + if asUint64 > math.MaxUint16 { + return 0, fmt.Errorf("staking time is too large. Max is %d", math.MaxUint16) + } + + return uint16(asUint64), nil +} + +func parseBtcAmount(str string) (btcutil.Amount, error) { + num, ok := sdkmath.NewIntFromString(str) + + if !ok { + return 0, fmt.Errorf("invalid staking value: %s", str) + } + + if num.IsNegative() { + return 0, fmt.Errorf("staking value is negative") + } + + if !num.IsInt64() { + return 0, fmt.Errorf("staking value is not valid uint") + } + + asInt64 := num.Int64() + + return btcutil.Amount(asInt64), nil +} diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index 5ea5c4e7d..fe07edc40 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -79,30 +79,6 @@ func (k Keeper) updateBTCDelegation( return nil } -// AddCovenantSigsToBTCDelegation adds a given covenant sig to a BTC delegation -// with the given staking tx hash -func (k Keeper) AddCovenantSigsToBTCDelegation( - ctx context.Context, - stakingTxHash string, - covenantSigs [][]byte, - covPk *bbn.BIP340PubKey, - quorum uint32, -) error { - adaptorSigs := make([]asig.AdaptorSignature, 0, len(covenantSigs)) - for _, s := range covenantSigs { - as, err := asig.NewAdaptorSignatureFromBytes(s) - if err != nil { - return err - } - adaptorSigs = append(adaptorSigs, *as) - } - addCovenantSig := func(btcDel *types.BTCDelegation) error { - return btcDel.AddCovenantSigs(covPk, adaptorSigs, quorum) - } - - return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSig) -} - func (k Keeper) AddUndelegationToBTCDelegation( ctx context.Context, stakingTxHash string, @@ -119,31 +95,49 @@ func (k Keeper) AddUndelegationToBTCDelegation( return k.updateBTCDelegation(ctx, stakingTxHash, addUndelegation) } -func (k Keeper) AddCovenantSigsToUndelegation( +// AddCovenantSigsToBTCDelegation adds covenant signatures to a BTC delegation +// with the given staking tx hash, including +// - a list of adaptor signatures over slashing tx, each encrypted by a restaked validator's PK +// - a Schnorr signature over unbonding tx +// - a list of adaptor signatures over unbonding slashing tx, each encrypted by a restaked validator's PK +func (k Keeper) AddCovenantSigsToBTCDelegation( ctx context.Context, stakingTxHash string, covPk *bbn.BIP340PubKey, + slashingSigsByte [][]byte, unbondingTxSigInfo *bbn.BIP340Signature, - slashUnbondingTxSigs [][]byte, - quorum uint32, + slashUnbondingTxSigsByte [][]byte, ) error { - adaptorSigs := make([]asig.AdaptorSignature, 0, len(slashUnbondingTxSigs)) - for _, s := range slashUnbondingTxSigs { + quorum := k.GetParams(ctx).CovenantQuorum + + slashingSigs := make([]asig.AdaptorSignature, 0, len(slashingSigsByte)) + for _, s := range slashingSigsByte { as, err := asig.NewAdaptorSignatureFromBytes(s) if err != nil { return err } - adaptorSigs = append(adaptorSigs, *as) + slashingSigs = append(slashingSigs, *as) } - addCovenantSigs := func(btcDel *types.BTCDelegation) error { - if btcDel.BtcUndelegation == nil { - return fmt.Errorf("the BTC delegation with staking tx hash %s did not receive undelegation request yet", stakingTxHash) + slashUnbondingTxSigs := make([]asig.AdaptorSignature, 0, len(slashUnbondingTxSigsByte)) + for _, s := range slashUnbondingTxSigsByte { + as, err := asig.NewAdaptorSignatureFromBytes(s) + if err != nil { + return err } + slashUnbondingTxSigs = append(slashUnbondingTxSigs, *as) + } - return btcDel.BtcUndelegation.AddCovenantSigs(covPk, unbondingTxSigInfo, adaptorSigs, quorum) + addCovenantSig := func(btcDel *types.BTCDelegation) error { + if err := btcDel.AddCovenantSigs(covPk, slashingSigs, quorum); err != nil { + return err + } + if err := btcDel.BtcUndelegation.AddCovenantSigs(covPk, unbondingTxSigInfo, slashUnbondingTxSigs, quorum); err != nil { + return err + } + return nil } - return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSigs) + return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSig) } // hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index ee239e56d..333aa36f2 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -6,7 +6,6 @@ import ( "testing" sdkmath "cosmossdk.io/math" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg" sdk "github.com/cosmos/cosmos-sdk/types" @@ -268,120 +267,6 @@ func FuzzPendingBTCDelegations(f *testing.F) { }) } -func FuzzUnbondingBTCDelegations(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - // Setup keeper and context - btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) - btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - btccKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() - keeper, ctx := testkeeper.BTCStakingKeeper(t, btclcKeeper, btccKeeper) - - // covenant and slashing addr - covenantSKs, covenantPKs, covenantQuorum := types.DefaultCovenantCommittee() - covBTCPKs := bbn.NewBIP340PKsFromBTCPKs(covenantPKs) - slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) - // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. - // NOTE - if the rate is higher or lower, it may produce slashing or change outputs - // with value below the dust threshold, causing test failure. - // Our goal is not to test failure due to such extreme cases here; - // this is already covered in FuzzGeneratingValidStakingSlashingTx - slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - - // Generate a random number of BTC validators - numBTCVals := datagen.RandomInt(r, 5) + 1 - btcVals := []*types.BTCValidator{} - for i := uint64(0); i < numBTCVals; i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) - require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcVals = append(btcVals, btcVal) - } - - // Generate a random number of BTC delegations under each validator - startHeight := datagen.RandomInt(r, 100) + 1 - endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 - numBTCDels := datagen.RandomInt(r, 10) + 1 - unbondingBtcDelsMap := make(map[string]*types.BTCDelegation) - for _, btcVal := range btcVals { - for j := uint64(0); j < numBTCDels; j++ { - delSK, _, err := datagen.GenRandomBTCKeyPair(r) - require.NoError(t, err) - btcDel, err := datagen.GenRandomBTCDelegation( - r, - t, - []bbn.BIP340PubKey{*btcVal.BtcPk}, - delSK, - covenantSKs, - covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), - startHeight, endHeight, 10000, - slashingRate, - ) - require.NoError(t, err) - - if datagen.RandomInt(r, 2) == 1 { - // add unbonding object in random BTC delegations to make them ready to receive covenant sig - btcDel.BtcUndelegation = &types.BTCUndelegation{} - - if datagen.RandomInt(r, 2) == 1 { - // these BTC delegations are unbonded - for i := range covenantSKs { - sig, err := schnorr.Sign(covenantSKs[i], datagen.GenRandomByteArray(r, 32)) - require.NoError(t, err) - unbondingSig := bbn.NewBIP340SignatureFromBTCSig(sig) - unbondingSigInfo := types.NewSignatureInfo(&covBTCPKs[i], &unbondingSig) - btcDel.BtcUndelegation.CovenantUnbondingSigList = append(btcDel.BtcUndelegation.CovenantUnbondingSigList, unbondingSigInfo) - } - btcDel.BtcUndelegation.CovenantSlashingSigs = btcDel.CovenantSigs - } else { - // these BTC delegations are unbonding - unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel - } - } - - err = keeper.AddBTCDelegation(ctx, btcDel) - require.NoError(t, err) - } - } - - babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) - keeper.IndexBTCHeight(ctx) - - // querying paginated BTC delegations and assert - // Generate a page request with a limit and a nil key - if len(unbondingBtcDelsMap) == 0 { - return - } - limit := datagen.RandomInt(r, len(unbondingBtcDelsMap)) + 1 - pagination := constructRequestWithLimit(r, limit) - req := &types.QueryBTCDelegationsRequest{ - Status: types.BTCDelegationStatus_UNBONDING, - Pagination: pagination, - } - for i := uint64(0); i < numBTCDels; i += limit { - resp, err := keeper.BTCDelegations(ctx, req) - require.NoError(t, err) - require.NotNil(t, resp) - for _, btcDel := range resp.BtcDelegations { - _, ok := unbondingBtcDelsMap[btcDel.BtcPk.MarshalHex()] - require.True(t, ok) - } - // Construct the next page request - pagination.Key = resp.Pagination.NextKey - } - }) -} - func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 1a82ebb13..a109a3695 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -2,13 +2,13 @@ package keeper import ( "context" - sdkmath "cosmossdk.io/math" "fmt" - "math" + + sdkmath "cosmossdk.io/math" errorsmod "cosmossdk.io/errors" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "google.golang.org/grpc/codes" @@ -89,6 +89,7 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea } // CreateBTCDelegation creates a BTC delegation +// TODO: refactor this handler. It's now too convoluted func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -96,81 +97,61 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre btccParams := ms.btccKeeper.GetParams(ctx) kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout - // 1. verify proof of possession + // verify proof of possession if err := req.Pop.Verify(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { return nil, types.ErrInvalidProofOfPossession.Wrapf("error while validating proof of posession: %v", err) } - // 2. Ensure list of validator BTC PKs is not empty - if len(req.ValBtcPkList) == 0 { - return nil, types.ErrEmptyValidatorList - } - - // 3. Ensure list of validator BTC PKs is not duplicated - if types.ExistsDup(req.ValBtcPkList) { - return nil, types.ErrDuplicatedValidator - } - - // 4. Ensure all validators are known to Babylon + // Ensure all validators are known to Babylon for _, valBTCPK := range req.ValBtcPkList { if !ms.HasBTCValidator(ctx, valBTCPK) { return nil, types.ErrBTCValNotFound.Wrapf("validator pk: %s", valBTCPK.MarshalHex()) } } - // 5. Parse staking tx + // Parse staking tx stakingMsgTx, err := bbn.NewBTCTxFromBytes(req.StakingTx.Transaction) - if err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("cannot be parsed: %v", err) } - // 6. Check staking tx is not duplicated + // Check staking tx is not duplicated stakingTxHash := stakingMsgTx.TxHash() - delgation := ms.getBTCDelegation(ctx, stakingTxHash) - if delgation != nil { return nil, types.ErrReusedStakingTx.Wrapf("duplicated tx hash: %s", stakingTxHash.String()) } - // 7. Check staking time is at most uint16 - if req.StakingTime > math.MaxUint16 { - return nil, types.ErrInvalidStakingTx.Wrapf("invalid lock time: %d, max: %d", req.StakingTime, math.MaxUint16) - } - - // 8. Check if data provided in request, matches data to which staking tx is comitted - validatorKeys := make([]*btcec.PublicKey, 0, len(req.ValBtcPkList)) - for _, valBTCPK := range req.ValBtcPkList { - validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) + // Check if data provided in request, matches data to which staking tx is committed + valPKs, err := bbn.NewBTCPKsFromBIP340PKs(req.ValBtcPkList) + if err != nil { + return nil, types.ErrInvalidStakingTx.Wrapf("cannot parse validator PK list: %v", err) } - - covenantKeys := make([]*btcec.PublicKey, 0, len(params.CovenantPks)) - for _, covenantPK := range params.CovenantPks { - covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) + covenantPKs, err := bbn.NewBTCPKsFromBIP340PKs(params.CovenantPks) + if err != nil { + // programming error + panic("failed to parse covenant PKs in KVStore") } stakingInfo, err := btcstaking.BuildStakingInfo( req.BtcPk.MustToBTCPK(), - validatorKeys, - covenantKeys, + valPKs, + covenantPKs, params.CovenantQuorum, uint16(req.StakingTime), btcutil.Amount(req.StakingValue), ms.btcNet, ) - if err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("err: %v", err) } stakingOutputIdx, err := bbn.GetOutputIdxInBTCTx(stakingMsgTx, stakingInfo.StakingOutput) - if err != nil { return nil, types.ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output") } - // 9. Check staking tx timelock has correct values + // Check staking tx timelock has correct values // get startheight and endheight of the timelock stakingTxHeader := ms.btclcKeeper.GetHeaderByHash(ctx, req.StakingTx.Key.Hash) if stakingTxHeader == nil { @@ -190,12 +171,12 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidStakingTx.Wrapf("staking tx's timelock has no more than w(=%d) blocks left", wValue) } - // 10. verify staking tx info, i.e., inclusion proof + // verify staking tx info, i.e., inclusion proof if err := req.StakingTx.VerifyInclusion(stakingTxHeader.Header, ms.btccKeeper.GetPowLimit()); err != nil { return nil, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain: %v", err) } - // 11. check slashing tx and its consistency with staking tx + // check slashing tx and its consistency with staking tx slashingMsgTx, err := req.SlashingTx.ToMsgTx() if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) @@ -208,7 +189,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) } - // 12. Check slashing tx and staking tx are valid and consistent + // Check slashing tx and staking tx are valid and consistent if err := btcstaking.CheckTransactions( slashingMsgTx, stakingMsgTx, @@ -221,23 +202,19 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidStakingTx.Wrap(err.Error()) } - stakingOutput := stakingMsgTx.TxOut[stakingOutputIdx] - - // 13. verify delegator sig against slashing path of the script + // verify delegator sig against slashing path of the staking tx's script slashingSpendInfo, err := stakingInfo.SlashingPathSpendInfo() - if err != nil { panic(fmt.Errorf("failed to construct slashing path from the staking tx: %w", err)) } err = req.SlashingTx.VerifySignature( - stakingOutput.PkScript, - stakingOutput.Value, + stakingInfo.StakingOutput.PkScript, + stakingInfo.StakingOutput.Value, slashingSpendInfo.GetPkScriptPath(), req.BtcPk.MustToBTCPK(), - req.DelegatorSig, + req.DelegatorSlashingSig, ) - if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) } @@ -253,166 +230,92 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre ValBtcPkList: req.ValBtcPkList, StartHeight: startHeight, EndHeight: endHeight, - TotalSat: uint64(stakingOutput.Value), + TotalSat: uint64(stakingInfo.StakingOutput.Value), StakingTx: req.StakingTx.Transaction, StakingOutputIdx: stakingOutputIdx, SlashingTx: req.SlashingTx, - DelegatorSig: req.DelegatorSig, + DelegatorSig: req.DelegatorSlashingSig, CovenantSigs: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant - BtcUndelegation: nil, - } - if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { - panic("failed to set BTC delegation that has passed verification") + BtcUndelegation: nil, // this will be constructed in below code } - // notify subscriber - if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { - panic(fmt.Errorf("failed to emit EventNewBTCDelegation: %w", err)) - } - - return &types.MsgCreateBTCDelegationResponse{}, nil -} - -// BtcUndelegate undelegates funds from existing delegation -func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - params := ms.GetParams(ctx) - slashingAddress := params.MustGetSlashingAddress(ms.btcNet) - wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + /* + logics about early unbonding + */ - // 1. deserialize provided transactions - slashingMsgTx, err := req.SlashingTx.ToMsgTx() + // deserialize provided transactions + unbondingSlashingMsgTx, err := req.UnbondingSlashingTx.ToMsgTx() if err != nil { - return nil, types.ErrInvalidSlashingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) + return nil, types.ErrInvalidSlashingTx.Wrapf("cannot convert unbonding slashing tx to wire.MsgTx: %v", err) } - unbondingMsgTx, err := bbn.NewBTCTxFromBytes(req.UnbondingTx) if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } - // 2. basic stateless checks for unbonding tx - if err := btcstaking.IsSimpleTransfer(unbondingMsgTx); err != nil { - return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) - } - - // 3. Check unbonding time (staking time from unbonding tx) is larger than finalization time + // Check unbonding time (staking time from unbonding tx) is larger than finalization time // Unbonding time must be strictly larger that babylon finalization time. if uint64(req.UnbondingTime) <= wValue { return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than finalization time %d", req.UnbondingTime, wValue) } - // 4. Check unbonding time is lower than max uint16 - if uint64(req.UnbondingTime) > math.MaxUint16 { - return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be lower than %d", req.UnbondingTime, math.MaxUint16) - } - - // retrieve staking tx hash from unbonding tx, at this point we know that unbonding tx is a simple transfer with - // one input and one output - unbondingTxFundingOutpoint := unbondingMsgTx.TxIn[0].PreviousOutPoint - stakingTxHash := unbondingTxFundingOutpoint.Hash.String() - - // 5. Check delegation wchich should be undelegeated exists and it is in correct state - del, err := ms.GetBTCDelegation(ctx, stakingTxHash) - - if err != nil { - return nil, types.ErrInvalidDelegationState.Wrapf("couldn't retrieve delegation for staking tx hash %s, err: %v", stakingTxHash, err) - } - - // 6. Check delegation state. Only active delegations can be unbonded. - btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := del.GetStatus(btcTip.Height, wValue, params.CovenantQuorum) - - if status != types.BTCDelegationStatus_ACTIVE { - return nil, types.ErrInvalidDelegationState.Wrapf("current status: %v, want: %s", status.String(), types.BTCDelegationStatus_ACTIVE.String()) - } - - // 7. Check unbonding tx commits to valid scripts - validatorKeys := make([]*btcec.PublicKey, 0, len(del.ValBtcPkList)) - // We retrieve validator keys from the delegation, as we want to check that unbonding tx commits to the same - // validator keys as staking tx. - for _, valBTCPK := range del.ValBtcPkList { - validatorKeys = append(validatorKeys, valBTCPK.MustToBTCPK()) - } - - covenantKeys := make([]*btcec.PublicKey, 0, len(params.CovenantPks)) - // as we do not rotate covenant keys, we can retrieve them from params - for _, covenantPK := range params.CovenantPks { - covenantKeys = append(covenantKeys, covenantPK.MustToBTCPK()) - } - + // building unbonding info unbondingInfo, err := btcstaking.BuildUnbondingInfo( - del.BtcPk.MustToBTCPK(), - validatorKeys, - covenantKeys, + newBTCDel.BtcPk.MustToBTCPK(), + valPKs, + covenantPKs, params.CovenantQuorum, uint16(req.UnbondingTime), btcutil.Amount(req.UnbondingValue), ms.btcNet, ) - if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } + // get unbonding output index unbondingOutputIdx, err := bbn.GetOutputIdxInBTCTx(unbondingMsgTx, unbondingInfo.UnbondingOutput) - if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not contain expected unbonding output") } - // 8. Check that slashing tx and unbonding tx are valid and consistent + // Check that slashing tx and unbonding tx are valid and consistent err = btcstaking.CheckTransactions( - slashingMsgTx, + unbondingSlashingMsgTx, unbondingMsgTx, unbondingOutputIdx, params.MinSlashingTxFeeSat, params.SlashingRate, - slashingAddress, + params.MustGetSlashingAddress(ms.btcNet), ms.btcNet, ) if err != nil { return nil, types.ErrInvalidUnbondingTx.Wrapf("err: %v", err) } - // 9. Check staker signature against slashing path of the unbonding tx - unbondingOutput := unbondingMsgTx.TxOut[unbondingOutputIdx] - - slashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() - + // Check staker signature against slashing path of the unbonding tx + unbondingSlashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error panic(err) } - err = req.SlashingTx.VerifySignature( - unbondingOutput.PkScript, - unbondingOutput.Value, - slashingSpendInfo.GetPkScriptPath(), - del.BtcPk.MustToBTCPK(), - req.DelegatorSlashingSig, + err = req.UnbondingSlashingTx.VerifySignature( + unbondingInfo.UnbondingOutput.PkScript, + unbondingInfo.UnbondingOutput.Value, + unbondingSlashingSpendInfo.GetPkScriptPath(), + newBTCDel.BtcPk.MustToBTCPK(), + req.DelegatorUnbondingSlashingSig, ) if err != nil { return nil, types.ErrInvalidSlashingTx.Wrapf("invalid delegator signature: %v", err) } - // 8. Check unbonding tx against staking tx. + // Check unbonding tx against staking tx. // - that input points to the staking tx, staking output // - fee is larger than 0 - stakingTxMsg, err := bbn.NewBTCTxFromBytes(del.StakingTx) - if err != nil { - // failing to get staking output info from a verified staking tx is a programming error - panic(fmt.Errorf("failed deserialize staking tx from db")) - } - - // we only check index of the staking output, as we already retrieved delegation - // by stakingTxHash computed from unbonding tx input - if unbondingTxFundingOutpoint.Index != uint32(del.StakingOutputIdx) { - return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx does not point to staking tx staking output") - } - - if unbondingMsgTx.TxOut[0].Value >= stakingTxMsg.TxOut[del.StakingOutputIdx].Value { + if unbondingMsgTx.TxOut[0].Value >= stakingMsgTx.TxOut[newBTCDel.StakingOutputIdx].Value { // Note: we do not enfore any minimum fee for unbonding tx, we only require that it is larger than 0 // Given that unbonding tx must not be replacable and we do not allow sending it second time, it places // burden on staker to choose right fee. @@ -421,44 +324,34 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx fee must be larger that 0") } - ud := types.BTCUndelegation{ - UnbondingTx: req.UnbondingTx, - SlashingTx: req.SlashingTx, - DelegatorSlashingSig: req.DelegatorSlashingSig, - // following objects needs to be filled by covenant and validator - // covenant emulators need to provide two sigs: - // - one for unbonding tx (schnorr sig) - // - one for validator of the slashing tx of unbonding tx (adaptor sig) + // all good, add BTC undelegation + newBTCDel.BtcUndelegation = &types.BTCUndelegation{ + UnbondingTx: req.UnbondingTx, + SlashingTx: req.UnbondingSlashingTx, + DelegatorSlashingSig: req.DelegatorUnbondingSlashingSig, + DelegatorUnbondingSig: nil, CovenantSlashingSigs: nil, CovenantUnbondingSigList: nil, UnbondingTime: req.UnbondingTime, } - if err := ms.AddUndelegationToBTCDelegation( - ctx, - stakingTxHash, - &ud); err != nil { + if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { panic(fmt.Errorf("failed to set BTC delegation that has passed verification: %w", err)) } // notify subscriber - event := &types.EventUnbondingBTCDelegation{ - BtcPk: del.BtcPk, - ValBtcPkList: del.ValBtcPkList, - StakingTxHash: stakingTxHash, - UnbondingTxHash: unbondingMsgTx.TxHash().String(), - } - if err := ctx.EventManager().EmitTypedEvent(event); err != nil { - panic(fmt.Errorf("failed to emit EventUnbondingBTCDelegation: %w", err)) + if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCDelegation{BtcDel: newBTCDel}); err != nil { + panic(fmt.Errorf("failed to emit EventNewBTCDelegation: %w", err)) } - return &types.MsgBTCUndelegateResponse{}, nil + return &types.MsgCreateBTCDelegationResponse{}, nil } // AddCovenantSig adds a signature from covenant to a BTC delegation -func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCovenantSig) (*types.MsgAddCovenantSigResponse, error) { +// TODO: refactor this handler. Now it's too convoluted +func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCovenantSigs) (*types.MsgAddCovenantSigsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - covenantQuorum := ms.GetParams(ctx).CovenantQuorum + params := ms.GetParams(ctx) // ensure BTC delegation exists btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) @@ -466,158 +359,73 @@ func (ms msgServer) AddCovenantSig(goCtx context.Context, req *types.MsgAddCoven return nil, err } + // ensure that the given covenant PK is in the parameter + if !params.HasCovenantPK(req.Pk) { + return nil, types.ErrInvalidCovenantPK.Wrapf("covenant pk: %s", req.Pk.MarshalHex()) + } + // Note: we assume the order of adaptor sigs is matched to the // order of validators in the delegation - // TODO ensure the order, currently, we only have one validator + // TODO: ensure the order for restaking, currently, we only have one validator // one covenant emulator - numAdaptorSig := len(req.Sigs) - numVals := len(btcDel.ValBtcPkList) - if numAdaptorSig != numVals { + if len(req.SlashingTxSigs) != len(btcDel.ValBtcPkList) { return nil, types.ErrInvalidCovenantSig.Wrapf( "number of covenant signatures: %d, number of validators being staked to: %d", - numAdaptorSig, numVals) - } - - // ensure that the given covenant PK is in the parameter - params := ms.GetParams(ctx) - if !params.HasCovenantPK(req.Pk) { - return nil, types.ErrInvalidCovenantPK.Wrapf("covenant pk: %s", req.Pk.MarshalHex()) + len(req.SlashingTxSigs), len(btcDel.ValBtcPkList)) } + /* + Verify each covenant adaptor signature over slashing tx + */ stakingInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) if err != nil { panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) } - slashingSpendInfo, err := stakingInfo.SlashingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error panic(err) } - - // verify each covenant adaptor signature with the corresponding validator public key - for i, sig := range req.Sigs { - err := verifySlashingTxAdaptorSig( - btcDel.SlashingTx, - stakingInfo.StakingOutput.PkScript, - stakingInfo.StakingOutput.Value, - slashingSpendInfo.GetPkScriptPath(), - req.Pk.MustToBTCPK(), - btcDel.ValBtcPkList[i].MustToBTCPK(), - sig, - ) - if err != nil { - return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) - } - } - - // all good, add signatures to BTC delegation and set it back to KVStore - if err := ms.AddCovenantSigsToBTCDelegation(ctx, req.StakingTxHash, req.Sigs, req.Pk, covenantQuorum); err != nil { - panic("failed to set BTC delegation that has passed verification") - } - - // notify subscriber - if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { - panic(fmt.Errorf("failed to emit EventActivateBTCDelegation: %w", err)) - } - - return &types.MsgAddCovenantSigResponse{}, nil -} - -func verifySlashingTxAdaptorSig( - slashingTx *types.BTCSlashingTx, - stakingPkScript []byte, - stakingAmount int64, - stakingScript []byte, - pk *btcec.PublicKey, - valPk *btcec.PublicKey, - sig []byte) error { - adaptorSig, err := asig.NewAdaptorSignatureFromBytes(sig) - if err != nil { - return err - } - - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPk) - if err != nil { - return err - } - - return slashingTx.EncVerifyAdaptorSignature( - stakingPkScript, - stakingAmount, - stakingScript, - pk, - encKey, - adaptorSig, + err = verifySlashingTxAdaptorSigs( + stakingInfo.StakingOutput, + slashingSpendInfo, + btcDel.SlashingTx, + req.Pk, + btcDel.ValBtcPkList, + req.SlashingTxSigs, ) -} - -func (ms msgServer) AddCovenantUnbondingSigs( - goCtx context.Context, - req *types.MsgAddCovenantUnbondingSigs, -) (*types.MsgAddCovenantUnbondingSigsResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - covenantQuorum := ms.GetParams(ctx).CovenantQuorum - - // 1. Check that delegation even exists for provided params - btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) if err != nil { - return nil, err - } - - btcTip := ms.btclcKeeper.GetTipInfo(ctx) - status := btcDel.GetStatus(btcTip.Height, wValue, covenantQuorum) - - // 2. Check that we are in proper status - if status != types.BTCDelegationStatus_UNBONDING { - return nil, types.ErrInvalidDelegationState.Wrapf("Expected status: %s, actual: %s", types.BTCDelegationStatus_UNBONDING.String(), status.String()) + return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) } - // 3. Check that the number of covenant sigs and number of the + // Check that the number of covenant sigs and number of the // validators are matched // Note: we assume the order of adaptor sigs is matched to the // order of validators in the delegation - // TODO ensure the order, currently, we only have one validator + // TODO: ensure the order for restaking, currently, we only have one validator // one covenant emulator - numAdaptorSig := len(req.SlashingUnbondingTxSigs) - numVals := len(btcDel.ValBtcPkList) - if numAdaptorSig != numVals { + if len(req.SlashingUnbondingTxSigs) != len(btcDel.ValBtcPkList) { return nil, types.ErrInvalidCovenantSig.Wrapf( "number of covenant signatures: %d, number of validators being staked to: %d", - numAdaptorSig, numVals) + len(req.SlashingUnbondingTxSigs), len(btcDel.ValBtcPkList)) } - // ensure that the given covenant PK is in the parameter - params := ms.GetParams(ctx) - if !params.HasCovenantPK(req.Pk) { - return nil, types.ErrInvalidCovenantPK - } - - unbondingTxMsg, err := bbn.NewBTCTxFromBytes(btcDel.BtcUndelegation.UnbondingTx) - + /* + Verify Schnorr signature over unbonding tx + */ + unbondingMsgTx, err := bbn.NewBTCTxFromBytes(btcDel.BtcUndelegation.UnbondingTx) if err != nil { panic(fmt.Errorf("failed to parse unbonding tx from existing delegation with hash %s : %v", req.StakingTxHash, err)) } - - unbondingTxHash := unbondingTxMsg.TxHash().String() - - stakingInfo, err := btcDel.GetStakingInfo(¶ms, ms.btcNet) - if err != nil { - panic(err) - } - unbondingSpendInfo, err := stakingInfo.UnbondingPathSpendInfo() if err != nil { // our staking info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error panic(err) } - - // 4. Verify signature of unbonding tx against staking tx output if err := btcstaking.VerifyTransactionSigWithOutputData( - unbondingTxMsg, + unbondingMsgTx, stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, unbondingSpendInfo.GetPkScriptPath(), @@ -627,61 +435,153 @@ func (ms msgServer) AddCovenantUnbondingSigs( return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) } - // 5. Verify signature of slashing tx against unbonding tx output - // unbonding tx always have only one output - unbondingOutput := unbondingTxMsg.TxOut[0] + /* + verify each adaptor signature on slashing unbonding tx + */ + unbondingOutput := unbondingMsgTx.TxOut[0] // unbonding tx always have only one output unbondingInfo, err := btcDel.GetUnbondingInfo(¶ms, ms.btcNet) if err != nil { panic(err) } - - slashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() - + unbondingSlashingSpendInfo, err := unbondingInfo.SlashingPathSpendInfo() if err != nil { // our unbonding info was constructed by using BuildStakingInfo constructor, so if // this fails, it is a programming error panic(err) } - - // verify each covenant adaptor signature with the corresponding validator public key - for i, sig := range req.SlashingUnbondingTxSigs { - err := verifySlashingTxAdaptorSig( - btcDel.BtcUndelegation.SlashingTx, - unbondingOutput.PkScript, - unbondingOutput.Value, - slashingSpendInfo.GetPkScriptPath(), - req.Pk.MustToBTCPK(), - btcDel.ValBtcPkList[i].MustToBTCPK(), - sig, - ) - if err != nil { - return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) - } + err = verifySlashingTxAdaptorSigs( + unbondingOutput, + unbondingSlashingSpendInfo, + btcDel.BtcUndelegation.SlashingTx, + req.Pk, + btcDel.ValBtcPkList, + req.SlashingUnbondingTxSigs, + ) + if err != nil { + return nil, types.ErrInvalidCovenantSig.Wrapf("err: %v", err) } - // all good, add signature to BTC undelegation and set it back to KVStore - if err := ms.AddCovenantSigsToUndelegation( + // all good, add signatures to BTC delegation/undelegation and set it back to KVStore + if err := ms.AddCovenantSigsToBTCDelegation( ctx, req.StakingTxHash, req.Pk, + req.SlashingTxSigs, req.UnbondingTxSig, req.SlashingUnbondingTxSigs, - covenantQuorum, ); err != nil { - panic("failed to set BTC delegation that has passed verification") + return nil, types.ErrInvalidCovenantSig.Wrapf("failed to add covenant signatures to BTC delegation: %v", err) } + // notify subscriber + if err := ctx.EventManager().EmitTypedEvent(&types.EventActivateBTCDelegation{BtcDel: btcDel}); err != nil { + panic(fmt.Errorf("failed to emit EventActivateBTCDelegation: %w", err)) + } + + return &types.MsgAddCovenantSigsResponse{}, nil +} + +// AddCovenantSig adds a signature from covenant to a BTC delegation +func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + bsParams := ms.GetParams(ctx) + + // ensure BTC delegation exists + btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) + if err != nil { + return nil, err + } + + // ensure the BTC delegation with the given staking tx hash is active + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + if btcDel.GetStatus(btcTip.Height, wValue, bsParams.CovenantQuorum) != types.BTCDelegationStatus_ACTIVE { + return nil, types.ErrInvalidBTCUndelegateReq.Wrap("cannot unbond an inactive BTC delegation") + } + + // verify the signature on unbonding tx from delegator + unbondingMsgTx, err := bbn.NewBTCTxFromBytes(btcDel.BtcUndelegation.UnbondingTx) + if err != nil { + panic(fmt.Errorf("failed to parse unbonding tx from existing delegation with hash %s : %v", req.StakingTxHash, err)) + } + stakingInfo, err := btcDel.GetStakingInfo(&bsParams, ms.btcNet) + if err != nil { + panic(fmt.Errorf("failed to get staking info from a verified delegation: %w", err)) + } + unbondingSpendInfo, err := stakingInfo.UnbondingPathSpendInfo() + if err != nil { + // our staking info was constructed by using BuildStakingInfo constructor, so if + // this fails, it is a programming error + panic(err) + } + if err := btcstaking.VerifyTransactionSigWithOutputData( + unbondingMsgTx, + stakingInfo.StakingOutput.PkScript, + stakingInfo.StakingOutput.Value, + unbondingSpendInfo.GetPkScriptPath(), + btcDel.BtcPk.MustToBTCPK(), + *req.UnbondingTxSig, + ); err != nil { + return nil, types.ErrInvalidCovenantSig.Wrap(err.Error()) + } + + // all good, add the signature to BTC delegation's undelegation + // and set back + err = ms.updateBTCDelegation(ctx, req.StakingTxHash, func(del *types.BTCDelegation) error { + del.BtcUndelegation.DelegatorUnbondingSig = req.UnbondingTxSig + return nil + }) + if err != nil { + return nil, err + } + + // notify subscriber about this unbonded BTC delegation event := &types.EventUnbondedBTCDelegation{ BtcPk: btcDel.BtcPk, ValBtcPkList: btcDel.ValBtcPkList, StakingTxHash: req.StakingTxHash, - UnbondingTxHash: unbondingTxHash, - FromState: types.BTCDelegationStatus_UNBONDING, + UnbondingTxHash: unbondingMsgTx.TxHash().String(), + FromState: types.BTCDelegationStatus_ACTIVE, } - if err := ctx.EventManager().EmitTypedEvent(event); err != nil { panic(fmt.Errorf("failed to emit EventUnbondedBTCDelegation: %w", err)) } - return nil, nil + return &types.MsgBTCUndelegateResponse{}, nil +} + +// verifySlashingTxAdaptorSigs verifies a list of adaptor signatures, each +// encrypted by a restaked validator PK and signed by the given PK, w.r.t. the +// given funding output (in staking or unbonding tx), slashing spend info and +// slashing tx +func verifySlashingTxAdaptorSigs( + fundingOut *wire.TxOut, + slashingSpendInfo *btcstaking.SpendInfo, + slashingTx *types.BTCSlashingTx, + pk *bbn.BIP340PubKey, + valPKs []bbn.BIP340PubKey, + sigs [][]byte, +) error { + for i, sig := range sigs { + adaptorSig, err := asig.NewAdaptorSignatureFromBytes(sig) + if err != nil { + return err + } + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPKs[i].MustToBTCPK()) + if err != nil { + return err + } + err = slashingTx.EncVerifyAdaptorSignature( + fundingOut.PkScript, + fundingOut.Value, + slashingSpendInfo.GetPkScriptPath(), + pk.MustToBTCPK(), + encKey, + adaptorSig, + ) + if err != nil { + return types.ErrInvalidCovenantSig.Wrapf("err: %v", err) + } + } + return nil } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index b6c1a7511..67572228c 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -10,15 +10,12 @@ import ( sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "github.com/babylonchain/babylon/btcstaking" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -59,7 +56,11 @@ func (h *Helper) NoError(err error) { } func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec.PublicKey) { - // TODO: randomise covenant committee and quorum? + // mocking stuff for BTC checkpoint keeper + h.BTCCheckpointKeeper.EXPECT().GetPowLimit().Return(h.Net.PowLimit).AnyTimes() + h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + + // randomise covenant committee covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) h.NoError(err) slashingAddress, err := datagen.GenRandomBTCAddress(r, h.Net) @@ -99,12 +100,12 @@ func (h *Helper) CreateDelegation( r *rand.Rand, validatorPK *btcec.PublicKey, changeAddress string, + stakingValue int64, stakingTime uint16, ) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) h.NoError(err) stakingTimeBlocks := stakingTime - stakingValue := int64(2 * 10e8) bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) h.NoError(err) @@ -143,10 +144,8 @@ func (h *Helper) CreateDelegation( txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, btcHeaderWithProof.SpvProof.MerkleNodes) // mock for testing k-deep stuff - h.BTCCheckpointKeeper.EXPECT().GetPowLimit().Return(h.Net.PowLimit).AnyTimes() - h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() h.BTCLightClientKeeper.EXPECT().GetHeaderByHash(gomock.Any(), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes() - h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}) + h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 30}).AnyTimes() slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() h.NoError(err) @@ -163,19 +162,57 @@ func (h *Helper) CreateDelegation( stakerPk := delSK.PubKey() stPk := bbn.NewBIP340PubKeyFromBTCPK(stakerPk) + /* + logics related to on-demand unbonding + */ + stkTxHash := testStakingInfo.StakingTx.TxHash() + stkOutputIdx := uint32(0) + defaultParams := btcctypes.DefaultParams() + + unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 + unbondingValue := stakingValue - 1000 + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( + r, + h.t, + h.Net, + delSK, + []*btcec.PublicKey{validatorPK}, + covPKs, + bsParams.CovenantQuorum, + wire.NewOutPoint(&stkTxHash, stkOutputIdx), + unbondingTime, + unbondingValue, + bsParams.SlashingAddress, + changeAddress, + bsParams.SlashingRate, + ) + h.NoError(err) + + delSlashingTxSig, err := testUnbondingInfo.GenDelSlashingTxSig(delSK) + h.NoError(err) + + serializedUnbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) + h.NoError(err) + // all good, construct and send MsgCreateBTCDelegation message msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - BtcPk: stPk, - ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, - Pop: pop, - StakingTime: uint32(stakingTimeBlocks), - StakingValue: stakingValue, - StakingTx: txInfo, - SlashingTx: testStakingInfo.SlashingTx, - DelegatorSig: delegatorSig, + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + BtcPk: stPk, + ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSlashingSig: delegatorSig, + UnbondingTx: serializedUnbondingTx, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: unbondingValue, + UnbondingSlashingTx: testUnbondingInfo.SlashingTx, + DelegatorUnbondingSlashingSig: delSlashingTxSig, } + _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) h.NoError(err) return stakingTxHash, delSK, delPK, msgCreateBTCDel @@ -185,35 +222,27 @@ func (h *Helper) CreateCovenantSigs( r *rand.Rand, covenantSKs []*btcec.PrivateKey, msgCreateBTCDel *types.MsgCreateBTCDelegation, - delegation *types.BTCDelegation, + del *types.BTCDelegation, ) { - stakingTx, err := bbn.NewBTCTxFromBytes(delegation.StakingTx) + stakingTx, err := bbn.NewBTCTxFromBytes(del.StakingTx) h.NoError(err) stakingTxHash := stakingTx.TxHash().String() bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - cPks, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) - h.NoError(err) - vPKs, err := bbn.NewBTCPKsFromBIP340PKs(delegation.ValBtcPkList) + vPKs, err := bbn.NewBTCPKsFromBIP340PKs(del.ValBtcPkList) h.NoError(err) - info, err := btcstaking.BuildStakingInfo( - delegation.BtcPk.MustToBTCPK(), - vPKs, - cPks, - bsParams.CovenantQuorum, - delegation.GetStakingTime(), - btcutil.Amount(delegation.TotalSat), - h.Net, - ) + stakingInfo, err := del.GetStakingInfo(&bsParams, h.Net) h.NoError(err) - slashingPathInfo, err := info.SlashingPathSpendInfo() + unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() + h.NoError(err) + slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() h.NoError(err) // generate all covenant signatures from all covenant members - covenantSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, vPKs, stakingTx, @@ -222,25 +251,60 @@ func (h *Helper) CreateCovenantSigs( ) h.NoError(err) + /* + Logics about on-demand unbonding + */ + + // slash unbonding tx spends unbonding tx + unbondingTx, err := bbn.NewBTCTxFromBytes(del.BtcUndelegation.UnbondingTx) + h.NoError(err) + unbondingInfo, err := del.GetUnbondingInfo(&bsParams, h.Net) + h.NoError(err) + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + h.NoError(err) + + // generate all covenant signatures from all covenant members + covenantUnbondingSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( + covenantSKs, + vPKs, + unbondingTx, + unbondingSlashingPathInfo.GetPkScriptPath(), + del.BtcUndelegation.SlashingTx, + ) + h.NoError(err) + // each covenant member submits signatures - for i := 0; i < len(covenantSigs); i++ { - msgAddCovenantSig := &types.MsgAddCovenantSig{ - Signer: msgCreateBTCDel.Signer, - Pk: covenantSigs[i].CovPk, - StakingTxHash: stakingTxHash, - Sigs: covenantSigs[i].AdaptorSigs, + covUnbondingSigs, err := datagen.GenCovenantUnbondingSigs(covenantSKs, stakingTx, del.StakingOutputIdx, unbondingPathInfo.GetPkScriptPath(), unbondingTx) + h.NoError(err) + + for i := 0; i < len(bsParams.CovenantPks); i++ { + msgAddCovenantSig := &types.MsgAddCovenantSigs{ + Signer: msgCreateBTCDel.Signer, + Pk: covenantSlashingTxSigs[i].CovPk, + StakingTxHash: stakingTxHash, + SlashingTxSigs: covenantSlashingTxSigs[i].AdaptorSigs, + UnbondingTxSig: bbn.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), + SlashingUnbondingTxSigs: covenantUnbondingSlashingTxSigs[i].AdaptorSigs, } - _, err = h.MsgServer.AddCovenantSig(h.Ctx, msgAddCovenantSig) + _, err = h.MsgServer.AddCovenantSigs(h.Ctx, msgAddCovenantSig) h.NoError(err) } /* ensure covenant sig is added successfully */ - actualDelWithCovenantSig, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + actualDelWithCovenantSigs, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) h.NoError(err) - require.Equal(h.t, len(actualDelWithCovenantSig.CovenantSigs), int(bsParams.CovenantQuorum)) // TODO: fix - require.True(h.t, actualDelWithCovenantSig.HasCovenantQuorum(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) + require.Equal(h.t, len(actualDelWithCovenantSigs.CovenantSigs), int(bsParams.CovenantQuorum)) // TODO: fix + require.True(h.t, actualDelWithCovenantSigs.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) + + require.NotNil(h.t, actualDelWithCovenantSigs.BtcUndelegation) + require.NotNil(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantSlashingSigs) + require.NotNil(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantUnbondingSigList) + require.Len(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantUnbondingSigList, int(bsParams.CovenantQuorum)) + require.Len(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantSlashingSigs, int(bsParams.CovenantQuorum)) + require.Len(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1) + } func (h *Helper) GetDelegationAndCheckValues( @@ -260,75 +324,10 @@ func (h *Helper) GetDelegationAndCheckValues( err = actualDel.ValidateBasic() h.NoError(err) // delegation is not activated by covenant yet - require.False(h.t, actualDel.HasCovenantQuorum(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) + require.False(h.t, actualDel.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) return actualDel } -func (h *Helper) CreateUndelegation( - r *rand.Rand, - actualDel *types.BTCDelegation, - delSK *btcec.PrivateKey, - validatorPK *btcec.PublicKey, - changeAddress string, -) *types.MsgBTCUndelegate { - stkTxHash, err := actualDel.GetStakingTxHash() - h.NoError(err) - stkOutputIdx := uint32(0) - defaultParams := btcctypes.DefaultParams() - - unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 - unbondingValue := int64(actualDel.TotalSat) - 1000 - bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) - h.NoError(err) - testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( - r, - h.t, - h.Net, - delSK, - []*btcec.PublicKey{validatorPK}, - covPKs, - bsParams.CovenantQuorum, - wire.NewOutPoint(&stkTxHash, stkOutputIdx), - unbondingTime, - unbondingValue, - bsParams.SlashingAddress, - changeAddress, - bsParams.SlashingRate, - ) - h.NoError(err) - // random signer - signer := datagen.GenRandomAccount().Address - unbondingTxMsg := testUnbondingInfo.UnbondingTx - - unbondingSlashingPathInfo, err := testUnbondingInfo.UnbondingInfo.SlashingPathSpendInfo() - h.NoError(err) - - sig, err := testUnbondingInfo.SlashingTx.Sign( - unbondingTxMsg, - 0, - unbondingSlashingPathInfo.GetPkScriptPath(), - delSK, - ) - h.NoError(err) - - serializedUnbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) - h.NoError(err) - - msg := &types.MsgBTCUndelegate{ - Signer: signer, - UnbondingTx: serializedUnbondingTx, - UnbondingTime: uint32(unbondingTime), - UnbondingValue: unbondingValue, - SlashingTx: testUnbondingInfo.SlashingTx, - DelegatorSlashingSig: sig, - } - h.BTCLightClientKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = h.MsgServer.BTCUndelegate(h.Ctx, msg) - h.NoError(err) - return msg -} - func FuzzMsgCreateBTCValidator(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) @@ -376,12 +375,11 @@ func FuzzMsgCreateBTCValidator(f *testing.F) { }) } -func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { +func FuzzCreateBTCDelegationAndAddCovenantSigs(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - net := &chaincfg.SimNetParams ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -390,43 +388,117 @@ func FuzzCreateBTCDelegationAndAddCovenantSig(f *testing.F) { btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) h := NewHelper(t, btclcKeeper, btccKeeper) - // set covenant PKs to params + // set all parameters covenantSKs, _ := h.GenAndApplyParams(r) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) require.NoError(t, err) - /* - generate and insert new BTC validator - */ + // generate and insert new BTC validator _, validatorPK, _ := h.CreateValidator(r) - /* - generate and insert new BTC delegation - */ - stakingTxHash, _, delPK, msgCreateBTCDel := h.CreateDelegation( + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + stakingTxHash, _, _, msgCreateBTCDel := h.CreateDelegation( r, validatorPK, changeAddress.EncodeAddress(), + stakingValue, 1000, ) - /* - verify the new BTC delegation - */ - // check existence - actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) + // ensure consistency between the msg and the BTC delegation in DB + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.Equal(h.t, msgCreateBTCDel.BabylonPk, actualDel.BabylonPk) + require.Equal(h.t, msgCreateBTCDel.Pop, actualDel.Pop) + require.Equal(h.t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx) + require.Equal(h.t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx) + // ensure the BTC delegation in DB is correctly formatted + err = actualDel.ValidateBasic() + h.NoError(err) + // delegation is not activated by covenant yet + require.False(h.t, actualDel.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) + + //generate and insert new covenant signatures + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) + + // ensure the BTC delegation now has voting power + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.True(h.t, actualDel.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) + votingPower := actualDel.VotingPower(h.BTCLightClientKeeper.GetTipInfo(h.Ctx).Height, h.BTCCheckpointKeeper.GetParams(h.Ctx).CheckpointFinalizationTimeout, h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum) + require.Equal(t, uint64(stakingValue), votingPower) + }) +} + +func FuzzBTCUndelegate(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + wValue := h.BTCCheckpointKeeper.GetParams(h.Ctx).CheckpointFinalizationTimeout - /* - generate and insert new covenant signature - */ + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new BTC validator + _, validatorPK, _ := h.CreateValidator(r) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + stakingTxHash, delSK, _, msgCreateBTCDel := h.CreateDelegation( + r, + validatorPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + ) + + // add covenant signatures to this BTC delegation + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) + + // ensure the BTC delegation is bonded right now + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + btcTip := h.BTCLightClientKeeper.GetTipInfo(h.Ctx).Height + status := actualDel.GetStatus(btcTip, wValue, bsParams.CovenantQuorum) + require.Equal(t, types.BTCDelegationStatus_ACTIVE, status) + + // delegator wants to unbond + delUnbondingSig, err := actualDel.SignUnbondingTx(&bsParams, h.Net, delSK) + h.NoError(err) + _, err = h.MsgServer.BTCUndelegate(h.Ctx, &types.MsgBTCUndelegate{ + Signer: datagen.GenRandomAccount().Address, + StakingTxHash: stakingTxHash, + UnbondingTxSig: bbn.NewBIP340SignatureFromBTCSig(delUnbondingSig), + }) + h.NoError(err) + + // ensure the BTC delegation is unbonded + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + status = actualDel.GetStatus(btcTip, wValue, bsParams.CovenantQuorum) + require.Equal(t, types.BTCDelegationStatus_UNBONDED, status) }) } func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) - net := &chaincfg.SimNetParams ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -440,7 +512,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { _, covenantPKs := h.GenAndApplyParams(r) bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) require.NoError(t, err) // We only generate a validator, but not insert it into KVStore. So later @@ -458,7 +530,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, t, - net, + h.Net, delSK, []*btcec.PublicKey{validatorPK}, covenantPKs, @@ -505,202 +577,18 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { // all good, construct and send MsgCreateBTCDelegation message msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, - BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), - Pop: pop, - StakingTime: uint32(stakingTimeBlocks), - StakingValue: stakingValue, - StakingTx: txInfo, - SlashingTx: testStakingInfo.SlashingTx, - DelegatorSig: delegatorSig, + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSlashingSig: delegatorSig, } _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) require.Error(t, err) require.True(t, errors.Is(err, types.ErrBTCValNotFound)) } - -func FuzzCreateBTCDelegationAndUndelegation(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - net := &chaincfg.SimNetParams - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - // mock BTC light client and BTC checkpoint modules - btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) - btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper) - - covenantSKs, _ := h.GenAndApplyParams(r) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) - _, validatorPK, _ := h.CreateValidator(r) - stakingTxHash, delSK, delPK, msgCreateBTCDel := h.CreateDelegation( - r, - validatorPK, - changeAddress.EncodeAddress(), - 1000, - ) - actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) - - undelegateMsg := h.CreateUndelegation( - r, - actualDel, - delSK, - validatorPK, - changeAddress.EncodeAddress(), - ) - - actualDelegationWithUnbonding, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) - require.NoError(t, err) - - require.NotNil(t, actualDelegationWithUnbonding.BtcUndelegation) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTx, undelegateMsg.UnbondingTx) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.SlashingTx, undelegateMsg.SlashingTx) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.DelegatorSlashingSig, undelegateMsg.DelegatorSlashingSig) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantSlashingSigs) - require.Equal(t, actualDelegationWithUnbonding.BtcUndelegation.UnbondingTime, undelegateMsg.UnbondingTime) - require.Nil(t, actualDelegationWithUnbonding.BtcUndelegation.CovenantUnbondingSigList) - }) -} - -func FuzzAddCovenantSigToUnbonding(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - net := &chaincfg.SimNetParams - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - // mock BTC light client and BTC checkpoint modules - btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) - btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper) - - covenantSKs, covenantPKs := h.GenAndApplyParams(r) - bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) - _, validatorPK, _ := h.CreateValidator(r) - stakingTxHash, delSK, delPK, msgCreateBTCDel := h.CreateDelegation( - r, - validatorPK, - changeAddress.EncodeAddress(), - 1000, - ) - actualDel := h.GetDelegationAndCheckValues(r, msgCreateBTCDel, validatorPK, delPK, stakingTxHash) - h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) - - undelegateMsg := h.CreateUndelegation( - r, - actualDel, - delSK, - validatorPK, - changeAddress.EncodeAddress(), - ) - - del, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) - require.NoError(t, err) - require.NotNil(t, del.BtcUndelegation) - - stakingTx, err := bbn.NewBTCTxFromBytes(del.StakingTx) - require.NoError(t, err) - - unbondingTx, err := bbn.NewBTCTxFromBytes(del.BtcUndelegation.UnbondingTx) - require.NoError(t, err) - - // Check sending covenant signatures - // unbonding tx spends staking tx - stakingInfo, err := btcstaking.BuildStakingInfo( - del.BtcPk.MustToBTCPK(), - []*btcec.PublicKey{validatorPK}, - covenantPKs, - bsParams.CovenantQuorum, - uint16(del.GetStakingTime()), - btcutil.Amount(del.TotalSat), - net, - ) - require.NoError(t, err) - - unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() - require.NoError(t, err) - - // slash unbonding tx spends unbonding tx - unbondingInfo, err := btcstaking.BuildUnbondingInfo( - del.BtcPk.MustToBTCPK(), - []*btcec.PublicKey{validatorPK}, - covenantPKs, - bsParams.CovenantQuorum, - uint16(del.BtcUndelegation.GetUnbondingTime()), - btcutil.Amount(unbondingTx.TxOut[0].Value), - net, - ) - require.NoError(t, err) - - unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() - require.NoError(t, err) - - enckey, err := asig.NewEncryptionKeyFromBTCPK(validatorPK) - require.NoError(t, err) - - // submit covenant signatures for each covenant member - for i := 0; i < int(bsParams.CovenantQuorum); i++ { - covenantUnbondingBTCSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - unbondingTx, - stakingTx, - del.StakingOutputIdx, - unbondingPathInfo.GetPkScriptPath(), - covenantSKs[i], - ) - - covenantUnbondingSig := bbn.NewBIP340SignatureFromBTCSig(covenantUnbondingBTCSig) - require.NoError(t, err) - - slashUnbondingTxSig, err := undelegateMsg.SlashingTx.EncSign( - unbondingTx, - 0, - unbondingSlashingPathInfo.GetPkScriptPath(), - covenantSKs[i], - enckey, - ) - require.NoError(t, err) - - covenantSigsMsg := types.MsgAddCovenantUnbondingSigs{ - Signer: datagen.GenRandomAccount().Address, - Pk: bbn.NewBIP340PubKeyFromBTCPK(covenantSKs[i].PubKey()), - StakingTxHash: stakingTxHash, - UnbondingTxSig: &covenantUnbondingSig, - SlashingUnbondingTxSigs: [][]byte{slashUnbondingTxSig.MustMarshal()}, - } - - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: actualDel.StartHeight + 1}) - _, err = h.MsgServer.AddCovenantUnbondingSigs(h.Ctx, &covenantSigsMsg) - require.NoError(t, err) - } - - delWithUnbondingSigs, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) - require.NoError(t, err) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs) - require.NotNil(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList) - require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantUnbondingSigList, int(bsParams.CovenantQuorum)) - require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs, int(bsParams.CovenantQuorum)) - require.Len(t, delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1) - - covPKMap := map[string]struct{}{} - for _, covPK := range covenantPKs { - covPKMap[bbn.NewBIP340PubKeyFromBTCPK(covPK).MarshalHex()] = struct{}{} - } - for _, actualCovSigs := range delWithUnbondingSigs.BtcUndelegation.CovenantSlashingSigs { - _, ok := covPKMap[actualCovSigs.CovPk.MarshalHex()] - require.True(t, ok) - } - }) -} diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index be0148915..1bc4b219a 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -80,22 +80,19 @@ func FuzzVotingPowerTable(f *testing.F) { } /* - Case 1: assert none of validators has voting power (since BTC height is 0) + Case 1: BTC height is 0 that is smaller than start height 1. + No BTC validator will have voting power */ babylonHeight := datagen.RandomInt(r, 10) + 1 ctx = ctx.WithBlockHeight(int64(babylonHeight)) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - for _, btcVal := range btcVals { - power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) + for i := uint64(0); i < numBTCVals; i++ { + power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) require.Zero(t, power) } - // since there is no BTC validator with BTC delegation, the BTC staking protocol is not activated yet - _, err = keeper.GetBTCStakingActivatedHeight(ctx) - require.Error(t, err) - /* Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power */ diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index ce883796b..3b037fc23 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -9,6 +9,7 @@ import ( asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -21,8 +22,6 @@ func NewBTCDelegationStatusFromString(statusStr string) (BTCDelegationStatus, er return BTCDelegationStatus_PENDING, nil case "active": return BTCDelegationStatus_ACTIVE, nil - case "unbonding": - return BTCDelegationStatus_UNBONDING, nil case "unbonded": return BTCDelegationStatus_UNBONDED, nil case "any": @@ -50,30 +49,31 @@ func (d *BTCDelegation) GetStakingTime() uint16 { // This is covered by expired for now as it is the default value. // Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig // Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig +// TODO: fix comment above // Expired: Delegation timelock func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64, covenantQuorum uint32) BTCDelegationStatus { - if d.BtcUndelegation != nil { - if d.BtcUndelegation.HasAllSignatures(covenantQuorum) { - return BTCDelegationStatus_UNBONDED - } - // If we received an undelegation but is still does not have all required signature, - // delegation receives UNBONING status. - // Voting power from this delegation is removed from the total voting power and now we - // are waiting for signatures from validator and covenant for delegation to become expired. - // For now we do not have any unbonding time on Babylon chain, only time lock on BTC chain - // we may consider adding unbonding time on Babylon chain later to avoid situation where - // we can lose to much voting power in to short time. - return BTCDelegationStatus_UNBONDING - } - - if d.StartHeight <= btcHeight && btcHeight+w <= d.EndHeight { - if d.HasCovenantQuorum(covenantQuorum) { - return BTCDelegationStatus_ACTIVE - } else { - return BTCDelegationStatus_PENDING - } + if d.BtcUndelegation.DelegatorUnbondingSig != nil { + // this means the delegator has signed unbonding signature, and Babylon will consider + // this BTC delegation unbonded directly + return BTCDelegationStatus_UNBONDED + } + + if btcHeight < d.StartHeight || btcHeight+w > d.EndHeight { + // staking tx's timelock has not begun, or is less than w BTC + // blocks left, or is expired + return BTCDelegationStatus_UNBONDED } - return BTCDelegationStatus_UNBONDED + + // at this point, BTC delegation has an active timelock, and Babylon is not + // aware of unbonding tx with delegator's signature + if d.HasCovenantQuorums(covenantQuorum) { + // this BTC delegation receives covenant quorums on + // {slashing/unbonding/unbondingslashing} txs, thus is active + return BTCDelegationStatus_ACTIVE + } + + // no covenant quorum yet, pending + return BTCDelegationStatus_PENDING } // VotingPower returns the voting power of the BTC delegation at a given BTC height @@ -143,10 +143,13 @@ func (d *BTCDelegation) ValidateBasic() error { return nil } -// HasCovenantQuorum returns whether a BTC delegation has sufficient sigs -// from Covenant members to make a quorum -func (d *BTCDelegation) HasCovenantQuorum(quorum uint32) bool { - return uint32(len(d.CovenantSigs)) >= quorum +// HasCovenantQuorum returns whether a BTC delegation has a quorum number of signatures +// from covenant members, including +// - adaptor signatures on slashing tx +// - Schnorr signatures on unbonding tx +// - adaptor signatrues on unbonding slashing tx +func (d *BTCDelegation) HasCovenantQuorums(quorum uint32) bool { + return uint32(len(d.CovenantSigs)) >= quorum && d.BtcUndelegation.HasCovenantQuorums(quorum) } // IsSignedByCovMember checks whether the given covenant PK has signed the delegation @@ -165,7 +168,7 @@ func (d *BTCDelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { // each BTC validator's PK this BTC delegation restakes to func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.AdaptorSignature, quorum uint32) error { // we can ignore the covenant sig if quorum is already reached - if d.HasCovenantQuorum(quorum) { + if d.HasCovenantQuorums(quorum) { return nil } // ensure that this covenant member has not signed the delegation yet @@ -211,6 +214,37 @@ func (d *BTCDelegation) GetStakingInfo(bsParams *Params, btcNet *chaincfg.Params return stakingInfo, nil } +func (d *BTCDelegation) SignUnbondingTx(bsParams *Params, btcNet *chaincfg.Params, sk *btcec.PrivateKey) (*schnorr.Signature, error) { + stakingTx, err := bbn.NewBTCTxFromBytes(d.StakingTx) + if err != nil { + return nil, fmt.Errorf("failed to parse staking transaction: %v", err) + } + unbondingTx, err := bbn.NewBTCTxFromBytes(d.BtcUndelegation.UnbondingTx) + if err != nil { + return nil, fmt.Errorf("failed to parse unbonding transaction: %v", err) + } + stakingInfo, err := d.GetStakingInfo(bsParams, btcNet) + if err != nil { + return nil, err + } + unbondingPath, err := stakingInfo.UnbondingPathSpendInfo() + if err != nil { + return nil, err + } + + sig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingTx, + stakingTx, + d.StakingOutputIdx, + unbondingPath.GetPkScriptPath(), + sk, + ) + if err != nil { + return nil, err + } + return sig, nil +} + // GetUnbondingInfo returns the unbonding info of the BTC delegation // the unbonding info can be used for constructing witness of unbonding slashing // tx with access to a BTC validator's SK diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index 2158aad73..59fed517b 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -25,6 +25,7 @@ func FuzzBTCDelegation(f *testing.F) { btcDel := &types.BTCDelegation{} // randomise voting power btcDel.TotalSat = datagen.RandomInt(r, 100000) + btcDel.BtcUndelegation = &types.BTCUndelegation{} // randomise covenant sig hasCovenantSig := datagen.RandomInt(r, 2) == 0 @@ -43,6 +44,8 @@ func FuzzBTCDelegation(f *testing.F) { AdaptorSigs: [][]byte{covenantSig.MustMarshal()}, } btcDel.CovenantSigs = []*types.CovenantAdaptorSignatures{covSigInfo} + btcDel.BtcUndelegation.CovenantSlashingSigs = btcDel.CovenantSigs // doesn't matter + btcDel.BtcUndelegation.CovenantUnbondingSigList = []*types.SignatureInfo{&types.SignatureInfo{}} // doesn't matter } // randomise start height and end height diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 77f3728ed..4e51ade3c 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -130,8 +130,7 @@ func (tx *BTCSlashingTx) Sign( if err != nil { return nil, err } - sig := bbn.NewBIP340SignatureFromBTCSig(schnorrSig) - return &sig, nil + return bbn.NewBIP340SignatureFromBTCSig(schnorrSig), nil } // VerifySignature verifies a signature on the slashing tx signed by staker, validator or covenant diff --git a/x/btcstaking/types/btc_undelegation.go b/x/btcstaking/types/btc_undelegation.go index 6363a79c4..61063efa4 100644 --- a/x/btcstaking/types/btc_undelegation.go +++ b/x/btcstaking/types/btc_undelegation.go @@ -37,8 +37,9 @@ func (ud *BTCUndelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { return ud.IsSignedByCovMemberOnUnbonding(covPk) && ud.IsSignedByCovMemberOnSlashing(covPk) } -func (ud *BTCUndelegation) HasAllSignatures(covenantQuorum uint32) bool { - return ud.HasCovenantQuorumOnUnbonding(covenantQuorum) && ud.HasCovenantQuorumOnSlashing(covenantQuorum) +func (ud *BTCUndelegation) HasCovenantQuorums(covenantQuorum uint32) bool { + return ud.HasCovenantQuorumOnUnbonding(covenantQuorum) && + ud.HasCovenantQuorumOnSlashing(covenantQuorum) } // AddCovenantSigs adds a Schnorr signature on the unbonding tx, and @@ -52,7 +53,7 @@ func (ud *BTCUndelegation) AddCovenantSigs( quorum uint32, ) error { // we can ignore the covenant slashing sig if quorum is already reached - if ud.HasAllSignatures(quorum) { + if ud.HasCovenantQuorums(quorum) { return nil } diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go index e84f8a319..49383630f 100644 --- a/x/btcstaking/types/btc_undelegation_test.go +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -81,22 +81,16 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { ) require.NoError(t, err) + // delegator signs the unbonding slashing tx + delSlashingTxSig, err := testInfo.GenDelSlashingTxSig(delSK) + require.NoError(t, err) + unbondingTxBytes, err := bbn.SerializeBTCTx(testInfo.UnbondingTx) require.NoError(t, err) // spend info of the unbonding slashing tx unbondingSlashingSpendInfo, err := testInfo.UnbondingInfo.SlashingPathSpendInfo() require.NoError(t, err) - - // delegator signs the unbonding slashing tx - delSig, err := testInfo.SlashingTx.Sign( - testInfo.UnbondingTx, - 0, - unbondingSlashingSpendInfo.GetPkScriptPath(), - delSK, - ) - require.NoError(t, err) - // covenant signs (using adaptor signature) the slashing tx covenantSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, @@ -111,7 +105,8 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { UnbondingTx: unbondingTxBytes, UnbondingTime: 100 + 1, SlashingTx: testInfo.SlashingTx, - DelegatorSlashingSig: delSig, + DelegatorUnbondingSig: nil, // not relevant here + DelegatorSlashingSig: delSlashingTxSig, CovenantSlashingSigs: covenantSigs, CovenantUnbondingSigList: nil, // not relevant here } diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 00ee07a55..139a02109 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -28,46 +28,37 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// BTCDelegationStatus is the status of a delegation. There are two possible state -// transition paths: -// 1. PENDING -> ACTIVE -> UNBONDED - this is the typical path when timelock of staking -// transaction expires. -// 2. PENDING _> ACTIVE -> UNBONDING -> UNBONDED - this is the path when staker requests undelegation through -// MsgBTCUndelegate message. +// BTCDelegationStatus is the status of a delegation. The state transition path is +// PENDING -> ACTIVE -> UNBONDED with two possibilities: +// 1. the typical path when timelock of staking transaction expires. +// 2. the path when staker requests early undelegation through MsgBTCUndelegate message. type BTCDelegationStatus int32 const ( - // PENDING defines a delegation that is waiting for a covenant signature to become active. + // PENDING defines a delegation that is waiting for covenant signatures to become active. BTCDelegationStatus_PENDING BTCDelegationStatus = 0 // ACTIVE defines a delegation that has voting power BTCDelegationStatus_ACTIVE BTCDelegationStatus = 1 - // UNBONDING defines a delegation that is being unbonded i.e it received an unbonding tx - // from staker, but not yet received signatures from validator and covenant. - // Delegation in this state already lost its voting power. - BTCDelegationStatus_UNBONDING BTCDelegationStatus = 2 // UNBONDED defines a delegation no longer has voting power: // - either reaching the end of staking transaction timelock - // - or receiving unbonding tx and then receiving signatures from validator and covenant for this - // unbonding tx. - BTCDelegationStatus_UNBONDED BTCDelegationStatus = 3 + // - or receiving unbonding tx with signatures from staker and covenant committee + BTCDelegationStatus_UNBONDED BTCDelegationStatus = 2 // ANY is any of the above status - BTCDelegationStatus_ANY BTCDelegationStatus = 4 + BTCDelegationStatus_ANY BTCDelegationStatus = 3 ) var BTCDelegationStatus_name = map[int32]string{ 0: "PENDING", 1: "ACTIVE", - 2: "UNBONDING", - 3: "UNBONDED", - 4: "ANY", + 2: "UNBONDED", + 3: "ANY", } var BTCDelegationStatus_value = map[string]int32{ - "PENDING": 0, - "ACTIVE": 1, - "UNBONDING": 2, - "UNBONDED": 3, - "ANY": 4, + "PENDING": 0, + "ACTIVE": 1, + "UNBONDED": 2, + "ANY": 3, } func (x BTCDelegationStatus) String() string { @@ -403,18 +394,24 @@ type BTCUndelegation struct { // It is partially signed by SK corresponding to btc_pk, but not signed by // validator or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + // delegator_unbonding_sig is the signature on the unbonding tx + // by the delegator (i.e., SK corresponding to btc_pk). + // It effectively proves that the delegator wants to unbond and thus + // Babylon will consider this BTC delegation unbonded. Delegator's BTC + // on Bitcoin will be unbonded after timelock + DelegatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_unbonding_sig,json=delegatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_unbonding_sig,omitempty"` // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbonding tx output. - DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx // by each covenant member // It will be a part of the witness for the staking tx output. - CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,5,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` + CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,6,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon - CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,6,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,7,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } func (m *BTCUndelegation) Reset() { *m = BTCUndelegation{} } @@ -741,77 +738,78 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1111 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0xae, 0xe3, 0x34, 0x6d, 0x4e, 0x12, 0x9a, 0x1d, 0x42, 0xf1, 0xb6, 0x22, 0x09, 0x61, 0x59, - 0x45, 0x88, 0x75, 0xb6, 0xdd, 0x1f, 0x01, 0x17, 0x48, 0x4d, 0x53, 0xd8, 0x68, 0xfb, 0x13, 0x9c, - 0x74, 0x11, 0x48, 0x10, 0x4d, 0xec, 0xa9, 0x63, 0x25, 0xf1, 0x58, 0x99, 0x49, 0x48, 0xee, 0xb9, - 0x45, 0xe2, 0x1d, 0xe0, 0x11, 0x78, 0x04, 0x2e, 0xb8, 0x41, 0x5a, 0xc1, 0x0d, 0xea, 0x45, 0x85, - 0xda, 0x17, 0x41, 0x1e, 0x4f, 0x62, 0xb7, 0xb4, 0xfb, 0xd7, 0xde, 0x65, 0xce, 0xcf, 0x77, 0xce, - 0xf9, 0xbe, 0x33, 0x13, 0xc3, 0xdd, 0x0e, 0xee, 0x4c, 0xfb, 0xd4, 0xad, 0x74, 0xb8, 0xc9, 0x38, - 0xee, 0x39, 0xae, 0x5d, 0x19, 0x6f, 0x44, 0x4e, 0xba, 0x37, 0xa4, 0x9c, 0xa2, 0x77, 0x64, 0x9c, - 0x1e, 0xf1, 0x8c, 0x37, 0xd6, 0x72, 0x36, 0xb5, 0xa9, 0x88, 0xa8, 0xf8, 0xbf, 0x82, 0xe0, 0xb5, - 0xdb, 0x26, 0x65, 0x03, 0xca, 0xda, 0x81, 0x23, 0x38, 0x48, 0x57, 0x29, 0x38, 0x55, 0xcc, 0xe1, - 0xd4, 0xe3, 0xb4, 0xc2, 0x88, 0xe9, 0x6d, 0x3e, 0x7a, 0xdc, 0xdb, 0xa8, 0xf4, 0xc8, 0x74, 0x16, - 0x73, 0x47, 0xc6, 0x84, 0xfd, 0x74, 0x08, 0xc7, 0x1b, 0x95, 0x73, 0x1d, 0xad, 0x15, 0x2e, 0xef, - 0xdc, 0xa3, 0x5e, 0x10, 0x50, 0xfa, 0x5b, 0x85, 0x74, 0xb5, 0xb5, 0xfd, 0x0c, 0xf7, 0x1d, 0x0b, - 0x73, 0x3a, 0x44, 0x3b, 0x90, 0xb2, 0x08, 0x33, 0x87, 0x8e, 0xc7, 0x1d, 0xea, 0x6a, 0x4a, 0x51, - 0x29, 0xa7, 0x36, 0x3f, 0xd0, 0x65, 0x7f, 0xe1, 0x54, 0xa2, 0x9a, 0x5e, 0x0b, 0x43, 0x8d, 0x68, - 0x1e, 0xda, 0x03, 0x30, 0xe9, 0x60, 0xe0, 0x30, 0xe6, 0xa3, 0xc4, 0x8a, 0x4a, 0x39, 0x59, 0xbd, - 0x77, 0x7c, 0x52, 0x58, 0x0f, 0x80, 0x98, 0xd5, 0xd3, 0x1d, 0x5a, 0x19, 0x60, 0xde, 0xd5, 0x77, - 0x89, 0x8d, 0xcd, 0x69, 0x8d, 0x98, 0x7f, 0xfd, 0x76, 0x0f, 0x64, 0x9d, 0x1a, 0x31, 0x8d, 0x08, - 0x00, 0xfa, 0x1c, 0x40, 0x4e, 0xd2, 0xf6, 0x7a, 0x9a, 0x2a, 0x9a, 0x2a, 0xcc, 0x9a, 0x0a, 0x68, - 0xd2, 0xe7, 0x34, 0xe9, 0x8d, 0x51, 0xe7, 0x29, 0x99, 0x1a, 0x49, 0x99, 0xd2, 0xe8, 0xa1, 0x3d, - 0x48, 0x74, 0xb8, 0xe9, 0xe7, 0xc6, 0x8b, 0x4a, 0x39, 0x5d, 0x7d, 0x7c, 0x7c, 0x52, 0xd8, 0xb4, - 0x1d, 0xde, 0x1d, 0x75, 0x74, 0x93, 0x0e, 0x2a, 0x32, 0xd2, 0xec, 0x62, 0xc7, 0x9d, 0x1d, 0x2a, - 0x7c, 0xea, 0x11, 0xa6, 0x57, 0xeb, 0x8d, 0x07, 0x0f, 0xef, 0x4b, 0xc8, 0xc5, 0x0e, 0x37, 0x1b, - 0x3d, 0xf4, 0x19, 0xa8, 0x1e, 0xf5, 0xb4, 0x45, 0xd1, 0x47, 0x59, 0xbf, 0x54, 0x76, 0xbd, 0x31, - 0xa4, 0xf4, 0xe8, 0xe0, 0xa8, 0x41, 0x19, 0x23, 0x62, 0x0a, 0xc3, 0x4f, 0x42, 0x0f, 0x61, 0x95, - 0xf5, 0x31, 0xeb, 0x12, 0xab, 0x3d, 0x1b, 0xa9, 0x4b, 0x1c, 0xbb, 0xcb, 0xb5, 0x44, 0x51, 0x29, - 0xc7, 0x8d, 0x9c, 0xf4, 0x56, 0x03, 0xe7, 0x13, 0xe1, 0x43, 0x1f, 0x03, 0x9a, 0x67, 0x71, 0x73, - 0x96, 0xb1, 0x24, 0x32, 0xb2, 0xb3, 0x0c, 0x6e, 0x06, 0xd1, 0xa5, 0x1f, 0x63, 0x90, 0x8b, 0xaa, - 0xfa, 0xb5, 0xc3, 0xbb, 0x7b, 0x84, 0xe3, 0x08, 0x0f, 0xca, 0x4d, 0xf0, 0xb0, 0x0a, 0x09, 0xd9, - 0x49, 0x4c, 0x74, 0x22, 0x4f, 0xe8, 0x7d, 0x48, 0x8f, 0x29, 0x77, 0x5c, 0xbb, 0xed, 0xd1, 0x1f, - 0xc8, 0x50, 0x08, 0x16, 0x37, 0x52, 0x81, 0xad, 0xe1, 0x9b, 0x5e, 0x40, 0x43, 0xfc, 0xb5, 0x69, - 0x58, 0xbc, 0x82, 0x86, 0x5f, 0x12, 0x90, 0xa9, 0xb6, 0xb6, 0x6b, 0xa4, 0x4f, 0x6c, 0xcc, 0xff, - 0xbf, 0x47, 0xca, 0x35, 0xf6, 0x28, 0x76, 0x83, 0x7b, 0xa4, 0xbe, 0xc9, 0x1e, 0x7d, 0x07, 0x2b, - 0x63, 0xdc, 0x6f, 0x07, 0xed, 0xb4, 0xfb, 0x0e, 0xf3, 0x99, 0x53, 0xaf, 0xd1, 0x53, 0x7a, 0x8c, - 0xfb, 0x55, 0xbf, 0xad, 0x5d, 0x87, 0x09, 0x09, 0x19, 0xc7, 0x43, 0x7e, 0x9e, 0xe3, 0x94, 0xb0, - 0x49, 0x31, 0xde, 0x03, 0x20, 0xae, 0x75, 0x7e, 0x7b, 0x93, 0xc4, 0xb5, 0xa4, 0x7b, 0x1d, 0x92, - 0x9c, 0x72, 0xdc, 0x6f, 0x33, 0x3c, 0xdb, 0xd4, 0x65, 0x61, 0x68, 0x62, 0x91, 0x2b, 0x47, 0x6c, - 0xf3, 0x89, 0xb6, 0xec, 0x93, 0x69, 0x24, 0xa5, 0xa5, 0x35, 0x11, 0x3a, 0x4b, 0x37, 0x1d, 0x71, - 0x6f, 0xc4, 0xdb, 0x8e, 0x35, 0xd1, 0x92, 0x45, 0xa5, 0x9c, 0x31, 0xb2, 0xd2, 0x73, 0x20, 0x1c, - 0x75, 0x6b, 0x82, 0x36, 0x21, 0x25, 0xb4, 0x97, 0x68, 0x20, 0xa4, 0xb9, 0x75, 0x7c, 0x52, 0xf0, - 0xd5, 0x6f, 0x4a, 0x4f, 0x6b, 0x62, 0x00, 0x9b, 0xff, 0x46, 0xdf, 0x43, 0xc6, 0x0a, 0xf6, 0x82, - 0x0e, 0xdb, 0xcc, 0xb1, 0xb5, 0x94, 0xc8, 0xfa, 0xf4, 0xf8, 0xa4, 0xf0, 0xe8, 0x75, 0xc8, 0x6b, - 0x3a, 0xb6, 0x8b, 0xf9, 0x68, 0x48, 0x8c, 0xf4, 0x1c, 0xaf, 0xe9, 0xd8, 0xe8, 0x10, 0x32, 0x26, - 0x1d, 0x13, 0x17, 0xbb, 0xdc, 0x87, 0x67, 0x5a, 0xba, 0xa8, 0x96, 0x53, 0x9b, 0xf7, 0xaf, 0x10, - 0x79, 0x5b, 0xc6, 0x6e, 0x59, 0xd8, 0x0b, 0x10, 0x02, 0x54, 0x66, 0xa4, 0x67, 0x30, 0x4d, 0xc7, - 0x66, 0xe8, 0x2b, 0xc8, 0xfa, 0x8a, 0x8f, 0x5c, 0x6b, 0xbe, 0xd4, 0x5a, 0x46, 0xac, 0xcf, 0xdd, - 0x2b, 0x90, 0xab, 0xad, 0xed, 0xc3, 0x48, 0xb4, 0xb1, 0xd2, 0xe1, 0x66, 0xd4, 0x50, 0xfa, 0x53, - 0x85, 0x95, 0x0b, 0x41, 0xbe, 0xfa, 0x23, 0xb7, 0x43, 0x5d, 0x4b, 0x52, 0x2a, 0x5e, 0x0b, 0x23, - 0x35, 0xb7, 0xb5, 0x26, 0xe8, 0x43, 0x78, 0x2b, 0x12, 0xe2, 0x0c, 0x88, 0xb8, 0x12, 0x19, 0x23, - 0x13, 0x06, 0x39, 0x03, 0x72, 0x51, 0x1b, 0xf5, 0x55, 0xb4, 0xa1, 0xb0, 0x1a, 0xd1, 0x66, 0x96, - 0xed, 0x8b, 0x14, 0xbf, 0xae, 0x48, 0xb9, 0x50, 0x24, 0x89, 0xeb, 0x8b, 0x75, 0x04, 0xab, 0xa1, - 0x58, 0x91, 0x7a, 0x4c, 0x5b, 0x7c, 0x43, 0xd5, 0x72, 0x73, 0xd5, 0xc2, 0x32, 0x0c, 0x99, 0xb0, - 0x3e, 0xaf, 0x13, 0x92, 0xc7, 0x1c, 0x3b, 0xb8, 0xbf, 0x09, 0x51, 0xec, 0xce, 0x15, 0xc5, 0xe6, - 0xe8, 0x75, 0xf7, 0x88, 0x1a, 0xda, 0x0c, 0xe8, 0x70, 0x86, 0xd3, 0x74, 0x6c, 0xff, 0xe6, 0x96, - 0x7e, 0x57, 0xe0, 0xed, 0x0b, 0x7a, 0xfa, 0x19, 0x37, 0xa8, 0xe9, 0x4b, 0xc6, 0x50, 0x6f, 0x64, - 0x8c, 0x26, 0xbc, 0x1b, 0xbe, 0xdd, 0x74, 0x18, 0x3e, 0xe2, 0x0c, 0x7d, 0x02, 0x71, 0x8b, 0xf4, - 0x99, 0xa6, 0xbc, 0xb0, 0xd0, 0xb9, 0x97, 0xdf, 0x10, 0x19, 0xa5, 0x7d, 0x58, 0xbf, 0x1c, 0xb4, - 0xee, 0x5a, 0x64, 0x82, 0x2a, 0x90, 0x0b, 0x5f, 0xa5, 0x76, 0x17, 0xb3, 0x6e, 0x30, 0x91, 0x5f, - 0x28, 0x6d, 0xdc, 0x9a, 0xbf, 0x4f, 0x4f, 0x30, 0xeb, 0x8a, 0x26, 0x7f, 0x55, 0x20, 0x73, 0x6e, - 0x20, 0xf4, 0x05, 0xc4, 0xae, 0xfd, 0xef, 0x1a, 0xf3, 0x7a, 0xe8, 0x29, 0xa8, 0xfe, 0xc2, 0xc7, - 0xae, 0xbb, 0xf0, 0x3e, 0x4a, 0xe9, 0x27, 0x05, 0x6e, 0x5f, 0xb9, 0xab, 0xfe, 0x9f, 0x9a, 0x49, - 0xc7, 0x37, 0xf0, 0x51, 0x60, 0xd2, 0x71, 0xa3, 0xe7, 0xef, 0x19, 0x0e, 0x6a, 0x04, 0x57, 0x28, - 0x26, 0xc8, 0x4b, 0xe1, 0x79, 0x5d, 0xf6, 0x51, 0x4b, 0x6c, 0x68, 0xc8, 0x7e, 0x93, 0x63, 0x3e, - 0x62, 0x28, 0x05, 0x4b, 0x8d, 0x9d, 0xfd, 0x5a, 0x7d, 0xff, 0xcb, 0xec, 0x02, 0x02, 0x48, 0x6c, - 0x6d, 0xb7, 0xea, 0xcf, 0x76, 0xb2, 0x0a, 0xca, 0x40, 0xf2, 0x70, 0xbf, 0x7a, 0x10, 0xb8, 0x62, - 0x28, 0x0d, 0xcb, 0xc1, 0x71, 0xa7, 0x96, 0x55, 0xd1, 0x12, 0xa8, 0x5b, 0xfb, 0xdf, 0x64, 0xe3, - 0xd5, 0xdd, 0x3f, 0x4e, 0xf3, 0xca, 0xf3, 0xd3, 0xbc, 0xf2, 0xef, 0x69, 0x5e, 0xf9, 0xf9, 0x2c, - 0xbf, 0xf0, 0xfc, 0x2c, 0xbf, 0xf0, 0xcf, 0x59, 0x7e, 0xe1, 0xdb, 0x97, 0x4e, 0x33, 0x89, 0x7e, - 0x20, 0x8b, 0xd1, 0x3a, 0x09, 0xf1, 0x81, 0xfc, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, - 0x7f, 0xe0, 0x9e, 0xfd, 0x0b, 0x00, 0x00, + // 1123 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4d, 0x6f, 0x1a, 0x47, + 0x18, 0xf6, 0x02, 0xc6, 0xe1, 0x05, 0x1a, 0x32, 0x25, 0xce, 0x26, 0x56, 0x81, 0xd2, 0x34, 0x42, + 0x55, 0xb3, 0xc4, 0xce, 0x87, 0xda, 0x1e, 0x2a, 0x05, 0xe3, 0x36, 0x28, 0x09, 0xa1, 0x0b, 0x4e, + 0xd5, 0x4a, 0x2d, 0x1a, 0x76, 0xc7, 0xcb, 0x0a, 0xd8, 0xd9, 0x32, 0x03, 0x85, 0x7b, 0xaf, 0x95, + 0xfa, 0x1f, 0xda, 0x9f, 0xd0, 0x1f, 0xd0, 0x43, 0x0f, 0x3d, 0x46, 0xed, 0xa5, 0xf2, 0xc1, 0xaa, + 0xec, 0x3f, 0x52, 0xed, 0xec, 0xb0, 0xbb, 0xb8, 0x76, 0xd2, 0x04, 0xdf, 0x98, 0xf7, 0xe3, 0x79, + 0x3f, 0x9e, 0x87, 0x99, 0x85, 0x5b, 0x3d, 0xdc, 0x9b, 0x0f, 0xa9, 0x53, 0xed, 0x71, 0x83, 0x71, + 0x3c, 0xb0, 0x1d, 0xab, 0x3a, 0xdd, 0x8e, 0x9c, 0x34, 0x77, 0x4c, 0x39, 0x45, 0x57, 0x65, 0x9c, + 0x16, 0xf1, 0x4c, 0xb7, 0x6f, 0xe4, 0x2d, 0x6a, 0x51, 0x11, 0x51, 0xf5, 0x7e, 0xf9, 0xc1, 0x37, + 0xae, 0x1b, 0x94, 0x8d, 0x28, 0xeb, 0xfa, 0x0e, 0xff, 0x20, 0x5d, 0x65, 0xff, 0x54, 0x35, 0xc6, + 0x73, 0x97, 0xd3, 0x2a, 0x23, 0x86, 0xbb, 0x73, 0xff, 0xc1, 0x60, 0xbb, 0x3a, 0x20, 0xf3, 0x45, + 0xcc, 0x4d, 0x19, 0x13, 0xf6, 0xd3, 0x23, 0x1c, 0x6f, 0x57, 0x97, 0x3a, 0xba, 0x51, 0x3c, 0xbb, + 0x73, 0x97, 0xba, 0x7e, 0x40, 0xf9, 0xaf, 0x38, 0x64, 0x6a, 0x9d, 0xdd, 0xe7, 0x78, 0x68, 0x9b, + 0x98, 0xd3, 0x31, 0xda, 0x83, 0xb4, 0x49, 0x98, 0x31, 0xb6, 0x5d, 0x6e, 0x53, 0x47, 0x55, 0x4a, + 0x4a, 0x25, 0xbd, 0xf3, 0x9e, 0x26, 0xfb, 0x0b, 0xa7, 0x12, 0xd5, 0xb4, 0x7a, 0x18, 0xaa, 0x47, + 0xf3, 0xd0, 0x53, 0x00, 0x83, 0x8e, 0x46, 0x36, 0x63, 0x1e, 0x4a, 0xac, 0xa4, 0x54, 0x52, 0xb5, + 0xdb, 0x87, 0x47, 0xc5, 0x2d, 0x1f, 0x88, 0x99, 0x03, 0xcd, 0xa6, 0xd5, 0x11, 0xe6, 0x7d, 0xed, + 0x09, 0xb1, 0xb0, 0x31, 0xaf, 0x13, 0xe3, 0xcf, 0x5f, 0x6f, 0x83, 0xac, 0x53, 0x27, 0x86, 0x1e, + 0x01, 0x40, 0x9f, 0x02, 0xc8, 0x49, 0xba, 0xee, 0x40, 0x8d, 0x8b, 0xa6, 0x8a, 0x8b, 0xa6, 0xfc, + 0x35, 0x69, 0xc1, 0x9a, 0xb4, 0xd6, 0xa4, 0xf7, 0x98, 0xcc, 0xf5, 0x94, 0x4c, 0x69, 0x0d, 0xd0, + 0x53, 0x48, 0xf6, 0xb8, 0xe1, 0xe5, 0x26, 0x4a, 0x4a, 0x25, 0x53, 0x7b, 0x70, 0x78, 0x54, 0xdc, + 0xb1, 0x6c, 0xde, 0x9f, 0xf4, 0x34, 0x83, 0x8e, 0xaa, 0x32, 0xd2, 0xe8, 0x63, 0xdb, 0x59, 0x1c, + 0xaa, 0x7c, 0xee, 0x12, 0xa6, 0xd5, 0x1a, 0xad, 0xbb, 0xf7, 0xee, 0x48, 0xc8, 0xf5, 0x1e, 0x37, + 0x5a, 0x03, 0xf4, 0x09, 0xc4, 0x5d, 0xea, 0xaa, 0xeb, 0xa2, 0x8f, 0x8a, 0x76, 0x26, 0xed, 0x5a, + 0x6b, 0x4c, 0xe9, 0xc1, 0xb3, 0x83, 0x16, 0x65, 0x8c, 0x88, 0x29, 0x74, 0x2f, 0x09, 0xdd, 0x83, + 0x4d, 0x36, 0xc4, 0xac, 0x4f, 0xcc, 0xee, 0x62, 0xa4, 0x3e, 0xb1, 0xad, 0x3e, 0x57, 0x93, 0x25, + 0xa5, 0x92, 0xd0, 0xf3, 0xd2, 0x5b, 0xf3, 0x9d, 0x8f, 0x84, 0x0f, 0x7d, 0x08, 0x28, 0xc8, 0xe2, + 0xc6, 0x22, 0x63, 0x43, 0x64, 0xe4, 0x16, 0x19, 0xdc, 0xf0, 0xa3, 0xcb, 0x3f, 0xc4, 0x20, 0x1f, + 0x65, 0xf5, 0x4b, 0x9b, 0xf7, 0x9f, 0x12, 0x8e, 0x23, 0x7b, 0x50, 0x2e, 0x62, 0x0f, 0x9b, 0x90, + 0x94, 0x9d, 0xc4, 0x44, 0x27, 0xf2, 0x84, 0xde, 0x85, 0xcc, 0x94, 0x72, 0xdb, 0xb1, 0xba, 0x2e, + 0xfd, 0x9e, 0x8c, 0x05, 0x61, 0x09, 0x3d, 0xed, 0xdb, 0x5a, 0x9e, 0xe9, 0x25, 0x6b, 0x48, 0xbc, + 0xf6, 0x1a, 0xd6, 0xcf, 0x59, 0xc3, 0xcf, 0x49, 0xc8, 0xd6, 0x3a, 0xbb, 0x75, 0x32, 0x24, 0x16, + 0xe6, 0xff, 0xd5, 0x91, 0xb2, 0x82, 0x8e, 0x62, 0x17, 0xa8, 0xa3, 0xf8, 0x9b, 0xe8, 0xe8, 0x1b, + 0xb8, 0x3c, 0xc5, 0xc3, 0xae, 0xdf, 0x4e, 0x77, 0x68, 0x33, 0x6f, 0x73, 0xf1, 0x15, 0x7a, 0xca, + 0x4c, 0xf1, 0xb0, 0xe6, 0xb5, 0xf5, 0xc4, 0x66, 0x82, 0x42, 0xc6, 0xf1, 0x98, 0x2f, 0xef, 0x38, + 0x2d, 0x6c, 0x92, 0x8c, 0x77, 0x00, 0x88, 0x63, 0x2e, 0xab, 0x37, 0x45, 0x1c, 0x53, 0xba, 0xb7, + 0x20, 0xc5, 0x29, 0xc7, 0xc3, 0x2e, 0xc3, 0x0b, 0xa5, 0x5e, 0x12, 0x86, 0x36, 0x16, 0xb9, 0x72, + 0xc4, 0x2e, 0x9f, 0xa9, 0x97, 0xbc, 0x65, 0xea, 0x29, 0x69, 0xe9, 0xcc, 0x04, 0xcf, 0xd2, 0x4d, + 0x27, 0xdc, 0x9d, 0xf0, 0xae, 0x6d, 0xce, 0xd4, 0x54, 0x49, 0xa9, 0x64, 0xf5, 0x9c, 0xf4, 0x3c, + 0x13, 0x8e, 0x86, 0x39, 0x43, 0x3b, 0x90, 0x16, 0xdc, 0x4b, 0x34, 0x10, 0xd4, 0x5c, 0x39, 0x3c, + 0x2a, 0x7a, 0xec, 0xb7, 0xa5, 0xa7, 0x33, 0xd3, 0x81, 0x05, 0xbf, 0xd1, 0xb7, 0x90, 0x35, 0x7d, + 0x5d, 0xd0, 0x71, 0x97, 0xd9, 0x96, 0x9a, 0x16, 0x59, 0x1f, 0x1f, 0x1e, 0x15, 0xef, 0xbf, 0xce, + 0xf2, 0xda, 0xb6, 0xe5, 0x60, 0x3e, 0x19, 0x13, 0x3d, 0x13, 0xe0, 0xb5, 0x6d, 0x0b, 0xed, 0x43, + 0xd6, 0xa0, 0x53, 0xe2, 0x60, 0x87, 0x7b, 0xf0, 0x4c, 0xcd, 0x94, 0xe2, 0x95, 0xf4, 0xce, 0x9d, + 0x73, 0x48, 0xde, 0x95, 0xb1, 0x0f, 0x4d, 0xec, 0xfa, 0x08, 0x3e, 0x2a, 0xd3, 0x33, 0x0b, 0x98, + 0xb6, 0x6d, 0x31, 0xf4, 0x05, 0xe4, 0x3c, 0xc6, 0x27, 0x8e, 0x19, 0x88, 0x5a, 0xcd, 0x0a, 0xf9, + 0xdc, 0x3a, 0x07, 0xb9, 0xd6, 0xd9, 0xdd, 0x8f, 0x44, 0xeb, 0x97, 0x7b, 0xdc, 0x88, 0x1a, 0xca, + 0xbf, 0x25, 0xe0, 0xf2, 0xa9, 0x20, 0x8f, 0xfd, 0x89, 0xd3, 0xa3, 0x8e, 0x29, 0x57, 0x2a, 0x6e, + 0x0b, 0x3d, 0x1d, 0xd8, 0x3a, 0x33, 0xf4, 0x3e, 0xbc, 0x15, 0x09, 0xb1, 0x47, 0x44, 0xfc, 0x25, + 0xb2, 0x7a, 0x36, 0x0c, 0xb2, 0x47, 0xe4, 0x34, 0x37, 0xf1, 0xff, 0xc3, 0xcd, 0x77, 0x70, 0x2d, + 0xe4, 0x26, 0x2c, 0xe2, 0xb1, 0x94, 0x58, 0x95, 0xa5, 0xab, 0x01, 0xf2, 0xfe, 0x02, 0xd8, 0xa3, + 0x8b, 0xc2, 0x66, 0x44, 0x0e, 0x8b, 0x86, 0xbd, 0x8a, 0xeb, 0xab, 0x56, 0xcc, 0x87, 0xba, 0x90, + 0xb8, 0x5e, 0xc1, 0x03, 0xd8, 0x0c, 0xf5, 0x11, 0xa9, 0xc7, 0xd4, 0xe4, 0x1b, 0x0a, 0x25, 0x1f, + 0x08, 0x25, 0x2c, 0xc3, 0x90, 0x01, 0x5b, 0x41, 0x9d, 0xa5, 0x55, 0xfa, 0x57, 0xc6, 0x86, 0x28, + 0x76, 0xf3, 0x9c, 0x62, 0x01, 0x7a, 0xc3, 0x39, 0xa0, 0xba, 0xba, 0x00, 0x8a, 0x6e, 0xce, 0xbb, + 0x2c, 0xca, 0xbf, 0x2b, 0xf0, 0xf6, 0x29, 0x09, 0x79, 0x19, 0x17, 0x28, 0xa3, 0x57, 0x8c, 0x11, + 0xbf, 0x90, 0x31, 0xda, 0x70, 0x2d, 0x7c, 0x2e, 0xe8, 0x38, 0x7c, 0x37, 0x18, 0xfa, 0x08, 0x12, + 0x26, 0x19, 0x32, 0x55, 0x79, 0x69, 0xa1, 0xa5, 0xc7, 0x46, 0x17, 0x19, 0xe5, 0x26, 0x6c, 0x9d, + 0x0d, 0xda, 0x70, 0x4c, 0x32, 0x43, 0x55, 0xc8, 0x87, 0x17, 0x61, 0xb7, 0x8f, 0x59, 0xdf, 0x9f, + 0xc8, 0x2b, 0x94, 0xd1, 0xaf, 0x04, 0x57, 0xe2, 0x23, 0xcc, 0xfa, 0xa2, 0xc9, 0x5f, 0x14, 0xc8, + 0x2e, 0x0d, 0x84, 0x3e, 0x83, 0xd8, 0xca, 0x0f, 0x7a, 0xcc, 0x1d, 0xa0, 0xc7, 0x10, 0xf7, 0x04, + 0x1f, 0x5b, 0x55, 0xf0, 0x1e, 0x4a, 0xf9, 0x47, 0x05, 0xae, 0x9f, 0xab, 0x55, 0xef, 0x1d, 0x35, + 0xe8, 0xf4, 0x02, 0xbe, 0x43, 0x0c, 0x3a, 0x6d, 0x0d, 0x3c, 0x9d, 0x61, 0xbf, 0x86, 0xff, 0x17, + 0x8a, 0x89, 0xe5, 0xa5, 0x71, 0x50, 0x97, 0x7d, 0xb0, 0x27, 0x14, 0x1a, 0x6e, 0xbf, 0xcd, 0x31, + 0x9f, 0x30, 0x94, 0x86, 0x8d, 0xd6, 0x5e, 0xb3, 0xde, 0x68, 0x7e, 0x9e, 0x5b, 0x43, 0x00, 0xc9, + 0x87, 0xbb, 0x9d, 0xc6, 0xf3, 0xbd, 0x9c, 0x82, 0x32, 0x70, 0x69, 0xbf, 0x59, 0x7b, 0xd6, 0xac, + 0xef, 0xd5, 0x73, 0x31, 0xb4, 0x01, 0xf1, 0x87, 0xcd, 0xaf, 0x72, 0xf1, 0xda, 0x93, 0x3f, 0x8e, + 0x0b, 0xca, 0x8b, 0xe3, 0x82, 0xf2, 0xcf, 0x71, 0x41, 0xf9, 0xe9, 0xa4, 0xb0, 0xf6, 0xe2, 0xa4, + 0xb0, 0xf6, 0xf7, 0x49, 0x61, 0xed, 0xeb, 0x57, 0xb6, 0x3f, 0x8b, 0x7e, 0x84, 0x8b, 0x59, 0x7a, + 0x49, 0xf1, 0x11, 0x7e, 0xf7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x8e, 0xcb, 0xae, 0x61, + 0x0c, 0x00, 0x00, } func (m *BTCValidator) Marshal() (dAtA []byte, err error) { @@ -1143,7 +1141,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 + dAtA[i] = 0x3a } } if len(m.CovenantSlashingSigs) > 0 { @@ -1157,7 +1155,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x32 } } if m.DelegatorSlashingSig != nil { @@ -1170,6 +1168,18 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x2a + } + if m.DelegatorUnbondingSig != nil { + { + size := m.DelegatorUnbondingSig.Size() + i -= size + if _, err := m.DelegatorUnbondingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x22 } if m.SlashingTx != nil { @@ -1557,6 +1567,10 @@ func (m *BTCUndelegation) Size() (n int) { l = m.SlashingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) } + if m.DelegatorUnbondingSig != nil { + l = m.DelegatorUnbondingSig.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } if m.DelegatorSlashingSig != nil { l = m.DelegatorSlashingSig.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -2658,6 +2672,41 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorUnbondingSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.DelegatorUnbondingSig = &v + if err := m.DelegatorUnbondingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } @@ -2692,7 +2741,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSigs", wireType) } @@ -2726,7 +2775,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 6: + case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index afc7b1955..b272c8b50 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -10,10 +10,8 @@ import ( func RegisterCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgCreateBTCValidator{}, "btcstaking/MsgCreateBTCValidator", nil) cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) - cdc.RegisterConcrete(&MsgAddCovenantSig{}, "btcstaking/MsgAddCovenantSig", nil) + cdc.RegisterConcrete(&MsgAddCovenantSigs{}, "btcstaking/MsgAddCovenantSigs", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) - cdc.RegisterConcrete(&MsgBTCUndelegate{}, "btcstaking/MsgBtcUndelegate", nil) - cdc.RegisterConcrete(&MsgAddCovenantUnbondingSigs{}, "btcstaking/MsgAddCovenantUnbondingSigs", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { @@ -22,10 +20,8 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgCreateBTCValidator{}, &MsgCreateBTCDelegation{}, - &MsgAddCovenantSig{}, + &MsgAddCovenantSigs{}, &MsgUpdateParams{}, - &MsgBTCUndelegate{}, - &MsgAddCovenantUnbondingSigs{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index 418ac8d30..d3409914e 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -27,4 +27,5 @@ var ( ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1118, "the validator list is empty") ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1119, "the proof of possession is not valid") ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1120, "the staking request contains duplicated validator public key") + ErrInvalidBTCUndelegateReq = errorsmod.Register(ModuleName, 1121, "invalid undelegation request") ) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index ece606e84..b4adb0e76 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -2,6 +2,7 @@ package types import ( "fmt" + math "math" errorsmod "cosmossdk.io/errors" @@ -17,9 +18,8 @@ var ( _ sdk.Msg = &MsgUpdateParams{} _ sdk.Msg = &MsgCreateBTCValidator{} _ sdk.Msg = &MsgCreateBTCDelegation{} - _ sdk.Msg = &MsgAddCovenantSig{} + _ sdk.Msg = &MsgAddCovenantSigs{} _ sdk.Msg = &MsgBTCUndelegate{} - _ sdk.Msg = &MsgAddCovenantUnbondingSigs{} ) // GetSigners returns the expected signers for a MsgUpdateParams message. @@ -102,13 +102,26 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.SlashingTx == nil { return fmt.Errorf("empty slashing tx") } - if m.DelegatorSig == nil { + if m.DelegatorSlashingSig == nil { return fmt.Errorf("empty delegator signature") } if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { return err } + // Check staking time is at most uint16 + if m.StakingTime > math.MaxUint16 { + return ErrInvalidStakingTx.Wrapf("invalid lock time: %d, max: %d", m.StakingTime, math.MaxUint16) + } + // Ensure list of validator BTC PKs is not empty + if len(m.ValBtcPkList) == 0 { + return ErrEmptyValidatorList + } + // Ensure list of validator BTC PKs is not duplicated + if ExistsDup(m.ValBtcPkList) { + return ErrDuplicatedValidator + } + // staking tx should be correctly formatted if err := m.StakingTx.ValidateBasic(); err != nil { return err @@ -117,67 +130,33 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { return err } - return nil -} - -func (m *MsgAddCovenantSig) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - -func (m *MsgAddCovenantSig) ValidateBasic() error { - if m.Pk == nil { - return fmt.Errorf("empty BTC covenant public key") - } - if m.Sigs == nil { - return fmt.Errorf("empty covenant signature") - } - if len(m.StakingTxHash) != chainhash.MaxHashStringSize { - return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) - } - - return nil -} - -func (m *MsgBTCUndelegate) ValidateBasic() error { + // verifications about on-demand unbonding if m.UnbondingTx == nil { return fmt.Errorf("empty unbonding tx") } - if m.SlashingTx == nil { + if m.UnbondingSlashingTx == nil { return fmt.Errorf("empty slashing tx") } - if m.DelegatorSlashingSig == nil { + if m.DelegatorUnbondingSlashingSig == nil { return fmt.Errorf("empty delegator signature") } - - if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { - return err - } unbondingTxMsg, err := bbn.NewBTCTxFromBytes(m.UnbondingTx) - if err != nil { return err } - if err := btcstaking.IsSimpleTransfer(unbondingTxMsg); err != nil { return err } - return nil -} - -func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) + // Check unbonding time is lower than max uint16 + if uint64(m.UnbondingTime) > math.MaxUint16 { + return ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be lower than %d", m.UnbondingTime, math.MaxUint16) } - return []sdk.AccAddress{signer} + + return nil } -func (m *MsgAddCovenantUnbondingSigs) GetSigners() []sdk.AccAddress { +func (m *MsgAddCovenantSigs) GetSigners() []sdk.AccAddress { signer, err := sdk.AccAddressFromBech32(m.Signer) if err != nil { panic(err) @@ -185,18 +164,44 @@ func (m *MsgAddCovenantUnbondingSigs) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } -func (m *MsgAddCovenantUnbondingSigs) ValidateBasic() error { +func (m *MsgAddCovenantSigs) ValidateBasic() error { if m.Pk == nil { return fmt.Errorf("empty BTC covenant public key") } + if m.SlashingTxSigs == nil { + return fmt.Errorf("empty covenant signatures on slashing tx") + } + if len(m.StakingTxHash) != chainhash.MaxHashStringSize { + return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) + } + + // verifications about on-demand unbonding if m.UnbondingTxSig == nil { return fmt.Errorf("empty covenant signature") } if m.SlashingUnbondingTxSigs == nil { return fmt.Errorf("empty covenant signature") } + + return nil +} + +func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { + signer, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{signer} +} + +func (m *MsgBTCUndelegate) ValidateBasic() error { if len(m.StakingTxHash) != chainhash.MaxHashStringSize { return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) } + + if m.UnbondingTxSig == nil { + return fmt.Errorf("empty signature from the delegator") + } + return nil } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index c0afcdf69..45459b2e9 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -170,11 +170,25 @@ type MsgCreateBTCDelegation struct { // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` - // delegator_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. // The staking tx output further needs signatures from covenant and validator in // order to be spendable. - DelegatorSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_sig,json=delegatorSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_sig,omitempty"` + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + /// fields related to on-demand unbonding + // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends + // staking output and sends it to the unbonding output + UnbondingTx []byte `protobuf:"bytes,11,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` + // unbonding_time is the time lock used in unbonding transaction + UnbondingTime uint32 `protobuf:"varint,12,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` + // unbonding_value is amount of satoshis locked in unbonding output. + // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding + UnbondingValue int64 `protobuf:"varint,13,opt,name=unbonding_value,json=unbondingValue,proto3" json:"unbonding_value,omitempty"` + // unbonding_slashing_tx is the slashing tx which slash unbonding contract + // Note that the tx itself does not contain signatures, which are off-chain. + UnbondingSlashingTx *BTCSlashingTx `protobuf:"bytes,14,opt,name=unbonding_slashing_tx,json=unbondingSlashingTx,proto3,customtype=BTCSlashingTx" json:"unbonding_slashing_tx,omitempty"` + // delegator_unbonding_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). + DelegatorUnbondingSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,15,opt,name=delegator_unbonding_slashing_sig,json=delegatorUnbondingSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_unbonding_slashing_sig,omitempty"` } func (m *MsgCreateBTCDelegation) Reset() { *m = MsgCreateBTCDelegation{} } @@ -252,6 +266,27 @@ func (m *MsgCreateBTCDelegation) GetStakingTx() *types1.TransactionInfo { return nil } +func (m *MsgCreateBTCDelegation) GetUnbondingTx() []byte { + if m != nil { + return m.UnbondingTx + } + return nil +} + +func (m *MsgCreateBTCDelegation) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + +func (m *MsgCreateBTCDelegation) GetUnbondingValue() int64 { + if m != nil { + return m.UnbondingValue + } + return 0 +} + // MsgCreateBTCDelegationResponse is the response for MsgCreateBTCDelegation type MsgCreateBTCDelegationResponse struct { } @@ -289,36 +324,40 @@ func (m *MsgCreateBTCDelegationResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCreateBTCDelegationResponse proto.InternalMessageInfo -// MsgBTCUndelegate is the message undelegating existing and active delegation -type MsgBTCUndelegate struct { +// MsgAddCovenantSigs is the message for handling signatures from a covenant member +type MsgAddCovenantSigs struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends - // staking output and sends it to the unbonding output - UnbondingTx []byte `protobuf:"bytes,2,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // unbonding_time is the time lock used in unbonding transaction - UnbondingTime uint32 `protobuf:"varint,3,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` - // unbonding_value is amount of satoshis locked in unbonding output. - // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding - UnbondingValue int64 `protobuf:"varint,4,opt,name=unbonding_value,json=unbondingValue,proto3" json:"unbonding_value,omitempty"` - // slashing_tx is the slashing tx which slash unbonding contract - // Note that the tx itself does not contain signatures, which are off-chain. - SlashingTx *BTCSlashingTx `protobuf:"bytes,5,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` - // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). - DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,6,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + // pk is the BTC public key of the covenant member + Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // sigs is a list of adaptor signatures of the covenant + // the order of sigs should respect the order of validators + // of the corresponding delegation + SlashingTxSigs [][]byte `protobuf:"bytes,4,rep,name=slashing_tx_sigs,json=slashingTxSigs,proto3" json:"slashing_tx_sigs,omitempty"` + // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon + // the signature follows encoding in BIP-340 spec + UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` + // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant + // on slashing tx corresponding to unbonding tx submitted to babylon + // the order of sigs should respect the order of validators + // of the corresponding delegation + SlashingUnbondingTxSigs [][]byte `protobuf:"bytes,6,rep,name=slashing_unbonding_tx_sigs,json=slashingUnbondingTxSigs,proto3" json:"slashing_unbonding_tx_sigs,omitempty"` } -func (m *MsgBTCUndelegate) Reset() { *m = MsgBTCUndelegate{} } -func (m *MsgBTCUndelegate) String() string { return proto.CompactTextString(m) } -func (*MsgBTCUndelegate) ProtoMessage() {} -func (*MsgBTCUndelegate) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantSigs) Reset() { *m = MsgAddCovenantSigs{} } +func (m *MsgAddCovenantSigs) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantSigs) ProtoMessage() {} +func (*MsgAddCovenantSigs) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{4} } -func (m *MsgBTCUndelegate) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantSigs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgBTCUndelegate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantSigs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgBTCUndelegate.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantSigs.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -328,62 +367,62 @@ func (m *MsgBTCUndelegate) XXX_Marshal(b []byte, deterministic bool) ([]byte, er return b[:n], nil } } -func (m *MsgBTCUndelegate) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgBTCUndelegate.Merge(m, src) +func (m *MsgAddCovenantSigs) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantSigs.Merge(m, src) } -func (m *MsgBTCUndelegate) XXX_Size() int { +func (m *MsgAddCovenantSigs) XXX_Size() int { return m.Size() } -func (m *MsgBTCUndelegate) XXX_DiscardUnknown() { - xxx_messageInfo_MsgBTCUndelegate.DiscardUnknown(m) +func (m *MsgAddCovenantSigs) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantSigs.DiscardUnknown(m) } -var xxx_messageInfo_MsgBTCUndelegate proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantSigs proto.InternalMessageInfo -func (m *MsgBTCUndelegate) GetSigner() string { +func (m *MsgAddCovenantSigs) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgBTCUndelegate) GetUnbondingTx() []byte { +func (m *MsgAddCovenantSigs) GetStakingTxHash() string { if m != nil { - return m.UnbondingTx + return m.StakingTxHash } - return nil + return "" } -func (m *MsgBTCUndelegate) GetUnbondingTime() uint32 { +func (m *MsgAddCovenantSigs) GetSlashingTxSigs() [][]byte { if m != nil { - return m.UnbondingTime + return m.SlashingTxSigs } - return 0 + return nil } -func (m *MsgBTCUndelegate) GetUnbondingValue() int64 { +func (m *MsgAddCovenantSigs) GetSlashingUnbondingTxSigs() [][]byte { if m != nil { - return m.UnbondingValue + return m.SlashingUnbondingTxSigs } - return 0 + return nil } -// MsgBtcUndelegateResponse is the response for MsgBtcUndelegate -type MsgBTCUndelegateResponse struct { +// MsgAddCovenantSigsResponse is the response for MsgAddCovenantSigs +type MsgAddCovenantSigsResponse struct { } -func (m *MsgBTCUndelegateResponse) Reset() { *m = MsgBTCUndelegateResponse{} } -func (m *MsgBTCUndelegateResponse) String() string { return proto.CompactTextString(m) } -func (*MsgBTCUndelegateResponse) ProtoMessage() {} -func (*MsgBTCUndelegateResponse) Descriptor() ([]byte, []int) { +func (m *MsgAddCovenantSigsResponse) Reset() { *m = MsgAddCovenantSigsResponse{} } +func (m *MsgAddCovenantSigsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddCovenantSigsResponse) ProtoMessage() {} +func (*MsgAddCovenantSigsResponse) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{5} } -func (m *MsgBTCUndelegateResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgAddCovenantSigsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgBTCUndelegateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgAddCovenantSigsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgBTCUndelegateResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgAddCovenantSigsResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -393,44 +432,43 @@ func (m *MsgBTCUndelegateResponse) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *MsgBTCUndelegateResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgBTCUndelegateResponse.Merge(m, src) +func (m *MsgAddCovenantSigsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddCovenantSigsResponse.Merge(m, src) } -func (m *MsgBTCUndelegateResponse) XXX_Size() int { +func (m *MsgAddCovenantSigsResponse) XXX_Size() int { return m.Size() } -func (m *MsgBTCUndelegateResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgBTCUndelegateResponse.DiscardUnknown(m) +func (m *MsgAddCovenantSigsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddCovenantSigsResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgAddCovenantSigsResponse proto.InternalMessageInfo -// MsgAddCovenantSig is the message for handling a signature from covenant -type MsgAddCovenantSig struct { +// MsgBTCUndelegate is the message for handling signature on unbonding tx +// from its delegator. This signature effectively proves that the delegator +// wants to unbond this BTC delegation +type MsgBTCUndelegate struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // pk is the BTC public key of the covenant member - Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` // staking_tx_hash is the hash of the staking tx. // It uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // sigs is a list of adaptor signatures of the covenant - // the order of sigs should respect the order of validators - // of the corresponding delegation - Sigs [][]byte `protobuf:"bytes,4,rep,name=sigs,proto3" json:"sigs,omitempty"` + StakingTxHash string `protobuf:"bytes,2,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // unbonding_tx_sig is the signature of the staker on the unbonding tx submitted to babylon + // the signature follows encoding in BIP-340 spec + UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` } -func (m *MsgAddCovenantSig) Reset() { *m = MsgAddCovenantSig{} } -func (m *MsgAddCovenantSig) String() string { return proto.CompactTextString(m) } -func (*MsgAddCovenantSig) ProtoMessage() {} -func (*MsgAddCovenantSig) Descriptor() ([]byte, []int) { +func (m *MsgBTCUndelegate) Reset() { *m = MsgBTCUndelegate{} } +func (m *MsgBTCUndelegate) String() string { return proto.CompactTextString(m) } +func (*MsgBTCUndelegate) ProtoMessage() {} +func (*MsgBTCUndelegate) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{6} } -func (m *MsgAddCovenantSig) XXX_Unmarshal(b []byte) error { +func (m *MsgBTCUndelegate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddCovenantSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgBTCUndelegate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddCovenantSig.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgBTCUndelegate.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -440,55 +478,48 @@ func (m *MsgAddCovenantSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, e return b[:n], nil } } -func (m *MsgAddCovenantSig) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddCovenantSig.Merge(m, src) +func (m *MsgBTCUndelegate) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBTCUndelegate.Merge(m, src) } -func (m *MsgAddCovenantSig) XXX_Size() int { +func (m *MsgBTCUndelegate) XXX_Size() int { return m.Size() } -func (m *MsgAddCovenantSig) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddCovenantSig.DiscardUnknown(m) +func (m *MsgBTCUndelegate) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBTCUndelegate.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddCovenantSig proto.InternalMessageInfo +var xxx_messageInfo_MsgBTCUndelegate proto.InternalMessageInfo -func (m *MsgAddCovenantSig) GetSigner() string { +func (m *MsgBTCUndelegate) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgAddCovenantSig) GetStakingTxHash() string { +func (m *MsgBTCUndelegate) GetStakingTxHash() string { if m != nil { return m.StakingTxHash } return "" } -func (m *MsgAddCovenantSig) GetSigs() [][]byte { - if m != nil { - return m.Sigs - } - return nil -} - -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -type MsgAddCovenantSigResponse struct { +// MsgBTCUndelegateResponse is the response for MsgBTCUndelegate +type MsgBTCUndelegateResponse struct { } -func (m *MsgAddCovenantSigResponse) Reset() { *m = MsgAddCovenantSigResponse{} } -func (m *MsgAddCovenantSigResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddCovenantSigResponse) ProtoMessage() {} -func (*MsgAddCovenantSigResponse) Descriptor() ([]byte, []int) { +func (m *MsgBTCUndelegateResponse) Reset() { *m = MsgBTCUndelegateResponse{} } +func (m *MsgBTCUndelegateResponse) String() string { return proto.CompactTextString(m) } +func (*MsgBTCUndelegateResponse) ProtoMessage() {} +func (*MsgBTCUndelegateResponse) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{7} } -func (m *MsgAddCovenantSigResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgBTCUndelegateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgAddCovenantSigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgBTCUndelegateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgAddCovenantSigResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgBTCUndelegateResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -498,17 +529,17 @@ func (m *MsgAddCovenantSigResponse) XXX_Marshal(b []byte, deterministic bool) ([ return b[:n], nil } } -func (m *MsgAddCovenantSigResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddCovenantSigResponse.Merge(m, src) +func (m *MsgBTCUndelegateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBTCUndelegateResponse.Merge(m, src) } -func (m *MsgAddCovenantSigResponse) XXX_Size() int { +func (m *MsgBTCUndelegateResponse) XXX_Size() int { return m.Size() } -func (m *MsgAddCovenantSigResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddCovenantSigResponse.DiscardUnknown(m) +func (m *MsgBTCUndelegateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBTCUndelegateResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgAddCovenantSigResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo // MsgUpdateParams defines a message for updating btcstaking module parameters. type MsgUpdateParams struct { @@ -607,206 +638,95 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo -// MsgAddCovenantUnbondingSigs is the message for handling a signature from covenant -type MsgAddCovenantUnbondingSigs struct { - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // pk is the BTC public key of the covenant member - Pk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=pk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"pk,omitempty"` - // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon - // the signature follows encoding in BIP-340 spec - UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` - // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant - // on slashing tx corresponding to unbonding tx submitted to babylon - // the order of sigs should respect the order of validators - // of the corresponding delegation - SlashingUnbondingTxSigs [][]byte `protobuf:"bytes,5,rep,name=slashing_unbonding_tx_sigs,json=slashingUnbondingTxSigs,proto3" json:"slashing_unbonding_tx_sigs,omitempty"` -} - -func (m *MsgAddCovenantUnbondingSigs) Reset() { *m = MsgAddCovenantUnbondingSigs{} } -func (m *MsgAddCovenantUnbondingSigs) String() string { return proto.CompactTextString(m) } -func (*MsgAddCovenantUnbondingSigs) ProtoMessage() {} -func (*MsgAddCovenantUnbondingSigs) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{10} -} -func (m *MsgAddCovenantUnbondingSigs) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddCovenantUnbondingSigs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddCovenantUnbondingSigs.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddCovenantUnbondingSigs) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddCovenantUnbondingSigs.Merge(m, src) -} -func (m *MsgAddCovenantUnbondingSigs) XXX_Size() int { - return m.Size() -} -func (m *MsgAddCovenantUnbondingSigs) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddCovenantUnbondingSigs.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddCovenantUnbondingSigs proto.InternalMessageInfo - -func (m *MsgAddCovenantUnbondingSigs) GetSigner() string { - if m != nil { - return m.Signer - } - return "" -} - -func (m *MsgAddCovenantUnbondingSigs) GetStakingTxHash() string { - if m != nil { - return m.StakingTxHash - } - return "" -} - -func (m *MsgAddCovenantUnbondingSigs) GetSlashingUnbondingTxSigs() [][]byte { - if m != nil { - return m.SlashingUnbondingTxSigs - } - return nil -} - -// MsgAddCovenantSigResponse is the response for MsgAddCovenantSig -type MsgAddCovenantUnbondingSigsResponse struct { -} - -func (m *MsgAddCovenantUnbondingSigsResponse) Reset() { *m = MsgAddCovenantUnbondingSigsResponse{} } -func (m *MsgAddCovenantUnbondingSigsResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddCovenantUnbondingSigsResponse) ProtoMessage() {} -func (*MsgAddCovenantUnbondingSigsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{11} -} -func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.Merge(m, src) -} -func (m *MsgAddCovenantUnbondingSigsResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgAddCovenantUnbondingSigsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddCovenantUnbondingSigsResponse proto.InternalMessageInfo - func init() { proto.RegisterType((*MsgCreateBTCValidator)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidator") proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") proto.RegisterType((*MsgCreateBTCDelegation)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegation") proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") + proto.RegisterType((*MsgAddCovenantSigs)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigs") + proto.RegisterType((*MsgAddCovenantSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigsResponse") proto.RegisterType((*MsgBTCUndelegate)(nil), "babylon.btcstaking.v1.MsgBTCUndelegate") proto.RegisterType((*MsgBTCUndelegateResponse)(nil), "babylon.btcstaking.v1.MsgBTCUndelegateResponse") - proto.RegisterType((*MsgAddCovenantSig)(nil), "babylon.btcstaking.v1.MsgAddCovenantSig") - proto.RegisterType((*MsgAddCovenantSigResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") - proto.RegisterType((*MsgAddCovenantUnbondingSigs)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigs") - proto.RegisterType((*MsgAddCovenantUnbondingSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantUnbondingSigsResponse") } func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1149 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0x4d, 0x6f, 0x1a, 0x57, - 0x17, 0xf6, 0xf0, 0xf5, 0xbe, 0x3e, 0x80, 0x9d, 0x4c, 0x93, 0x78, 0x8c, 0x15, 0x20, 0xb8, 0x49, - 0x68, 0x14, 0x0f, 0x31, 0x89, 0x2d, 0xd5, 0x91, 0x2a, 0x05, 0xbb, 0x55, 0xa2, 0x1a, 0x15, 0x0d, - 0x38, 0x8b, 0x4a, 0x2d, 0xba, 0x0c, 0xd7, 0xc3, 0x08, 0x98, 0x3b, 0x9a, 0x7b, 0x41, 0xa0, 0x6e, - 0xaa, 0x2e, 0x2b, 0x55, 0xea, 0xaa, 0xbb, 0xfe, 0x87, 0x2c, 0xb2, 0xec, 0x0f, 0xc8, 0xae, 0x69, - 0xd4, 0x45, 0xe5, 0x85, 0x55, 0xd9, 0x8b, 0xfc, 0x8d, 0x6a, 0x66, 0xee, 0x7c, 0x40, 0xa0, 0xc5, - 0x71, 0x17, 0xdd, 0xcd, 0x9d, 0xfb, 0x9c, 0xe7, 0x9c, 0xf3, 0x9c, 0x8f, 0x01, 0xc8, 0xb6, 0x50, - 0x6b, 0xdc, 0x23, 0x46, 0xa9, 0xc5, 0x54, 0xca, 0x50, 0x57, 0x37, 0xb4, 0xd2, 0x70, 0xbb, 0xc4, - 0x46, 0xb2, 0x69, 0x11, 0x46, 0xc4, 0xeb, 0xfc, 0x5e, 0x0e, 0xee, 0xe5, 0xe1, 0x76, 0xe6, 0x9a, - 0x46, 0x34, 0xe2, 0x20, 0x4a, 0xf6, 0x93, 0x0b, 0xce, 0xac, 0xab, 0x84, 0xf6, 0x09, 0x6d, 0xba, - 0x17, 0xee, 0x81, 0x5f, 0xad, 0xb9, 0xa7, 0x52, 0x9f, 0x3a, 0xfc, 0x7d, 0xaa, 0xf1, 0x8b, 0x02, - 0xbf, 0x50, 0xad, 0xb1, 0xc9, 0x48, 0x89, 0x62, 0xd5, 0x2c, 0xef, 0xec, 0x76, 0xb7, 0x4b, 0x5d, - 0x3c, 0xf6, 0x8c, 0x0b, 0xb3, 0x83, 0x34, 0x91, 0x85, 0xfa, 0x1e, 0xe6, 0x7e, 0x08, 0xa3, 0x76, - 0xb0, 0xda, 0x35, 0x89, 0x6e, 0x30, 0x1b, 0x36, 0xf1, 0x82, 0xa3, 0x3f, 0xe4, 0x5e, 0x03, 0xb6, - 0x16, 0x66, 0x68, 0xdb, 0x3b, 0x73, 0x54, 0x6e, 0x8e, 0x5f, 0x62, 0xba, 0x80, 0xc2, 0xcf, 0x51, - 0xb8, 0x5e, 0xa5, 0xda, 0xbe, 0x85, 0x11, 0xc3, 0x95, 0xc6, 0xfe, 0x73, 0xd4, 0xd3, 0xdb, 0x88, - 0x11, 0x4b, 0xbc, 0x01, 0x09, 0xaa, 0x6b, 0x06, 0xb6, 0x24, 0x21, 0x2f, 0x14, 0x97, 0x15, 0x7e, - 0x12, 0x3f, 0x85, 0x64, 0x1b, 0x53, 0xd5, 0xd2, 0x4d, 0xa6, 0x13, 0x43, 0x8a, 0xe4, 0x85, 0x62, - 0xb2, 0xbc, 0x29, 0x73, 0xad, 0x02, 0x85, 0x9d, 0x70, 0xe4, 0x83, 0x00, 0xaa, 0x84, 0xed, 0xc4, - 0x2a, 0x80, 0x4a, 0xfa, 0x7d, 0x9d, 0x52, 0x9b, 0x25, 0x6a, 0xbb, 0xa8, 0x6c, 0x9d, 0x9c, 0xe6, - 0x36, 0x5c, 0x22, 0xda, 0xee, 0xca, 0x3a, 0x29, 0xf5, 0x11, 0xeb, 0xc8, 0x87, 0x58, 0x43, 0xea, - 0xf8, 0x00, 0xab, 0x6f, 0x5e, 0x6e, 0x01, 0xf7, 0x73, 0x80, 0x55, 0x25, 0x44, 0x20, 0x7e, 0x02, - 0xc0, 0x53, 0x6d, 0x9a, 0x5d, 0x29, 0xe6, 0x04, 0x95, 0xf3, 0x82, 0x72, 0x2b, 0x23, 0xfb, 0x95, - 0x91, 0x6b, 0x83, 0xd6, 0xe7, 0x78, 0xac, 0x2c, 0x73, 0x93, 0x5a, 0x57, 0xac, 0x42, 0xa2, 0xc5, - 0x54, 0xdb, 0x36, 0x9e, 0x17, 0x8a, 0xa9, 0xca, 0xee, 0xc9, 0x69, 0xae, 0xac, 0xe9, 0xac, 0x33, - 0x68, 0xc9, 0x2a, 0xe9, 0x97, 0x38, 0x52, 0xed, 0x20, 0xdd, 0xf0, 0x0e, 0x25, 0x36, 0x36, 0x31, - 0x95, 0x2b, 0xcf, 0x6a, 0x0f, 0x1f, 0x3d, 0xe0, 0x94, 0xf1, 0x16, 0x53, 0x6b, 0x5d, 0x71, 0x0f, - 0xa2, 0x26, 0x31, 0xa5, 0x84, 0x13, 0x47, 0x51, 0x9e, 0xd9, 0x82, 0x72, 0xcd, 0x22, 0xe4, 0xf8, - 0x8b, 0xe3, 0x1a, 0xa1, 0x14, 0x3b, 0x59, 0x28, 0xb6, 0xd1, 0x5e, 0xf2, 0xbb, 0xb7, 0x2f, 0xee, - 0x71, 0xb5, 0x0b, 0x39, 0xb8, 0x39, 0xb3, 0x3c, 0x0a, 0xa6, 0x26, 0x31, 0x28, 0x2e, 0xfc, 0x10, - 0x87, 0x1b, 0x61, 0xc4, 0x01, 0xee, 0x61, 0x0d, 0x39, 0x12, 0xcf, 0xab, 0xe0, 0xa4, 0x56, 0x91, - 0x0b, 0x6b, 0xc5, 0x93, 0x8b, 0xbe, 0x47, 0x72, 0x21, 0x9d, 0x63, 0xff, 0x86, 0xce, 0x5f, 0xc1, - 0xea, 0x10, 0xf5, 0x9a, 0x2e, 0x65, 0xb3, 0xa7, 0x53, 0x26, 0xc5, 0xf3, 0xd1, 0x4b, 0xf0, 0xa6, - 0x86, 0xa8, 0x57, 0xb1, 0xa9, 0x0f, 0x75, 0xca, 0xc4, 0x5b, 0x90, 0xe2, 0x29, 0x35, 0x99, 0xde, - 0xc7, 0x4e, 0x3d, 0xd3, 0x4a, 0x92, 0xbf, 0x6b, 0xe8, 0x7d, 0x2c, 0x6e, 0x42, 0xda, 0x83, 0x0c, - 0x51, 0x6f, 0x80, 0xa5, 0xff, 0xe5, 0x85, 0x62, 0x54, 0xf1, 0xec, 0x9e, 0xdb, 0xef, 0xc4, 0xa7, - 0x00, 0x3e, 0xcf, 0x48, 0xfa, 0xbf, 0x23, 0xdc, 0x47, 0x61, 0xe1, 0x42, 0xe3, 0x3d, 0xdc, 0x96, - 0x1b, 0x16, 0x32, 0x28, 0x52, 0xed, 0x22, 0x3e, 0x33, 0x8e, 0x89, 0xb2, 0xec, 0x39, 0x1c, 0x89, - 0x65, 0x48, 0xd2, 0x1e, 0xa2, 0x1d, 0x4e, 0xb5, 0xec, 0x88, 0x78, 0xf5, 0xe4, 0x34, 0x97, 0xae, - 0x34, 0xf6, 0xeb, 0xfc, 0xa6, 0x31, 0x52, 0x80, 0xfa, 0xcf, 0xe2, 0xd7, 0x90, 0x6e, 0xbb, 0x5d, - 0x41, 0xac, 0x26, 0xd5, 0x35, 0x09, 0x1c, 0xab, 0x8f, 0x4f, 0x4e, 0x73, 0x3b, 0x17, 0x91, 0xa8, - 0xae, 0x6b, 0x06, 0x62, 0x03, 0x0b, 0x2b, 0x29, 0x9f, 0xaf, 0xae, 0x6b, 0x93, 0x0d, 0x9b, 0x87, - 0xec, 0xec, 0x76, 0xf4, 0x3b, 0xf6, 0xd7, 0x08, 0x5c, 0xa9, 0x52, 0xad, 0xd2, 0xd8, 0x3f, 0x32, - 0x38, 0x0f, 0x9e, 0xdb, 0xab, 0xb7, 0x20, 0x35, 0x30, 0x5a, 0xc4, 0x68, 0xf3, 0x84, 0xed, 0x6e, - 0x4d, 0x29, 0x49, 0xff, 0x5d, 0x63, 0x24, 0xde, 0x86, 0x95, 0x10, 0xc4, 0x2e, 0x53, 0xd4, 0x29, - 0x53, 0x3a, 0x00, 0xd9, 0x85, 0xba, 0x0b, 0xab, 0x01, 0xcc, 0x2d, 0x55, 0xcc, 0x29, 0x55, 0x60, - 0xed, 0x16, 0x6b, 0x4a, 0xe2, 0xf8, 0x22, 0x12, 0x13, 0xb8, 0x11, 0x92, 0xd8, 0xb3, 0xb6, 0xb5, - 0x4e, 0x5c, 0x56, 0xeb, 0x6b, 0x81, 0xd6, 0x9c, 0xf7, 0x1d, 0xcd, 0x33, 0x20, 0x4d, 0x0b, 0xea, - 0xab, 0xfd, 0x8b, 0x00, 0x57, 0xab, 0x54, 0x7b, 0xd2, 0x6e, 0xef, 0x93, 0x21, 0x36, 0x90, 0xc1, - 0xea, 0xba, 0x36, 0x57, 0xee, 0xcf, 0x20, 0xc2, 0x57, 0xc2, 0xfb, 0x8f, 0x50, 0xc4, 0xec, 0x8a, - 0x77, 0x60, 0x35, 0x68, 0xf8, 0x66, 0x07, 0xd1, 0x8e, 0xbb, 0xe2, 0x95, 0xb4, 0xdf, 0xca, 0x4f, - 0x11, 0xed, 0x88, 0x22, 0xc4, 0xa8, 0xae, 0x51, 0x29, 0x66, 0x0f, 0xad, 0xe2, 0x3c, 0x4f, 0xa6, - 0xb6, 0x01, 0xeb, 0xef, 0x44, 0xef, 0xe7, 0xf6, 0x93, 0x00, 0xab, 0x55, 0xaa, 0x1d, 0x99, 0x6d, - 0xc4, 0x70, 0xcd, 0xf9, 0x96, 0x8a, 0xbb, 0xb0, 0x8c, 0x06, 0xac, 0x43, 0x2c, 0x9d, 0x8d, 0xdd, - 0xe4, 0x2a, 0xd2, 0x9b, 0x97, 0x5b, 0xd7, 0xf8, 0x7a, 0x7b, 0xd2, 0x6e, 0x5b, 0x98, 0xd2, 0x3a, - 0xb3, 0x74, 0x43, 0x53, 0x02, 0xa8, 0xf8, 0x18, 0x12, 0xee, 0xd7, 0x98, 0x2f, 0xc4, 0x9b, 0xf3, - 0xf6, 0x9a, 0x03, 0xaa, 0xc4, 0x5e, 0x9d, 0xe6, 0x96, 0x14, 0x6e, 0xb2, 0xb7, 0x62, 0x87, 0x1c, - 0x90, 0x15, 0xd6, 0x61, 0x6d, 0x2a, 0x2e, 0x3f, 0xe6, 0xdf, 0x23, 0xb0, 0x31, 0x99, 0xd1, 0x91, - 0xd7, 0x7e, 0x75, 0x5d, 0xa3, 0xff, 0x99, 0xca, 0xa8, 0x70, 0x25, 0x3c, 0x78, 0x4e, 0x2f, 0xc7, - 0x2e, 0xdb, 0xcb, 0x2b, 0xa1, 0xb9, 0xb5, 0xdb, 0xf0, 0x31, 0x64, 0xfc, 0x61, 0x99, 0xf6, 0x46, - 0xdd, 0x4d, 0xae, 0xac, 0x79, 0x88, 0xa3, 0x09, 0xdb, 0xa9, 0x3e, 0xb9, 0x0d, 0x9b, 0x7f, 0xa3, - 0xaa, 0xa7, 0x7e, 0xf9, 0xb7, 0x38, 0x44, 0xab, 0x54, 0x13, 0x47, 0x20, 0xce, 0xf8, 0xc9, 0x73, - 0x7f, 0x4e, 0xcd, 0x67, 0x7e, 0x81, 0x33, 0x8f, 0x2e, 0x82, 0xf6, 0x22, 0x10, 0xbf, 0x81, 0x0f, - 0x66, 0x7d, 0xab, 0xb7, 0x16, 0x20, 0x0b, 0xe0, 0x99, 0x9d, 0x0b, 0xc1, 0x7d, 0xe7, 0x3a, 0xa4, - 0x27, 0xd7, 0xee, 0xdd, 0xf9, 0x3c, 0x13, 0xc0, 0x4c, 0x69, 0x41, 0xa0, 0xef, 0xaa, 0x07, 0x2b, - 0x53, 0x3b, 0xa7, 0x38, 0x9f, 0x62, 0x12, 0x99, 0x79, 0xb0, 0x28, 0xd2, 0xf7, 0xf6, 0xbd, 0x00, - 0xd2, 0xdc, 0x91, 0x2a, 0x2f, 0x44, 0x37, 0x61, 0x93, 0xd9, 0xbb, 0xb8, 0x8d, 0x1f, 0xcc, 0x31, - 0xa4, 0x26, 0x56, 0xd2, 0x9d, 0xf9, 0x5c, 0x61, 0x5c, 0x46, 0x5e, 0x0c, 0xe7, 0xf9, 0xc9, 0xc4, - 0xbf, 0x7d, 0xfb, 0xe2, 0x9e, 0x50, 0x39, 0x7c, 0x75, 0x96, 0x15, 0x5e, 0x9f, 0x65, 0x85, 0x3f, - 0xcf, 0xb2, 0xc2, 0x8f, 0xe7, 0xd9, 0xa5, 0xd7, 0xe7, 0xd9, 0xa5, 0x3f, 0xce, 0xb3, 0x4b, 0x5f, - 0xfe, 0xe3, 0x8e, 0x18, 0x85, 0xff, 0x17, 0x38, 0x23, 0xdb, 0x4a, 0x38, 0xff, 0x0b, 0x1e, 0xfe, - 0x15, 0x00, 0x00, 0xff, 0xff, 0x22, 0x62, 0x6f, 0x70, 0x57, 0x0d, 0x00, 0x00, + // 1140 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdf, 0x6e, 0x1a, 0xc7, + 0x17, 0xf6, 0x82, 0x4d, 0x7e, 0x3e, 0x18, 0x93, 0xdf, 0x26, 0xb6, 0x37, 0xb4, 0x06, 0x4a, 0xda, + 0x84, 0x44, 0xf1, 0x52, 0x9c, 0xd8, 0x52, 0x1d, 0xa9, 0x52, 0xb0, 0x53, 0x25, 0xaa, 0x51, 0xd1, + 0x82, 0x73, 0x51, 0xa9, 0x42, 0xc3, 0x32, 0x5e, 0x46, 0xc0, 0xce, 0x6a, 0x67, 0x40, 0xa0, 0xde, + 0x54, 0x79, 0x82, 0x5e, 0xf5, 0xae, 0xef, 0x90, 0x8b, 0x3c, 0x42, 0xa5, 0xe6, 0x32, 0xca, 0x55, + 0x65, 0xa9, 0x56, 0x65, 0x5f, 0xe4, 0x09, 0x7a, 0x5f, 0xed, 0xee, 0xec, 0x1f, 0x28, 0xa8, 0x76, + 0x9c, 0xbb, 0xdd, 0x99, 0xef, 0x7c, 0xe7, 0x9c, 0xef, 0x9c, 0x33, 0x33, 0x90, 0x6d, 0xa1, 0xd6, + 0xb8, 0x47, 0xcd, 0x52, 0x8b, 0xeb, 0x8c, 0xa3, 0x2e, 0x31, 0x8d, 0xd2, 0xb0, 0x5c, 0xe2, 0x23, + 0xd5, 0xb2, 0x29, 0xa7, 0xf2, 0x9a, 0xd8, 0x57, 0xc3, 0x7d, 0x75, 0x58, 0xce, 0xdc, 0x34, 0xa8, + 0x41, 0x5d, 0x44, 0xc9, 0xf9, 0xf2, 0xc0, 0x99, 0x5b, 0x3a, 0x65, 0x7d, 0xca, 0x9a, 0xde, 0x86, + 0xf7, 0x23, 0xb6, 0x36, 0xbc, 0xbf, 0x52, 0x9f, 0xb9, 0xfc, 0x7d, 0x66, 0x88, 0x8d, 0x82, 0xd8, + 0xd0, 0xed, 0xb1, 0xc5, 0x69, 0x89, 0x61, 0xdd, 0xda, 0xde, 0xd9, 0xed, 0x96, 0x4b, 0x5d, 0x3c, + 0xf6, 0x8d, 0x0b, 0xb3, 0x83, 0xb4, 0x90, 0x8d, 0xfa, 0x3e, 0xe6, 0x41, 0x04, 0xa3, 0x77, 0xb0, + 0xde, 0xb5, 0x28, 0x31, 0xb9, 0x03, 0x9b, 0x58, 0x10, 0xe8, 0xcf, 0x85, 0xd7, 0x90, 0xad, 0x85, + 0x39, 0x2a, 0xfb, 0xff, 0x02, 0x95, 0x9b, 0xe3, 0x97, 0x5a, 0x1e, 0xa0, 0xf0, 0x6b, 0x1c, 0xd6, + 0xaa, 0xcc, 0xd8, 0xb7, 0x31, 0xe2, 0xb8, 0xd2, 0xd8, 0x7f, 0x81, 0x7a, 0xa4, 0x8d, 0x38, 0xb5, + 0xe5, 0x75, 0x48, 0x30, 0x62, 0x98, 0xd8, 0x56, 0xa4, 0xbc, 0x54, 0x5c, 0xd6, 0xc4, 0x9f, 0xfc, + 0x14, 0x92, 0x6d, 0xcc, 0x74, 0x9b, 0x58, 0x9c, 0x50, 0x53, 0x89, 0xe5, 0xa5, 0x62, 0x72, 0xfb, + 0xb6, 0x2a, 0xb4, 0x0a, 0x15, 0x76, 0xc3, 0x51, 0x0f, 0x42, 0xa8, 0x16, 0xb5, 0x93, 0xab, 0x00, + 0x3a, 0xed, 0xf7, 0x09, 0x63, 0x0e, 0x4b, 0xdc, 0x71, 0x51, 0xd9, 0x3a, 0x39, 0xcd, 0x7d, 0xe2, + 0x11, 0xb1, 0x76, 0x57, 0x25, 0xb4, 0xd4, 0x47, 0xbc, 0xa3, 0x1e, 0x62, 0x03, 0xe9, 0xe3, 0x03, + 0xac, 0xbf, 0x7b, 0xbd, 0x05, 0xc2, 0xcf, 0x01, 0xd6, 0xb5, 0x08, 0x81, 0xfc, 0x35, 0x80, 0x48, + 0xb5, 0x69, 0x75, 0x95, 0x45, 0x37, 0xa8, 0x9c, 0x1f, 0x94, 0x57, 0x19, 0x35, 0xa8, 0x8c, 0x5a, + 0x1b, 0xb4, 0xbe, 0xc5, 0x63, 0x6d, 0x59, 0x98, 0xd4, 0xba, 0x72, 0x15, 0x12, 0x2d, 0xae, 0x3b, + 0xb6, 0x4b, 0x79, 0xa9, 0xb8, 0x52, 0xd9, 0x3d, 0x39, 0xcd, 0x6d, 0x1b, 0x84, 0x77, 0x06, 0x2d, + 0x55, 0xa7, 0xfd, 0x92, 0x40, 0xea, 0x1d, 0x44, 0x4c, 0xff, 0xa7, 0xc4, 0xc7, 0x16, 0x66, 0x6a, + 0xe5, 0x79, 0xed, 0xe1, 0xa3, 0x2f, 0x05, 0xe5, 0x52, 0x8b, 0xeb, 0xb5, 0xae, 0xbc, 0x07, 0x71, + 0x8b, 0x5a, 0x4a, 0xc2, 0x8d, 0xa3, 0xa8, 0xce, 0x6c, 0x41, 0xb5, 0x66, 0x53, 0x7a, 0xfc, 0xdd, + 0x71, 0x8d, 0x32, 0x86, 0xdd, 0x2c, 0x34, 0xc7, 0x68, 0x2f, 0xf9, 0xf2, 0xfd, 0xab, 0xfb, 0x42, + 0xed, 0x42, 0x0e, 0x36, 0x67, 0x96, 0x47, 0xc3, 0xcc, 0xa2, 0x26, 0xc3, 0x85, 0x3f, 0xaf, 0xc1, + 0x7a, 0x14, 0x71, 0x80, 0x7b, 0xd8, 0x40, 0xae, 0xc4, 0xf3, 0x2a, 0x38, 0xa9, 0x55, 0xec, 0xd2, + 0x5a, 0x89, 0xe4, 0xe2, 0x1f, 0x90, 0x5c, 0x44, 0xe7, 0xc5, 0x8f, 0xa1, 0xf3, 0x0f, 0x90, 0x1e, + 0xa2, 0x5e, 0xd3, 0xa3, 0x6c, 0xf6, 0x08, 0xe3, 0xca, 0x52, 0x3e, 0x7e, 0x05, 0xde, 0x95, 0x21, + 0xea, 0x55, 0x1c, 0xea, 0x43, 0xc2, 0xb8, 0xfc, 0x19, 0xac, 0x88, 0x94, 0x9a, 0x9c, 0xf4, 0xb1, + 0x5b, 0xcf, 0x94, 0x96, 0x14, 0x6b, 0x0d, 0xd2, 0xc7, 0xf2, 0x6d, 0x48, 0xf9, 0x90, 0x21, 0xea, + 0x0d, 0xb0, 0x72, 0x2d, 0x2f, 0x15, 0xe3, 0x9a, 0x6f, 0xf7, 0xc2, 0x59, 0x93, 0x9f, 0x01, 0x04, + 0x3c, 0x23, 0xe5, 0x7f, 0xae, 0x70, 0xf7, 0xa2, 0xc2, 0x45, 0xc6, 0x7b, 0x58, 0x56, 0x1b, 0x36, + 0x32, 0x19, 0xd2, 0x9d, 0x22, 0x3e, 0x37, 0x8f, 0xa9, 0xb6, 0xec, 0x3b, 0x1c, 0xc9, 0xdb, 0x90, + 0x64, 0x3d, 0xc4, 0x3a, 0x82, 0x6a, 0xd9, 0x15, 0xf1, 0xff, 0x27, 0xa7, 0xb9, 0x54, 0xa5, 0xb1, + 0x5f, 0x17, 0x3b, 0x8d, 0x91, 0x06, 0x2c, 0xf8, 0x96, 0x29, 0xac, 0xb7, 0xbd, 0xae, 0xa0, 0x76, + 0x33, 0xb0, 0x66, 0xc4, 0x50, 0xc0, 0x35, 0xff, 0xea, 0xe4, 0x34, 0xb7, 0x73, 0x19, 0xad, 0xea, + 0xc4, 0x30, 0x11, 0x1f, 0xd8, 0x58, 0xbb, 0x19, 0x10, 0xfb, 0xbe, 0xeb, 0xc4, 0x70, 0x64, 0x1b, + 0x98, 0x2d, 0x6a, 0xb6, 0x45, 0x94, 0x49, 0xc7, 0x8d, 0x96, 0x0c, 0xd6, 0x1a, 0x23, 0xf9, 0x0b, + 0x58, 0x8d, 0x40, 0x1c, 0x6d, 0x57, 0x5c, 0x6d, 0x53, 0x21, 0xc8, 0x51, 0xf7, 0x2e, 0xa4, 0x43, + 0x98, 0xa7, 0x6f, 0xca, 0xd5, 0x37, 0xb4, 0xf6, 0x14, 0x7e, 0x0a, 0x6b, 0x21, 0x30, 0xaa, 0xd0, + 0xea, 0x3c, 0x85, 0x6e, 0x04, 0xf8, 0x70, 0x51, 0x7e, 0x29, 0x41, 0x3e, 0xd4, 0x6a, 0x06, 0xa3, + 0xa3, 0x5a, 0xfa, 0xaa, 0xaa, 0x6d, 0x06, 0x2e, 0x8e, 0xa6, 0x63, 0xa8, 0x13, 0x63, 0xf2, 0x00, + 0xc8, 0x43, 0x76, 0xf6, 0x78, 0x07, 0x27, 0xc0, 0xdf, 0x31, 0x90, 0xab, 0xcc, 0x78, 0xd2, 0x6e, + 0xef, 0xd3, 0x21, 0x36, 0x91, 0xc9, 0xeb, 0xc4, 0x60, 0x73, 0xa7, 0xff, 0x1b, 0x88, 0x89, 0xa9, + 0xff, 0xf0, 0x29, 0x89, 0x59, 0x5d, 0xf9, 0x0e, 0xa4, 0xc3, 0x9e, 0x6e, 0x76, 0x10, 0xeb, 0x78, + 0xa7, 0xb8, 0x96, 0x0a, 0xba, 0xf5, 0x19, 0x62, 0x1d, 0xb9, 0x08, 0xd7, 0x23, 0xf5, 0x70, 0x04, + 0x64, 0xca, 0xa2, 0x33, 0xa3, 0xda, 0x6a, 0xd8, 0xa3, 0x6e, 0xc4, 0x3a, 0x5c, 0x8f, 0xb6, 0x8d, + 0xab, 0xf5, 0xd2, 0x55, 0xb5, 0x5e, 0x8d, 0x74, 0x9d, 0xd3, 0x9b, 0x8f, 0x21, 0x13, 0x84, 0x33, + 0xed, 0x8d, 0x29, 0x09, 0x37, 0xb0, 0x0d, 0x1f, 0x71, 0x34, 0x61, 0xcb, 0x26, 0x2b, 0xf3, 0x29, + 0x64, 0xfe, 0x2d, 0x7b, 0x50, 0x95, 0xdf, 0x24, 0xb8, 0x5e, 0x65, 0x46, 0xa5, 0xb1, 0x7f, 0x64, + 0x8a, 0x72, 0xe3, 0xb9, 0x35, 0x99, 0xa1, 0x65, 0x6c, 0x96, 0x96, 0xb3, 0x14, 0x8a, 0x7f, 0x64, + 0x85, 0x26, 0x93, 0xcc, 0x80, 0x32, 0x9d, 0x45, 0x90, 0xe2, 0x2f, 0x12, 0xa4, 0xab, 0xcc, 0x38, + 0xb2, 0xda, 0x88, 0xe3, 0x9a, 0xfb, 0x94, 0x91, 0x77, 0x61, 0x19, 0x0d, 0x78, 0x87, 0xda, 0x84, + 0x8f, 0xbd, 0x24, 0x2b, 0xca, 0xbb, 0xd7, 0x5b, 0x37, 0xc5, 0xed, 0xf2, 0xa4, 0xdd, 0xb6, 0x31, + 0x63, 0x75, 0x6e, 0x13, 0xd3, 0xd0, 0x42, 0xa8, 0xfc, 0x18, 0x12, 0xde, 0x63, 0x48, 0xdc, 0x47, + 0x9b, 0xf3, 0xae, 0x15, 0x17, 0x54, 0x59, 0x7c, 0x73, 0x9a, 0x5b, 0xd0, 0x84, 0xc9, 0xde, 0xaa, + 0x13, 0x71, 0x48, 0x56, 0xb8, 0x05, 0x1b, 0x53, 0x71, 0xf9, 0x31, 0x6f, 0xff, 0xbe, 0x08, 0xf1, + 0x2a, 0x33, 0xe4, 0x11, 0xc8, 0x33, 0xde, 0x3c, 0x0f, 0xe6, 0x78, 0x9d, 0x79, 0x05, 0x67, 0x1e, + 0x5d, 0x06, 0xed, 0x47, 0x20, 0xff, 0x08, 0x37, 0x66, 0x5d, 0xd6, 0x5b, 0x17, 0x20, 0x0b, 0xe1, + 0x99, 0x9d, 0x4b, 0xc1, 0x03, 0xe7, 0x14, 0xd2, 0xd3, 0xe7, 0xc4, 0xbd, 0xf9, 0x4c, 0x53, 0xd0, + 0x4c, 0xf9, 0xc2, 0xd0, 0xc0, 0xe1, 0x31, 0xac, 0x4c, 0xf4, 0xc7, 0x9d, 0xf9, 0x14, 0x51, 0x5c, + 0x46, 0xbd, 0x18, 0x2e, 0xf0, 0x43, 0x20, 0x35, 0x39, 0x6a, 0x77, 0xe7, 0x13, 0x4c, 0x00, 0x33, + 0xa5, 0x0b, 0x02, 0x7d, 0x57, 0x99, 0xa5, 0x9f, 0xde, 0xbf, 0xba, 0x2f, 0x55, 0x0e, 0xdf, 0x9c, + 0x65, 0xa5, 0xb7, 0x67, 0x59, 0xe9, 0xaf, 0xb3, 0xac, 0xf4, 0xf3, 0x79, 0x76, 0xe1, 0xed, 0x79, + 0x76, 0xe1, 0x8f, 0xf3, 0xec, 0xc2, 0xf7, 0xff, 0x79, 0xa2, 0x8e, 0xa2, 0xcf, 0x71, 0x77, 0x28, + 0x5b, 0x09, 0xf7, 0x39, 0xfe, 0xf0, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x52, 0xfc, 0xf9, 0xf0, + 0xce, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -825,16 +745,12 @@ type MsgClient interface { CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) - // BtcUndelegate undelegates funds from exsitng btc delegation - BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) - // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation - AddCovenantSig(ctx context.Context, in *MsgAddCovenantSig, opts ...grpc.CallOption) (*MsgAddCovenantSigResponse, error) - // AddCovenantUnbondingSigs handles two signatures from covenant for: - // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbonding tx submitted to babylon by staker - AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) + // AddCovenantSigs handles signatures from a covenant member + AddCovenantSigs(ctx context.Context, in *MsgAddCovenantSigs, opts ...grpc.CallOption) (*MsgAddCovenantSigsResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) + // BTCUndelegate handles a signature on unbonding tx from its delegator + BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) } type msgClient struct { @@ -863,36 +779,27 @@ func (c *msgClient) CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDel return out, nil } -func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) { - out := new(MsgBTCUndelegateResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/BTCUndelegate", in, out, opts...) +func (c *msgClient) AddCovenantSigs(ctx context.Context, in *MsgAddCovenantSigs, opts ...grpc.CallOption) (*MsgAddCovenantSigsResponse, error) { + out := new(MsgAddCovenantSigsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddCovenantSigs", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *msgClient) AddCovenantSig(ctx context.Context, in *MsgAddCovenantSig, opts ...grpc.CallOption) (*MsgAddCovenantSigResponse, error) { - out := new(MsgAddCovenantSigResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddCovenantSig", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) AddCovenantUnbondingSigs(ctx context.Context, in *MsgAddCovenantUnbondingSigs, opts ...grpc.CallOption) (*MsgAddCovenantUnbondingSigsResponse, error) { - out := new(MsgAddCovenantUnbondingSigsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/AddCovenantUnbondingSigs", in, out, opts...) +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { - out := new(MsgUpdateParamsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) +func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) { + out := new(MsgBTCUndelegateResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/BTCUndelegate", in, out, opts...) if err != nil { return nil, err } @@ -905,16 +812,12 @@ type MsgServer interface { CreateBTCValidator(context.Context, *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) - // BtcUndelegate undelegates funds from exsitng btc delegation - BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) - // AddCovenantSig handles a signature from covenant for slashing tx of staking tx for delegation - AddCovenantSig(context.Context, *MsgAddCovenantSig) (*MsgAddCovenantSigResponse, error) - // AddCovenantUnbondingSigs handles two signatures from covenant for: - // - unbonding tx submitted to babylon by staker - // - slashing tx corresponding to unbonding tx submitted to babylon by staker - AddCovenantUnbondingSigs(context.Context, *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) + // AddCovenantSigs handles signatures from a covenant member + AddCovenantSigs(context.Context, *MsgAddCovenantSigs) (*MsgAddCovenantSigsResponse, error) // UpdateParams updates the btcstaking module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) + // BTCUndelegate handles a signature on unbonding tx from its delegator + BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -927,18 +830,15 @@ func (*UnimplementedMsgServer) CreateBTCValidator(ctx context.Context, req *MsgC func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateBTCDelegation not implemented") } -func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCUndelegate not implemented") -} -func (*UnimplementedMsgServer) AddCovenantSig(ctx context.Context, req *MsgAddCovenantSig) (*MsgAddCovenantSigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddCovenantSig not implemented") -} -func (*UnimplementedMsgServer) AddCovenantUnbondingSigs(ctx context.Context, req *MsgAddCovenantUnbondingSigs) (*MsgAddCovenantUnbondingSigsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddCovenantUnbondingSigs not implemented") +func (*UnimplementedMsgServer) AddCovenantSigs(ctx context.Context, req *MsgAddCovenantSigs) (*MsgAddCovenantSigsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddCovenantSigs not implemented") } func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } +func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BTCUndelegate not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -980,74 +880,56 @@ func _Msg_CreateBTCDelegation_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } -func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgBTCUndelegate) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).BTCUndelegate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/BTCUndelegate", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).BTCUndelegate(ctx, req.(*MsgBTCUndelegate)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_AddCovenantSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddCovenantSig) +func _Msg_AddCovenantSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddCovenantSigs) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).AddCovenantSig(ctx, in) + return srv.(MsgServer).AddCovenantSigs(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/AddCovenantSig", + FullMethod: "/babylon.btcstaking.v1.Msg/AddCovenantSigs", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddCovenantSig(ctx, req.(*MsgAddCovenantSig)) + return srv.(MsgServer).AddCovenantSigs(ctx, req.(*MsgAddCovenantSigs)) } return interceptor(ctx, in, info, handler) } -func _Msg_AddCovenantUnbondingSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddCovenantUnbondingSigs) +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).AddCovenantUnbondingSigs(ctx, in) + return srv.(MsgServer).UpdateParams(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/AddCovenantUnbondingSigs", + FullMethod: "/babylon.btcstaking.v1.Msg/UpdateParams", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddCovenantUnbondingSigs(ctx, req.(*MsgAddCovenantUnbondingSigs)) + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) } return interceptor(ctx, in, info, handler) } -func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgUpdateParams) +func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgBTCUndelegate) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).UpdateParams(ctx, in) + return srv.(MsgServer).BTCUndelegate(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/UpdateParams", + FullMethod: "/babylon.btcstaking.v1.Msg/BTCUndelegate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + return srv.(MsgServer).BTCUndelegate(ctx, req.(*MsgBTCUndelegate)) } return interceptor(ctx, in, info, handler) } @@ -1065,21 +947,17 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Handler: _Msg_CreateBTCDelegation_Handler, }, { - MethodName: "BTCUndelegate", - Handler: _Msg_BTCUndelegate_Handler, - }, - { - MethodName: "AddCovenantSig", - Handler: _Msg_AddCovenantSig_Handler, - }, - { - MethodName: "AddCovenantUnbondingSigs", - Handler: _Msg_AddCovenantUnbondingSigs_Handler, + MethodName: "AddCovenantSigs", + Handler: _Msg_AddCovenantSigs_Handler, }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, }, + { + MethodName: "BTCUndelegate", + Handler: _Msg_BTCUndelegate_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btcstaking/v1/tx.proto", @@ -1218,11 +1096,52 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l - if m.DelegatorSig != nil { + if m.DelegatorUnbondingSlashingSig != nil { { - size := m.DelegatorSig.Size() + size := m.DelegatorUnbondingSlashingSig.Size() i -= size - if _, err := m.DelegatorSig.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.DelegatorUnbondingSlashingSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x7a + } + if m.UnbondingSlashingTx != nil { + { + size := m.UnbondingSlashingTx.Size() + i -= size + if _, err := m.UnbondingSlashingTx.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x72 + } + if m.UnbondingValue != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.UnbondingValue)) + i-- + dAtA[i] = 0x68 + } + if m.UnbondingTime != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x60 + } + if len(m.UnbondingTx) > 0 { + i -= len(m.UnbondingTx) + copy(dAtA[i:], m.UnbondingTx) + i = encodeVarintTx(dAtA, i, uint64(len(m.UnbondingTx))) + i-- + dAtA[i] = 0x5a + } + if m.DelegatorSlashingSig != nil { + { + size := m.DelegatorSlashingSig.Size() + i -= size + if _, err := m.DelegatorSlashingSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1347,7 +1266,7 @@ func (m *MsgCreateBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } -func (m *MsgBTCUndelegate) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantSigs) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1357,33 +1276,30 @@ func (m *MsgBTCUndelegate) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgBTCUndelegate) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigs) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.DelegatorSlashingSig != nil { - { - size := m.DelegatorSlashingSig.Size() - i -= size - if _, err := m.DelegatorSlashingSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) + if len(m.SlashingUnbondingTxSigs) > 0 { + for iNdEx := len(m.SlashingUnbondingTxSigs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.SlashingUnbondingTxSigs[iNdEx]) + copy(dAtA[i:], m.SlashingUnbondingTxSigs[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.SlashingUnbondingTxSigs[iNdEx]))) + i-- + dAtA[i] = 0x32 } - i-- - dAtA[i] = 0x32 } - if m.SlashingTx != nil { + if m.UnbondingTxSig != nil { { - size := m.SlashingTx.Size() + size := m.UnbondingTxSig.Size() i -= size - if _, err := m.SlashingTx.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1391,81 +1307,11 @@ func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - if m.UnbondingValue != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.UnbondingValue)) - i-- - dAtA[i] = 0x20 - } - if m.UnbondingTime != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.UnbondingTime)) - i-- - dAtA[i] = 0x18 - } - if len(m.UnbondingTx) > 0 { - i -= len(m.UnbondingTx) - copy(dAtA[i:], m.UnbondingTx) - i = encodeVarintTx(dAtA, i, uint64(len(m.UnbondingTx))) - i-- - dAtA[i] = 0x12 - } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgBTCUndelegateResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgBTCUndelegateResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgBTCUndelegateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgAddCovenantSig) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgAddCovenantSig) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddCovenantSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Sigs) > 0 { - for iNdEx := len(m.Sigs) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Sigs[iNdEx]) - copy(dAtA[i:], m.Sigs[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sigs[iNdEx]))) + if len(m.SlashingTxSigs) > 0 { + for iNdEx := len(m.SlashingTxSigs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.SlashingTxSigs[iNdEx]) + copy(dAtA[i:], m.SlashingTxSigs[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.SlashingTxSigs[iNdEx]))) i-- dAtA[i] = 0x22 } @@ -1499,7 +1345,7 @@ func (m *MsgAddCovenantSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgAddCovenantSigResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgAddCovenantSigsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1509,12 +1355,12 @@ func (m *MsgAddCovenantSigResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddCovenantSigResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddCovenantSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgAddCovenantSigsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1522,7 +1368,7 @@ func (m *MsgAddCovenantSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { +func (m *MsgBTCUndelegate) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1532,37 +1378,46 @@ func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgBTCUndelegate) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgBTCUndelegate) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - { - size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.UnbondingTxSig != nil { + { + size := m.UnbondingTxSig.Size() + i -= size + if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a } - i-- - dAtA[i] = 0x12 - if len(m.Authority) > 0 { - i -= len(m.Authority) - copy(dAtA[i:], m.Authority) - i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgBTCUndelegateResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1572,12 +1427,12 @@ func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgBTCUndelegateResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgBTCUndelegateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1585,7 +1440,7 @@ func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } -func (m *MsgAddCovenantUnbondingSigs) Marshal() (dAtA []byte, err error) { +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1595,67 +1450,37 @@ func (m *MsgAddCovenantUnbondingSigs) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgAddCovenantUnbondingSigs) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddCovenantUnbondingSigs) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.SlashingUnbondingTxSigs) > 0 { - for iNdEx := len(m.SlashingUnbondingTxSigs) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.SlashingUnbondingTxSigs[iNdEx]) - copy(dAtA[i:], m.SlashingUnbondingTxSigs[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.SlashingUnbondingTxSigs[iNdEx]))) - i-- - dAtA[i] = 0x2a - } - } - if m.UnbondingTxSig != nil { - { - size := m.UnbondingTxSig.Size() - i -= size - if _, err := m.UnbondingTxSig.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - if len(m.StakingTxHash) > 0 { - i -= len(m.StakingTxHash) - copy(dAtA[i:], m.StakingTxHash) - i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) - i-- - dAtA[i] = 0x1a - } - if m.Pk != nil { - { - size := m.Pk.Size() - i -= size - if _, err := m.Pk.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } - i-- - dAtA[i] = 0x12 + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *MsgAddCovenantUnbondingSigsResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1665,12 +1490,12 @@ func (m *MsgAddCovenantUnbondingSigsResponse) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *MsgAddCovenantUnbondingSigsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgAddCovenantUnbondingSigsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1773,30 +1598,8 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.SlashingTx.Size() n += 1 + l + sovTx(uint64(l)) } - if m.DelegatorSig != nil { - l = m.DelegatorSig.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgCreateBTCDelegationResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgBTCUndelegate) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { + if m.DelegatorSlashingSig != nil { + l = m.DelegatorSlashingSig.Size() n += 1 + l + sovTx(uint64(l)) } l = len(m.UnbondingTx) @@ -1809,18 +1612,18 @@ func (m *MsgBTCUndelegate) Size() (n int) { if m.UnbondingValue != 0 { n += 1 + sovTx(uint64(m.UnbondingValue)) } - if m.SlashingTx != nil { - l = m.SlashingTx.Size() + if m.UnbondingSlashingTx != nil { + l = m.UnbondingSlashingTx.Size() n += 1 + l + sovTx(uint64(l)) } - if m.DelegatorSlashingSig != nil { - l = m.DelegatorSlashingSig.Size() + if m.DelegatorUnbondingSlashingSig != nil { + l = m.DelegatorUnbondingSlashingSig.Size() n += 1 + l + sovTx(uint64(l)) } return n } -func (m *MsgBTCUndelegateResponse) Size() (n int) { +func (m *MsgCreateBTCDelegationResponse) Size() (n int) { if m == nil { return 0 } @@ -1829,7 +1632,7 @@ func (m *MsgBTCUndelegateResponse) Size() (n int) { return n } -func (m *MsgAddCovenantSig) Size() (n int) { +func (m *MsgAddCovenantSigs) Size() (n int) { if m == nil { return 0 } @@ -1847,8 +1650,18 @@ func (m *MsgAddCovenantSig) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if len(m.Sigs) > 0 { - for _, b := range m.Sigs { + if len(m.SlashingTxSigs) > 0 { + for _, b := range m.SlashingTxSigs { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } + } + if m.UnbondingTxSig != nil { + l = m.UnbondingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } + if len(m.SlashingUnbondingTxSigs) > 0 { + for _, b := range m.SlashingUnbondingTxSigs { l = len(b) n += 1 + l + sovTx(uint64(l)) } @@ -1856,7 +1669,7 @@ func (m *MsgAddCovenantSig) Size() (n int) { return n } -func (m *MsgAddCovenantSigResponse) Size() (n int) { +func (m *MsgAddCovenantSigsResponse) Size() (n int) { if m == nil { return 0 } @@ -1865,22 +1678,28 @@ func (m *MsgAddCovenantSigResponse) Size() (n int) { return n } -func (m *MsgUpdateParams) Size() (n int) { +func (m *MsgBTCUndelegate) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Authority) + l = len(m.Signer) if l > 0 { n += 1 + l + sovTx(uint64(l)) } - l = m.Params.Size() - n += 1 + l + sovTx(uint64(l)) + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.UnbondingTxSig != nil { + l = m.UnbondingTxSig.Size() + n += 1 + l + sovTx(uint64(l)) + } return n } -func (m *MsgUpdateParamsResponse) Size() (n int) { +func (m *MsgBTCUndelegateResponse) Size() (n int) { if m == nil { return 0 } @@ -1889,38 +1708,22 @@ func (m *MsgUpdateParamsResponse) Size() (n int) { return n } -func (m *MsgAddCovenantUnbondingSigs) Size() (n int) { +func (m *MsgUpdateParams) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Pk != nil { - l = m.Pk.Size() - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.StakingTxHash) + l = len(m.Authority) if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.UnbondingTxSig != nil { - l = m.UnbondingTxSig.Size() - n += 1 + l + sovTx(uint64(l)) - } - if len(m.SlashingUnbondingTxSigs) > 0 { - for _, b := range m.SlashingUnbondingTxSigs { - l = len(b) - n += 1 + l + sovTx(uint64(l)) - } - } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) return n } -func (m *MsgAddCovenantUnbondingSigsResponse) Size() (n int) { +func (m *MsgUpdateParamsResponse) Size() (n int) { if m == nil { return 0 } @@ -2560,7 +2363,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2588,144 +2391,12 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.DelegatorSig = &v - if err := m.DelegatorSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { + m.DelegatorSlashingSig = &v + if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgBTCUndelegate: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBTCUndelegate: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } @@ -2759,7 +2430,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { m.UnbondingTx = []byte{} } iNdEx = postIndex - case 3: + case 12: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) } @@ -2778,7 +2449,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { break } } - case 4: + case 13: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingValue", wireType) } @@ -2797,9 +2468,9 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { break } } - case 5: + case 14: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingSlashingTx", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2827,14 +2498,14 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v BTCSlashingTx - m.SlashingTx = &v - if err := m.SlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.UnbondingSlashingTx = &v + if err := m.UnbondingSlashingTx.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 6: + case 15: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorUnbondingSlashingSig", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2862,8 +2533,8 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340Signature - m.DelegatorSlashingSig = &v - if err := m.DelegatorSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.DelegatorUnbondingSlashingSig = &v + if err := m.DelegatorUnbondingSlashingSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2888,7 +2559,7 @@ func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { +func (m *MsgCreateBTCDelegationResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2911,10 +2582,10 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgBTCUndelegateResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBTCUndelegateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCreateBTCDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2938,7 +2609,7 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantSigs) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2961,10 +2632,10 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddCovenantSig: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantSigs: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddCovenantSig: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantSigs: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3068,7 +2739,74 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sigs", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingTxSigs", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SlashingTxSigs = append(m.SlashingTxSigs, make([]byte, postIndex-iNdEx)) + copy(m.SlashingTxSigs[len(m.SlashingTxSigs)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.UnbondingTxSig = &v + if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSigs", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3095,8 +2833,8 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Sigs = append(m.Sigs, make([]byte, postIndex-iNdEx)) - copy(m.Sigs[len(m.Sigs)-1], dAtA[iNdEx:postIndex]) + m.SlashingUnbondingTxSigs = append(m.SlashingUnbondingTxSigs, make([]byte, postIndex-iNdEx)) + copy(m.SlashingUnbondingTxSigs[len(m.SlashingUnbondingTxSigs)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -3119,7 +2857,7 @@ func (m *MsgAddCovenantSig) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddCovenantSigResponse) Unmarshal(dAtA []byte) error { +func (m *MsgAddCovenantSigsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3142,10 +2880,10 @@ func (m *MsgAddCovenantSigResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddCovenantSigResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgAddCovenantSigsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddCovenantSigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgAddCovenantSigsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -3169,7 +2907,7 @@ func (m *MsgAddCovenantSigResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { +func (m *MsgBTCUndelegate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3192,15 +2930,15 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + return fmt.Errorf("proto: MsgBTCUndelegate: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgBTCUndelegate: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3228,13 +2966,13 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Authority = string(dAtA[iNdEx:postIndex]) + m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -3244,22 +2982,56 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340Signature + m.UnbondingTxSig = &v + if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3284,7 +3056,7 @@ func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { +func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3307,10 +3079,10 @@ func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgBTCUndelegateResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgBTCUndelegateResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -3334,7 +3106,7 @@ func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3357,15 +3129,15 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddCovenantUnbondingSigs: wiretype end group for non-group") + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddCovenantUnbondingSigs: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3393,80 +3165,13 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signer = string(dAtA[iNdEx:postIndex]) + m.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pk", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.Pk = &v - if err := m.Pk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakingTxHash = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxSig", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -3476,59 +3181,25 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340Signature - m.UnbondingTxSig = &v - if err := m.UnbondingTxSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingUnbondingTxSigs", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SlashingUnbondingTxSigs = append(m.SlashingUnbondingTxSigs, make([]byte, postIndex-iNdEx)) - copy(m.SlashingUnbondingTxSigs[len(m.SlashingUnbondingTxSigs)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -3550,7 +3221,7 @@ func (m *MsgAddCovenantUnbondingSigs) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgAddCovenantUnbondingSigsResponse) Unmarshal(dAtA []byte) error { +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3573,10 +3244,10 @@ func (m *MsgAddCovenantUnbondingSigsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgAddCovenantUnbondingSigsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddCovenantUnbondingSigsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: From 9180fd8ff1080b28872435137e4196b07020357c Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 7 Dec 2023 22:03:24 +1100 Subject: [PATCH 122/202] chore: replacing usage of Header with HeaderInfo in ctx (#140) --- app/export.go | 13 +- proto/babylon/epoching/v1/epoching.proto | 14 +- .../zoneconcierge/v1/zoneconcierge.proto | 11 +- testutil/datagen/epoching.go | 5 +- testutil/datagen/tendermint.go | 20 +- testutil/keeper/btccheckpoint.go | 5 +- testutil/keeper/btclightclient.go | 5 +- testutil/keeper/btcstaking.go | 5 +- testutil/keeper/checkpointing.go | 5 +- testutil/keeper/epoching.go | 5 +- testutil/keeper/finality.go | 5 +- testutil/keeper/incentive.go | 6 +- testutil/keeper/zoneconcierge.go | 5 +- x/btcstaking/keeper/btc_height_index.go | 5 +- x/btcstaking/keeper/btc_height_index_test.go | 2 +- x/btcstaking/keeper/btc_validators.go | 3 +- x/btcstaking/keeper/grpc_query.go | 31 +- x/btcstaking/keeper/grpc_query_test.go | 15 +- x/btcstaking/keeper/incentive.go | 2 +- x/btcstaking/keeper/incentive_test.go | 2 +- x/btcstaking/keeper/voting_power_table.go | 2 +- .../keeper/voting_power_table_test.go | 14 +- x/checkpointing/abci.go | 2 +- x/checkpointing/keeper/keeper_test.go | 6 +- x/checkpointing/keeper/msg_server_test.go | 6 +- x/checkpointing/types/types.go | 2 +- x/checkpointing/types/types_test.go | 12 +- x/epoching/keeper/apphash_chain.go | 8 +- .../keeper/drop_validator_msg_decorator.go | 2 +- x/epoching/keeper/epochs.go | 14 +- x/epoching/keeper/grpc_query_test.go | 10 +- x/epoching/keeper/lifecycle_delegation.go | 3 +- x/epoching/keeper/lifecycle_validator.go | 3 +- x/epoching/keeper/modified_staking.go | 17 +- x/epoching/keeper/msg_server.go | 12 +- x/epoching/keeper/staking_functions.go | 3 +- x/epoching/testepoching/helper.go | 4 +- x/epoching/types/epoching.go | 21 +- x/epoching/types/epoching.pb.go | 250 ++++++++--------- x/finality/keeper/evidence.go | 3 +- x/finality/keeper/indexed_blocks.go | 7 +- x/finality/keeper/msg_server_test.go | 4 +- x/finality/keeper/tallying.go | 4 +- x/finality/keeper/tallying_test.go | 24 +- x/finality/keeper/votes.go | 3 +- x/incentive/abci.go | 2 +- x/incentive/keeper/btc_staking_gauge.go | 3 +- x/incentive/keeper/btc_staking_gauge_test.go | 2 +- .../keeper/intercept_fee_collector_test.go | 7 +- x/zoneconcierge/keeper/header_handler.go | 17 +- .../keeper/ibc_header_decorator.go | 2 +- x/zoneconcierge/keeper/ibc_packet.go | 7 +- .../keeper/ibc_packet_btc_timestamp.go | 2 +- .../keeper/proof_block_in_epoch.go | 6 +- x/zoneconcierge/keeper/proof_epoch_sealed.go | 8 +- .../keeper/proof_epoch_sealed_test.go | 2 +- .../keeper/proof_finalized_chain_info.go | 2 +- x/zoneconcierge/types/types.go | 2 +- x/zoneconcierge/types/zoneconcierge.go | 26 +- x/zoneconcierge/types/zoneconcierge.pb.go | 265 ++++++++++-------- 60 files changed, 521 insertions(+), 432 deletions(-) diff --git a/app/export.go b/app/export.go index 5c2e90a3b..105c96987 100644 --- a/app/export.go +++ b/app/export.go @@ -1,10 +1,11 @@ package app import ( - storetypes "cosmossdk.io/store/types" "encoding/json" "log" + storetypes "cosmossdk.io/store/types" + servertypes "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" @@ -116,8 +117,10 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) // set context height to zero - height := ctx.BlockHeight() - ctx = ctx.WithBlockHeight(0) + height := ctx.HeaderInfo().Height + headerInfo := ctx.HeaderInfo() + headerInfo.Height = 0 + ctx = ctx.WithHeaderInfo(headerInfo) // reinitialize all validators err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { @@ -164,7 +167,9 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr } // reset context height - ctx = ctx.WithBlockHeight(height) + headerInfo = ctx.HeaderInfo() + headerInfo.Height = height + ctx = ctx.WithHeaderInfo(headerInfo) /* Handle staking state. */ diff --git a/proto/babylon/epoching/v1/epoching.proto b/proto/babylon/epoching/v1/epoching.proto index 66c7faf51..802d86404 100644 --- a/proto/babylon/epoching/v1/epoching.proto +++ b/proto/babylon/epoching/v1/epoching.proto @@ -3,7 +3,6 @@ package babylon.epoching.v1; import "google/protobuf/timestamp.proto"; import "gogoproto/gogo.proto"; -import "tendermint/types/types.proto"; import "cosmos/staking/v1beta1/tx.proto"; option go_package = "github.com/babylonchain/babylon/x/epoching/types"; @@ -13,19 +12,20 @@ message Epoch { uint64 epoch_number = 1; uint64 current_epoch_interval = 2; uint64 first_block_height = 3; - // last_block_header is the header of the last block in this epoch. - // Babylon needs to remember the last header of each epoch to complete + // last_block_time is the time of the last block in this epoch. + // Babylon needs to remember the last header's time of each epoch to complete // unbonding validators/delegations when a previous epoch's checkpoint is - // finalised. The last_block_header field is nil in the epoch's beginning, and + // finalised. The last_block_time field is nil in the epoch's beginning, and // is set upon the end of this epoch. - tendermint.types.Header last_block_header = 4; + google.protobuf.Timestamp last_block_time = 4 [ (gogoproto.stdtime) = true ]; // app_hash_root is the Merkle root of all AppHashs in this epoch // It will be used for proving a block is in an epoch bytes app_hash_root = 5; - // sealer_header is the 2nd header of the next epoch + // sealer_header_hash is the app_hash of the sealer header, i.e., 2nd header + // of the next epoch // This validator set has generated a BLS multisig on `app_hash` of // the sealer header - tendermint.types.Header sealer_header = 6; + bytes sealer_header_hash = 6; } // QueuedMessage is a message that can change the validator set and is delayed diff --git a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto index ae05c3490..d216ec3be 100644 --- a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto +++ b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto @@ -26,15 +26,18 @@ message IndexedHeader { // it is needed for CZ to unbond all mature validators/delegations // before this timestamp when this header is BTC-finalised google.protobuf.Timestamp time = 4 [ (gogoproto.stdtime) = true ]; - // babylon_header is the header of the babylon block that includes this CZ + // babylon_header_hash is the hash of the babylon block that includes this CZ // header - tendermint.types.Header babylon_header = 5; + bytes babylon_header_hash = 5; + // babylon_header_height is the height of the babylon block that includes this CZ + // header + uint64 babylon_header_height = 6; // epoch is the epoch number of this header on Babylon ledger - uint64 babylon_epoch = 6; + uint64 babylon_epoch = 7; // babylon_tx_hash is the hash of the tx that includes this header // (babylon_block_height, babylon_tx_hash) jointly provides the position of // the header on Babylon ledger - bytes babylon_tx_hash = 7; + bytes babylon_tx_hash = 8; } // Forks is a list of non-canonical `IndexedHeader`s at the same height. diff --git a/testutil/datagen/epoching.go b/testutil/datagen/epoching.go index 4b44d4c69..6c1e67196 100644 --- a/testutil/datagen/epoching.go +++ b/testutil/datagen/epoching.go @@ -35,8 +35,9 @@ func GenRandomEpoch(r *rand.Rand) *epochingtypes.Epoch { epochNum, epochInterval, firstBlockHeight, - lastBlockHeader, + &lastBlockHeader.Time, ) - epoch.SealerHeader = GenRandomTMHeader(r, "test-chain", firstBlockHeight+epochInterval+1) // 2nd block in the next epoch + sealerHeader := GenRandomTMHeader(r, "test-chain", firstBlockHeight+epochInterval+1) // 2nd block in the next epoch + epoch.SealerHeaderHash = sealerHeader.AppHash return &epoch } diff --git a/testutil/datagen/tendermint.go b/testutil/datagen/tendermint.go index 6a87e9095..b3c2365fa 100644 --- a/testutil/datagen/tendermint.go +++ b/testutil/datagen/tendermint.go @@ -6,14 +6,15 @@ import ( zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) func GenRandomTMHeader(r *rand.Rand, chainID string, height uint64) *tmproto.Header { return &tmproto.Header{ - ChainID: chainID, - Height: int64(height), - Time: time.Now(), + ChainID: chainID, + Height: int64(height), + Time: time.Now(), AppHash: GenRandomByteArray(r, 32), } } @@ -22,8 +23,8 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp return &ibctmtypes.Header{ SignedHeader: &tmproto.SignedHeader{ Header: &tmproto.Header{ - ChainID: chainID, - Height: int64(height), + ChainID: chainID, + Height: int64(height), AppHash: GenRandomByteArray(r, 32), }, }, @@ -32,8 +33,15 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp func HeaderToHeaderInfo(header *ibctmtypes.Header) *zctypes.HeaderInfo { return &zctypes.HeaderInfo{ - Hash: header.Header.AppHash, + AppHash: header.Header.AppHash, ChainId: header.Header.ChainID, Height: uint64(header.Header.Height), } } + +func WithCtxHeight(ctx sdk.Context, height uint64) sdk.Context { + headerInfo := ctx.HeaderInfo() + headerInfo.Height = int64(height) + ctx = ctx.WithHeaderInfo(headerInfo) + return ctx +} diff --git a/testutil/keeper/btccheckpoint.go b/testutil/keeper/btccheckpoint.go index 9b7e36e4c..53353f1d6 100644 --- a/testutil/keeper/btccheckpoint.go +++ b/testutil/keeper/btccheckpoint.go @@ -1,9 +1,11 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "math/big" @@ -51,6 +53,7 @@ func NewBTCCheckpointKeeper( ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) // Initialize params if err := k.SetParams(ctx, btcctypes.DefaultParams()); err != nil { diff --git a/testutil/keeper/btclightclient.go b/testutil/keeper/btclightclient.go index 52ffa7478..060fd1600 100644 --- a/testutil/keeper/btclightclient.go +++ b/testutil/keeper/btclightclient.go @@ -1,10 +1,12 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -40,6 +42,7 @@ func BTCLightClientKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) return &k, ctx } diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index 350e53900..f4f0196b9 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -1,9 +1,11 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -42,6 +44,7 @@ func BTCStakingKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, btcc ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) // Initialize params if err := k.SetParams(ctx, types.DefaultParams()); err != nil { diff --git a/testutil/keeper/checkpointing.go b/testutil/keeper/checkpointing.go index 6e73487e8..194a941e1 100644 --- a/testutil/keeper/checkpointing.go +++ b/testutil/keeper/checkpointing.go @@ -1,10 +1,12 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -40,6 +42,7 @@ func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.Bl ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) return &k, ctx, cdc } diff --git a/testutil/keeper/epoching.go b/testutil/keeper/epoching.go index c142ac994..e3fb6bbee 100644 --- a/testutil/keeper/epoching.go +++ b/testutil/keeper/epoching.go @@ -1,9 +1,11 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -44,6 +46,7 @@ func EpochingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { // TODO: add msgServiceRouter? ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) // Initialize params if err := k.SetParams(ctx, types.DefaultParams()); err != nil { diff --git a/testutil/keeper/finality.go b/testutil/keeper/finality.go index c9de2df4b..e5ae97810 100644 --- a/testutil/keeper/finality.go +++ b/testutil/keeper/finality.go @@ -1,10 +1,12 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" storemetrics "cosmossdk.io/store/metrics" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -40,6 +42,7 @@ func FinalityKeeper(t testing.TB, bsKeeper types.BTCStakingKeeper, iKeeper types ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) // Initialize params if err := k.SetParams(ctx, types.DefaultParams()); err != nil { diff --git a/testutil/keeper/incentive.go b/testutil/keeper/incentive.go index 0ffdb9d21..750394cf2 100644 --- a/testutil/keeper/incentive.go +++ b/testutil/keeper/incentive.go @@ -1,12 +1,12 @@ package keeper import ( - storemetrics "cosmossdk.io/store/metrics" - "github.com/cosmos/cosmos-sdk/runtime" "testing" + "cosmossdk.io/core/header" "cosmossdk.io/log" "cosmossdk.io/store" + storemetrics "cosmossdk.io/store/metrics" storetypes "cosmossdk.io/store/types" "github.com/babylonchain/babylon/x/incentive/keeper" "github.com/babylonchain/babylon/x/incentive/types" @@ -14,6 +14,7 @@ import ( dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -42,6 +43,7 @@ func IncentiveKeeper(t testing.TB, bankKeeper types.BankKeeper, accountKeeper ty ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + ctx = ctx.WithHeaderInfo(header.Info{}) // Initialize params if err := k.SetParams(ctx, types.DefaultParams()); err != nil { diff --git a/testutil/keeper/zoneconcierge.go b/testutil/keeper/zoneconcierge.go index 692f01bd4..a575f2a2c 100644 --- a/testutil/keeper/zoneconcierge.go +++ b/testutil/keeper/zoneconcierge.go @@ -1,10 +1,12 @@ package keeper import ( + "testing" + + "cosmossdk.io/core/header" "cosmossdk.io/store/metrics" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/runtime" - "testing" "cosmossdk.io/log" "cosmossdk.io/store" @@ -101,6 +103,7 @@ func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, c ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, logger) + ctx = ctx.WithHeaderInfo(header.Info{}) return k, ctx } diff --git a/x/btcstaking/keeper/btc_height_index.go b/x/btcstaking/keeper/btc_height_index.go index 90954aa05..9ed5e3961 100644 --- a/x/btcstaking/keeper/btc_height_index.go +++ b/x/btcstaking/keeper/btc_height_index.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -10,7 +11,7 @@ import ( // IndexBTCHeight indexes the current BTC height, and saves it to KVStore func (k Keeper) IndexBTCHeight(ctx context.Context) { - babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) + babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) btcTip := k.btclcKeeper.GetTipInfo(ctx) if btcTip == nil { return @@ -30,7 +31,7 @@ func (k Keeper) GetBTCHeightAtBabylonHeight(ctx context.Context, babylonHeight u } func (k Keeper) GetCurrentBTCHeight(ctx context.Context) (uint64, error) { - babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) + babylonHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) return k.GetBTCHeightAtBabylonHeight(ctx, babylonHeight) } diff --git a/x/btcstaking/keeper/btc_height_index_test.go b/x/btcstaking/keeper/btc_height_index_test.go index 1ed867f6a..27fdf2bc9 100644 --- a/x/btcstaking/keeper/btc_height_index_test.go +++ b/x/btcstaking/keeper/btc_height_index_test.go @@ -26,7 +26,7 @@ func FuzzBTCHeightIndex(f *testing.F) { // randomise Babylon height and BTC height babylonHeight := datagen.RandomInt(r, 100) - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btcHeight := datagen.RandomInt(r, 100) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: btcHeight}).Times(1) keeper.IndexBTCHeight(ctx) diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index f70923473..4d41c82b0 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" @@ -45,7 +46,7 @@ func (k Keeper) SlashBTCValidator(ctx context.Context, valBTCPK []byte) error { if btcVal.IsSlashed() { return types.ErrBTCValAlreadySlashed } - btcVal.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) + btcVal.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) btcTip := k.btclcKeeper.GetTipInfo(ctx) if btcTip == nil { panic(fmt.Errorf("failed to get current BTC tip")) diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index d98144c25..819d454b6 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -73,18 +73,14 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio return nil, status.Error(codes.InvalidArgument, "empty request") } - sdkCtx := sdk.UnwrapSDKContext(ctx) covenantQuorum := k.GetParams(ctx).CovenantQuorum // get current BTC height - btcTipHeight, err := k.GetCurrentBTCHeight(sdkCtx) - if err != nil { - return nil, err - } + btcTipHeight := k.btclcKeeper.GetTipInfo(ctx).Height // get value of w - wValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - store := k.btcDelegationStore(sdkCtx) + store := k.btcDelegationStore(ctx) var btcDels []*types.BTCDelegation pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) { var btcDel types.BTCDelegation @@ -142,7 +138,7 @@ func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBT sdkCtx := sdk.UnwrapSDKContext(ctx) power := uint64(0) - curHeight := uint64(sdkCtx.BlockHeight()) + curHeight := uint64(sdkCtx.HeaderInfo().Height) // if voting power table is recorded at the current height, use this voting power if k.HasVotingPowerTable(sdkCtx, curHeight) { @@ -257,8 +253,6 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation return nil, status.Error(codes.InvalidArgument, "empty request") } - sdkCtx := sdk.UnwrapSDKContext(ctx) - // decode staking tx hash stakingTxHash, err := chainhash.NewHashFromStr(req.StakingTxHashHex) if err != nil { @@ -266,14 +260,14 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation } // find BTC delegation - btcDel := k.getBTCDelegation(sdkCtx, *stakingTxHash) + btcDel := k.getBTCDelegation(ctx, *stakingTxHash) if btcDel == nil { return nil, types.ErrBTCDelegationNotFound } // check whether it's active - currentTip := k.btclcKeeper.GetTipInfo(sdkCtx) - currentWValue := k.btccKeeper.GetParams(sdkCtx).CheckpointFinalizationTimeout + currentTip := k.btclcKeeper.GetTipInfo(ctx) + currentWValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout isActive := btcDel.GetStatus( currentTip.Height, currentWValue, @@ -281,13 +275,10 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation ) == types.BTCDelegationStatus_ACTIVE // get its undelegation info - var undelegationInfo *types.BTCUndelegationInfo - if btcDel.BtcUndelegation != nil { - undelegationInfo = &types.BTCUndelegationInfo{ - UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, - UnbondingTime: btcDel.BtcUndelegation.UnbondingTime, - CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, - } + undelegationInfo := &types.BTCUndelegationInfo{ + UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, + UnbondingTime: btcDel.BtcUndelegation.UnbondingTime, + CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, } return &types.QueryBTCDelegationResponse{ diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 333aa36f2..b9806b489 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -200,6 +200,8 @@ func FuzzPendingBTCDelegations(f *testing.F) { // Generate a random number of BTC delegations under each validator startHeight := datagen.RandomInt(r, 100) + 1 + btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).AnyTimes() + endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 numBTCDels := datagen.RandomInt(r, 10) + 1 pendingBtcDelsMap := make(map[string]*types.BTCDelegation) @@ -228,7 +230,6 @@ func FuzzPendingBTCDelegations(f *testing.F) { require.NoError(t, err) txHash := btcDel.MustGetStakingTxHash().String() - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) delView, err := keeper.BTCDelegation(ctx, &types.QueryBTCDelegationRequest{ StakingTxHashHex: txHash, }) @@ -238,9 +239,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { } babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) - btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) - keeper.IndexBTCHeight(ctx) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) // querying paginated BTC delegations and assert // Generate a page request with a limit and a nil key @@ -310,7 +309,7 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { keeper.SetBTCValidator(ctx, btcVal) // set random voting power at random height randomHeight := datagen.RandomInt(r, 100) + 1 - ctx = ctx.WithBlockHeight(int64(randomHeight)) + ctx = datagen.WithCtxHeight(ctx, randomHeight) randomPower := datagen.RandomInt(r, 100) + 1 keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) @@ -325,14 +324,14 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { // if height increments but voting power hasn't recorded yet, then // we need to return the height and voting power at the last height - ctx = ctx.WithBlockHeight(int64(randomHeight + 1)) + ctx = datagen.WithCtxHeight(ctx, randomHeight+1) resp, err = keeper.BTCValidatorCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight, resp.Height) require.Equal(t, randomPower, resp.VotingPower) // but no more - ctx = ctx.WithBlockHeight(int64(randomHeight + 2)) + ctx = datagen.WithCtxHeight(ctx, randomHeight+2) resp, err = keeper.BTCValidatorCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight+1, resp.Height) @@ -512,7 +511,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { require.Error(t, err) babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).Times(1) keeper.IndexBTCHeight(ctx) diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go index af8408a0f..c0ac8863b 100644 --- a/x/btcstaking/keeper/incentive.go +++ b/x/btcstaking/keeper/incentive.go @@ -69,7 +69,7 @@ func (k Keeper) RecordRewardDistCache(ctx context.Context) { } // all good, set the reward distribution cache of the current height - k.setRewardDistCache(ctx, uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()), rdc) + k.setRewardDistCache(ctx, uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height), rdc) } func (k Keeper) setRewardDistCache(ctx context.Context, height uint64, rdc *types.RewardDistCache) { diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index eacc367f8..cabc0890d 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -84,7 +84,7 @@ func FuzzRecordRewardDistCache(f *testing.F) { // record reward distribution cache babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordRewardDistCache(ctx) diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index a9b326b24..226dbe049 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -18,7 +18,7 @@ import ( func (k Keeper) RecordVotingPowerTable(ctx context.Context) { covenantQuorum := k.GetParams(ctx).CovenantQuorum // tip of Babylon and Bitcoin - babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) + babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) btcTipHeight, err := k.GetCurrentBTCHeight(ctx) if err != nil { return diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 1bc4b219a..d0895a845 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -84,7 +84,7 @@ func FuzzVotingPowerTable(f *testing.F) { No BTC validator will have voting power */ babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) @@ -97,7 +97,7 @@ func FuzzVotingPowerTable(f *testing.F) { Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power */ babylonHeight += datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) @@ -135,7 +135,7 @@ func FuzzVotingPowerTable(f *testing.F) { require.NoError(t, err) // move to later Babylon height and 2nd BTC height babylonHeight += datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1) // index height and record power table keeper.IndexBTCHeight(ctx) @@ -169,7 +169,7 @@ func FuzzVotingPowerTable(f *testing.F) { Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) */ babylonHeight += datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) @@ -258,7 +258,7 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { // record voting power table babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) @@ -353,7 +353,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { // record voting power table babylonHeight := datagen.RandomInt(r, 10) + 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) @@ -397,7 +397,7 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { // record voting power table babylonHeight += 1 - ctx = ctx.WithBlockHeight(int64(babylonHeight)) + ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) diff --git a/x/checkpointing/abci.go b/x/checkpointing/abci.go index 0f5b5733d..ec4c6793f 100644 --- a/x/checkpointing/abci.go +++ b/x/checkpointing/abci.go @@ -34,7 +34,7 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { // note that this epochNum is obtained after the BeginBlocker of the epoching module is executed // meaning that the epochNum has been incremented upon a new epoch - appHash := sdkCtx.BlockHeader().AppHash + appHash := sdkCtx.HeaderInfo().AppHash ckpt, err := k.BuildRawCheckpoint(ctx, epoch.EpochNumber-1, appHash) if err != nil { panic("failed to generate a raw checkpoint") diff --git a/x/checkpointing/keeper/keeper_test.go b/x/checkpointing/keeper/keeper_test.go index c79337a96..c613fbf44 100644 --- a/x/checkpointing/keeper/keeper_test.go +++ b/x/checkpointing/keeper/keeper_test.go @@ -231,14 +231,14 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { }) } -func makeBtcCkptBytes(r *rand.Rand, epoch uint64, lch []byte, bitmap []byte, blsSig []byte, t *testing.T) *btctxformatter.RawBtcCheckpoint { +func makeBtcCkptBytes(r *rand.Rand, epoch uint64, appHash []byte, bitmap []byte, blsSig []byte, t *testing.T) *btctxformatter.RawBtcCheckpoint { tag := datagen.GenRandomByteArray(r, btctxformatter.TagLength) babylonTag := btctxformatter.BabylonTag(tag[:btctxformatter.TagLength]) address := datagen.GenRandomByteArray(r, btctxformatter.AddressLength) rawBTCCkpt := &btctxformatter.RawBtcCheckpoint{ Epoch: epoch, - AppHash: lch, + AppHash: appHash, BitMap: bitmap, SubmitterAddress: address, BlsSig: blsSig, @@ -263,7 +263,7 @@ func makeBtcCkptBytes(r *rand.Rand, epoch uint64, lch []byte, bitmap []byte, bls } func curStateUpdate(ctx sdk.Context, status types.CheckpointStatus) *types.CheckpointStateUpdate { - height, time := ctx.BlockHeight(), ctx.BlockTime() + height, time := ctx.HeaderInfo().Height, ctx.HeaderInfo().Time return &types.CheckpointStateUpdate{ State: status, BlockHeight: uint64(height), diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index e75423a4e..fdbb996db 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -288,7 +288,7 @@ func FuzzAddBlsSig_NotInValSet(f *testing.F) { require.NoError(t, err) // build BLS sig from a random validator (not in the validator set) - appHash := ctx.BlockHeader().AppHash + appHash := ctx.HeaderInfo().AppHash blsPrivKey := bls12381.GenPrivKey() valAddr := datagen.GenRandomValidatorAddress() signBytes := types.GetSignBytes(endingEpoch, appHash) @@ -321,7 +321,7 @@ func FuzzAddBlsSig_CkptNotExist(f *testing.F) { // build BLS signature from a random validator of the validator set n := len(helper.GenValidators.BlsPrivKeys) i := r.Intn(n) - appHash := ctx.BlockHeader().AppHash + appHash := ctx.HeaderInfo().AppHash blsPrivKey := helper.GenValidators.BlsPrivKeys[i] addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) require.NoError(t, err) @@ -412,7 +412,7 @@ func FuzzAddBlsSig_InvalidSignature(f *testing.F) { n := len(helper.GenValidators.BlsPrivKeys) i := r.Intn(n) // inject random last commit hash - appHash := ctx.BlockHeader().AppHash + appHash := ctx.HeaderInfo().AppHash addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) require.NoError(t, err) blsSig := datagen.GenRandomBlsMultiSig(r) diff --git a/x/checkpointing/types/types.go b/x/checkpointing/types/types.go index 279c758a2..eb02ec64d 100644 --- a/x/checkpointing/types/types.go +++ b/x/checkpointing/types/types.go @@ -113,7 +113,7 @@ func (cm *RawCheckpointWithMeta) IsMoreMatureThanStatus(status CheckpointStatus) // where the time/height are captured by the current ctx func (cm *RawCheckpointWithMeta) RecordStateUpdate(ctx context.Context, status CheckpointStatus) { sdkCtx := sdk.UnwrapSDKContext(ctx) - height, time := sdkCtx.BlockHeight(), sdkCtx.BlockTime() + height, time := sdkCtx.HeaderInfo().Height, sdkCtx.HeaderInfo().Time stateUpdate := &CheckpointStateUpdate{ State: status, BlockHeight: uint64(height), diff --git a/x/checkpointing/types/types_test.go b/x/checkpointing/types/types_test.go index c29abc0e6..0d90ba99e 100644 --- a/x/checkpointing/types/types_test.go +++ b/x/checkpointing/types/types_test.go @@ -20,10 +20,10 @@ func TestRawCheckpointWithMeta_Accumulate1(t *testing.T) { n := 1 totalPower := int64(10) ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - lch := datagen.GenRandomAppHash(r) - msg := types.GetSignBytes(epochNum, lch) + appHash := datagen.GenRandomAppHash(r) + msg := types.GetSignBytes(epochNum, appHash) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) - ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, lch) + ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, appHash) require.NoError(t, err) valSet := datagen.GenRandomValSet(n) err = ckpt.Accumulate(valSet, valSet[0].Addr, blsPubkeys[0], blsSigs[0], totalPower) @@ -43,10 +43,10 @@ func TestRawCheckpointWithMeta_Accumulate4(t *testing.T) { n := 4 totalPower := int64(10) * int64(n) ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - lch := datagen.GenRandomAppHash(r) - msg := types.GetSignBytes(epochNum, lch) + appHash := datagen.GenRandomAppHash(r) + msg := types.GetSignBytes(epochNum, appHash) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) - ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, lch) + ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, appHash) require.NoError(t, err) valSet := datagen.GenRandomValSet(n) for i := 0; i < n; i++ { diff --git a/x/epoching/keeper/apphash_chain.go b/x/epoching/keeper/apphash_chain.go index b42fe0701..c083d8cc8 100644 --- a/x/epoching/keeper/apphash_chain.go +++ b/x/epoching/keeper/apphash_chain.go @@ -35,8 +35,8 @@ func (k Keeper) GetAppHash(ctx context.Context, height uint64) ([]byte, error) { // RecordAppHash stores the AppHash of the current header to KVStore func (k Keeper) RecordAppHash(ctx context.Context) { sdkCtx := sdk.UnwrapSDKContext(ctx) - height := uint64(sdkCtx.BlockHeader().Height) - appHash := sdkCtx.BlockHeader().AppHash + height := uint64(sdkCtx.HeaderInfo().Height) + appHash := sdkCtx.HeaderInfo().AppHash // HACK: the app hash for the first height is set to nil // instead of the hash of an empty byte slice as intended // see proposed fix: https://github.com/cosmos/cosmos-sdk/pull/18524 @@ -58,7 +58,7 @@ func (k Keeper) GetAllAppHashesForEpoch(ctx context.Context, epoch *types.Epoch) // fetch each AppHash in this epoch appHashs := [][]byte{} - for i := epoch.FirstBlockHeight; i <= uint64(epoch.LastBlockHeader.Height); i++ { + for i := epoch.FirstBlockHeight; i <= epoch.GetLastBlockHeight(); i++ { appHash, err := k.GetAppHash(ctx, i) if err != nil { return nil, err @@ -77,7 +77,7 @@ func (k Keeper) ProveAppHashInEpoch(ctx context.Context, height uint64, epochNum return nil, err } if !epoch.WithinBoundary(height) { - return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "the given height %d is not in epoch %d (interval [%d, %d])", height, epoch.EpochNumber, epoch.FirstBlockHeight, uint64(epoch.LastBlockHeader.Height)) + return nil, errorsmod.Wrapf(types.ErrInvalidHeight, "the given height %d is not in epoch %d (interval [%d, %d])", height, epoch.EpochNumber, epoch.FirstBlockHeight, epoch.GetLastBlockHeight()) } // calculate index of this height in this epoch diff --git a/x/epoching/keeper/drop_validator_msg_decorator.go b/x/epoching/keeper/drop_validator_msg_decorator.go index 6e0f1509a..ca982c7b3 100644 --- a/x/epoching/keeper/drop_validator_msg_decorator.go +++ b/x/epoching/keeper/drop_validator_msg_decorator.go @@ -27,7 +27,7 @@ func NewDropValidatorMsgDecorator(ek Keeper) *DropValidatorMsgDecorator { // TODO (non-urgent): after we bump to Cosmos SDK v0.46, add MsgCancelUnbondingDelegation func (qmd DropValidatorMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { // skip if at genesis block, as genesis state contains txs that bootstrap the initial validator set - if ctx.BlockHeight() == 0 { + if ctx.HeaderInfo().Height == 0 { return next(ctx, tx, simulate) } // after genesis, if validator-related message, reject msg diff --git a/x/epoching/keeper/epochs.go b/x/epoching/keeper/epochs.go index 89dae1049..4574a30bb 100644 --- a/x/epoching/keeper/epochs.go +++ b/x/epoching/keeper/epochs.go @@ -56,12 +56,12 @@ func (k Keeper) getEpochInfo(ctx context.Context, epochNumber uint64) (*types.Ep // InitEpoch sets the zero epoch number to DB func (k Keeper) InitEpoch(ctx context.Context) { - header := sdk.UnwrapSDKContext(ctx).BlockHeader() + header := sdk.UnwrapSDKContext(ctx).HeaderInfo() if header.Height > 0 { panic("InitEpoch can be invoked only at genesis") } epochInterval := k.GetParams(ctx).EpochInterval - epoch := types.NewEpoch(0, epochInterval, 0, &header) + epoch := types.NewEpoch(0, epochInterval, 0, &header.Time) k.setEpochInfo(ctx, 0, &epoch) k.setEpochNumber(ctx, 0) @@ -90,8 +90,8 @@ func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx context.Context) error { return errorsmod.Wrapf(types.ErrInvalidHeight, "RecordLastBlockHeader can only be invoked at the last block of an epoch") } // record last block header - header := sdk.UnwrapSDKContext(ctx).BlockHeader() - epoch.LastBlockHeader = &header + header := sdk.UnwrapSDKContext(ctx).HeaderInfo() + epoch.LastBlockTime = &header.Time // calculate and record the Merkle root appHashes, err := k.GetAllAppHashesForEpoch(ctx, epoch) if err != nil { @@ -113,7 +113,7 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch if !epoch.IsSecondBlock(ctx) { panic("RecordSealerHeaderForPrevEpoch can only be invoked at the second header of a non-zero epoch") } - header := sdk.UnwrapSDKContext(ctx).BlockHeader() + header := sdk.UnwrapSDKContext(ctx).HeaderInfo() // get the sealed epoch, i.e., the epoch earlier than the current epoch sealedEpoch, err := k.GetHistoricalEpoch(ctx, epoch.EpochNumber-1) @@ -122,7 +122,7 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch } // record the sealer header for the sealed epoch - sealedEpoch.SealerHeader = &header + sealedEpoch.SealerHeaderHash = header.AppHash k.setEpochInfo(ctx, sealedEpoch.EpochNumber, sealedEpoch) return sealedEpoch @@ -137,7 +137,7 @@ func (k Keeper) IncEpoch(ctx context.Context) types.Epoch { k.setEpochNumber(ctx, incrementedEpochNumber) epochInterval := k.GetParams(ctx).EpochInterval - newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(sdkCtx.BlockHeight()), nil) + newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(sdkCtx.HeaderInfo().Height), nil) k.setEpochInfo(ctx, incrementedEpochNumber, &newEpoch) return newEpoch diff --git a/x/epoching/keeper/grpc_query_test.go b/x/epoching/keeper/grpc_query_test.go index 735fe4fca..4b6649705 100644 --- a/x/epoching/keeper/grpc_query_test.go +++ b/x/epoching/keeper/grpc_query_test.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "cosmossdk.io/core/header" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/babylonchain/babylon/testutil/datagen" @@ -76,7 +77,14 @@ func FuzzCurrentEpoch(f *testing.F) { // starting from epoch 1 for i := uint64(1); i < increment; i++ { // this ensures that IncEpoch is invoked only at the first header of each epoch - ctx = ctx.WithBlockHeader(*datagen.GenRandomTMHeader(r, "chain-test", i*epochInterval+1)) + randomHeader := datagen.GenRandomTMHeader(r, "chain-test", i*epochInterval+1) + headerInfo := header.Info{ + AppHash: randomHeader.AppHash, + Height: randomHeader.Height, + Time: randomHeader.Time, + ChainID: randomHeader.ChainID, + } + ctx = ctx.WithHeaderInfo(headerInfo) keeper.IncEpoch(ctx) } req := types.QueryCurrentEpochRequest{} diff --git a/x/epoching/keeper/lifecycle_delegation.go b/x/epoching/keeper/lifecycle_delegation.go index 0e6b5b4cc..669e581f6 100644 --- a/x/epoching/keeper/lifecycle_delegation.go +++ b/x/epoching/keeper/lifecycle_delegation.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -20,7 +21,7 @@ func (k Keeper) RecordNewDelegationState(ctx context.Context, delAddr sdk.AccAdd } } sdkCtx := sdk.UnwrapSDKContext(ctx) - height, time := sdkCtx.BlockHeight(), sdkCtx.BlockTime() + height, time := sdkCtx.HeaderInfo().Height, sdkCtx.HeaderInfo().Time DelegationStateUpdate := types.DelegationStateUpdate{ State: state, ValAddr: valAddr.String(), diff --git a/x/epoching/keeper/lifecycle_validator.go b/x/epoching/keeper/lifecycle_validator.go index 07fc6b860..75f6250bf 100644 --- a/x/epoching/keeper/lifecycle_validator.go +++ b/x/epoching/keeper/lifecycle_validator.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -19,7 +20,7 @@ func (k Keeper) RecordNewValState(ctx sdk.Context, valAddr sdk.ValAddress, state ValLife: []*types.ValStateUpdate{}, } } - height, time := ctx.BlockHeight(), ctx.BlockTime() + height, time := ctx.HeaderInfo().Height, ctx.HeaderInfo().Time valStateUpdate := types.ValStateUpdate{ State: state, BlockHeight: uint64(height), diff --git a/x/epoching/keeper/modified_staking.go b/x/epoching/keeper/modified_staking.go index 23fd3134e..37a9a88ec 100644 --- a/x/epoching/keeper/modified_staking.go +++ b/x/epoching/keeper/modified_staking.go @@ -28,13 +28,16 @@ func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { if err != nil { panic(err) } - epochBoundaryHeader := finalizedEpoch.LastBlockHeader params, err := k.stk.GetParams(ctx) if err != nil { panic(err) } - epochBoundaryHeader.Time = epochBoundaryHeader.Time.Add(params.UnbondingTime) // nullifies the effect of UnbondingTime in staking module - ctx = sdkCtx.WithBlockHeader(*epochBoundaryHeader) + // nullifies the effect of UnbondingTime in staking module + // NOTE: we offset time in both Header and HeaderInfo for full compatibility + finalizedTime := finalizedEpoch.LastBlockTime.Add(params.UnbondingTime) + headerInfo := sdkCtx.HeaderInfo() + headerInfo.Time = finalizedTime + ctx = sdkCtx.WithBlockTime(finalizedTime).WithHeaderInfo(headerInfo) // unbond all mature validators till the last block of the given epoch matureValidators := k.getAllMatureValidators(sdkCtx) @@ -50,7 +53,7 @@ func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { } // get all mature unbonding delegations the epoch boundary from the ubd queue. - matureUnbonds, err := k.stk.DequeueAllMatureUBDQueue(ctx, epochBoundaryHeader.Time) + matureUnbonds, err := k.stk.DequeueAllMatureUBDQueue(ctx, finalizedTime) if err != nil { panic(err) } @@ -88,7 +91,7 @@ func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { } // get all mature redelegations till the epoch boundary from the red queue. - matureRedelegations, err := k.stk.DequeueAllMatureRedelegationQueue(ctx, epochBoundaryHeader.Time) + matureRedelegations, err := k.stk.DequeueAllMatureRedelegationQueue(ctx, finalizedTime) if err != nil { panic(err) } @@ -164,8 +167,8 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx context.Context) []abci.Va func (k Keeper) getAllMatureValidators(ctx sdk.Context) []sdk.ValAddress { matureValAddrs := []sdk.ValAddress{} - blockTime := ctx.BlockTime() - blockHeight := ctx.BlockHeight() + blockTime := ctx.HeaderInfo().Time + blockHeight := ctx.HeaderInfo().Height // unbondingValIterator will contains all validator addresses indexed under // the ValidatorQueueKey prefix. Note, the entire index key is composed as diff --git a/x/epoching/keeper/msg_server.go b/x/epoching/keeper/msg_server.go index 44890e382..e4b8deae8 100644 --- a/x/epoching/keeper/msg_server.go +++ b/x/epoching/keeper/msg_server.go @@ -48,11 +48,11 @@ func (ms msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrapped ) } - blockHeight := uint64(ctx.BlockHeader().Height) + blockHeight := uint64(ctx.HeaderInfo().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } - blockTime := ctx.BlockTime() + blockTime := ctx.HeaderInfo().Time txid := tmhash.Sum(ctx.TxBytes()) queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg) @@ -104,11 +104,11 @@ func (ms msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrapp ) } - blockHeight := uint64(ctx.BlockHeader().Height) + blockHeight := uint64(ctx.HeaderInfo().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } - blockTime := ctx.BlockTime() + blockTime := ctx.HeaderInfo().Time txid := tmhash.Sum(ctx.TxBytes()) queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg) @@ -163,11 +163,11 @@ func (ms msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.Msg return nil, err } - blockHeight := uint64(ctx.BlockHeader().Height) + blockHeight := uint64(ctx.HeaderInfo().Height) if blockHeight == 0 { return nil, types.ErrZeroEpochMsg } - blockTime := ctx.BlockTime() + blockTime := ctx.HeaderInfo().Time txid := tmhash.Sum(ctx.TxBytes()) queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg) diff --git a/x/epoching/keeper/staking_functions.go b/x/epoching/keeper/staking_functions.go index ec5e9d0b7..52e94be6a 100644 --- a/x/epoching/keeper/staking_functions.go +++ b/x/epoching/keeper/staking_functions.go @@ -2,6 +2,7 @@ package keeper import ( "context" + errorsmod "cosmossdk.io/errors" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -90,7 +91,7 @@ func (k Keeper) CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.M // check if SetInitialCommission fails or not commission := stakingtypes.NewCommissionWithTime( msg.Commission.Rate, msg.Commission.MaxRate, - msg.Commission.MaxChangeRate, sdkCtx.BlockHeader().Time, + msg.Commission.MaxChangeRate, sdkCtx.HeaderInfo().Time, ) if _, err := validator.SetInitialCommission(commission); err != nil { return err diff --git a/x/epoching/testepoching/helper.go b/x/epoching/testepoching/helper.go index 97c861b0c..bf9fae62b 100644 --- a/x/epoching/testepoching/helper.go +++ b/x/epoching/testepoching/helper.go @@ -65,7 +65,7 @@ func NewHelper(t *testing.T) *Helper { } app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, []authtypes.GenesisAccount{acc}, balance) - ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 + ctx := app.BaseApp.NewContext(false).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 epochingKeeper := app.EpochingKeeper @@ -106,7 +106,7 @@ func NewHelperWithValSet(t *testing.T) *Helper { // setup the app and ctx app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, GenAccs, balance) - ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 + ctx := app.BaseApp.NewContext(false).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 // get necessary subsets of the app/keeper epochingKeeper := app.EpochingKeeper diff --git a/x/epoching/types/epoching.go b/x/epoching/types/epoching.go index 1d416d32a..741c67612 100644 --- a/x/epoching/types/epoching.go +++ b/x/epoching/types/epoching.go @@ -10,7 +10,6 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cometbft/cometbft/crypto/tmhash" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -18,12 +17,12 @@ import ( // The relationship between block and epoch is as follows, assuming epoch interval of 5: // 0 | 1 2 3 4 5 | 6 7 8 9 10 | // 0 | 1 | 2 | -func NewEpoch(epochNumber uint64, epochInterval uint64, firstBlockHeight uint64, lastBlockHeader *tmproto.Header) Epoch { +func NewEpoch(epochNumber uint64, epochInterval uint64, firstBlockHeight uint64, lastBlockTime *time.Time) Epoch { return Epoch{ EpochNumber: epochNumber, CurrentEpochInterval: epochInterval, FirstBlockHeight: firstBlockHeight, - LastBlockHeader: lastBlockHeader, + LastBlockTime: lastBlockTime, // NOTE: SealerHeader will be set in the next epoch } } @@ -35,6 +34,10 @@ func (e Epoch) GetLastBlockHeight() uint64 { return e.FirstBlockHeight + e.CurrentEpochInterval - 1 } +func (e Epoch) GetSealerBlockHeight() uint64 { + return e.GetLastBlockHeight() + 2 +} + func (e Epoch) GetSecondBlockHeight() uint64 { if e.EpochNumber == 0 { panic("should not be called when epoch number is zero") @@ -43,18 +46,18 @@ func (e Epoch) GetSecondBlockHeight() uint64 { } func (e Epoch) IsLastBlock(ctx context.Context) bool { - return e.GetLastBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) + return e.GetLastBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } func (e Epoch) IsFirstBlock(ctx context.Context) bool { - return e.FirstBlockHeight == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) + return e.FirstBlockHeight == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } func (e Epoch) IsSecondBlock(ctx context.Context) bool { if e.EpochNumber == 0 { return false } - return e.GetSecondBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).BlockHeader().Height) + return e.GetSecondBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } // IsFirstBlockOfNextEpoch checks whether the current block is the first block of @@ -65,16 +68,16 @@ func (e Epoch) IsSecondBlock(ctx context.Context) bool { func (e Epoch) IsFirstBlockOfNextEpoch(ctx context.Context) bool { sdkCtx := sdk.UnwrapSDKContext(ctx) if e.EpochNumber == 0 { - return sdkCtx.BlockHeader().Height == 1 + return sdkCtx.HeaderInfo().Height == 1 } else { - height := uint64(sdkCtx.BlockHeader().Height) + height := uint64(sdkCtx.HeaderInfo().Height) return e.FirstBlockHeight+e.CurrentEpochInterval == height } } // WithinBoundary checks whether the given height is within this epoch or not func (e Epoch) WithinBoundary(height uint64) bool { - if height < e.FirstBlockHeight || height > uint64(e.LastBlockHeader.Height) { + if height < e.FirstBlockHeight || height > uint64(e.GetLastBlockHeight()) { return false } else { return true diff --git a/x/epoching/types/epoching.pb.go b/x/epoching/types/epoching.pb.go index a09931f41..10e17c4dd 100644 --- a/x/epoching/types/epoching.pb.go +++ b/x/epoching/types/epoching.pb.go @@ -5,8 +5,8 @@ package types import ( fmt "fmt" - types "github.com/cometbft/cometbft/proto/tendermint/types" - types1 "github.com/cosmos/cosmos-sdk/x/staking/types" + _ "github.com/cometbft/cometbft/proto/tendermint/types" + types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" @@ -74,19 +74,20 @@ type Epoch struct { EpochNumber uint64 `protobuf:"varint,1,opt,name=epoch_number,json=epochNumber,proto3" json:"epoch_number,omitempty"` CurrentEpochInterval uint64 `protobuf:"varint,2,opt,name=current_epoch_interval,json=currentEpochInterval,proto3" json:"current_epoch_interval,omitempty"` FirstBlockHeight uint64 `protobuf:"varint,3,opt,name=first_block_height,json=firstBlockHeight,proto3" json:"first_block_height,omitempty"` - // last_block_header is the header of the last block in this epoch. - // Babylon needs to remember the last header of each epoch to complete + // last_block_time is the time of the last block in this epoch. + // Babylon needs to remember the last header's time of each epoch to complete // unbonding validators/delegations when a previous epoch's checkpoint is - // finalised. The last_block_header field is nil in the epoch's beginning, and + // finalised. The last_block_time field is nil in the epoch's beginning, and // is set upon the end of this epoch. - LastBlockHeader *types.Header `protobuf:"bytes,4,opt,name=last_block_header,json=lastBlockHeader,proto3" json:"last_block_header,omitempty"` + LastBlockTime *time.Time `protobuf:"bytes,4,opt,name=last_block_time,json=lastBlockTime,proto3,stdtime" json:"last_block_time,omitempty"` // app_hash_root is the Merkle root of all AppHashs in this epoch // It will be used for proving a block is in an epoch AppHashRoot []byte `protobuf:"bytes,5,opt,name=app_hash_root,json=appHashRoot,proto3" json:"app_hash_root,omitempty"` - // sealer_header is the 2nd header of the next epoch + // sealer_header_hash is the app_hash of the sealer header, i.e., 2nd header + // of the next epoch // This validator set has generated a BLS multisig on `app_hash` of // the sealer header - SealerHeader *types.Header `protobuf:"bytes,6,opt,name=sealer_header,json=sealerHeader,proto3" json:"sealer_header,omitempty"` + SealerHeaderHash []byte `protobuf:"bytes,6,opt,name=sealer_header_hash,json=sealerHeaderHash,proto3" json:"sealer_header_hash,omitempty"` } func (m *Epoch) Reset() { *m = Epoch{} } @@ -143,9 +144,9 @@ func (m *Epoch) GetFirstBlockHeight() uint64 { return 0 } -func (m *Epoch) GetLastBlockHeader() *types.Header { +func (m *Epoch) GetLastBlockTime() *time.Time { if m != nil { - return m.LastBlockHeader + return m.LastBlockTime } return nil } @@ -157,9 +158,9 @@ func (m *Epoch) GetAppHashRoot() []byte { return nil } -func (m *Epoch) GetSealerHeader() *types.Header { +func (m *Epoch) GetSealerHeaderHash() []byte { if m != nil { - return m.SealerHeader + return m.SealerHeaderHash } return nil } @@ -226,16 +227,16 @@ type isQueuedMessage_Msg interface { } type QueuedMessage_MsgCreateValidator struct { - MsgCreateValidator *types1.MsgCreateValidator `protobuf:"bytes,5,opt,name=msg_create_validator,json=msgCreateValidator,proto3,oneof" json:"msg_create_validator,omitempty"` + MsgCreateValidator *types.MsgCreateValidator `protobuf:"bytes,5,opt,name=msg_create_validator,json=msgCreateValidator,proto3,oneof" json:"msg_create_validator,omitempty"` } type QueuedMessage_MsgDelegate struct { - MsgDelegate *types1.MsgDelegate `protobuf:"bytes,6,opt,name=msg_delegate,json=msgDelegate,proto3,oneof" json:"msg_delegate,omitempty"` + MsgDelegate *types.MsgDelegate `protobuf:"bytes,6,opt,name=msg_delegate,json=msgDelegate,proto3,oneof" json:"msg_delegate,omitempty"` } type QueuedMessage_MsgUndelegate struct { - MsgUndelegate *types1.MsgUndelegate `protobuf:"bytes,7,opt,name=msg_undelegate,json=msgUndelegate,proto3,oneof" json:"msg_undelegate,omitempty"` + MsgUndelegate *types.MsgUndelegate `protobuf:"bytes,7,opt,name=msg_undelegate,json=msgUndelegate,proto3,oneof" json:"msg_undelegate,omitempty"` } type QueuedMessage_MsgBeginRedelegate struct { - MsgBeginRedelegate *types1.MsgBeginRedelegate `protobuf:"bytes,8,opt,name=msg_begin_redelegate,json=msgBeginRedelegate,proto3,oneof" json:"msg_begin_redelegate,omitempty"` + MsgBeginRedelegate *types.MsgBeginRedelegate `protobuf:"bytes,8,opt,name=msg_begin_redelegate,json=msgBeginRedelegate,proto3,oneof" json:"msg_begin_redelegate,omitempty"` } func (*QueuedMessage_MsgCreateValidator) isQueuedMessage_Msg() {} @@ -278,28 +279,28 @@ func (m *QueuedMessage) GetBlockTime() *time.Time { return nil } -func (m *QueuedMessage) GetMsgCreateValidator() *types1.MsgCreateValidator { +func (m *QueuedMessage) GetMsgCreateValidator() *types.MsgCreateValidator { if x, ok := m.GetMsg().(*QueuedMessage_MsgCreateValidator); ok { return x.MsgCreateValidator } return nil } -func (m *QueuedMessage) GetMsgDelegate() *types1.MsgDelegate { +func (m *QueuedMessage) GetMsgDelegate() *types.MsgDelegate { if x, ok := m.GetMsg().(*QueuedMessage_MsgDelegate); ok { return x.MsgDelegate } return nil } -func (m *QueuedMessage) GetMsgUndelegate() *types1.MsgUndelegate { +func (m *QueuedMessage) GetMsgUndelegate() *types.MsgUndelegate { if x, ok := m.GetMsg().(*QueuedMessage_MsgUndelegate); ok { return x.MsgUndelegate } return nil } -func (m *QueuedMessage) GetMsgBeginRedelegate() *types1.MsgBeginRedelegate { +func (m *QueuedMessage) GetMsgBeginRedelegate() *types.MsgBeginRedelegate { if x, ok := m.GetMsg().(*QueuedMessage_MsgBeginRedelegate); ok { return x.MsgBeginRedelegate } @@ -681,61 +682,61 @@ func init() { } var fileDescriptor_2f2f209d5311f84c = []byte{ - // 864 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0x8e, 0xf3, 0xd3, 0x36, 0x27, 0x49, 0xc9, 0x4e, 0xbb, 0x28, 0x54, 0x28, 0x2d, 0x5e, 0x21, - 0x55, 0x15, 0xb2, 0x69, 0x59, 0xb8, 0x03, 0xb4, 0xd9, 0x44, 0xa4, 0x68, 0x9b, 0x15, 0x66, 0xdb, - 0x0b, 0x2e, 0xb0, 0xc6, 0xf6, 0xa9, 0x63, 0xad, 0xed, 0xb1, 0x3c, 0x93, 0x6c, 0x7a, 0xc7, 0x23, - 0xec, 0x73, 0xf0, 0x02, 0xbc, 0x02, 0x17, 0x5c, 0xec, 0x25, 0x77, 0xa0, 0xf6, 0x45, 0xd0, 0xcc, - 0x38, 0x7f, 0x6c, 0xb4, 0x05, 0x21, 0x71, 0x13, 0xcd, 0x39, 0xe7, 0x3b, 0xdf, 0x9c, 0xf3, 0xe9, - 0x1b, 0x07, 0x4c, 0x8f, 0x7a, 0x37, 0x31, 0x4b, 0x6d, 0xcc, 0x98, 0x3f, 0x8e, 0xd2, 0xd0, 0x9e, - 0x9e, 0x2e, 0xce, 0x56, 0x96, 0x33, 0xc1, 0xc8, 0x5e, 0x81, 0xb1, 0x16, 0xf9, 0xe9, 0xe9, 0xc1, - 0x61, 0xc8, 0x58, 0x18, 0xa3, 0xad, 0x20, 0xde, 0xe4, 0xda, 0x16, 0x51, 0x82, 0x5c, 0xd0, 0x24, - 0xd3, 0x5d, 0x07, 0xfb, 0x21, 0x0b, 0x99, 0x3a, 0xda, 0xf2, 0x54, 0x64, 0x3f, 0x14, 0x98, 0x06, - 0x98, 0x27, 0x51, 0x2a, 0x6c, 0x71, 0x93, 0x21, 0xd7, 0xbf, 0x45, 0xf5, 0xd0, 0x67, 0x3c, 0x61, - 0xdc, 0xe6, 0x82, 0xbe, 0xd4, 0xb3, 0x78, 0x28, 0xe8, 0xa9, 0x2d, 0x66, 0x1a, 0x60, 0xfe, 0x52, - 0x86, 0xda, 0x40, 0x4e, 0x41, 0x3e, 0x82, 0xa6, 0x1a, 0xc7, 0x4d, 0x27, 0x89, 0x87, 0x79, 0xc7, - 0x38, 0x32, 0x8e, 0xab, 0x4e, 0x43, 0xe5, 0x46, 0x2a, 0x45, 0x1e, 0xc3, 0xfb, 0xfe, 0x24, 0xcf, - 0x31, 0x15, 0xae, 0x86, 0x46, 0xa9, 0xc0, 0x7c, 0x4a, 0xe3, 0x4e, 0x59, 0x81, 0xf7, 0x8b, 0xaa, - 0x22, 0x3c, 0x2f, 0x6a, 0xe4, 0x13, 0x20, 0xd7, 0x51, 0xce, 0x85, 0xeb, 0xc5, 0xcc, 0x7f, 0xe9, - 0x8e, 0x31, 0x0a, 0xc7, 0xa2, 0x53, 0x51, 0x1d, 0x6d, 0x55, 0xe9, 0xc9, 0xc2, 0x50, 0xe5, 0x49, - 0x1f, 0x1e, 0xc4, 0x74, 0x05, 0x4c, 0x03, 0xcc, 0x3b, 0xd5, 0x23, 0xe3, 0xb8, 0x71, 0xd6, 0xb1, - 0x96, 0xbb, 0x5a, 0x7a, 0xcb, 0xa1, 0xaa, 0x3b, 0xef, 0xc9, 0x96, 0x82, 0x45, 0x26, 0x88, 0x09, - 0x2d, 0x9a, 0x65, 0xee, 0x98, 0xf2, 0xb1, 0x9b, 0x33, 0x26, 0x3a, 0xb5, 0x23, 0xe3, 0xb8, 0xe9, - 0x34, 0x68, 0x96, 0x0d, 0x29, 0x1f, 0x3b, 0x8c, 0x09, 0xf2, 0x25, 0xb4, 0x38, 0xd2, 0x18, 0xf3, - 0xf9, 0x2d, 0x5b, 0xf7, 0xdc, 0xd2, 0xd4, 0x70, 0x1d, 0x99, 0x3f, 0x55, 0xa1, 0xf5, 0xdd, 0x04, - 0x27, 0x18, 0x5c, 0x20, 0xe7, 0x34, 0x44, 0xb2, 0x07, 0x35, 0x31, 0x73, 0xa3, 0x40, 0x49, 0xd7, - 0x74, 0xaa, 0x62, 0x76, 0x1e, 0x90, 0x87, 0xb0, 0x95, 0xf0, 0x50, 0x66, 0xcb, 0x2a, 0x5b, 0x4b, - 0x78, 0x78, 0x1e, 0x48, 0xb5, 0x37, 0xc8, 0xd1, 0xf0, 0x56, 0x94, 0xf8, 0x1a, 0x40, 0x43, 0xa4, - 0x11, 0x0a, 0x09, 0x0e, 0x2c, 0xed, 0x12, 0x6b, 0xee, 0x12, 0xeb, 0xc5, 0xdc, 0x25, 0xbd, 0xea, - 0xeb, 0x3f, 0x0e, 0x0d, 0xa7, 0xae, 0x7a, 0x64, 0x96, 0xfc, 0x08, 0xfb, 0xf2, 0x6a, 0x3f, 0x47, - 0x2a, 0xd0, 0x9d, 0xd2, 0x38, 0x0a, 0xa8, 0x60, 0xb9, 0xd2, 0xa2, 0x71, 0x76, 0x62, 0x69, 0x6f, - 0x58, 0x85, 0x37, 0xac, 0xc2, 0x1b, 0xd6, 0x05, 0x0f, 0x9f, 0xaa, 0x96, 0xab, 0x79, 0xc7, 0xb0, - 0xe4, 0x90, 0xe4, 0xad, 0x2c, 0x19, 0x42, 0x53, 0xf2, 0x07, 0x18, 0x63, 0x48, 0x05, 0x16, 0xfa, - 0x3d, 0x7a, 0x07, 0x6f, 0xbf, 0x80, 0x0e, 0x4b, 0x4e, 0x23, 0x59, 0x86, 0x64, 0x04, 0xbb, 0x92, - 0x69, 0x92, 0x2e, 0xb8, 0xb6, 0x15, 0xd7, 0xc7, 0xef, 0xe0, 0xba, 0x5c, 0x80, 0x87, 0x25, 0xa7, - 0x95, 0xac, 0x26, 0xe6, 0x9b, 0x7b, 0x18, 0x46, 0xa9, 0x9b, 0xe3, 0x82, 0x75, 0xe7, 0xde, 0xcd, - 0x7b, 0xb2, 0xc5, 0xc1, 0x15, 0x6a, 0xb9, 0xf9, 0xdf, 0xb2, 0xbd, 0x1a, 0x54, 0x12, 0x1e, 0x9a, - 0x29, 0x3c, 0x58, 0x73, 0xc0, 0xb3, 0x88, 0x8b, 0x7f, 0xf2, 0x8e, 0xbe, 0x80, 0x6a, 0xc2, 0x43, - 0xde, 0x29, 0x1f, 0x55, 0x8e, 0x1b, 0x67, 0xa6, 0xb5, 0xe1, 0x73, 0x60, 0xad, 0x11, 0x3b, 0x0a, - 0x6f, 0xfe, 0x6c, 0xc0, 0xee, 0x15, 0x8d, 0xbf, 0x17, 0x54, 0xe0, 0x65, 0x16, 0xc8, 0x4d, 0x1f, - 0x43, 0x8d, 0xcb, 0x50, 0x5d, 0xb3, 0x7b, 0xd6, 0xdd, 0xc8, 0xd5, 0x63, 0x69, 0xa0, 0x9a, 0x1c, - 0x0d, 0x7e, 0xcb, 0x7d, 0xe5, 0xfb, 0xdc, 0x57, 0xf9, 0xd7, 0xee, 0x33, 0x19, 0x90, 0x85, 0x55, - 0x9e, 0x45, 0xd7, 0xe8, 0xdf, 0xf8, 0x31, 0x92, 0x0f, 0x60, 0x67, 0x4a, 0x63, 0x97, 0x06, 0x81, - 0x56, 0xa6, 0xee, 0x6c, 0x4f, 0x69, 0xfc, 0x24, 0x08, 0x72, 0xf2, 0x95, 0x2e, 0xc5, 0xd1, 0x35, - 0x16, 0xca, 0x3c, 0xda, 0xb8, 0xcd, 0xba, 0x02, 0xaa, 0x5f, 0xf2, 0x9b, 0xbf, 0x19, 0xf0, 0xb0, - 0x70, 0x54, 0xc4, 0xd2, 0xff, 0x2e, 0xd2, 0xea, 0xa8, 0xe5, 0xf5, 0x51, 0xff, 0x87, 0xd7, 0x6b, - 0xbe, 0x82, 0xbd, 0xe5, 0x36, 0x6b, 0x02, 0x06, 0xb8, 0x2e, 0x60, 0x80, 0x7a, 0xaa, 0x81, 0x2e, - 0xad, 0x08, 0x78, 0xb2, 0x71, 0xd3, 0x8d, 0x22, 0x29, 0x1a, 0xa5, 0xe3, 0xe7, 0x50, 0x5f, 0xbe, - 0x71, 0x02, 0xd5, 0xc5, 0x55, 0x4d, 0x47, 0x9d, 0xc9, 0x3e, 0xd4, 0x32, 0xf6, 0x0a, 0xb5, 0x2a, - 0x15, 0x47, 0x07, 0x27, 0x23, 0xa8, 0x2f, 0x24, 0x24, 0x0d, 0xd8, 0x7e, 0xea, 0x0c, 0x9e, 0xbc, - 0x18, 0xf4, 0xdb, 0x25, 0x02, 0xb0, 0xd5, 0x7b, 0x3e, 0xea, 0x0f, 0xfa, 0x6d, 0x83, 0xb4, 0xa0, - 0x7e, 0x39, 0x92, 0xd1, 0xf9, 0xe8, 0x9b, 0x76, 0x99, 0x34, 0x61, 0x47, 0x87, 0x83, 0x7e, 0xbb, - 0x22, 0xbb, 0x9c, 0xc1, 0xc5, 0xf3, 0xab, 0x41, 0xbf, 0x5d, 0xed, 0x7d, 0xfb, 0xeb, 0x6d, 0xd7, - 0x78, 0x73, 0xdb, 0x35, 0xfe, 0xbc, 0xed, 0x1a, 0xaf, 0xef, 0xba, 0xa5, 0x37, 0x77, 0xdd, 0xd2, - 0xef, 0x77, 0xdd, 0xd2, 0x0f, 0x9f, 0x86, 0x91, 0x18, 0x4f, 0x3c, 0xcb, 0x67, 0x89, 0x5d, 0xec, - 0xe7, 0x8f, 0x69, 0x94, 0xce, 0x03, 0x7b, 0xb6, 0xfc, 0xf3, 0x55, 0x1f, 0x70, 0x6f, 0x4b, 0x09, - 0xfe, 0xd9, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xfb, 0xf0, 0x87, 0xbf, 0x9d, 0x07, 0x00, 0x00, + // 856 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xcf, 0x6e, 0xdb, 0xc6, + 0x13, 0x16, 0xf5, 0xc7, 0xb6, 0x86, 0x92, 0x7f, 0xfa, 0xad, 0x9d, 0x42, 0x35, 0x0a, 0xd9, 0x65, + 0x50, 0xc0, 0x30, 0x0a, 0xb2, 0x56, 0xd3, 0x1e, 0x5b, 0x44, 0x91, 0x50, 0xb9, 0x88, 0x15, 0x94, + 0x8d, 0x7d, 0xe8, 0xa1, 0xc4, 0x92, 0x1c, 0x93, 0x44, 0x48, 0x2e, 0xc1, 0x5d, 0x29, 0xf6, 0xad, + 0x8f, 0x90, 0xe7, 0xc8, 0x93, 0xf4, 0xd0, 0x43, 0x8e, 0xbd, 0xb5, 0xb0, 0x5f, 0xa4, 0xd8, 0x25, + 0x45, 0x4b, 0x8d, 0x90, 0xf4, 0x0f, 0xd0, 0x8b, 0xbd, 0x33, 0xf3, 0xcd, 0xb7, 0xf3, 0x7d, 0x98, + 0x15, 0xc1, 0x70, 0xa9, 0x7b, 0x13, 0xb3, 0xd4, 0xc2, 0x8c, 0x79, 0x61, 0x94, 0x06, 0xd6, 0xe2, + 0xb4, 0x3a, 0x9b, 0x59, 0xce, 0x04, 0x23, 0x7b, 0x25, 0xc6, 0xac, 0xf2, 0x8b, 0xd3, 0x83, 0xc3, + 0x80, 0xb1, 0x20, 0x46, 0x4b, 0x41, 0xdc, 0xf9, 0x95, 0x25, 0xa2, 0x04, 0xb9, 0xa0, 0x49, 0x56, + 0x74, 0x1d, 0xec, 0x07, 0x2c, 0x60, 0xea, 0x68, 0xc9, 0x53, 0x99, 0xfd, 0x48, 0x60, 0xea, 0x63, + 0x9e, 0x44, 0xa9, 0xb0, 0xc4, 0x4d, 0x86, 0xbc, 0xf8, 0x5b, 0x56, 0x0f, 0x3d, 0xc6, 0x13, 0xc6, + 0x2d, 0x2e, 0xe8, 0x8b, 0x62, 0x16, 0x17, 0x05, 0x3d, 0xb5, 0xc4, 0x75, 0x01, 0x30, 0x5e, 0xd7, + 0xa1, 0x35, 0x91, 0x53, 0x90, 0x8f, 0xa1, 0xa3, 0xc6, 0x71, 0xd2, 0x79, 0xe2, 0x62, 0xde, 0xd7, + 0x8e, 0xb4, 0xe3, 0xa6, 0xad, 0xab, 0xdc, 0x4c, 0xa5, 0xc8, 0x23, 0xf8, 0xc0, 0x9b, 0xe7, 0x39, + 0xa6, 0xc2, 0x29, 0xa0, 0x51, 0x2a, 0x30, 0x5f, 0xd0, 0xb8, 0x5f, 0x57, 0xe0, 0xfd, 0xb2, 0xaa, + 0x08, 0xcf, 0xca, 0x1a, 0xf9, 0x14, 0xc8, 0x55, 0x94, 0x73, 0xe1, 0xb8, 0x31, 0xf3, 0x5e, 0x38, + 0x21, 0x46, 0x41, 0x28, 0xfa, 0x0d, 0xd5, 0xd1, 0x53, 0x95, 0x91, 0x2c, 0x4c, 0x55, 0x9e, 0x4c, + 0xe1, 0x7f, 0x31, 0xad, 0xc0, 0xd2, 0x83, 0x7e, 0xf3, 0x48, 0x3b, 0xd6, 0x87, 0x07, 0x66, 0x61, + 0x90, 0xb9, 0x34, 0xc8, 0x7c, 0xbe, 0x34, 0x68, 0xd4, 0x7c, 0xf5, 0xdb, 0xa1, 0x66, 0x77, 0x65, + 0xa3, 0xe2, 0x92, 0x15, 0x62, 0x40, 0x97, 0x66, 0x99, 0x13, 0x52, 0x1e, 0x3a, 0x39, 0x63, 0xa2, + 0xdf, 0x3a, 0xd2, 0x8e, 0x3b, 0xb6, 0x4e, 0xb3, 0x6c, 0x4a, 0x79, 0x68, 0x33, 0x26, 0xe4, 0x6c, + 0x1c, 0x69, 0x8c, 0xb9, 0x13, 0x22, 0xf5, 0xe5, 0x3f, 0xca, 0xc3, 0xfe, 0x96, 0x02, 0xf6, 0x8a, + 0xca, 0x54, 0x15, 0x64, 0x87, 0xf1, 0x53, 0x13, 0xba, 0xdf, 0xcd, 0x71, 0x8e, 0xfe, 0x39, 0x72, + 0x4e, 0x03, 0x24, 0x7b, 0xd0, 0x12, 0xd7, 0x4e, 0xe4, 0x2b, 0xb7, 0x3a, 0x76, 0x53, 0x5c, 0x9f, + 0xf9, 0xe4, 0x01, 0x6c, 0x25, 0x3c, 0x90, 0xd9, 0xba, 0xca, 0xb6, 0x12, 0x1e, 0x9c, 0xf9, 0xd2, + 0xe0, 0x0d, 0x0e, 0xe8, 0xee, 0x8a, 0xf8, 0xaf, 0x01, 0xfe, 0x81, 0xee, 0xb6, 0x5b, 0x69, 0xfe, + 0x11, 0xf6, 0xe5, 0xd5, 0x5e, 0x8e, 0x54, 0xa0, 0xb3, 0xa0, 0x71, 0xe4, 0x53, 0xc1, 0x72, 0x25, + 0x5d, 0x1f, 0x9e, 0x98, 0xc5, 0x3a, 0x98, 0xe5, 0x3a, 0x98, 0xe5, 0x3a, 0x98, 0xe7, 0x3c, 0x78, + 0xa2, 0x5a, 0x2e, 0x97, 0x1d, 0xd3, 0x9a, 0x4d, 0x92, 0xb7, 0xb2, 0x64, 0x0a, 0x1d, 0xc9, 0xef, + 0x63, 0x8c, 0x01, 0x15, 0xa8, 0x9c, 0xd2, 0x87, 0x0f, 0xdf, 0xc1, 0x3b, 0x2e, 0xa1, 0xd3, 0x9a, + 0xad, 0x27, 0xf7, 0x21, 0x99, 0xc1, 0xae, 0x64, 0x9a, 0xa7, 0x15, 0xd7, 0xb6, 0xe2, 0xfa, 0xe4, + 0x1d, 0x5c, 0x17, 0x15, 0x78, 0x5a, 0xb3, 0xbb, 0xc9, 0x6a, 0x62, 0xa9, 0xdc, 0xc5, 0x20, 0x4a, + 0x9d, 0x1c, 0x2b, 0xd6, 0x9d, 0xf7, 0x2a, 0x1f, 0xc9, 0x16, 0x1b, 0x57, 0xa8, 0xa5, 0xf2, 0x3f, + 0x65, 0x47, 0x2d, 0x68, 0x24, 0x3c, 0x30, 0x52, 0xf8, 0xff, 0xda, 0x06, 0x3c, 0x8d, 0xb8, 0xf8, + 0x2b, 0x4f, 0xe7, 0x4b, 0x68, 0x26, 0x3c, 0xe0, 0xfd, 0xfa, 0x51, 0xe3, 0x58, 0x1f, 0x1a, 0xe6, + 0x86, 0x5f, 0x00, 0x73, 0x8d, 0xd8, 0x56, 0x78, 0xe3, 0xb5, 0x06, 0xbb, 0x97, 0x34, 0xfe, 0x5e, + 0x50, 0x81, 0x17, 0x99, 0x2f, 0x95, 0x3e, 0x82, 0x16, 0x97, 0xa1, 0xba, 0x66, 0x77, 0x38, 0xd8, + 0xc8, 0x35, 0x62, 0xa9, 0xaf, 0x9a, 0xec, 0x02, 0xfc, 0xd6, 0xf6, 0xd5, 0xdf, 0xb7, 0x7d, 0x8d, + 0xbf, 0xbd, 0x7d, 0x06, 0x03, 0x52, 0xad, 0xca, 0xd3, 0xe8, 0x0a, 0xbd, 0x1b, 0x2f, 0x46, 0xf2, + 0x21, 0xec, 0x2c, 0x68, 0xec, 0x50, 0xdf, 0x2f, 0x9c, 0x69, 0xdb, 0xdb, 0x0b, 0x1a, 0x3f, 0xf6, + 0xfd, 0x9c, 0x7c, 0x55, 0x94, 0xe2, 0xe8, 0x0a, 0x4b, 0x67, 0x1e, 0x6e, 0x54, 0xb3, 0xee, 0x80, + 0xea, 0x97, 0xfc, 0xc6, 0x2f, 0x1a, 0x3c, 0x28, 0x37, 0x2a, 0x62, 0xe9, 0xbf, 0x37, 0x69, 0x75, + 0xd4, 0xfa, 0xfa, 0xa8, 0xff, 0xc1, 0xeb, 0x35, 0x5e, 0xc2, 0xde, 0xbd, 0x9a, 0x35, 0x03, 0x7d, + 0x5c, 0x37, 0xd0, 0xc7, 0x62, 0xaa, 0x49, 0x51, 0x5a, 0x31, 0xf0, 0x64, 0xa3, 0xd2, 0x8d, 0x26, + 0x29, 0x1a, 0xe5, 0xe3, 0x17, 0xd0, 0xbe, 0x7f, 0xe3, 0x04, 0x9a, 0xd5, 0x55, 0x1d, 0x5b, 0x9d, + 0xc9, 0x3e, 0xb4, 0x32, 0xf6, 0x12, 0x0b, 0x57, 0x1a, 0x76, 0x11, 0x9c, 0xcc, 0xa0, 0x5d, 0x59, + 0x48, 0x74, 0xd8, 0x7e, 0x62, 0x4f, 0x1e, 0x3f, 0x9f, 0x8c, 0x7b, 0x35, 0x02, 0xb0, 0x35, 0x7a, + 0x36, 0x1b, 0x4f, 0xc6, 0x3d, 0x8d, 0x74, 0xa1, 0x7d, 0x31, 0x93, 0xd1, 0xd9, 0xec, 0x9b, 0x5e, + 0x9d, 0x74, 0x60, 0xa7, 0x08, 0x27, 0xe3, 0x5e, 0x43, 0x76, 0xd9, 0x93, 0xf3, 0x67, 0x97, 0x93, + 0x71, 0xaf, 0x39, 0xfa, 0xf6, 0xe7, 0xdb, 0x81, 0xf6, 0xe6, 0x76, 0xa0, 0xfd, 0x7e, 0x3b, 0xd0, + 0x5e, 0xdd, 0x0d, 0x6a, 0x6f, 0xee, 0x06, 0xb5, 0x5f, 0xef, 0x06, 0xb5, 0x1f, 0x3e, 0x0b, 0x22, + 0x11, 0xce, 0x5d, 0xd3, 0x63, 0x89, 0x55, 0xea, 0xf3, 0x42, 0x1a, 0xa5, 0xcb, 0xc0, 0xba, 0xbe, + 0xff, 0xde, 0xaa, 0xef, 0x9f, 0xbb, 0xa5, 0x0c, 0xff, 0xfc, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xb2, 0x50, 0x99, 0x42, 0x90, 0x07, 0x00, 0x00, } func (m *Epoch) Marshal() (dAtA []byte, err error) { @@ -758,15 +759,10 @@ func (m *Epoch) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.SealerHeader != nil { - { - size, err := m.SealerHeader.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEpoching(dAtA, i, uint64(size)) - } + if len(m.SealerHeaderHash) > 0 { + i -= len(m.SealerHeaderHash) + copy(dAtA[i:], m.SealerHeaderHash) + i = encodeVarintEpoching(dAtA, i, uint64(len(m.SealerHeaderHash))) i-- dAtA[i] = 0x32 } @@ -777,15 +773,13 @@ func (m *Epoch) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x2a } - if m.LastBlockHeader != nil { - { - size, err := m.LastBlockHeader.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintEpoching(dAtA, i, uint64(size)) + if m.LastBlockTime != nil { + n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.LastBlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.LastBlockTime):]) + if err1 != nil { + return 0, err1 } + i -= n1 + i = encodeVarintEpoching(dAtA, i, uint64(n1)) i-- dAtA[i] = 0x22 } @@ -837,12 +831,12 @@ func (m *QueuedMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { } } if m.BlockTime != nil { - n3, err3 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err3 != nil { - return 0, err3 + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err2 != nil { + return 0, err2 } - i -= n3 - i = encodeVarintEpoching(dAtA, i, uint64(n3)) + i -= n2 + i = encodeVarintEpoching(dAtA, i, uint64(n2)) i-- dAtA[i] = 0x22 } @@ -1015,12 +1009,12 @@ func (m *ValStateUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.BlockTime != nil { - n8, err8 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err8 != nil { - return 0, err8 + n7, err7 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err7 != nil { + return 0, err7 } - i -= n8 - i = encodeVarintEpoching(dAtA, i, uint64(n8)) + i -= n7 + i = encodeVarintEpoching(dAtA, i, uint64(n7)) i-- dAtA[i] = 0x1a } @@ -1102,12 +1096,12 @@ func (m *DelegationStateUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.BlockTime != nil { - n9, err9 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err9 != nil { - return 0, err9 + n8, err8 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err8 != nil { + return 0, err8 } - i -= n9 - i = encodeVarintEpoching(dAtA, i, uint64(n9)) + i -= n8 + i = encodeVarintEpoching(dAtA, i, uint64(n8)) i-- dAtA[i] = 0x22 } @@ -1236,16 +1230,16 @@ func (m *Epoch) Size() (n int) { if m.FirstBlockHeight != 0 { n += 1 + sovEpoching(uint64(m.FirstBlockHeight)) } - if m.LastBlockHeader != nil { - l = m.LastBlockHeader.Size() + if m.LastBlockTime != nil { + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.LastBlockTime) n += 1 + l + sovEpoching(uint64(l)) } l = len(m.AppHashRoot) if l > 0 { n += 1 + l + sovEpoching(uint64(l)) } - if m.SealerHeader != nil { - l = m.SealerHeader.Size() + l = len(m.SealerHeaderHash) + if l > 0 { n += 1 + l + sovEpoching(uint64(l)) } return n @@ -1534,7 +1528,7 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { } case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastBlockHeader", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LastBlockTime", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1561,10 +1555,10 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.LastBlockHeader == nil { - m.LastBlockHeader = &types.Header{} + if m.LastBlockTime == nil { + m.LastBlockTime = new(time.Time) } - if err := m.LastBlockHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(m.LastBlockTime, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1604,9 +1598,9 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SealerHeader", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SealerHeaderHash", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEpoching @@ -1616,26 +1610,24 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthEpoching } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthEpoching } if postIndex > l { return io.ErrUnexpectedEOF } - if m.SealerHeader == nil { - m.SealerHeader = &types.Header{} - } - if err := m.SealerHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.SealerHeaderHash = append(m.SealerHeaderHash[:0], dAtA[iNdEx:postIndex]...) + if m.SealerHeaderHash == nil { + m.SealerHeaderHash = []byte{} } iNdEx = postIndex default: @@ -1840,7 +1832,7 @@ func (m *QueuedMessage) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &types1.MsgCreateValidator{} + v := &types.MsgCreateValidator{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1875,7 +1867,7 @@ func (m *QueuedMessage) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &types1.MsgDelegate{} + v := &types.MsgDelegate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1910,7 +1902,7 @@ func (m *QueuedMessage) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &types1.MsgUndelegate{} + v := &types.MsgUndelegate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1945,7 +1937,7 @@ func (m *QueuedMessage) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &types1.MsgBeginRedelegate{} + v := &types.MsgBeginRedelegate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/finality/keeper/evidence.go b/x/finality/keeper/evidence.go index bae60ed9b..73785a55f 100644 --- a/x/finality/keeper/evidence.go +++ b/x/finality/keeper/evidence.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/finality/types" @@ -20,7 +21,7 @@ func (k Keeper) HasEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, hei } func (k Keeper) GetEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { - if uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) < height { + if uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) < height { return nil, types.ErrHeightTooHigh } store := k.evidenceStore(ctx, valBtcPK) diff --git a/x/finality/keeper/indexed_blocks.go b/x/finality/keeper/indexed_blocks.go index 62b3ba7e1..fd7fc0771 100644 --- a/x/finality/keeper/indexed_blocks.go +++ b/x/finality/keeper/indexed_blocks.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" "github.com/babylonchain/babylon/x/finality/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -11,10 +12,10 @@ import ( // IndexBlock indexes the current block, saves the corresponding indexed block // to KVStore func (k Keeper) IndexBlock(ctx context.Context) { - header := sdk.UnwrapSDKContext(ctx).BlockHeader() + headerInfo := sdk.UnwrapSDKContext(ctx).HeaderInfo() ib := &types.IndexedBlock{ - Height: uint64(header.Height), - AppHash: header.AppHash, + Height: uint64(headerInfo.Height), + AppHash: headerInfo.AppHash, Finalized: false, } k.SetBlock(ctx, ib) diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index 659e04b37..c174552b5 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -5,13 +5,13 @@ import ( "math/rand" "testing" + "cosmossdk.io/core/header" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/finality/keeper" "github.com/babylonchain/babylon/x/finality/types" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -156,7 +156,7 @@ func FuzzAddFinalitySig(f *testing.F) { // Case 3: successful if the BTC validator has voting power and has not casted this vote yet // index this block first - ctx = ctx.WithBlockHeader(tmproto.Header{Height: int64(blockHeight), AppHash: blockHash}) + ctx = ctx.WithHeaderInfo(header.Info{Height: int64(blockHeight), AppHash: blockHash}) fKeeper.IndexBlock(ctx) bsKeeper.EXPECT().GetBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(btcVal, nil).Times(1) // add vote and it should work diff --git a/x/finality/keeper/tallying.go b/x/finality/keeper/tallying.go index 9283fe30a..838a34a3e 100644 --- a/x/finality/keeper/tallying.go +++ b/x/finality/keeper/tallying.go @@ -22,7 +22,7 @@ func (k Keeper) TallyBlocks(ctx context.Context) { if err != nil { // invoking TallyBlocks when BTC staking protocol is not activated is a programming error panic(fmt.Errorf("cannot tally a block when the BTC staking protocol hasn't been activated yet, current height: %v, activated height: %v", - sdkCtx.BlockHeight(), activatedHeight)) + sdkCtx.HeaderInfo().Height, activatedHeight)) } // start finalising blocks since max(activatedHeight, nextHeightToFinalize) @@ -38,7 +38,7 @@ func (k Keeper) TallyBlocks(ctx context.Context) { // - has validators, finalised: impossible to happen, panic // - does not have validators, finalised: impossible to happen, panic // After this for loop, the blocks since earliest activated height are either finalised or non-finalisable - for i := startHeight; i <= uint64(sdkCtx.BlockHeight()); i++ { + for i := startHeight; i <= uint64(sdkCtx.HeaderInfo().Height); i++ { ib, err := k.GetBlock(ctx, i) if err != nil { panic(err) // failing to get an existing block is a programming error diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 2252c09e4..2719d6d86 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -34,12 +34,12 @@ func FuzzTallying_PanicCases(f *testing.F) { // Case 2: expect to panic if finalised block with nil validator set fKeeper.SetBlock(ctx, &types.IndexedBlock{ - Height: 1, - AppHash: datagen.GenRandomByteArray(r, 32), - Finalized: true, + Height: 1, + AppHash: datagen.GenRandomByteArray(r, 32), + Finalized: true, }) // activate BTC staking protocol at height 1 - ctx = ctx.WithBlockHeight(1) + ctx = datagen.WithCtxHeight(ctx, 1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(1), nil).Times(1) bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(uint64(1))).Return(nil).Times(1) require.Panics(t, func() { fKeeper.TallyBlocks(ctx) }) @@ -66,16 +66,16 @@ func FuzzTallying_FinalizingNoBlock(f *testing.F) { for i := activatedHeight; i < activatedHeight+10; i++ { // index blocks fKeeper.SetBlock(ctx, &types.IndexedBlock{ - Height: i, - AppHash: datagen.GenRandomByteArray(r, 32), - Finalized: false, + Height: i, + AppHash: datagen.GenRandomByteArray(r, 32), + Finalized: false, }) // this block does not have QC err := giveNoQCToHeight(r, ctx, bsKeeper, fKeeper, i) require.NoError(t, err) } // add mock queries to GetBTCStakingActivatedHeight - ctx = ctx.WithBlockHeight(int64(activatedHeight) + 10 - 1) + ctx = datagen.WithCtxHeight(ctx, activatedHeight+10-1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) // tally blocks and none of them should be finalised fKeeper.TallyBlocks(ctx) @@ -109,9 +109,9 @@ func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { for i := activatedHeight; i < activatedHeight+10; i++ { // index blocks fKeeper.SetBlock(ctx, &types.IndexedBlock{ - Height: i, - AppHash: datagen.GenRandomByteArray(r, 32), - Finalized: false, + Height: i, + AppHash: datagen.GenRandomByteArray(r, 32), + Finalized: false, }) if i < activatedHeight+numWithQCs { // this block has QC @@ -128,7 +128,7 @@ func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { iKeeper.EXPECT().RewardBTCStaking(gomock.Any(), gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) bsKeeper.EXPECT().RemoveRewardDistCache(gomock.Any(), gomock.Any()).Return().Times(int(numWithQCs)) // add mock queries to GetBTCStakingActivatedHeight - ctx = ctx.WithBlockHeight(int64(activatedHeight) + 10 - 1) + ctx = datagen.WithCtxHeight(ctx, activatedHeight+10-1) bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(activatedHeight, nil).Times(1) // tally blocks and none of them should be finalised fKeeper.TallyBlocks(ctx) diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index e27e6dd1e..fb0eb1c90 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "github.com/cosmos/cosmos-sdk/runtime" "cosmossdk.io/store/prefix" @@ -22,7 +23,7 @@ func (k Keeper) HasSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340P } func (k Keeper) GetSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { - if uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) < height { + if uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) < height { return nil, types.ErrHeightTooHigh } store := k.voteStore(ctx, height) diff --git a/x/incentive/abci.go b/x/incentive/abci.go index e8e685d09..66cdb7da9 100644 --- a/x/incentive/abci.go +++ b/x/incentive/abci.go @@ -18,7 +18,7 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { // - send a portion of coins in the fee collector account to the incentive module account // - accumulate BTC staking gauge at the current height // - accumulate BTC timestamping gauge at the current epoch - if sdk.UnwrapSDKContext(ctx).BlockHeight() > 0 { + if sdk.UnwrapSDKContext(ctx).HeaderInfo().Height > 0 { k.HandleCoinsInFeeCollector(ctx) } return nil diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index 252f2f082..a62150cd0 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/store/prefix" bstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/babylonchain/babylon/x/incentive/types" @@ -40,7 +41,7 @@ func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, rdc *bstype func (k Keeper) accumulateBTCStakingReward(ctx context.Context, btcStakingReward sdk.Coins) { // update BTC staking gauge - height := uint64(sdk.UnwrapSDKContext(ctx).BlockHeight()) + height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) gauge := types.NewGauge(btcStakingReward...) k.SetBTCStakingGauge(ctx, height, gauge) diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go index 067275726..2cdadec4c 100644 --- a/x/incentive/keeper/btc_staking_gauge_test.go +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -26,7 +26,7 @@ func FuzzRewardBTCStaking(f *testing.F) { // create incentive keeper keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, nil, nil) height := datagen.RandomInt(r, 1000) - ctx = ctx.WithBlockHeight(int64(height)) + ctx = datagen.WithCtxHeight(ctx, height) // set a random gauge gauge := datagen.GenRandomGauge(r) diff --git a/x/incentive/keeper/intercept_fee_collector_test.go b/x/incentive/keeper/intercept_fee_collector_test.go index 93c203e53..189be6ba1 100644 --- a/x/incentive/keeper/intercept_fee_collector_test.go +++ b/x/incentive/keeper/intercept_fee_collector_test.go @@ -1,10 +1,11 @@ package keeper_test import ( - sdkmath "cosmossdk.io/math" "math/rand" "testing" + sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" @@ -43,7 +44,7 @@ func FuzzInterceptFeeCollector(f *testing.F) { keeper, ctx := testkeeper.IncentiveKeeper(t, bankKeeper, accountKeeper, epochingKeeper) height := datagen.RandomInt(r, 1000) - ctx = ctx.WithBlockHeight(int64(height)) + ctx = datagen.WithCtxHeight(ctx, height) // mock (thus ensure) that fees with the exact portion is intercepted // NOTE: if the actual fees are different from feesForIncentive the test will fail @@ -70,7 +71,7 @@ func FuzzInterceptFeeCollector(f *testing.F) { // accumulate for this epoch again and see if the epoch's BTC timestamping gauge has accumulated or not height += 1 - ctx = ctx.WithBlockHeight(int64(height)) + ctx = datagen.WithCtxHeight(ctx, height) bankKeeper.EXPECT().GetAllBalances(gomock.Any(), feeCollectorAcc.GetAddress()).Return(fees).Times(1) accountKeeper.EXPECT().GetModuleAccount(gomock.Any(), authtypes.FeeCollectorName).Return(feeCollectorAcc).Times(1) epochingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: epochNum}).Times(1) diff --git a/x/zoneconcierge/keeper/header_handler.go b/x/zoneconcierge/keeper/header_handler.go index e2b4b340f..2477cc830 100644 --- a/x/zoneconcierge/keeper/header_handler.go +++ b/x/zoneconcierge/keeper/header_handler.go @@ -11,15 +11,16 @@ import ( // HandleHeaderWithValidCommit handles a CZ header with a valid QC func (k Keeper) HandleHeaderWithValidCommit(ctx context.Context, txHash []byte, header *types.HeaderInfo, isOnFork bool) { - babylonHeader := sdk.UnwrapSDKContext(ctx).BlockHeader() + babylonHeader := sdk.UnwrapSDKContext(ctx).HeaderInfo() indexedHeader := types.IndexedHeader{ - ChainId: header.ChainId, - Hash: header.Hash, - Height: header.Height, - Time: &header.Time, - BabylonHeader: &babylonHeader, - BabylonEpoch: k.GetEpoch(ctx).EpochNumber, - BabylonTxHash: txHash, + ChainId: header.ChainId, + Hash: header.AppHash, + Height: header.Height, + Time: &header.Time, + BabylonHeaderHash: babylonHeader.AppHash, + BabylonHeaderHeight: uint64(babylonHeader.Height), + BabylonEpoch: k.GetEpoch(ctx).EpochNumber, + BabylonTxHash: txHash, } var ( diff --git a/x/zoneconcierge/keeper/ibc_header_decorator.go b/x/zoneconcierge/keeper/ibc_header_decorator.go index 243008124..668117532 100644 --- a/x/zoneconcierge/keeper/ibc_header_decorator.go +++ b/x/zoneconcierge/keeper/ibc_header_decorator.go @@ -29,7 +29,7 @@ func (d IBCHeaderDecorator) getHeaderAndClientState(ctx sdk.Context, m sdk.Msg) headerInfo := &types.HeaderInfo{ ClientId: msgUpdateClient.ClientId, ChainId: ibctmHeader.Header.ChainID, - Hash: ibctmHeader.Header.AppHash, + AppHash: ibctmHeader.Header.AppHash, Height: uint64(ibctmHeader.Header.Height), Time: ibctmHeader.Header.Time, } diff --git a/x/zoneconcierge/keeper/ibc_packet.go b/x/zoneconcierge/keeper/ibc_packet.go index 5b3445f3e..266e8fd35 100644 --- a/x/zoneconcierge/keeper/ibc_packet.go +++ b/x/zoneconcierge/keeper/ibc_packet.go @@ -3,13 +3,14 @@ package keeper import ( "context" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "time" + sdk "github.com/cosmos/cosmos-sdk/types" + errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/zoneconcierge/types" "github.com/cosmos/cosmos-sdk/telemetry" - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" //nolint:staticcheck channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v8/modules/core/24-host" coretypes "github.com/cosmos/ibc-go/v8/modules/core/types" @@ -35,7 +36,7 @@ func (k Keeper) SendIBCPacket(ctx context.Context, channel channeltypes.Identifi // timeout timeoutPeriod := time.Duration(k.GetParams(sdkCtx).IbcPacketTimeoutSeconds) * time.Second - timeoutTime := uint64(sdkCtx.BlockHeader().Time.Add(timeoutPeriod).UnixNano()) + timeoutTime := uint64(sdkCtx.HeaderInfo().Time.Add(timeoutPeriod).UnixNano()) zeroheight := clienttypes.ZeroHeight() seq, err := k.ics4Wrapper.SendPacket( diff --git a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go index d13783ec6..249b367e4 100644 --- a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go +++ b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go @@ -147,7 +147,7 @@ func (k Keeper) createBTCTimestamp(ctx context.Context, chainID string, channel } // get proofHeaderInEpoch - proofHeaderInEpoch, err := k.ProveHeaderInEpoch(ctx, finalizedChainInfo.LatestHeader.BabylonHeader, finalizedInfo.EpochInfo) + proofHeaderInEpoch, err := k.ProveHeaderInEpoch(ctx, finalizedChainInfo.LatestHeader.BabylonHeaderHeight, finalizedInfo.EpochInfo) if err != nil { return nil, fmt.Errorf("failed to generate proofHeaderInEpoch for chain %s: %w", chainID, err) } diff --git a/x/zoneconcierge/keeper/proof_block_in_epoch.go b/x/zoneconcierge/keeper/proof_block_in_epoch.go index c0f1754c6..c87648bb1 100644 --- a/x/zoneconcierge/keeper/proof_block_in_epoch.go +++ b/x/zoneconcierge/keeper/proof_block_in_epoch.go @@ -2,11 +2,11 @@ package keeper import ( "context" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" ) -func (k Keeper) ProveHeaderInEpoch(ctx context.Context, header *tmproto.Header, epoch *epochingtypes.Epoch) (*tmcrypto.Proof, error) { - return k.epochingKeeper.ProveAppHashInEpoch(ctx, uint64(header.Height), epoch.EpochNumber) +func (k Keeper) ProveHeaderInEpoch(ctx context.Context, headerHeight uint64, epoch *epochingtypes.Epoch) (*tmcrypto.Proof, error) { + return k.epochingKeeper.ProveAppHashInEpoch(ctx, headerHeight, epoch.EpochNumber) } diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed.go b/x/zoneconcierge/keeper/proof_epoch_sealed.go index 33baa22c4..98aa7231d 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed.go @@ -21,7 +21,7 @@ func getEpochInfoKey(epochNumber uint64) []byte { func (k Keeper) ProveEpochInfo(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { epochInfoKey := getEpochInfoKey(epoch.EpochNumber) - _, _, proof, err := k.QueryStore(epochingtypes.StoreKey, epochInfoKey, epoch.SealerHeader.Height) + _, _, proof, err := k.QueryStore(epochingtypes.StoreKey, epochInfoKey, int64(epoch.GetSealerBlockHeight())) if err != nil { return nil, err } @@ -37,7 +37,7 @@ func getValSetKey(epochNumber uint64) []byte { func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { valSetKey := getValSetKey(epoch.EpochNumber) - _, _, proof, err := k.QueryStore(checkpointingtypes.StoreKey, valSetKey, epoch.SealerHeader.Height) + _, _, proof, err := k.QueryStore(checkpointingtypes.StoreKey, valSetKey, int64(epoch.GetSealerBlockHeight())) if err != nil { return nil, err } @@ -118,7 +118,7 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R // This is different from the checkpoint verification rules in checkpointing, // where a checkpoint with valid BLS multisig but different appHash signals a dishonest majority equivocation. appHashInCkpt := rawCkpt.AppHash - appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeader.AppHash) + appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeaderHash) if !appHashInCkpt.Equal(appHashInSealerHeader) { return fmt.Errorf("AppHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", appHashInCkpt.String(), appHashInSealerHeader.String()) } @@ -147,7 +147,7 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R } // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeader.AppHash + root := epoch.SealerHeaderHash // Ensure The epoch medatata is committed to the app_hash of the sealer header epochBytes, err := epoch.Marshal() diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go index 1d838f5c0..7e17560e1 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go @@ -60,7 +60,7 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { // construct the rawCkpt // Note that the BlsMultiSig will be generated and assigned later - appHash := checkpointingtypes.AppHash(epoch.SealerHeader.AppHash) + appHash := checkpointingtypes.AppHash(epoch.SealerHeaderHash) rawCkpt := &checkpointingtypes.RawCheckpoint{ EpochNum: epoch.EpochNumber, AppHash: &appHash, diff --git a/x/zoneconcierge/keeper/proof_finalized_chain_info.go b/x/zoneconcierge/keeper/proof_finalized_chain_info.go index f8d99fc1c..b6b890219 100644 --- a/x/zoneconcierge/keeper/proof_finalized_chain_info.go +++ b/x/zoneconcierge/keeper/proof_finalized_chain_info.go @@ -29,7 +29,7 @@ func (k Keeper) proveFinalizedChainInfo( } // proof that the block is in this epoch - proof.ProofHeaderInEpoch, err = k.ProveHeaderInEpoch(ctx, chainInfo.LatestHeader.BabylonHeader, epochInfo) + proof.ProofHeaderInEpoch, err = k.ProveHeaderInEpoch(ctx, chainInfo.LatestHeader.BabylonHeaderHeight, epochInfo) if err != nil { return nil, err } diff --git a/x/zoneconcierge/types/types.go b/x/zoneconcierge/types/types.go index 1354f1342..6795ce58c 100644 --- a/x/zoneconcierge/types/types.go +++ b/x/zoneconcierge/types/types.go @@ -13,7 +13,7 @@ func (ci *ChainInfo) IsLatestHeader(header *IndexedHeader) bool { type HeaderInfo struct { ClientId string ChainId string - Hash []byte + AppHash []byte Height uint64 Time time.Time } diff --git a/x/zoneconcierge/types/zoneconcierge.go b/x/zoneconcierge/types/zoneconcierge.go index 9fe7f95a5..02e237d68 100644 --- a/x/zoneconcierge/types/zoneconcierge.go +++ b/x/zoneconcierge/types/zoneconcierge.go @@ -21,11 +21,14 @@ func (p *ProofEpochSealed) ValidateBasic() error { func (ih *IndexedHeader) ValidateBasic() error { if len(ih.ChainId) == 0 { return fmt.Errorf("empty ChainID") - } else if len(ih.Hash) == 0 { + } + if len(ih.Hash) == 0 { return fmt.Errorf("empty Hash") - } else if ih.BabylonHeader == nil { - return fmt.Errorf("nil BabylonHeader") - } else if len(ih.BabylonTxHash) == 0 { + } + if len(ih.BabylonHeaderHash) == 0 { + return fmt.Errorf("empty BabylonHeader hash") + } + if len(ih.BabylonTxHash) == 0 { return fmt.Errorf("empty BabylonTxHash") } return nil @@ -38,13 +41,20 @@ func (ih *IndexedHeader) Equal(ih2 *IndexedHeader) bool { if ih.ChainId != ih2.ChainId { return false - } else if !bytes.Equal(ih.Hash, ih2.Hash) { + } + if !bytes.Equal(ih.Hash, ih2.Hash) { return false - } else if ih.Height != ih2.Height { + } + if ih.Height != ih2.Height { return false - } else if !bytes.Equal(ih.BabylonHeader.AppHash, ih2.BabylonHeader.AppHash) { + } + if !bytes.Equal(ih.BabylonHeaderHash, ih2.BabylonHeaderHash) { + return false + } + if ih.BabylonHeaderHeight != ih2.BabylonHeaderHeight { return false - } else if ih.BabylonEpoch != ih2.BabylonEpoch { + } + if ih.BabylonEpoch != ih2.BabylonEpoch { return false } return bytes.Equal(ih.BabylonTxHash, ih2.BabylonTxHash) diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index e4270f746..64ae776c8 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -5,12 +5,12 @@ package types import ( fmt "fmt" - types3 "github.com/babylonchain/babylon/x/btccheckpoint/types" + types2 "github.com/babylonchain/babylon/x/btccheckpoint/types" types4 "github.com/babylonchain/babylon/x/btclightclient/types" - types2 "github.com/babylonchain/babylon/x/checkpointing/types" - types1 "github.com/babylonchain/babylon/x/epoching/types" + types1 "github.com/babylonchain/babylon/x/checkpointing/types" + types "github.com/babylonchain/babylon/x/epoching/types" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - types "github.com/cometbft/cometbft/proto/tendermint/types" + types3 "github.com/cometbft/cometbft/proto/tendermint/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" @@ -46,15 +46,18 @@ type IndexedHeader struct { // it is needed for CZ to unbond all mature validators/delegations // before this timestamp when this header is BTC-finalised Time *time.Time `protobuf:"bytes,4,opt,name=time,proto3,stdtime" json:"time,omitempty"` - // babylon_header is the header of the babylon block that includes this CZ + // babylon_header_hash is the hash of the babylon block that includes this CZ // header - BabylonHeader *types.Header `protobuf:"bytes,5,opt,name=babylon_header,json=babylonHeader,proto3" json:"babylon_header,omitempty"` + BabylonHeaderHash []byte `protobuf:"bytes,5,opt,name=babylon_header_hash,json=babylonHeaderHash,proto3" json:"babylon_header_hash,omitempty"` + // babylon_header_height is the height of the babylon block that includes this CZ + // header + BabylonHeaderHeight uint64 `protobuf:"varint,6,opt,name=babylon_header_height,json=babylonHeaderHeight,proto3" json:"babylon_header_height,omitempty"` // epoch is the epoch number of this header on Babylon ledger - BabylonEpoch uint64 `protobuf:"varint,6,opt,name=babylon_epoch,json=babylonEpoch,proto3" json:"babylon_epoch,omitempty"` + BabylonEpoch uint64 `protobuf:"varint,7,opt,name=babylon_epoch,json=babylonEpoch,proto3" json:"babylon_epoch,omitempty"` // babylon_tx_hash is the hash of the tx that includes this header // (babylon_block_height, babylon_tx_hash) jointly provides the position of // the header on Babylon ledger - BabylonTxHash []byte `protobuf:"bytes,7,opt,name=babylon_tx_hash,json=babylonTxHash,proto3" json:"babylon_tx_hash,omitempty"` + BabylonTxHash []byte `protobuf:"bytes,8,opt,name=babylon_tx_hash,json=babylonTxHash,proto3" json:"babylon_tx_hash,omitempty"` } func (m *IndexedHeader) Reset() { *m = IndexedHeader{} } @@ -118,13 +121,20 @@ func (m *IndexedHeader) GetTime() *time.Time { return nil } -func (m *IndexedHeader) GetBabylonHeader() *types.Header { +func (m *IndexedHeader) GetBabylonHeaderHash() []byte { if m != nil { - return m.BabylonHeader + return m.BabylonHeaderHash } return nil } +func (m *IndexedHeader) GetBabylonHeaderHeight() uint64 { + if m != nil { + return m.BabylonHeaderHeight + } + return 0 +} + func (m *IndexedHeader) GetBabylonEpoch() uint64 { if m != nil { return m.BabylonEpoch @@ -282,12 +292,12 @@ type FinalizedChainInfo struct { // finalized_chain_info is the info of the CZ FinalizedChainInfo *ChainInfo `protobuf:"bytes,2,opt,name=finalized_chain_info,json=finalizedChainInfo,proto3" json:"finalized_chain_info,omitempty"` // epoch_info is the metadata of the last BTC-finalised epoch - EpochInfo *types1.Epoch `protobuf:"bytes,3,opt,name=epoch_info,json=epochInfo,proto3" json:"epoch_info,omitempty"` + EpochInfo *types.Epoch `protobuf:"bytes,3,opt,name=epoch_info,json=epochInfo,proto3" json:"epoch_info,omitempty"` // raw_checkpoint is the raw checkpoint of this epoch - RawCheckpoint *types2.RawCheckpoint `protobuf:"bytes,4,opt,name=raw_checkpoint,json=rawCheckpoint,proto3" json:"raw_checkpoint,omitempty"` + RawCheckpoint *types1.RawCheckpoint `protobuf:"bytes,4,opt,name=raw_checkpoint,json=rawCheckpoint,proto3" json:"raw_checkpoint,omitempty"` // btc_submission_key is position of two BTC txs that include the raw // checkpoint of this epoch - BtcSubmissionKey *types3.SubmissionKey `protobuf:"bytes,5,opt,name=btc_submission_key,json=btcSubmissionKey,proto3" json:"btc_submission_key,omitempty"` + BtcSubmissionKey *types2.SubmissionKey `protobuf:"bytes,5,opt,name=btc_submission_key,json=btcSubmissionKey,proto3" json:"btc_submission_key,omitempty"` // proof is the proof that the chain info is finalized Proof *ProofFinalizedChainInfo `protobuf:"bytes,6,opt,name=proof,proto3" json:"proof,omitempty"` } @@ -339,21 +349,21 @@ func (m *FinalizedChainInfo) GetFinalizedChainInfo() *ChainInfo { return nil } -func (m *FinalizedChainInfo) GetEpochInfo() *types1.Epoch { +func (m *FinalizedChainInfo) GetEpochInfo() *types.Epoch { if m != nil { return m.EpochInfo } return nil } -func (m *FinalizedChainInfo) GetRawCheckpoint() *types2.RawCheckpoint { +func (m *FinalizedChainInfo) GetRawCheckpoint() *types1.RawCheckpoint { if m != nil { return m.RawCheckpoint } return nil } -func (m *FinalizedChainInfo) GetBtcSubmissionKey() *types3.SubmissionKey { +func (m *FinalizedChainInfo) GetBtcSubmissionKey() *types2.SubmissionKey { if m != nil { return m.BtcSubmissionKey } @@ -381,7 +391,7 @@ type ProofEpochSealed struct { // validator_set is the validator set of the sealed epoch // This validator set has generated a BLS multisig on `app_hash` of // the sealer header - ValidatorSet []*types2.ValidatorWithBlsKey `protobuf:"bytes,1,rep,name=validator_set,json=validatorSet,proto3" json:"validator_set,omitempty"` + ValidatorSet []*types1.ValidatorWithBlsKey `protobuf:"bytes,1,rep,name=validator_set,json=validatorSet,proto3" json:"validator_set,omitempty"` // proof_epoch_info is the Merkle proof that the epoch's metadata is committed // to `app_hash` of the sealer header ProofEpochInfo *crypto.ProofOps `protobuf:"bytes,2,opt,name=proof_epoch_info,json=proofEpochInfo,proto3" json:"proof_epoch_info,omitempty"` @@ -423,7 +433,7 @@ func (m *ProofEpochSealed) XXX_DiscardUnknown() { var xxx_messageInfo_ProofEpochSealed proto.InternalMessageInfo -func (m *ProofEpochSealed) GetValidatorSet() []*types2.ValidatorWithBlsKey { +func (m *ProofEpochSealed) GetValidatorSet() []*types1.ValidatorWithBlsKey { if m != nil { return m.ValidatorSet } @@ -449,7 +459,7 @@ func (m *ProofEpochSealed) GetProofEpochValSet() *crypto.ProofOps { type ProofFinalizedChainInfo struct { // proof_tx_in_block is the proof that tx that carries the header is included // in a certain Babylon block - ProofTxInBlock *types.TxProof `protobuf:"bytes,4,opt,name=proof_tx_in_block,json=proofTxInBlock,proto3" json:"proof_tx_in_block,omitempty"` + ProofTxInBlock *types3.TxProof `protobuf:"bytes,4,opt,name=proof_tx_in_block,json=proofTxInBlock,proto3" json:"proof_tx_in_block,omitempty"` // proof_header_in_epoch is the proof that the Babylon header is in a certain // epoch ProofHeaderInEpoch *crypto.Proof `protobuf:"bytes,5,opt,name=proof_header_in_epoch,json=proofHeaderInEpoch,proto3" json:"proof_header_in_epoch,omitempty"` @@ -458,7 +468,7 @@ type ProofFinalizedChainInfo struct { // proof_epoch_submitted is the proof that the epoch's checkpoint is included // in BTC ledger It is the two TransactionInfo in the best (i.e., earliest) // checkpoint submission - ProofEpochSubmitted []*types3.TransactionInfo `protobuf:"bytes,7,rep,name=proof_epoch_submitted,json=proofEpochSubmitted,proto3" json:"proof_epoch_submitted,omitempty"` + ProofEpochSubmitted []*types2.TransactionInfo `protobuf:"bytes,7,rep,name=proof_epoch_submitted,json=proofEpochSubmitted,proto3" json:"proof_epoch_submitted,omitempty"` } func (m *ProofFinalizedChainInfo) Reset() { *m = ProofFinalizedChainInfo{} } @@ -494,7 +504,7 @@ func (m *ProofFinalizedChainInfo) XXX_DiscardUnknown() { var xxx_messageInfo_ProofFinalizedChainInfo proto.InternalMessageInfo -func (m *ProofFinalizedChainInfo) GetProofTxInBlock() *types.TxProof { +func (m *ProofFinalizedChainInfo) GetProofTxInBlock() *types3.TxProof { if m != nil { return m.ProofTxInBlock } @@ -515,7 +525,7 @@ func (m *ProofFinalizedChainInfo) GetProofEpochSealed() *ProofEpochSealed { return nil } -func (m *ProofFinalizedChainInfo) GetProofEpochSubmitted() []*types3.TransactionInfo { +func (m *ProofFinalizedChainInfo) GetProofEpochSubmitted() []*types2.TransactionInfo { if m != nil { return m.ProofEpochSubmitted } @@ -582,67 +592,68 @@ func init() { } var fileDescriptor_ab886e1868e5c5cd = []byte{ - // 959 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0xda, 0x4e, 0x42, 0xc6, 0x71, 0x1a, 0xa6, 0x85, 0x6e, 0x02, 0x38, 0x96, 0x2b, 0x15, - 0x17, 0xc1, 0x5a, 0x0e, 0x70, 0x80, 0x0b, 0xc2, 0xa6, 0xa5, 0x6e, 0x11, 0x45, 0x63, 0xb7, 0x20, - 0x04, 0x5a, 0xed, 0x9f, 0xf1, 0xee, 0x2a, 0xeb, 0x1d, 0x6b, 0x67, 0xe2, 0xda, 0xfd, 0x14, 0x3d, - 0xf3, 0x2d, 0xf8, 0x16, 0x1c, 0x7b, 0xe4, 0x06, 0x4a, 0xc4, 0x37, 0xe0, 0xc2, 0x0d, 0xcd, 0x9b, - 0x99, 0xf5, 0x9a, 0xe0, 0xb6, 0x97, 0x68, 0x67, 0xe6, 0xf7, 0x7e, 0xf3, 0x9b, 0xf7, 0x7b, 0xef, - 0x39, 0xe8, 0x43, 0xdf, 0xf3, 0x97, 0x29, 0xcb, 0xba, 0xcf, 0x58, 0x46, 0x03, 0x96, 0x05, 0x09, - 0xcd, 0x23, 0xda, 0x9d, 0xf7, 0xd6, 0x37, 0x9c, 0x59, 0xce, 0x04, 0xc3, 0xb6, 0x46, 0x3b, 0xeb, - 0x87, 0xf3, 0xde, 0xf1, 0x8d, 0x88, 0x45, 0x0c, 0x40, 0x5d, 0xf9, 0xa5, 0xf0, 0xc7, 0x27, 0x11, - 0x63, 0x51, 0x4a, 0xbb, 0xb0, 0xf2, 0xcf, 0x27, 0x5d, 0x91, 0x4c, 0x29, 0x17, 0xde, 0x74, 0xa6, - 0x01, 0xef, 0x0a, 0x9a, 0x85, 0x34, 0x9f, 0x26, 0x99, 0xe8, 0x8a, 0xe5, 0x8c, 0x72, 0xf5, 0x57, - 0x9f, 0xbe, 0x57, 0x3a, 0x0d, 0xf2, 0xe5, 0x4c, 0x30, 0xc9, 0xc4, 0x26, 0xfa, 0xb8, 0xd0, 0xee, - 0x8b, 0x20, 0x88, 0x69, 0x70, 0x36, 0x63, 0x12, 0x39, 0xef, 0xad, 0x6f, 0x68, 0xf4, 0x6d, 0x83, - 0x5e, 0x9d, 0x24, 0x59, 0x04, 0xe8, 0x94, 0xbb, 0x67, 0x74, 0xa9, 0x71, 0x77, 0x36, 0xe2, 0xae, - 0x50, 0xb6, 0x0d, 0x94, 0xce, 0x58, 0x10, 0x6b, 0x94, 0xf9, 0xd6, 0x18, 0xa7, 0x24, 0x32, 0x4d, - 0xa2, 0x58, 0xfe, 0xa5, 0x85, 0xca, 0xd2, 0x8e, 0xc2, 0xb7, 0x7f, 0xa9, 0xa0, 0xc6, 0x30, 0x0b, - 0xe9, 0x82, 0x86, 0xf7, 0xa9, 0x17, 0xd2, 0x1c, 0x1f, 0xa1, 0x37, 0x82, 0xd8, 0x4b, 0x32, 0x37, - 0x09, 0x6d, 0xab, 0x65, 0x75, 0xf6, 0xc8, 0x2e, 0xac, 0x87, 0x21, 0xc6, 0xa8, 0x16, 0x7b, 0x3c, - 0xb6, 0x2b, 0x2d, 0xab, 0xb3, 0x4f, 0xe0, 0x1b, 0xbf, 0x8d, 0x76, 0x62, 0x2a, 0x69, 0xed, 0x6a, - 0xcb, 0xea, 0xd4, 0x88, 0x5e, 0xe1, 0x4f, 0x50, 0x4d, 0x66, 0xdf, 0xae, 0xb5, 0xac, 0x4e, 0xfd, - 0xf4, 0xd8, 0x51, 0xd6, 0x38, 0xc6, 0x1a, 0x67, 0x6c, 0xac, 0xe9, 0xd7, 0x9e, 0xff, 0x71, 0x62, - 0x11, 0x40, 0xe3, 0x2f, 0xd0, 0x81, 0x7e, 0x80, 0x1b, 0x83, 0x1c, 0x7b, 0x1b, 0xe2, 0x6d, 0x67, - 0xe5, 0x8d, 0xa3, 0x3c, 0x53, 0x72, 0x49, 0x43, 0xe3, 0xb5, 0xfa, 0x5b, 0xc8, 0x6c, 0xb8, 0x90, - 0x19, 0x7b, 0x07, 0x54, 0xed, 0xeb, 0xcd, 0xbb, 0x72, 0x0f, 0xdf, 0x46, 0xd7, 0x0c, 0x48, 0x2c, - 0x5c, 0x78, 0xd2, 0x2e, 0x3c, 0xc9, 0xc4, 0x8e, 0x17, 0xf7, 0x3d, 0x1e, 0xb7, 0x1f, 0xa0, 0xed, - 0x7b, 0x2c, 0x3f, 0xe3, 0xf8, 0x4b, 0xb4, 0xab, 0xe4, 0x70, 0xbb, 0xda, 0xaa, 0x76, 0xea, 0xa7, - 0xef, 0x3b, 0x9b, 0x4a, 0xd3, 0x59, 0xcb, 0x26, 0x31, 0x71, 0xed, 0xbf, 0x2d, 0xb4, 0x37, 0x80, - 0x3c, 0x66, 0x13, 0xf6, 0xb2, 0x24, 0x7f, 0x83, 0x1a, 0xa9, 0x27, 0x28, 0x17, 0x26, 0x03, 0x15, - 0xc8, 0xc0, 0x6b, 0xdf, 0xb8, 0xaf, 0xa2, 0x75, 0x3e, 0xfa, 0x48, 0xaf, 0xdd, 0x89, 0x7c, 0x09, - 0x98, 0x54, 0x3f, 0x3d, 0xd9, 0x4c, 0x06, 0x0f, 0x26, 0x75, 0x15, 0xa4, 0x5e, 0xff, 0x39, 0x3a, - 0x2a, 0x1a, 0x89, 0x86, 0x5a, 0x16, 0x77, 0x03, 0x76, 0x9e, 0x09, 0xf0, 0xb7, 0x46, 0x6e, 0x96, - 0x00, 0xea, 0x66, 0x3e, 0x90, 0xc7, 0xed, 0x5f, 0xab, 0x08, 0xdf, 0x4b, 0x32, 0x2f, 0x4d, 0x9e, - 0xd1, 0xf0, 0xb5, 0xde, 0xff, 0x18, 0xdd, 0x98, 0x98, 0x00, 0x57, 0x83, 0xb2, 0x09, 0xd3, 0x69, - 0xb8, 0xb5, 0x59, 0x79, 0xc1, 0x4e, 0xf0, 0xe4, 0xea, 0x8d, 0x9f, 0x21, 0x04, 0x05, 0xa1, 0xc8, - 0xaa, 0xba, 0x2a, 0x0d, 0x59, 0xd1, 0x45, 0xf3, 0x9e, 0x03, 0x35, 0x42, 0xf6, 0x60, 0x0b, 0x42, - 0xbf, 0x45, 0x07, 0xb9, 0xf7, 0xd4, 0x5d, 0xf5, 0xa3, 0x2e, 0xea, 0x95, 0x25, 0x6b, 0xbd, 0x2b, - 0x39, 0x88, 0xf7, 0x74, 0x50, 0xec, 0x91, 0x46, 0x5e, 0x5e, 0xe2, 0xc7, 0x08, 0xfb, 0x22, 0x70, - 0xf9, 0xb9, 0x3f, 0x4d, 0x38, 0x4f, 0x58, 0x26, 0xc7, 0x81, 0x2e, 0xf4, 0x15, 0xe7, 0xfa, 0x50, - 0x99, 0xf7, 0x9c, 0x51, 0x81, 0x7f, 0x48, 0x97, 0xe4, 0xd0, 0x17, 0xc1, 0xda, 0x0e, 0xfe, 0x1a, - 0x6d, 0xc3, 0xb8, 0x82, 0x92, 0xaf, 0x9f, 0xf6, 0x36, 0x67, 0xea, 0x3b, 0x09, 0xbb, 0xea, 0x0a, - 0x51, 0xf1, 0xed, 0x7f, 0x2c, 0x74, 0x08, 0x10, 0xc8, 0xc4, 0x88, 0x7a, 0x29, 0x0d, 0x31, 0x41, - 0x8d, 0xb9, 0x97, 0x26, 0xa1, 0x27, 0x58, 0xee, 0x72, 0x2a, 0x6c, 0x0b, 0x1a, 0xe1, 0xa3, 0xcd, - 0x39, 0x78, 0x62, 0xe0, 0xdf, 0x27, 0x22, 0xee, 0xa7, 0x5c, 0xaa, 0xde, 0x2f, 0x38, 0x46, 0x54, - 0xe0, 0xbb, 0xe8, 0x10, 0x6e, 0x74, 0x4b, 0xce, 0x28, 0x9b, 0xdf, 0x29, 0xf7, 0xbb, 0x9a, 0xc5, - 0x4a, 0xf5, 0xa3, 0x19, 0x27, 0x07, 0xb3, 0x42, 0x1c, 0xf8, 0xf3, 0x00, 0x5d, 0x2f, 0xd3, 0xcc, - 0xbd, 0x14, 0x04, 0x56, 0x5f, 0xcd, 0x74, 0xb8, 0x62, 0x7a, 0xe2, 0xa5, 0x23, 0x2a, 0xda, 0x7f, - 0x55, 0xd0, 0xcd, 0x0d, 0xe9, 0xc1, 0x5f, 0xa1, 0x37, 0xd5, 0x3d, 0x62, 0xe1, 0x26, 0x99, 0xeb, - 0xa7, 0x2c, 0x38, 0xd3, 0xa5, 0x70, 0x74, 0x75, 0x3e, 0x8d, 0x17, 0xc0, 0xa3, 0xd5, 0x8e, 0x17, - 0xc3, 0xac, 0x2f, 0x03, 0xf0, 0x43, 0xf4, 0x96, 0x62, 0x51, 0x7d, 0x24, 0x99, 0xd4, 0xa4, 0xfa, - 0x9f, 0x49, 0x57, 0xd6, 0x4b, 0x30, 0x84, 0xa9, 0xee, 0x1a, 0xea, 0x49, 0xf6, 0x03, 0xc2, 0xe5, - 0xa7, 0x73, 0xf0, 0x4a, 0x17, 0xc0, 0x07, 0xaf, 0x28, 0x80, 0x92, 0xbb, 0xe5, 0x44, 0x68, 0xbf, - 0x7f, 0x36, 0x32, 0x35, 0xb3, 0x2c, 0x35, 0x21, 0x68, 0x68, 0xef, 0x82, 0xef, 0x77, 0x36, 0xd7, - 0xe9, 0x38, 0xf7, 0x32, 0xee, 0x05, 0x22, 0x61, 0xaa, 0xaa, 0xae, 0x97, 0xb8, 0x0d, 0x4b, 0xfb, - 0x27, 0x74, 0xad, 0x3f, 0x1e, 0x40, 0x6e, 0x47, 0x34, 0x9a, 0xd2, 0x4c, 0xe0, 0x21, 0xaa, 0xcb, - 0xb6, 0x30, 0x83, 0xb6, 0x02, 0xf7, 0x74, 0xca, 0xf7, 0x94, 0x7f, 0xbe, 0xe6, 0x3d, 0xa7, 0x3f, - 0x1e, 0x98, 0x6c, 0x4c, 0x18, 0x41, 0xbe, 0x08, 0xf4, 0xe8, 0xe9, 0x3f, 0xfa, 0xed, 0xa2, 0x69, - 0xbd, 0xb8, 0x68, 0x5a, 0x7f, 0x5e, 0x34, 0xad, 0xe7, 0x97, 0xcd, 0xad, 0x17, 0x97, 0xcd, 0xad, - 0xdf, 0x2f, 0x9b, 0x5b, 0x3f, 0x7e, 0x1a, 0x25, 0x22, 0x3e, 0xf7, 0x9d, 0x80, 0x4d, 0xbb, 0x9a, - 0x19, 0x66, 0x8c, 0x59, 0x74, 0x17, 0xff, 0xf9, 0xd7, 0x04, 0xcc, 0xf4, 0x77, 0xe0, 0x77, 0xeb, - 0xe3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x48, 0xbe, 0x03, 0x62, 0xc0, 0x08, 0x00, 0x00, + // 967 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4d, 0x6f, 0x1b, 0x45, + 0x18, 0xce, 0xc6, 0x4e, 0xd2, 0x8c, 0xe3, 0x36, 0x9d, 0xb4, 0x74, 0x13, 0xc0, 0xb1, 0x5c, 0xa9, + 0xb8, 0x08, 0xd6, 0xb2, 0x81, 0x03, 0xdc, 0xb0, 0x69, 0xa9, 0x5b, 0x44, 0xd1, 0xda, 0x2d, 0x08, + 0x81, 0x56, 0xfb, 0x31, 0xde, 0x5d, 0x65, 0xbd, 0x63, 0xed, 0x8c, 0x5d, 0xbb, 0xbf, 0xa2, 0x7f, + 0x85, 0x1f, 0xc0, 0x9d, 0x63, 0x8f, 0xdc, 0x40, 0x89, 0xf8, 0x07, 0x5c, 0xb8, 0xa1, 0x79, 0x67, + 0x66, 0xbd, 0x6e, 0xb4, 0x84, 0x5e, 0xac, 0x9d, 0x99, 0xe7, 0x7d, 0xde, 0x67, 0xde, 0xaf, 0x31, + 0xfa, 0xc8, 0x73, 0xbd, 0x55, 0x42, 0xd3, 0xce, 0x4b, 0x9a, 0x12, 0x9f, 0xa6, 0x7e, 0x4c, 0xb2, + 0x90, 0x74, 0x16, 0xdd, 0xcd, 0x0d, 0x6b, 0x96, 0x51, 0x4e, 0xb1, 0xa9, 0xd0, 0xd6, 0xe6, 0xe1, + 0xa2, 0x7b, 0x72, 0x2b, 0xa4, 0x21, 0x05, 0x50, 0x47, 0x7c, 0x49, 0xfc, 0xc9, 0x69, 0x48, 0x69, + 0x98, 0x90, 0x0e, 0xac, 0xbc, 0xf9, 0xa4, 0xc3, 0xe3, 0x29, 0x61, 0xdc, 0x9d, 0xce, 0x14, 0xe0, + 0x3d, 0x4e, 0xd2, 0x80, 0x64, 0xd3, 0x38, 0xe5, 0x1d, 0xbe, 0x9a, 0x11, 0x26, 0x7f, 0xd5, 0xe9, + 0xfb, 0x85, 0x53, 0x3f, 0x5b, 0xcd, 0x38, 0x15, 0x4c, 0x74, 0xa2, 0x8e, 0x73, 0xed, 0x1e, 0xf7, + 0xfd, 0x88, 0xf8, 0x67, 0x33, 0x2a, 0x90, 0x8b, 0xee, 0xe6, 0x86, 0x42, 0xdf, 0xd3, 0xe8, 0xf5, + 0x49, 0x9c, 0x86, 0x80, 0x4e, 0x98, 0x73, 0x46, 0x56, 0x0a, 0x77, 0xbf, 0x14, 0x77, 0x89, 0xb2, + 0xa5, 0xa1, 0x64, 0x46, 0xfd, 0x48, 0xa1, 0xf4, 0xb7, 0xc2, 0x58, 0x05, 0x91, 0x49, 0x1c, 0x46, + 0xe2, 0x97, 0xe4, 0x2a, 0x0b, 0x3b, 0x12, 0xdf, 0xfa, 0x75, 0x1b, 0xd5, 0x87, 0x69, 0x40, 0x96, + 0x24, 0x78, 0x44, 0xdc, 0x80, 0x64, 0xf8, 0x18, 0x5d, 0xf3, 0x23, 0x37, 0x4e, 0x9d, 0x38, 0x30, + 0x8d, 0xa6, 0xd1, 0xde, 0xb7, 0xf7, 0x60, 0x3d, 0x0c, 0x30, 0x46, 0xd5, 0xc8, 0x65, 0x91, 0xb9, + 0xdd, 0x34, 0xda, 0x07, 0x36, 0x7c, 0xe3, 0x77, 0xd0, 0x6e, 0x44, 0x04, 0xad, 0x59, 0x69, 0x1a, + 0xed, 0xaa, 0xad, 0x56, 0xf8, 0x53, 0x54, 0x15, 0xd1, 0x37, 0xab, 0x4d, 0xa3, 0x5d, 0xeb, 0x9d, + 0x58, 0x32, 0x35, 0x96, 0x4e, 0x8d, 0x35, 0xd6, 0xa9, 0xe9, 0x57, 0x5f, 0xfd, 0x71, 0x6a, 0xd8, + 0x80, 0xc6, 0x16, 0x3a, 0x52, 0x17, 0x70, 0x22, 0x90, 0xe3, 0x80, 0xc3, 0x1d, 0x70, 0x78, 0x53, + 0x1d, 0x49, 0xa1, 0x8f, 0x84, 0xf7, 0x1e, 0xba, 0xfd, 0x26, 0x5e, 0x8a, 0xd9, 0x05, 0x31, 0x47, + 0x9b, 0x16, 0x52, 0xd9, 0x5d, 0x54, 0xd7, 0x36, 0x10, 0x3c, 0x73, 0x0f, 0xb0, 0x07, 0x6a, 0xf3, + 0x81, 0xd8, 0xc3, 0xf7, 0xd0, 0x0d, 0x0d, 0xe2, 0x4b, 0x29, 0xe2, 0x1a, 0x88, 0xd0, 0xb6, 0xe3, + 0xa5, 0x10, 0xd0, 0x7a, 0x8c, 0x76, 0x1e, 0xd2, 0xec, 0x8c, 0xe1, 0x2f, 0xd1, 0x9e, 0x54, 0xc0, + 0xcc, 0x4a, 0xb3, 0xd2, 0xae, 0xf5, 0x3e, 0xb0, 0xca, 0xaa, 0xd7, 0xda, 0x08, 0xb8, 0xad, 0xed, + 0x5a, 0x7f, 0x1b, 0x68, 0x7f, 0x00, 0xa1, 0x4e, 0x27, 0xf4, 0xbf, 0xf2, 0xf0, 0x0d, 0xaa, 0x27, + 0x2e, 0x27, 0x8c, 0xab, 0x4b, 0x43, 0x42, 0xde, 0xc2, 0xe3, 0x81, 0xb4, 0x56, 0x09, 0xef, 0x23, + 0xb5, 0x76, 0x26, 0xe2, 0x26, 0x90, 0xc7, 0x5a, 0xef, 0xb4, 0x9c, 0x0c, 0x2e, 0x6c, 0xd7, 0xa4, + 0x91, 0xbc, 0xfd, 0x17, 0xe8, 0x38, 0xef, 0x35, 0x12, 0x28, 0x59, 0xcc, 0xf1, 0xe9, 0x3c, 0xe5, + 0x50, 0x02, 0x55, 0xfb, 0x4e, 0x01, 0x20, 0x3d, 0xb3, 0x81, 0x38, 0x6e, 0xfd, 0x52, 0x41, 0xf8, + 0x61, 0x9c, 0xba, 0x49, 0xfc, 0x92, 0x04, 0xff, 0xeb, 0xfe, 0xcf, 0xd0, 0xad, 0x89, 0x36, 0x70, + 0x14, 0x28, 0x9d, 0x50, 0x15, 0x86, 0xbb, 0xe5, 0xca, 0x73, 0x76, 0x1b, 0x4f, 0x2e, 0x7b, 0xfc, + 0x1c, 0x21, 0x28, 0x08, 0x49, 0x56, 0x51, 0x85, 0xab, 0xc9, 0xf2, 0x46, 0x5b, 0x74, 0x2d, 0xa8, + 0x11, 0x7b, 0x1f, 0xb6, 0xc0, 0xf4, 0x5b, 0x74, 0x3d, 0x73, 0x5f, 0x38, 0xeb, 0x96, 0x55, 0x75, + 0xbf, 0x4e, 0xc9, 0x46, 0x7b, 0x0b, 0x0e, 0xdb, 0x7d, 0x31, 0xc8, 0xf7, 0xec, 0x7a, 0x56, 0x5c, + 0xe2, 0x67, 0x08, 0x7b, 0xdc, 0x77, 0xd8, 0xdc, 0x9b, 0xc6, 0x8c, 0xc5, 0x34, 0x15, 0x13, 0x03, + 0xda, 0xa0, 0xc8, 0xb9, 0x39, 0x77, 0x16, 0x5d, 0x6b, 0x94, 0xe3, 0x9f, 0x90, 0x95, 0x7d, 0xe8, + 0x71, 0x7f, 0x63, 0x07, 0x7f, 0x8d, 0x76, 0x60, 0xa2, 0x41, 0x7b, 0xd4, 0x7a, 0xdd, 0xf2, 0x48, + 0x7d, 0x27, 0x60, 0x97, 0xb3, 0x62, 0x4b, 0xfb, 0xd6, 0x3f, 0x06, 0x3a, 0x04, 0x08, 0x44, 0x62, + 0x44, 0xdc, 0x84, 0x04, 0xd8, 0x46, 0xf5, 0x85, 0x9b, 0xc4, 0x81, 0xcb, 0x69, 0xe6, 0x30, 0xc2, + 0x4d, 0x03, 0x1a, 0xe1, 0xe3, 0xf2, 0x18, 0x3c, 0xd7, 0xf0, 0xef, 0x63, 0x1e, 0xf5, 0x13, 0x26, + 0x54, 0x1f, 0xe4, 0x1c, 0x23, 0xc2, 0xf1, 0x03, 0x74, 0x08, 0x1e, 0x9d, 0x42, 0x66, 0x64, 0x9a, + 0xdf, 0xb5, 0xd6, 0xe3, 0xda, 0x92, 0xe3, 0x5a, 0xaa, 0x7e, 0x3a, 0x63, 0xf6, 0xf5, 0x59, 0x2e, + 0x0e, 0xf2, 0xf3, 0x18, 0x1d, 0x15, 0x69, 0x16, 0x6e, 0x02, 0x02, 0x2b, 0x57, 0x33, 0x1d, 0xae, + 0x99, 0x9e, 0xbb, 0xc9, 0x88, 0xf0, 0xd6, 0x5f, 0xdb, 0xe8, 0x4e, 0x49, 0x78, 0xf0, 0x57, 0xe8, + 0xa6, 0xf4, 0xc3, 0x97, 0x4e, 0x9c, 0x3a, 0x5e, 0x42, 0xfd, 0x33, 0x55, 0x0a, 0xc7, 0x45, 0x2f, + 0xf2, 0xd9, 0x19, 0x2f, 0x81, 0x47, 0xa9, 0x1d, 0x2f, 0x87, 0x69, 0x5f, 0x18, 0xe0, 0x27, 0xe8, + 0xb6, 0x64, 0x51, 0x33, 0x2d, 0xd6, 0x93, 0x4a, 0x16, 0x80, 0x59, 0xa6, 0xd7, 0xc6, 0x60, 0x26, + 0xbb, 0x6b, 0xa8, 0x26, 0xd9, 0x0f, 0x08, 0x17, 0xaf, 0xce, 0x20, 0x57, 0xaa, 0x00, 0x3e, 0xbc, + 0xa2, 0x00, 0x0a, 0xd9, 0x2d, 0x06, 0x42, 0xe5, 0xfb, 0x67, 0x2d, 0x53, 0x31, 0x8b, 0x52, 0xe3, + 0x9c, 0x04, 0xe6, 0x1e, 0xe4, 0xfd, 0x7e, 0x79, 0x9d, 0x8e, 0x33, 0x37, 0x65, 0xae, 0xcf, 0x63, + 0x2a, 0xab, 0xea, 0xa8, 0xc0, 0xad, 0x59, 0x5a, 0x3f, 0xa1, 0x1b, 0xfd, 0xf1, 0x00, 0x62, 0x3b, + 0x22, 0xe1, 0x94, 0xa4, 0x1c, 0x0f, 0x51, 0x4d, 0xb4, 0x85, 0x1e, 0xb4, 0xdb, 0xe0, 0xa7, 0x5d, + 0xf4, 0x53, 0x7c, 0xe1, 0x16, 0x5d, 0xab, 0x3f, 0x1e, 0xe8, 0x68, 0x4c, 0xa8, 0x8d, 0x3c, 0xee, + 0xab, 0xd1, 0xd3, 0x7f, 0xfa, 0xdb, 0x79, 0xc3, 0x78, 0x7d, 0xde, 0x30, 0xfe, 0x3c, 0x6f, 0x18, + 0xaf, 0x2e, 0x1a, 0x5b, 0xaf, 0x2f, 0x1a, 0x5b, 0xbf, 0x5f, 0x34, 0xb6, 0x7e, 0xfc, 0x2c, 0x8c, + 0x79, 0x34, 0xf7, 0x2c, 0x9f, 0x4e, 0x3b, 0x8a, 0x19, 0x66, 0x8c, 0x5e, 0x74, 0x96, 0x6f, 0xfc, + 0x7b, 0x81, 0x64, 0x7a, 0xbb, 0xf0, 0xb4, 0x7d, 0xf2, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, + 0x4d, 0x01, 0x46, 0xe3, 0x08, 0x00, 0x00, } func (m *IndexedHeader) Marshal() (dAtA []byte, err error) { @@ -670,32 +681,32 @@ func (m *IndexedHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.BabylonTxHash) i = encodeVarintZoneconcierge(dAtA, i, uint64(len(m.BabylonTxHash))) i-- - dAtA[i] = 0x3a + dAtA[i] = 0x42 } if m.BabylonEpoch != 0 { i = encodeVarintZoneconcierge(dAtA, i, uint64(m.BabylonEpoch)) i-- + dAtA[i] = 0x38 + } + if m.BabylonHeaderHeight != 0 { + i = encodeVarintZoneconcierge(dAtA, i, uint64(m.BabylonHeaderHeight)) + i-- dAtA[i] = 0x30 } - if m.BabylonHeader != nil { - { - size, err := m.BabylonHeader.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) - } + if len(m.BabylonHeaderHash) > 0 { + i -= len(m.BabylonHeaderHash) + copy(dAtA[i:], m.BabylonHeaderHash) + i = encodeVarintZoneconcierge(dAtA, i, uint64(len(m.BabylonHeaderHash))) i-- dAtA[i] = 0x2a } if m.Time != nil { - n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.Time, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.Time):]) - if err2 != nil { - return 0, err2 + n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.Time, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.Time):]) + if err1 != nil { + return 0, err1 } - i -= n2 - i = encodeVarintZoneconcierge(dAtA, i, uint64(n2)) + i -= n1 + i = encodeVarintZoneconcierge(dAtA, i, uint64(n1)) i-- dAtA[i] = 0x22 } @@ -1110,10 +1121,13 @@ func (m *IndexedHeader) Size() (n int) { l = github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.Time) n += 1 + l + sovZoneconcierge(uint64(l)) } - if m.BabylonHeader != nil { - l = m.BabylonHeader.Size() + l = len(m.BabylonHeaderHash) + if l > 0 { n += 1 + l + sovZoneconcierge(uint64(l)) } + if m.BabylonHeaderHeight != 0 { + n += 1 + sovZoneconcierge(uint64(m.BabylonHeaderHeight)) + } if m.BabylonEpoch != 0 { n += 1 + sovZoneconcierge(uint64(m.BabylonEpoch)) } @@ -1419,9 +1433,9 @@ func (m *IndexedHeader) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BabylonHeader", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BabylonHeaderHash", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowZoneconcierge @@ -1431,29 +1445,46 @@ func (m *IndexedHeader) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthZoneconcierge } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthZoneconcierge } if postIndex > l { return io.ErrUnexpectedEOF } - if m.BabylonHeader == nil { - m.BabylonHeader = &types.Header{} - } - if err := m.BabylonHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.BabylonHeaderHash = append(m.BabylonHeaderHash[:0], dAtA[iNdEx:postIndex]...) + if m.BabylonHeaderHash == nil { + m.BabylonHeaderHash = []byte{} } iNdEx = postIndex case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BabylonHeaderHeight", wireType) + } + m.BabylonHeaderHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowZoneconcierge + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BabylonHeaderHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BabylonEpoch", wireType) } @@ -1472,7 +1503,7 @@ func (m *IndexedHeader) Unmarshal(dAtA []byte) error { break } } - case 7: + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BabylonTxHash", wireType) } @@ -1911,7 +1942,7 @@ func (m *FinalizedChainInfo) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.EpochInfo == nil { - m.EpochInfo = &types1.Epoch{} + m.EpochInfo = &types.Epoch{} } if err := m.EpochInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1947,7 +1978,7 @@ func (m *FinalizedChainInfo) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.RawCheckpoint == nil { - m.RawCheckpoint = &types2.RawCheckpoint{} + m.RawCheckpoint = &types1.RawCheckpoint{} } if err := m.RawCheckpoint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -1983,7 +2014,7 @@ func (m *FinalizedChainInfo) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.BtcSubmissionKey == nil { - m.BtcSubmissionKey = &types3.SubmissionKey{} + m.BtcSubmissionKey = &types2.SubmissionKey{} } if err := m.BtcSubmissionKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -2104,7 +2135,7 @@ func (m *ProofEpochSealed) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValidatorSet = append(m.ValidatorSet, &types2.ValidatorWithBlsKey{}) + m.ValidatorSet = append(m.ValidatorSet, &types1.ValidatorWithBlsKey{}) if err := m.ValidatorSet[len(m.ValidatorSet)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -2261,7 +2292,7 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.ProofTxInBlock == nil { - m.ProofTxInBlock = &types.TxProof{} + m.ProofTxInBlock = &types3.TxProof{} } if err := m.ProofTxInBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -2368,7 +2399,7 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ProofEpochSubmitted = append(m.ProofEpochSubmitted, &types3.TransactionInfo{}) + m.ProofEpochSubmitted = append(m.ProofEpochSubmitted, &types2.TransactionInfo{}) if err := m.ProofEpochSubmitted[len(m.ProofEpochSubmitted)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } From 4ca7a48fdd85cfd24a3d7d8104244462f34377d5 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Thu, 7 Dec 2023 15:47:02 +0400 Subject: [PATCH 123/202] fix: Do not enforce slashing address being different than change address (#142) --- btcstaking/errors.go | 1 - btcstaking/staking.go | 5 ----- 2 files changed, 6 deletions(-) diff --git a/btcstaking/errors.go b/btcstaking/errors.go index 85899c5a4..af1625f3a 100644 --- a/btcstaking/errors.go +++ b/btcstaking/errors.go @@ -7,5 +7,4 @@ var ( ErrDustOutputFound = errors.New("transaction contains a dust output") ErrInsufficientSlashingAmount = errors.New("insufficient slashing amount") ErrInsufficientChangeAmount = errors.New("insufficient change amount") - ErrSameAddress = errors.New("slashing and change addresses cannot be the same") ) diff --git a/btcstaking/staking.go b/btcstaking/staking.go index 08864309a..e464ecf0d 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -47,11 +47,6 @@ func BuildSlashingTxFromOutpoint( return nil, ErrInvalidSlashingRate } - // Check if slashing address and change address are the same - if slashingAddress.EncodeAddress() == changeAddress.EncodeAddress() { - return nil, ErrSameAddress - } - // Calculate the amount to be slashed slashingRateFloat64, err := slashingRate.Float64() if err != nil { From ab4cd5503eb87baad4f45972a5bca13ce1e19c1e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 8 Dec 2023 15:11:58 +1100 Subject: [PATCH 124/202] epoching: queue `MsgCancelUnbondingDelegation` message to epoch boundary (#141) --- client/docs/swagger-ui/swagger.yaml | 5276 ++++------------- proto/babylon/epoching/v1/epoching.proto | 8 +- proto/babylon/epoching/v1/events.proto | 10 + proto/babylon/epoching/v1/tx.proto | 19 + x/epoching/client/cli/tx.go | 55 +- .../keeper/drop_validator_msg_decorator.go | 4 +- .../drop_validator_msg_decorator_test.go | 1 + x/epoching/keeper/epoch_msg_queue.go | 33 +- x/epoching/keeper/hooks.go | 3 +- x/epoching/keeper/lifecycle_delegation.go | 3 +- x/epoching/keeper/modified_staking.go | 10 +- x/epoching/keeper/msg_server.go | 65 + x/epoching/keeper/msg_server_test.go | 31 + x/epoching/testepoching/helper.go | 10 + x/epoching/types/epoching.go | 13 +- x/epoching/types/epoching.pb.go | 299 +- x/epoching/types/errors.go | 4 +- x/epoching/types/events.pb.go | 412 +- x/epoching/types/msg.go | 31 +- x/epoching/types/msg_test.go | 35 +- x/epoching/types/tx.pb.go | 415 +- 21 files changed, 2314 insertions(+), 4423 deletions(-) diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 6da7695d5..8239c4496 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -1347,6 +1347,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1386,7 +1390,6 @@ paths: name "y.z". - JSON @@ -1468,6 +1471,22 @@ paths: delegation val_addr: type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. + + + NOTE: The amount field is an Int which implements + the custom method + + signatures required by gogoproto. block_height: type: string format: uint64 @@ -1600,6 +1619,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1639,7 +1662,6 @@ paths: name "y.z". - JSON @@ -1715,96 +1737,23 @@ paths: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in - this epoch. + last_block_time is the time of the last block in this + epoch. - Babylon needs to remember the last header of each epoch - to complete + Babylon needs to remember the last header's time of each + epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the + finalised. The last_block_time field is nil in the epoch's beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte @@ -1813,90 +1762,19 @@ paths: epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer header, + i.e., 2nd header + + of the next epoch This validator set has generated a BLS multisig on `app_hash` of the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. title: Epoch is a structure that contains the metadata of an epoch pagination: title: pagination defines the pagination in the response @@ -2043,6 +1921,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2082,7 +1964,6 @@ paths: name "y.z". - JSON @@ -2202,96 +2083,23 @@ paths: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to - complete + Babylon needs to remember the last header's time of each + epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the - epoch's beginning, and + finalised. The last_block_time field is nil in the epoch's + beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing - a block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte @@ -2300,90 +2108,19 @@ paths: epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer header, + i.e., 2nd header + + of the next epoch This validator set has generated a BLS multisig on `app_hash` of the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing - a block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. title: Epoch is a structure that contains the metadata of an epoch title: >- QueryEpochInfoRequest is the response type for the Query/EpochInfo @@ -2501,6 +2238,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2540,7 +2281,6 @@ paths: name "y.z". - JSON @@ -2782,6 +2522,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2821,7 +2565,6 @@ paths: name "y.z". - JSON @@ -2968,24 +2711,60 @@ paths: of coins from a delegator and source validator to a destination validator. - title: >- - QueuedMessage is a message that can change the validator set - and is delayed - - to the epoch boundary - title: msgs is the list of messages queued in the current epoch - pagination: - title: pagination defines the pagination in the response - type: object - properties: - next_key: - type: string - format: byte - description: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently. It will be empty if - there are no more results. - total: + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. + + + NOTE: The amount field is an Int which implements + the custom method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding + delegation entry balance + creation_height: + type: string + format: int64 + description: >- + creation_height is the height which the unbonding + took place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for + performing a cancel unbonding delegation for delegator + title: >- + QueuedMessage is a message that can change the validator set + and is delayed + + to the epoch boundary + title: msgs is the list of messages queued in the current epoch + pagination: + title: pagination defines the pagination in the response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: type: string format: uint64 title: >- @@ -3121,6 +2900,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3160,7 +2943,6 @@ paths: name "y.z". - JSON @@ -3437,6 +3219,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3476,7 +3262,6 @@ paths: name "y.z". - JSON @@ -3792,6 +3577,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3832,7 +3621,6 @@ paths: name "y.z". - JSON @@ -3979,6 +3767,43 @@ paths: of coins from a delegator and source validator to a destination validator. + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and + an amount. + + + NOTE: The amount field is an Int which + implements the custom method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to + unbonding delegation entry balance + creation_height: + type: string + format: int64 + description: >- + creation_height is the height which the + unbonding took place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK + message for performing a cancel unbonding + delegation for delegator title: >- QueuedMessage is a message that can change the validator set and is delayed @@ -4138,6 +3963,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4177,7 +4006,6 @@ paths: name "y.z". - JSON @@ -4423,6 +4251,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4462,7 +4294,6 @@ paths: name "y.z". - JSON @@ -4672,6 +4503,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4711,7 +4546,6 @@ paths: name "y.z". - JSON @@ -5048,8 +4882,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS sigs + are signed on bitmap: @@ -5131,8 +4965,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS + sigs are signed on bitmap: @@ -5297,8 +5131,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS + sigs are signed on bitmap: @@ -5535,8 +5369,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS + sigs are signed on bitmap: @@ -5792,88 +5626,22 @@ paths: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing - a block in the blockchain, - - including all blockchain data structures and the rules - of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -5924,88 +5692,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -6174,6 +5876,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6213,7 +5919,6 @@ paths: name "y.z". - JSON @@ -6426,6 +6131,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6465,7 +6174,6 @@ paths: name "y.z". - JSON @@ -6612,88 +6320,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -6748,92 +6390,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures - and the rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the + babylon block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: >- - hashes from the app output from the prev - block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: >- - Header defines the structure of a block - header. + header babylon_epoch: type: string format: uint64 @@ -7016,6 +6588,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7055,7 +6631,6 @@ paths: name "y.z". - JSON @@ -7153,88 +6728,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -7289,92 +6798,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures - and the rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the + babylon block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: >- - hashes from the app output from the prev - block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: >- - Header defines the structure of a block - header. + header babylon_epoch: type: string format: uint64 @@ -7560,6 +6999,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7599,7 +7042,6 @@ paths: name "y.z". - JSON @@ -7702,88 +7144,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -7837,90 +7213,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and - the rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: >- - hashes from the app output from the prev - block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the + babylon block that includes this CZ + + header babylon_epoch: type: string format: uint64 @@ -8000,96 +7308,23 @@ paths: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to - complete + Babylon needs to remember the last header's time of each + epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the - epoch's beginning, and + finalised. The last_block_time field is nil in the epoch's + beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing - a block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte @@ -8098,90 +7333,19 @@ paths: epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer header, + i.e., 2nd header + + of the next epoch This validator set has generated a BLS multisig on `app_hash` of the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing - a block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. raw_checkpoint: title: raw_checkpoint is the raw checkpoint of this epoch type: object @@ -8196,8 +7360,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS sigs + are signed on bitmap: @@ -8569,6 +7733,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -8608,7 +7776,6 @@ paths: name "y.z". - JSON @@ -8730,90 +7897,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and - the rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: >- - hashes from the app output from the prev - block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the + babylon block that includes this CZ + + header babylon_epoch: type: string format: uint64 @@ -8868,93 +7967,22 @@ paths: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the + babylon_header_hash is the hash of the + babylon block that includes this CZ + + header + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules - for processing a block in the - blockchain, - - including all blockchain data structures - and the rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: >- - hashes from the app output from the prev - block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: >- - Header defines the structure of a block - header. babylon_epoch: type: string format: uint64 @@ -9036,96 +8064,23 @@ paths: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each - epoch to complete + Babylon needs to remember the last header's time of + each epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the + finalised. The last_block_time field is nil in the epoch's beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte @@ -9134,90 +8089,19 @@ paths: this epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer + header, i.e., 2nd header + + of the next epoch This validator set has generated a BLS multisig on `app_hash` of the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. raw_checkpoint: title: raw_checkpoint is the raw checkpoint of this epoch type: object @@ -9232,8 +8116,8 @@ paths: type: string format: byte title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are + app_hash defines the 'AppHash' that individual BLS + sigs are signed on bitmap: @@ -9614,6 +8498,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -9653,7 +8541,6 @@ paths: name "y.z". - JSON @@ -9752,93 +8639,27 @@ paths: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. - babylon_epoch: + babylon_header_height: type: string format: uint64 title: >- - epoch is the epoch number of this header on Babylon + babylon_header_height is the height of the babylon block + that includes this CZ + + header + babylon_epoch: + type: string + format: uint64 + title: >- + epoch is the epoch number of this header on Babylon ledger babylon_tx_hash: type: string @@ -10000,6 +8821,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10039,7 +8864,6 @@ paths: name "y.z". - JSON @@ -10183,88 +9007,22 @@ paths: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -10402,6 +9160,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10441,7 +9203,6 @@ paths: name "y.z". - JSON @@ -10630,6 +9391,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10669,7 +9434,6 @@ paths: name "y.z". - JSON @@ -11528,6 +10292,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -11563,7 +10331,6 @@ definitions: name "y.z". - JSON @@ -11704,6 +10471,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -11739,7 +10510,6 @@ definitions: name "y.z". - JSON @@ -12028,6 +10798,21 @@ definitions: title: BondState is the bond state of a validator or delegation val_addr: type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. block_height: type: string format: uint64 @@ -12063,6 +10848,18 @@ definitions: title: BondState is the bond state of a validator or delegation val_addr: type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. block_height: type: string format: uint64 @@ -12084,183 +10881,39 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this epoch. + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to complete + Babylon needs to remember the last header's time of each epoch to + complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the epoch's - beginning, and + finalised. The last_block_time field is nil in the epoch's beginning, + and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block in - the blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte title: |- app_hash_root is the Merkle root of all AppHashs in this epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch - - This validator set has generated a BLS multisig on `app_hash` - of + sealer_header_hash is the app_hash of the sealer header, i.e., 2nd + header - the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block in - the blockchain, + of the next epoch - including all blockchain data structures and the rules of the - application's + This validator set has generated a BLS multisig on `app_hash` of - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + the sealer header title: Epoch is a structure that contains the metadata of an epoch babylon.epoching.v1.Params: type: object @@ -12318,6 +10971,21 @@ definitions: title: BondState is the bond state of a validator or delegation val_addr: type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. block_height: type: string format: uint64 @@ -12350,185 +11018,40 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this epoch. + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to + Babylon needs to remember the last header's time of each epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the epoch's + finalised. The last_block_time field is nil in the epoch's beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, + app_hash_root: + type: string + format: byte + title: |- + app_hash_root is the Merkle root of all AppHashs in this epoch + It will be used for proving a block is in an epoch + sealer_header_hash: + type: string + format: byte + title: >- + sealer_header_hash is the app_hash of the sealer header, i.e., 2nd + header - including all blockchain data structures and the rules of the - application's + of the next epoch - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - app_hash_root: - type: string - format: byte - title: |- - app_hash_root is the Merkle root of all AppHashs in this epoch - It will be used for proving a block is in an epoch - sealer_header: - title: >- - sealer_header is the 2nd header of the next epoch - - This validator set has generated a BLS multisig on - `app_hash` of + This validator set has generated a BLS multisig on `app_hash` of the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. title: Epoch is a structure that contains the metadata of an epoch title: QueryEpochInfoRequest is the response type for the Query/EpochInfo method babylon.epoching.v1.QueryEpochMsgsResponse: @@ -12713,6 +11236,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -12752,7 +11279,6 @@ definitions: name "y.z". - JSON @@ -12893,6 +11419,41 @@ definitions: of coins from a delegator and source validator to a destination validator. + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding delegation + entry balance + creation_height: + type: string + format: int64 + description: >- + creation_height is the height which the unbonding took + place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for + performing a cancel unbonding delegation for delegator title: >- QueuedMessage is a message that can change the validator set and is delayed @@ -12995,185 +11556,40 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this epoch. + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to - complete + Babylon needs to remember the last header's time of each epoch + to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the epoch's + finalised. The last_block_time field is nil in the epoch's beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte title: |- app_hash_root is the Merkle root of all AppHashs in this epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer header, i.e., + 2nd header - This validator set has generated a BLS multisig on - `app_hash` of - - the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, + of the next epoch - including all blockchain data structures and the rules of - the application's + This validator set has generated a BLS multisig on `app_hash` of - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + the sealer header title: Epoch is a structure that contains the metadata of an epoch pagination: title: pagination defines the pagination in the response @@ -13399,6 +11815,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13438,7 +11858,6 @@ definitions: name "y.z". - JSON @@ -13585,6 +12004,42 @@ definitions: of coins from a delegator and source validator to a destination validator. + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. + + + NOTE: The amount field is an Int which implements the + custom method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding + delegation entry balance + creation_height: + type: string + format: int64 + description: >- + creation_height is the height which the unbonding took + place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for + performing a cancel unbonding delegation for delegator title: >- QueuedMessage is a message that can change the validator set and is delayed @@ -13856,6 +12311,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13892,7 +12351,6 @@ definitions: name "y.z". - JSON @@ -14024,10 +12482,43 @@ definitions: of coins from a delegator and source validator to a destination validator. - title: >- - QueuedMessage is a message that can change the validator set and is - delayed - + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding delegation entry + balance + creation_height: + type: string + format: int64 + description: creation_height is the height which the unbonding took place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for performing a + cancel unbonding delegation for delegator + title: >- + QueuedMessage is a message that can change the validator set and is + delayed + to the epoch boundary babylon.epoching.v1.QueuedMessageList: type: object @@ -14214,6 +12705,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -14253,7 +12748,6 @@ definitions: name "y.z". - JSON @@ -14394,6 +12888,41 @@ definitions: of coins from a delegator and source validator to a destination validator. + msg_cancel_unbonding_delegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding delegation + entry balance + creation_height: + type: string + format: int64 + description: >- + creation_height is the height which the unbonding took + place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for + performing a cancel unbonding delegation for delegator title: >- QueuedMessage is a message that can change the validator set and is delayed @@ -14557,6 +13086,36 @@ definitions: description: |- MsgBeginRedelegate defines a SDK message for performing a redelegation of coins from a delegator and source validator to a destination validator. + cosmos.staking.v1beta1.MsgCancelUnbondingDelegation: + type: object + properties: + delegator_address: + type: string + validator_address: + type: string + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. + title: >- + amount is always less than or equal to unbonding delegation entry + balance + creation_height: + type: string + format: int64 + description: creation_height is the height which the unbonding took place. + description: 'Since: cosmos-sdk 0.46' + title: >- + MsgCancelUnbondingDelegation defines the SDK message for performing a + cancel unbonding delegation for delegator cosmos.staking.v1beta1.MsgCreateValidator: type: object properties: @@ -14700,6 +13259,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -14735,7 +13298,6 @@ definitions: name "y.z". - JSON @@ -14826,127 +13388,6 @@ definitions: description: |- MsgUndelegate defines a SDK message for performing an undelegation from a delegate and a validator. - tendermint.types.BlockID: - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - title: BlockID - tendermint.types.Header: - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block in the - blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. - tendermint.types.PartSetHeader: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - tendermint.version.Consensus: - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block in the - blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. babylon.checkpointing.v1.CheckpointStateUpdate: type: object properties: @@ -15081,10 +13522,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that individual BLS - sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15122,10 +13561,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15262,10 +13699,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that individual - BLS sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15384,10 +13819,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15537,10 +13970,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that individual BLS sigs - are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15570,10 +14001,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that individual BLS - sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -15706,88 +14135,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -15836,88 +14199,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -16016,88 +14313,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -16147,88 +14378,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -16308,213 +14473,66 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: + last_block_time: + type: string + format: date-time description: >- - last_block_header is the header of the last block in this epoch. + last_block_time is the time of the last block in this epoch. - Babylon needs to remember the last header of each epoch to + Babylon needs to remember the last header's time of each epoch to complete unbonding validators/delegations when a previous epoch's checkpoint is - finalised. The last_block_header field is nil in the epoch's + finalised. The last_block_time field is nil in the epoch's beginning, and is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte app_hash_root: type: string format: byte title: |- app_hash_root is the Merkle root of all AppHashs in this epoch It will be used for proving a block is in an epoch - sealer_header: + sealer_header_hash: + type: string + format: byte title: >- - sealer_header is the 2nd header of the next epoch + sealer_header_hash is the app_hash of the sealer header, i.e., 2nd + header - This validator set has generated a BLS multisig on - `app_hash` of + of the next epoch - the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, + This validator set has generated a BLS multisig on `app_hash` of - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. - raw_checkpoint: - title: raw_checkpoint is the raw checkpoint of this epoch - type: object - properties: - epoch_num: - type: string - format: uint64 - title: epoch_num defines the epoch number the raw checkpoint is for - app_hash: - type: string - format: byte - title: >- - app_hash defines the 'AppHash' that individual BLS - sigs are - - signed on - bitmap: - type: string - format: byte - title: >- - bitmap defines the bitmap that indicates the signers of the BLS - multi sig - bls_multi_sig: - type: string - format: byte - title: >- - bls_multi_sig defines the multi sig that is aggregated from - individual BLS + the sealer header + raw_checkpoint: + title: raw_checkpoint is the raw checkpoint of this epoch + type: object + properties: + epoch_num: + type: string + format: uint64 + title: epoch_num defines the epoch number the raw checkpoint is for + app_hash: + type: string + format: byte + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are + signed on + bitmap: + type: string + format: byte + title: >- + bitmap defines the bitmap that indicates the signers of the BLS + multi sig + bls_multi_sig: + type: string + format: byte + title: >- + bls_multi_sig defines the multi sig that is aggregated from + individual BLS sigs btc_submission_key: @@ -16625,8 +14643,8 @@ definitions: title: >- validator_set is the validator set of the sealed epoch - This validator set has generated a BLS multisig on - `app_hash` of + This validator set has generated a BLS multisig on `app_hash` + of the sealer header proof_epoch_info: @@ -16783,88 +14801,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes - this CZ + babylon_header_hash is the hash of the babylon block that + includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -16932,88 +14884,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes this - CZ + babylon_header_hash is the hash of the babylon block that includes + this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block in - the blockchain, - - including all blockchain data structures and the rules of the - application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that includes + this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -17062,12 +14948,9 @@ definitions: bls public key - title: >- + title: |- validator_set is the validator set of the sealed epoch - - This validator set has generated a BLS multisig on `app_hash` - of - + This validator set has generated a BLS multisig on `app_hash` of the sealer header proof_epoch_info: title: >- @@ -17219,12 +15102,9 @@ definitions: its bls public key - title: >- + title: |- validator_set is the validator set of the sealed epoch - - This validator set has generated a BLS multisig on - `app_hash` of - + This validator set has generated a BLS multisig on `app_hash` of the sealer header proof_epoch_info: title: >- @@ -17425,88 +15305,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -17557,88 +15371,22 @@ definitions: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -17747,88 +15495,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -17879,88 +15561,22 @@ definitions: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -18070,88 +15686,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -18201,88 +15751,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -18362,185 +15846,40 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: - description: >- - last_block_header is the header of the last block in this epoch. - - Babylon needs to remember the last header of each epoch to - complete - - unbonding validators/delegations when a previous epoch's - checkpoint is - - finalised. The last_block_header field is nil in the epoch's - beginning, and - - is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - app_hash_root: + last_block_time: type: string - format: byte - title: |- - app_hash_root is the Merkle root of all AppHashs in this epoch - It will be used for proving a block is in an epoch - sealer_header: - title: >- - sealer_header is the 2nd header of the next epoch - - This validator set has generated a BLS multisig on - `app_hash` of - - the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + format: date-time + description: >- + last_block_time is the time of the last block in this epoch. + + Babylon needs to remember the last header's time of each epoch to + complete + + unbonding validators/delegations when a previous epoch's + checkpoint is + + finalised. The last_block_time field is nil in the epoch's + beginning, and + + is set upon the end of this epoch. + app_hash_root: + type: string + format: byte + title: |- + app_hash_root is the Merkle root of all AppHashs in this epoch + It will be used for proving a block is in an epoch + sealer_header_hash: + type: string + format: byte + title: >- + sealer_header_hash is the app_hash of the sealer header, i.e., 2nd + header + + of the next epoch + + This validator set has generated a BLS multisig on `app_hash` of + + the sealer header raw_checkpoint: title: raw_checkpoint is the raw checkpoint of this epoch type: object @@ -18552,10 +15891,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that individual BLS - sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -18679,8 +16016,8 @@ definitions: title: >- validator_set is the validator set of the sealed epoch - This validator set has generated a BLS multisig on - `app_hash` of + This validator set has generated a BLS multisig on `app_hash` + of the sealer header proof_epoch_info: @@ -18854,88 +16191,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that - includes this CZ + babylon_header_hash is the hash of the babylon block + that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and the - rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -18988,88 +16259,22 @@ definitions: before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block - that includes this CZ + babylon_header_hash is the hash of the babylon + block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for - processing a block in the blockchain, - - including all blockchain data structures and - the rules of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon + block that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -19149,188 +16354,43 @@ definitions: first_block_height: type: string format: uint64 - last_block_header: - description: >- - last_block_header is the header of the last block in this - epoch. - - Babylon needs to remember the last header of each epoch to - complete - - unbonding validators/delegations when a previous epoch's - checkpoint is - - finalised. The last_block_header field is nil in the epoch's - beginning, and - - is set upon the end of this epoch. - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - app_hash_root: - type: string - format: byte - title: >- - app_hash_root is the Merkle root of all AppHashs in this - epoch - - It will be used for proving a block is in an epoch - sealer_header: - title: >- - sealer_header is the 2nd header of the next epoch - - This validator set has generated a BLS multisig on - `app_hash` of - - the sealer header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's - - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + last_block_time: + type: string + format: date-time + description: >- + last_block_time is the time of the last block in this epoch. + + Babylon needs to remember the last header's time of each + epoch to complete + + unbonding validators/delegations when a previous epoch's + checkpoint is + + finalised. The last_block_time field is nil in the epoch's + beginning, and + + is set upon the end of this epoch. + app_hash_root: + type: string + format: byte + title: >- + app_hash_root is the Merkle root of all AppHashs in this + epoch + + It will be used for proving a block is in an epoch + sealer_header_hash: + type: string + format: byte + title: >- + sealer_header_hash is the app_hash of the sealer header, + i.e., 2nd header + + of the next epoch + + This validator set has generated a BLS multisig on + `app_hash` of + + the sealer header raw_checkpoint: title: raw_checkpoint is the raw checkpoint of this epoch type: object @@ -19342,10 +16402,8 @@ definitions: app_hash: type: string format: byte - title: >- - app_hash defines the 'AppHash' that - individual BLS sigs are - + title: |- + app_hash defines the 'AppHash' that individual BLS sigs are signed on bitmap: type: string @@ -19630,88 +16688,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a block - in the blockchain, - - including all blockchain data structures and the rules of the - application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -19760,88 +16752,22 @@ definitions: validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that + babylon_header_hash is the hash of the babylon block that includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules - of the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block + that includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -19919,88 +16845,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes - this CZ + babylon_header_hash is the hash of the babylon block that + includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 @@ -20052,88 +16912,22 @@ definitions: time is the timestamp of this header on CZ ledger it is needed for CZ to unbond all mature validators/delegations before this timestamp when this header is BTC-finalised - babylon_header: + babylon_header_hash: + type: string + format: byte title: >- - babylon_header is the header of the babylon block that includes - this CZ + babylon_header_hash is the hash of the babylon block that + includes this CZ header - type: object - properties: - version: - title: basic block info - type: object - properties: - block: - type: string - format: uint64 - app: - type: string - format: uint64 - description: >- - Consensus captures the consensus rules for processing a - block in the blockchain, - - including all blockchain data structures and the rules of - the application's + babylon_header_height: + type: string + format: uint64 + title: >- + babylon_header_height is the height of the babylon block that + includes this CZ - state transition machine. - chain_id: - type: string - height: - type: string - format: int64 - time: - type: string - format: date-time - last_block_id: - title: prev block info - type: object - properties: - hash: - type: string - format: byte - part_set_header: - type: object - properties: - total: - type: integer - format: int64 - hash: - type: string - format: byte - title: PartsetHeader - app_hash: - type: string - format: byte - title: hashes of block data - data_hash: - type: string - format: byte - validators_hash: - type: string - format: byte - title: hashes from the app output from the prev block - next_validators_hash: - type: string - format: byte - consensus_hash: - type: string - format: byte - app_hash: - type: string - format: byte - last_results_hash: - type: string - format: byte - evidence_hash: - type: string - format: byte - title: consensus info - proposer_address: - type: string - format: byte - description: Header defines the structure of a block header. + header babylon_epoch: type: string format: uint64 diff --git a/proto/babylon/epoching/v1/epoching.proto b/proto/babylon/epoching/v1/epoching.proto index 802d86404..fc8686f3e 100644 --- a/proto/babylon/epoching/v1/epoching.proto +++ b/proto/babylon/epoching/v1/epoching.proto @@ -4,6 +4,7 @@ package babylon.epoching.v1; import "google/protobuf/timestamp.proto"; import "gogoproto/gogo.proto"; import "cosmos/staking/v1beta1/tx.proto"; +import "cosmos/base/v1beta1/coin.proto"; option go_package = "github.com/babylonchain/babylon/x/epoching/types"; @@ -46,7 +47,7 @@ message QueuedMessage { cosmos.staking.v1beta1.MsgDelegate msg_delegate = 6; cosmos.staking.v1beta1.MsgUndelegate msg_undelegate = 7; cosmos.staking.v1beta1.MsgBeginRedelegate msg_begin_redelegate = 8; - // TODO: after we bump to Cosmos SDK v0.46, add MsgCancelUnbondingDelegation + cosmos.staking.v1beta1.MsgCancelUnbondingDelegation msg_cancel_unbonding_delegation = 9; } } @@ -90,8 +91,9 @@ message ValidatorLifecycle { message DelegationStateUpdate { BondState state = 1; string val_addr = 2; - uint64 block_height = 3; - google.protobuf.Timestamp block_time = 4 [ (gogoproto.stdtime) = true ]; + cosmos.base.v1beta1.Coin amount = 3; + uint64 block_height = 4; + google.protobuf.Timestamp block_time = 5 [ (gogoproto.stdtime) = true ]; } // ValidatorLifecycle is a message that records records the lifecycle of diff --git a/proto/babylon/epoching/v1/events.proto b/proto/babylon/epoching/v1/events.proto index ec210e2f8..f41c527e3 100644 --- a/proto/babylon/epoching/v1/events.proto +++ b/proto/babylon/epoching/v1/events.proto @@ -63,3 +63,13 @@ message EventWrappedBeginRedelegate { string denom = 5; uint64 epoch_boundary = 6; } + +// EventWrappedCancelUnbondingDelegation is the event emitted when a +// MsgWrappedCancelUnbondingDelegation has been queued +message EventWrappedCancelUnbondingDelegation { + string delegator_address = 1; + string validator_address = 2; + uint64 amount = 3; + int64 creation_height = 4; + uint64 epoch_boundary = 5; +} diff --git a/proto/babylon/epoching/v1/tx.proto b/proto/babylon/epoching/v1/tx.proto index 0f1c83f3c..47f24da33 100644 --- a/proto/babylon/epoching/v1/tx.proto +++ b/proto/babylon/epoching/v1/tx.proto @@ -28,6 +28,11 @@ service Msg { rpc WrappedBeginRedelegate(MsgWrappedBeginRedelegate) returns (MsgWrappedBeginRedelegateResponse); + // WrappedCancelUnbondingDelegation defines a method for cancelling unbonding of + // coins from a delegator and source validator to a destination validator. + rpc WrappedCancelUnbondingDelegation(MsgWrappedCancelUnbondingDelegation) + returns (MsgWrappedCancelUnbondingDelegationResponse); + // UpdateParams defines a method for updating epoching module parameters. rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } @@ -71,6 +76,20 @@ message MsgWrappedBeginRedelegate { // MsgWrappedBeginRedelegate message message MsgWrappedBeginRedelegateResponse {} +// MsgWrappedCancelUnbondingDelegation is the message for cancelling +// an unbonding delegation +message MsgWrappedCancelUnbondingDelegation { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "msg"; + + cosmos.staking.v1beta1.MsgCancelUnbondingDelegation msg = 1; +} + +// MsgWrappedCancelUnbondingDelegationResponse is the response to the +// MsgWrappedCancelUnbondingDelegation message +message MsgWrappedCancelUnbondingDelegationResponse {} + // MsgUpdateParams defines a message for updating epoching module parameters. message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; diff --git a/x/epoching/client/cli/tx.go b/x/epoching/client/cli/tx.go index c72da9c14..42923beaf 100644 --- a/x/epoching/client/cli/tx.go +++ b/x/epoching/client/cli/tx.go @@ -2,9 +2,11 @@ package cli import ( "fmt" - "github.com/spf13/cobra" + "strconv" "strings" + "github.com/spf13/cobra" + "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cosmos/cosmos-sdk/client" @@ -29,6 +31,7 @@ func GetTxCmd() *cobra.Command { NewDelegateCmd(), NewRedelegateCmd(), NewUnbondCmd(), + NewCancelUnbondingCmd(), ) return cmd @@ -129,6 +132,56 @@ $ %s tx epoching redelegate %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj %s1l2rsakp return cmd } +func NewCancelUnbondingCmd() *cobra.Command { + bech32PrefixValAddr := params.Bech32PrefixAccAddr + denom := params.DefaultBondDenom + + cmd := &cobra.Command{ + Use: "cancel-unbond [validator-addr] [amount] [creation-height]", + Short: "Cancel unbonding delegation and delegate back to the validator", + Args: cobra.ExactArgs(3), + Long: strings.TrimSpace( + fmt.Sprintf(`Cancel Unbonding Delegation and delegate back to the validator. + +Example: +$ %s tx staking cancel-unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100%s 2 --from mykey +`, + version.AppName, bech32PrefixValAddr, denom, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + delAddr := clientCtx.GetFromAddress() + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + amount, err := sdk.ParseCoinNormalized(args[1]) + if err != nil { + return err + } + + creationHeight, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return err + } + + stakingMsg := stakingtypes.NewMsgCancelUnbondingDelegation(delAddr.String(), valAddr.String(), creationHeight, amount) + msg := types.NewMsgWrappedCancelUnbondingDelegation(stakingMsg) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + func NewUnbondCmd() *cobra.Command { bech32PrefixValAddr := params.Bech32PrefixAccAddr denom := params.DefaultBondDenom diff --git a/x/epoching/keeper/drop_validator_msg_decorator.go b/x/epoching/keeper/drop_validator_msg_decorator.go index ca982c7b3..54fff1349 100644 --- a/x/epoching/keeper/drop_validator_msg_decorator.go +++ b/x/epoching/keeper/drop_validator_msg_decorator.go @@ -24,7 +24,7 @@ func NewDropValidatorMsgDecorator(ek Keeper) *DropValidatorMsgDecorator { // - MsgDelegate // - MsgUndelegate // - MsgBeginRedelegate -// TODO (non-urgent): after we bump to Cosmos SDK v0.46, add MsgCancelUnbondingDelegation +// - MsgCancelUnbondingDelegation func (qmd DropValidatorMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { // skip if at genesis block, as genesis state contains txs that bootstrap the initial validator set if ctx.HeaderInfo().Height == 0 { @@ -43,7 +43,7 @@ func (qmd DropValidatorMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu // IsValidatorRelatedMsg checks if the given message is of non-wrapped type, which should be rejected func (qmd DropValidatorMsgDecorator) IsValidatorRelatedMsg(msg sdk.Msg) bool { switch msg.(type) { - case *stakingtypes.MsgCreateValidator, *stakingtypes.MsgDelegate, *stakingtypes.MsgUndelegate, *stakingtypes.MsgBeginRedelegate: + case *stakingtypes.MsgCreateValidator, *stakingtypes.MsgDelegate, *stakingtypes.MsgUndelegate, *stakingtypes.MsgBeginRedelegate, *stakingtypes.MsgCancelUnbondingDelegation: return true default: return false diff --git a/x/epoching/keeper/drop_validator_msg_decorator_test.go b/x/epoching/keeper/drop_validator_msg_decorator_test.go index e6c5ffa17..a6411e373 100644 --- a/x/epoching/keeper/drop_validator_msg_decorator_test.go +++ b/x/epoching/keeper/drop_validator_msg_decorator_test.go @@ -19,6 +19,7 @@ func TestDropValidatorMsgDecorator(t *testing.T) { {&stakingtypes.MsgDelegate{}, true}, {&stakingtypes.MsgUndelegate{}, true}, {&stakingtypes.MsgBeginRedelegate{}, true}, + {&stakingtypes.MsgCancelUnbondingDelegation{}, true}, // allowed message types {&stakingtypes.MsgEditValidator{}, false}, } diff --git a/x/epoching/keeper/epoch_msg_queue.go b/x/epoching/keeper/epoch_msg_queue.go index 7e5706b32..52970fc52 100644 --- a/x/epoching/keeper/epoch_msg_queue.go +++ b/x/epoching/keeper/epoch_msg_queue.go @@ -2,8 +2,9 @@ package keeper import ( "context" - storetypes "cosmossdk.io/store/types" "fmt" + + storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/runtime" errorsmod "cosmossdk.io/errors" @@ -147,11 +148,12 @@ func (k Keeper) HandleQueuedMsg(ctx context.Context, msg *types.QueuedMessage) ( if err != nil { return nil, err } + amount := &unwrappedMsg.MsgCreateValidator.Value // self-bonded to the created validator - if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_CREATED); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_CREATED); err != nil { return nil, err } - if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_BONDED); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_BONDED); err != nil { return nil, err } case *types.QueuedMessage_MsgDelegate: @@ -163,11 +165,12 @@ func (k Keeper) HandleQueuedMsg(ctx context.Context, msg *types.QueuedMessage) ( if err != nil { return nil, err } + amount := &unwrappedMsg.MsgDelegate.Amount // created and bonded to the validator - if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_CREATED); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_CREATED); err != nil { return nil, err } - if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_BONDED); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_BONDED); err != nil { return nil, err } case *types.QueuedMessage_MsgUndelegate: @@ -179,9 +182,10 @@ func (k Keeper) HandleQueuedMsg(ctx context.Context, msg *types.QueuedMessage) ( if err != nil { return nil, err } + amount := &unwrappedMsg.MsgUndelegate.Amount // unbonding from the validator // (in `ApplyMatureUnbonding`) AFTER mature, unbonded from the validator - if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, types.BondState_UNBONDING); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_UNBONDING); err != nil { return nil, err } case *types.QueuedMessage_MsgBeginRedelegate: @@ -193,9 +197,24 @@ func (k Keeper) HandleQueuedMsg(ctx context.Context, msg *types.QueuedMessage) ( if err != nil { return nil, err } + amount := &unwrappedMsg.MsgBeginRedelegate.Amount // unbonding from the source validator // (in `ApplyMatureUnbonding`) AFTER mature, unbonded from the source validator, created/bonded to the destination validator - if err := k.RecordNewDelegationState(ctx, delAddr, srcValAddr, types.BondState_UNBONDING); err != nil { + if err := k.RecordNewDelegationState(ctx, delAddr, srcValAddr, amount, types.BondState_UNBONDING); err != nil { + return nil, err + } + case *types.QueuedMessage_MsgCancelUnbondingDelegation: + delAddr, err := sdk.AccAddressFromBech32(unwrappedMsg.MsgCancelUnbondingDelegation.DelegatorAddress) + if err != nil { + return nil, err + } + valAddr, err := sdk.ValAddressFromBech32(unwrappedMsg.MsgCancelUnbondingDelegation.ValidatorAddress) + if err != nil { + return nil, err + } + amount := &unwrappedMsg.MsgCancelUnbondingDelegation.Amount + // this delegation is now bonded again + if err := k.RecordNewDelegationState(ctx, delAddr, valAddr, amount, types.BondState_BONDED); err != nil { return nil, err } default: diff --git a/x/epoching/keeper/hooks.go b/x/epoching/keeper/hooks.go index 30cd92fc4..46cb2a758 100644 --- a/x/epoching/keeper/hooks.go +++ b/x/epoching/keeper/hooks.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "cosmossdk.io/math" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/epoching/types" @@ -104,7 +105,7 @@ func (h Hooks) AfterValidatorBeginUnbonding(ctx context.Context, consAddr sdk.Co } func (h Hooks) BeforeDelegationRemoved(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error { - return h.k.RecordNewDelegationState(sdk.UnwrapSDKContext(ctx), delAddr, valAddr, types.BondState_REMOVED) + return h.k.RecordNewDelegationState(sdk.UnwrapSDKContext(ctx), delAddr, valAddr, nil, types.BondState_REMOVED) } func (h Hooks) AfterUnbondingInitiated(ctx context.Context, id uint64) error { diff --git a/x/epoching/keeper/lifecycle_delegation.go b/x/epoching/keeper/lifecycle_delegation.go index 669e581f6..d00859908 100644 --- a/x/epoching/keeper/lifecycle_delegation.go +++ b/x/epoching/keeper/lifecycle_delegation.go @@ -12,7 +12,7 @@ import ( // TODO: add more tests on the lifecycle record // RecordNewDelegationState adds a state for a delegation lifecycle, including created, bonded, unbonding and unbonded -func (k Keeper) RecordNewDelegationState(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, state types.BondState) error { +func (k Keeper) RecordNewDelegationState(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount *sdk.Coin, state types.BondState) error { lc := k.GetDelegationLifecycle(ctx, delAddr) if lc == nil { lc = &types.DelegationLifecycle{ @@ -25,6 +25,7 @@ func (k Keeper) RecordNewDelegationState(ctx context.Context, delAddr sdk.AccAdd DelegationStateUpdate := types.DelegationStateUpdate{ State: state, ValAddr: valAddr.String(), + Amount: amount, BlockHeight: uint64(height), BlockTime: &time, } diff --git a/x/epoching/keeper/modified_staking.go b/x/epoching/keeper/modified_staking.go index 37a9a88ec..44c193494 100644 --- a/x/epoching/keeper/modified_staking.go +++ b/x/epoching/keeper/modified_staking.go @@ -76,7 +76,8 @@ func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { // Babylon modification: record delegation state // AFTER mature, unbonded from the validator - if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valAddr, types.BondState_UNBONDED); err != nil { + // TODO: find a way to specify amount? + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valAddr, nil, types.BondState_UNBONDED); err != nil { panic(err) } @@ -123,13 +124,14 @@ func (k Keeper) ApplyMatureUnbonding(ctx context.Context, epochNumber uint64) { // Babylon modification: record delegation state // AFTER mature, unbonded from the source validator, created/bonded to the destination validator - if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valSrcAddr, types.BondState_UNBONDED); err != nil { + // TODO: find a way to specify amount? + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valSrcAddr, nil, types.BondState_UNBONDED); err != nil { panic(err) } - if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, types.BondState_CREATED); err != nil { + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, nil, types.BondState_CREATED); err != nil { panic(err) } - if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, types.BondState_BONDED); err != nil { + if err := k.RecordNewDelegationState(currentSdkCtx, delAddr, valDstAddr, nil, types.BondState_BONDED); err != nil { panic(err) } diff --git a/x/epoching/keeper/msg_server.go b/x/epoching/keeper/msg_server.go index e4b8deae8..182ffbd73 100644 --- a/x/epoching/keeper/msg_server.go +++ b/x/epoching/keeper/msg_server.go @@ -193,6 +193,71 @@ func (ms msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.Msg return &types.MsgWrappedBeginRedelegateResponse{}, nil } +// WrappedCancelUnbondingDelegation handles the MsgWrappedCancelUnbondingDelegation request +func (ms msgServer) WrappedCancelUnbondingDelegation(goCtx context.Context, msg *types.MsgWrappedCancelUnbondingDelegation) (*types.MsgWrappedCancelUnbondingDelegationResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // verification rules ported from staking module + if _, err := sdk.AccAddressFromBech32(msg.Msg.DelegatorAddress); err != nil { + return nil, err + } + + if _, err := sdk.ValAddressFromBech32(msg.Msg.ValidatorAddress); err != nil { + return nil, err + } + + if !msg.Msg.Amount.IsValid() || !msg.Msg.Amount.Amount.IsPositive() { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid amount", + ) + } + + if msg.Msg.CreationHeight <= 0 { + return nil, errorsmod.Wrap( + sdkerrors.ErrInvalidRequest, + "invalid height", + ) + } + + bondDenom, err := ms.stk.BondDenom(ctx) + if err != nil { + return nil, err + } + if msg.Msg.Amount.Denom != bondDenom { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Msg.Amount.Denom, bondDenom, + ) + } + + blockHeight := uint64(ctx.BlockHeader().Height) + if blockHeight == 0 { + return nil, types.ErrZeroEpochMsg + } + blockTime := ctx.BlockTime() + txid := tmhash.Sum(ctx.TxBytes()) + queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg) + if err != nil { + return nil, err + } + + ms.EnqueueMsg(ctx, queuedMsg) + err = ctx.EventManager().EmitTypedEvents( + &types.EventWrappedCancelUnbondingDelegation{ + DelegatorAddress: msg.Msg.DelegatorAddress, + ValidatorAddress: msg.Msg.ValidatorAddress, + Amount: msg.Msg.Amount.Amount.Uint64(), + CreationHeight: msg.Msg.CreationHeight, + EpochBoundary: ms.GetEpoch(ctx).GetLastBlockHeight(), + }, + ) + if err != nil { + return nil, err + } + + return &types.MsgWrappedCancelUnbondingDelegationResponse{}, nil +} + // UpdateParams updates the params. // TODO investigate when it is the best time to update the params. We can update them // when the epoch changes, but we can also update them during the epoch and extend diff --git a/x/epoching/keeper/msg_server_test.go b/x/epoching/keeper/msg_server_test.go index e08652432..266de27ef 100644 --- a/x/epoching/keeper/msg_server_test.go +++ b/x/epoching/keeper/msg_server_test.go @@ -102,3 +102,34 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { } } } + +func TestMsgWrappedCancelUnbondingDelegation(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + helper := testepoching.NewHelper(t) + msgSrvr := helper.MsgSrvr + // enter 1st epoch, in which BBN starts handling validator-related msgs + ctx, err := helper.GenAndApplyEmptyBlock(r) + require.NoError(t, err) + + testCases := []struct { + name string + req *stakingtypes.MsgCancelUnbondingDelegation + expectErr bool + }{ + { + "empty wrapped msg", + &stakingtypes.MsgCancelUnbondingDelegation{}, + true, + }, + } + for _, tc := range testCases { + wrappedMsg := types.NewMsgWrappedCancelUnbondingDelegation(tc.req) + + _, err := msgSrvr.WrappedCancelUnbondingDelegation(ctx, wrappedMsg) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } +} diff --git a/x/epoching/testepoching/helper.go b/x/epoching/testepoching/helper.go index bf9fae62b..df6f77915 100644 --- a/x/epoching/testepoching/helper.go +++ b/x/epoching/testepoching/helper.go @@ -212,6 +212,16 @@ func (h *Helper) WrappedBeginRedelegate(delegator sdk.AccAddress, srcVal sdk.Val }) } +// WrappedCancelUnbondingDelegation calls handler to cancel unbonding a delegation +func (h *Helper) WrappedCancelUnbondingDelegation(delegator sdk.AccAddress, val sdk.ValAddress, amount math.Int, creationHeight int64) *sdk.Result { + unbondAmt := sdk.NewCoin(appparams.DefaultBondDenom, amount) + msg := stakingtypes.NewMsgCancelUnbondingDelegation(delegator.String(), val.String(), creationHeight, unbondAmt) + wmsg := types.NewMsgWrappedCancelUnbondingDelegation(msg) + return h.Handle(func(ctx sdk.Context) (proto.Message, error) { + return h.MsgSrvr.WrappedCancelUnbondingDelegation(ctx, wmsg) + }) +} + // Handle executes an action function with the Helper's context, wraps the result into an SDK service result, and performs two assertions before returning it func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Result { res, err := action(h.Ctx) diff --git a/x/epoching/types/epoching.go b/x/epoching/types/epoching.go index 741c67612..2d2bfd6a9 100644 --- a/x/epoching/types/epoching.go +++ b/x/epoching/types/epoching.go @@ -95,8 +95,7 @@ func (e Epoch) ValidateBasic() error { // NewQueuedMessage creates a new QueuedMessage from a wrapped msg // i.e., wrapped -> unwrapped -> QueuedMessage func NewQueuedMessage(blockHeight uint64, blockTime time.Time, txid []byte, msg sdk.Msg) (QueuedMessage, error) { - // marshal the actual msg (MsgDelegate, MsgBeginRedelegate, MsgUndelegate, ...) inside isQueuedMessage_Msg - // TODO (non-urgent): after we bump to Cosmos SDK v0.46, add MsgCancelUnbondingDelegation + // marshal the actual msg (MsgDelegate, MsgBeginRedelegate, MsgUndelegate, MsgCancelUnbondingDelegation) inside isQueuedMessage_Msg var qmsg isQueuedMessage_Msg var msgBytes []byte var err error @@ -122,6 +121,13 @@ func NewQueuedMessage(blockHeight uint64, blockTime time.Time, txid []byte, msg qmsg = &QueuedMessage_MsgUndelegate{ MsgUndelegate: msgWithType.Msg, } + case *MsgWrappedCancelUnbondingDelegation: + if msgBytes, err = msgWithType.Msg.Marshal(); err != nil { + return QueuedMessage{}, err + } + qmsg = &QueuedMessage_MsgCancelUnbondingDelegation{ + MsgCancelUnbondingDelegation: msgWithType.Msg, + } case *stakingtypes.MsgCreateValidator: if msgBytes, err = msgWithType.Marshal(); err != nil { return QueuedMessage{}, err @@ -155,7 +161,6 @@ func (qm QueuedMessage) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error func (qm *QueuedMessage) UnwrapToSdkMsg() sdk.Msg { var unwrappedMsgWithType sdk.Msg - // TODO (non-urgent): after we bump to Cosmos SDK v0.46, add MsgCancelUnbondingDelegation switch unwrappedMsg := qm.Msg.(type) { case *QueuedMessage_MsgCreateValidator: unwrappedMsgWithType = unwrappedMsg.MsgCreateValidator @@ -165,6 +170,8 @@ func (qm *QueuedMessage) UnwrapToSdkMsg() sdk.Msg { unwrappedMsgWithType = unwrappedMsg.MsgUndelegate case *QueuedMessage_MsgBeginRedelegate: unwrappedMsgWithType = unwrappedMsg.MsgBeginRedelegate + case *QueuedMessage_MsgCancelUnbondingDelegation: + unwrappedMsgWithType = unwrappedMsg.MsgCancelUnbondingDelegation default: panic(errorsmod.Wrap(ErrInvalidQueuedMessageType, qm.String())) } diff --git a/x/epoching/types/epoching.pb.go b/x/epoching/types/epoching.pb.go index 10e17c4dd..a0e7dddb0 100644 --- a/x/epoching/types/epoching.pb.go +++ b/x/epoching/types/epoching.pb.go @@ -5,7 +5,7 @@ package types import ( fmt "fmt" - _ "github.com/cometbft/cometbft/proto/tendermint/types" + types1 "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -184,6 +184,7 @@ type QueuedMessage struct { // *QueuedMessage_MsgDelegate // *QueuedMessage_MsgUndelegate // *QueuedMessage_MsgBeginRedelegate + // *QueuedMessage_MsgCancelUnbondingDelegation Msg isQueuedMessage_Msg `protobuf_oneof:"msg"` } @@ -238,11 +239,15 @@ type QueuedMessage_MsgUndelegate struct { type QueuedMessage_MsgBeginRedelegate struct { MsgBeginRedelegate *types.MsgBeginRedelegate `protobuf:"bytes,8,opt,name=msg_begin_redelegate,json=msgBeginRedelegate,proto3,oneof" json:"msg_begin_redelegate,omitempty"` } +type QueuedMessage_MsgCancelUnbondingDelegation struct { + MsgCancelUnbondingDelegation *types.MsgCancelUnbondingDelegation `protobuf:"bytes,9,opt,name=msg_cancel_unbonding_delegation,json=msgCancelUnbondingDelegation,proto3,oneof" json:"msg_cancel_unbonding_delegation,omitempty"` +} -func (*QueuedMessage_MsgCreateValidator) isQueuedMessage_Msg() {} -func (*QueuedMessage_MsgDelegate) isQueuedMessage_Msg() {} -func (*QueuedMessage_MsgUndelegate) isQueuedMessage_Msg() {} -func (*QueuedMessage_MsgBeginRedelegate) isQueuedMessage_Msg() {} +func (*QueuedMessage_MsgCreateValidator) isQueuedMessage_Msg() {} +func (*QueuedMessage_MsgDelegate) isQueuedMessage_Msg() {} +func (*QueuedMessage_MsgUndelegate) isQueuedMessage_Msg() {} +func (*QueuedMessage_MsgBeginRedelegate) isQueuedMessage_Msg() {} +func (*QueuedMessage_MsgCancelUnbondingDelegation) isQueuedMessage_Msg() {} func (m *QueuedMessage) GetMsg() isQueuedMessage_Msg { if m != nil { @@ -307,6 +312,13 @@ func (m *QueuedMessage) GetMsgBeginRedelegate() *types.MsgBeginRedelegate { return nil } +func (m *QueuedMessage) GetMsgCancelUnbondingDelegation() *types.MsgCancelUnbondingDelegation { + if x, ok := m.GetMsg().(*QueuedMessage_MsgCancelUnbondingDelegation); ok { + return x.MsgCancelUnbondingDelegation + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*QueuedMessage) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -314,6 +326,7 @@ func (*QueuedMessage) XXX_OneofWrappers() []interface{} { (*QueuedMessage_MsgDelegate)(nil), (*QueuedMessage_MsgUndelegate)(nil), (*QueuedMessage_MsgBeginRedelegate)(nil), + (*QueuedMessage_MsgCancelUnbondingDelegation)(nil), } } @@ -489,10 +502,11 @@ func (m *ValidatorLifecycle) GetValLife() []*ValStateUpdate { // DelegationStateUpdate is the message that records a state update of a // delegation type DelegationStateUpdate struct { - State BondState `protobuf:"varint,1,opt,name=state,proto3,enum=babylon.epoching.v1.BondState" json:"state,omitempty"` - ValAddr string `protobuf:"bytes,2,opt,name=val_addr,json=valAddr,proto3" json:"val_addr,omitempty"` - BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - BlockTime *time.Time `protobuf:"bytes,4,opt,name=block_time,json=blockTime,proto3,stdtime" json:"block_time,omitempty"` + State BondState `protobuf:"varint,1,opt,name=state,proto3,enum=babylon.epoching.v1.BondState" json:"state,omitempty"` + ValAddr string `protobuf:"bytes,2,opt,name=val_addr,json=valAddr,proto3" json:"val_addr,omitempty"` + Amount *types1.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + BlockHeight uint64 `protobuf:"varint,4,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + BlockTime *time.Time `protobuf:"bytes,5,opt,name=block_time,json=blockTime,proto3,stdtime" json:"block_time,omitempty"` } func (m *DelegationStateUpdate) Reset() { *m = DelegationStateUpdate{} } @@ -542,6 +556,13 @@ func (m *DelegationStateUpdate) GetValAddr() string { return "" } +func (m *DelegationStateUpdate) GetAmount() *types1.Coin { + if m != nil { + return m.Amount + } + return nil +} + func (m *DelegationStateUpdate) GetBlockHeight() uint64 { if m != nil { return m.BlockHeight @@ -682,61 +703,65 @@ func init() { } var fileDescriptor_2f2f209d5311f84c = []byte{ - // 856 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xcf, 0x6e, 0xdb, 0xc6, - 0x13, 0x16, 0xf5, 0xc7, 0xb6, 0x86, 0x92, 0x7f, 0xfa, 0xad, 0x9d, 0x42, 0x35, 0x0a, 0xd9, 0x65, - 0x50, 0xc0, 0x30, 0x0a, 0xb2, 0x56, 0xd3, 0x1e, 0x5b, 0x44, 0x91, 0x50, 0xb9, 0x88, 0x15, 0x94, - 0x8d, 0x7d, 0xe8, 0xa1, 0xc4, 0x92, 0x1c, 0x93, 0x44, 0x48, 0x2e, 0xc1, 0x5d, 0x29, 0xf6, 0xad, - 0x8f, 0x90, 0xe7, 0xc8, 0x93, 0xf4, 0xd0, 0x43, 0x8e, 0xbd, 0xb5, 0xb0, 0x5f, 0xa4, 0xd8, 0x25, - 0x45, 0x4b, 0x8d, 0x90, 0xf4, 0x0f, 0xd0, 0x8b, 0xbd, 0x33, 0xf3, 0xcd, 0xb7, 0xf3, 0x7d, 0x98, - 0x15, 0xc1, 0x70, 0xa9, 0x7b, 0x13, 0xb3, 0xd4, 0xc2, 0x8c, 0x79, 0x61, 0x94, 0x06, 0xd6, 0xe2, - 0xb4, 0x3a, 0x9b, 0x59, 0xce, 0x04, 0x23, 0x7b, 0x25, 0xc6, 0xac, 0xf2, 0x8b, 0xd3, 0x83, 0xc3, - 0x80, 0xb1, 0x20, 0x46, 0x4b, 0x41, 0xdc, 0xf9, 0x95, 0x25, 0xa2, 0x04, 0xb9, 0xa0, 0x49, 0x56, - 0x74, 0x1d, 0xec, 0x07, 0x2c, 0x60, 0xea, 0x68, 0xc9, 0x53, 0x99, 0xfd, 0x48, 0x60, 0xea, 0x63, - 0x9e, 0x44, 0xa9, 0xb0, 0xc4, 0x4d, 0x86, 0xbc, 0xf8, 0x5b, 0x56, 0x0f, 0x3d, 0xc6, 0x13, 0xc6, - 0x2d, 0x2e, 0xe8, 0x8b, 0x62, 0x16, 0x17, 0x05, 0x3d, 0xb5, 0xc4, 0x75, 0x01, 0x30, 0x5e, 0xd7, - 0xa1, 0x35, 0x91, 0x53, 0x90, 0x8f, 0xa1, 0xa3, 0xc6, 0x71, 0xd2, 0x79, 0xe2, 0x62, 0xde, 0xd7, - 0x8e, 0xb4, 0xe3, 0xa6, 0xad, 0xab, 0xdc, 0x4c, 0xa5, 0xc8, 0x23, 0xf8, 0xc0, 0x9b, 0xe7, 0x39, - 0xa6, 0xc2, 0x29, 0xa0, 0x51, 0x2a, 0x30, 0x5f, 0xd0, 0xb8, 0x5f, 0x57, 0xe0, 0xfd, 0xb2, 0xaa, - 0x08, 0xcf, 0xca, 0x1a, 0xf9, 0x14, 0xc8, 0x55, 0x94, 0x73, 0xe1, 0xb8, 0x31, 0xf3, 0x5e, 0x38, - 0x21, 0x46, 0x41, 0x28, 0xfa, 0x0d, 0xd5, 0xd1, 0x53, 0x95, 0x91, 0x2c, 0x4c, 0x55, 0x9e, 0x4c, - 0xe1, 0x7f, 0x31, 0xad, 0xc0, 0xd2, 0x83, 0x7e, 0xf3, 0x48, 0x3b, 0xd6, 0x87, 0x07, 0x66, 0x61, - 0x90, 0xb9, 0x34, 0xc8, 0x7c, 0xbe, 0x34, 0x68, 0xd4, 0x7c, 0xf5, 0xdb, 0xa1, 0x66, 0x77, 0x65, - 0xa3, 0xe2, 0x92, 0x15, 0x62, 0x40, 0x97, 0x66, 0x99, 0x13, 0x52, 0x1e, 0x3a, 0x39, 0x63, 0xa2, - 0xdf, 0x3a, 0xd2, 0x8e, 0x3b, 0xb6, 0x4e, 0xb3, 0x6c, 0x4a, 0x79, 0x68, 0x33, 0x26, 0xe4, 0x6c, - 0x1c, 0x69, 0x8c, 0xb9, 0x13, 0x22, 0xf5, 0xe5, 0x3f, 0xca, 0xc3, 0xfe, 0x96, 0x02, 0xf6, 0x8a, - 0xca, 0x54, 0x15, 0x64, 0x87, 0xf1, 0x53, 0x13, 0xba, 0xdf, 0xcd, 0x71, 0x8e, 0xfe, 0x39, 0x72, - 0x4e, 0x03, 0x24, 0x7b, 0xd0, 0x12, 0xd7, 0x4e, 0xe4, 0x2b, 0xb7, 0x3a, 0x76, 0x53, 0x5c, 0x9f, - 0xf9, 0xe4, 0x01, 0x6c, 0x25, 0x3c, 0x90, 0xd9, 0xba, 0xca, 0xb6, 0x12, 0x1e, 0x9c, 0xf9, 0xd2, - 0xe0, 0x0d, 0x0e, 0xe8, 0xee, 0x8a, 0xf8, 0xaf, 0x01, 0xfe, 0x81, 0xee, 0xb6, 0x5b, 0x69, 0xfe, - 0x11, 0xf6, 0xe5, 0xd5, 0x5e, 0x8e, 0x54, 0xa0, 0xb3, 0xa0, 0x71, 0xe4, 0x53, 0xc1, 0x72, 0x25, - 0x5d, 0x1f, 0x9e, 0x98, 0xc5, 0x3a, 0x98, 0xe5, 0x3a, 0x98, 0xe5, 0x3a, 0x98, 0xe7, 0x3c, 0x78, - 0xa2, 0x5a, 0x2e, 0x97, 0x1d, 0xd3, 0x9a, 0x4d, 0x92, 0xb7, 0xb2, 0x64, 0x0a, 0x1d, 0xc9, 0xef, - 0x63, 0x8c, 0x01, 0x15, 0xa8, 0x9c, 0xd2, 0x87, 0x0f, 0xdf, 0xc1, 0x3b, 0x2e, 0xa1, 0xd3, 0x9a, - 0xad, 0x27, 0xf7, 0x21, 0x99, 0xc1, 0xae, 0x64, 0x9a, 0xa7, 0x15, 0xd7, 0xb6, 0xe2, 0xfa, 0xe4, - 0x1d, 0x5c, 0x17, 0x15, 0x78, 0x5a, 0xb3, 0xbb, 0xc9, 0x6a, 0x62, 0xa9, 0xdc, 0xc5, 0x20, 0x4a, - 0x9d, 0x1c, 0x2b, 0xd6, 0x9d, 0xf7, 0x2a, 0x1f, 0xc9, 0x16, 0x1b, 0x57, 0xa8, 0xa5, 0xf2, 0x3f, - 0x65, 0x47, 0x2d, 0x68, 0x24, 0x3c, 0x30, 0x52, 0xf8, 0xff, 0xda, 0x06, 0x3c, 0x8d, 0xb8, 0xf8, - 0x2b, 0x4f, 0xe7, 0x4b, 0x68, 0x26, 0x3c, 0xe0, 0xfd, 0xfa, 0x51, 0xe3, 0x58, 0x1f, 0x1a, 0xe6, - 0x86, 0x5f, 0x00, 0x73, 0x8d, 0xd8, 0x56, 0x78, 0xe3, 0xb5, 0x06, 0xbb, 0x97, 0x34, 0xfe, 0x5e, - 0x50, 0x81, 0x17, 0x99, 0x2f, 0x95, 0x3e, 0x82, 0x16, 0x97, 0xa1, 0xba, 0x66, 0x77, 0x38, 0xd8, - 0xc8, 0x35, 0x62, 0xa9, 0xaf, 0x9a, 0xec, 0x02, 0xfc, 0xd6, 0xf6, 0xd5, 0xdf, 0xb7, 0x7d, 0x8d, - 0xbf, 0xbd, 0x7d, 0x06, 0x03, 0x52, 0xad, 0xca, 0xd3, 0xe8, 0x0a, 0xbd, 0x1b, 0x2f, 0x46, 0xf2, - 0x21, 0xec, 0x2c, 0x68, 0xec, 0x50, 0xdf, 0x2f, 0x9c, 0x69, 0xdb, 0xdb, 0x0b, 0x1a, 0x3f, 0xf6, - 0xfd, 0x9c, 0x7c, 0x55, 0x94, 0xe2, 0xe8, 0x0a, 0x4b, 0x67, 0x1e, 0x6e, 0x54, 0xb3, 0xee, 0x80, - 0xea, 0x97, 0xfc, 0xc6, 0x2f, 0x1a, 0x3c, 0x28, 0x37, 0x2a, 0x62, 0xe9, 0xbf, 0x37, 0x69, 0x75, - 0xd4, 0xfa, 0xfa, 0xa8, 0xff, 0xc1, 0xeb, 0x35, 0x5e, 0xc2, 0xde, 0xbd, 0x9a, 0x35, 0x03, 0x7d, - 0x5c, 0x37, 0xd0, 0xc7, 0x62, 0xaa, 0x49, 0x51, 0x5a, 0x31, 0xf0, 0x64, 0xa3, 0xd2, 0x8d, 0x26, - 0x29, 0x1a, 0xe5, 0xe3, 0x17, 0xd0, 0xbe, 0x7f, 0xe3, 0x04, 0x9a, 0xd5, 0x55, 0x1d, 0x5b, 0x9d, - 0xc9, 0x3e, 0xb4, 0x32, 0xf6, 0x12, 0x0b, 0x57, 0x1a, 0x76, 0x11, 0x9c, 0xcc, 0xa0, 0x5d, 0x59, - 0x48, 0x74, 0xd8, 0x7e, 0x62, 0x4f, 0x1e, 0x3f, 0x9f, 0x8c, 0x7b, 0x35, 0x02, 0xb0, 0x35, 0x7a, - 0x36, 0x1b, 0x4f, 0xc6, 0x3d, 0x8d, 0x74, 0xa1, 0x7d, 0x31, 0x93, 0xd1, 0xd9, 0xec, 0x9b, 0x5e, - 0x9d, 0x74, 0x60, 0xa7, 0x08, 0x27, 0xe3, 0x5e, 0x43, 0x76, 0xd9, 0x93, 0xf3, 0x67, 0x97, 0x93, - 0x71, 0xaf, 0x39, 0xfa, 0xf6, 0xe7, 0xdb, 0x81, 0xf6, 0xe6, 0x76, 0xa0, 0xfd, 0x7e, 0x3b, 0xd0, - 0x5e, 0xdd, 0x0d, 0x6a, 0x6f, 0xee, 0x06, 0xb5, 0x5f, 0xef, 0x06, 0xb5, 0x1f, 0x3e, 0x0b, 0x22, - 0x11, 0xce, 0x5d, 0xd3, 0x63, 0x89, 0x55, 0xea, 0xf3, 0x42, 0x1a, 0xa5, 0xcb, 0xc0, 0xba, 0xbe, - 0xff, 0xde, 0xaa, 0xef, 0x9f, 0xbb, 0xa5, 0x0c, 0xff, 0xfc, 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xb2, 0x50, 0x99, 0x42, 0x90, 0x07, 0x00, 0x00, + // 914 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x96, 0xdf, 0x6e, 0xdb, 0x36, + 0x14, 0xc6, 0x2d, 0xff, 0x4b, 0x4c, 0xdb, 0x99, 0xc7, 0xa4, 0x83, 0x1b, 0x0c, 0x4e, 0xa6, 0x62, + 0x40, 0x10, 0x0c, 0xd2, 0xe2, 0x65, 0xbb, 0xdc, 0x50, 0xc7, 0xc6, 0x94, 0xa1, 0x71, 0x31, 0xae, + 0xc9, 0xc5, 0x2e, 0x26, 0x50, 0x12, 0x23, 0x09, 0x95, 0x48, 0x41, 0xa4, 0xdc, 0xe4, 0x62, 0xef, + 0xd0, 0xe7, 0xe8, 0x93, 0xec, 0xb2, 0x97, 0xbb, 0xdb, 0x90, 0x3c, 0x48, 0x07, 0x52, 0xb2, 0x6c, + 0x2f, 0x46, 0xb2, 0xac, 0x57, 0x26, 0xcf, 0x39, 0xdf, 0x47, 0x9e, 0x9f, 0x8f, 0x09, 0x03, 0xdd, + 0xc1, 0xce, 0x75, 0xc4, 0xa8, 0x49, 0x12, 0xe6, 0x06, 0x21, 0xf5, 0xcd, 0xd9, 0x51, 0xb9, 0x36, + 0x92, 0x94, 0x09, 0x06, 0xb7, 0x8b, 0x1a, 0xa3, 0x8c, 0xcf, 0x8e, 0x76, 0xf7, 0x7c, 0xc6, 0xfc, + 0x88, 0x98, 0xaa, 0xc4, 0xc9, 0x2e, 0x4d, 0x11, 0xc6, 0x84, 0x0b, 0x1c, 0x27, 0xb9, 0x6a, 0x77, + 0xc7, 0x67, 0x3e, 0x53, 0x4b, 0x53, 0xae, 0x8a, 0xe8, 0x9e, 0xcb, 0x78, 0xcc, 0xb8, 0xc9, 0x05, + 0x7e, 0x9d, 0x9f, 0xe6, 0x10, 0x81, 0x8f, 0x4c, 0x71, 0x55, 0x14, 0x0c, 0x8a, 0x02, 0x07, 0x73, + 0x52, 0x66, 0x5d, 0x16, 0xd2, 0x3c, 0xaf, 0xbf, 0xab, 0x82, 0xc6, 0x44, 0xde, 0x03, 0x7e, 0x01, + 0x3a, 0xea, 0x42, 0x36, 0xcd, 0x62, 0x87, 0xa4, 0x7d, 0x6d, 0x5f, 0x3b, 0xa8, 0xa3, 0xb6, 0x8a, + 0x4d, 0x55, 0x08, 0x1e, 0x83, 0xcf, 0xdc, 0x2c, 0x4d, 0x09, 0x15, 0x76, 0x5e, 0x1a, 0x52, 0x41, + 0xd2, 0x19, 0x8e, 0xfa, 0x55, 0x55, 0xbc, 0x53, 0x64, 0x95, 0xe1, 0x69, 0x91, 0x83, 0x5f, 0x01, + 0x78, 0x19, 0xa6, 0x5c, 0xd8, 0x4e, 0xc4, 0xdc, 0xd7, 0x76, 0x40, 0x42, 0x3f, 0x10, 0xfd, 0x9a, + 0x52, 0xf4, 0x54, 0x66, 0x24, 0x13, 0x96, 0x8a, 0x43, 0x0b, 0x7c, 0x12, 0xe1, 0xb2, 0x58, 0x52, + 0xe8, 0xd7, 0xf7, 0xb5, 0x83, 0xf6, 0x70, 0xd7, 0xc8, 0x11, 0x19, 0x73, 0x44, 0xc6, 0xab, 0x39, + 0xa2, 0x51, 0xfd, 0xed, 0x5f, 0x7b, 0x1a, 0xea, 0x4a, 0xa1, 0xf2, 0x92, 0x19, 0xa8, 0x83, 0x2e, + 0x4e, 0x12, 0x3b, 0xc0, 0x3c, 0xb0, 0x53, 0xc6, 0x44, 0xbf, 0xb1, 0xaf, 0x1d, 0x74, 0x50, 0x1b, + 0x27, 0x89, 0x85, 0x79, 0x80, 0x18, 0x13, 0xf2, 0x6e, 0x9c, 0xe0, 0x88, 0xa4, 0x76, 0x40, 0xb0, + 0x27, 0x3f, 0x30, 0x0f, 0xfa, 0x4d, 0x55, 0xd8, 0xcb, 0x33, 0x96, 0x4a, 0x48, 0x85, 0xfe, 0xa1, + 0x0e, 0xba, 0x3f, 0x67, 0x24, 0x23, 0xde, 0x19, 0xe1, 0x1c, 0xfb, 0x04, 0x6e, 0x83, 0x86, 0xb8, + 0xb2, 0x43, 0x4f, 0xd1, 0xea, 0xa0, 0xba, 0xb8, 0x3a, 0xf5, 0xe0, 0x13, 0xd0, 0x8c, 0xb9, 0x2f, + 0xa3, 0x55, 0x15, 0x6d, 0xc4, 0xdc, 0x3f, 0xf5, 0x24, 0xe0, 0x35, 0x04, 0xda, 0xce, 0x52, 0xf3, + 0x3f, 0x00, 0xf0, 0x3f, 0xfa, 0x6e, 0x39, 0x65, 0xcf, 0xbf, 0x81, 0x1d, 0x79, 0xb4, 0x9b, 0x12, + 0x2c, 0x88, 0x3d, 0xc3, 0x51, 0xe8, 0x61, 0xc1, 0x52, 0xd5, 0x7a, 0x7b, 0x78, 0x68, 0xe4, 0xd3, + 0x60, 0x14, 0xe3, 0x62, 0x14, 0x03, 0x61, 0x9c, 0x71, 0xff, 0x44, 0x49, 0x2e, 0xe6, 0x0a, 0xab, + 0x82, 0x60, 0x7c, 0x27, 0x0a, 0x2d, 0xd0, 0x91, 0xfe, 0x1e, 0x89, 0x88, 0x8f, 0x05, 0x51, 0xa4, + 0xda, 0xc3, 0x67, 0xf7, 0xf8, 0x8e, 0x8b, 0x52, 0xab, 0x82, 0xda, 0xf1, 0x62, 0x0b, 0xa7, 0x60, + 0x4b, 0x3a, 0x65, 0xb4, 0xf4, 0xda, 0x50, 0x5e, 0x5f, 0xde, 0xe3, 0x75, 0x5e, 0x16, 0x5b, 0x15, + 0xd4, 0x8d, 0x97, 0x03, 0xf3, 0xce, 0x1d, 0xe2, 0x87, 0xd4, 0x4e, 0x49, 0xe9, 0xba, 0xf9, 0x60, + 0xe7, 0x23, 0x29, 0x41, 0x64, 0xc9, 0x5a, 0x76, 0xfe, 0xaf, 0x28, 0xfc, 0x1d, 0xec, 0x29, 0xb2, + 0x98, 0xba, 0x24, 0xb2, 0x33, 0xea, 0x30, 0xea, 0x85, 0xb4, 0x44, 0x11, 0x32, 0xda, 0x6f, 0xa9, + 0xa3, 0x8e, 0xef, 0x83, 0xac, 0xd4, 0xe7, 0x73, 0xf1, 0xb8, 0xd4, 0x5a, 0x15, 0xf4, 0x79, 0x7c, + 0x4f, 0x7e, 0xd4, 0x00, 0xb5, 0x98, 0xfb, 0x3a, 0x05, 0x9f, 0xae, 0x0c, 0xe0, 0x8b, 0x90, 0x8b, + 0xff, 0xf2, 0xcb, 0xfd, 0x0e, 0xd4, 0x63, 0xee, 0xf3, 0x7e, 0x75, 0xbf, 0x76, 0xd0, 0x1e, 0xea, + 0xc6, 0x9a, 0x27, 0xc8, 0x58, 0x31, 0x46, 0xaa, 0x5e, 0x7f, 0xa7, 0x81, 0xad, 0x0b, 0x1c, 0xfd, + 0x22, 0xb0, 0x20, 0xe7, 0x89, 0x27, 0x41, 0x1c, 0x83, 0x06, 0x97, 0x5b, 0x75, 0xcc, 0xd6, 0x70, + 0xb0, 0xd6, 0x6b, 0xc4, 0xa8, 0xa7, 0x44, 0x28, 0x2f, 0xbe, 0x33, 0xfc, 0xd5, 0x87, 0x86, 0xbf, + 0xf6, 0xe8, 0xe1, 0xd7, 0x19, 0x80, 0xe5, 0xa4, 0xbe, 0x08, 0x2f, 0x89, 0x7b, 0xed, 0x46, 0x04, + 0x3e, 0x05, 0x9b, 0x33, 0x1c, 0xd9, 0xd8, 0xf3, 0x72, 0x32, 0x2d, 0xb4, 0x31, 0xc3, 0xd1, 0x73, + 0xcf, 0x4b, 0xe1, 0xf7, 0x79, 0x2a, 0x0a, 0x2f, 0x49, 0x41, 0xe6, 0xd9, 0xda, 0x6e, 0x56, 0x09, + 0x28, 0xbd, 0xf4, 0xd7, 0x3f, 0x68, 0xe0, 0xc9, 0xe2, 0x3b, 0xfa, 0x78, 0x48, 0xcb, 0x57, 0xad, + 0xae, 0x5e, 0xf5, 0x08, 0x34, 0x71, 0xcc, 0x32, 0x2a, 0x0a, 0x30, 0x4f, 0xe7, 0x53, 0x26, 0x1f, + 0xf6, 0x72, 0xc4, 0x4e, 0x58, 0x48, 0x51, 0x51, 0x78, 0x07, 0x79, 0xfd, 0x21, 0xe4, 0x8d, 0xc7, + 0x23, 0x7f, 0x03, 0xb6, 0x17, 0x00, 0x56, 0x98, 0x7b, 0x64, 0x95, 0xb9, 0x47, 0xf2, 0x46, 0x26, + 0x79, 0x6a, 0x89, 0xf9, 0xe1, 0x5a, 0x38, 0x6b, 0xb9, 0x2a, 0x1b, 0x85, 0xfe, 0x5b, 0xd0, 0x5a, + 0xbc, 0x4a, 0x10, 0xd4, 0xcb, 0xa3, 0x3a, 0x48, 0xad, 0xe1, 0x0e, 0x68, 0x24, 0xec, 0x0d, 0xc9, + 0x41, 0xd6, 0x50, 0xbe, 0x39, 0x9c, 0x82, 0x56, 0x49, 0x1d, 0xb6, 0xc1, 0xc6, 0x09, 0x9a, 0x3c, + 0x7f, 0x35, 0x19, 0xf7, 0x2a, 0x10, 0x80, 0xe6, 0xe8, 0xe5, 0x74, 0x3c, 0x19, 0xf7, 0x34, 0xd8, + 0x05, 0xad, 0xf3, 0xa9, 0xdc, 0x9d, 0x4e, 0x7f, 0xec, 0x55, 0x61, 0x07, 0x6c, 0xe6, 0xdb, 0xc9, + 0xb8, 0x57, 0x93, 0x2a, 0x34, 0x39, 0x7b, 0x79, 0x31, 0x19, 0xf7, 0xea, 0xa3, 0x9f, 0xfe, 0xb8, + 0x19, 0x68, 0xef, 0x6f, 0x06, 0xda, 0xdf, 0x37, 0x03, 0xed, 0xed, 0xed, 0xa0, 0xf2, 0xfe, 0x76, + 0x50, 0xf9, 0xf3, 0x76, 0x50, 0xf9, 0xf5, 0x6b, 0x3f, 0x14, 0x41, 0xe6, 0x18, 0x2e, 0x8b, 0xcd, + 0xa2, 0x3f, 0x37, 0xc0, 0x21, 0x9d, 0x6f, 0xcc, 0xab, 0xc5, 0x7f, 0x04, 0x71, 0x9d, 0x10, 0xee, + 0x34, 0x15, 0xf0, 0x6f, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x32, 0x7b, 0xad, 0x44, 0x08, + 0x00, 0x00, } func (m *Epoch) Marshal() (dAtA []byte, err error) { @@ -946,6 +971,27 @@ func (m *QueuedMessage_MsgBeginRedelegate) MarshalToSizedBuffer(dAtA []byte) (in } return len(dAtA) - i, nil } +func (m *QueuedMessage_MsgCancelUnbondingDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueuedMessage_MsgCancelUnbondingDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.MsgCancelUnbondingDelegation != nil { + { + size, err := m.MsgCancelUnbondingDelegation.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEpoching(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + return len(dAtA) - i, nil +} func (m *QueuedMessageList) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1009,12 +1055,12 @@ func (m *ValStateUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.BlockTime != nil { - n7, err7 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err7 != nil { - return 0, err7 + n8, err8 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err8 != nil { + return 0, err8 } - i -= n7 - i = encodeVarintEpoching(dAtA, i, uint64(n7)) + i -= n8 + i = encodeVarintEpoching(dAtA, i, uint64(n8)) i-- dAtA[i] = 0x1a } @@ -1096,19 +1142,31 @@ func (m *DelegationStateUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.BlockTime != nil { - n8, err8 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err8 != nil { - return 0, err8 + n9, err9 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err9 != nil { + return 0, err9 } - i -= n8 - i = encodeVarintEpoching(dAtA, i, uint64(n8)) + i -= n9 + i = encodeVarintEpoching(dAtA, i, uint64(n9)) i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a } if m.BlockHeight != 0 { i = encodeVarintEpoching(dAtA, i, uint64(m.BlockHeight)) i-- - dAtA[i] = 0x18 + dAtA[i] = 0x20 + } + if m.Amount != nil { + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEpoching(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a } if len(m.ValAddr) > 0 { i -= len(m.ValAddr) @@ -1320,6 +1378,18 @@ func (m *QueuedMessage_MsgBeginRedelegate) Size() (n int) { } return n } +func (m *QueuedMessage_MsgCancelUnbondingDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MsgCancelUnbondingDelegation != nil { + l = m.MsgCancelUnbondingDelegation.Size() + n += 1 + l + sovEpoching(uint64(l)) + } + return n +} func (m *QueuedMessageList) Size() (n int) { if m == nil { return 0 @@ -1389,6 +1459,10 @@ func (m *DelegationStateUpdate) Size() (n int) { if l > 0 { n += 1 + l + sovEpoching(uint64(l)) } + if m.Amount != nil { + l = m.Amount.Size() + n += 1 + l + sovEpoching(uint64(l)) + } if m.BlockHeight != 0 { n += 1 + sovEpoching(uint64(m.BlockHeight)) } @@ -1943,6 +2017,41 @@ func (m *QueuedMessage) Unmarshal(dAtA []byte) error { } m.Msg = &QueuedMessage_MsgBeginRedelegate{v} iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MsgCancelUnbondingDelegation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEpoching + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEpoching + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEpoching + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &types.MsgCancelUnbondingDelegation{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Msg = &QueuedMessage_MsgCancelUnbondingDelegation{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEpoching(dAtA[iNdEx:]) @@ -2388,6 +2497,42 @@ func (m *DelegationStateUpdate) Unmarshal(dAtA []byte) error { m.ValAddr = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEpoching + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEpoching + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEpoching + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Amount == nil { + m.Amount = &types1.Coin{} + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) } @@ -2406,7 +2551,7 @@ func (m *DelegationStateUpdate) Unmarshal(dAtA []byte) error { break } } - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BlockTime", wireType) } diff --git a/x/epoching/types/errors.go b/x/epoching/types/errors.go index bd1028ef3..1162299c1 100644 --- a/x/epoching/types/errors.go +++ b/x/epoching/types/errors.go @@ -8,9 +8,9 @@ import ( var ( ErrUnwrappedMsgType = errorsmod.Register(ModuleName, 1, ` - invalid message type in {MsgCreateValidator, MsgDelegate, MsgUndelegate, MsgBeginRedelegate} + invalid message type in {MsgCreateValidator, MsgDelegate, MsgUndelegate, MsgBeginRedelegate, MsgCancelUnbondingDelegation} messages. For creating a validator use the wrapped version under 'tx checkpointing create-validator' - and for the other messages use the wrapped versions under 'tx epoching {delegate,undelegate,redelegate}'`) + and for the other messages use the wrapped versions under 'tx epoching {delegate,undelegate,redelegate,cancel-unbond}'`) ErrInvalidQueuedMessageType = errorsmod.Register(ModuleName, 2, "invalid message type of a QueuedMessage") ErrUnknownEpochNumber = errorsmod.Register(ModuleName, 3, "the epoch number is not known in DB") ErrUnknownSlashedVotingPower = errorsmod.Register(ModuleName, 5, "the slashed voting power is not known in DB. Maybe the epoch has been checkpointed?") diff --git a/x/epoching/types/events.pb.go b/x/epoching/types/events.pb.go index 5037813b2..a1b01c203 100644 --- a/x/epoching/types/events.pb.go +++ b/x/epoching/types/events.pb.go @@ -505,6 +505,84 @@ func (m *EventWrappedBeginRedelegate) GetEpochBoundary() uint64 { return 0 } +// EventWrappedCancelUnbondingDelegation is the event emitted when a +// MsgWrappedCancelUnbondingDelegation has been queued +type EventWrappedCancelUnbondingDelegation struct { + DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty"` + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + CreationHeight int64 `protobuf:"varint,4,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty"` + EpochBoundary uint64 `protobuf:"varint,5,opt,name=epoch_boundary,json=epochBoundary,proto3" json:"epoch_boundary,omitempty"` +} + +func (m *EventWrappedCancelUnbondingDelegation) Reset() { *m = EventWrappedCancelUnbondingDelegation{} } +func (m *EventWrappedCancelUnbondingDelegation) String() string { return proto.CompactTextString(m) } +func (*EventWrappedCancelUnbondingDelegation) ProtoMessage() {} +func (*EventWrappedCancelUnbondingDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_2f0a2c43c7aaeb43, []int{7} +} +func (m *EventWrappedCancelUnbondingDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventWrappedCancelUnbondingDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventWrappedCancelUnbondingDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventWrappedCancelUnbondingDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventWrappedCancelUnbondingDelegation.Merge(m, src) +} +func (m *EventWrappedCancelUnbondingDelegation) XXX_Size() int { + return m.Size() +} +func (m *EventWrappedCancelUnbondingDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_EventWrappedCancelUnbondingDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_EventWrappedCancelUnbondingDelegation proto.InternalMessageInfo + +func (m *EventWrappedCancelUnbondingDelegation) GetDelegatorAddress() string { + if m != nil { + return m.DelegatorAddress + } + return "" +} + +func (m *EventWrappedCancelUnbondingDelegation) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + +func (m *EventWrappedCancelUnbondingDelegation) GetAmount() uint64 { + if m != nil { + return m.Amount + } + return 0 +} + +func (m *EventWrappedCancelUnbondingDelegation) GetCreationHeight() int64 { + if m != nil { + return m.CreationHeight + } + return 0 +} + +func (m *EventWrappedCancelUnbondingDelegation) GetEpochBoundary() uint64 { + if m != nil { + return m.EpochBoundary + } + return 0 +} + func init() { proto.RegisterType((*EventBeginEpoch)(nil), "babylon.epoching.v1.EventBeginEpoch") proto.RegisterType((*EventEndEpoch)(nil), "babylon.epoching.v1.EventEndEpoch") @@ -513,51 +591,56 @@ func init() { proto.RegisterType((*EventWrappedDelegate)(nil), "babylon.epoching.v1.EventWrappedDelegate") proto.RegisterType((*EventWrappedUndelegate)(nil), "babylon.epoching.v1.EventWrappedUndelegate") proto.RegisterType((*EventWrappedBeginRedelegate)(nil), "babylon.epoching.v1.EventWrappedBeginRedelegate") + proto.RegisterType((*EventWrappedCancelUnbondingDelegation)(nil), "babylon.epoching.v1.EventWrappedCancelUnbondingDelegation") } func init() { proto.RegisterFile("babylon/epoching/v1/events.proto", fileDescriptor_2f0a2c43c7aaeb43) } var fileDescriptor_2f0a2c43c7aaeb43 = []byte{ - // 624 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x54, 0xdd, 0x6a, 0xd4, 0x4e, - 0x14, 0x6f, 0xf6, 0xeb, 0x4f, 0xe7, 0xdf, 0x6a, 0x3b, 0xbb, 0x2e, 0xc1, 0xe2, 0xba, 0x2e, 0x08, - 0x05, 0x75, 0xd3, 0xaa, 0x88, 0x78, 0xd7, 0xc5, 0x82, 0x15, 0x14, 0x8d, 0xb5, 0x82, 0x37, 0x61, - 0xb2, 0x73, 0x4c, 0x06, 0x93, 0x99, 0x30, 0x33, 0x59, 0xbb, 0x6f, 0xe1, 0x0b, 0x78, 0xe7, 0x03, - 0xf8, 0x12, 0xa2, 0x97, 0xbd, 0x14, 0x2f, 0x44, 0xda, 0x17, 0x91, 0x4c, 0x3e, 0x1a, 0x6d, 0x2b, - 0xe2, 0x9d, 0x77, 0x33, 0xbf, 0x8f, 0x33, 0xf9, 0x9d, 0x93, 0x19, 0x34, 0xf4, 0x89, 0x3f, 0x8f, - 0x04, 0x77, 0x20, 0x11, 0xd3, 0x90, 0xf1, 0xc0, 0x99, 0x6d, 0x3a, 0x30, 0x03, 0xae, 0xd5, 0x38, - 0x91, 0x42, 0x0b, 0xdc, 0x2d, 0x14, 0xe3, 0x52, 0x31, 0x9e, 0x6d, 0x5e, 0xec, 0x05, 0x22, 0x10, - 0x86, 0x77, 0xb2, 0x55, 0x2e, 0x1d, 0xdd, 0x46, 0xe7, 0xb7, 0x33, 0xeb, 0x04, 0x02, 0xc6, 0xb7, - 0x33, 0x39, 0xbe, 0x82, 0x96, 0x8c, 0xcf, 0xe3, 0x69, 0xec, 0x83, 0xb4, 0xad, 0xa1, 0xb5, 0xde, - 0x72, 0xff, 0x37, 0xd8, 0x63, 0x03, 0x8d, 0x6e, 0xa2, 0x65, 0xe3, 0xda, 0xe6, 0xf4, 0x8f, 0x3d, - 0x1f, 0x1a, 0xa8, 0x67, 0x4c, 0x0f, 0x08, 0xa7, 0x11, 0x3c, 0x4d, 0x21, 0x05, 0xfa, 0x48, 0x05, - 0x78, 0x8c, 0xba, 0x42, 0xb2, 0x80, 0x71, 0x12, 0x79, 0x26, 0x86, 0xa7, 0xe7, 0x09, 0x98, 0x12, - 0x8b, 0xee, 0x6a, 0x49, 0x19, 0xeb, 0xee, 0x3c, 0x81, 0x13, 0x67, 0x35, 0x4e, 0x9c, 0x85, 0xfb, - 0xa8, 0x13, 0x02, 0x0b, 0x42, 0x6d, 0x37, 0x0d, 0x59, 0xec, 0x70, 0x17, 0xb5, 0xf5, 0xbe, 0xc7, - 0xa8, 0xdd, 0x1a, 0x5a, 0xeb, 0x4b, 0x6e, 0x4b, 0xef, 0xef, 0x50, 0x7c, 0x01, 0x75, 0x62, 0x15, - 0x64, 0x68, 0xdb, 0xa0, 0xed, 0x58, 0x05, 0x3b, 0x14, 0xbf, 0xae, 0x7d, 0x16, 0xd1, 0x5a, 0x32, - 0x3f, 0xd5, 0xa0, 0xec, 0xce, 0xb0, 0xb9, 0xbe, 0x34, 0xb9, 0xf7, 0xf5, 0xdb, 0xe5, 0x3b, 0x01, - 0xd3, 0x61, 0xea, 0x8f, 0xa7, 0x22, 0x76, 0xa6, 0x22, 0x06, 0xed, 0xbf, 0xd2, 0xc7, 0x0b, 0xe2, - 0x4f, 0x99, 0x93, 0x05, 0x51, 0x63, 0xf3, 0xe9, 0x5b, 0x65, 0x09, 0x17, 0x97, 0x65, 0x2b, 0x48, - 0xe1, 0x1e, 0x6a, 0x83, 0x94, 0x42, 0xda, 0xff, 0x99, 0xd4, 0xf9, 0x66, 0xf4, 0xde, 0x42, 0x5d, - 0x63, 0x7e, 0x16, 0x11, 0x15, 0xee, 0x86, 0x12, 0x54, 0x28, 0x22, 0x8a, 0x37, 0x50, 0x4f, 0x65, - 0x08, 0x50, 0x6f, 0x26, 0x34, 0xe3, 0x81, 0x97, 0x88, 0x37, 0x45, 0xd7, 0x9b, 0x2e, 0x2e, 0xb8, - 0x3d, 0x43, 0x3d, 0xc9, 0x18, 0x7c, 0x1d, 0x61, 0x2d, 0x34, 0x89, 0x7e, 0xd6, 0x37, 0x8c, 0x7e, - 0xc5, 0x30, 0x75, 0xf5, 0x0d, 0x84, 0xab, 0xfa, 0x24, 0x62, 0x94, 0x68, 0x21, 0x95, 0xdd, 0xcc, - 0x92, 0xbb, 0xab, 0x65, 0xf5, 0x8a, 0x18, 0x7d, 0xb4, 0x8a, 0xc9, 0xbe, 0x90, 0x24, 0x49, 0x80, - 0xde, 0x87, 0x08, 0x02, 0xa2, 0x01, 0x5f, 0x43, 0xab, 0x34, 0x5f, 0x0b, 0xe9, 0x11, 0x4a, 0x25, - 0x28, 0x55, 0xcc, 0x75, 0xa5, 0x22, 0xb6, 0x72, 0x3c, 0x13, 0x57, 0x87, 0x55, 0xe2, 0x46, 0x2e, - 0xae, 0x88, 0x52, 0xdc, 0x47, 0x1d, 0x12, 0x8b, 0x94, 0x57, 0x03, 0xce, 0x77, 0x59, 0x1f, 0x29, - 0x70, 0x11, 0x9b, 0x01, 0x2f, 0xba, 0xf9, 0x06, 0x5f, 0x45, 0xe7, 0xf2, 0x3f, 0xc6, 0x17, 0x29, - 0xa7, 0x44, 0xce, 0xcd, 0xa4, 0x5b, 0xee, 0xb2, 0x41, 0x27, 0x05, 0x38, 0xfa, 0x64, 0xa1, 0x7e, - 0x3d, 0xc7, 0x73, 0x4e, 0xff, 0xd1, 0x24, 0xef, 0x1a, 0x68, 0xad, 0x9e, 0xc4, 0xdc, 0x6e, 0x17, - 0xfe, 0x2e, 0xce, 0x5d, 0x64, 0x2b, 0x91, 0xca, 0x29, 0x78, 0x67, 0xa5, 0xea, 0xe7, 0xfc, 0xde, - 0xaf, 0xd9, 0x26, 0xe8, 0x12, 0x05, 0xa5, 0x19, 0x27, 0x9a, 0x09, 0x7e, 0x8a, 0xbd, 0x69, 0xec, - 0x6b, 0x35, 0xd1, 0xde, 0xd9, 0xfd, 0x69, 0x9d, 0xde, 0x9f, 0xf6, 0xef, 0xfb, 0xd3, 0x39, 0xa5, - 0x3f, 0x93, 0x87, 0x9f, 0x0f, 0x07, 0xd6, 0xc1, 0xe1, 0xc0, 0xfa, 0x7e, 0x38, 0xb0, 0xde, 0x1e, - 0x0d, 0x16, 0x0e, 0x8e, 0x06, 0x0b, 0x5f, 0x8e, 0x06, 0x0b, 0x2f, 0x37, 0x6a, 0x97, 0xba, 0x78, - 0x45, 0xa7, 0x21, 0x61, 0xbc, 0xdc, 0x38, 0xfb, 0xc7, 0xcf, 0xae, 0xb9, 0xdd, 0x7e, 0xc7, 0x3c, - 0xa4, 0xb7, 0x7e, 0x04, 0x00, 0x00, 0xff, 0xff, 0x77, 0x37, 0xe9, 0x99, 0x97, 0x05, 0x00, 0x00, + // 674 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x55, 0xcd, 0x6e, 0xd4, 0x3c, + 0x14, 0x6d, 0xe6, 0xef, 0x53, 0xfd, 0xf5, 0xd7, 0x33, 0x8c, 0x22, 0x2a, 0x86, 0x61, 0xa4, 0x8a, + 0x4a, 0xc0, 0xa4, 0x05, 0x84, 0x10, 0xbb, 0x0e, 0x54, 0x6a, 0x91, 0x40, 0x10, 0xda, 0x22, 0xb1, + 0x89, 0x9c, 0xd8, 0x24, 0x16, 0x89, 0x1d, 0xd9, 0xce, 0xd0, 0x79, 0x0b, 0x5e, 0x80, 0x1d, 0x0f, + 0xc0, 0x4b, 0x20, 0x58, 0x76, 0x89, 0x58, 0x20, 0xd4, 0xae, 0x78, 0x0b, 0x14, 0xe7, 0xa7, 0x81, + 0x4e, 0x51, 0xc5, 0x06, 0xb1, 0xf3, 0x3d, 0xe7, 0xdc, 0xeb, 0x9c, 0x7b, 0x1d, 0x1b, 0xf4, 0x5d, + 0xe4, 0x4e, 0x42, 0xce, 0x2c, 0x12, 0x73, 0x2f, 0xa0, 0xcc, 0xb7, 0xc6, 0x1b, 0x16, 0x19, 0x13, + 0xa6, 0xe4, 0x30, 0x16, 0x5c, 0x71, 0xd8, 0xce, 0x15, 0xc3, 0x42, 0x31, 0x1c, 0x6f, 0x5c, 0xec, + 0xf8, 0xdc, 0xe7, 0x9a, 0xb7, 0xd2, 0x55, 0x26, 0x1d, 0xdc, 0x06, 0x8b, 0x5b, 0x69, 0xea, 0x88, + 0xf8, 0x94, 0x6d, 0xa5, 0x72, 0x78, 0x05, 0xcc, 0xe9, 0x3c, 0x87, 0x25, 0x91, 0x4b, 0x84, 0x69, + 0xf4, 0x8d, 0xb5, 0x86, 0xfd, 0xbf, 0xc6, 0x1e, 0x6b, 0x68, 0x70, 0x13, 0xcc, 0xeb, 0xac, 0x2d, + 0x86, 0xcf, 0x9d, 0xf3, 0xbe, 0x06, 0x3a, 0x3a, 0x69, 0x1b, 0x31, 0x1c, 0x92, 0xa7, 0x09, 0x49, + 0x08, 0x7e, 0x24, 0x7d, 0x38, 0x04, 0x6d, 0x2e, 0xa8, 0x4f, 0x19, 0x0a, 0x1d, 0x6d, 0xc3, 0x51, + 0x93, 0x98, 0xe8, 0x12, 0xb3, 0xf6, 0x72, 0x41, 0xe9, 0xd4, 0xdd, 0x49, 0x4c, 0x4e, 0xed, 0x55, + 0x3b, 0xb5, 0x17, 0xec, 0x82, 0x56, 0x40, 0xa8, 0x1f, 0x28, 0xb3, 0xae, 0xc9, 0x3c, 0x82, 0x6d, + 0xd0, 0x54, 0x07, 0x0e, 0xc5, 0x66, 0xa3, 0x6f, 0xac, 0xcd, 0xd9, 0x0d, 0x75, 0xb0, 0x83, 0xe1, + 0x05, 0xd0, 0x8a, 0xa4, 0x9f, 0xa2, 0x4d, 0x8d, 0x36, 0x23, 0xe9, 0xef, 0x60, 0xf8, 0xaa, 0xf2, + 0x59, 0x48, 0x29, 0x41, 0xdd, 0x44, 0x11, 0x69, 0xb6, 0xfa, 0xf5, 0xb5, 0xb9, 0xd1, 0xbd, 0x2f, + 0x5f, 0x2f, 0xdf, 0xf1, 0xa9, 0x0a, 0x12, 0x77, 0xe8, 0xf1, 0xc8, 0xf2, 0x78, 0x44, 0x94, 0xfb, + 0x52, 0x9d, 0x2c, 0x90, 0xeb, 0x51, 0x2b, 0x35, 0x22, 0x87, 0xfa, 0xd3, 0x37, 0x8b, 0x12, 0x36, + 0x2c, 0xca, 0x96, 0x90, 0x84, 0x1d, 0xd0, 0x24, 0x42, 0x70, 0x61, 0xfe, 0xa7, 0x5d, 0x67, 0xc1, + 0xe0, 0x9d, 0x01, 0xda, 0x3a, 0xf9, 0x59, 0x88, 0x64, 0xb0, 0x1b, 0x08, 0x22, 0x03, 0x1e, 0x62, + 0xb8, 0x0e, 0x3a, 0x32, 0x45, 0x08, 0x76, 0xc6, 0x5c, 0x51, 0xe6, 0x3b, 0x31, 0x7f, 0x9d, 0x77, + 0xbd, 0x6e, 0xc3, 0x9c, 0xdb, 0xd7, 0xd4, 0x93, 0x94, 0x81, 0xd7, 0x01, 0x54, 0x5c, 0xa1, 0xf0, + 0x67, 0x7d, 0x4d, 0xeb, 0x97, 0x34, 0x53, 0x55, 0xdf, 0x00, 0xb0, 0xac, 0x8f, 0x42, 0x8a, 0x91, + 0xe2, 0x42, 0x9a, 0xf5, 0xd4, 0xb9, 0xbd, 0x5c, 0x54, 0x2f, 0x89, 0xc1, 0x07, 0x23, 0x9f, 0xec, + 0x73, 0x81, 0xe2, 0x98, 0xe0, 0x07, 0x24, 0x24, 0x3e, 0x52, 0x04, 0x5e, 0x03, 0xcb, 0x38, 0x5b, + 0x73, 0xe1, 0x20, 0x8c, 0x05, 0x91, 0x32, 0x9f, 0xeb, 0x52, 0x49, 0x6c, 0x66, 0x78, 0x2a, 0x2e, + 0x37, 0x2b, 0xc5, 0xb5, 0x4c, 0x5c, 0x12, 0x85, 0xb8, 0x0b, 0x5a, 0x28, 0xe2, 0x09, 0x2b, 0x07, + 0x9c, 0x45, 0x69, 0x1f, 0x31, 0x61, 0x3c, 0xd2, 0x03, 0x9e, 0xb5, 0xb3, 0x00, 0xae, 0x82, 0x85, + 0xec, 0xc4, 0xb8, 0x3c, 0x61, 0x18, 0x89, 0x89, 0x9e, 0x74, 0xc3, 0x9e, 0xd7, 0xe8, 0x28, 0x07, + 0x07, 0x1f, 0x0d, 0xd0, 0xad, 0xfa, 0xd8, 0x63, 0xf8, 0x1f, 0x75, 0xf2, 0xb6, 0x06, 0x56, 0xaa, + 0x4e, 0xf4, 0xdf, 0x6d, 0x93, 0x3f, 0xb3, 0x73, 0x17, 0x98, 0x92, 0x27, 0xc2, 0x23, 0xce, 0x59, + 0xae, 0xba, 0x19, 0xbf, 0xff, 0xab, 0xb7, 0x11, 0xb8, 0x84, 0x89, 0x54, 0x94, 0x21, 0x45, 0x39, + 0x9b, 0x92, 0x5e, 0xd7, 0xe9, 0x2b, 0x15, 0xd1, 0xfe, 0xd9, 0xfd, 0x69, 0x4c, 0xef, 0x4f, 0xf3, + 0xf7, 0xfd, 0x69, 0x4d, 0xeb, 0xcf, 0x77, 0x03, 0xac, 0x56, 0xfb, 0x73, 0x1f, 0x31, 0x8f, 0x84, + 0x7b, 0xcc, 0xe5, 0x0c, 0x53, 0xe6, 0xe7, 0x07, 0x98, 0x72, 0xf6, 0x17, 0x06, 0x7f, 0x15, 0x2c, + 0x7a, 0x82, 0x64, 0x1d, 0xcb, 0x2f, 0xb1, 0x86, 0xfe, 0x4f, 0x17, 0x0a, 0x78, 0x3b, 0xbb, 0xcc, + 0xce, 0x77, 0x16, 0x46, 0x0f, 0x3f, 0x1d, 0xf5, 0x8c, 0xc3, 0xa3, 0x9e, 0xf1, 0xed, 0xa8, 0x67, + 0xbc, 0x39, 0xee, 0xcd, 0x1c, 0x1e, 0xf7, 0x66, 0x3e, 0x1f, 0xf7, 0x66, 0x5e, 0xac, 0x57, 0x2e, + 0xb0, 0xfc, 0xc5, 0xf0, 0x02, 0x44, 0x59, 0x11, 0x58, 0x07, 0x27, 0x4f, 0x8c, 0xbe, 0xc9, 0xdc, + 0x96, 0x7e, 0x34, 0x6e, 0xfd, 0x08, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x83, 0x9b, 0xc9, 0x83, 0x06, + 0x00, 0x00, } func (m *EventBeginEpoch) Marshal() (dAtA []byte, err error) { @@ -902,6 +985,58 @@ func (m *EventWrappedBeginRedelegate) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } +func (m *EventWrappedCancelUnbondingDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventWrappedCancelUnbondingDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventWrappedCancelUnbondingDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EpochBoundary != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.EpochBoundary)) + i-- + dAtA[i] = 0x28 + } + if m.CreationHeight != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.CreationHeight)) + i-- + dAtA[i] = 0x20 + } + if m.Amount != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.Amount)) + i-- + dAtA[i] = 0x18 + } + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 + } + if len(m.DelegatorAddress) > 0 { + i -= len(m.DelegatorAddress) + copy(dAtA[i:], m.DelegatorAddress) + i = encodeVarintEvents(dAtA, i, uint64(len(m.DelegatorAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1080,6 +1215,32 @@ func (m *EventWrappedBeginRedelegate) Size() (n int) { return n } +func (m *EventWrappedCancelUnbondingDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.DelegatorAddress) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if m.Amount != 0 { + n += 1 + sovEvents(uint64(m.Amount)) + } + if m.CreationHeight != 0 { + n += 1 + sovEvents(uint64(m.CreationHeight)) + } + if m.EpochBoundary != 0 { + n += 1 + sovEvents(uint64(m.EpochBoundary)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2183,6 +2344,177 @@ func (m *EventWrappedBeginRedelegate) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventWrappedCancelUnbondingDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventWrappedCancelUnbondingDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventWrappedCancelUnbondingDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DelegatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DelegatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + m.Amount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Amount |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CreationHeight", wireType) + } + m.CreationHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CreationHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochBoundary", wireType) + } + m.EpochBoundary = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochBoundary |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/epoching/types/msg.go b/x/epoching/types/msg.go index 0bac4757e..53eaa16d9 100644 --- a/x/epoching/types/msg.go +++ b/x/epoching/types/msg.go @@ -8,9 +8,10 @@ import ( // staking message types const ( - TypeMsgWrappedDelegate = "wrapped_delegate" - TypeMsgWrappedUndelegate = "wrapped_begin_unbonding" - TypeMsgWrappedBeginRedelegate = "wrapped_begin_redelegate" + TypeMsgWrappedDelegate = "wrapped_delegate" + TypeMsgWrappedUndelegate = "wrapped_begin_unbonding" + TypeMsgWrappedBeginRedelegate = "wrapped_begin_redelegate" + TypeMsgWrappedCancelUnbondingDelegation = "wrapped_cancel_unbonding_delegation" ) // ensure that these message types implement the sdk.Msg interface @@ -18,6 +19,7 @@ var ( _ sdk.Msg = &MsgWrappedDelegate{} _ sdk.Msg = &MsgWrappedUndelegate{} _ sdk.Msg = &MsgWrappedBeginRedelegate{} + _ sdk.Msg = &MsgWrappedCancelUnbondingDelegation{} _ sdk.Msg = &MsgUpdateParams{} ) @@ -84,6 +86,29 @@ func (msg MsgWrappedBeginRedelegate) ValidateBasic() error { return nil } +// NewMsgWrappedCancelUnbondingDelegation creates a new MsgWrappedCancelUnbondingDelegation instance. +func NewMsgWrappedCancelUnbondingDelegation(msg *stakingtypes.MsgCancelUnbondingDelegation) *MsgWrappedCancelUnbondingDelegation { + return &MsgWrappedCancelUnbondingDelegation{ + Msg: msg, + } +} + +// Route implements the sdk.Msg interface. +func (msg MsgWrappedCancelUnbondingDelegation) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgWrappedCancelUnbondingDelegation) Type() string { + return TypeMsgWrappedCancelUnbondingDelegation +} + +// ValidateBasic implements the sdk.Msg interface. +func (msg MsgWrappedCancelUnbondingDelegation) ValidateBasic() error { + if msg.Msg == nil { + return ErrNoWrappedMsg + } + return nil +} + // GetSigners returns the expected signers for a MsgUpdateParams message. func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { addr, _ := sdk.AccAddressFromBech32(m.Authority) diff --git a/x/epoching/types/msg_test.go b/x/epoching/types/msg_test.go index 61f37f04f..7385ed9dc 100644 --- a/x/epoching/types/msg_test.go +++ b/x/epoching/types/msg_test.go @@ -1,11 +1,12 @@ package types_test import ( - sdkmath "cosmossdk.io/math" - appparams "github.com/babylonchain/babylon/app/params" "testing" "time" + sdkmath "cosmossdk.io/math" + appparams "github.com/babylonchain/babylon/app/params" + "github.com/stretchr/testify/require" "github.com/babylonchain/babylon/x/epoching/types" @@ -176,3 +177,33 @@ func TestMsgWrappedUndelegate(t *testing.T) { } } } + +// test ValidateBasic for MsgWrappedCancelUnbondingDelegation +func TestMsgWrappedCancelUnbondingDelegation(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.ValAddress + amount sdk.Coin + creationHeight int64 + expectPass bool + }{ + {"regular", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), 10, true}, + {"no wrapped msg", nil, nil, coinPos, 0, false}, + } + + for _, tc := range tests { + var msg *types.MsgWrappedCancelUnbondingDelegation + if tc.delegatorAddr == nil { + msg = types.NewMsgWrappedCancelUnbondingDelegation(nil) + } else { + msgUnwrapped := stakingtypes.NewMsgCancelUnbondingDelegation(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.creationHeight, tc.amount) + msg = types.NewMsgWrappedCancelUnbondingDelegation(msgUnwrapped) + } + if tc.expectPass { + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.Error(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} diff --git a/x/epoching/types/tx.pb.go b/x/epoching/types/tx.pb.go index fc3ec2d6e..fd16d4af4 100644 --- a/x/epoching/types/tx.pb.go +++ b/x/epoching/types/tx.pb.go @@ -259,6 +259,87 @@ func (m *MsgWrappedBeginRedelegateResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgWrappedBeginRedelegateResponse proto.InternalMessageInfo +// MsgWrappedCancelUnbondingDelegation is the message for cancelling +// an unbonding delegation +type MsgWrappedCancelUnbondingDelegation struct { + Msg *types.MsgCancelUnbondingDelegation `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (m *MsgWrappedCancelUnbondingDelegation) Reset() { *m = MsgWrappedCancelUnbondingDelegation{} } +func (m *MsgWrappedCancelUnbondingDelegation) String() string { return proto.CompactTextString(m) } +func (*MsgWrappedCancelUnbondingDelegation) ProtoMessage() {} +func (*MsgWrappedCancelUnbondingDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_a5fc8fed8f4e58b6, []int{6} +} +func (m *MsgWrappedCancelUnbondingDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWrappedCancelUnbondingDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWrappedCancelUnbondingDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWrappedCancelUnbondingDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWrappedCancelUnbondingDelegation.Merge(m, src) +} +func (m *MsgWrappedCancelUnbondingDelegation) XXX_Size() int { + return m.Size() +} +func (m *MsgWrappedCancelUnbondingDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWrappedCancelUnbondingDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWrappedCancelUnbondingDelegation proto.InternalMessageInfo + +// MsgWrappedCancelUnbondingDelegationResponse is the response to the +// MsgWrappedCancelUnbondingDelegation message +type MsgWrappedCancelUnbondingDelegationResponse struct { +} + +func (m *MsgWrappedCancelUnbondingDelegationResponse) Reset() { + *m = MsgWrappedCancelUnbondingDelegationResponse{} +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) String() string { + return proto.CompactTextString(m) +} +func (*MsgWrappedCancelUnbondingDelegationResponse) ProtoMessage() {} +func (*MsgWrappedCancelUnbondingDelegationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a5fc8fed8f4e58b6, []int{7} +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgWrappedCancelUnbondingDelegationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgWrappedCancelUnbondingDelegationResponse.Merge(m, src) +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgWrappedCancelUnbondingDelegationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgWrappedCancelUnbondingDelegationResponse proto.InternalMessageInfo + // MsgUpdateParams defines a message for updating epoching module parameters. type MsgUpdateParams struct { // authority is the address of the governance account. @@ -276,7 +357,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_a5fc8fed8f4e58b6, []int{6} + return fileDescriptor_a5fc8fed8f4e58b6, []int{8} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -327,7 +408,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_a5fc8fed8f4e58b6, []int{7} + return fileDescriptor_a5fc8fed8f4e58b6, []int{9} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -363,6 +444,8 @@ func init() { proto.RegisterType((*MsgWrappedUndelegateResponse)(nil), "babylon.epoching.v1.MsgWrappedUndelegateResponse") proto.RegisterType((*MsgWrappedBeginRedelegate)(nil), "babylon.epoching.v1.MsgWrappedBeginRedelegate") proto.RegisterType((*MsgWrappedBeginRedelegateResponse)(nil), "babylon.epoching.v1.MsgWrappedBeginRedelegateResponse") + proto.RegisterType((*MsgWrappedCancelUnbondingDelegation)(nil), "babylon.epoching.v1.MsgWrappedCancelUnbondingDelegation") + proto.RegisterType((*MsgWrappedCancelUnbondingDelegationResponse)(nil), "babylon.epoching.v1.MsgWrappedCancelUnbondingDelegationResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.epoching.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.epoching.v1.MsgUpdateParamsResponse") } @@ -370,40 +453,44 @@ func init() { func init() { proto.RegisterFile("babylon/epoching/v1/tx.proto", fileDescriptor_a5fc8fed8f4e58b6) } var fileDescriptor_a5fc8fed8f4e58b6 = []byte{ - // 526 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xc1, 0x6a, 0x13, 0x41, - 0x1c, 0xc6, 0x77, 0x8d, 0x16, 0xfa, 0x57, 0xac, 0xae, 0xc1, 0x26, 0x63, 0xd8, 0xd4, 0x54, 0x51, - 0x83, 0xce, 0x9a, 0x8a, 0x15, 0x8b, 0x17, 0x83, 0x27, 0x21, 0x20, 0x11, 0x11, 0x04, 0x91, 0xd9, - 0xec, 0x30, 0x59, 0xd2, 0xdd, 0x59, 0x77, 0xa6, 0xa5, 0xb9, 0x15, 0x4f, 0x1e, 0x3d, 0xf8, 0x00, - 0x7d, 0x84, 0x1e, 0x7c, 0x88, 0x1e, 0x8b, 0x5e, 0x3c, 0x89, 0x24, 0x87, 0xfa, 0x18, 0xb2, 0xbb, - 0xb3, 0xbb, 0x31, 0xc9, 0xb6, 0xf1, 0x96, 0xc9, 0xff, 0xfb, 0x7f, 0xdf, 0x6f, 0xd8, 0x8f, 0x81, - 0x9a, 0x4d, 0xec, 0xe1, 0x36, 0xf7, 0x2d, 0x1a, 0xf0, 0x5e, 0xdf, 0xf5, 0x99, 0xb5, 0xdb, 0xb2, - 0xe4, 0x1e, 0x0e, 0x42, 0x2e, 0xb9, 0x71, 0x4d, 0x4d, 0x71, 0x3a, 0xc5, 0xbb, 0x2d, 0x54, 0x66, - 0x9c, 0xf1, 0x78, 0x6e, 0x45, 0xbf, 0x12, 0x29, 0xaa, 0xf7, 0xb8, 0xf0, 0xb8, 0xb0, 0x84, 0x24, - 0x83, 0xc4, 0xc6, 0xa6, 0x92, 0xe4, 0x5e, 0x68, 0x6d, 0x5e, 0x52, 0x40, 0x42, 0xe2, 0x09, 0xa5, - 0xa8, 0x26, 0x16, 0x1f, 0x12, 0xef, 0xe4, 0xa0, 0x46, 0xab, 0xca, 0xdd, 0x13, 0xf1, 0x9a, 0x27, - 0x58, 0x32, 0x68, 0xbc, 0x07, 0xa3, 0x23, 0xd8, 0xdb, 0x90, 0x04, 0x01, 0x75, 0x5e, 0xd0, 0x6d, - 0xca, 0x88, 0xa4, 0xc6, 0x63, 0x28, 0x79, 0x82, 0x55, 0xf4, 0x35, 0xfd, 0xee, 0xc5, 0x8d, 0x75, - 0xac, 0xac, 0x14, 0x1a, 0x56, 0x68, 0xb8, 0x23, 0x58, 0xba, 0xd1, 0x8d, 0xf4, 0x5b, 0x57, 0x3e, - 0x1f, 0xd4, 0xb5, 0x3f, 0x07, 0x75, 0xed, 0xd3, 0xc9, 0x61, 0x33, 0xfa, 0xa7, 0x51, 0x03, 0x34, - 0x6b, 0xdf, 0xa5, 0x22, 0xe0, 0xbe, 0xa0, 0x0d, 0x02, 0xe5, 0x7c, 0xfa, 0xc6, 0x77, 0xd2, 0xf8, - 0x27, 0x93, 0xf1, 0xb7, 0x4f, 0x89, 0xcf, 0x77, 0x8a, 0x00, 0x4c, 0xa8, 0xcd, 0x8b, 0xc8, 0x10, - 0x06, 0x50, 0xcd, 0xe7, 0x6d, 0xca, 0x5c, 0xbf, 0x4b, 0x33, 0x8e, 0x67, 0x93, 0x1c, 0xcd, 0x53, - 0x38, 0xa6, 0x16, 0x8b, 0x60, 0xd6, 0xe1, 0x66, 0x61, 0x58, 0x46, 0xf4, 0x55, 0x87, 0x95, 0xe8, - 0x6a, 0x81, 0x43, 0x24, 0x7d, 0x15, 0x7f, 0x5f, 0x63, 0x13, 0x96, 0xc9, 0x8e, 0xec, 0xf3, 0xd0, - 0x95, 0xc3, 0x18, 0x67, 0xb9, 0x5d, 0xf9, 0xfe, 0xed, 0x41, 0x59, 0x11, 0x3d, 0x77, 0x9c, 0x90, - 0x0a, 0xf1, 0x5a, 0x86, 0xae, 0xcf, 0xba, 0xb9, 0xd4, 0x78, 0x0a, 0x4b, 0x49, 0x43, 0x2a, 0xe7, - 0xe2, 0x3b, 0xdc, 0xc0, 0x73, 0x0a, 0x89, 0x93, 0x90, 0xf6, 0xf9, 0xa3, 0x5f, 0x75, 0xad, 0xab, - 0x16, 0xb6, 0x2e, 0x47, 0xd4, 0xb9, 0x55, 0xa3, 0x0a, 0xab, 0x53, 0x54, 0x29, 0xf1, 0xc6, 0x8f, - 0x12, 0x94, 0x3a, 0x82, 0x19, 0x03, 0x58, 0x99, 0x2e, 0xd2, 0x9d, 0xb9, 0x81, 0xb3, 0x95, 0x40, - 0xd6, 0x82, 0xc2, 0x34, 0xd4, 0xf8, 0x08, 0x57, 0x67, 0x8b, 0x73, 0xef, 0x0c, 0x97, 0x5c, 0x8a, - 0x5a, 0x0b, 0x4b, 0xb3, 0xc8, 0x7d, 0x1d, 0xae, 0x17, 0x34, 0x05, 0x9f, 0xe1, 0x36, 0xa5, 0x47, - 0x9b, 0xff, 0xa7, 0xcf, 0x10, 0x6c, 0xb8, 0xf4, 0x4f, 0x31, 0x6e, 0x15, 0xf9, 0x4c, 0xaa, 0xd0, - 0xfd, 0x45, 0x54, 0x69, 0x06, 0xba, 0xb0, 0x7f, 0x72, 0xd8, 0xd4, 0xdb, 0x2f, 0x8f, 0x46, 0xa6, - 0x7e, 0x3c, 0x32, 0xf5, 0xdf, 0x23, 0x53, 0xff, 0x32, 0x36, 0xb5, 0xe3, 0xb1, 0xa9, 0xfd, 0x1c, - 0x9b, 0xda, 0xbb, 0x87, 0xcc, 0x95, 0xfd, 0x1d, 0x1b, 0xf7, 0xb8, 0x67, 0x29, 0xe3, 0x5e, 0x9f, - 0xb8, 0x7e, 0x7a, 0xb0, 0xf6, 0xf2, 0x37, 0x4a, 0x0e, 0x03, 0x2a, 0xec, 0xa5, 0xf8, 0xb1, 0x79, - 0xf4, 0x37, 0x00, 0x00, 0xff, 0xff, 0x58, 0xb7, 0x7d, 0x0e, 0x2e, 0x05, 0x00, 0x00, + // 585 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x4f, 0x6f, 0x12, 0x41, + 0x18, 0xc6, 0x77, 0x6d, 0x6d, 0xd2, 0x57, 0x63, 0x75, 0x25, 0x16, 0x56, 0xb2, 0x20, 0x68, 0x54, + 0xb4, 0xbb, 0x52, 0xb5, 0x6a, 0xe3, 0x41, 0xd1, 0x78, 0x30, 0x21, 0x31, 0x98, 0xc6, 0xc4, 0xc4, + 0x98, 0x59, 0x76, 0x32, 0x6c, 0x60, 0x67, 0xd6, 0x9d, 0x69, 0x53, 0x4e, 0x36, 0x9e, 0x3c, 0x7a, + 0xf0, 0x6c, 0xfa, 0x11, 0x7a, 0xf0, 0x43, 0xf4, 0xd8, 0x78, 0xf2, 0x64, 0x0c, 0x1c, 0xea, 0x07, + 0xf0, 0x03, 0x18, 0xf6, 0x2f, 0x02, 0x0b, 0xe8, 0x8d, 0xe1, 0x7d, 0xde, 0xe7, 0xf9, 0xc1, 0x3e, + 0x3b, 0x90, 0x37, 0x91, 0xd9, 0xed, 0x30, 0x6a, 0x60, 0x97, 0x35, 0x5b, 0x36, 0x25, 0xc6, 0x4e, + 0xd5, 0x10, 0xbb, 0xba, 0xeb, 0x31, 0xc1, 0x94, 0xf3, 0xe1, 0x54, 0x8f, 0xa6, 0xfa, 0x4e, 0x55, + 0xcd, 0x10, 0x46, 0x98, 0x3f, 0x37, 0x06, 0x9f, 0x02, 0xa9, 0x5a, 0x68, 0x32, 0xee, 0x30, 0x6e, + 0x70, 0x81, 0xda, 0x81, 0x8d, 0x89, 0x05, 0x4a, 0xbc, 0xd4, 0xe2, 0xa4, 0x24, 0x17, 0x79, 0xc8, + 0xe1, 0xa1, 0x22, 0x17, 0x58, 0xbc, 0x0d, 0xbc, 0x83, 0x43, 0x38, 0x5a, 0x0d, 0xdd, 0x1d, 0xee, + 0xaf, 0x39, 0x9c, 0x04, 0x83, 0xd2, 0x1b, 0x50, 0xea, 0x9c, 0xbc, 0xf2, 0x90, 0xeb, 0x62, 0xeb, + 0x29, 0xee, 0x60, 0x82, 0x04, 0x56, 0xee, 0xc2, 0x82, 0xc3, 0x49, 0x56, 0x2e, 0xca, 0xd7, 0x4e, + 0xad, 0x97, 0xf5, 0xd0, 0x2a, 0x44, 0xd3, 0x43, 0x34, 0xbd, 0xce, 0x49, 0xb4, 0xd1, 0x18, 0xe8, + 0x37, 0xcf, 0x7e, 0xdc, 0x2f, 0x48, 0xbf, 0xf6, 0x0b, 0xd2, 0x87, 0xe3, 0x83, 0xca, 0xe0, 0x9b, + 0x52, 0x1e, 0xd4, 0x71, 0xfb, 0x06, 0xe6, 0x2e, 0xa3, 0x1c, 0x97, 0x10, 0x64, 0x92, 0xe9, 0x16, + 0xb5, 0xa2, 0xf8, 0x7b, 0xc3, 0xf1, 0x57, 0xa6, 0xc4, 0x27, 0x3b, 0x69, 0x00, 0x1a, 0xe4, 0x27, + 0x45, 0xc4, 0x08, 0x6d, 0xc8, 0x25, 0xf3, 0x1a, 0x26, 0x36, 0x6d, 0xe0, 0x98, 0xe3, 0xe1, 0x30, + 0x47, 0x65, 0x0a, 0xc7, 0xc8, 0x62, 0x1a, 0x4c, 0x19, 0x2e, 0xa5, 0x86, 0xc5, 0x44, 0xef, 0xa1, + 0x9c, 0x88, 0x9e, 0x20, 0xda, 0xc4, 0x9d, 0x2d, 0x6a, 0x32, 0x6a, 0xd9, 0x34, 0xfa, 0xbb, 0x6d, + 0x46, 0x95, 0x67, 0xc3, 0x6c, 0x77, 0xa6, 0xb0, 0xa5, 0x5a, 0xa4, 0x51, 0xae, 0xc1, 0x8d, 0x39, + 0x00, 0x62, 0xde, 0xcf, 0x32, 0xac, 0x0c, 0x1e, 0x85, 0x6b, 0x21, 0x81, 0x5f, 0xf8, 0x7d, 0x54, + 0x36, 0x60, 0x19, 0x6d, 0x8b, 0x16, 0xf3, 0x6c, 0xd1, 0xf5, 0x11, 0x97, 0x6b, 0xd9, 0x6f, 0x5f, + 0xd7, 0x32, 0x21, 0xe5, 0x63, 0xcb, 0xf2, 0x30, 0xe7, 0x2f, 0x85, 0x67, 0x53, 0xd2, 0x48, 0xa4, + 0xca, 0x03, 0x58, 0x0a, 0x1a, 0x9d, 0x3d, 0xe1, 0xff, 0xae, 0x8b, 0xfa, 0x84, 0x17, 0x48, 0x0f, + 0x42, 0x6a, 0x8b, 0x87, 0x3f, 0x0a, 0x52, 0x23, 0x5c, 0xd8, 0x3c, 0x33, 0xe0, 0x4f, 0xac, 0x4a, + 0x39, 0x58, 0x1d, 0xa1, 0x8a, 0x88, 0xd7, 0x7f, 0x2f, 0xc2, 0x42, 0x9d, 0x13, 0xa5, 0x0d, 0x2b, + 0xa3, 0xc5, 0xbf, 0x3a, 0x31, 0x70, 0xbc, 0xc2, 0xaa, 0x31, 0xa7, 0x30, 0x0a, 0x55, 0xde, 0xc1, + 0xb9, 0xf1, 0xa2, 0x5f, 0x9f, 0xe1, 0x92, 0x48, 0xd5, 0xea, 0xdc, 0xd2, 0x38, 0x72, 0x4f, 0x86, + 0x0b, 0x29, 0xcd, 0xd6, 0x67, 0xb8, 0x8d, 0xe8, 0xd5, 0x8d, 0x7f, 0xd3, 0xc7, 0x08, 0x5f, 0x64, + 0x28, 0xce, 0xac, 0xf2, 0xfd, 0x19, 0xe6, 0xa9, 0x9b, 0xea, 0xa3, 0xff, 0xdd, 0x8c, 0x01, 0x4d, + 0x38, 0xfd, 0x57, 0x73, 0x2f, 0xa7, 0x39, 0x0e, 0xab, 0xd4, 0x9b, 0xf3, 0xa8, 0xa2, 0x0c, 0xf5, + 0xe4, 0xde, 0xf1, 0x41, 0x45, 0xae, 0x3d, 0x3f, 0xec, 0x69, 0xf2, 0x51, 0x4f, 0x93, 0x7f, 0xf6, + 0x34, 0xf9, 0x53, 0x5f, 0x93, 0x8e, 0xfa, 0x9a, 0xf4, 0xbd, 0xaf, 0x49, 0xaf, 0x6f, 0x11, 0x5b, + 0xb4, 0xb6, 0x4d, 0xbd, 0xc9, 0x1c, 0x23, 0x34, 0x6e, 0xb6, 0x90, 0x4d, 0xa3, 0x83, 0xb1, 0x9b, + 0x5c, 0xfa, 0xa2, 0xeb, 0x62, 0x6e, 0x2e, 0xf9, 0xb7, 0xf7, 0xed, 0x3f, 0x01, 0x00, 0x00, 0xff, + 0xff, 0xdf, 0xdb, 0xcd, 0x7f, 0x7f, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -427,6 +514,9 @@ type MsgClient interface { // WrappedBeginRedelegate defines a method for performing a redelegation of // coins from a delegator and source validator to a destination validator. WrappedBeginRedelegate(ctx context.Context, in *MsgWrappedBeginRedelegate, opts ...grpc.CallOption) (*MsgWrappedBeginRedelegateResponse, error) + // WrappedCancelUnbondingDelegation defines a method for cancelling unbonding of + // coins from a delegator and source validator to a destination validator. + WrappedCancelUnbondingDelegation(ctx context.Context, in *MsgWrappedCancelUnbondingDelegation, opts ...grpc.CallOption) (*MsgWrappedCancelUnbondingDelegationResponse, error) // UpdateParams defines a method for updating epoching module parameters. UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } @@ -466,6 +556,15 @@ func (c *msgClient) WrappedBeginRedelegate(ctx context.Context, in *MsgWrappedBe return out, nil } +func (c *msgClient) WrappedCancelUnbondingDelegation(ctx context.Context, in *MsgWrappedCancelUnbondingDelegation, opts ...grpc.CallOption) (*MsgWrappedCancelUnbondingDelegationResponse, error) { + out := new(MsgWrappedCancelUnbondingDelegationResponse) + err := c.cc.Invoke(ctx, "/babylon.epoching.v1.Msg/WrappedCancelUnbondingDelegation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/babylon.epoching.v1.Msg/UpdateParams", in, out, opts...) @@ -486,6 +585,9 @@ type MsgServer interface { // WrappedBeginRedelegate defines a method for performing a redelegation of // coins from a delegator and source validator to a destination validator. WrappedBeginRedelegate(context.Context, *MsgWrappedBeginRedelegate) (*MsgWrappedBeginRedelegateResponse, error) + // WrappedCancelUnbondingDelegation defines a method for cancelling unbonding of + // coins from a delegator and source validator to a destination validator. + WrappedCancelUnbondingDelegation(context.Context, *MsgWrappedCancelUnbondingDelegation) (*MsgWrappedCancelUnbondingDelegationResponse, error) // UpdateParams defines a method for updating epoching module parameters. UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } @@ -503,6 +605,9 @@ func (*UnimplementedMsgServer) WrappedUndelegate(ctx context.Context, req *MsgWr func (*UnimplementedMsgServer) WrappedBeginRedelegate(ctx context.Context, req *MsgWrappedBeginRedelegate) (*MsgWrappedBeginRedelegateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WrappedBeginRedelegate not implemented") } +func (*UnimplementedMsgServer) WrappedCancelUnbondingDelegation(ctx context.Context, req *MsgWrappedCancelUnbondingDelegation) (*MsgWrappedCancelUnbondingDelegationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WrappedCancelUnbondingDelegation not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -565,6 +670,24 @@ func _Msg_WrappedBeginRedelegate_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Msg_WrappedCancelUnbondingDelegation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgWrappedCancelUnbondingDelegation) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).WrappedCancelUnbondingDelegation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.epoching.v1.Msg/WrappedCancelUnbondingDelegation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).WrappedCancelUnbondingDelegation(ctx, req.(*MsgWrappedCancelUnbondingDelegation)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -599,6 +722,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "WrappedBeginRedelegate", Handler: _Msg_WrappedBeginRedelegate_Handler, }, + { + MethodName: "WrappedCancelUnbondingDelegation", + Handler: _Msg_WrappedCancelUnbondingDelegation_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -782,6 +909,64 @@ func (m *MsgWrappedBeginRedelegateResponse) MarshalToSizedBuffer(dAtA []byte) (i return len(dAtA) - i, nil } +func (m *MsgWrappedCancelUnbondingDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWrappedCancelUnbondingDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWrappedCancelUnbondingDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Msg != nil { + { + size, err := m.Msg.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgWrappedCancelUnbondingDelegationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgWrappedCancelUnbondingDelegationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgWrappedCancelUnbondingDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -922,6 +1107,28 @@ func (m *MsgWrappedBeginRedelegateResponse) Size() (n int) { return n } +func (m *MsgWrappedCancelUnbondingDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Msg != nil { + l = m.Msg.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgWrappedCancelUnbondingDelegationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgUpdateParams) Size() (n int) { if m == nil { return 0 @@ -1360,6 +1567,142 @@ func (m *MsgWrappedBeginRedelegateResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgWrappedCancelUnbondingDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWrappedCancelUnbondingDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWrappedCancelUnbondingDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Msg == nil { + m.Msg = &types.MsgCancelUnbondingDelegation{} + } + if err := m.Msg.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgWrappedCancelUnbondingDelegationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgWrappedCancelUnbondingDelegationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgWrappedCancelUnbondingDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From d745d10727022aa93ca20b59b6f4d60159b9e3f1 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 8 Dec 2023 20:36:06 +1100 Subject: [PATCH 125/202] chore: cleanup deprecated msg interfaces and simulations (#144) --- x/btccheckpoint/keeper/msg_server.go | 3 + x/btccheckpoint/module_simulation.go | 45 --- x/btccheckpoint/simulation/simap.go | 15 - x/btccheckpoint/types/msgs.go | 37 --- x/btclightclient/module_simulation.go | 45 --- x/btclightclient/simulation/simap.go | 15 - x/btclightclient/types/msgs.go | 21 -- x/btcstaking/keeper/msg_server.go | 21 ++ x/btcstaking/keeper/msg_server_test.go | 48 ++- x/btcstaking/module_simulation.go | 55 ---- x/btcstaking/simulation/helpers.go | 15 - x/btcstaking/types/msg.go | 54 ---- x/checkpointing/keeper/msg_server.go | 2 +- x/checkpointing/module_simulation.go | 47 --- x/checkpointing/simulation/simap.go | 15 - x/checkpointing/types/msgs.go | 19 +- x/epoching/keeper/msg_server.go | 15 + x/epoching/module.go | 10 +- x/epoching/module_simulation.go | 39 --- x/epoching/simulation/decoder.go | 63 ---- x/epoching/simulation/decoder_test.go | 81 ----- x/epoching/simulation/genesis.go | 38 --- x/epoching/simulation/genesis_test.go | 74 ----- x/epoching/simulation/operations.go | 371 ----------------------- x/epoching/simulation/operations_test.go | 1 - x/epoching/simulation/simap.go | 15 - x/epoching/types/msg.go | 86 ------ x/epoching/types/msg_test.go | 120 -------- x/finality/keeper/msg_server.go | 31 +- x/finality/module_simulation.go | 54 ---- x/finality/simulation/helpers.go | 15 - x/finality/types/msg.go | 78 +---- x/finality/types/msg_test.go | 5 +- x/incentive/keeper/msg_server.go | 3 + x/incentive/module_simulation.go | 54 ---- x/incentive/simulation/helpers.go | 15 - x/incentive/types/msg.go | 38 --- x/monitor/module_simulation.go | 42 --- x/monitor/simulation/simap.go | 15 - x/zoneconcierge/keeper/msg_server.go | 3 + x/zoneconcierge/module_simulation.go | 47 --- x/zoneconcierge/simulation/helpers.go | 15 - x/zoneconcierge/types/msg.go | 20 -- 43 files changed, 122 insertions(+), 1683 deletions(-) delete mode 100644 x/btccheckpoint/module_simulation.go delete mode 100644 x/btccheckpoint/simulation/simap.go delete mode 100644 x/btclightclient/module_simulation.go delete mode 100644 x/btclightclient/simulation/simap.go delete mode 100644 x/btcstaking/module_simulation.go delete mode 100644 x/btcstaking/simulation/helpers.go delete mode 100644 x/checkpointing/module_simulation.go delete mode 100644 x/checkpointing/simulation/simap.go delete mode 100644 x/epoching/module_simulation.go delete mode 100644 x/epoching/simulation/decoder.go delete mode 100644 x/epoching/simulation/decoder_test.go delete mode 100644 x/epoching/simulation/genesis.go delete mode 100644 x/epoching/simulation/genesis_test.go delete mode 100644 x/epoching/simulation/operations.go delete mode 100644 x/epoching/simulation/operations_test.go delete mode 100644 x/epoching/simulation/simap.go delete mode 100644 x/finality/module_simulation.go delete mode 100644 x/finality/simulation/helpers.go delete mode 100644 x/incentive/module_simulation.go delete mode 100644 x/incentive/simulation/helpers.go delete mode 100644 x/monitor/module_simulation.go delete mode 100644 x/monitor/simulation/simap.go delete mode 100644 x/zoneconcierge/module_simulation.go delete mode 100644 x/zoneconcierge/simulation/helpers.go diff --git a/x/btccheckpoint/keeper/msg_server.go b/x/btccheckpoint/keeper/msg_server.go index b5faff81c..17480a698 100644 --- a/x/btccheckpoint/keeper/msg_server.go +++ b/x/btccheckpoint/keeper/msg_server.go @@ -100,6 +100,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.k.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.k.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.k.SetParams(ctx, req.Params); err != nil { diff --git a/x/btccheckpoint/module_simulation.go b/x/btccheckpoint/module_simulation.go deleted file mode 100644 index cd9dd2454..000000000 --- a/x/btccheckpoint/module_simulation.go +++ /dev/null @@ -1,45 +0,0 @@ -package btccheckpoint - -import ( - btccheckpointsimulation "github.com/babylonchain/babylon/x/btccheckpoint/simulation" - "github.com/babylonchain/babylon/x/btccheckpoint/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = btccheckpointsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - btccheckpointGenesis := types.GenesisState{ - Params: types.DefaultParams(), - } - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&btccheckpointGenesis) -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} diff --git a/x/btccheckpoint/simulation/simap.go b/x/btccheckpoint/simulation/simap.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/btccheckpoint/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/btccheckpoint/types/msgs.go b/x/btccheckpoint/types/msgs.go index 96fca5003..312f7cc68 100644 --- a/x/btccheckpoint/types/msgs.go +++ b/x/btccheckpoint/types/msgs.go @@ -5,8 +5,6 @@ import ( fmt "fmt" "math/big" - errorsmod "cosmossdk.io/errors" - txformat "github.com/babylonchain/babylon/btctxformatter" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -109,43 +107,8 @@ func ParseSubmission( return sub, nil } -func (m *MsgInsertBTCSpvProof) ValidateBasic() error { - // m.Proofs are validated in ante-handler - _, err := sdk.AccAddressFromBech32(m.Submitter) - - if err != nil { - return sdkerrors.ErrInvalidAddress.Wrapf("invalid submitter address: %s", err) - } - - return nil -} - -func (m *MsgInsertBTCSpvProof) GetSigners() []sdk.AccAddress { - // cosmos-sdk modules usually ignore possible error here, we panic for the sake - // of informing something terrible had happend - - submitter, err := sdk.AccAddressFromBech32(m.Submitter) - if err != nil { - // Panic, since the GetSigners method is called after ValidateBasic - // which performs the same check. - panic(err) - } - - return []sdk.AccAddress{submitter} -} - -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - // ValidateBasic does a sanity check on the provided data. func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - if err := m.Params.Validate(); err != nil { return err } diff --git a/x/btclightclient/module_simulation.go b/x/btclightclient/module_simulation.go deleted file mode 100644 index 2ee7e42a0..000000000 --- a/x/btclightclient/module_simulation.go +++ /dev/null @@ -1,45 +0,0 @@ -package btclightclient - -import ( - "github.com/babylonchain/babylon/testutil/sample" - btclightclientsimulation "github.com/babylonchain/babylon/x/btclightclient/simulation" - "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = btclightclientsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - btclightclientGenesis := types.DefaultGenesis() - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(btclightclientGenesis) -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} diff --git a/x/btclightclient/simulation/simap.go b/x/btclightclient/simulation/simap.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/btclightclient/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/btclightclient/types/msgs.go b/x/btclightclient/types/msgs.go index 95d44df18..74d5e8fbb 100644 --- a/x/btclightclient/types/msgs.go +++ b/x/btclightclient/types/msgs.go @@ -39,27 +39,6 @@ func NewMsgInsertHeaders(signer sdk.AccAddress, headersHex string) (*MsgInsertHe return &MsgInsertHeaders{Signer: signer.String(), Headers: headers}, nil } -func (msg *MsgInsertHeaders) ValidateBasic() error { - // This function validates stateless message elements - // msg.Header is validated in ante-handler - _, err := sdk.AccAddressFromBech32(msg.Signer) - if err != nil { - return err - } - return nil -} - -func (msg *MsgInsertHeaders) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(msg.Signer) - if err != nil { - // Panic, since the GetSigners method is called after ValidateBasic - // which performs the same check. - panic(err) - } - - return []sdk.AccAddress{signer} -} - func (msg *MsgInsertHeaders) ValidateHeaders(powLimit *big.Int) error { // TOOD: Limit number of headers in message? for _, header := range msg.Headers { diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index a109a3695..a0d6760a3 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -37,6 +37,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.SetParams(ctx, req.Params); err != nil { @@ -50,6 +53,10 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { // ensure the validator address does not exist before ctx := sdk.UnwrapSDKContext(goCtx) + // basic stateless checks + if err := req.ValidateBasic(); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", err) + } // verify proof of possession if err := req.Pop.Verify(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { @@ -92,6 +99,10 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // TODO: refactor this handler. It's now too convoluted func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCreateBTCDelegation) (*types.MsgCreateBTCDelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // basic stateless checks + if err := req.ValidateBasic(); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", err) + } params := ms.GetParams(ctx) btccParams := ms.btccKeeper.GetParams(ctx) @@ -351,6 +362,11 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // TODO: refactor this handler. Now it's too convoluted func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCovenantSigs) (*types.MsgAddCovenantSigsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // basic stateless checks + if err := req.ValidateBasic(); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", err) + } + params := ms.GetParams(ctx) // ensure BTC delegation exists @@ -484,6 +500,11 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove // AddCovenantSig adds a signature from covenant to a BTC delegation func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // basic stateless checks + if err := req.ValidateBasic(); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", err) + } + bsParams := ms.GetParams(ctx) // ensure BTC delegation exists diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 67572228c..88f724b02 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -575,18 +575,46 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { ) require.NoError(t, err) + stkTxHash := testStakingInfo.StakingTx.TxHash() + unbondingTime := 100 + 1 + unbondingValue := stakingValue - datagen.UnbondingTxFee // TODO: parameterise fee + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( + r, + t, + h.Net, + delSK, + []*btcec.PublicKey{validatorPK}, + covenantPKs, + bsParams.CovenantQuorum, + wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), + uint16(unbondingTime), + unbondingValue, + bsParams.SlashingAddress, + changeAddress.EncodeAddress(), + bsParams.SlashingRate, + ) + unbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) + h.NoError(err) + delUnbondingSlashingSig, err := testUnbondingInfo.GenDelSlashingTxSig(delSK) + h.NoError(err) + // all good, construct and send MsgCreateBTCDelegation message msgCreateBTCDel := &types.MsgCreateBTCDelegation{ - Signer: signer, - BabylonPk: delBabylonPK.(*secp256k1.PubKey), - ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, - BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), - Pop: pop, - StakingTime: uint32(stakingTimeBlocks), - StakingValue: stakingValue, - StakingTx: txInfo, - SlashingTx: testStakingInfo.SlashingTx, - DelegatorSlashingSig: delegatorSig, + Signer: signer, + BabylonPk: delBabylonPK.(*secp256k1.PubKey), + ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSlashingSig: delegatorSig, + UnbondingTx: unbondingTx, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: unbondingValue, + UnbondingSlashingTx: testUnbondingInfo.SlashingTx, + DelegatorUnbondingSlashingSig: delUnbondingSlashingSig, } _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) require.Error(t, err) diff --git a/x/btcstaking/module_simulation.go b/x/btcstaking/module_simulation.go deleted file mode 100644 index 6a816bd75..000000000 --- a/x/btcstaking/module_simulation.go +++ /dev/null @@ -1,55 +0,0 @@ -package btcstaking - -import ( - "math/rand" - - "github.com/babylonchain/babylon/testutil/sample" - btcstakingsimulation "github.com/babylonchain/babylon/x/btcstaking/simulation" - "github.com/babylonchain/babylon/x/btcstaking/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = btcstakingsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace - _ = rand.Rand{} -) - -// GenerateGenesisState creates a randomized GenState of the module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - btcstakingGenesis := types.GenesisState{ - Params: types.DefaultParams(), - } - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&btcstakingGenesis) -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} - -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{} -} diff --git a/x/btcstaking/simulation/helpers.go b/x/btcstaking/simulation/helpers.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/btcstaking/simulation/helpers.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index b4adb0e76..0ff123ff0 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -4,11 +4,8 @@ import ( "fmt" math "math" - errorsmod "cosmossdk.io/errors" - "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -22,33 +19,6 @@ var ( _ sdk.Msg = &MsgBTCUndelegate{} ) -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - - if err := m.Params.Validate(); err != nil { - return err - } - - return nil -} - -func (m *MsgCreateBTCValidator) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - func (m *MsgCreateBTCValidator) ValidateBasic() error { if m.Commission == nil { return fmt.Errorf("empty commission") @@ -78,14 +48,6 @@ func (m *MsgCreateBTCValidator) ValidateBasic() error { return nil } -func (m *MsgCreateBTCDelegation) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.BabylonPk == nil { return fmt.Errorf("empty Babylon public key") @@ -156,14 +118,6 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { return nil } -func (m *MsgAddCovenantSigs) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - func (m *MsgAddCovenantSigs) ValidateBasic() error { if m.Pk == nil { return fmt.Errorf("empty BTC covenant public key") @@ -186,14 +140,6 @@ func (m *MsgAddCovenantSigs) ValidateBasic() error { return nil } -func (m *MsgBTCUndelegate) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - func (m *MsgBTCUndelegate) ValidateBasic() error { if len(m.StakingTxHash) != chainhash.MaxHashStringSize { return fmt.Errorf("staking tx hash is not %d", chainhash.MaxHashStringSize) diff --git a/x/checkpointing/keeper/msg_server.go b/x/checkpointing/keeper/msg_server.go index 70baa36a5..20d0870f4 100644 --- a/x/checkpointing/keeper/msg_server.go +++ b/x/checkpointing/keeper/msg_server.go @@ -27,7 +27,7 @@ var _ types.MsgServer = msgServer{} func (m msgServer) AddBlsSig(goCtx context.Context, msg *types.MsgAddBlsSig) (*types.MsgAddBlsSigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - ctx.Logger().Info(fmt.Sprintf("received BLS sig for epoch %d from %s", msg.BlsSig.EpochNum, msg.GetSigners())) + ctx.Logger().Info(fmt.Sprintf("received BLS sig for epoch %d from %s", msg.BlsSig.EpochNum, msg.Signer)) err := m.k.addBlsSig(ctx, msg.BlsSig) if err != nil { diff --git a/x/checkpointing/module_simulation.go b/x/checkpointing/module_simulation.go deleted file mode 100644 index 1303f639b..000000000 --- a/x/checkpointing/module_simulation.go +++ /dev/null @@ -1,47 +0,0 @@ -package checkpointing - -import ( - "github.com/babylonchain/babylon/testutil/sample" - checkpointingsimulation "github.com/babylonchain/babylon/x/checkpointing/simulation" - "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = checkpointingsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -const () - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - headeroracleGenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&headeroracleGenesis) -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} diff --git a/x/checkpointing/simulation/simap.go b/x/checkpointing/simulation/simap.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/checkpointing/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/checkpointing/types/msgs.go b/x/checkpointing/types/msgs.go index d824e0e6b..4a4f99e9a 100644 --- a/x/checkpointing/types/msgs.go +++ b/x/checkpointing/types/msgs.go @@ -41,13 +41,7 @@ func NewMsgWrappedCreateValidator(msgCreateVal *stakingtypes.MsgCreateValidator, // ValidateBasic validates stateless message elements func (m *MsgAddBlsSig) ValidateBasic() error { - // This function validates stateless message elements - _, err := sdk.ValAddressFromBech32(m.BlsSig.SignerAddress) - if err != nil { - return err - } - - err = m.BlsSig.BlsSig.ValidateBasic() + err := m.BlsSig.BlsSig.ValidateBasic() if err != nil { return err } @@ -59,17 +53,6 @@ func (m *MsgAddBlsSig) ValidateBasic() error { return nil } -func (m *MsgAddBlsSig) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - // Panic, since the GetSigners method is called after ValidateBasic - // which performs the same check. - panic(err) - } - - return []sdk.AccAddress{signer} -} - func (m *MsgWrappedCreateValidator) VerifyPoP(valPubkey cryptotypes.PubKey) bool { return m.Key.Pop.IsValid(*m.Key.Pubkey, valPubkey) } diff --git a/x/epoching/keeper/msg_server.go b/x/epoching/keeper/msg_server.go index 182ffbd73..3db5b554d 100644 --- a/x/epoching/keeper/msg_server.go +++ b/x/epoching/keeper/msg_server.go @@ -26,6 +26,9 @@ var _ types.MsgServer = msgServer{} // WrappedDelegate handles the MsgWrappedDelegate request func (ms msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedDelegate) (*types.MsgWrappedDelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + if msg.Msg == nil { + return nil, types.ErrNoWrappedMsg + } // verification rules ported from staking module valAddr, valErr := sdk.ValAddressFromBech32(msg.Msg.ValidatorAddress) @@ -81,6 +84,9 @@ func (ms msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrapped // WrappedUndelegate handles the MsgWrappedUndelegate request func (ms msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrappedUndelegate) (*types.MsgWrappedUndelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + if msg.Msg == nil { + return nil, types.ErrNoWrappedMsg + } // verification rules ported from staking module valAddr, err := sdk.ValAddressFromBech32(msg.Msg.ValidatorAddress) @@ -137,6 +143,9 @@ func (ms msgServer) WrappedUndelegate(goCtx context.Context, msg *types.MsgWrapp // WrappedBeginRedelegate handles the MsgWrappedBeginRedelegate request func (ms msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.MsgWrappedBeginRedelegate) (*types.MsgWrappedBeginRedelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + if msg.Msg == nil { + return nil, types.ErrNoWrappedMsg + } // verification rules ported from staking module valSrcAddr, err := sdk.ValAddressFromBech32(msg.Msg.ValidatorSrcAddress) @@ -196,6 +205,9 @@ func (ms msgServer) WrappedBeginRedelegate(goCtx context.Context, msg *types.Msg // WrappedCancelUnbondingDelegation handles the MsgWrappedCancelUnbondingDelegation request func (ms msgServer) WrappedCancelUnbondingDelegation(goCtx context.Context, msg *types.MsgWrappedCancelUnbondingDelegation) (*types.MsgWrappedCancelUnbondingDelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + if msg.Msg == nil { + return nil, types.ErrNoWrappedMsg + } // verification rules ported from staking module if _, err := sdk.AccAddressFromBech32(msg.Msg.DelegatorAddress); err != nil { @@ -266,6 +278,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.SetParams(ctx, req.Params); err != nil { diff --git a/x/epoching/module.go b/x/epoching/module.go index 34f93dcdb..f21bdcdcb 100644 --- a/x/epoching/module.go +++ b/x/epoching/module.go @@ -2,10 +2,11 @@ package epoching import ( "context" - "cosmossdk.io/core/appmodule" "encoding/json" "fmt" + "cosmossdk.io/core/appmodule" + "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -23,10 +24,9 @@ import ( ) var ( - _ module.AppModuleBasic = AppModuleBasic{} - _ appmodule.HasBeginBlocker = AppModule{} - _ module.HasABCIEndBlock = AppModule{} - _ module.AppModuleSimulation = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.HasBeginBlocker = AppModule{} + _ module.HasABCIEndBlock = AppModule{} ) // ---------------------------------------------------------------------------- diff --git a/x/epoching/module_simulation.go b/x/epoching/module_simulation.go deleted file mode 100644 index 0b622d3e4..000000000 --- a/x/epoching/module_simulation.go +++ /dev/null @@ -1,39 +0,0 @@ -package epoching - -import ( - epochingsimulation "github.com/babylonchain/babylon/x/epoching/simulation" - "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = epochingsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - epochingsimulation.RandomizedGenState(simState) -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { - sdr[types.StoreKey] = epochingsimulation.NewDecodeStore(am.cdc) -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return epochingsimulation.WeightedOperations( - simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.stakingKeeper, am.keeper, - ) -} diff --git a/x/epoching/simulation/decoder.go b/x/epoching/simulation/decoder.go deleted file mode 100644 index 9c3bccc45..000000000 --- a/x/epoching/simulation/decoder.go +++ /dev/null @@ -1,63 +0,0 @@ -package simulation - -import ( - "bytes" - "fmt" - - errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" - "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/kv" -) - -// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's -// Value to the corresponding epoching type. -func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { - return func(kvA, kvB kv.Pair) string { - switch { - case bytes.Equal(kvA.Key[:1], types.EpochNumberKey), - bytes.Equal(kvA.Key[:1], types.QueueLengthKey): - return fmt.Sprintf("%v\n%v", sdk.BigEndianToUint64(kvA.Value), sdk.BigEndianToUint64(kvB.Value)) - - case bytes.Equal(kvA.Key[:1], types.MsgQueueKey): - var qmA, qmB sdk.Msg - err := cdc.UnmarshalInterface(kvA.Value, &qmA) - if err != nil { - panic(err) - } - err = cdc.UnmarshalInterface(kvB.Value, &qmB) - if err != nil { - panic(err) - } - return fmt.Sprintf("%v\n%v", qmA.(*types.QueuedMessage).MsgId, qmB.(*types.QueuedMessage).MsgId) - - case bytes.Equal(kvA.Key[:1], types.ValidatorSetKey), - bytes.Equal(kvA.Key[:1], types.SlashedValidatorSetKey): - valSetA, err := types.NewValidatorSetFromBytes(kvA.Value) - if err != nil { - panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) - } - valSetB, err := types.NewValidatorSetFromBytes(kvB.Value) - if err != nil { - panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) - } - return fmt.Sprintf("%v\n%v", valSetA, valSetB) - - case bytes.Equal(kvA.Key[:1], types.VotingPowerKey), - bytes.Equal(kvA.Key[:1], types.SlashedVotingPowerKey): - var powerA, powerB math.Int - if err := powerA.Unmarshal(kvA.Value); err != nil { - panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) - } - if err := powerB.Unmarshal(kvA.Value); err != nil { - panic(errorsmod.Wrap(types.ErrUnmarshal, err.Error())) - } - return fmt.Sprintf("%v\n%v", powerA, powerB) - - default: - panic(fmt.Sprintf("invalid epoching key prefix %X", kvA.Key[:1])) - } - } -} diff --git a/x/epoching/simulation/decoder_test.go b/x/epoching/simulation/decoder_test.go deleted file mode 100644 index a93085931..000000000 --- a/x/epoching/simulation/decoder_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package simulation_test - -import ( - sdkmath "cosmossdk.io/math" - "fmt" - "github.com/babylonchain/babylon/app" - "testing" - - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/kv" - - "github.com/babylonchain/babylon/x/epoching/simulation" - "github.com/babylonchain/babylon/x/epoching/types" -) - -var ( - delPk1 = ed25519.GenPrivKey().PubKey() - valAddr1 = sdk.ValAddress(delPk1.Address()) - oneBytes, _ = sdkmath.NewInt(1).Marshal() -) - -func TestDecodeStore(t *testing.T) { - cdc := app.GetEncodingConfig().Codec - dec := simulation.NewDecodeStore(cdc) - - epochNumber := uint64(123) - queuedMsg := types.QueuedMessage{ - TxId: sdk.Uint64ToBigEndian(333), - MsgId: sdk.Uint64ToBigEndian(444), - Msg: &types.QueuedMessage_MsgDelegate{MsgDelegate: &stakingtypes.MsgDelegate{}}, - } - valSet := types.ValidatorSet{ - types.Validator{ - Addr: valAddr1, - Power: 1, - }, - } - - marshaledQueueMsg, err := cdc.MarshalInterface(&queuedMsg) - require.NoError(t, err) - kvPairs := kv.Pairs{ - Pairs: []kv.Pair{ - {Key: types.EpochNumberKey, Value: sdk.Uint64ToBigEndian(epochNumber)}, - {Key: types.MsgQueueKey, Value: marshaledQueueMsg}, - {Key: types.ValidatorSetKey, Value: valSet.MustMarshal()}, - {Key: types.SlashedValidatorSetKey, Value: valSet.MustMarshal()}, - {Key: types.VotingPowerKey, Value: oneBytes}, - {Key: types.SlashedVotingPowerKey, Value: oneBytes}, - {Key: []byte{0x99}, Value: []byte{0x99}}, // This test should panic - }, - } - - tests := []struct { - name string - expectedLog string - }{ - {"EpochNumber", fmt.Sprintf("%v\n%v", epochNumber, epochNumber)}, - {"QueuedMsg", fmt.Sprintf("%v\n%v", queuedMsg.MsgId, queuedMsg.MsgId)}, - {"ValidatorSet", fmt.Sprintf("%v\n%v", valSet, valSet)}, - {"SlashedValidatorSet", fmt.Sprintf("%v\n%v", valSet, valSet)}, - {"VotingPower", fmt.Sprintf("%v\n%v", sdkmath.NewInt(1), sdkmath.NewInt(1))}, - {"SlashedVotingPower", fmt.Sprintf("%v\n%v", sdkmath.NewInt(1), sdkmath.NewInt(1))}, - {"other", ""}, - } - for i, tt := range tests { - i, tt := i, tt - t.Run(tt.name, func(t *testing.T) { - switch i { - case len(tests) - 1: - require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) - default: - require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) - } - }) - } -} diff --git a/x/epoching/simulation/genesis.go b/x/epoching/simulation/genesis.go deleted file mode 100644 index 1adca826d..000000000 --- a/x/epoching/simulation/genesis.go +++ /dev/null @@ -1,38 +0,0 @@ -package simulation - -import ( - "encoding/json" - "fmt" - "math/rand" - - "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/types/module" -) - -// Simulation parameter constants -const ( - EpochIntervalKey = "epoch_interval" -) - -// genEpochInterval returns randomized EpochInterval -func genEpochInterval(r *rand.Rand) uint64 { - return uint64(r.Intn(10) + 2) -} - -// RandomizedGenState generates a random GenesisState for staking -func RandomizedGenState(simState *module.SimulationState) { - var epochInterval uint64 - simState.AppParams.GetOrGenerate( - EpochIntervalKey, &epochInterval, simState.Rand, - func(r *rand.Rand) { epochInterval = genEpochInterval(r) }, - ) - params := types.NewParams(epochInterval) - epochingGenesis := types.NewGenesis(params) - - bz, err := json.MarshalIndent(&epochingGenesis.Params, "", " ") - if err != nil { - panic(err) - } - fmt.Printf("Selected randomly generated epoching parameters:\n%s\n", bz) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(epochingGenesis) -} diff --git a/x/epoching/simulation/genesis_test.go b/x/epoching/simulation/genesis_test.go deleted file mode 100644 index 3e8a7d10b..000000000 --- a/x/epoching/simulation/genesis_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package simulation_test - -import ( - "encoding/json" - "math/rand" - "testing" - - sdkmath "cosmossdk.io/math" - - "github.com/stretchr/testify/require" - - "github.com/babylonchain/babylon/x/epoching/simulation" - "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// TestRandomizedGenState tests the normal scenario of applying RandomizedGenState given a fixed seed. -// Abnormal scenarios are not tested here. -func TestRandomizedGenState(t *testing.T) { - interfaceRegistry := codectypes.NewInterfaceRegistry() - cryptocodec.RegisterInterfaces(interfaceRegistry) - cdc := codec.NewProtoCodec(interfaceRegistry) - - s := rand.NewSource(1) - r := rand.New(s) - - simState := module.SimulationState{ - AppParams: make(simtypes.AppParams), - Cdc: cdc, - Rand: r, - NumBonded: 3, - Accounts: simtypes.RandomAccounts(r, 3), - InitialStake: sdkmath.NewInt(1000), - GenState: make(map[string]json.RawMessage), - } - - simulation.RandomizedGenState(&simState) - - var epochingGenesis types.GenesisState - simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &epochingGenesis) - - require.Equal(t, uint64(0x2), epochingGenesis.Params.EpochInterval) -} - -// TestRandomizedGenState1 tests abnormal scenarios of applying RandomizedGenState. -func TestRandomizedGenState1(t *testing.T) { - interfaceRegistry := codectypes.NewInterfaceRegistry() - cdc := codec.NewProtoCodec(interfaceRegistry) - - s := rand.NewSource(1) - r := rand.New(s) - // all these tests will panic - tests := []struct { - simState module.SimulationState - panicMsg string - }{ - { // panic => reason: incomplete initialization of the simState - module.SimulationState{}, "invalid memory address or nil pointer dereference"}, - { // panic => reason: incomplete initialization of the simState - module.SimulationState{ - AppParams: make(simtypes.AppParams), - Cdc: cdc, - Rand: r, - }, "invalid memory address or nil pointer dereference"}, - } - - for _, tt := range tests { - require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) - } -} diff --git a/x/epoching/simulation/operations.go b/x/epoching/simulation/operations.go deleted file mode 100644 index 893364f88..000000000 --- a/x/epoching/simulation/operations.go +++ /dev/null @@ -1,371 +0,0 @@ -package simulation - -import ( - "fmt" - "math/rand" - - "github.com/babylonchain/babylon/x/epoching/keeper" - "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - simappparams "github.com/cosmos/cosmos-sdk/x/staking/simulation" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -// Simulation operation weights constants -const ( - OpWeightMsgWrappedDelegate = "op_weight_msg_wrapped_delegate" - OpWeightMsgWrappedUndelegate = "op_weight_msg_wrapped_undelegate" - OpWeightMsgWrappedBeginRedelegate = "op_weight_msg_wrapped_begin_redelegate" -) - -// WeightedOperations returns all the operations from the module with their respective weights -func WeightedOperations( - appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper, bk types.BankKeeper, stk types.StakingKeeper, k keeper.Keeper, -) simulation.WeightedOperations { - var ( - weightMsgWrappedDelegate int - weightMsgWrappedUndelegate int - weightMsgWrappedBeginRedelegate int - ) - - appParams.GetOrGenerate(OpWeightMsgWrappedDelegate, &weightMsgWrappedDelegate, nil, - func(_ *rand.Rand) { - weightMsgWrappedDelegate = simappparams.DefaultWeightMsgDelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs - }, - ) - - appParams.GetOrGenerate(OpWeightMsgWrappedUndelegate, &weightMsgWrappedUndelegate, nil, - func(_ *rand.Rand) { - weightMsgWrappedUndelegate = simappparams.DefaultWeightMsgUndelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs - }, - ) - - appParams.GetOrGenerate(OpWeightMsgWrappedBeginRedelegate, &weightMsgWrappedBeginRedelegate, nil, - func(_ *rand.Rand) { - weightMsgWrappedBeginRedelegate = simappparams.DefaultWeightMsgBeginRedelegate // TODO: use our own (and randomised) weight rather than those from the unwrapped msgs - }, - ) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgWrappedDelegate, - SimulateMsgWrappedDelegate(ak, bk, stk, k), - ), - simulation.NewWeightedOperation( - weightMsgWrappedUndelegate, - SimulateMsgWrappedUndelegate(ak, bk, stk, k), - ), - simulation.NewWeightedOperation( - weightMsgWrappedBeginRedelegate, - SimulateMsgWrappedBeginRedelegate(ak, bk, stk, k), - ), - } -} - -// SimulateMsgDelegate generates a MsgDelegate with random values -func SimulateMsgWrappedDelegate(ak types.AccountKeeper, bk types.BankKeeper, stk types.StakingKeeper, k keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - epoch := k.GetEpoch(ctx) - valSet := k.GetValidatorSet(ctx, epoch.EpochNumber) - if len(valSet) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "number of validators in this epoch equal zero"), nil, nil - } - - // pick a random validator - i := r.Intn(len(valSet)) - val, err := stk.GetValidator(ctx, valSet[i].Addr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to pick a validator"), nil, err - } - if val.InvalidExRate() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "validator's invalid exchange rate"), nil, nil - } - - // pick a random bondAmt - simAccount, _ := simtypes.RandomAcc(r, accs) - params, err := stk.GetParams(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to get params"), nil, err - } - denom := params.BondDenom - amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount - if !amount.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "balance is negative"), nil, nil - } - amount, err = simtypes.RandPositiveInt(r, amount) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to generate positive amount"), nil, err - } - bondAmt := sdk.NewCoin(denom, amount) - - // pick a random fee rate - var fees sdk.Coins - account := ak.GetAccount(ctx, simAccount.Address) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - coins, hasNeg := spendable.SafeSub(sdk.Coins{bondAmt}...) - if !hasNeg { - fees, err = simtypes.RandomFees(r, ctx, coins) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedDelegate, "unable to generate fees"), nil, err - } - } - - msg := stakingtypes.NewMsgDelegate(simAccount.Address.String(), val.GetOperator(), bondAmt) - wmsg := types.NewMsgWrappedDelegate(msg) - txCtx := simulation.OperationInput{ - App: app, - TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, - Cdc: nil, - Msg: wmsg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - ModuleName: types.ModuleName, - } - - return simulation.GenAndDeliverTx(txCtx, fees) - } -} - -// SimulateMsgUndelegate generates a MsgUndelegate with random values -func SimulateMsgWrappedUndelegate(ak types.AccountKeeper, bk types.BankKeeper, stk types.StakingKeeper, k keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - epoch := k.GetEpoch(ctx) - valSet := k.GetValidatorSet(ctx, epoch.EpochNumber) - if len(valSet) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "number of validators in this epoch equal zero"), nil, nil - } - - // pick a random validator - i := r.Intn(len(valSet)) - val, err := stk.GetValidator(ctx, valSet[i].Addr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to pick a validator"), nil, err - } - if val.InvalidExRate() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "validator's invalid exchange rate"), nil, nil - } - - // pick a random delegator from validator - valAddr, err := sdk.ValAddressFromBech32(val.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err - } - delegations, err := stk.GetValidatorDelegations(ctx, valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator delegations"), nil, err - } - if delegations == nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "keeper does not have any delegation entries"), nil, nil - } - delegation := delegations[r.Intn(len(delegations))] - delAddr, err := sdk.AccAddressFromBech32(delegation.GetDelegatorAddr()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get delegation address"), nil, err - } - hasMaxUnbEntries, err := stk.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "could not check whether delegator has max unbonding entries"), nil, err - } - if hasMaxUnbEntries { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "keeper reaches max unbonding delegation entries"), nil, nil - } - - // pick a random unbondAmt - totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt() - if !totalBond.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "total bond is negative"), nil, nil - } - unbondAmt, err := simtypes.RandPositiveInt(r, totalBond) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "invalid unbond amount"), nil, err - } - if unbondAmt.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unbond amount is zero"), nil, nil - } - - bondDenom, err := stk.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get bond denomination"), nil, err - } - msg := stakingtypes.NewMsgUndelegate( - delAddr.String(), valAddr.String(), sdk.NewCoin(bondDenom, unbondAmt), - ) - wmsg := types.NewMsgWrappedUndelegate(msg) - - // need to retrieve the simulation account associated with delegation to retrieve PrivKey - var simAccount simtypes.Account - - for _, simAcc := range accs { - if simAcc.Address.Equals(delAddr) { - simAccount = simAcc - break - } - } - // if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error - if simAccount.PrivKey == nil { - return simtypes.NoOpMsg(types.ModuleName, wmsg.Type(), "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) - } - - account := ak.GetAccount(ctx, delAddr) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, - Cdc: nil, - Msg: wmsg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// SimulateMsgWrappedBeginRedelegate generates a MsgBeginRedelegate with random values -func SimulateMsgWrappedBeginRedelegate(ak types.AccountKeeper, bk types.BankKeeper, stk types.StakingKeeper, k keeper.Keeper) simtypes.Operation { - return func( - r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - epoch := k.GetEpoch(ctx) - valSet := k.GetValidatorSet(ctx, epoch.EpochNumber) - if len(valSet) == 0 { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "number of validators in this epoch equal zero"), nil, nil - } - - // pick a random source validator - i := r.Intn(len(valSet)) - srcVal, err := stk.GetValidator(ctx, valSet[i].Addr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, err - } - - srcAddr, err := sdk.ValAddressFromBech32(srcVal.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err - } - - delegations, err := stk.GetValidatorDelegations(ctx, srcAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to find validator delegations"), nil, err - } - if delegations == nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "keeper does have any delegation entries"), nil, nil - } - - // pick a random delegator from src validator - delegation := delegations[r.Intn(len(delegations))] - delAddr, err := sdk.AccAddressFromBech32(delegation.GetDelegatorAddr()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get delegation address"), nil, err - } - - hasRedel, err := stk.HasReceivingRedelegation(ctx, delAddr, srcAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "could not check whether validator has redelegations"), nil, err - } - if hasRedel { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "receiving redelegation is not allowed"), nil, nil - } - - // pick a random destination validator - i = r.Intn(len(valSet)) - destVal, err := stk.GetValidator(ctx, valSet[i].Addr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to pick a validator"), nil, err - } - destAddr, err := sdk.ValAddressFromBech32(destVal.GetOperator()) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to get validator address"), nil, err - } - hasMaxRedelEntries, err := stk.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedUndelegate, "unable to check whether delegator has max redelegations"), nil, err - } - if srcAddr.Equals(destAddr) || destVal.InvalidExRate() || hasMaxRedelEntries { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "checks failed"), nil, nil - } - - // pick a random redAmt - totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt() - if !totalBond.IsPositive() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "total bond is negative"), nil, nil - } - redAmt, err := simtypes.RandPositiveInt(r, totalBond) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "unable to generate positive amount"), nil, err - } - if redAmt.IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "amount is zero"), nil, nil - } - - // check if the shares truncate to zero - shares, err := srcVal.SharesFromTokens(redAmt) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "invalid shares"), nil, err - } - - if srcVal.TokensFromShares(shares).TruncateInt().IsZero() { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "shares truncate to zero"), nil, nil // skip - } - - // need to retrieve the simulation account associated with delegation to retrieve PrivKey - var simAccount simtypes.Account - - for _, simAcc := range accs { - if simAcc.Address.Equals(delAddr) { - simAccount = simAcc - break - } - } - - // if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error - if simAccount.PrivKey == nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) - } - - account := ak.GetAccount(ctx, delAddr) - spendable := bk.SpendableCoins(ctx, account.GetAddress()) - - bondDenom, err := stk.BondDenom(ctx) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgWrappedBeginRedelegate, "could not get bond denomination"), nil, err - } - msg := stakingtypes.NewMsgBeginRedelegate( - delAddr.String(), srcAddr.String(), destAddr.String(), - sdk.NewCoin(bondDenom, redAmt), - ) - wmsg := types.NewMsgWrappedBeginRedelegate(msg) - - txCtx := simulation.OperationInput{ - R: r, - App: app, - TxGen: moduletestutil.MakeTestEncodingConfig().TxConfig, - Cdc: nil, - Msg: wmsg, - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: spendable, - } - - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} diff --git a/x/epoching/simulation/operations_test.go b/x/epoching/simulation/operations_test.go deleted file mode 100644 index 5c6452f9a..000000000 --- a/x/epoching/simulation/operations_test.go +++ /dev/null @@ -1 +0,0 @@ -package simulation_test diff --git a/x/epoching/simulation/simap.go b/x/epoching/simulation/simap.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/epoching/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/epoching/types/msg.go b/x/epoching/types/msg.go index 53eaa16d9..97f73d92d 100644 --- a/x/epoching/types/msg.go +++ b/x/epoching/types/msg.go @@ -1,19 +1,10 @@ package types import ( - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// staking message types -const ( - TypeMsgWrappedDelegate = "wrapped_delegate" - TypeMsgWrappedUndelegate = "wrapped_begin_unbonding" - TypeMsgWrappedBeginRedelegate = "wrapped_begin_redelegate" - TypeMsgWrappedCancelUnbondingDelegation = "wrapped_cancel_unbonding_delegation" -) - // ensure that these message types implement the sdk.Msg interface var ( _ sdk.Msg = &MsgWrappedDelegate{} @@ -30,20 +21,6 @@ func NewMsgWrappedDelegate(msg *stakingtypes.MsgDelegate) *MsgWrappedDelegate { } } -// Route implements the sdk.Msg interface. -func (msg MsgWrappedDelegate) Route() string { return RouterKey } - -// Type implements the sdk.Msg interface. -func (msg MsgWrappedDelegate) Type() string { return TypeMsgWrappedDelegate } - -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgWrappedDelegate) ValidateBasic() error { - if msg.Msg == nil { - return ErrNoWrappedMsg - } - return nil -} - // NewMsgWrappedUndelegate creates a new MsgWrappedUndelegate instance. func NewMsgWrappedUndelegate(msg *stakingtypes.MsgUndelegate) *MsgWrappedUndelegate { return &MsgWrappedUndelegate{ @@ -51,20 +28,6 @@ func NewMsgWrappedUndelegate(msg *stakingtypes.MsgUndelegate) *MsgWrappedUndeleg } } -// Route implements the sdk.Msg interface. -func (msg MsgWrappedUndelegate) Route() string { return RouterKey } - -// Type implements the sdk.Msg interface. -func (msg MsgWrappedUndelegate) Type() string { return TypeMsgWrappedUndelegate } - -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgWrappedUndelegate) ValidateBasic() error { - if msg.Msg == nil { - return ErrNoWrappedMsg - } - return nil -} - // NewMsgWrappedBeginRedelegate creates a new MsgWrappedBeginRedelegate instance. func NewMsgWrappedBeginRedelegate(msg *stakingtypes.MsgBeginRedelegate) *MsgWrappedBeginRedelegate { return &MsgWrappedBeginRedelegate{ @@ -72,58 +35,9 @@ func NewMsgWrappedBeginRedelegate(msg *stakingtypes.MsgBeginRedelegate) *MsgWrap } } -// Route implements the sdk.Msg interface. -func (msg MsgWrappedBeginRedelegate) Route() string { return RouterKey } - -// Type implements the sdk.Msg interface. -func (msg MsgWrappedBeginRedelegate) Type() string { return TypeMsgWrappedBeginRedelegate } - -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgWrappedBeginRedelegate) ValidateBasic() error { - if msg.Msg == nil { - return ErrNoWrappedMsg - } - return nil -} - // NewMsgWrappedCancelUnbondingDelegation creates a new MsgWrappedCancelUnbondingDelegation instance. func NewMsgWrappedCancelUnbondingDelegation(msg *stakingtypes.MsgCancelUnbondingDelegation) *MsgWrappedCancelUnbondingDelegation { return &MsgWrappedCancelUnbondingDelegation{ Msg: msg, } } - -// Route implements the sdk.Msg interface. -func (msg MsgWrappedCancelUnbondingDelegation) Route() string { return RouterKey } - -// Type implements the sdk.Msg interface. -func (msg MsgWrappedCancelUnbondingDelegation) Type() string { - return TypeMsgWrappedCancelUnbondingDelegation -} - -// ValidateBasic implements the sdk.Msg interface. -func (msg MsgWrappedCancelUnbondingDelegation) ValidateBasic() error { - if msg.Msg == nil { - return ErrNoWrappedMsg - } - return nil -} - -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - - if err := m.Params.Validate(); err != nil { - return err - } - - return nil -} diff --git a/x/epoching/types/msg_test.go b/x/epoching/types/msg_test.go index 7385ed9dc..f5822d564 100644 --- a/x/epoching/types/msg_test.go +++ b/x/epoching/types/msg_test.go @@ -24,10 +24,8 @@ import ( var ( pk1 = ed25519.GenPrivKey().PubKey() pk2 = ed25519.GenPrivKey().PubKey() - pk3 = ed25519.GenPrivKey().PubKey() valAddr1 = sdk.ValAddress(pk1.Address()) valAddr2 = sdk.ValAddress(pk2.Address()) - valAddr3 = sdk.ValAddress(pk3.Address()) coinPos = sdk.NewInt64Coin(appparams.DefaultBondDenom, 1000) ) @@ -89,121 +87,3 @@ func TestMsgDecode(t *testing.T) { require.Equal(t, qmsg.MsgId, qmsg2.MsgId) require.True(t, msgcreateval1.Pubkey.Equal(msgcreateval2.Pubkey)) } - -// test ValidateBasic for MsgWrappedDelegate -func TestMsgWrappedDelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - bond sdk.Coin - expectPass bool - }{ - {"basic good", sdk.AccAddress(valAddr1), valAddr2, coinPos, true}, - {"no wrapped msg", nil, nil, coinPos, false}, - } - - for _, tc := range tests { - var msg *types.MsgWrappedDelegate - if tc.delegatorAddr == nil { - msg = types.NewMsgWrappedDelegate(nil) - } else { - msgUnwrapped := stakingtypes.NewMsgDelegate(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.bond) - msg = types.NewMsgWrappedDelegate(msgUnwrapped) - } - if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.Error(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgWrappedBeginRedelegate -func TestMsgWrappedBeginRedelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorSrcAddr sdk.ValAddress - validatorDstAddr sdk.ValAddress - amount sdk.Coin - expectPass bool - }{ - {"regular", sdk.AccAddress(valAddr1), valAddr2, valAddr3, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), true}, - {"no wrapped msg", nil, nil, nil, coinPos, false}, - } - - for _, tc := range tests { - var msg *types.MsgWrappedBeginRedelegate - if tc.delegatorAddr == nil { - msg = types.NewMsgWrappedBeginRedelegate(nil) - } else { - msgUnwrapped := stakingtypes.NewMsgBeginRedelegate(tc.delegatorAddr.String(), tc.validatorSrcAddr.String(), tc.validatorDstAddr.String(), tc.amount) - msg = types.NewMsgWrappedBeginRedelegate(msgUnwrapped) - } - if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.Error(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgWrappedUndelegate -func TestMsgWrappedUndelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - amount sdk.Coin - expectPass bool - }{ - {"regular", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), true}, - {"no wrapped msg", nil, nil, coinPos, false}, - } - - for _, tc := range tests { - var msg *types.MsgWrappedUndelegate - if tc.delegatorAddr == nil { - msg = types.NewMsgWrappedUndelegate(nil) - } else { - msgUnwrapped := stakingtypes.NewMsgUndelegate(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.amount) - msg = types.NewMsgWrappedUndelegate(msgUnwrapped) - } - if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.Error(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgWrappedCancelUnbondingDelegation -func TestMsgWrappedCancelUnbondingDelegation(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - amount sdk.Coin - creationHeight int64 - expectPass bool - }{ - {"regular", sdk.AccAddress(valAddr1), valAddr2, sdk.NewInt64Coin(appparams.DefaultBondDenom, 1), 10, true}, - {"no wrapped msg", nil, nil, coinPos, 0, false}, - } - - for _, tc := range tests { - var msg *types.MsgWrappedCancelUnbondingDelegation - if tc.delegatorAddr == nil { - msg = types.NewMsgWrappedCancelUnbondingDelegation(nil) - } else { - msgUnwrapped := stakingtypes.NewMsgCancelUnbondingDelegation(tc.delegatorAddr.String(), tc.validatorAddr.String(), tc.creationHeight, tc.amount) - msg = types.NewMsgWrappedCancelUnbondingDelegation(msgUnwrapped) - } - if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.Error(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 25831d066..3f6495da8 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -31,6 +31,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.SetParams(ctx, req.Params); err != nil { @@ -71,12 +74,18 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal } // ensure the BTC validator has voting power at this height + if req.ValBtcPk == nil { + return nil, types.ErrInvalidFinalitySig.Wrap("empty validator BTC PK") + } valPK := req.ValBtcPk if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { return nil, types.ErrInvalidFinalitySig.Wrapf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) } // ensure the BTC validator has not casted the same vote yet + if req.FinalitySig == nil { + return nil, types.ErrInvalidFinalitySig.Wrap("empty finality signature") + } existingSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) if err == nil && existingSig.Equals(req.FinalitySig) { return nil, types.ErrDuplicatedFinalitySig @@ -107,13 +116,13 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // construct evidence evidence := &types.Evidence{ - ValBtcPk: req.ValBtcPk, - BlockHeight: req.BlockHeight, - PubRand: pubRand, - CanonicalAppHash: indexedBlock.AppHash, - CanonicalFinalitySig: nil, - ForkAppHash: req.BlockAppHash, - ForkFinalitySig: req.FinalitySig, + ValBtcPk: req.ValBtcPk, + BlockHeight: req.BlockHeight, + PubRand: pubRand, + CanonicalAppHash: indexedBlock.AppHash, + CanonicalFinalitySig: nil, + ForkAppHash: req.BlockAppHash, + ForkFinalitySig: req.FinalitySig, } // if this BTC validator has also signed canonical block, slash it @@ -173,6 +182,9 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi } // ensure the BTC validator is registered + if req.ValBtcPk == nil { + return nil, types.ErrInvalidPubRand.Wrap("empty validator public key") + } valBTCPKBytes := req.ValBtcPk.MustMarshal() if !ms.BTCStakingKeeper.HasBTCValidator(ctx, valBTCPKBytes) { return nil, bstypes.ErrBTCValNotFound.Wrapf("the validator with BTC PK %v is not registered", valBTCPKBytes) @@ -194,6 +206,11 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi return nil, types.ErrInvalidPubRand.Wrapf("the start height (%d) has overlap with the height of the highest public randomness (%d)", req.StartHeight, height) } + // verify signature over the list + if err := req.VerifySig(); err != nil { + return nil, types.ErrInvalidPubRand.Wrapf("invalid signature over the public randomness list: %v", err) + } + // all good, commit the given public randomness list ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil diff --git a/x/finality/module_simulation.go b/x/finality/module_simulation.go deleted file mode 100644 index 4db5a78c0..000000000 --- a/x/finality/module_simulation.go +++ /dev/null @@ -1,54 +0,0 @@ -package finality - -import ( - "math/rand" - - "github.com/babylonchain/babylon/testutil/sample" - finalitysimulation "github.com/babylonchain/babylon/x/finality/simulation" - "github.com/babylonchain/babylon/x/finality/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = finalitysimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace - _ = rand.Rand{} -) - -// GenerateGenesisState creates a randomized GenState of the module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - finalityGenesis := types.GenesisState{ - Params: types.DefaultParams(), - } - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&finalityGenesis) -} - -// RegisterStoreDecoder registers a decoder. -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} - -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{} -} diff --git a/x/finality/simulation/helpers.go b/x/finality/simulation/helpers.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/finality/simulation/helpers.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index e8f34cd02..f9ce2bfe9 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -3,7 +3,6 @@ package types import ( "fmt" - errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/crypto/eots" bbn "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/btcec/v2" @@ -18,30 +17,11 @@ var ( _ sdk.Msg = &MsgCommitPubRandList{} ) -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - - if err := m.Params.Validate(); err != nil { - return err - } - - return nil -} - func NewMsgAddFinalitySig(signer string, sk *btcec.PrivateKey, sr *eots.PrivateRand, blockHeight uint64, blockHash []byte) (*MsgAddFinalitySig, error) { msg := &MsgAddFinalitySig{ - Signer: signer, - ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), - BlockHeight: blockHeight, + Signer: signer, + ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + BlockHeight: blockHeight, BlockAppHash: blockHash, } msgToSign := msg.MsgToSign() @@ -54,30 +34,6 @@ func NewMsgAddFinalitySig(signer string, sk *btcec.PrivateKey, sr *eots.PrivateR return msg, nil } -// GetSigners returns the expected signers for a MsgAddFinalitySig message. -func (m *MsgAddFinalitySig) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgAddFinalitySig) ValidateBasic() error { - if m.ValBtcPk == nil { - return fmt.Errorf("empty validator BTC PK") - } - if len(m.BlockAppHash) != tmhash.Size { - return fmt.Errorf("malformed block hash") - } - if m.FinalitySig == nil { - return fmt.Errorf("empty finality signature") - } - - return nil -} - func (m *MsgAddFinalitySig) MsgToSign() []byte { return msgToSignForVote(m.BlockHeight, m.BlockAppHash) } @@ -92,29 +48,6 @@ func (m *MsgAddFinalitySig) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { return eots.Verify(pk, pubRand.ToFieldVal(), msgToSign, m.FinalitySig.ToModNScalar()) } -// GetSigners returns the expected signers for a MsgCommitPubRandList message. -func (m *MsgCommitPubRandList) GetSigners() []sdk.AccAddress { - signer, err := sdk.AccAddressFromBech32(m.Signer) - if err != nil { - panic(err) - } - return []sdk.AccAddress{signer} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgCommitPubRandList) ValidateBasic() error { - if m.ValBtcPk == nil { - return fmt.Errorf("empty validator BTC PK") - } - if len(m.PubRandList) == 0 { - return fmt.Errorf("empty list of public randomness") - } - if m.Sig == nil { - return fmt.Errorf("empty signature") - } - return m.verifySig() -} - // HashToSign returns a 32-byte hash of (start_height || pub_rand_list) // The signature in MsgCommitPubRandList will be on this hash func (m *MsgCommitPubRandList) HashToSign() ([]byte, error) { @@ -130,7 +63,7 @@ func (m *MsgCommitPubRandList) HashToSign() ([]byte, error) { return hasher.Sum(nil), nil } -func (m *MsgCommitPubRandList) verifySig() error { +func (m *MsgCommitPubRandList) VerifySig() error { msgHash, err := m.HashToSign() if err != nil { return err @@ -139,6 +72,9 @@ func (m *MsgCommitPubRandList) verifySig() error { if err != nil { return err } + if m.Sig == nil { + return fmt.Errorf("empty signature") + } schnorrSig, err := m.Sig.ToBTCSig() if err != nil { return err diff --git a/x/finality/types/msg_test.go b/x/finality/types/msg_test.go index 27abb3624..9dafd9d14 100644 --- a/x/finality/types/msg_test.go +++ b/x/finality/types/msg_test.go @@ -28,9 +28,6 @@ func FuzzMsgAddFinalitySig(f *testing.F) { msg, err := types.NewMsgAddFinalitySig(signer, sk, sr, blockHeight, blockHash) require.NoError(t, err) - // basic sanity checks - err = msg.ValidateBasic() - require.NoError(t, err) // verify msg's EOTS sig against the given public randomness err = msg.VerifyEOTSSig(bbn.NewSchnorrPubRandFromFieldVal(pr)) require.NoError(t, err) @@ -52,7 +49,7 @@ func FuzzMsgCommitPubRandList(f *testing.F) { require.NoError(t, err) // sanity checks, including verifying signature - err = msg.ValidateBasic() + err = msg.VerifySig() require.NoError(t, err) }) } diff --git a/x/incentive/keeper/msg_server.go b/x/incentive/keeper/msg_server.go index 20f77ff23..3d9ff5399 100644 --- a/x/incentive/keeper/msg_server.go +++ b/x/incentive/keeper/msg_server.go @@ -28,6 +28,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.SetParams(ctx, req.Params); err != nil { diff --git a/x/incentive/module_simulation.go b/x/incentive/module_simulation.go deleted file mode 100644 index 1b5b69064..000000000 --- a/x/incentive/module_simulation.go +++ /dev/null @@ -1,54 +0,0 @@ -package incentive - -import ( - "math/rand" - - "github.com/babylonchain/babylon/testutil/sample" - incentivesimulation "github.com/babylonchain/babylon/x/incentive/simulation" - "github.com/babylonchain/babylon/x/incentive/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = incentivesimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace - _ = rand.Rand{} -) - -// GenerateGenesisState creates a randomized GenState of the module. -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - incentiveGenesis := types.GenesisState{ - Params: types.DefaultParams(), - } - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&incentiveGenesis) -} - -// RegisterStoreDecoder registers a decoder. -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} - -// ProposalContents doesn't return any content functions for governance proposals. -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} - -// ProposalMsgs returns msgs used for governance proposals for simulations. -func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { - return []simtypes.WeightedProposalMsg{} -} diff --git a/x/incentive/simulation/helpers.go b/x/incentive/simulation/helpers.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/incentive/simulation/helpers.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/incentive/types/msg.go b/x/incentive/types/msg.go index d6b485c32..a0c4caffa 100644 --- a/x/incentive/types/msg.go +++ b/x/incentive/types/msg.go @@ -1,7 +1,6 @@ package types import ( - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -10,40 +9,3 @@ var ( _ sdk.Msg = &MsgWithdrawReward{} _ sdk.Msg = &MsgUpdateParams{} ) - -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgWithdrawReward) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Address) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgWithdrawReward) ValidateBasic() error { - if _, err := NewStakeHolderTypeFromString(m.Type); err != nil { - return err - } - if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { - return err - } - - return nil -} - -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - - if err := m.Params.Validate(); err != nil { - return err - } - - return nil -} diff --git a/x/monitor/module_simulation.go b/x/monitor/module_simulation.go deleted file mode 100644 index 3db534441..000000000 --- a/x/monitor/module_simulation.go +++ /dev/null @@ -1,42 +0,0 @@ -package monitor - -import ( - monitorsimulation "github.com/babylonchain/babylon/x/monitor/simulation" - "github.com/babylonchain/babylon/x/monitor/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = monitorsimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - monitorgenesis := types.GenesisState{} - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&monitorgenesis) -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} diff --git a/x/monitor/simulation/simap.go b/x/monitor/simulation/simap.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/monitor/simulation/simap.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/zoneconcierge/keeper/msg_server.go b/x/zoneconcierge/keeper/msg_server.go index 70709886b..dee48dd04 100644 --- a/x/zoneconcierge/keeper/msg_server.go +++ b/x/zoneconcierge/keeper/msg_server.go @@ -26,6 +26,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara if ms.authority != req.Authority { return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, req.Authority) } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } ctx := sdk.UnwrapSDKContext(goCtx) if err := ms.SetParams(ctx, req.Params); err != nil { diff --git a/x/zoneconcierge/module_simulation.go b/x/zoneconcierge/module_simulation.go deleted file mode 100644 index 795437f50..000000000 --- a/x/zoneconcierge/module_simulation.go +++ /dev/null @@ -1,47 +0,0 @@ -package zoneconcierge - -import ( - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - - "github.com/babylonchain/babylon/testutil/sample" - zoneconciergesimulation "github.com/babylonchain/babylon/x/zoneconcierge/simulation" - "github.com/babylonchain/babylon/x/zoneconcierge/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// avoid unused import issue -var ( - _ = sample.AccAddress - _ = zoneconciergesimulation.FindAccount - _ = simulation.MsgEntryKind - _ = baseapp.Paramspace -) - -// GenerateGenesisState creates a randomized GenState of the module -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - accs := make([]string, len(simState.Accounts)) - for i, acc := range simState.Accounts { - accs[i] = acc.Address.String() - } - zoneconciergeGenesis := types.GenesisState{ - PortId: types.PortID, - } - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&zoneconciergeGenesis) -} - -// ProposalContents doesn't return any content functions for governance proposals -func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { - return nil -} - -// RegisterStoreDecoder registers a decoder -func (am AppModule) RegisterStoreDecoder(_ simtypes.StoreDecoderRegistry) {} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - operations := make([]simtypes.WeightedOperation, 0) - - return operations -} diff --git a/x/zoneconcierge/simulation/helpers.go b/x/zoneconcierge/simulation/helpers.go deleted file mode 100644 index 92c437c0d..000000000 --- a/x/zoneconcierge/simulation/helpers.go +++ /dev/null @@ -1,15 +0,0 @@ -package simulation - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// FindAccount find a specific address from an account list -func FindAccount(accs []simtypes.Account, address string) (simtypes.Account, bool) { - creator, err := sdk.AccAddressFromBech32(address) - if err != nil { - panic(err) - } - return simtypes.FindAccount(accs, creator) -} diff --git a/x/zoneconcierge/types/msg.go b/x/zoneconcierge/types/msg.go index fadaf538a..782e3dcc1 100644 --- a/x/zoneconcierge/types/msg.go +++ b/x/zoneconcierge/types/msg.go @@ -1,7 +1,6 @@ package types import ( - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -9,22 +8,3 @@ import ( var ( _ sdk.Msg = &MsgUpdateParams{} ) - -// GetSigners returns the expected signers for a MsgUpdateParams message. -func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { - addr, _ := sdk.AccAddressFromBech32(m.Authority) - return []sdk.AccAddress{addr} -} - -// ValidateBasic does a sanity check on the provided data. -func (m *MsgUpdateParams) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { - return errorsmod.Wrap(err, "invalid authority address") - } - - if err := m.Params.Validate(); err != nil { - return err - } - - return nil -} From 697323c889990484b597432732aac77ae4112e3b Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 12 Dec 2023 14:28:31 +1100 Subject: [PATCH 126/202] btcstaking: disallow empty moniker (#145) --- testutil/datagen/btcstaking.go | 2 +- x/btcstaking/types/msg.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 2d5a80bb5..e2d59030c 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -48,7 +48,7 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe // commission commission := sdkmath.LegacyNewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] // description - description := stakingtypes.Description{} + description := stakingtypes.Description{Moniker: GenRandomHexStr(r, 10)} // key pairs btcPK := btcSK.PubKey() bip340PK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index 0ff123ff0..a0ce94fd7 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -26,6 +26,9 @@ func (m *MsgCreateBTCValidator) ValidateBasic() error { if m.Description == nil { return fmt.Errorf("empty description") } + if len(m.Description.Moniker) == 0 { + return fmt.Errorf("empty moniker") + } if _, err := m.Description.EnsureLength(); err != nil { return err } From 3504554e618b0ed963a24c8442034b821360fe14 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 12 Dec 2023 20:27:59 +1100 Subject: [PATCH 127/202] zoneconcierge: fix btc timestamp format for Cosmos SDK v0.50 (#146) --- app/app.go | 17 +- .../zoneconcierge/v1/zoneconcierge.proto | 16 +- testutil/keeper/zoneconcierge.go | 3 +- x/zoneconcierge/genesis_test.go | 2 +- x/zoneconcierge/keeper/grpc_query_test.go | 14 +- .../keeper/ibc_packet_btc_timestamp.go | 28 +- x/zoneconcierge/keeper/keeper.go | 4 +- x/zoneconcierge/keeper/params_test.go | 2 +- .../keeper/proof_block_in_epoch.go | 12 - .../keeper/proof_cz_header_in_epoch.go | 68 +++++ .../keeper/proof_epoch_sealed_test.go | 2 +- .../keeper/proof_finalized_chain_info.go | 10 +- x/zoneconcierge/keeper/proof_tx_in_block.go | 34 --- x/zoneconcierge/types/expected_keepers.go | 2 - x/zoneconcierge/types/mocked_keepers.go | 16 -- x/zoneconcierge/types/zoneconcierge.pb.go | 240 +++++++----------- 16 files changed, 186 insertions(+), 284 deletions(-) delete mode 100644 x/zoneconcierge/keeper/proof_block_in_epoch.go create mode 100644 x/zoneconcierge/keeper/proof_cz_header_in_epoch.go delete mode 100644 x/zoneconcierge/keeper/proof_tx_in_block.go diff --git a/app/app.go b/app/app.go index b2d8f74c2..ea5318c9f 100644 --- a/app/app.go +++ b/app/app.go @@ -1,21 +1,22 @@ package app import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" "cosmossdk.io/x/circuit" circuitkeeper "cosmossdk.io/x/circuit/keeper" - "encoding/json" - "fmt" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/types/msgservice" "github.com/cosmos/gogoproto/proto" - "io" - "os" - "path/filepath" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" @@ -595,11 +596,6 @@ func NewBabylonApp( authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - // create Tendermint client - cmtClient, err := client.NewClientFromNode(privSigner.ClientCtx.NodeURI) // create a Tendermint client for ZoneConcierge - if err != nil { - panic(fmt.Errorf("couldn't get client from nodeURI %s: %w", privSigner.ClientCtx.NodeURI, err)) - } // create querier for KVStore storeQuerier, ok := app.CommitMultiStore().(storetypes.Queryable) if !ok { @@ -626,7 +622,6 @@ func NewBabylonApp( &checkpointingKeeper, &btcCheckpointKeeper, epochingKeeper, - cmtClient, storeQuerier, scopedZoneConciergeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto index d216ec3be..45093b1b4 100644 --- a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto +++ b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto @@ -3,7 +3,6 @@ package babylon.zoneconcierge.v1; import "gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; -import "tendermint/types/types.proto"; import "tendermint/crypto/proof.proto"; import "babylon/btccheckpoint/v1/btccheckpoint.proto"; import "babylon/checkpointing/v1/bls_key.proto"; @@ -122,21 +121,18 @@ message ProofFinalizedChainInfo { The following fields include proofs that attest the chain info is BTC-finalised */ - // proof_tx_in_block is the proof that tx that carries the header is included - // in a certain Babylon block - tendermint.types.TxProof proof_tx_in_block = 4; - // proof_header_in_epoch is the proof that the Babylon header is in a certain - // epoch - tendermint.crypto.Proof proof_header_in_epoch = 5; + // proof_cz_header_in_epoch is the proof that the CZ header is timestamped + // within a certain epoch + tendermint.crypto.ProofOps proof_cz_header_in_epoch = 1; // proof_epoch_sealed is the proof that the epoch is sealed - babylon.zoneconcierge.v1.ProofEpochSealed proof_epoch_sealed = 6; + babylon.zoneconcierge.v1.ProofEpochSealed proof_epoch_sealed = 2; // proof_epoch_submitted is the proof that the epoch's checkpoint is included // in BTC ledger It is the two TransactionInfo in the best (i.e., earliest) // checkpoint submission - repeated babylon.btccheckpoint.v1.TransactionInfo proof_epoch_submitted = 7; + repeated babylon.btccheckpoint.v1.TransactionInfo proof_epoch_submitted = 3; } // Btc light client chain segment grown during last finalized epoch message BTCChainSegment { - repeated babylon.btclightclient.v1.BTCHeaderInfo btc_headers = 2; + repeated babylon.btclightclient.v1.BTCHeaderInfo btc_headers = 1; } diff --git a/testutil/keeper/zoneconcierge.go b/testutil/keeper/zoneconcierge.go index a575f2a2c..5e26d6d68 100644 --- a/testutil/keeper/zoneconcierge.go +++ b/testutil/keeper/zoneconcierge.go @@ -69,7 +69,7 @@ func (zoneconciergeStoreQuerier) Query(req *storetypes.RequestQuery) (*storetype }, nil } -func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper, cmtClient types.CometClient) (*keeper.Keeper, sdk.Context) { +func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper) (*keeper.Keeper, sdk.Context) { logger := log.NewTestLogger(t) storeKey := storetypes.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -96,7 +96,6 @@ func ZoneConciergeKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, c checkpointingKeeper, btccKeeper, epochingKeeper, - cmtClient, zoneconciergeStoreQuerier{}, capabilityKeeper.ScopeToModule("ZoneconciergeScopedKeeper"), authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/x/zoneconcierge/genesis_test.go b/x/zoneconcierge/genesis_test.go index 04e049233..59ae3107d 100644 --- a/x/zoneconcierge/genesis_test.go +++ b/x/zoneconcierge/genesis_test.go @@ -16,7 +16,7 @@ func TestGenesis(t *testing.T) { Params: types.Params{IbcPacketTimeoutSeconds: 100}, } - k, ctx := keepertest.ZoneConciergeKeeper(t, nil, nil, nil, nil, nil) + k, ctx := keepertest.ZoneConciergeKeeper(t, nil, nil, nil, nil) zoneconcierge.InitGenesis(ctx, *k, genesisState) got := zoneconcierge.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/zoneconcierge/keeper/grpc_query_test.go b/x/zoneconcierge/keeper/grpc_query_test.go index 9c7b55b0f..7024c1e6c 100644 --- a/x/zoneconcierge/keeper/grpc_query_test.go +++ b/x/zoneconcierge/keeper/grpc_query_test.go @@ -6,9 +6,6 @@ import ( "github.com/babylonchain/babylon/app" btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" - tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/types/query" ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" "github.com/golang/mock/gomock" @@ -392,21 +389,12 @@ func FuzzFinalizedChainInfo(f *testing.F) { epochingKeeper := zctypes.NewMockEpochingKeeper(ctrl) epochingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(epoch).AnyTimes() epochingKeeper.EXPECT().GetHistoricalEpoch(gomock.Any(), gomock.Eq(epoch.EpochNumber)).Return(epoch, nil).AnyTimes() - epochingKeeper.EXPECT().ProveAppHashInEpoch(gomock.Any(), gomock.Any(), gomock.Eq(epoch.EpochNumber)).Return(&tmcrypto.Proof{}, nil).AnyTimes() // mock btclc keeper btclcKeeper := zctypes.NewMockBTCLightClientKeeper(ctrl) mockBTCHeaderInfo := datagen.GenRandomBTCHeaderInfo(r) btclcKeeper.EXPECT().GetMainChainFrom(gomock.Any(), gomock.Any()).Return([]*btclightclienttypes.BTCHeaderInfo{mockBTCHeaderInfo}).AnyTimes() - // mock Comet client - // TODO: integration tests with Comet - cmtClient := zctypes.NewMockCometClient(ctrl) - resTx := &tmrpctypes.ResultTx{ - Proof: tmtypes.TxProof{}, - } - cmtClient.EXPECT().Tx(gomock.Any(), gomock.Any(), true).Return(resTx, nil).AnyTimes() - - zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, btclcKeeper, checkpointingKeeper, btccKeeper, epochingKeeper, cmtClient) + zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, btclcKeeper, checkpointingKeeper, btccKeeper, epochingKeeper) hooks := zcKeeper.Hooks() var ( diff --git a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go index 249b367e4..494810b49 100644 --- a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go +++ b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go @@ -36,11 +36,11 @@ func (k Keeper) getChainID(ctx context.Context, channel channeltypes.IdentifiedC } // cast clientState to comet clientState // TODO: support for chains other than Cosmos zones - cmtClient, ok := clientState.(*ibctmtypes.ClientState) + cmtClientState, ok := clientState.(*ibctmtypes.ClientState) if !ok { - return "", fmt.Errorf("client must be a Comet client, expected: %T, got: %T", &ibctmtypes.ClientState{}, cmtClient) + return "", fmt.Errorf("client must be a Comet client, expected: %T, got: %T", &ibctmtypes.ClientState{}, cmtClientState) } - return cmtClient.ChainId, nil + return cmtClientState.ChainId, nil } // getFinalizedInfo returns metadata and proofs that are identical to all BTC timestamps in the same epoch @@ -130,31 +130,23 @@ func (k Keeper) createBTCTimestamp(ctx context.Context, chainID string, channel RawCheckpoint: finalizedInfo.RawCheckpoint, BtcSubmissionKey: finalizedInfo.BTCSubmissionKey, Proof: &types.ProofFinalizedChainInfo{ - ProofTxInBlock: nil, - ProofHeaderInEpoch: nil, - ProofEpochSealed: finalizedInfo.ProofEpochSealed, - ProofEpochSubmitted: finalizedInfo.ProofEpochSubmitted, + ProofCzHeaderInEpoch: nil, + ProofEpochSealed: finalizedInfo.ProofEpochSealed, + ProofEpochSubmitted: finalizedInfo.ProofEpochSubmitted, }, } // if there is a CZ header checkpointed in this finalised epoch, // add this CZ header and corresponding proofs to the BTC timestamp if finalizedChainInfo.LatestHeader.BabylonEpoch == epochNum { - // get proofTxInBlock - proofTxInBlock, err := k.ProveTxInBlock(ctx, finalizedChainInfo.LatestHeader.BabylonTxHash) + // get proofCZHeaderInEpoch + proofCZHeaderInEpoch, err := k.ProveCZHeaderInEpoch(ctx, finalizedChainInfo.LatestHeader, finalizedInfo.EpochInfo) if err != nil { - return nil, fmt.Errorf("failed to generate proofTxInBlock for chain %s: %w", chainID, err) - } - - // get proofHeaderInEpoch - proofHeaderInEpoch, err := k.ProveHeaderInEpoch(ctx, finalizedChainInfo.LatestHeader.BabylonHeaderHeight, finalizedInfo.EpochInfo) - if err != nil { - return nil, fmt.Errorf("failed to generate proofHeaderInEpoch for chain %s: %w", chainID, err) + return nil, fmt.Errorf("failed to generate proofCZHeaderInEpoch for chain %s: %w", chainID, err) } btcTimestamp.Header = finalizedChainInfo.LatestHeader - btcTimestamp.Proof.ProofTxInBlock = proofTxInBlock - btcTimestamp.Proof.ProofHeaderInEpoch = proofHeaderInEpoch + btcTimestamp.Proof.ProofCzHeaderInEpoch = proofCZHeaderInEpoch } return btcTimestamp, nil diff --git a/x/zoneconcierge/keeper/keeper.go b/x/zoneconcierge/keeper/keeper.go index b676178c8..b34290f6e 100644 --- a/x/zoneconcierge/keeper/keeper.go +++ b/x/zoneconcierge/keeper/keeper.go @@ -2,6 +2,7 @@ package keeper import ( "context" + corestoretypes "cosmossdk.io/core/store" "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" @@ -28,7 +29,6 @@ type ( checkpointingKeeper types.CheckpointingKeeper btccKeeper types.BtcCheckpointKeeper epochingKeeper types.EpochingKeeper - cmtClient types.CometClient storeQuerier storetypes.Queryable scopedKeeper types.ScopedKeeper // the address capable of executing a MsgUpdateParams message. Typically, this @@ -50,7 +50,6 @@ func NewKeeper( checkpointingKeeper types.CheckpointingKeeper, btccKeeper types.BtcCheckpointKeeper, epochingKeeper types.EpochingKeeper, - cmtClient types.CometClient, storeQuerier storetypes.Queryable, scopedKeeper types.ScopedKeeper, authority string, @@ -68,7 +67,6 @@ func NewKeeper( checkpointingKeeper: checkpointingKeeper, btccKeeper: btccKeeper, epochingKeeper: epochingKeeper, - cmtClient: cmtClient, storeQuerier: storeQuerier, scopedKeeper: scopedKeeper, authority: authority, diff --git a/x/zoneconcierge/keeper/params_test.go b/x/zoneconcierge/keeper/params_test.go index 70c1f96a6..b8f8f772d 100644 --- a/x/zoneconcierge/keeper/params_test.go +++ b/x/zoneconcierge/keeper/params_test.go @@ -9,7 +9,7 @@ import ( ) func TestGetParams(t *testing.T) { - k, ctx := testkeeper.ZoneConciergeKeeper(t, nil, nil, nil, nil, nil) + k, ctx := testkeeper.ZoneConciergeKeeper(t, nil, nil, nil, nil) params := types.DefaultParams() if err := k.SetParams(ctx, params); err != nil { diff --git a/x/zoneconcierge/keeper/proof_block_in_epoch.go b/x/zoneconcierge/keeper/proof_block_in_epoch.go deleted file mode 100644 index c87648bb1..000000000 --- a/x/zoneconcierge/keeper/proof_block_in_epoch.go +++ /dev/null @@ -1,12 +0,0 @@ -package keeper - -import ( - "context" - - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" -) - -func (k Keeper) ProveHeaderInEpoch(ctx context.Context, headerHeight uint64, epoch *epochingtypes.Epoch) (*tmcrypto.Proof, error) { - return k.epochingKeeper.ProveAppHashInEpoch(ctx, headerHeight, epoch.EpochNumber) -} diff --git a/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go b/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go new file mode 100644 index 000000000..1216d3ae9 --- /dev/null +++ b/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go @@ -0,0 +1,68 @@ +package keeper + +import ( + "context" + "fmt" + + errorsmod "cosmossdk.io/errors" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + "github.com/babylonchain/babylon/x/zoneconcierge/types" + tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO: fuzz tests for verifying proofs generated from KVStore + +func getCZHeaderKey(chainID string, height uint64) []byte { + key := types.CanonicalChainKey + key = append(key, []byte(chainID)...) + key = append(key, sdk.Uint64ToBigEndian(height)...) + return key +} + +func (k Keeper) ProveCZHeaderInEpoch(_ context.Context, header *types.IndexedHeader, epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { + czHeaderKey := getCZHeaderKey(header.ChainId, header.Height) + _, _, proof, err := k.QueryStore(types.StoreKey, czHeaderKey, int64(epoch.GetSealerBlockHeight())) + if err != nil { + return nil, err + } + + return proof, nil +} + +func VerifyCZHeaderInEpoch(header *types.IndexedHeader, epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { + // nil check + if header == nil { + return fmt.Errorf("header is nil") + } else if epoch == nil { + return fmt.Errorf("epoch is nil") + } else if proof == nil { + return fmt.Errorf("proof is nil") + } + + // sanity check + if err := header.ValidateBasic(); err != nil { + return err + } else if err := epoch.ValidateBasic(); err != nil { + return err + } + + // ensure epoch number is same in epoch and CZ header + if epoch.EpochNumber != header.BabylonEpoch { + return fmt.Errorf("epoch.EpochNumber (%d) is not equal to header.BabylonEpoch (%d)", epoch.EpochNumber, header.BabylonEpoch) + } + + // get the Merkle root, i.e., the AppHash of the sealer header + root := epoch.SealerHeaderHash + + // Ensure The header is committed to the AppHash of the sealer header + headerBytes, err := header.Marshal() + if err != nil { + return err + } + if err := VerifyStore(root, types.StoreKey, getCZHeaderKey(header.ChainId, header.Height), headerBytes, proof); err != nil { + return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for CZ header: %v", err) + } + + return nil +} diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go index 7e17560e1..dad1356aa 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go @@ -82,7 +82,7 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { epochingKeeper.EXPECT().GetEpoch(gomock.Any()).Return(epoch).AnyTimes() epochingKeeper.EXPECT().GetHistoricalEpoch(gomock.Any(), gomock.Eq(epoch.EpochNumber)).Return(epoch, nil).AnyTimes() // create zcKeeper and ctx - zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, nil, checkpointingKeeper, nil, epochingKeeper, nil) + zcKeeper, ctx := testkeeper.ZoneConciergeKeeper(t, nil, checkpointingKeeper, nil, epochingKeeper) // prove proof, err := zcKeeper.ProveEpochSealed(ctx, epoch.EpochNumber) diff --git a/x/zoneconcierge/keeper/proof_finalized_chain_info.go b/x/zoneconcierge/keeper/proof_finalized_chain_info.go index b6b890219..6c2617c99 100644 --- a/x/zoneconcierge/keeper/proof_finalized_chain_info.go +++ b/x/zoneconcierge/keeper/proof_finalized_chain_info.go @@ -22,14 +22,8 @@ func (k Keeper) proveFinalizedChainInfo( proof = &types.ProofFinalizedChainInfo{} ) - // Proof that the Babylon tx is in block - proof.ProofTxInBlock, err = k.ProveTxInBlock(ctx, chainInfo.LatestHeader.BabylonTxHash) - if err != nil { - return nil, err - } - - // proof that the block is in this epoch - proof.ProofHeaderInEpoch, err = k.ProveHeaderInEpoch(ctx, chainInfo.LatestHeader.BabylonHeaderHeight, epochInfo) + // Proof that the CZ header is timestamped in epoch + proof.ProofCzHeaderInEpoch, err = k.ProveCZHeaderInEpoch(ctx, chainInfo.LatestHeader, epochInfo) if err != nil { return nil, err } diff --git a/x/zoneconcierge/keeper/proof_tx_in_block.go b/x/zoneconcierge/keeper/proof_tx_in_block.go deleted file mode 100644 index ee6373647..000000000 --- a/x/zoneconcierge/keeper/proof_tx_in_block.go +++ /dev/null @@ -1,34 +0,0 @@ -package keeper - -import ( - "context" - "crypto/sha256" - "fmt" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmtypes "github.com/cometbft/cometbft/types" -) - -func (k Keeper) ProveTxInBlock(ctx context.Context, txHash []byte) (*tmproto.TxProof, error) { - if len(txHash) != sha256.Size { - return nil, fmt.Errorf("invalid txHash length: expected: %d, actual: %d", sha256.Size, len(txHash)) - } - - // query the tx with inclusion proof - resTx, err := k.cmtClient.Tx(ctx, txHash, true) - if err != nil { - return nil, err - } - - txProof := resTx.Proof.ToProto() - return &txProof, nil -} - -func VerifyTxInBlock(txHash []byte, proof *tmproto.TxProof) error { - txProof, err := tmtypes.TxProofFromProto(*proof) - if err != nil { - return err - } - - return txProof.Proof.Verify(txProof.RootHash, txHash) -} diff --git a/x/zoneconcierge/types/expected_keepers.go b/x/zoneconcierge/types/expected_keepers.go index b618d3abe..303c584ce 100644 --- a/x/zoneconcierge/types/expected_keepers.go +++ b/x/zoneconcierge/types/expected_keepers.go @@ -10,7 +10,6 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" ctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" @@ -103,7 +102,6 @@ type CheckpointingKeeper interface { type EpochingKeeper interface { GetHistoricalEpoch(ctx context.Context, epochNumber uint64) (*epochingtypes.Epoch, error) GetEpoch(ctx context.Context) *epochingtypes.Epoch - ProveAppHashInEpoch(ctx context.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) } // CometClient is a Comet client that allows to query tx inclusion proofs diff --git a/x/zoneconcierge/types/mocked_keepers.go b/x/zoneconcierge/types/mocked_keepers.go index ae09ce756..16f242bc4 100644 --- a/x/zoneconcierge/types/mocked_keepers.go +++ b/x/zoneconcierge/types/mocked_keepers.go @@ -13,7 +13,6 @@ import ( types1 "github.com/babylonchain/babylon/x/btclightclient/types" types2 "github.com/babylonchain/babylon/x/checkpointing/types" types3 "github.com/babylonchain/babylon/x/epoching/types" - crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" coretypes "github.com/cometbft/cometbft/rpc/core/types" types4 "github.com/cosmos/cosmos-sdk/types" types5 "github.com/cosmos/ibc-go/modules/capability/types" @@ -789,21 +788,6 @@ func (mr *MockEpochingKeeperMockRecorder) GetHistoricalEpoch(ctx, epochNumber in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHistoricalEpoch", reflect.TypeOf((*MockEpochingKeeper)(nil).GetHistoricalEpoch), ctx, epochNumber) } -// ProveAppHashInEpoch mocks base method. -func (m *MockEpochingKeeper) ProveAppHashInEpoch(ctx context.Context, height, epochNumber uint64) (*crypto.Proof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProveAppHashInEpoch", ctx, height, epochNumber) - ret0, _ := ret[0].(*crypto.Proof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ProveAppHashInEpoch indicates an expected call of ProveAppHashInEpoch. -func (mr *MockEpochingKeeperMockRecorder) ProveAppHashInEpoch(ctx, height, epochNumber interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProveAppHashInEpoch", reflect.TypeOf((*MockEpochingKeeper)(nil).ProveAppHashInEpoch), ctx, height, epochNumber) -} - // MockCometClient is a mock of CometClient interface. type MockCometClient struct { ctrl *gomock.Controller diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index 64ae776c8..a6c66e7dd 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -6,11 +6,11 @@ package types import ( fmt "fmt" types2 "github.com/babylonchain/babylon/x/btccheckpoint/types" - types4 "github.com/babylonchain/babylon/x/btclightclient/types" + types3 "github.com/babylonchain/babylon/x/btclightclient/types" types1 "github.com/babylonchain/babylon/x/checkpointing/types" types "github.com/babylonchain/babylon/x/epoching/types" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - types3 "github.com/cometbft/cometbft/proto/tendermint/types" + _ "github.com/cometbft/cometbft/proto/tendermint/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" @@ -457,18 +457,15 @@ func (m *ProofEpochSealed) GetProofEpochValSet() *crypto.ProofOps { // ProofFinalizedChainInfo is a set of proofs that attest a chain info is // BTC-finalised type ProofFinalizedChainInfo struct { - // proof_tx_in_block is the proof that tx that carries the header is included - // in a certain Babylon block - ProofTxInBlock *types3.TxProof `protobuf:"bytes,4,opt,name=proof_tx_in_block,json=proofTxInBlock,proto3" json:"proof_tx_in_block,omitempty"` - // proof_header_in_epoch is the proof that the Babylon header is in a certain - // epoch - ProofHeaderInEpoch *crypto.Proof `protobuf:"bytes,5,opt,name=proof_header_in_epoch,json=proofHeaderInEpoch,proto3" json:"proof_header_in_epoch,omitempty"` + // proof_cz_header_in_epoch is the proof that the CZ header is timestamped + // within a certain epoch + ProofCzHeaderInEpoch *crypto.ProofOps `protobuf:"bytes,1,opt,name=proof_cz_header_in_epoch,json=proofCzHeaderInEpoch,proto3" json:"proof_cz_header_in_epoch,omitempty"` // proof_epoch_sealed is the proof that the epoch is sealed - ProofEpochSealed *ProofEpochSealed `protobuf:"bytes,6,opt,name=proof_epoch_sealed,json=proofEpochSealed,proto3" json:"proof_epoch_sealed,omitempty"` + ProofEpochSealed *ProofEpochSealed `protobuf:"bytes,2,opt,name=proof_epoch_sealed,json=proofEpochSealed,proto3" json:"proof_epoch_sealed,omitempty"` // proof_epoch_submitted is the proof that the epoch's checkpoint is included // in BTC ledger It is the two TransactionInfo in the best (i.e., earliest) // checkpoint submission - ProofEpochSubmitted []*types2.TransactionInfo `protobuf:"bytes,7,rep,name=proof_epoch_submitted,json=proofEpochSubmitted,proto3" json:"proof_epoch_submitted,omitempty"` + ProofEpochSubmitted []*types2.TransactionInfo `protobuf:"bytes,3,rep,name=proof_epoch_submitted,json=proofEpochSubmitted,proto3" json:"proof_epoch_submitted,omitempty"` } func (m *ProofFinalizedChainInfo) Reset() { *m = ProofFinalizedChainInfo{} } @@ -504,16 +501,9 @@ func (m *ProofFinalizedChainInfo) XXX_DiscardUnknown() { var xxx_messageInfo_ProofFinalizedChainInfo proto.InternalMessageInfo -func (m *ProofFinalizedChainInfo) GetProofTxInBlock() *types3.TxProof { +func (m *ProofFinalizedChainInfo) GetProofCzHeaderInEpoch() *crypto.ProofOps { if m != nil { - return m.ProofTxInBlock - } - return nil -} - -func (m *ProofFinalizedChainInfo) GetProofHeaderInEpoch() *crypto.Proof { - if m != nil { - return m.ProofHeaderInEpoch + return m.ProofCzHeaderInEpoch } return nil } @@ -534,7 +524,7 @@ func (m *ProofFinalizedChainInfo) GetProofEpochSubmitted() []*types2.Transaction // Btc light client chain segment grown during last finalized epoch type BTCChainSegment struct { - BtcHeaders []*types4.BTCHeaderInfo `protobuf:"bytes,2,rep,name=btc_headers,json=btcHeaders,proto3" json:"btc_headers,omitempty"` + BtcHeaders []*types3.BTCHeaderInfo `protobuf:"bytes,1,rep,name=btc_headers,json=btcHeaders,proto3" json:"btc_headers,omitempty"` } func (m *BTCChainSegment) Reset() { *m = BTCChainSegment{} } @@ -570,7 +560,7 @@ func (m *BTCChainSegment) XXX_DiscardUnknown() { var xxx_messageInfo_BTCChainSegment proto.InternalMessageInfo -func (m *BTCChainSegment) GetBtcHeaders() []*types4.BTCHeaderInfo { +func (m *BTCChainSegment) GetBtcHeaders() []*types3.BTCHeaderInfo { if m != nil { return m.BtcHeaders } @@ -592,68 +582,66 @@ func init() { } var fileDescriptor_ab886e1868e5c5cd = []byte{ - // 967 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4d, 0x6f, 0x1b, 0x45, - 0x18, 0xce, 0xc6, 0x4e, 0xd2, 0x8c, 0xe3, 0x36, 0x9d, 0xb4, 0x74, 0x13, 0xc0, 0xb1, 0x5c, 0xa9, - 0xb8, 0x08, 0xd6, 0xb2, 0x81, 0x03, 0xdc, 0xb0, 0x69, 0xa9, 0x5b, 0x44, 0xd1, 0xda, 0x2d, 0x08, - 0x81, 0x56, 0xfb, 0x31, 0xde, 0x5d, 0x65, 0xbd, 0x63, 0xed, 0x8c, 0x5d, 0xbb, 0xbf, 0xa2, 0x7f, - 0x85, 0x1f, 0xc0, 0x9d, 0x63, 0x8f, 0xdc, 0x40, 0x89, 0xf8, 0x07, 0x5c, 0xb8, 0xa1, 0x79, 0x67, - 0x66, 0xbd, 0x6e, 0xb4, 0x84, 0x5e, 0xac, 0x9d, 0x99, 0xe7, 0x7d, 0xde, 0x67, 0xde, 0xaf, 0x31, - 0xfa, 0xc8, 0x73, 0xbd, 0x55, 0x42, 0xd3, 0xce, 0x4b, 0x9a, 0x12, 0x9f, 0xa6, 0x7e, 0x4c, 0xb2, - 0x90, 0x74, 0x16, 0xdd, 0xcd, 0x0d, 0x6b, 0x96, 0x51, 0x4e, 0xb1, 0xa9, 0xd0, 0xd6, 0xe6, 0xe1, - 0xa2, 0x7b, 0x72, 0x2b, 0xa4, 0x21, 0x05, 0x50, 0x47, 0x7c, 0x49, 0xfc, 0xc9, 0x69, 0x48, 0x69, - 0x98, 0x90, 0x0e, 0xac, 0xbc, 0xf9, 0xa4, 0xc3, 0xe3, 0x29, 0x61, 0xdc, 0x9d, 0xce, 0x14, 0xe0, - 0x3d, 0x4e, 0xd2, 0x80, 0x64, 0xd3, 0x38, 0xe5, 0x1d, 0xbe, 0x9a, 0x11, 0x26, 0x7f, 0xd5, 0xe9, - 0xfb, 0x85, 0x53, 0x3f, 0x5b, 0xcd, 0x38, 0x15, 0x4c, 0x74, 0xa2, 0x8e, 0x73, 0xed, 0x1e, 0xf7, - 0xfd, 0x88, 0xf8, 0x67, 0x33, 0x2a, 0x90, 0x8b, 0xee, 0xe6, 0x86, 0x42, 0xdf, 0xd3, 0xe8, 0xf5, - 0x49, 0x9c, 0x86, 0x80, 0x4e, 0x98, 0x73, 0x46, 0x56, 0x0a, 0x77, 0xbf, 0x14, 0x77, 0x89, 0xb2, - 0xa5, 0xa1, 0x64, 0x46, 0xfd, 0x48, 0xa1, 0xf4, 0xb7, 0xc2, 0x58, 0x05, 0x91, 0x49, 0x1c, 0x46, - 0xe2, 0x97, 0xe4, 0x2a, 0x0b, 0x3b, 0x12, 0xdf, 0xfa, 0x75, 0x1b, 0xd5, 0x87, 0x69, 0x40, 0x96, - 0x24, 0x78, 0x44, 0xdc, 0x80, 0x64, 0xf8, 0x18, 0x5d, 0xf3, 0x23, 0x37, 0x4e, 0x9d, 0x38, 0x30, - 0x8d, 0xa6, 0xd1, 0xde, 0xb7, 0xf7, 0x60, 0x3d, 0x0c, 0x30, 0x46, 0xd5, 0xc8, 0x65, 0x91, 0xb9, - 0xdd, 0x34, 0xda, 0x07, 0x36, 0x7c, 0xe3, 0x77, 0xd0, 0x6e, 0x44, 0x04, 0xad, 0x59, 0x69, 0x1a, - 0xed, 0xaa, 0xad, 0x56, 0xf8, 0x53, 0x54, 0x15, 0xd1, 0x37, 0xab, 0x4d, 0xa3, 0x5d, 0xeb, 0x9d, - 0x58, 0x32, 0x35, 0x96, 0x4e, 0x8d, 0x35, 0xd6, 0xa9, 0xe9, 0x57, 0x5f, 0xfd, 0x71, 0x6a, 0xd8, - 0x80, 0xc6, 0x16, 0x3a, 0x52, 0x17, 0x70, 0x22, 0x90, 0xe3, 0x80, 0xc3, 0x1d, 0x70, 0x78, 0x53, - 0x1d, 0x49, 0xa1, 0x8f, 0x84, 0xf7, 0x1e, 0xba, 0xfd, 0x26, 0x5e, 0x8a, 0xd9, 0x05, 0x31, 0x47, - 0x9b, 0x16, 0x52, 0xd9, 0x5d, 0x54, 0xd7, 0x36, 0x10, 0x3c, 0x73, 0x0f, 0xb0, 0x07, 0x6a, 0xf3, - 0x81, 0xd8, 0xc3, 0xf7, 0xd0, 0x0d, 0x0d, 0xe2, 0x4b, 0x29, 0xe2, 0x1a, 0x88, 0xd0, 0xb6, 0xe3, - 0xa5, 0x10, 0xd0, 0x7a, 0x8c, 0x76, 0x1e, 0xd2, 0xec, 0x8c, 0xe1, 0x2f, 0xd1, 0x9e, 0x54, 0xc0, - 0xcc, 0x4a, 0xb3, 0xd2, 0xae, 0xf5, 0x3e, 0xb0, 0xca, 0xaa, 0xd7, 0xda, 0x08, 0xb8, 0xad, 0xed, - 0x5a, 0x7f, 0x1b, 0x68, 0x7f, 0x00, 0xa1, 0x4e, 0x27, 0xf4, 0xbf, 0xf2, 0xf0, 0x0d, 0xaa, 0x27, - 0x2e, 0x27, 0x8c, 0xab, 0x4b, 0x43, 0x42, 0xde, 0xc2, 0xe3, 0x81, 0xb4, 0x56, 0x09, 0xef, 0x23, - 0xb5, 0x76, 0x26, 0xe2, 0x26, 0x90, 0xc7, 0x5a, 0xef, 0xb4, 0x9c, 0x0c, 0x2e, 0x6c, 0xd7, 0xa4, - 0x91, 0xbc, 0xfd, 0x17, 0xe8, 0x38, 0xef, 0x35, 0x12, 0x28, 0x59, 0xcc, 0xf1, 0xe9, 0x3c, 0xe5, - 0x50, 0x02, 0x55, 0xfb, 0x4e, 0x01, 0x20, 0x3d, 0xb3, 0x81, 0x38, 0x6e, 0xfd, 0x52, 0x41, 0xf8, - 0x61, 0x9c, 0xba, 0x49, 0xfc, 0x92, 0x04, 0xff, 0xeb, 0xfe, 0xcf, 0xd0, 0xad, 0x89, 0x36, 0x70, - 0x14, 0x28, 0x9d, 0x50, 0x15, 0x86, 0xbb, 0xe5, 0xca, 0x73, 0x76, 0x1b, 0x4f, 0x2e, 0x7b, 0xfc, - 0x1c, 0x21, 0x28, 0x08, 0x49, 0x56, 0x51, 0x85, 0xab, 0xc9, 0xf2, 0x46, 0x5b, 0x74, 0x2d, 0xa8, - 0x11, 0x7b, 0x1f, 0xb6, 0xc0, 0xf4, 0x5b, 0x74, 0x3d, 0x73, 0x5f, 0x38, 0xeb, 0x96, 0x55, 0x75, - 0xbf, 0x4e, 0xc9, 0x46, 0x7b, 0x0b, 0x0e, 0xdb, 0x7d, 0x31, 0xc8, 0xf7, 0xec, 0x7a, 0x56, 0x5c, - 0xe2, 0x67, 0x08, 0x7b, 0xdc, 0x77, 0xd8, 0xdc, 0x9b, 0xc6, 0x8c, 0xc5, 0x34, 0x15, 0x13, 0x03, - 0xda, 0xa0, 0xc8, 0xb9, 0x39, 0x77, 0x16, 0x5d, 0x6b, 0x94, 0xe3, 0x9f, 0x90, 0x95, 0x7d, 0xe8, - 0x71, 0x7f, 0x63, 0x07, 0x7f, 0x8d, 0x76, 0x60, 0xa2, 0x41, 0x7b, 0xd4, 0x7a, 0xdd, 0xf2, 0x48, - 0x7d, 0x27, 0x60, 0x97, 0xb3, 0x62, 0x4b, 0xfb, 0xd6, 0x3f, 0x06, 0x3a, 0x04, 0x08, 0x44, 0x62, - 0x44, 0xdc, 0x84, 0x04, 0xd8, 0x46, 0xf5, 0x85, 0x9b, 0xc4, 0x81, 0xcb, 0x69, 0xe6, 0x30, 0xc2, - 0x4d, 0x03, 0x1a, 0xe1, 0xe3, 0xf2, 0x18, 0x3c, 0xd7, 0xf0, 0xef, 0x63, 0x1e, 0xf5, 0x13, 0x26, - 0x54, 0x1f, 0xe4, 0x1c, 0x23, 0xc2, 0xf1, 0x03, 0x74, 0x08, 0x1e, 0x9d, 0x42, 0x66, 0x64, 0x9a, - 0xdf, 0xb5, 0xd6, 0xe3, 0xda, 0x92, 0xe3, 0x5a, 0xaa, 0x7e, 0x3a, 0x63, 0xf6, 0xf5, 0x59, 0x2e, - 0x0e, 0xf2, 0xf3, 0x18, 0x1d, 0x15, 0x69, 0x16, 0x6e, 0x02, 0x02, 0x2b, 0x57, 0x33, 0x1d, 0xae, - 0x99, 0x9e, 0xbb, 0xc9, 0x88, 0xf0, 0xd6, 0x5f, 0xdb, 0xe8, 0x4e, 0x49, 0x78, 0xf0, 0x57, 0xe8, - 0xa6, 0xf4, 0xc3, 0x97, 0x4e, 0x9c, 0x3a, 0x5e, 0x42, 0xfd, 0x33, 0x55, 0x0a, 0xc7, 0x45, 0x2f, - 0xf2, 0xd9, 0x19, 0x2f, 0x81, 0x47, 0xa9, 0x1d, 0x2f, 0x87, 0x69, 0x5f, 0x18, 0xe0, 0x27, 0xe8, - 0xb6, 0x64, 0x51, 0x33, 0x2d, 0xd6, 0x93, 0x4a, 0x16, 0x80, 0x59, 0xa6, 0xd7, 0xc6, 0x60, 0x26, - 0xbb, 0x6b, 0xa8, 0x26, 0xd9, 0x0f, 0x08, 0x17, 0xaf, 0xce, 0x20, 0x57, 0xaa, 0x00, 0x3e, 0xbc, - 0xa2, 0x00, 0x0a, 0xd9, 0x2d, 0x06, 0x42, 0xe5, 0xfb, 0x67, 0x2d, 0x53, 0x31, 0x8b, 0x52, 0xe3, - 0x9c, 0x04, 0xe6, 0x1e, 0xe4, 0xfd, 0x7e, 0x79, 0x9d, 0x8e, 0x33, 0x37, 0x65, 0xae, 0xcf, 0x63, - 0x2a, 0xab, 0xea, 0xa8, 0xc0, 0xad, 0x59, 0x5a, 0x3f, 0xa1, 0x1b, 0xfd, 0xf1, 0x00, 0x62, 0x3b, - 0x22, 0xe1, 0x94, 0xa4, 0x1c, 0x0f, 0x51, 0x4d, 0xb4, 0x85, 0x1e, 0xb4, 0xdb, 0xe0, 0xa7, 0x5d, - 0xf4, 0x53, 0x7c, 0xe1, 0x16, 0x5d, 0xab, 0x3f, 0x1e, 0xe8, 0x68, 0x4c, 0xa8, 0x8d, 0x3c, 0xee, - 0xab, 0xd1, 0xd3, 0x7f, 0xfa, 0xdb, 0x79, 0xc3, 0x78, 0x7d, 0xde, 0x30, 0xfe, 0x3c, 0x6f, 0x18, - 0xaf, 0x2e, 0x1a, 0x5b, 0xaf, 0x2f, 0x1a, 0x5b, 0xbf, 0x5f, 0x34, 0xb6, 0x7e, 0xfc, 0x2c, 0x8c, - 0x79, 0x34, 0xf7, 0x2c, 0x9f, 0x4e, 0x3b, 0x8a, 0x19, 0x66, 0x8c, 0x5e, 0x74, 0x96, 0x6f, 0xfc, - 0x7b, 0x81, 0x64, 0x7a, 0xbb, 0xf0, 0xb4, 0x7d, 0xf2, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, - 0x4d, 0x01, 0x46, 0xe3, 0x08, 0x00, 0x00, + // 934 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4f, 0x73, 0xdb, 0x44, + 0x14, 0xaf, 0x62, 0x27, 0x69, 0x9e, 0xe3, 0x36, 0x6c, 0x52, 0xaa, 0x06, 0x70, 0x3c, 0xee, 0x4c, + 0x71, 0x19, 0x90, 0xc7, 0x06, 0x0e, 0x70, 0xc3, 0x9e, 0x96, 0xa6, 0x30, 0x94, 0x91, 0xdd, 0xc2, + 0x30, 0x30, 0x1a, 0xfd, 0x59, 0x4b, 0x9a, 0xc8, 0x5a, 0x8f, 0xb4, 0x71, 0xe3, 0x7c, 0x8a, 0x7e, + 0x0b, 0xce, 0x7c, 0x00, 0xee, 0x1c, 0x7b, 0xe4, 0x06, 0x93, 0x7c, 0x05, 0x2e, 0xdc, 0x98, 0x7d, + 0xbb, 0x2b, 0x4b, 0xcd, 0x98, 0xc0, 0x45, 0xa3, 0xdd, 0xfd, 0xbd, 0xdf, 0xfb, 0xed, 0xfb, 0x27, + 0xc1, 0x87, 0x9e, 0xeb, 0x2d, 0x13, 0x96, 0xf6, 0xce, 0x59, 0x4a, 0x7d, 0x96, 0xfa, 0x31, 0xcd, + 0x42, 0xda, 0x5b, 0xf4, 0xab, 0x1b, 0xd6, 0x3c, 0x63, 0x9c, 0x11, 0x53, 0xa1, 0xad, 0xea, 0xe1, + 0xa2, 0x7f, 0x78, 0x10, 0xb2, 0x90, 0x21, 0xa8, 0x27, 0xde, 0x24, 0xfe, 0xf0, 0x28, 0x64, 0x2c, + 0x4c, 0x68, 0x0f, 0x57, 0xde, 0xe9, 0xb4, 0xc7, 0xe3, 0x19, 0xcd, 0xb9, 0x3b, 0x9b, 0x2b, 0xc0, + 0xbb, 0x9c, 0xa6, 0x01, 0xcd, 0x66, 0x71, 0xca, 0x7b, 0x7c, 0x39, 0xa7, 0xb9, 0x7c, 0xaa, 0xd3, + 0xf7, 0x4a, 0xa7, 0x7e, 0xb6, 0x9c, 0x73, 0x26, 0x98, 0xd8, 0x54, 0x1d, 0x17, 0xda, 0x3d, 0xee, + 0xfb, 0x11, 0xf5, 0x4f, 0xe6, 0x4c, 0x20, 0x17, 0xfd, 0xea, 0x86, 0x42, 0x3f, 0xd0, 0xe8, 0xd5, + 0x49, 0x9c, 0x86, 0x88, 0x4e, 0x72, 0xe7, 0x84, 0x2e, 0x15, 0xee, 0xe1, 0x5a, 0xdc, 0x15, 0xca, + 0x8e, 0x86, 0xd2, 0x39, 0xf3, 0x23, 0x85, 0xd2, 0xef, 0x0a, 0x63, 0x95, 0x44, 0x26, 0x71, 0x18, + 0x89, 0x27, 0x2d, 0x54, 0x96, 0x76, 0x24, 0xbe, 0xf3, 0xeb, 0x06, 0x34, 0x8f, 0xd3, 0x80, 0x9e, + 0xd1, 0xe0, 0x09, 0x75, 0x03, 0x9a, 0x91, 0x7b, 0x70, 0xd3, 0x8f, 0xdc, 0x38, 0x75, 0xe2, 0xc0, + 0x34, 0xda, 0x46, 0x77, 0xc7, 0xde, 0xc6, 0xf5, 0x71, 0x40, 0x08, 0xd4, 0x23, 0x37, 0x8f, 0xcc, + 0x8d, 0xb6, 0xd1, 0xdd, 0xb5, 0xf1, 0x9d, 0xbc, 0x0d, 0x5b, 0x11, 0x15, 0xb4, 0x66, 0xad, 0x6d, + 0x74, 0xeb, 0xb6, 0x5a, 0x91, 0x4f, 0xa0, 0x2e, 0xa2, 0x6f, 0xd6, 0xdb, 0x46, 0xb7, 0x31, 0x38, + 0xb4, 0x64, 0x6a, 0x2c, 0x9d, 0x1a, 0x6b, 0xa2, 0x53, 0x33, 0xac, 0xbf, 0xfa, 0xe3, 0xc8, 0xb0, + 0x11, 0x4d, 0x2c, 0xd8, 0x57, 0x17, 0x70, 0x22, 0x94, 0xe3, 0xa0, 0xc3, 0x4d, 0x74, 0xf8, 0x96, + 0x3a, 0x92, 0x42, 0x9f, 0x08, 0xef, 0x03, 0xb8, 0xf3, 0x26, 0x5e, 0x8a, 0xd9, 0x42, 0x31, 0xfb, + 0x55, 0x0b, 0xa9, 0xec, 0x3e, 0x34, 0xb5, 0x0d, 0x06, 0xcf, 0xdc, 0x46, 0xec, 0xae, 0xda, 0x7c, + 0x24, 0xf6, 0xc8, 0x03, 0xb8, 0xad, 0x41, 0xfc, 0x4c, 0x8a, 0xb8, 0x89, 0x22, 0xb4, 0xed, 0xe4, + 0x4c, 0x08, 0xe8, 0x3c, 0x85, 0xcd, 0xc7, 0x2c, 0x3b, 0xc9, 0xc9, 0x17, 0xb0, 0x2d, 0x15, 0xe4, + 0x66, 0xad, 0x5d, 0xeb, 0x36, 0x06, 0xef, 0x5b, 0xeb, 0xaa, 0xd7, 0xaa, 0x04, 0xdc, 0xd6, 0x76, + 0x9d, 0xbf, 0x0c, 0xd8, 0x19, 0x61, 0xa8, 0xd3, 0x29, 0xfb, 0xb7, 0x3c, 0x7c, 0x0d, 0xcd, 0xc4, + 0xe5, 0x34, 0xe7, 0xea, 0xd2, 0x98, 0x90, 0xff, 0xe1, 0x71, 0x57, 0x5a, 0xab, 0x84, 0x0f, 0x41, + 0xad, 0x9d, 0xa9, 0xb8, 0x09, 0xe6, 0xb1, 0x31, 0x38, 0x5a, 0x4f, 0x86, 0x17, 0xb6, 0x1b, 0xd2, + 0x48, 0xde, 0xfe, 0x73, 0xb8, 0x57, 0xf4, 0x1a, 0x0d, 0x94, 0xac, 0xdc, 0xf1, 0xd9, 0x69, 0xca, + 0xb1, 0x04, 0xea, 0xf6, 0xdd, 0x12, 0x40, 0x7a, 0xce, 0x47, 0xe2, 0xb8, 0xf3, 0x4b, 0x0d, 0xc8, + 0xe3, 0x38, 0x75, 0x93, 0xf8, 0x9c, 0x06, 0xff, 0xe9, 0xfe, 0xcf, 0xe1, 0x60, 0xaa, 0x0d, 0x1c, + 0x05, 0x4a, 0xa7, 0x4c, 0x85, 0xe1, 0xfe, 0x7a, 0xe5, 0x05, 0xbb, 0x4d, 0xa6, 0x57, 0x3d, 0x7e, + 0x06, 0x80, 0x05, 0x21, 0xc9, 0x6a, 0xaa, 0x70, 0x35, 0x59, 0xd1, 0x68, 0x8b, 0xbe, 0x85, 0x35, + 0x62, 0xef, 0xe0, 0x16, 0x9a, 0x7e, 0x03, 0xb7, 0x32, 0xf7, 0xa5, 0xb3, 0x6a, 0x59, 0x55, 0xf7, + 0xab, 0x94, 0x54, 0xda, 0x5b, 0x70, 0xd8, 0xee, 0xcb, 0x51, 0xb1, 0x67, 0x37, 0xb3, 0xf2, 0x92, + 0x3c, 0x07, 0xe2, 0x71, 0xdf, 0xc9, 0x4f, 0xbd, 0x59, 0x9c, 0xe7, 0x31, 0x4b, 0xc5, 0xc4, 0xc0, + 0x36, 0x28, 0x73, 0x56, 0xe7, 0xce, 0xa2, 0x6f, 0x8d, 0x0b, 0xfc, 0x57, 0x74, 0x69, 0xef, 0x79, + 0xdc, 0xaf, 0xec, 0x90, 0x2f, 0x61, 0x13, 0x27, 0x1a, 0xb6, 0x47, 0x63, 0xd0, 0x5f, 0x1f, 0xa9, + 0x6f, 0x05, 0xec, 0x6a, 0x56, 0x6c, 0x69, 0xdf, 0xf9, 0xdb, 0x80, 0x3d, 0x84, 0x60, 0x24, 0xc6, + 0xd4, 0x4d, 0x68, 0x40, 0x6c, 0x68, 0x2e, 0xdc, 0x24, 0x0e, 0x5c, 0xce, 0x32, 0x27, 0xa7, 0xdc, + 0x34, 0xb0, 0x11, 0x3e, 0x5a, 0x1f, 0x83, 0x17, 0x1a, 0xfe, 0x5d, 0xcc, 0xa3, 0x61, 0x92, 0x0b, + 0xd5, 0xbb, 0x05, 0xc7, 0x98, 0x72, 0xf2, 0x08, 0xf6, 0xd0, 0xa3, 0x53, 0xca, 0x8c, 0x4c, 0xf3, + 0x3b, 0xd6, 0x6a, 0x5c, 0x5b, 0x72, 0x5c, 0x4b, 0xd5, 0xcf, 0xe6, 0xb9, 0x7d, 0x6b, 0x5e, 0x88, + 0xc3, 0xfc, 0x3c, 0x85, 0xfd, 0x32, 0xcd, 0xc2, 0x4d, 0x50, 0x60, 0xed, 0x7a, 0xa6, 0xbd, 0x15, + 0xd3, 0x0b, 0x37, 0x19, 0x53, 0xde, 0xf9, 0x79, 0x03, 0xee, 0xae, 0x09, 0x0f, 0x19, 0x83, 0x29, + 0xfd, 0xf8, 0xe7, 0x7a, 0x20, 0xc5, 0x7a, 0xcc, 0x18, 0xd7, 0x3b, 0x3b, 0x40, 0xe3, 0xd1, 0xb9, + 0xec, 0x8f, 0x63, 0x35, 0x8b, 0xbe, 0x07, 0x52, 0x16, 0x9f, 0x63, 0xb4, 0x55, 0x14, 0x3e, 0xb8, + 0x26, 0x85, 0xa5, 0xfc, 0x94, 0xaf, 0xa2, 0x32, 0xf6, 0x13, 0xdc, 0xa9, 0x30, 0x8b, 0x62, 0xe1, + 0x9c, 0x06, 0x6a, 0x84, 0x3d, 0x5c, 0x5f, 0x69, 0x93, 0xcc, 0x4d, 0x73, 0xd7, 0xe7, 0x31, 0x93, + 0x75, 0xb1, 0x5f, 0xe2, 0xd6, 0x2c, 0x9d, 0x1f, 0xe1, 0xf6, 0x70, 0x32, 0xc2, 0xe8, 0x8c, 0x69, + 0x38, 0xa3, 0x29, 0x27, 0xc7, 0xd0, 0x10, 0x85, 0xad, 0x47, 0xa5, 0xac, 0x90, 0x6e, 0xd9, 0x4f, + 0xf9, 0x1b, 0xb5, 0xe8, 0x5b, 0xc3, 0xc9, 0x48, 0x47, 0x63, 0xca, 0x6c, 0xf0, 0xb8, 0xaf, 0x86, + 0xc7, 0xf0, 0xd9, 0x6f, 0x17, 0x2d, 0xe3, 0xf5, 0x45, 0xcb, 0xf8, 0xf3, 0xa2, 0x65, 0xbc, 0xba, + 0x6c, 0xdd, 0x78, 0x7d, 0xd9, 0xba, 0xf1, 0xfb, 0x65, 0xeb, 0xc6, 0x0f, 0x9f, 0x86, 0x31, 0x8f, + 0x4e, 0x3d, 0xcb, 0x67, 0xb3, 0x9e, 0x62, 0xc6, 0x29, 0xa1, 0x17, 0xbd, 0xb3, 0x37, 0xfe, 0x3f, + 0xf0, 0x2f, 0xc0, 0xdb, 0xc2, 0x8f, 0xd3, 0xc7, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x02, 0xb5, + 0x3e, 0x43, 0xa5, 0x08, 0x00, 0x00, } func (m *IndexedHeader) Marshal() (dAtA []byte, err error) { @@ -1010,7 +998,7 @@ func (m *ProofFinalizedChainInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x3a + dAtA[i] = 0x1a } } if m.ProofEpochSealed != nil { @@ -1023,23 +1011,11 @@ func (m *ProofFinalizedChainInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 - } - if m.ProofHeaderInEpoch != nil { - { - size, err := m.ProofHeaderInEpoch.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a + dAtA[i] = 0x12 } - if m.ProofTxInBlock != nil { + if m.ProofCzHeaderInEpoch != nil { { - size, err := m.ProofTxInBlock.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ProofCzHeaderInEpoch.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1047,7 +1023,7 @@ func (m *ProofFinalizedChainInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -1083,7 +1059,7 @@ func (m *BTCChainSegment) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintZoneconcierge(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0xa } } return len(dAtA) - i, nil @@ -1239,12 +1215,8 @@ func (m *ProofFinalizedChainInfo) Size() (n int) { } var l int _ = l - if m.ProofTxInBlock != nil { - l = m.ProofTxInBlock.Size() - n += 1 + l + sovZoneconcierge(uint64(l)) - } - if m.ProofHeaderInEpoch != nil { - l = m.ProofHeaderInEpoch.Size() + if m.ProofCzHeaderInEpoch != nil { + l = m.ProofCzHeaderInEpoch.Size() n += 1 + l + sovZoneconcierge(uint64(l)) } if m.ProofEpochSealed != nil { @@ -2262,45 +2234,9 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: ProofFinalizedChainInfo: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProofTxInBlock", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowZoneconcierge - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthZoneconcierge - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthZoneconcierge - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ProofTxInBlock == nil { - m.ProofTxInBlock = &types3.TxProof{} - } - if err := m.ProofTxInBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProofHeaderInEpoch", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ProofCzHeaderInEpoch", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2327,14 +2263,14 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ProofHeaderInEpoch == nil { - m.ProofHeaderInEpoch = &crypto.Proof{} + if m.ProofCzHeaderInEpoch == nil { + m.ProofCzHeaderInEpoch = &crypto.ProofOps{} } - if err := m.ProofHeaderInEpoch.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ProofCzHeaderInEpoch.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 6: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProofEpochSealed", wireType) } @@ -2370,7 +2306,7 @@ func (m *ProofFinalizedChainInfo) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProofEpochSubmitted", wireType) } @@ -2454,7 +2390,7 @@ func (m *BTCChainSegment) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: BTCChainSegment: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: + case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BtcHeaders", wireType) } @@ -2483,7 +2419,7 @@ func (m *BTCChainSegment) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcHeaders = append(m.BtcHeaders, &types4.BTCHeaderInfo{}) + m.BtcHeaders = append(m.BtcHeaders, &types3.BTCHeaderInfo{}) if err := m.BtcHeaders[len(m.BtcHeaders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } From 11792cad8a84469025504a5a27e9724e9c7e8d39 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 13 Dec 2023 14:51:36 +0400 Subject: [PATCH 128/202] chore: Rename BTC Validator to Finality Provider (#148) --- btcstaking/btcstaking_test.go | 60 +- btcstaking/staking_test.go | 30 +- btcstaking/types.go | 26 +- btcstaking/witness_utils.go | 16 +- cmd/babylond/cmd/flags.go | 52 +- cmd/babylond/cmd/genesis.go | 6 +- cmd/babylond/cmd/testnet.go | 2 +- proto/babylon/btcstaking/v1/btcstaking.proto | 50 +- proto/babylon/btcstaking/v1/events.proto | 20 +- proto/babylon/btcstaking/v1/incentive.proto | 20 +- proto/babylon/btcstaking/v1/params.proto | 6 +- proto/babylon/btcstaking/v1/query.proto | 146 +-- proto/babylon/btcstaking/v1/tx.proto | 38 +- proto/babylon/finality/v1/events.proto | 6 +- proto/babylon/finality/v1/finality.proto | 14 +- proto/babylon/finality/v1/query.proto | 24 +- proto/babylon/finality/v1/tx.proto | 18 +- proto/babylon/incentive/params.proto | 6 +- proto/babylon/incentive/tx.proto | 2 +- test/e2e/btc_staking_e2e_test.go | 126 +- .../configurer/chain/commands_btcstaking.go | 30 +- .../configurer/chain/queries_btcstaking.go | 26 +- testutil/datagen/btcstaking.go | 50 +- testutil/datagen/finality.go | 4 +- testutil/datagen/incentive.go | 26 +- x/btcstaking/abci.go | 2 +- x/btcstaking/client/cli/query.go | 56 +- x/btcstaking/client/cli/tx.go | 30 +- x/btcstaking/keeper/btc_delegators.go | 52 +- x/btcstaking/keeper/btc_validators.go | 64 +- x/btcstaking/keeper/grpc_query.go | 94 +- x/btcstaking/keeper/grpc_query_test.go | 240 ++-- x/btcstaking/keeper/incentive.go | 36 +- x/btcstaking/keeper/incentive_test.go | 40 +- x/btcstaking/keeper/msg_server.go | 78 +- x/btcstaking/keeper/msg_server_test.go | 124 +- x/btcstaking/keeper/params.go | 2 +- x/btcstaking/keeper/voting_power_table.go | 94 +- .../keeper/voting_power_table_test.go | 190 +-- x/btcstaking/types/btc_delegation.go | 42 +- x/btcstaking/types/btc_delegation_test.go | 12 +- x/btcstaking/types/btc_slashing_tx.go | 18 +- x/btcstaking/types/btc_slashing_tx_test.go | 8 +- x/btcstaking/types/btc_undelegation.go | 2 +- x/btcstaking/types/btc_undelegation_test.go | 12 +- x/btcstaking/types/btcstaking.go | 50 +- x/btcstaking/types/btcstaking.pb.go | 318 ++--- x/btcstaking/types/codec.go | 4 +- x/btcstaking/types/errors.go | 10 +- x/btcstaking/types/events.pb.go | 177 +-- x/btcstaking/types/genesis_test.go | 28 +- x/btcstaking/types/incentive.go | 44 +- x/btcstaking/types/incentive.pb.go | 151 +-- x/btcstaking/types/keys.go | 14 +- x/btcstaking/types/msg.go | 16 +- x/btcstaking/types/params.go | 18 +- x/btcstaking/types/params.pb.go | 84 +- x/btcstaking/types/query.pb.go | 1167 +++++++++-------- x/btcstaking/types/query.pb.gw.go | 250 ++-- x/btcstaking/types/tx.pb.go | 322 ++--- x/finality/abci.go | 2 +- x/finality/client/cli/query.go | 10 +- x/finality/client/cli/tx.go | 16 +- x/finality/keeper/evidence.go | 24 +- x/finality/keeper/grpc_query.go | 28 +- x/finality/keeper/grpc_query_test.go | 40 +- x/finality/keeper/msg_server.go | 106 +- x/finality/keeper/msg_server_test.go | 72 +- x/finality/keeper/public_randomness.go | 38 +- x/finality/keeper/tallying.go | 56 +- x/finality/keeper/tallying_test.go | 32 +- x/finality/keeper/votes.go | 26 +- x/finality/types/errors.go | 2 +- x/finality/types/events.go | 4 +- x/finality/types/events.pb.go | 70 +- x/finality/types/expected_keepers.go | 8 +- x/finality/types/finality.go | 8 +- x/finality/types/finality.pb.go | 82 +- x/finality/types/mocked_keepers.go | 46 +- x/finality/types/msg.go | 6 +- x/finality/types/query.pb.go | 192 +-- x/finality/types/query.pb.gw.go | 38 +- x/finality/types/tx.pb.go | 132 +- x/incentive/client/cli/tx.go | 2 +- x/incentive/keeper/btc_staking_gauge.go | 24 +- x/incentive/keeper/btc_staking_gauge_test.go | 24 +- .../keeper/btc_timestamping_gauge_test.go | 2 +- x/incentive/keeper/reward_gauge.go | 2 +- x/incentive/types/incentive.go | 16 +- x/incentive/types/params.pb.go | 6 +- x/incentive/types/tx.pb.go | 2 +- x/zoneconcierge/types/zoneconcierge.pb.go | 120 +- 92 files changed, 2955 insertions(+), 2934 deletions(-) diff --git a/btcstaking/btcstaking_test.go b/btcstaking/btcstaking_test.go index 86722c9e9..c4600f092 100644 --- a/btcstaking/btcstaking_test.go +++ b/btcstaking/btcstaking_test.go @@ -20,7 +20,7 @@ import ( type TestScenario struct { StakerKey *btcec.PrivateKey - ValidatorKeys []*btcec.PrivateKey + FinalityProviderKeys []*btcec.PrivateKey CovenantKeys []*btcec.PrivateKey RequiredCovenantSigs uint32 StakingAmount btcutil.Amount @@ -30,7 +30,7 @@ type TestScenario struct { func GenerateTestScenario( r *rand.Rand, t *testing.T, - numValidatorKeys uint32, + numFinalityProviderKeys uint32, numCovenantKeys uint32, requiredCovenantSigs uint32, stakingAmount btcutil.Amount, @@ -39,12 +39,12 @@ func GenerateTestScenario( stakerPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) - validatorKeys := make([]*btcec.PrivateKey, numValidatorKeys) - for i := uint32(0); i < numValidatorKeys; i++ { + finalityProviderKeys := make([]*btcec.PrivateKey, numFinalityProviderKeys) + for i := uint32(0); i < numFinalityProviderKeys; i++ { covenantPrivKey, err := btcec.NewPrivateKey() require.NoError(t, err) - validatorKeys[i] = covenantPrivKey + finalityProviderKeys[i] = covenantPrivKey } covenantKeys := make([]*btcec.PrivateKey, numCovenantKeys) @@ -58,7 +58,7 @@ func GenerateTestScenario( return &TestScenario{ StakerKey: stakerPrivKey, - ValidatorKeys: validatorKeys, + FinalityProviderKeys: finalityProviderKeys, CovenantKeys: covenantKeys, RequiredCovenantSigs: requiredCovenantSigs, StakingAmount: stakingAmount, @@ -76,14 +76,14 @@ func (t *TestScenario) CovenantPublicKeys() []*btcec.PublicKey { return covenantPubKeys } -func (t *TestScenario) ValidatorPublicKeys() []*btcec.PublicKey { - validatorPubKeys := make([]*btcec.PublicKey, len(t.ValidatorKeys)) +func (t *TestScenario) FinalityProviderPublicKeys() []*btcec.PublicKey { + finalityProviderPubKeys := make([]*btcec.PublicKey, len(t.FinalityProviderKeys)) - for i, validatorKey := range t.ValidatorKeys { - validatorPubKeys[i] = validatorKey.PubKey() + for i, fpKey := range t.FinalityProviderKeys { + finalityProviderPubKeys[i] = fpKey.PubKey() } - return validatorPubKeys + return finalityProviderPubKeys } func TestSpendingTimeLockPath(t *testing.T) { @@ -100,7 +100,7 @@ func TestSpendingTimeLockPath(t *testing.T) { stakingInfo, err := btcstaking.BuildStakingInfo( scenario.StakerKey.PubKey(), - scenario.ValidatorPublicKeys(), + scenario.FinalityProviderPublicKeys(), scenario.CovenantPublicKeys(), scenario.RequiredCovenantSigs, scenario.StakingTime, @@ -238,7 +238,7 @@ func TestSpendingUnbondingPathCovenant35MultiSig(t *testing.T) { stakingInfo, err := btcstaking.BuildStakingInfo( scenario.StakerKey.PubKey(), - scenario.ValidatorPublicKeys(), + scenario.FinalityProviderPublicKeys(), scenario.CovenantPublicKeys(), scenario.RequiredCovenantSigs, scenario.StakingTime, @@ -343,7 +343,7 @@ func TestSpendingUnbondingPathSingleKeyCovenant(t *testing.T) { stakingInfo, err := btcstaking.BuildStakingInfo( scenario.StakerKey.PubKey(), - scenario.ValidatorPublicKeys(), + scenario.FinalityProviderPublicKeys(), scenario.CovenantPublicKeys(), scenario.RequiredCovenantSigs, scenario.StakingTime, @@ -415,7 +415,7 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { stakingInfo, err := btcstaking.BuildStakingInfo( scenario.StakerKey.PubKey(), - scenario.ValidatorPublicKeys(), + scenario.FinalityProviderPublicKeys(), scenario.CovenantPublicKeys(), scenario.RequiredCovenantSigs, scenario.StakingTime, @@ -438,7 +438,7 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { si, err := stakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - // generate staker signature, covenant signatures, and validator signature + // generate staker signature, covenant signatures, and finality provider signature stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, @@ -453,23 +453,23 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { stakingInfo.StakingOutput, si.RevealedLeaf, ) - validatorSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + fpSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, - scenario.ValidatorKeys[0], + scenario.FinalityProviderKeys[0], si.RevealedLeaf, ) require.NoError(t, err) witness, err := si.CreateSlashingPathWitness( covenantSigantures, - []*schnorr.Signature{validatorSig}, + []*schnorr.Signature{fpSig}, stakerSig, ) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness - // now as we have validator signature execution should succeed + // now as we have finality provider signature execution should succeed prevOutputFetcher := stakingInfo.GetOutputFetcher() newEngine := func() (*txscript.Engine, error) { return txscript.NewEngine( @@ -482,10 +482,10 @@ func TestSpendingSlashingPathCovenant35MultiSig(t *testing.T) { btctest.AssertEngineExecution(t, 0, true, newEngine) } -func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) { +func TestSpendingSlashingPathCovenant35MultiSigFinalityProviderRestaking(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - // we have 3 out of 5 covenant committee, and we are restaking to 2 validators + // we have 3 out of 5 covenant committee, and we are restaking to 2 finality providers scenario := GenerateTestScenario( r, t, @@ -498,7 +498,7 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) stakingInfo, err := btcstaking.BuildStakingInfo( scenario.StakerKey.PubKey(), - scenario.ValidatorPublicKeys(), + scenario.FinalityProviderPublicKeys(), scenario.CovenantPublicKeys(), scenario.RequiredCovenantSigs, scenario.StakingTime, @@ -521,7 +521,7 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) si, err := stakingInfo.SlashingPathSpendInfo() require.NoError(t, err) - // generate staker signature, covenant signatures, and validator signature + // generate staker signature, covenant signatures, and finality provider signature stakerSig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( spendStakeTx, stakingInfo.StakingOutput, @@ -541,19 +541,19 @@ func TestSpendingSlashingPathCovenant35MultiSigValidatorRestaking(t *testing.T) covenantSigantures[0] = nil covenantSigantures[1] = nil - // only use one of the validator signatures - // script should still be valid as we require only one validator signature + // only use one of the finality provider signatures + // script should still be valid as we require only one finality provider signature // to be present - validatorsSignatures := GenerateSignatures( + fpSignatures := GenerateSignatures( t, - scenario.ValidatorKeys, + scenario.FinalityProviderKeys, spendStakeTx, stakingInfo.StakingOutput, si.RevealedLeaf, ) - validatorsSignatures[0] = nil + fpSignatures[0] = nil - witness, err := si.CreateSlashingPathWitness(covenantSigantures, validatorsSignatures, stakerSig) + witness, err := si.CreateSlashingPathWitness(covenantSigantures, fpSignatures, stakerSig) require.NoError(t, err) spendStakeTx.TxIn[0].Witness = witness diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index d1b01111b..554fdce49 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -20,41 +20,41 @@ import ( // StakingScriptData is a struct that holds data parsed from staking script type StakingScriptData struct { - StakerKey *btcec.PublicKey - ValidatorKey *btcec.PublicKey - CovenantKey *btcec.PublicKey - StakingTime uint16 + StakerKey *btcec.PublicKey + FinalityProviderKey *btcec.PublicKey + CovenantKey *btcec.PublicKey + StakingTime uint16 } func NewStakingScriptData( stakerKey, - validatorKey, + fpKey, covenantKey *btcec.PublicKey, stakingTime uint16) (*StakingScriptData, error) { - if stakerKey == nil || validatorKey == nil || covenantKey == nil { - return nil, fmt.Errorf("staker, validator and covenant keys cannot be nil") + if stakerKey == nil || fpKey == nil || covenantKey == nil { + return nil, fmt.Errorf("staker, finality provider and covenant keys cannot be nil") } return &StakingScriptData{ - StakerKey: stakerKey, - ValidatorKey: validatorKey, - CovenantKey: covenantKey, - StakingTime: stakingTime, + StakerKey: stakerKey, + FinalityProviderKey: fpKey, + CovenantKey: covenantKey, + StakingTime: stakingTime, }, nil } func genValidStakingScriptData(_ *testing.T, r *rand.Rand) *StakingScriptData { stakerPrivKeyBytes := datagen.GenRandomByteArray(r, 32) - validatorPrivKeyBytes := datagen.GenRandomByteArray(r, 32) + fpPrivKeyBytes := datagen.GenRandomByteArray(r, 32) covenantPrivKeyBytes := datagen.GenRandomByteArray(r, 32) stakingTime := uint16(r.Intn(math.MaxUint16)) _, stakerPublicKey := btcec.PrivKeyFromBytes(stakerPrivKeyBytes) - _, validatorPublicKey := btcec.PrivKeyFromBytes(validatorPrivKeyBytes) + _, fpPublicKey := btcec.PrivKeyFromBytes(fpPrivKeyBytes) _, covenantPublicKey := btcec.PrivKeyFromBytes(covenantPrivKeyBytes) - sd, _ := NewStakingScriptData(stakerPublicKey, validatorPublicKey, covenantPublicKey, stakingTime) + sd, _ := NewStakingScriptData(stakerPublicKey, fpPublicKey, covenantPublicKey, stakingTime) return sd } @@ -81,7 +81,7 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { if i == stakingOutputIdx { info, err := btcstaking.BuildStakingInfo( sd.StakerKey, - []*btcec.PublicKey{sd.ValidatorKey}, + []*btcec.PublicKey{sd.FinalityProviderKey}, []*btcec.PublicKey{sd.CovenantKey}, 1, sd.StakingTime, diff --git a/btcstaking/types.go b/btcstaking/types.go index d1857900d..2d421203f 100644 --- a/btcstaking/types.go +++ b/btcstaking/types.go @@ -176,7 +176,7 @@ func (t *taprootScriptHolder) taprootPkScript(net *chaincfg.Params) ([]byte, err // Staking script has 3 spending paths: // 1. Staker can spend after relative time lock - staking // 2. Staker can spend with covenat cooperation any time -// 3. Staker can spend with validator and covenant cooperation any time. +// 3. Staker can spend with finality provider and covenant cooperation any time. type StakingInfo struct { StakingOutput *wire.TxOut scriptHolder *taprootScriptHolder @@ -260,13 +260,13 @@ type babylonScriptPaths struct { // slashingPathScript is the script path for slashing // OP_CHECKSIGVERIFY // OP_CHECKSIG ... OP_CHECKSIGADD M OP_GREATERTHANOREQUAL OP_VERIFY - // OP_CHECKSIG ... OP_CHECKSIGADD 1 OP_GREATERTHANOREQUAL OP_VERIFY + // OP_CHECKSIG ... OP_CHECKSIGADD 1 OP_GREATERTHANOREQUAL OP_VERIFY slashingPathScript []byte } func newBabylonScriptPaths( stakerKey *btcec.PublicKey, - validatorKeys []*btcec.PublicKey, + fpKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, covenantQuorum uint32, lockTime uint16, @@ -300,11 +300,11 @@ func newBabylonScriptPaths( return nil, err } - validatorSigScript, err := buildMultiSigScript( - validatorKeys, - // we always require only one validator to sign + fpSigScript, err := buildMultiSigScript( + fpKeys, + // we always require only one finality provider to sign 1, - // we need to run verify to clear the stack, as validator multisig is in the middle of the script + // we need to run verify to clear the stack, as finality provider multisig is in the middle of the script true, ) @@ -319,7 +319,7 @@ func newBabylonScriptPaths( slashingPathScript := aggregateScripts( stakerSigScript, - validatorSigScript, + fpSigScript, covenantMultisigScript, ) @@ -332,7 +332,7 @@ func newBabylonScriptPaths( func BuildStakingInfo( stakerKey *btcec.PublicKey, - validatorKeys []*btcec.PublicKey, + fpKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, covenantQuorum uint32, stakingTime uint16, @@ -343,7 +343,7 @@ func BuildStakingInfo( babylonScripts, err := newBabylonScriptPaths( stakerKey, - validatorKeys, + fpKeys, covenantKeys, covenantQuorum, stakingTime, @@ -402,7 +402,7 @@ func (i *StakingInfo) SlashingPathSpendInfo() (*SpendInfo, error) { // Unbonding script has 2 spending paths: // 1. Staker can spend after relative time lock - staking -// 2. Staker can spend with validator and covenant cooperation any time. +// 2. Staker can spend with finality provider and covenant cooperation any time. type UnbondingInfo struct { UnbondingOutput *wire.TxOut scriptHolder *taprootScriptHolder @@ -412,7 +412,7 @@ type UnbondingInfo struct { func BuildUnbondingInfo( stakerKey *btcec.PublicKey, - validatorKeys []*btcec.PublicKey, + fpKeys []*btcec.PublicKey, covenantKeys []*btcec.PublicKey, covenantQuorum uint32, unbondingTime uint16, @@ -423,7 +423,7 @@ func BuildUnbondingInfo( babylonScripts, err := newBabylonScriptPaths( stakerKey, - validatorKeys, + fpKeys, covenantKeys, covenantQuorum, unbondingTime, diff --git a/btcstaking/witness_utils.go b/btcstaking/witness_utils.go index 87a8d2f1c..a733ab1e9 100644 --- a/btcstaking/witness_utils.go +++ b/btcstaking/witness_utils.go @@ -46,7 +46,7 @@ func (si *SpendInfo) CreateUnbondingPathWitness(covenantSigs []*schnorr.Signatur return CreateWitness(si, witnessStack) } -func (si *SpendInfo) CreateSlashingPathWitness(covenantSigs []*schnorr.Signature, validatorSigs []*schnorr.Signature, delegatorSig *schnorr.Signature) (wire.TxWitness, error) { +func (si *SpendInfo) CreateSlashingPathWitness(covenantSigs []*schnorr.Signature, fpSigs []*schnorr.Signature, delegatorSig *schnorr.Signature) (wire.TxWitness, error) { if si == nil { panic("cannot build witness without spend info") } @@ -66,16 +66,16 @@ func (si *SpendInfo) CreateSlashingPathWitness(covenantSigs []*schnorr.Signature } } - // add validator signatures to witness stack - // NOTE: only 1 of the validator signatures needs to be non-nil - if len(validatorSigs) == 0 { - return nil, fmt.Errorf("validator signatures should not be empty") + // add finality provider signatures to witness stack + // NOTE: only 1 of the finality provider signatures needs to be non-nil + if len(fpSigs) == 0 { + return nil, fmt.Errorf("finality provider signatures should not be empty") } - for _, valSig := range validatorSigs { - if valSig == nil { + for _, fpSig := range fpSigs { + if fpSig == nil { witnessStack = append(witnessStack, []byte{}) } else { - witnessStack = append(witnessStack, valSig.Serialize()) + witnessStack = append(witnessStack, fpSig.Serialize()) } } diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 08befd304..6b239b41a 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -14,28 +14,28 @@ import ( ) const ( - flagMaxActiveValidators = "max-active-validators" - flagBtcConfirmationDepth = "btc-confirmation-depth" - flagEpochInterval = "epoch-interval" - flagBtcFinalizationTimeout = "btc-finalization-timeout" - flagCheckpointTag = "checkpoint-tag" - flagBaseBtcHeaderHex = "btc-base-header" - flagBaseBtcHeaderHeight = "btc-base-header-height" - flagInflationRateChange = "inflation-rate-change" - flagInflationMax = "inflation-max" - flagInflationMin = "inflation-min" - flagGoalBonded = "goal-bonded" - flagBlocksPerYear = "blocks-per-year" - flagGenesisTime = "genesis-time" - flagBlockGasLimit = "block-gas-limit" - flagCovenantPks = "covenant-pks" - flagCovenantQuorum = "covenant-quorum" - flagMaxActiveBTCValidators = "max-active-btc-validators" - flagSlashingAddress = "slashing-address" - flagMinSlashingFee = "min-slashing-fee-sat" - flagSlashingRate = "slashing-rate" - flagMinPubRand = "min-pub-rand" - flagMinCommissionRate = "min-commission-rate" + flagMaxActiveValidators = "max-active-validators" + flagBtcConfirmationDepth = "btc-confirmation-depth" + flagEpochInterval = "epoch-interval" + flagBtcFinalizationTimeout = "btc-finalization-timeout" + flagCheckpointTag = "checkpoint-tag" + flagBaseBtcHeaderHex = "btc-base-header" + flagBaseBtcHeaderHeight = "btc-base-header-height" + flagInflationRateChange = "inflation-rate-change" + flagInflationMax = "inflation-max" + flagInflationMin = "inflation-min" + flagGoalBonded = "goal-bonded" + flagBlocksPerYear = "blocks-per-year" + flagGenesisTime = "genesis-time" + flagBlockGasLimit = "block-gas-limit" + flagCovenantPks = "covenant-pks" + flagCovenantQuorum = "covenant-quorum" + flagMaxActiveFinalityProviders = "max-active-finality-providers" + flagSlashingAddress = "slashing-address" + flagMinSlashingFee = "min-slashing-fee-sat" + flagSlashingRate = "slashing-rate" + flagMinPubRand = "min-pub-rand" + flagMinCommissionRate = "min-commission-rate" ) type GenesisCLIArgs struct { @@ -59,7 +59,7 @@ type GenesisCLIArgs struct { SlashingAddress string MinSlashingTransactionFeeSat int64 SlashingRate math.LegacyDec - MaxActiveBTCValidators uint32 + MaxActiveFinalityProviders uint32 MinPubRand uint64 MinCommissionRate math.LegacyDec } @@ -85,7 +85,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") cmd.Flags().String(flagSlashingRate, "0.1", "Bitcoin staking slashing rate") - cmd.Flags().Uint32(flagMaxActiveBTCValidators, 100, "Bitcoin staking maximum active BTC validators") + cmd.Flags().Uint32(flagMaxActiveFinalityProviders, 100, "Bitcoin staking maximum active finality providers") // finality args cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") // inflation args @@ -115,7 +115,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) slashingRate, _ := cmd.Flags().GetString(flagSlashingRate) - maxActiveBTCValidators, _ := cmd.Flags().GetUint32(flagMaxActiveBTCValidators) + maxActiveFinalityProviders, _ := cmd.Flags().GetUint32(flagMaxActiveFinalityProviders) minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) @@ -146,7 +146,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { MinSlashingTransactionFeeSat: minSlashingFee, MinCommissionRate: math.LegacyMustNewDecFromStr(minCommissionRate), SlashingRate: math.LegacyMustNewDecFromStr(slashingRate), - MaxActiveBTCValidators: maxActiveBTCValidators, + MaxActiveFinalityProviders: maxActiveFinalityProviders, MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 2bee1fe00..11eb0bfa8 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -71,7 +71,7 @@ Example: genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveBTCValidators, + genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) @@ -232,7 +232,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, - minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveBTCValidators uint32, + minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -321,7 +321,7 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee genParams.BtcstakingParams.MinCommissionRate = minCommissionRate genParams.BtcstakingParams.SlashingRate = slashingRate - genParams.BtcstakingParams.MaxActiveBtcValidators = maxActiveBTCValidators + genParams.BtcstakingParams.MaxActiveFinalityProviders = maxActiveFinalityProviders if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 0210279d3..f7b99bbb0 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -100,7 +100,7 @@ Example: genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, - genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveBTCValidators, genesisCliArgs.MinPubRand, + genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index 0fddb4e6e..f53aeca25 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -9,48 +9,48 @@ import "babylon/btcstaking/v1/pop.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; -// BTCValidator defines a BTC validator -message BTCValidator { - // description defines the description terms for the BTC validator. +// FinalityProvider defines a finality provider +message FinalityProvider { + // description defines the description terms for the finality provider. cosmos.staking.v1beta1.Description description = 1; - // commission defines the commission rate of BTC validator. + // commission defines the commission rate of the finality provider. string commission = 2 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; - // babylon_pk is the Babylon secp256k1 PK of this BTC validator + // babylon_pk is the Babylon secp256k1 PK of this finality provider cosmos.crypto.secp256k1.PubKey babylon_pk = 3; - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec bytes btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 5; // slashed_babylon_height indicates the Babylon height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed uint64 slashed_babylon_height = 6; // slashed_btc_height indicates the BTC height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed uint64 slashed_btc_height = 7; } -// BTCValidatorWithMeta wraps the BTCValidator with meta data. -message BTCValidatorWithMeta { - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator +// FinalityProviderWithMeta wraps the FinalityProvider with meta data. +message FinalityProviderWithMeta { + // btc_pk is the Bitcoin secp256k1 PK of thisfinality provider // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // height is the queried Babylon height uint64 height = 2; - // voting_power is the voting power of this BTC validator at the given height + // voting_power is the voting power of this finality provider at the given height uint64 voting_power = 3; // slashed_babylon_height indicates the Babylon height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed uint64 slashed_babylon_height = 4; // slashed_btc_height indicates the BTC height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed uint64 slashed_btc_height = 5; } @@ -63,11 +63,11 @@ message BTCDelegation { bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 3; - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - repeated bytes val_btc_pk_list = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // to multiple finality providers + repeated bytes fp_btc_pk_list = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock uint64 start_height = 5; @@ -83,7 +83,7 @@ message BTCDelegation { uint32 staking_output_idx = 9; // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or covenant yet. + // finality provider or covenant yet. bytes slashing_tx = 10 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). @@ -110,7 +110,7 @@ message BTCUndelegation { uint32 unbonding_time = 2; // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or covenant yet. + // finality provider or covenant yet. bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_unbonding_sig is the signature on the unbonding tx // by the delegator (i.e., SK corresponding to btc_pk). @@ -181,10 +181,10 @@ message SignatureInfo { } // CovenantAdaptorSignatures is a list adaptor signatures signed by the -// covenant with different validator's public keys as encryption keys +// covenant with different finality provider's public keys as encryption keys message CovenantAdaptorSignatures { // cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature bytes cov_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC validator's public key + // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key repeated bytes adaptor_sigs = 2; } diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto index b3b5513ba..f447f4904 100644 --- a/proto/babylon/btcstaking/v1/events.proto +++ b/proto/babylon/btcstaking/v1/events.proto @@ -6,8 +6,8 @@ import "babylon/btcstaking/v1/btcstaking.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; -// EventNewBTCValidator is the event emitted when a BTC validator is created -message EventNewBTCValidator { BTCValidator btc_val = 1; } +// EventNewFinalityProvider is the event emitted when a finality provider is created +message EventNewFinalityProvider { FinalityProvider fp = 1; } // EventNewBTCDelegation is the event emitted when a BTC delegation is created // NOTE: the BTC delegation is not active thus does not have voting power yet @@ -24,13 +24,13 @@ message EventUnbondingBTCDelegation { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the BTC finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // to multiple finality providers + repeated bytes fp_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 3; // unbonding_tx_hash is the hash of the unbonding tx. string unbonding_tx_hash = 4; @@ -42,13 +42,13 @@ message EventUnbondedBTCDelegation { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // to multiple finality providers + repeated bytes fp_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation string staking_tx_hash = 3; // unbonding_tx_hash is the hash of the unbonding tx. string unbonding_tx_hash = 4; diff --git a/proto/babylon/btcstaking/v1/incentive.proto b/proto/babylon/btcstaking/v1/incentive.proto index e6c78c7d8..659b54d98 100644 --- a/proto/babylon/btcstaking/v1/incentive.proto +++ b/proto/babylon/btcstaking/v1/incentive.proto @@ -7,28 +7,28 @@ import "cosmos/crypto/secp256k1/keys.proto"; option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; -// RewardDistCache is the cache for reward distribution of BTC validators at a height +// RewardDistCache is the cache for reward distribution of finality providers at a height message RewardDistCache { uint64 total_voting_power = 1; - // btc_vals is a list of BTC validators' voting power information - repeated BTCValDistInfo btc_vals = 2; + // finality_providers is a list of finality providers' voting power information + repeated FinalityProviderDistInfo finality_providers = 2; } -// BTCValDistInfo is the reward distribution of a BTC validator and its BTC delegations -message BTCValDistInfo { - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator +// FinalityProviderDistInfo is the reward distribution of a finality provider and its BTC delegations +message FinalityProviderDistInfo { + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // babylon_pk is the Babylon public key of the BTC validator + // babylon_pk is the Babylon public key of the finality provider cosmos.crypto.secp256k1.PubKey babylon_pk = 2; - // commission defines the commission rate of BTC validator + // commission defines the commission rate of finality provider string commission = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; - // total_voting_power is the total voting power of the BTC validator + // total_voting_power is the total voting power of the finality provider uint64 total_voting_power = 4; - // btc_dels is a list of BTC delegations' voting power information under this BTC validator + // btc_dels is a list of BTC delegations' voting power information under this finality provider repeated BTCDelDistInfo btc_dels = 5; } diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 9445294ae..fb611fea7 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -23,7 +23,7 @@ message Params { // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? int64 min_slashing_tx_fee_sat = 4; - // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators + // min_commission_rate is the chain-wide minimum commission rate that a finality provider can charge their delegators string min_commission_rate = 5 [ (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false @@ -35,6 +35,6 @@ message Params { (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; - // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol - uint32 max_active_btc_validators = 7; + // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol + uint32 max_active_finality_providers = 7; } diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 978ae855f..9656c0e17 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -16,14 +16,14 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/params"; } - // BTCValidators queries all BTC validators - rpc BTCValidators(QueryBTCValidatorsRequest) returns (QueryBTCValidatorsResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators"; + // FinalityProviders queries all finality providers + rpc FinalityProviders(QueryFinalityProvidersRequest) returns (QueryFinalityProvidersResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers"; } - // BTCValidator info about one validator - rpc BTCValidator(QueryBTCValidatorRequest) returns (QueryBTCValidatorResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/validator"; + // FinalityProvider info about one finality provider + rpc FinalityProvider(QueryFinalityProviderRequest) returns (QueryFinalityProviderResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers/{fp_btc_pk_hex}/finality_provider"; } // BTCDelegations queries all BTC delegations under a given status @@ -31,30 +31,30 @@ service Query { option (google.api.http).get = "/babylon/btcstaking/v1/btc_delegations"; } - // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. - rpc ActiveBTCValidatorsAtHeight(QueryActiveBTCValidatorsAtHeightRequest) returns (QueryActiveBTCValidatorsAtHeightResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{height}"; + // ActiveFinalityProvidersAtHeight queries finality providers with non zero voting power at given height. + rpc ActiveFinalityProvidersAtHeight(QueryActiveFinalityProvidersAtHeightRequest) returns (QueryActiveFinalityProvidersAtHeightResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers/{height}"; } - // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height - rpc BTCValidatorPowerAtHeight(QueryBTCValidatorPowerAtHeightRequest) returns (QueryBTCValidatorPowerAtHeightResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power/{height}"; + // FinalityProviderPowerAtHeight queries the voting power of a finality provider at a given height + rpc FinalityProviderPowerAtHeight(QueryFinalityProviderPowerAtHeightRequest) returns (QueryFinalityProviderPowerAtHeightResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers/{fp_btc_pk_hex}/power/{height}"; } - // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height - rpc BTCValidatorCurrentPower(QueryBTCValidatorCurrentPowerRequest) returns (QueryBTCValidatorCurrentPowerResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/power"; + // FinalityProviderCurrentPower queries the voting power of a finality provider at the current height + rpc FinalityProviderCurrentPower(QueryFinalityProviderCurrentPowerRequest) returns (QueryFinalityProviderCurrentPowerResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers/{fp_btc_pk_hex}/power"; } // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when - // there exists 1 BTC validator with voting power + // there exists 1 finality provider with voting power rpc ActivatedHeight(QueryActivatedHeightRequest) returns (QueryActivatedHeightResponse) { option (google.api.http).get = "/babylon/btcstaking/v1/activated_height"; } - // BTCValidatorDelegations queries all BTC delegations of the given BTC validator - rpc BTCValidatorDelegations(QueryBTCValidatorDelegationsRequest) returns (QueryBTCValidatorDelegationsResponse) { - option (google.api.http).get = "/babylon/btcstaking/v1/btc_validators/{val_btc_pk_hex}/delegations"; + // FinalityProviderDelegations queries all BTC delegations of the given finality provider + rpc FinalityProviderDelegations(QueryFinalityProviderDelegationsRequest) returns (QueryFinalityProviderDelegationsResponse) { + option (google.api.http).get = "/babylon/btcstaking/v1/finality_providers/{fp_btc_pk_hex}/delegations"; } // BTCDelegation retrieves delegation by corresponding staking tx hash @@ -72,34 +72,34 @@ message QueryParamsResponse { Params params = 1 [(gogoproto.nullable) = false]; } -// QueryBTCValidatorsRequest is the request type for the -// Query/BTCValidators RPC method. -message QueryBTCValidatorsRequest { +// QueryFinalityProvidersRequest is the request type for the +// Query/FinalityProviders RPC method. +message QueryFinalityProvidersRequest { // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 1; } -// QueryBTCValidatorsResponse is the response type for the -// Query/BTCValidators RPC method. -message QueryBTCValidatorsResponse { - // btc_validators contains all the BTC validators - repeated BTCValidator btc_validators = 1; +// QueryFinalityProvidersResponse is the response type for the +// Query/FinalityProviders RPC method. +message QueryFinalityProvidersResponse { + // finality_providers contains all the finality providers + repeated FinalityProvider finality_providers = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; } -// QueryBTCValidatorsRequest requests information about a BTC validator -message QueryBTCValidatorRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that - string val_btc_pk_hex = 1; +// QueryFinalityProviderRequest requests information about a finality provider +message QueryFinalityProviderRequest { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider + string fp_btc_pk_hex = 1; } -// QueryBTCValidatorsResponse resoponse contains information about a BTC validator -message QueryBTCValidatorResponse { - // btc_validator contains the BTC validator - BTCValidator btc_validator = 1; +// QueryFinalityProviderResponse contains information about a finality provider +message QueryFinalityProviderResponse { + // finality_provider contains the FinalityProvider + FinalityProvider finality_provider = 1; } // QueryBTCDelegationsRequest is the request type for the @@ -122,58 +122,58 @@ message QueryBTCDelegationsResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } -// QueryBTCValidatorPowerAtHeightRequest is the request type for the -// Query/BTCValidatorPowerAtHeight RPC method. -message QueryBTCValidatorPowerAtHeightRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderPowerAtHeightRequest is the request type for the +// Query/FinalityProviderPowerAtHeight RPC method. +message QueryFinalityProviderPowerAtHeightRequest { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider that // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - string val_btc_pk_hex = 1; + string fp_btc_pk_hex = 1; - // height is used for querying the given validator's voting power at this height + // height is used for querying the given finality provider's voting power at this height uint64 height = 2; } -// QueryBTCValidatorPowerAtHeightResponse is the response type for the -// Query/BTCValidatorPowerAtHeight RPC method. -message QueryBTCValidatorPowerAtHeightResponse { - // voting_power is the voting power of the BTC validator +// QueryFinalityProviderPowerAtHeightResponse is the response type for the +// Query/FinalityProviderPowerAtHeight RPC method. +message QueryFinalityProviderPowerAtHeightResponse { + // voting_power is the voting power of the finality provider uint64 voting_power = 1; } -// QueryBTCValidatorCurrentPowerRequest is the request type for the -// Query/BTCValidatorCurrentPower RPC method. -message QueryBTCValidatorCurrentPowerRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderCurrentPowerRequest is the request type for the +// Query/FinalityProviderCurrentPower RPC method. +message QueryFinalityProviderCurrentPowerRequest { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider that // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - string val_btc_pk_hex = 1; + string fp_btc_pk_hex = 1; } -// QueryBTCValidatorCurrentPowerResponse is the response type for the -// Query/BTCValidatorCurrentPower RPC method. -message QueryBTCValidatorCurrentPowerResponse { +// QueryFinalityProviderCurrentPowerResponse is the response type for the +// Query/FinalityProviderCurrentPower RPC method. +message QueryFinalityProviderCurrentPowerResponse { // height is the current height uint64 height = 1; - // voting_power is the voting power of the BTC validator + // voting_power is the voting power of the finality provider uint64 voting_power = 2; } -// QueryActiveBTCValidatorsAtHeightRequest is the request type for the -// Query/ActiveBTCValidatorsAtHeight RPC method. -message QueryActiveBTCValidatorsAtHeightRequest { - // height defines at which Babylon height to query the BTC validators info. +// QueryActiveFinalityProvidersAtHeightRequest is the request type for the +// Query/ActiveFinalityProvidersAtHeight RPC method. +message QueryActiveFinalityProvidersAtHeightRequest { + // height defines at which Babylon height to query the finality providers info. uint64 height = 1; // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 2; } -// QueryActiveBTCValidatorsAtHeightResponse is the response type for the -// Query/ActiveBTCValidatorsAtHeight RPC method. -message QueryActiveBTCValidatorsAtHeightResponse { - // btc_validators contains all the queried BTC validators. - repeated BTCValidatorWithMeta btc_validators = 1; +// QueryActiveFinalityProvidersAtHeightResponse is the response type for the +// Query/ActiveFinalityProvidersAtHeight RPC method. +message QueryActiveFinalityProvidersAtHeightResponse { + // finality_providers contains all the queried finality providersn. + repeated FinalityProviderWithMeta finality_providers = 1; // pagination defines the pagination in the response. cosmos.base.query.v1beta1.PageResponse pagination = 2; @@ -187,21 +187,21 @@ message QueryActivatedHeightResponse { uint64 height = 1; } -// QueryBTCValidatorDelegationsRequest is the request type for the -// Query/BTCValidatorDelegations RPC method. -message QueryBTCValidatorDelegationsRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderDelegationsRequest is the request type for the +// Query/FinalityProviderDelegations RPC method. +message QueryFinalityProviderDelegationsRequest { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality providerthat // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - string val_btc_pk_hex = 1; + string fp_btc_pk_hex = 1; // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 2; } -// QueryBTCValidatorDelegationsResponse is the response type for the -// Query/BTCValidatorDelegations RPC method. -message QueryBTCValidatorDelegationsResponse { +// QueryFinalityProviderDelegationsResponse is the response type for the +// Query/FinalityProviderDelegations RPC method. +message QueryFinalityProviderDelegationsResponse { // btc_delegator_delegations contains all the queried BTC delegations. repeated BTCDelegatorDelegations btc_delegator_delegations = 1; @@ -222,9 +222,9 @@ message QueryBTCDelegationResponse { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to - repeated bytes val_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + repeated bytes fp_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock uint64 start_height = 3; diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index 4cbad4fb8..b52c7ca36 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -17,8 +17,8 @@ option go_package = "github.com/babylonchain/babylon/x/btcstaking/types"; service Msg { option (cosmos.msg.v1.service) = true; - // CreateBTCValidator creates a new BTC validator - rpc CreateBTCValidator(MsgCreateBTCValidator) returns (MsgCreateBTCValidatorResponse); + // CreateFinalityProvider creates a new finality provider + rpc CreateFinalityProvider(MsgCreateFinalityProvider) returns (MsgCreateFinalityProviderResponse); // CreateBTCDelegation creates a new BTC delegation rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); // AddCovenantSigs handles signatures from a covenant member @@ -29,29 +29,29 @@ service Msg { rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); } -// MsgCreateBTCValidator is the message for creating a BTC validator -message MsgCreateBTCValidator { +// MsgCreateFinalityProvider is the message for creating a finality provider +message MsgCreateFinalityProvider { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; - // description defines the description terms for the BTC validator. + // description defines the description terms for the finality provider. cosmos.staking.v1beta1.Description description = 2; - // commission defines the commission rate of BTC validator. + // commission defines the commission rate of finality provider. string commission = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec" ]; - // babylon_pk is the Babylon secp256k1 PK of this BTC validator + // babylon_pk is the Babylon secp256k1 PK of this finality provider cosmos.crypto.secp256k1.PubKey babylon_pk = 4; - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec bytes btc_pk = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // pop is the proof of possession of babylon_pk and btc_pk ProofOfPossession pop = 6; } -// MsgCreateBTCValidatorResponse is the response for MsgCreateBTCValidator -message MsgCreateBTCValidatorResponse {} +// MsgCreateFinalityProviderResponse is the response for MsgCreateFinalityProvider +message MsgCreateFinalityProviderResponse {} // MsgCreateBTCDelegation is the message for creating a BTC delegation message MsgCreateBTCDelegation { @@ -64,26 +64,26 @@ message MsgCreateBTCDelegation { ProofOfPossession pop = 3; // btc_pk is the Bitcoin secp256k1 PK of the BTC delegator bytes btc_pk = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one - // validator pk it means that delegation is re-staked - repeated bytes val_btc_pk_list = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // fp_btc_pk_list is the list of Bitcoin secp256k1 PKs of the finality providers, if there is more than one + // finality provider pk it means that delegation is re-staked + repeated bytes fp_btc_pk_list = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // staking_time is the time lock used in staking transaction uint32 staking_time = 6; - // staking_value is amout of satoshis locked in staking output + // staking_value is the amount of satoshis locked in staking output int64 staking_value = 7; - // staking_tx is the staking tx along with the merekle proof of inclusion in btc block + // staking_tx is the staking tx along with the merkle proof of inclusion in btc block babylon.btccheckpoint.v1.TransactionInfo staking_tx = 8; // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. bytes slashing_tx = 9 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - // The staking tx output further needs signatures from covenant and validator in + // The staking tx output further needs signatures from covenant and finality provider in // order to be spendable. bytes delegator_slashing_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; /// fields related to on-demand unbonding - // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends + // unbonding_tx is a bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output bytes unbonding_tx = 11; // unbonding_time is the time lock used in unbonding transaction @@ -111,7 +111,7 @@ message MsgAddCovenantSigs { // It uniquely identifies a BTC delegation string staking_tx_hash = 3; // sigs is a list of adaptor signatures of the covenant - // the order of sigs should respect the order of validators + // the order of sigs should respect the order of finality providers // of the corresponding delegation repeated bytes slashing_tx_sigs = 4; // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon @@ -119,7 +119,7 @@ message MsgAddCovenantSigs { bytes unbonding_tx_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant // on slashing tx corresponding to unbonding tx submitted to babylon - // the order of sigs should respect the order of validators + // the order of sigs should respect the order of finality providers // of the corresponding delegation repeated bytes slashing_unbonding_tx_sigs = 6; } diff --git a/proto/babylon/finality/v1/events.proto b/proto/babylon/finality/v1/events.proto index 37e6354d8..61d7f6d5e 100644 --- a/proto/babylon/finality/v1/events.proto +++ b/proto/babylon/finality/v1/events.proto @@ -5,9 +5,9 @@ import "babylon/finality/v1/finality.proto"; option go_package = "github.com/babylonchain/babylon/x/finality/types"; -// EventSlashedBTCValidator is the event emitted when a BTC validator is slashed +// EventSlashedFinalityProvider is the event emitted when a finality provider is slashed // due to signing two conflicting blocks -message EventSlashedBTCValidator { - // evidence is the evidence that the BTC validator double signs +message EventSlashedFinalityProvider { + // evidence is the evidence that the finality provider double signs Evidence evidence = 1; } diff --git a/proto/babylon/finality/v1/finality.proto b/proto/babylon/finality/v1/finality.proto index e03a89ba9..f0901c24b 100644 --- a/proto/babylon/finality/v1/finality.proto +++ b/proto/babylon/finality/v1/finality.proto @@ -12,18 +12,18 @@ message IndexedBlock { // app_hash is the AppHash of the block bytes app_hash = 2; // finalized indicates whether the IndexedBlock is finalised by 2/3 - // BTC validators or not + // finality providers or not bool finalized = 3; } -// Evidence is the evidence that a BTC validator has signed finality +// Evidence is the evidence that a finality provider has signed finality // signatures with correct public randomness on two conflicting Babylon headers message Evidence { - // val_btc_pk is the BTC Pk of the validator that casts this vote - bytes val_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // fp_btc_pk is the BTC Pk of the finality provider that casts this vote + bytes fp_btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // block_height is the height of the conflicting blocks uint64 block_height = 2; - // pub_rand is the public randomness the BTC validator has committed to + // pub_rand is the public randomness the finality provider has committed to bytes pub_rand = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; // canonical_app_hash is the AppHash of the canonical block bytes canonical_app_hash = 4; @@ -32,9 +32,9 @@ message Evidence { // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator + // `r` is the public randomness that is already committed by the finality provider bytes canonical_finality_sig = 6 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; // fork_finality_sig is the finality signature to the fork block // where finality signature is an EOTS signature bytes fork_finality_sig = 7 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; -} \ No newline at end of file +} diff --git a/proto/babylon/finality/v1/query.proto b/proto/babylon/finality/v1/query.proto index 0173a0217..7c618d127 100644 --- a/proto/babylon/finality/v1/query.proto +++ b/proto/babylon/finality/v1/query.proto @@ -16,9 +16,9 @@ service Query { option (google.api.http).get = "/babylon/finality/v1/params"; } - // ListPublicRandomness is a range query for public randomness of a given BTC validator + // ListPublicRandomness is a range query for public randomness of a given finality provider rpc ListPublicRandomness(QueryListPublicRandomnessRequest) returns (QueryListPublicRandomnessResponse) { - option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/public_randomness_list"; + option (google.api.http).get = "/babylon/finality/v1/finality_providers/{fp_btc_pk_hex}/public_randomness_list"; } // Block queries a block at a given height @@ -31,14 +31,14 @@ service Query { option (google.api.http).get = "/babylon/finality/v1/blocks"; } - // VotesAtHeight queries BTC validators who have signed the block at given height. + // VotesAtHeight queries finality providers who have signed the block at given height. rpc VotesAtHeight(QueryVotesAtHeightRequest) returns (QueryVotesAtHeightResponse) { option (google.api.http).get = "/babylon/finality/v1/votes/{height}"; } // Evidence queries the first evidence which can be used for extracting the BTC SK rpc Evidence(QueryEvidenceRequest) returns (QueryEvidenceResponse) { - option (google.api.http).get = "/babylon/finality/v1/btc_validators/{val_btc_pk_hex}/evidence"; + option (google.api.http).get = "/babylon/finality/v1/finality_providers/{fp_btc_pk_hex}/evidence"; } // ListEvidences queries is a range query for evidences @@ -59,8 +59,8 @@ message QueryParamsResponse { // QueryListPublicRandomnessRequest is the request type for the // Query/ListPublicRandomness RPC method. message QueryListPublicRandomnessRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator - string val_btc_pk_hex = 1; + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider + string fp_btc_pk_hex = 1; // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 2; @@ -70,7 +70,7 @@ message QueryListPublicRandomnessRequest { // Query/ListPublicRandomness RPC method. message QueryListPublicRandomnessResponse { // pub_rand_map is the map where the key is the height and the value - // is the public randomness at this height for the given BTC validator + // is the public randomness at this height for the given finality provider map pub_rand_map = 1 [(gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; // pagination defines the pagination in the response. @@ -125,14 +125,14 @@ message QueryListBlocksResponse { // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. message QueryVotesAtHeightRequest { - // height defines at which height to query the BTC validators. + // height defines at which height to query the finality providers. uint64 height = 1; } // QueryVotesAtHeightResponse is the response type for the // Query/VotesAtHeight RPC method. message QueryVotesAtHeightResponse { - // btc_pk is the Bitcoin secp256k1 PK of BTC validators who have signed the block at given height. + // btc_pk is the Bitcoin secp256k1 PK of finality providers who have signed the block at given height. // the PK follows encoding in BIP-340 spec repeated bytes btc_pks = 1 [(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey"]; } @@ -140,9 +140,9 @@ message QueryVotesAtHeightResponse { // QueryEvidenceRequest is the request type for the // Query/Evidence RPC method. message QueryEvidenceRequest { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK - // (in BIP340 format) of the BTC validator - string val_btc_pk_hex = 1; + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK + // (in BIP340 format) of the finality provider + string fp_btc_pk_hex = 1; } // QueryEvidenceResponse is the response type for the diff --git a/proto/babylon/finality/v1/tx.proto b/proto/babylon/finality/v1/tx.proto index c3f714c87..d683f872c 100644 --- a/proto/babylon/finality/v1/tx.proto +++ b/proto/babylon/finality/v1/tx.proto @@ -26,8 +26,8 @@ message MsgAddFinalitySig { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; - // val_btc_pk is the BTC Pk of the validator that casts this vote - bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // fp_btc_pk is the BTC Pk of the finality provider that casts this vote + bytes fp_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // block_height is the height of the voted block uint64 block_height = 3; // block_app_hash is the AppHash of the voted block @@ -35,7 +35,7 @@ message MsgAddFinalitySig { // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator + // `r` is the public randomness that is already committed by the finality provider bytes finality_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrEOTSSig" ]; } // MsgAddFinalitySigResponse is the response to the MsgAddFinalitySig message @@ -46,17 +46,17 @@ message MsgCommitPubRandList { option (cosmos.msg.v1.signer) = "signer"; string signer = 1; - // val_btc_pk is the BTC Pk of the validator that commits the public randomness - bytes val_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // fp_btc_pk is the BTC Pk of the finality provider that commits the public randomness + bytes fp_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // start_height is the start block height of the list of public randomness uint64 start_height = 3; // pub_rand_list is the list of public randomness repeated bytes pub_rand_list = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.SchnorrPubRand" ]; // sig is the signature on (start_height || pub_rand_list) signed by - // SK corresponding to val_btc_pk. This prevents others to commit public - // randomness on behalf of val_btc_pk - // TODO: another option is to restrict signer to correspond to val_btc_pk. This restricts - // the tx submitter to be the holder of val_btc_pk. Decide this later + // SK corresponding to fp_btc_pk. This prevents others to commit public + // randomness on behalf of fp_btc_pk + // TODO: another option is to restrict signer to correspond to fp_btc_pk. This restricts + // the tx submitter to be the holder of fp_btc_pk. Decide this later bytes sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; } // MsgCommitPubRandListResponse is the response to the MsgCommitPubRandList message diff --git a/proto/babylon/incentive/params.proto b/proto/babylon/incentive/params.proto index 7ac4deed7..c576cb9a4 100644 --- a/proto/babylon/incentive/params.proto +++ b/proto/babylon/incentive/params.proto @@ -25,9 +25,9 @@ message Params { (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; - // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations - // NOTE: the portion of each BTC validator/delegation is calculated by using its voting - // power and BTC validator's commission + // btc_staking_portion is the portion of rewards that goes to Finality Providers/delegations + // NOTE: the portion of each Finality Provider/delegation is calculated by using its voting + // power and finality provider's commission string btc_staking_portion = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", diff --git a/proto/babylon/incentive/tx.proto b/proto/babylon/incentive/tx.proto index fb784eb7e..965147bd5 100644 --- a/proto/babylon/incentive/tx.proto +++ b/proto/babylon/incentive/tx.proto @@ -23,7 +23,7 @@ service Msg { // MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. message MsgWithdrawReward { option (cosmos.msg.v1.signer) = "address"; - // {submitter, reporter, btc_validator, btc_delegation} + // {submitter, reporter, finality_provider, btc_delegation} string type = 1; // address is the address of the stakeholder in bech32 string // signer of this msg has to be this address diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 332d00027..23fd83d2c 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -28,9 +28,9 @@ import ( var ( r = rand.New(rand.NewSource(time.Now().Unix())) net = &chaincfg.SimNetParams - // BTC validator - valBTCSK, _, _ = datagen.GenRandomBTCKeyPair(r) - btcVal *bstypes.BTCValidator + // finality provider + fpBTCSK, _, _ = datagen.GenRandomBTCKeyPair(r) + fp *bstypes.FinalityProvider // BTC delegation delBTCSK, delBTCPK, _ = datagen.GenRandomBTCKeyPair(r) // covenant @@ -68,32 +68,32 @@ func (s *BTCStakingTestSuite) TearDownSuite() { s.Require().NoError(err) } -// TestCreateBTCValidatorAndDelegation is an end-to-end test for -// user story 1: user creates BTC validator and BTC delegation -func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { +// TestCreateFinalityProviderAndDelegation is an end-to-end test for +// user story 1: user creates finality provider and BTC delegation +func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { chainA := s.configurer.GetChainConfig(0) chainA.WaitUntilHeight(1) nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) /* - create a random BTC validator on Babylon + create a random finality provider on Babylon */ - // NOTE: we use the node's secret key as Babylon secret key for the BTC validator - btcVal, err = datagen.GenRandomBTCValidatorWithBTCBabylonSKs(r, valBTCSK, nonValidatorNode.SecretKey) + // NOTE: we use the node's secret key as Babylon secret key for the finality provider + fp, err = datagen.GenRandomFinalityProviderWithBTCBabylonSKs(r, fpBTCSK, nonValidatorNode.SecretKey) s.NoError(err) - nonValidatorNode.CreateBTCValidator(btcVal.BabylonPk, btcVal.BtcPk, btcVal.Pop, btcVal.Description.Moniker, btcVal.Description.Identity, btcVal.Description.Website, btcVal.Description.SecurityContact, btcVal.Description.Details, btcVal.Commission) + nonValidatorNode.CreateFinalityProvider(fp.BabylonPk, fp.BtcPk, fp.Pop, fp.Description.Moniker, fp.Description.Identity, fp.Description.Website, fp.Description.SecurityContact, fp.Description.Details, fp.Commission) // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - // query the existence of BTC validator and assert equivalence - actualBtcVals := nonValidatorNode.QueryBTCValidators() - s.Len(actualBtcVals, 1) - s.Equal(util.Cdc.MustMarshal(btcVal), util.Cdc.MustMarshal(actualBtcVals[0])) + // query the existence of finality provider and assert equivalence + actualFps := nonValidatorNode.QueryFinalityProviders() + s.Len(actualFps, 1) + s.Equal(util.Cdc.MustMarshal(fp), util.Cdc.MustMarshal(actualFps[0])) /* - create a random BTC delegation under this BTC validator + create a random BTC delegation under this finality provider */ // BTC staking params, BTC delegation key pairs and PoP params := nonValidatorNode.QueryBTCStakingParams() @@ -113,7 +113,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.T(), net, delBTCSK, - []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + []*btcec.PublicKey{fp.BtcPk.MustToBTCPK()}, covenantBTCPKs, covenantQuorum, stakingTimeBlocks, @@ -156,7 +156,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { s.T(), net, delBTCSK, - []*btcec.PublicKey{btcVal.BtcPk.MustToBTCPK()}, + []*btcec.PublicKey{fp.BtcPk.MustToBTCPK()}, covenantBTCPKs, covenantQuorum, wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), @@ -174,7 +174,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { bbn.NewBIP340PubKeyFromBTCPK(delBTCPK), pop, stakingTxInfo, - btcVal.BtcPk, + fp.BtcPk, stakingTimeBlocks, btcutil.Amount(stakingValue), testStakingInfo.SlashingTx, @@ -190,7 +190,7 @@ func (s *BTCStakingTestSuite) Test1CreateBTCValidatorAndDelegation() { nonValidatorNode.WaitForNextBlock() nonValidatorNode.WaitForNextBlock() - pendingDelSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + pendingDelSet := nonValidatorNode.QueryFinalityProviderDelegations(fp.BtcPk.MarshalHex()) s.Len(pendingDelSet, 1) pendingDels := pendingDelSet[0] s.Len(pendingDels.Dels, 1) @@ -211,7 +211,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { s.NoError(err) // get last BTC delegation - pendingDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + pendingDelsSet := nonValidatorNode.QueryFinalityProviderDelegations(fp.BtcPk.MarshalHex()) s.Len(pendingDelsSet, 1) pendingDels := pendingDelsSet[0] s.Len(pendingDels.Dels, 1) @@ -226,7 +226,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { params := nonValidatorNode.QueryBTCStakingParams() - validatorBTCPKs, err := bbn.NewBTCPKsFromBIP340PKs(pendingDel.ValBtcPkList) + fpBTCPKs, err := bbn.NewBTCPKsFromBIP340PKs(pendingDel.FpBtcPkList) s.NoError(err) stakingInfo, err := pendingDel.GetStakingInfo(params, net) @@ -241,7 +241,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { // covenant signatures on slashing tx covenantSlashingSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, - validatorBTCPKs, + fpBTCPKs, stakingMsgTx, stakingSlashingPathInfo.GetPkScriptPath(), slashingTx, @@ -269,7 +269,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { s.NoError(err) covenantUnbondingSlashingSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, - validatorBTCPKs, + fpBTCPKs, unbondingTx, unbondingSlashingPathInfo.GetPkScriptPath(), pendingDel.BtcUndelegation.SlashingTx, @@ -293,7 +293,7 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { nonValidatorNode.WaitForNextBlock() // ensure the BTC delegation has covenant sigs now - activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + activeDelsSet := nonValidatorNode.QueryFinalityProviderDelegations(fp.BtcPk.MarshalHex()) s.Len(activeDelsSet, 1) activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) @@ -307,17 +307,17 @@ func (s *BTCStakingTestSuite) Test2SubmitCovenantSignature() { // ensure BTC staking is activated activatedHeight := nonValidatorNode.QueryActivatedHeight() s.Positive(activatedHeight) - // ensure BTC validator has voting power at activated height + // ensure finality provider has voting power at activated height currentBtcTip, err := nonValidatorNode.QueryTip() s.NoError(err) - activeBTCVals := nonValidatorNode.QueryActiveBTCValidatorsAtHeight(activatedHeight) - s.Len(activeBTCVals, 1) - s.Equal(activeBTCVals[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) - s.Equal(activeBTCVals[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) + activeFps := nonValidatorNode.QueryActiveFinalityProvidersAtHeight(activatedHeight) + s.Len(activeFps, 1) + s.Equal(activeFps[0].VotingPower, activeDels.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) + s.Equal(activeFps[0].VotingPower, activeDel.VotingPower(currentBtcTip.Height, initialization.BabylonBtcFinalizationPeriod, params.CovenantQuorum)) } // Test2CommitPublicRandomnessAndSubmitFinalitySignature is an end-to-end -// test for user story 3: BTC validator commits public randomness and submits +// test for user story 3: finality provider commits public randomness and submits // finality signature, such that blocks can be finalised. func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignature() { chainA := s.configurer.GetChainConfig(0) @@ -333,10 +333,10 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat commit a number of public randomness since activatedHeight */ // commit public randomness list - srList, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, valBTCSK, activatedHeight, 100) + srList, msgCommitPubRandList, err := datagen.GenRandomMsgCommitPubRandList(r, fpBTCSK, activatedHeight, 100) s.NoError(err) nonValidatorNode.CommitPubRandList( - msgCommitPubRandList.ValBtcPk, + msgCommitPubRandList.FpBtcPk, msgCommitPubRandList.StartHeight, msgCommitPubRandList.PubRandList, msgCommitPubRandList.Sig, @@ -346,14 +346,14 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat nonValidatorNode.WaitForNextBlock() var pubRandMap map[uint64]*bbn.SchnorrPubRand s.Eventually(func() bool { - pubRandMap = nonValidatorNode.QueryListPublicRandomness(btcVal.BtcPk) + pubRandMap = nonValidatorNode.QueryListPublicRandomness(fp.BtcPk) return len(pubRandMap) > 0 }, time.Minute, time.Second*5) s.Equal(pubRandMap[activatedHeight].MustMarshal(), msgCommitPubRandList.PubRandList[0].MustMarshal()) - // no reward gauge for BTC validator and delegation yet - btcValBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) - _, err = nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + // no reward gauge for finality provider and delegation yet + fpBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) + _, err = nonValidatorNode.QueryRewardGauge(fpBabylonAddr) s.Error(err) delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) _, err = nonValidatorNode.QueryRewardGauge(delBabylonAddr) @@ -369,11 +369,11 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat msgToSign := append(sdk.Uint64ToBigEndian(activatedHeight), appHash...) // generate EOTS signature - sig, err := eots.Sign(valBTCSK, srList[0], msgToSign) + sig, err := eots.Sign(fpBTCSK, srList[0], msgToSign) s.NoError(err) eotsSig := bbn.NewSchnorrEOTSSigFromModNScalar(sig) // submit finality signature - nonValidatorNode.AddFinalitySig(btcVal.BtcPk, activatedHeight, appHash, eotsSig) + nonValidatorNode.AddFinalitySig(fp.BtcPk, activatedHeight, appHash, eotsSig) // ensure vote is eventually cast nonValidatorNode.WaitForNextBlock() @@ -383,7 +383,7 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat return len(votes) > 0 }, time.Minute, time.Second*5) s.Equal(1, len(votes)) - s.Equal(votes[0].MarshalHex(), btcVal.BtcPk.MarshalHex()) + s.Equal(votes[0].MarshalHex(), fp.BtcPk.MarshalHex()) // once the vote is cast, ensure block is finalised finalizedBlock := nonValidatorNode.QueryIndexedBlock(activatedHeight) s.NotEmpty(finalizedBlock) @@ -392,12 +392,12 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.NotEmpty(finalizedBlocks) s.Equal(appHash.Bytes(), finalizedBlocks[0].AppHash) - // ensure BTC validator has received rewards after the block is finalised - btcValRewardGauges, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + // ensure finality providerhas received rewards after the block is finalised + fpRewardGauges, err := nonValidatorNode.QueryRewardGauge(fpBabylonAddr) s.NoError(err) - btcValRewardGauge, ok := btcValRewardGauges[itypes.BTCValidatorType.String()] + fpRewardGauge, ok := fpRewardGauges[itypes.FinalityProviderType.String()] s.True(ok) - s.True(btcValRewardGauge.Coins.IsAllPositive()) + s.True(fpRewardGauge.Coins.IsAllPositive()) // ensure BTC delegation has received rewards after the block is finalised btcDelRewardGauges, err := nonValidatorNode.QueryRewardGauge(delBabylonAddr) s.NoError(err) @@ -411,33 +411,33 @@ func (s *BTCStakingTestSuite) Test4WithdrawReward() { nonValidatorNode, err := chainA.GetNodeAtIndex(2) s.NoError(err) - // BTC validator balance before withdraw - btcValBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) + // finality provider balance before withdraw + fpBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) - btcValBalance, err := nonValidatorNode.QueryBalances(btcValBabylonAddr.String()) + fpBalance, err := nonValidatorNode.QueryBalances(fpBabylonAddr.String()) s.NoError(err) - // BTC validator reward gauge should not be fully withdrawn - btcValRgs, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + // finality provider reward gauge should not be fully withdrawn + fpRgs, err := nonValidatorNode.QueryRewardGauge(fpBabylonAddr) s.NoError(err) - btcValRg := btcValRgs[itypes.BTCValidatorType.String()] - s.T().Logf("BTC validator's withdrawable reward before withdrawing: %s", btcValRg.GetWithdrawableCoins().String()) - s.False(btcValRg.IsFullyWithdrawn()) + fpRg := fpRgs[itypes.FinalityProviderType.String()] + s.T().Logf("finality provider's withdrawable reward before withdrawing: %s", fpRg.GetWithdrawableCoins().String()) + s.False(fpRg.IsFullyWithdrawn()) - // withdraw BTC validator reward - nonValidatorNode.WithdrawReward(itypes.BTCValidatorType.String(), initialization.ValidatorWalletName) + // withdraw finality provider reward + nonValidatorNode.WithdrawReward(itypes.FinalityProviderType.String(), initialization.ValidatorWalletName) nonValidatorNode.WaitForNextBlock() - // balance after withdrawing BTC validator reward - btcValBalance2, err := nonValidatorNode.QueryBalances(btcValBabylonAddr.String()) + // balance after withdrawing finality provider reward + fpBalance2, err := nonValidatorNode.QueryBalances(fpBabylonAddr.String()) s.NoError(err) - s.T().Logf("btcValBalance2: %s; btcValBalance: %s", btcValBalance2.String(), btcValBalance.String()) - s.True(btcValBalance2.IsAllGT(btcValBalance)) - // BTC validator reward gauge should be fully withdrawn now - btcValRgs2, err := nonValidatorNode.QueryRewardGauge(btcValBabylonAddr) + s.T().Logf("fpBalance2: %s; fpBalance: %s", fpBalance2.String(), fpBalance.String()) + s.True(fpBalance2.IsAllGT(fpBalance)) + // finality provider reward gauge should be fully withdrawn now + fpRgs2, err := nonValidatorNode.QueryRewardGauge(fpBabylonAddr) s.NoError(err) - btcValRg2 := btcValRgs2[itypes.BTCValidatorType.String()] - s.T().Logf("BTC validator's withdrawable reward after withdrawing: %s", btcValRg2.GetWithdrawableCoins().String()) - s.True(btcValRg2.IsFullyWithdrawn()) + fpRg2 := fpRgs2[itypes.FinalityProviderType.String()] + s.T().Logf("finality provider's withdrawable reward after withdrawing: %s", fpRg2.GetWithdrawableCoins().String()) + s.True(fpRg2.IsFullyWithdrawn()) // BTC delegation balance before withdraw btcDelBalance, err := nonValidatorNode.QueryBalances(delBabylonAddr.String()) @@ -475,7 +475,7 @@ func (s *BTCStakingTestSuite) Test5SubmitStakerUnbonding() { // wait for a block so that above txs take effect nonValidatorNode.WaitForNextBlock() - activeDelsSet := nonValidatorNode.QueryBTCValidatorDelegations(btcVal.BtcPk.MarshalHex()) + activeDelsSet := nonValidatorNode.QueryFinalityProviderDelegations(fp.BtcPk.MarshalHex()) s.Len(activeDelsSet, 1) activeDels := activeDelsSet[0] s.Len(activeDels.Dels, 1) diff --git a/test/e2e/configurer/chain/commands_btcstaking.go b/test/e2e/configurer/chain/commands_btcstaking.go index 505bc47db..17636b517 100644 --- a/test/e2e/configurer/chain/commands_btcstaking.go +++ b/test/e2e/configurer/chain/commands_btcstaking.go @@ -20,8 +20,8 @@ import ( bstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) -func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *sdkmath.LegacyDec) { - n.LogActionF("creating BTC validator") +func (n *NodeConfig) CreateFinalityProvider(babylonPK *secp256k1.PubKey, btcPK *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, moniker, identity, website, securityContract, details string, commission *sdkmath.LegacyDec) { + n.LogActionF("creating finality provider") // get babylon PK hex babylonPKBytes, err := babylonPK.Marshal() @@ -34,11 +34,11 @@ func (n *NodeConfig) CreateBTCValidator(babylonPK *secp256k1.PubKey, btcPK *bbn. require.NoError(n.t, err) cmd := []string{ - "babylond", "tx", "btcstaking", "create-btc-validator", babylonPKHex, btcPKHex, popHex, "--from=val", "--moniker", moniker, "--identity", identity, "--website", website, "--security-contact", securityContract, "--details", details, "--commission-rate", commission.String(), + "babylond", "tx", "btcstaking", "create-finality-provider", babylonPKHex, btcPKHex, popHex, "--from=val", "--moniker", moniker, "--identity", identity, "--website", website, "--security-contact", securityContract, "--details", details, "--commission-rate", commission.String(), } _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) - n.LogActionF("successfully created BTC validator") + n.LogActionF("successfully created finality provider") } func (n *NodeConfig) CreateBTCDelegation( @@ -46,7 +46,7 @@ func (n *NodeConfig) CreateBTCDelegation( btcPk *bbn.BIP340PubKey, pop *bstypes.ProofOfPossession, stakingTxInfo *btcctypes.TransactionInfo, - valPK *bbn.BIP340PubKey, + fpPK *bbn.BIP340PubKey, stakingTimeBlocks uint16, stakingValue btcutil.Amount, slashingTx *bstypes.BTCSlashingTx, @@ -74,7 +74,7 @@ func (n *NodeConfig) CreateBTCDelegation( stakingTxInfoHex, err := stakingTxInfo.ToHexStr() require.NoError(n.t, err) - valPKHex := valPK.MarshalHex() + fpPKHex := fpPK.MarshalHex() stakingTimeString := sdkmath.NewUint(uint64(stakingTimeBlocks)).String() stakingValueString := sdkmath.NewInt(int64(stakingValue)).String() @@ -93,7 +93,7 @@ func (n *NodeConfig) CreateBTCDelegation( unbondingValueStr := sdkmath.NewInt(int64(unbondingValue)).String() delUnbondingSlashingSigHex := delUnbondingSlashingSig.ToHexStr() - cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, btcPkHex, popHex, stakingTxInfoHex, valPKHex, stakingTimeString, stakingValueString, slashingTxHex, delegatorSigHex, unbondingTxHex, unbondingSlashingTxHex, unbondingTimeStr, unbondingValueStr, delUnbondingSlashingSigHex, "--from=val"} + cmd := []string{"babylond", "tx", "btcstaking", "create-btc-delegation", babylonPKHex, btcPkHex, popHex, stakingTxInfoHex, fpPKHex, stakingTimeString, stakingValueString, slashingTxHex, delegatorSigHex, unbondingTxHex, unbondingSlashingTxHex, unbondingTimeStr, unbondingValueStr, delUnbondingSlashingSigHex, "--from=val"} _, _, err = n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully created BTC delegation") @@ -133,14 +133,14 @@ func (n *NodeConfig) AddCovenantSigs(covPK *bbn.BIP340PubKey, stakingTxHash stri n.LogActionF("successfully added covenant sigatures") } -func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand, sig *bbn.BIP340Signature) { +func (n *NodeConfig) CommitPubRandList(fpBTCPK *bbn.BIP340PubKey, startHeight uint64, pubRandList []bbn.SchnorrPubRand, sig *bbn.BIP340Signature) { n.LogActionF("committing public randomness list") cmd := []string{"babylond", "tx", "finality", "commit-pubrand-list"} - // add val BTC PK to cmd - valBTCPKHex := valBTCPK.MarshalHex() - cmd = append(cmd, valBTCPKHex) + // add finality provider BTC PK to cmd + fpBTCPKHex := fpBTCPK.MarshalHex() + cmd = append(cmd, fpBTCPKHex) // add start height to cmd startHeightStr := strconv.FormatUint(startHeight, 10) @@ -167,15 +167,15 @@ func (n *NodeConfig) CommitPubRandList(valBTCPK *bbn.BIP340PubKey, startHeight u n.LogActionF("successfully committed public randomness list") } -func (n *NodeConfig) AddFinalitySig(valBTCPK *bbn.BIP340PubKey, blockHeight uint64, blockLch []byte, finalitySig *bbn.SchnorrEOTSSig) { +func (n *NodeConfig) AddFinalitySig(fpBTCPK *bbn.BIP340PubKey, blockHeight uint64, blockLch []byte, finalitySig *bbn.SchnorrEOTSSig) { n.LogActionF("add finality signature") - valBTCPKHex := valBTCPK.MarshalHex() + fpBTCPKHex := fpBTCPK.MarshalHex() blockHeightStr := strconv.FormatUint(blockHeight, 10) blockLchHex := hex.EncodeToString(blockLch) finalitySigHex := finalitySig.ToHexStr() - cmd := []string{"babylond", "tx", "finality", "add-finality-sig", valBTCPKHex, blockHeightStr, blockLchHex, finalitySigHex, "--from=val", "--gas=500000"} + cmd := []string{"babylond", "tx", "finality", "add-finality-sig", fpBTCPKHex, blockHeightStr, blockLchHex, finalitySigHex, "--from=val", "--gas=500000"} _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) require.NoError(n.t, err) n.LogActionF("successfully added finality signature") @@ -186,7 +186,7 @@ func (n *NodeConfig) AddCovenantUnbondingSigs( stakingTxHash string, unbondingTxSig *bbn.BIP340Signature, slashUnbondingTxSigs []*asig.AdaptorSignature) { - n.LogActionF("adding validator signature") + n.LogActionF("adding finality provider signature") covPKHex := covPK.MarshalHex() unbondingTxSigHex := unbondingTxSig.ToHexStr() diff --git a/test/e2e/configurer/chain/queries_btcstaking.go b/test/e2e/configurer/chain/queries_btcstaking.go index 38963560e..cb55b0ab3 100644 --- a/test/e2e/configurer/chain/queries_btcstaking.go +++ b/test/e2e/configurer/chain/queries_btcstaking.go @@ -22,35 +22,35 @@ func (n *NodeConfig) QueryBTCStakingParams() *bstypes.Params { return &resp.Params } -func (n *NodeConfig) QueryBTCValidators() []*bstypes.BTCValidator { - bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/btc_validators", url.Values{}) +func (n *NodeConfig) QueryFinalityProviders() []*bstypes.FinalityProvider { + bz, err := n.QueryGRPCGateway("/babylon/btcstaking/v1/finality_providers", url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryBTCValidatorsResponse + var resp bstypes.QueryFinalityProvidersResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) - return resp.BtcValidators + return resp.FinalityProviders } -func (n *NodeConfig) QueryActiveBTCValidatorsAtHeight(height uint64) []*bstypes.BTCValidatorWithMeta { - path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%d", height) +func (n *NodeConfig) QueryActiveFinalityProvidersAtHeight(height uint64) []*bstypes.FinalityProviderWithMeta { + path := fmt.Sprintf("/babylon/btcstaking/v1/finality_providers/%d", height) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryActiveBTCValidatorsAtHeightResponse + var resp bstypes.QueryActiveFinalityProvidersAtHeightResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) - return resp.BtcValidators + return resp.FinalityProviders } -func (n *NodeConfig) QueryBTCValidatorDelegations(valBTCPK string) []*bstypes.BTCDelegatorDelegations { - path := fmt.Sprintf("/babylon/btcstaking/v1/btc_validators/%s/delegations", valBTCPK) +func (n *NodeConfig) QueryFinalityProviderDelegations(fpBTCPK string) []*bstypes.BTCDelegatorDelegations { + path := fmt.Sprintf("/babylon/btcstaking/v1/finality_providers/%s/delegations", fpBTCPK) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) - var resp bstypes.QueryBTCValidatorDelegationsResponse + var resp bstypes.QueryFinalityProviderDelegationsResponse err = util.Cdc.UnmarshalJSON(bz, &resp) require.NoError(n.t, err) @@ -94,8 +94,8 @@ func (n *NodeConfig) QueryActivatedHeight() uint64 { } // TODO: pagination support -func (n *NodeConfig) QueryListPublicRandomness(valBTCPK *bbn.BIP340PubKey) map[uint64]*bbn.SchnorrPubRand { - path := fmt.Sprintf("/babylon/finality/v1/btc_validators/%s/public_randomness_list", valBTCPK.MarshalHex()) +func (n *NodeConfig) QueryListPublicRandomness(fpBTCPK *bbn.BIP340PubKey) map[uint64]*bbn.SchnorrPubRand { + path := fmt.Sprintf("/babylon/finality/v1/finality_providers/%s/public_randomness_list", fpBTCPK.MarshalHex()) bz, err := n.QueryGRPCGateway(path, url.Values{}) require.NoError(n.t, err) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index e2d59030c..4cd1487f8 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -27,24 +27,24 @@ const ( UnbondingTxFee = int64(1000) ) -func GenRandomBTCValidator(r *rand.Rand) (*bstypes.BTCValidator, error) { +func GenRandomFinalityProvider(r *rand.Rand) (*bstypes.FinalityProvider, error) { // key pairs btcSK, _, err := GenRandomBTCKeyPair(r) if err != nil { return nil, err } - return GenRandomBTCValidatorWithBTCSK(r, btcSK) + return GenRandomFinalityProviderWithBTCSK(r, btcSK) } -func GenRandomBTCValidatorWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.BTCValidator, error) { +func GenRandomFinalityProviderWithBTCSK(r *rand.Rand, btcSK *btcec.PrivateKey) (*bstypes.FinalityProvider, error) { bbnSK, _, err := GenRandomSecp256k1KeyPair(r) if err != nil { return nil, err } - return GenRandomBTCValidatorWithBTCBabylonSKs(r, btcSK, bbnSK) + return GenRandomFinalityProviderWithBTCBabylonSKs(r, btcSK, bbnSK) } -func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKey, bbnSK cryptotypes.PrivKey) (*bstypes.BTCValidator, error) { +func GenRandomFinalityProviderWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKey, bbnSK cryptotypes.PrivKey) (*bstypes.FinalityProvider, error) { // commission commission := sdkmath.LegacyNewDecWithPrec(int64(RandomInt(r, 49)+1), 2) // [1/100, 50/100] // description @@ -62,7 +62,7 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe if err != nil { return nil, err } - return &bstypes.BTCValidator{ + return &bstypes.FinalityProvider{ Description: &description, Commission: &commission, BabylonPk: secp256k1PK, @@ -75,7 +75,7 @@ func GenRandomBTCValidatorWithBTCBabylonSKs(r *rand.Rand, btcSK *btcec.PrivateKe func GenRandomBTCDelegation( r *rand.Rand, t *testing.T, - valBTCPKs []bbn.BIP340PubKey, + fpBTCPKs []bbn.BIP340PubKey, delSK *btcec.PrivateKey, covenantSKs []*btcec.PrivateKey, covenantQuorum uint32, @@ -91,14 +91,14 @@ func GenRandomBTCDelegation( for _, covenantSK := range covenantSKs { covenantBTCPKs = append(covenantBTCPKs, covenantSK.PubKey()) } - // list of validator PKs - valPKs := []*btcec.PublicKey{} - for _, valBTCPK := range valBTCPKs { - valPK, err := valBTCPK.ToBTCPK() + // list of finality provider PKs + fpPKs := []*btcec.PublicKey{} + for _, fpBTCPK := range fpBTCPKs { + fpPK, err := fpBTCPK.ToBTCPK() if err != nil { return nil, err } - valPKs = append(valPKs, valPK) + fpPKs = append(fpPKs, fpPK) } // BTC delegation Babylon key pairs @@ -121,7 +121,7 @@ func GenRandomBTCDelegation( t, net, delSK, - valPKs, + fpPKs, covenantBTCPKs, covenantQuorum, uint16(endHeight-startHeight), @@ -147,7 +147,7 @@ func GenRandomBTCDelegation( // covenant sigs covenantSigs, err := GenCovenantAdaptorSigs( covenantSKs, - valPKs, + fpPKs, stakingMsgTx, slashingPathSpendInfo.GetPkScriptPath(), stakingSlashingInfo.SlashingTx, @@ -161,7 +161,7 @@ func GenRandomBTCDelegation( BabylonPk: secp256k1PK, BtcPk: delBTCPK, Pop: pop, - ValBtcPkList: valBTCPKs, + FpBtcPkList: fpBTCPKs, StartHeight: startHeight, EndHeight: endHeight, TotalSat: totalSat, @@ -185,7 +185,7 @@ func GenRandomBTCDelegation( t, net, delSK, - valPKs, + fpPKs, covenantBTCPKs, covenantQuorum, wire.NewOutPoint(&stkTxHash, StakingOutIdx), @@ -215,7 +215,7 @@ func GenRandomBTCDelegation( covUnbondingSlashingSigs, covUnbondingSigs, err := unbondingSlashingInfo.GenCovenantSigs( covenantSKs, - valPKs, + fpPKs, stakingMsgTx, unbondingPathSpendInfo.GetPkScriptPath(), ) @@ -245,7 +245,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( btcNet *chaincfg.Params, outPoint *wire.OutPoint, stakerSK *btcec.PrivateKey, - validatorPKs []*btcec.PublicKey, + fpPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, covenantQuorum uint32, stakingTimeBlocks uint16, @@ -256,7 +256,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( stakingInfo, err := btcstaking.BuildStakingInfo( stakerSK.PubKey(), - validatorPKs, + fpPKs, covenantPKs, covenantQuorum, stakingTimeBlocks, @@ -306,7 +306,7 @@ func GenBTCStakingSlashingInfo( t *testing.T, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, - validatorPKs []*btcec.PublicKey, + fpPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, covenantQuorum uint32, stakingTimeBlocks uint16, @@ -323,7 +323,7 @@ func GenBTCStakingSlashingInfo( btcNet, outPoint, stakerSK, - validatorPKs, + fpPKs, covenantPKs, covenantQuorum, stakingTimeBlocks, @@ -337,7 +337,7 @@ func GenBTCUnbondingSlashingInfo( t *testing.T, btcNet *chaincfg.Params, stakerSK *btcec.PrivateKey, - validatorPKs []*btcec.PublicKey, + fpPKs []*btcec.PublicKey, covenantPKs []*btcec.PublicKey, covenantQuorum uint32, stakingTransactionOutpoint *wire.OutPoint, @@ -349,7 +349,7 @@ func GenBTCUnbondingSlashingInfo( unbondingInfo, err := btcstaking.BuildUnbondingInfo( stakerSK.PubKey(), - validatorPKs, + fpPKs, covenantPKs, covenantQuorum, stakingTimeBlocks, @@ -407,7 +407,7 @@ func (info *TestUnbondingSlashingInfo) GenDelSlashingTxSig(sk *btcec.PrivateKey) func (info *TestUnbondingSlashingInfo) GenCovenantSigs( covSKs []*btcec.PrivateKey, - valPKs []*btcec.PublicKey, + fpPKs []*btcec.PublicKey, stakingTx *wire.MsgTx, unbondingPkScriptPath []byte, ) ([]*bstypes.CovenantAdaptorSignatures, []*bstypes.SignatureInfo, error) { @@ -418,7 +418,7 @@ func (info *TestUnbondingSlashingInfo) GenCovenantSigs( covUnbondingSlashingSigs, err := GenCovenantAdaptorSigs( covSKs, - valPKs, + fpPKs, info.UnbondingTx, unbondingSlashingPathInfo.GetPkScriptPath(), info.SlashingTx, diff --git a/testutil/datagen/finality.go b/testutil/datagen/finality.go index f4cb4dd7a..73eae543b 100644 --- a/testutil/datagen/finality.go +++ b/testutil/datagen/finality.go @@ -34,7 +34,7 @@ func GenRandomMsgCommitPubRandList(r *rand.Rand, sk *btcec.PrivateKey, startHeig msg := &ftypes.MsgCommitPubRandList{ Signer: GenRandomAccount().Address, - ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + FpBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), StartHeight: startHeight, PubRandList: prList, } @@ -69,7 +69,7 @@ func GenRandomEvidence(r *rand.Rand, sk *btcec.PrivateKey, height uint64) (*ftyp } evidence := &ftypes.Evidence{ - ValBtcPk: bip340PK, + FpBtcPk: bip340PK, BlockHeight: height, PubRand: bbn.NewSchnorrPubRandFromFieldVal(pr), CanonicalAppHash: cAppHash, diff --git a/testutil/datagen/incentive.go b/testutil/datagen/incentive.go index e1741a836..1c775d574 100644 --- a/testutil/datagen/incentive.go +++ b/testutil/datagen/incentive.go @@ -82,34 +82,34 @@ func GenRandomBTCDelDistInfo(r *rand.Rand) *bstypes.BTCDelDistInfo { } } -func GenRandomBTCValDistInfo(r *rand.Rand) (*bstypes.BTCValDistInfo, error) { - // create BTC validator with random commission - btcVal, err := GenRandomBTCValidator(r) +func GenRandomFinalityProviderDistInfo(r *rand.Rand) (*bstypes.FinalityProviderDistInfo, error) { + // create finality provider with random commission + fp, err := GenRandomFinalityProvider(r) if err != nil { return nil, err } - // create BTC validator distribution info - btcValDistInfo := bstypes.NewBTCValDistInfo(btcVal) + // create finality provider distribution info + fpDistInfo := bstypes.NewFinalityProviderDistInfo(fp) // add a random number of BTC delegation distribution info numBTCDels := RandomInt(r, 100) + 1 for i := uint64(0); i < numBTCDels; i++ { btcDelDistInfo := GenRandomBTCDelDistInfo(r) - btcValDistInfo.BtcDels = append(btcValDistInfo.BtcDels, btcDelDistInfo) - btcValDistInfo.TotalVotingPower += btcDelDistInfo.VotingPower + fpDistInfo.BtcDels = append(fpDistInfo.BtcDels, btcDelDistInfo) + fpDistInfo.TotalVotingPower += btcDelDistInfo.VotingPower } - return btcValDistInfo, nil + return fpDistInfo, nil } func GenRandomBTCStakingRewardDistCache(r *rand.Rand) (*bstypes.RewardDistCache, error) { rdc := bstypes.NewRewardDistCache() - // a random number of BTC validators - numBTCVals := RandomInt(r, 10) + 1 - for i := uint64(0); i < numBTCVals; i++ { - v, err := GenRandomBTCValDistInfo(r) + // a random number of finality providers + numFps := RandomInt(r, 10) + 1 + for i := uint64(0); i < numFps; i++ { + v, err := GenRandomFinalityProviderDistInfo(r) if err != nil { return nil, err } - rdc.AddBTCValDistInfo(v) + rdc.AddFinalityProviderDistInfo(v) } return rdc, nil } diff --git a/x/btcstaking/abci.go b/x/btcstaking/abci.go index 9c94b9481..f28c89f79 100644 --- a/x/btcstaking/abci.go +++ b/x/btcstaking/abci.go @@ -19,7 +19,7 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { k.RecordVotingPowerTable(ctx) // if BTC staking is activated, record reward distribution cache at the current height // TODO: consider merging RecordVotingPowerTable and RecordRewardDistCache so that we - // only need to perform one full scan over BTC validators/delegations + // only need to perform one full scan over finality providers/delegations if k.IsBTCStakingActivated(ctx) { k.RecordRewardDistCache(ctx) } diff --git a/x/btcstaking/client/cli/query.go b/x/btcstaking/client/cli/query.go index 856c1fce9..9d70db427 100644 --- a/x/btcstaking/client/cli/query.go +++ b/x/btcstaking/client/cli/query.go @@ -23,20 +23,20 @@ func GetQueryCmd(queryRoute string) *cobra.Command { } cmd.AddCommand(CmdQueryParams()) - cmd.AddCommand(CmdBTCValidators()) + cmd.AddCommand(CmdFinalityProviders()) cmd.AddCommand(CmdBTCDelegations()) - cmd.AddCommand(CmdBTCValidatorsAtHeight()) - cmd.AddCommand(CmdBTCValidatorPowerAtHeight()) + cmd.AddCommand(CmdFinalityProvidersAtHeight()) + cmd.AddCommand(CmdFinalityProviderPowerAtHeight()) cmd.AddCommand(CmdActivatedHeight()) - cmd.AddCommand(CmdBTCValidatorDelegations()) + cmd.AddCommand(CmdFinalityProviderDelegations()) return cmd } -func CmdBTCValidators() *cobra.Command { +func CmdFinalityProviders() *cobra.Command { cmd := &cobra.Command{ - Use: "btc-validators", - Short: "retrieve all BTC validators", + Use: "finality-providers", + Short: "retrieve all finality providers", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -48,7 +48,7 @@ func CmdBTCValidators() *cobra.Command { return err } - res, err := queryClient.BTCValidators(cmd.Context(), &types.QueryBTCValidatorsRequest{ + res, err := queryClient.FinalityProviders(cmd.Context(), &types.QueryFinalityProvidersRequest{ Pagination: pageReq, }) if err != nil { @@ -60,7 +60,7 @@ func CmdBTCValidators() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "btc-validators") + flags.AddPaginationFlagsToCmd(cmd, "finality-providers") return cmd } @@ -102,10 +102,10 @@ func CmdBTCDelegations() *cobra.Command { return cmd } -func CmdBTCValidatorPowerAtHeight() *cobra.Command { +func CmdFinalityProviderPowerAtHeight() *cobra.Command { cmd := &cobra.Command{ - Use: "btc-validator-power-at-height [val_btc_pk_hex] [height]", - Short: "get the voting power of a given BTC validator at a given height", + Use: "finality-provider-power-at-height [fp_btc_pk_hex] [height]", + Short: "get the voting power of a given finality provider at a given height", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -116,9 +116,9 @@ func CmdBTCValidatorPowerAtHeight() *cobra.Command { if err != nil { return err } - res, err := queryClient.BTCValidatorPowerAtHeight(cmd.Context(), &types.QueryBTCValidatorPowerAtHeightRequest{ - ValBtcPkHex: args[0], - Height: height, + res, err := queryClient.FinalityProviderPowerAtHeight(cmd.Context(), &types.QueryFinalityProviderPowerAtHeightRequest{ + FpBtcPkHex: args[0], + Height: height, }) if err != nil { return err @@ -136,7 +136,7 @@ func CmdBTCValidatorPowerAtHeight() *cobra.Command { func CmdActivatedHeight() *cobra.Command { cmd := &cobra.Command{ Use: "activated-height", - Short: "get activated height, i.e., the first height where there exists 1 BTC validator with voting power", + Short: "get activated height, i.e., the first height where there exists 1 finality provider with voting power", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -157,10 +157,10 @@ func CmdActivatedHeight() *cobra.Command { return cmd } -func CmdBTCValidatorsAtHeight() *cobra.Command { +func CmdFinalityProvidersAtHeight() *cobra.Command { cmd := &cobra.Command{ - Use: "btc-validators-at-height [height]", - Short: "retrieve all BTC validators at a given babylon height", + Use: "finality-providers-at-height [height]", + Short: "retrieve all finality providers at a given babylon height", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -177,7 +177,7 @@ func CmdBTCValidatorsAtHeight() *cobra.Command { return err } - res, err := queryClient.ActiveBTCValidatorsAtHeight(cmd.Context(), &types.QueryActiveBTCValidatorsAtHeightRequest{ + res, err := queryClient.ActiveFinalityProvidersAtHeight(cmd.Context(), &types.QueryActiveFinalityProvidersAtHeightRequest{ Height: height, Pagination: pageReq, }) @@ -190,15 +190,15 @@ func CmdBTCValidatorsAtHeight() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "btc-validators-at-height") + flags.AddPaginationFlagsToCmd(cmd, "finality-providers-at-height") return cmd } -func CmdBTCValidatorDelegations() *cobra.Command { +func CmdFinalityProviderDelegations() *cobra.Command { cmd := &cobra.Command{ - Use: "btc-validator-delegations [btc_val_pk_hex]", - Short: "retrieve all delegations under a given BTC validator", + Use: "finality-provider-delegations [fp_pk_hex]", + Short: "retrieve all delegations under a given finality provider", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -210,9 +210,9 @@ func CmdBTCValidatorDelegations() *cobra.Command { return err } - res, err := queryClient.BTCValidatorDelegations(cmd.Context(), &types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: args[0], - Pagination: pageReq, + res, err := queryClient.FinalityProviderDelegations(cmd.Context(), &types.QueryFinalityProviderDelegationsRequest{ + FpBtcPkHex: args[0], + Pagination: pageReq, }) if err != nil { return err @@ -223,7 +223,7 @@ func CmdBTCValidatorDelegations() *cobra.Command { } flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "btc-validator-delegations") + flags.AddPaginationFlagsToCmd(cmd, "finality-provider-delegations") return cmd } diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index df640e9ac..c99fd18fd 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -39,7 +39,7 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand( - NewCreateBTCValidatorCmd(), + NewCreateFinalityProvicerCmd(), NewCreateBTCDelegationCmd(), NewAddCovenantSigsCmd(), NewBTCUndelegateCmd(), @@ -48,13 +48,13 @@ func GetTxCmd() *cobra.Command { return cmd } -func NewCreateBTCValidatorCmd() *cobra.Command { +func NewCreateFinalityProvicerCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-validator [babylon_pk] [btc_pk] [pop]", + Use: "create-finality-provider [babylon_pk] [btc_pk] [pop]", Args: cobra.ExactArgs(3), - Short: "Create a BTC validator", + Short: "Create a finality provider", Long: strings.TrimSpace( - `Create a BTC validator.`, // TODO: example + `Create a finality provider.`, // TODO: example ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -106,7 +106,7 @@ func NewCreateBTCValidatorCmd() *cobra.Command { return err } - msg := types.MsgCreateBTCValidator{ + msg := types.MsgCreateFinalityProvider{ Signer: clientCtx.FromAddress.String(), Description: &description, Commission: &rate, @@ -120,10 +120,10 @@ func NewCreateBTCValidatorCmd() *cobra.Command { } fs := cmd.Flags() - fs.String(FlagMoniker, "", "The validator's (optional) moniker") - fs.String(FlagWebsite, "", "The validator's (optional) website") - fs.String(FlagSecurityContact, "", "The validator's (optional) security contact email") - fs.String(FlagDetails, "", "The validator's (optional) details") + fs.String(FlagMoniker, "", "The finality provider's (optional) moniker") + fs.String(FlagWebsite, "", "The finality provider's (optional) website") + fs.String(FlagSecurityContact, "", "The finality provider's (optional) security contact email") + fs.String(FlagDetails, "", "The finality provider's (optional) details") fs.String(FlagIdentity, "", "The (optional) identity signature (ex. UPort or Keybase)") fs.String(FlagCommissionRate, "0", "The initial commission rate percentage") @@ -134,7 +134,7 @@ func NewCreateBTCValidatorCmd() *cobra.Command { func NewCreateBTCDelegationCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-btc-delegation [babylon_pk] [btc_pk] [pop] [staking_tx_info] [val_pk] [staking_time] [staking_value] [slashing_tx] [delegator_slashing_sig] [unbonding_tx] [unbonding_slashing_tx] [unbonding_time] [unbonding_value] [delegator_unbonding_slashing_sig]", + Use: "create-btc-delegation [babylon_pk] [btc_pk] [pop] [staking_tx_info] [fp_pk] [staking_time] [staking_value] [slashing_tx] [delegator_slashing_sig] [unbonding_tx] [unbonding_slashing_tx] [unbonding_time] [unbonding_value] [delegator_unbonding_slashing_sig]", Args: cobra.ExactArgs(14), Short: "Create a BTC delegation", Long: strings.TrimSpace( @@ -175,9 +175,9 @@ func NewCreateBTCDelegationCmd() *cobra.Command { return err } - // TODO: Support multiple validators - // get validator PK - valPK, err := bbn.NewBIP340PubKeyFromHex(args[4]) + // TODO: Support multiple finality providers + // get finality provider PK + fpPK, err := bbn.NewBIP340PubKeyFromHex(args[4]) if err != nil { return err } @@ -238,7 +238,7 @@ func NewCreateBTCDelegationCmd() *cobra.Command { Signer: clientCtx.FromAddress.String(), BabylonPk: &babylonPK, BtcPk: btcPK, - ValBtcPkList: []bbn.BIP340PubKey{*valPK}, + FpBtcPkList: []bbn.BIP340PubKey{*fpPK}, Pop: pop, StakingTime: uint32(stakingTime), StakingValue: int64(stakingValue), diff --git a/x/btcstaking/keeper/btc_delegators.go b/x/btcstaking/keeper/btc_delegators.go index fe07edc40..8202b868a 100644 --- a/x/btcstaking/keeper/btc_delegators.go +++ b/x/btcstaking/keeper/btc_delegators.go @@ -27,11 +27,11 @@ func (k Keeper) AddBTCDelegation(ctx context.Context, btcDel *types.BTCDelegatio return err } - // for each BTC validator the delegation restakes to, update its index - for _, valBTCPK := range btcDel.ValBtcPkList { + // for each finality provider the delegation restakes to, update its index + for _, fpBTCPK := range btcDel.FpBtcPkList { var btcDelIndex = types.NewBTCDelegatorDelegationIndex() - if k.hasBTCDelegatorDelegations(ctx, &valBTCPK, btcDel.BtcPk) { - btcDelIndex, err = k.getBTCDelegatorDelegationIndex(ctx, &valBTCPK, btcDel.BtcPk) + if k.hasBTCDelegatorDelegations(ctx, &fpBTCPK, btcDel.BtcPk) { + btcDelIndex, err = k.getBTCDelegatorDelegationIndex(ctx, &fpBTCPK, btcDel.BtcPk) if err != nil { // this can only be a programming error panic(fmt.Errorf("failed to get BTC delegations while hasBTCDelegatorDelegations returns true")) @@ -43,7 +43,7 @@ func (k Keeper) AddBTCDelegation(ctx context.Context, btcDel *types.BTCDelegatio return types.ErrInvalidStakingTx.Wrapf(err.Error()) } // save the index - store := k.btcDelegatorStore(ctx, &valBTCPK) + store := k.btcDelegatorStore(ctx, &fpBTCPK) delBTCPKBytes := btcDel.BtcPk.MustMarshal() btcDelIndexBytes := k.cdc.MustMarshal(btcDelIndex) store.Set(delBTCPKBytes, btcDelIndexBytes) @@ -55,7 +55,7 @@ func (k Keeper) AddBTCDelegation(ctx context.Context, btcDel *types.BTCDelegatio return nil } -// updateBTCDelegation updates an existing BTC delegation w.r.t. validator BTC PK, delegator BTC PK, +// updateBTCDelegation updates an existing BTC delegation w.r.t. finality provider BTC PK, delegator BTC PK, // and staking tx hash by using a given function func (k Keeper) updateBTCDelegation( ctx context.Context, @@ -97,9 +97,9 @@ func (k Keeper) AddUndelegationToBTCDelegation( // AddCovenantSigsToBTCDelegation adds covenant signatures to a BTC delegation // with the given staking tx hash, including -// - a list of adaptor signatures over slashing tx, each encrypted by a restaked validator's PK +// - a list of adaptor signatures over slashing tx, each encrypted by a restaked finality provider's PK // - a Schnorr signature over unbonding tx -// - a list of adaptor signatures over unbonding slashing tx, each encrypted by a restaked validator's PK +// - a list of adaptor signatures over unbonding slashing tx, each encrypted by a restaked finality provider's PK func (k Keeper) AddCovenantSigsToBTCDelegation( ctx context.Context, stakingTxHash string, @@ -140,27 +140,27 @@ func (k Keeper) AddCovenantSigsToBTCDelegation( return k.updateBTCDelegation(ctx, stakingTxHash, addCovenantSig) } -// hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given BTC validator -func (k Keeper) hasBTCDelegatorDelegations(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { - valBTCPKBytes := valBTCPK.MustMarshal() +// hasBTCDelegatorDelegations checks if the given BTC delegator has any BTC delegations under a given finality provider +func (k Keeper) hasBTCDelegatorDelegations(ctx context.Context, fpBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) bool { + fpBTCPKBytes := fpBTCPK.MustMarshal() delBTCPKBytes := delBTCPK.MustMarshal() - if !k.HasBTCValidator(ctx, valBTCPKBytes) { + if !k.HasFinalityProvider(ctx, fpBTCPKBytes) { return false } - store := k.btcDelegatorStore(ctx, valBTCPK) + store := k.btcDelegatorStore(ctx, fpBTCPK) return store.Has(delBTCPKBytes) } -// getBTCDelegatorDelegationIndex gets the BTC delegation index with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegatorDelegationIndex(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegationIndex, error) { - valBTCPKBytes := valBTCPK.MustMarshal() +// getBTCDelegatorDelegationIndex gets the BTC delegation index with a given BTC PK under a given finality provider +func (k Keeper) getBTCDelegatorDelegationIndex(ctx context.Context, fpBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegationIndex, error) { + fpBTCPKBytes := fpBTCPK.MustMarshal() delBTCPKBytes := delBTCPK.MustMarshal() - store := k.btcDelegatorStore(ctx, valBTCPK) + store := k.btcDelegatorStore(ctx, fpBTCPK) - // ensure the BTC validator exists - if !k.HasBTCValidator(ctx, valBTCPKBytes) { - return nil, types.ErrBTCValNotFound + // ensure the finality provider exists + if !k.HasFinalityProvider(ctx, fpBTCPKBytes) { + return nil, types.ErrFpNotFound } // ensure BTC delegator exists @@ -174,9 +174,9 @@ func (k Keeper) getBTCDelegatorDelegationIndex(ctx context.Context, valBTCPK *bb return &btcDelIndex, nil } -// getBTCDelegatorDelegations gets the BTC delegations with a given BTC PK under a given BTC validator -func (k Keeper) getBTCDelegatorDelegations(ctx context.Context, valBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { - btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, valBTCPK, delBTCPK) +// getBTCDelegatorDelegations gets the BTC delegations with a given BTC PK under a given finality provider +func (k Keeper) getBTCDelegatorDelegations(ctx context.Context, fpBTCPK *bbn.BIP340PubKey, delBTCPK *bbn.BIP340PubKey) (*types.BTCDelegatorDelegations, error) { + btcDelIndex, err := k.getBTCDelegatorDelegationIndex(ctx, fpBTCPK, delBTCPK) if err != nil { return nil, err } @@ -206,11 +206,11 @@ func (k Keeper) GetBTCDelegation(ctx context.Context, stakingTxHashStr string) ( } // btcDelegatorStore returns the KVStore of the BTC delegators -// prefix: BTCDelegatorKey || validator's Bitcoin secp256k1 PK +// prefix: BTCDelegatorKey || finality provider's Bitcoin secp256k1 PK // key: delegator's Bitcoin secp256k1 PK // value: BTCDelegatorDelegationIndex (a list of BTCDelegations' staking tx hashes) -func (k Keeper) btcDelegatorStore(ctx context.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { +func (k Keeper) btcDelegatorStore(ctx context.Context, fpBTCPK *bbn.BIP340PubKey) prefix.Store { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) delegationStore := prefix.NewStore(storeAdapter, types.BTCDelegatorKey) - return prefix.NewStore(delegationStore, valBTCPK.MustMarshal()) + return prefix.NewStore(delegationStore, fpBTCPK.MustMarshal()) } diff --git a/x/btcstaking/keeper/btc_validators.go b/x/btcstaking/keeper/btc_validators.go index 4d41c82b0..01b7e0c77 100644 --- a/x/btcstaking/keeper/btc_validators.go +++ b/x/btcstaking/keeper/btc_validators.go @@ -11,56 +11,56 @@ import ( "github.com/babylonchain/babylon/x/btcstaking/types" ) -// SetBTCValidator adds the given BTC validator to KVStore -func (k Keeper) SetBTCValidator(ctx context.Context, btcVal *types.BTCValidator) { - store := k.btcValidatorStore(ctx) - btcValBytes := k.cdc.MustMarshal(btcVal) - store.Set(btcVal.BtcPk.MustMarshal(), btcValBytes) +// SetFinalityProvider adds the given finality provider to KVStore +func (k Keeper) SetFinalityProvider(ctx context.Context, fp *types.FinalityProvider) { + store := k.finalityProviderStore(ctx) + fpBytes := k.cdc.MustMarshal(fp) + store.Set(fp.BtcPk.MustMarshal(), fpBytes) } -// HasBTCValidator checks if the BTC validator exists -func (k Keeper) HasBTCValidator(ctx context.Context, valBTCPK []byte) bool { - store := k.btcValidatorStore(ctx) - return store.Has(valBTCPK) +// HasFinalityProvider checks if the finality provider exists +func (k Keeper) HasFinalityProvider(ctx context.Context, fpBTCPK []byte) bool { + store := k.finalityProviderStore(ctx) + return store.Has(fpBTCPK) } -// GetBTCValidator gets the BTC validator with the given validator Bitcoin PK -func (k Keeper) GetBTCValidator(ctx context.Context, valBTCPK []byte) (*types.BTCValidator, error) { - store := k.btcValidatorStore(ctx) - if !k.HasBTCValidator(ctx, valBTCPK) { - return nil, types.ErrBTCValNotFound +// GetFinalityProvider gets the finality provider with the given finality provider Bitcoin PK +func (k Keeper) GetFinalityProvider(ctx context.Context, fpBTCPK []byte) (*types.FinalityProvider, error) { + store := k.finalityProviderStore(ctx) + if !k.HasFinalityProvider(ctx, fpBTCPK) { + return nil, types.ErrFpNotFound } - btcValBytes := store.Get(valBTCPK) - var btcVal types.BTCValidator - k.cdc.MustUnmarshal(btcValBytes, &btcVal) - return &btcVal, nil + fpBytes := store.Get(fpBTCPK) + var fp types.FinalityProvider + k.cdc.MustUnmarshal(fpBytes, &fp) + return &fp, nil } -// SlashBTCValidator slashes a BTC validator with the given PK -// A slashed BTC validator will not have voting power -func (k Keeper) SlashBTCValidator(ctx context.Context, valBTCPK []byte) error { - btcVal, err := k.GetBTCValidator(ctx, valBTCPK) +// SlashFinalityProvider slashes a finality provider with the given PK +// A slashed finality provider will not have voting power +func (k Keeper) SlashFinalityProvider(ctx context.Context, fpBTCPK []byte) error { + fp, err := k.GetFinalityProvider(ctx, fpBTCPK) if err != nil { return err } - if btcVal.IsSlashed() { - return types.ErrBTCValAlreadySlashed + if fp.IsSlashed() { + return types.ErrFpAlreadySlashed } - btcVal.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) + fp.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) btcTip := k.btclcKeeper.GetTipInfo(ctx) if btcTip == nil { panic(fmt.Errorf("failed to get current BTC tip")) } - btcVal.SlashedBtcHeight = btcTip.Height - k.SetBTCValidator(ctx, btcVal) + fp.SlashedBtcHeight = btcTip.Height + k.SetFinalityProvider(ctx, fp) return nil } -// btcValidatorStore returns the KVStore of the BTC validator set -// prefix: BTCValidatorKey +// finalityProviderStore returns the KVStore of the finality provider set +// prefix: FinalityProviderKey // key: Bitcoin secp256k1 PK -// value: BTCValidator object -func (k Keeper) btcValidatorStore(ctx context.Context) prefix.Store { +// value: FinalityProvider object +func (k Keeper) finalityProviderStore(ctx context.Context) prefix.Store { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - return prefix.NewStore(storeAdapter, types.BTCValidatorKey) + return prefix.NewStore(storeAdapter, types.FinalityProviderKey) } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 819d454b6..6791483b2 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -17,54 +17,54 @@ import ( var _ types.QueryServer = Keeper{} -// BTCValidators returns a paginated list of all Babylon maintained validators -func (k Keeper) BTCValidators(ctx context.Context, req *types.QueryBTCValidatorsRequest) (*types.QueryBTCValidatorsResponse, error) { +// FinalityProviders returns a paginated list of all Babylon maintained finality providers +func (k Keeper) FinalityProviders(ctx context.Context, req *types.QueryFinalityProvidersRequest) (*types.QueryFinalityProvidersResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } sdkCtx := sdk.UnwrapSDKContext(ctx) - store := k.btcValidatorStore(sdkCtx) + store := k.finalityProviderStore(sdkCtx) - var btcValidators []*types.BTCValidator + var finalityProviders []*types.FinalityProvider pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { - var btcValidator types.BTCValidator - k.cdc.MustUnmarshal(value, &btcValidator) - btcValidators = append(btcValidators, &btcValidator) + var finalityProvider types.FinalityProvider + k.cdc.MustUnmarshal(value, &finalityProvider) + finalityProviders = append(finalityProviders, &finalityProvider) return nil }) if err != nil { return nil, err } - return &types.QueryBTCValidatorsResponse{BtcValidators: btcValidators, Pagination: pageRes}, nil + return &types.QueryFinalityProvidersResponse{FinalityProviders: finalityProviders, Pagination: pageRes}, nil } -// BTCValidator returns the validator with the specified validator BTC PK -func (k Keeper) BTCValidator(ctx context.Context, req *types.QueryBTCValidatorRequest) (*types.QueryBTCValidatorResponse, error) { +// FinalityProvider returns the finality provider with the specified finality provider BTC PK +func (k Keeper) FinalityProvider(ctx context.Context, req *types.QueryFinalityProviderRequest) (*types.QueryFinalityProviderResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - if len(req.ValBtcPkHex) == 0 { + if len(req.FpBtcPkHex) == 0 { return nil, errorsmod.Wrapf( - sdkerrors.ErrInvalidRequest, "validator BTC public key cannot be empty") + sdkerrors.ErrInvalidRequest, "finality provider BTC public key cannot be empty") } - valPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { return nil, err } sdkCtx := sdk.UnwrapSDKContext(ctx) - val, err := k.GetBTCValidator(sdkCtx, valPK.MustMarshal()) + fp, err := k.GetFinalityProvider(sdkCtx, fpPK.MustMarshal()) if err != nil { return nil, err } - return &types.QueryBTCValidatorResponse{BtcValidator: val}, nil + return &types.QueryFinalityProviderResponse{FinalityProvider: fp}, nil } // BTCDelegations returns all BTC delegations under a given status @@ -106,34 +106,34 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio }, nil } -// BTCValidatorPowerAtHeight returns the voting power of the specified validator +// FinalityProviderPowerAtHeight returns the voting power of the specified finality provider // at the provided Babylon height -func (k Keeper) BTCValidatorPowerAtHeight(ctx context.Context, req *types.QueryBTCValidatorPowerAtHeightRequest) (*types.QueryBTCValidatorPowerAtHeightResponse, error) { +func (k Keeper) FinalityProviderPowerAtHeight(ctx context.Context, req *types.QueryFinalityProviderPowerAtHeightRequest) (*types.QueryFinalityProviderPowerAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal finality provider BTC PK hex: %v", err) } sdkCtx := sdk.UnwrapSDKContext(ctx) - power := k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), req.Height) + power := k.GetVotingPower(sdkCtx, fpBTCPK.MustMarshal(), req.Height) - return &types.QueryBTCValidatorPowerAtHeightResponse{VotingPower: power}, nil + return &types.QueryFinalityProviderPowerAtHeightResponse{VotingPower: power}, nil } -// BTCValidatorCurrentPower returns the voting power of the specified validator +// FinalityProviderCurrentPower returns the voting power of the specified finality provider // at the current height -func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBTCValidatorCurrentPowerRequest) (*types.QueryBTCValidatorCurrentPowerResponse, error) { +func (k Keeper) FinalityProviderCurrentPower(ctx context.Context, req *types.QueryFinalityProviderCurrentPowerRequest) (*types.QueryFinalityProviderCurrentPowerResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal finality provider BTC PK hex: %v", err) } sdkCtx := sdk.UnwrapSDKContext(ctx) @@ -142,20 +142,20 @@ func (k Keeper) BTCValidatorCurrentPower(ctx context.Context, req *types.QueryBT // if voting power table is recorded at the current height, use this voting power if k.HasVotingPowerTable(sdkCtx, curHeight) { - power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) + power = k.GetVotingPower(sdkCtx, fpBTCPK.MustMarshal(), curHeight) } else { // NOTE: it's possible that the voting power is not recorded at the current height, // e.g., `EndBlock` is not reached yet // in this case, we use the last height curHeight -= 1 - power = k.GetVotingPower(sdkCtx, valBTCPK.MustMarshal(), curHeight) + power = k.GetVotingPower(sdkCtx, fpBTCPK.MustMarshal(), curHeight) } - return &types.QueryBTCValidatorCurrentPowerResponse{Height: curHeight, VotingPower: power}, nil + return &types.QueryFinalityProviderCurrentPowerResponse{Height: curHeight, VotingPower: power}, nil } -// ActiveBTCValidatorsAtHeight returns the active BTC validators at the provided height -func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.QueryActiveBTCValidatorsAtHeightRequest) (*types.QueryActiveBTCValidatorsAtHeightResponse, error) { +// ActiveFinalityProvidersAtHeight returns the active finality providers at the provided height +func (k Keeper) ActiveFinalityProvidersAtHeight(ctx context.Context, req *types.QueryActiveFinalityProvidersAtHeightRequest) (*types.QueryActiveFinalityProvidersAtHeightResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } @@ -163,23 +163,23 @@ func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.Quer sdkCtx := sdk.UnwrapSDKContext(ctx) store := k.votingPowerStore(sdkCtx, req.Height) - var btcValidatorsWithMeta []*types.BTCValidatorWithMeta + var finalityProvidersWithMeta []*types.FinalityProviderWithMeta pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { - btcValidator, err := k.GetBTCValidator(sdkCtx, key) + finalityProvider, err := k.GetFinalityProvider(sdkCtx, key) if err != nil { return err } votingPower := k.GetVotingPower(sdkCtx, key, req.Height) if votingPower > 0 { - btcValidatorWithMeta := types.BTCValidatorWithMeta{ - BtcPk: btcValidator.BtcPk, + finalityProviderWithMeta := types.FinalityProviderWithMeta{ + BtcPk: finalityProvider.BtcPk, Height: req.Height, VotingPower: votingPower, - SlashedBabylonHeight: btcValidator.SlashedBabylonHeight, - SlashedBtcHeight: btcValidator.SlashedBtcHeight, + SlashedBabylonHeight: finalityProvider.SlashedBabylonHeight, + SlashedBtcHeight: finalityProvider.SlashedBtcHeight, } - btcValidatorsWithMeta = append(btcValidatorsWithMeta, &btcValidatorWithMeta) + finalityProvidersWithMeta = append(finalityProvidersWithMeta, &finalityProviderWithMeta) } return nil @@ -188,7 +188,7 @@ func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.Quer return nil, err } - return &types.QueryActiveBTCValidatorsAtHeightResponse{BtcValidators: btcValidatorsWithMeta, Pagination: pageRes}, nil + return &types.QueryActiveFinalityProvidersAtHeightResponse{FinalityProviders: finalityProvidersWithMeta, Pagination: pageRes}, nil } // ActivatedHeight returns the Babylon height in which the BTC Staking protocol was enabled @@ -206,24 +206,24 @@ func (k Keeper) ActivatedHeight(ctx context.Context, req *types.QueryActivatedHe return &types.QueryActivatedHeightResponse{Height: activatedHeight}, nil } -// BTCValidatorDelegations returns all the delegations of the provided validator filtered by the provided status. -func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTCValidatorDelegationsRequest) (*types.QueryBTCValidatorDelegationsResponse, error) { +// FinalityProviderDelegations returns all the delegations of the provided finality provider filtered by the provided status. +func (k Keeper) FinalityProviderDelegations(ctx context.Context, req *types.QueryFinalityProviderDelegationsRequest) (*types.QueryFinalityProviderDelegationsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - if len(req.ValBtcPkHex) == 0 { + if len(req.FpBtcPkHex) == 0 { return nil, errorsmod.Wrapf( - sdkerrors.ErrInvalidRequest, "validator BTC public key cannot be empty") + sdkerrors.ErrInvalidRequest, "finality provider BTC public key cannot be empty") } - valPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { return nil, err } sdkCtx := sdk.UnwrapSDKContext(ctx) - btcDelStore := k.btcDelegatorStore(sdkCtx, valPK) + btcDelStore := k.btcDelegatorStore(sdkCtx, fpPK) btcDels := []*types.BTCDelegatorDelegations{} pageRes, err := query.Paginate(btcDelStore, req.Pagination, func(key, value []byte) error { @@ -232,7 +232,7 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC return err } - curBTCDels, err := k.getBTCDelegatorDelegations(sdkCtx, valPK, delBTCPK) + curBTCDels, err := k.getBTCDelegatorDelegations(sdkCtx, fpPK, delBTCPK) if err != nil { return err } @@ -244,7 +244,7 @@ func (k Keeper) BTCValidatorDelegations(ctx context.Context, req *types.QueryBTC return nil, err } - return &types.QueryBTCValidatorDelegationsResponse{BtcDelegatorDelegations: btcDels, Pagination: pageRes}, nil + return &types.QueryFinalityProviderDelegationsResponse{BtcDelegatorDelegations: btcDels, Pagination: pageRes}, nil } // BTCDelegation returns existing btc delegation by staking tx hash @@ -283,7 +283,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation return &types.QueryBTCDelegationResponse{ BtcPk: btcDel.BtcPk, - ValBtcPkList: btcDel.ValBtcPkList, + FpBtcPkList: btcDel.FpBtcPkList, StartHeight: btcDel.StartHeight, EndHeight: btcDel.EndHeight, TotalSat: btcDel.TotalSat, diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index b9806b489..97a1641b2 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -35,9 +35,9 @@ func FuzzActivatedHeight(f *testing.F) { require.Error(t, err) randomActivatedHeight := datagen.RandomInt(r, 100) + 1 - btcVal, err := datagen.GenRandomBTCValidator(r) + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomActivatedHeight, uint64(10)) + keeper.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), randomActivatedHeight, uint64(10)) // now it's activated resp, err := keeper.ActivatedHeight(ctx, &types.QueryActivatedHeightRequest{}) @@ -46,7 +46,7 @@ func FuzzActivatedHeight(f *testing.F) { }) } -func FuzzBTCValidators(f *testing.F) { +func FuzzFinalityProviders(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -55,19 +55,19 @@ func FuzzBTCValidators(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) - // Generate random btc validators and add them to kv store - btcValsMap := make(map[string]*types.BTCValidator) + // Generate random finality providers and add them to kv store + fpsMap := make(map[string]*types.FinalityProvider) for i := 0; i < int(datagen.RandomInt(r, 10)+1); i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcValsMap[btcVal.BtcPk.MarshalHex()] = btcVal + keeper.SetFinalityProvider(ctx, fp) + fpsMap[fp.BtcPk.MarshalHex()] = fp } - numOfBTCValsInStore := len(btcValsMap) + numOfFpsInStore := len(fpsMap) // Test nil request - resp, err := keeper.BTCValidators(ctx, nil) + resp, err := keeper.FinalityProviders(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -76,16 +76,16 @@ func FuzzBTCValidators(f *testing.F) { } // Generate a page request with a limit and a nil key - limit := datagen.RandomInt(r, numOfBTCValsInStore) + 1 + limit := datagen.RandomInt(r, numOfFpsInStore) + 1 pagination := constructRequestWithLimit(r, limit) // Generate the initial query - req := types.QueryBTCValidatorsRequest{Pagination: pagination} - // Construct a mapping from the btc vals found to a boolean value - // Will be used later to evaluate whether all the btc vals were returned - btcValsFound := make(map[string]bool, 0) + req := types.QueryFinalityProvidersRequest{Pagination: pagination} + // Construct a mapping from the finality providers found to a boolean value + // Will be used later to evaluate whether all the finality providers were returned + fpsFound := make(map[string]bool, 0) - for i := uint64(0); i < uint64(numOfBTCValsInStore); i += limit { - resp, err = keeper.BTCValidators(ctx, &req) + for i := uint64(0); i < uint64(numOfFpsInStore); i += limit { + resp, err = keeper.FinalityProviders(ctx, &req) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -93,26 +93,26 @@ func FuzzBTCValidators(f *testing.F) { t.Fatalf("Valid request led to a nil response") } - for _, val := range resp.BtcValidators { + for _, fp := range resp.FinalityProviders { // Check if the pk exists in the map - if _, ok := btcValsMap[val.BtcPk.MarshalHex()]; !ok { - t.Fatalf("rpc returned a val that was not created") + if _, ok := fpsMap[fp.BtcPk.MarshalHex()]; !ok { + t.Fatalf("rpc returned a finality provider that was not created") } - btcValsFound[val.BtcPk.MarshalHex()] = true + fpsFound[fp.BtcPk.MarshalHex()] = true } // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorsRequest{Pagination: pagination} + req = types.QueryFinalityProvidersRequest{Pagination: pagination} } - if len(btcValsFound) != len(btcValsMap) { - t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcValsFound), len(btcValsMap)) + if len(fpsFound) != len(fpsMap) { + t.Errorf("Some finality providers were missed. Got %d while %d were expected", len(fpsFound), len(fpsMap)) } }) } -func FuzzBTCValidator(f *testing.F) { +func FuzzFinalityProvider(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -120,25 +120,25 @@ func FuzzBTCValidator(f *testing.F) { keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) - // Generate random btc validators and add them to kv store - btcValsMap := make(map[string]*types.BTCValidator) + // Generate random finality providers and add them to kv store + fpsMap := make(map[string]*types.FinalityProvider) for i := 0; i < int(datagen.RandomInt(r, 10)+1); i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcValsMap[btcVal.BtcPk.MarshalHex()] = btcVal + keeper.SetFinalityProvider(ctx, fp) + fpsMap[fp.BtcPk.MarshalHex()] = fp } // Test nil request - resp, err := keeper.BTCValidators(ctx, nil) + resp, err := keeper.FinalityProvider(ctx, nil) require.Error(t, err) require.Nil(t, resp) - for k, v := range btcValsMap { + for k, v := range fpsMap { // Generate a request with a valid key - req := types.QueryBTCValidatorRequest{ValBtcPkHex: k} - resp, err := keeper.BTCValidator(ctx, &req) + req := types.QueryFinalityProviderRequest{FpBtcPkHex: k} + resp, err := keeper.FinalityProvider(ctx, &req) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -147,18 +147,18 @@ func FuzzBTCValidator(f *testing.F) { } // check keys from map matches those in returned response - require.Equal(t, v.BtcPk.MarshalHex(), resp.BtcValidator.BtcPk.MarshalHex()) - require.Equal(t, v.BabylonPk, resp.BtcValidator.BabylonPk) + require.Equal(t, v.BtcPk.MarshalHex(), resp.FinalityProvider.BtcPk.MarshalHex()) + require.Equal(t, v.BabylonPk, resp.FinalityProvider.BabylonPk) } - // check some random non exsisting guy - btcVal, err := datagen.GenRandomBTCValidator(r) + // check some random non-existing guy + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - req := types.QueryBTCValidatorRequest{ValBtcPkHex: btcVal.BtcPk.MarshalHex()} - respNonExists, err := keeper.BTCValidator(ctx, &req) + req := types.QueryFinalityProviderRequest{FpBtcPkHex: fp.BtcPk.MarshalHex()} + respNonExists, err := keeper.FinalityProvider(ctx, &req) require.Error(t, err) require.Nil(t, respNonExists) - require.True(t, errors.Is(err, types.ErrBTCValNotFound)) + require.True(t, errors.Is(err, types.ErrFpNotFound)) }) } @@ -188,31 +188,31 @@ func FuzzPendingBTCDelegations(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // Generate a random number of BTC validators - numBTCVals := datagen.RandomInt(r, 5) + 1 - btcVals := []*types.BTCValidator{} - for i := uint64(0); i < numBTCVals; i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + // Generate a random number of finality providers + numFps := datagen.RandomInt(r, 5) + 1 + fps := []*types.FinalityProvider{} + for i := uint64(0); i < numFps; i++ { + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcVals = append(btcVals, btcVal) + keeper.SetFinalityProvider(ctx, fp) + fps = append(fps, fp) } - // Generate a random number of BTC delegations under each validator + // Generate a random number of BTC delegations under each finality provider startHeight := datagen.RandomInt(r, 100) + 1 btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: startHeight}).AnyTimes() endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 numBTCDels := datagen.RandomInt(r, 10) + 1 pendingBtcDelsMap := make(map[string]*types.BTCDelegation) - for _, btcVal := range btcVals { + for _, fp := range fps { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*btcVal.BtcPk}, + []bbn.BIP340PubKey{*fp.BtcPk}, delSK, covenantSKs, covenantQuorum, @@ -266,7 +266,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { }) } -func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { +func FuzzFinalityProviderPowerAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -274,27 +274,27 @@ func FuzzBTCValidatorVotingPowerAtHeight(f *testing.F) { // Setup keeper and context keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) - // random BTC validator - btcVal, err := datagen.GenRandomBTCValidator(r) + // random finality provider + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - // add this BTC validator - keeper.SetBTCValidator(ctx, btcVal) + // add this finality provider + keeper.SetFinalityProvider(ctx, fp) // set random voting power at random height randomHeight := datagen.RandomInt(r, 100) + 1 randomPower := datagen.RandomInt(r, 100) + 1 - keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) + keeper.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), randomHeight, randomPower) - req := &types.QueryBTCValidatorPowerAtHeightRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - Height: randomHeight, + req := &types.QueryFinalityProviderPowerAtHeightRequest{ + FpBtcPkHex: fp.BtcPk.MarshalHex(), + Height: randomHeight, } - resp, err := keeper.BTCValidatorPowerAtHeight(ctx, req) + resp, err := keeper.FinalityProviderPowerAtHeight(ctx, req) require.NoError(t, err) require.Equal(t, randomPower, resp.VotingPower) }) } -func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { +func FuzzFinalityProviderCurrentVotingPower(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -302,22 +302,22 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { // Setup keeper and context keeper, ctx := testkeeper.BTCStakingKeeper(t, nil, nil) - // random BTC validator - btcVal, err := datagen.GenRandomBTCValidator(r) + // random finality provider + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - // add this BTC validator - keeper.SetBTCValidator(ctx, btcVal) + // add this finality provider + keeper.SetFinalityProvider(ctx, fp) // set random voting power at random height randomHeight := datagen.RandomInt(r, 100) + 1 ctx = datagen.WithCtxHeight(ctx, randomHeight) randomPower := datagen.RandomInt(r, 100) + 1 - keeper.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), randomHeight, randomPower) + keeper.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), randomHeight, randomPower) // assert voting power at current height - req := &types.QueryBTCValidatorCurrentPowerRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), + req := &types.QueryFinalityProviderCurrentPowerRequest{ + FpBtcPkHex: fp.BtcPk.MarshalHex(), } - resp, err := keeper.BTCValidatorCurrentPower(ctx, req) + resp, err := keeper.FinalityProviderCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight, resp.Height) require.Equal(t, randomPower, resp.VotingPower) @@ -325,21 +325,21 @@ func FuzzBTCValidatorCurrentVotingPower(f *testing.F) { // if height increments but voting power hasn't recorded yet, then // we need to return the height and voting power at the last height ctx = datagen.WithCtxHeight(ctx, randomHeight+1) - resp, err = keeper.BTCValidatorCurrentPower(ctx, req) + resp, err = keeper.FinalityProviderCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight, resp.Height) require.Equal(t, randomPower, resp.VotingPower) // but no more ctx = datagen.WithCtxHeight(ctx, randomHeight+2) - resp, err = keeper.BTCValidatorCurrentPower(ctx, req) + resp, err = keeper.FinalityProviderCurrentPower(ctx, req) require.NoError(t, err) require.Equal(t, randomHeight+1, resp.Height) require.Equal(t, uint64(0), resp.VotingPower) }) } -func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { +func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -360,24 +360,24 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // Generate a random batch of validators - var btcVals []*types.BTCValidator - numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 1 - numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) - for i := uint64(0); i < numBTCVals; i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + // Generate a random batch of finality providers + var fps []*types.FinalityProvider + numFpsWithVotingPower := datagen.RandomInt(r, 10) + 1 + numFps := numFpsWithVotingPower + datagen.RandomInt(r, 10) + for i := uint64(0); i < numFps; i++ { + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcVals = append(btcVals, btcVal) + keeper.SetFinalityProvider(ctx, fp) + fps = append(fps, fp) } - // For numBTCValsWithVotingPower validators, generate a random number of BTC delegations + // For numFpsWithVotingPower finality providers, generate a random number of BTC delegations numBTCDels := datagen.RandomInt(r, 10) + 1 babylonHeight := datagen.RandomInt(r, 10) + 1 - btcValsWithVotingPowerMap := make(map[string]*types.BTCValidator) - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - valBTCPK := btcVals[i].BtcPk - btcValsWithVotingPowerMap[valBTCPK.MarshalHex()] = btcVals[i] + fpsWithVotingPowerMap := make(map[string]*types.FinalityProvider) + for i := uint64(0); i < numFpsWithVotingPower; i++ { + fpBTCPK := fps[i].BtcPk + fpsWithVotingPowerMap[fpBTCPK.MarshalHex()] = fps[i] var totalVotingPower uint64 for j := uint64(0); j < numBTCDels; j++ { @@ -386,7 +386,7 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*valBTCPK}, + []bbn.BIP340PubKey{*fpBTCPK}, delSK, covenantSKs, covenantQuorum, @@ -400,11 +400,11 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { totalVotingPower += btcDel.TotalSat } - keeper.SetVotingPower(ctx, valBTCPK.MustMarshal(), babylonHeight, totalVotingPower) + keeper.SetVotingPower(ctx, fpBTCPK.MustMarshal(), babylonHeight, totalVotingPower) } // Test nil request - resp, err := keeper.ActiveBTCValidatorsAtHeight(ctx, nil) + resp, err := keeper.ActiveFinalityProvidersAtHeight(ctx, nil) if resp != nil { t.Errorf("Nil input led to a non-nil response") } @@ -413,16 +413,16 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { } // Generate a page request with a limit and a nil key - limit := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) + 1 + limit := datagen.RandomInt(r, int(numFpsWithVotingPower)) + 1 pagination := constructRequestWithLimit(r, limit) // Generate the initial query - req := types.QueryActiveBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} - // Construct a mapping from the btc vals found to a boolean value - // Will be used later to evaluate whether all the btc vals were returned - btcValsFound := make(map[string]bool, 0) + req := types.QueryActiveFinalityProvidersAtHeightRequest{Height: babylonHeight, Pagination: pagination} + // Construct a mapping from the finality providers found to a boolean value + // Will be used later to evaluate whether all the finality providers were returned + fpsFound := make(map[string]bool, 0) - for i := uint64(0); i < numBTCValsWithVotingPower; i += limit { - resp, err = keeper.ActiveBTCValidatorsAtHeight(ctx, &req) + for i := uint64(0); i < numFpsWithVotingPower; i += limit { + resp, err = keeper.ActiveFinalityProvidersAtHeight(ctx, &req) if err != nil { t.Errorf("Valid request led to an error %s", err) } @@ -430,26 +430,26 @@ func FuzzActiveBTCValidatorsAtHeight(f *testing.F) { t.Fatalf("Valid request led to a nil response") } - for _, val := range resp.BtcValidators { + for _, fp := range resp.FinalityProviders { // Check if the pk exists in the map - if _, ok := btcValsWithVotingPowerMap[val.BtcPk.MarshalHex()]; !ok { - t.Fatalf("rpc returned a val that was not created") + if _, ok := fpsWithVotingPowerMap[fp.BtcPk.MarshalHex()]; !ok { + t.Fatalf("rpc returned a finality provider that was not created") } - btcValsFound[val.BtcPk.MarshalHex()] = true + fpsFound[fp.BtcPk.MarshalHex()] = true } // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryActiveBTCValidatorsAtHeightRequest{Height: babylonHeight, Pagination: pagination} + req = types.QueryActiveFinalityProvidersAtHeightRequest{Height: babylonHeight, Pagination: pagination} } - if len(btcValsFound) != len(btcValsWithVotingPowerMap) { - t.Errorf("Some vals were missed. Got %d while %d were expected", len(btcValsFound), len(btcValsWithVotingPowerMap)) + if len(fpsFound) != len(fpsWithVotingPowerMap) { + t.Errorf("Some finality providers were missed. Got %d while %d were expected", len(fpsFound), len(fpsWithVotingPowerMap)) } }) } -func FuzzBTCValidatorDelegations(f *testing.F) { +func FuzzFinalityProviderDelegations(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) @@ -475,14 +475,14 @@ func FuzzBTCValidatorDelegations(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // Generate a btc validator - btcVal, err := datagen.GenRandomBTCValidator(r) + // Generate a finality provider + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) + keeper.SetFinalityProvider(ctx, fp) startHeight := datagen.RandomInt(r, 100) + 1 endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 - // Generate a random number of BTC delegations under this validator + // Generate a random number of BTC delegations under this finality provider numBTCDels := datagen.RandomInt(r, 10) + 1 expectedBtcDelsMap := make(map[string]*types.BTCDelegation) for j := uint64(0); j < numBTCDels; j++ { @@ -491,7 +491,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*btcVal.BtcPk}, + []bbn.BIP340PubKey{*fp.BtcPk}, delSK, covenantSKs, covenantQuorum, @@ -506,7 +506,7 @@ func FuzzBTCValidatorDelegations(f *testing.F) { } // Test nil request - resp, err := keeper.BTCValidatorDelegations(ctx, nil) + resp, err := keeper.FinalityProviderDelegations(ctx, nil) require.Nil(t, resp) require.Error(t, err) @@ -520,22 +520,22 @@ func FuzzBTCValidatorDelegations(f *testing.F) { limit := datagen.RandomInt(r, len(expectedBtcDelsMap)) + 1 pagination := constructRequestWithLimit(r, limit) // Generate the initial query - req := types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - Pagination: pagination, + req := types.QueryFinalityProviderDelegationsRequest{ + FpBtcPkHex: fp.BtcPk.MarshalHex(), + Pagination: pagination, } - // Construct a mapping from the btc vals found to a boolean value - // Will be used later to evaluate whether all the btc vals were returned + // Construct a mapping from the finality providers found to a boolean value + // Will be used later to evaluate whether all the finality providers were returned btcDelsFound := make(map[string]bool, 0) for i := uint64(0); i < numBTCDels; i += limit { - resp, err = keeper.BTCValidatorDelegations(ctx, &req) + resp, err = keeper.FinalityProviderDelegations(ctx, &req) require.NoError(t, err) require.NotNil(t, resp) for _, btcDels := range resp.BtcDelegatorDelegations { require.Len(t, btcDels.Dels, 1) btcDel := btcDels.Dels[0] - require.Equal(t, btcVal.BtcPk, &btcDel.ValBtcPkList[0]) + require.Equal(t, fp.BtcPk, &btcDel.FpBtcPkList[0]) // Check if the pk exists in the map _, ok := expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] require.True(t, ok) @@ -543,9 +543,9 @@ func FuzzBTCValidatorDelegations(f *testing.F) { } // Construct the next page request pagination = constructRequestWithKeyAndLimit(r, resp.Pagination.NextKey, limit) - req = types.QueryBTCValidatorDelegationsRequest{ - ValBtcPkHex: btcVal.BtcPk.MarshalHex(), - Pagination: pagination, + req = types.QueryFinalityProviderDelegationsRequest{ + FpBtcPkHex: fp.BtcPk.MarshalHex(), + Pagination: pagination, } } require.Equal(t, len(btcDelsFound), len(expectedBtcDelsMap)) diff --git a/x/btcstaking/keeper/incentive.go b/x/btcstaking/keeper/incentive.go index c0ac8863b..622397ed4 100644 --- a/x/btcstaking/keeper/incentive.go +++ b/x/btcstaking/keeper/incentive.go @@ -23,31 +23,31 @@ func (k Keeper) RecordRewardDistCache(ctx context.Context) { rdc := types.NewRewardDistCache() - // iterate all BTC validators to add each BTC validator's distribution info + // iterate all finality providers to add each finality provider's distribution info // to reward distribution cache - btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) - defer btcValIter.Close() - for ; btcValIter.Valid(); btcValIter.Next() { - valBTCPKBytes := btcValIter.Key() - valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + fpIter := k.finalityProviderStore(ctx).Iterator(nil, nil) + defer fpIter.Close() + for ; fpIter.Valid(); fpIter.Next() { + fpBTCPKBytes := fpIter.Key() + fpBTCPK, err := bbn.NewBIP340PubKey(fpBTCPKBytes) if err != nil { - // failing to unmarshal BTC validator PK in KVStore is a programming error + // failing to unmarshal finality provider PK in KVStore is a programming error panic(err) } - btcVal, err := k.GetBTCValidator(ctx, valBTCPKBytes) + fp, err := k.GetFinalityProvider(ctx, fpBTCPKBytes) if err != nil { - // failing to get a BTC validator with voting power is a programming error + // failing to get a finality provider with voting power is a programming error panic(err) } - if btcVal.IsSlashed() { - // slashed BTC validator will not get any reward + if fp.IsSlashed() { + // slashed finality provider will not get any reward continue } - // iterate over all BTC delegations under this validator to compute - // the BTC validator's distribution info - btcValDistInfo := types.NewBTCValDistInfo(btcVal) - btcDelIter := k.btcDelegatorStore(ctx, valBTCPK).Iterator(nil, nil) + // iterate over all BTC delegations under this finality provider to compute + // the finality provider's distribution info + fpDistInfo := types.NewFinalityProviderDistInfo(fp) + btcDelIter := k.btcDelegatorStore(ctx, fpBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { // unmarshal var btcDelIndex types.BTCDelegatorDelegationIndex @@ -59,13 +59,13 @@ func (k Keeper) RecordRewardDistCache(ctx context.Context) { panic(err) // only programming error is possible } btcDel := k.getBTCDelegation(ctx, *stakingTxHash) - btcValDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue, covenantQuorum) + fpDistInfo.AddBTCDel(btcDel, btcTipHeight, wValue, covenantQuorum) } } btcDelIter.Close() - // try to add this BTC validator distribution info to reward distribution cache - rdc.AddBTCValDistInfo(btcValDistInfo) + // try to add this finality provider distribution info to reward distribution cache + rdc.AddFinalityProviderDistInfo(fpDistInfo) } // all good, set the reward distribution cache of the current height diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index cabc0890d..836cf4162 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -44,31 +44,31 @@ func FuzzRecordRewardDistCache(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // generate a random batch of validators - numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 - numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) - btcValsWithVotingPowerMap := map[string]*types.BTCValidator{} - for i := uint64(0); i < numBTCVals; i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + // generate a random batch of finality providers + numFpsWithVotingPower := datagen.RandomInt(r, 10) + 2 + numFps := numFpsWithVotingPower + datagen.RandomInt(r, 10) + fpsWithVotingPowerMap := map[string]*types.FinalityProvider{} + for i := uint64(0); i < numFps; i++ { + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - if i < numBTCValsWithVotingPower { - // these BTC validators will receive BTC delegations and have voting power - btcValsWithVotingPowerMap[btcVal.BabylonPk.String()] = btcVal + keeper.SetFinalityProvider(ctx, fp) + if i < numFpsWithVotingPower { + // these finality providers will receive BTC delegations and have voting power + fpsWithVotingPowerMap[fp.BabylonPk.String()] = fp } } - // for the first numBTCValsWithVotingPower validators, generate a random number of BTC delegations + // for the first numFpsWithVotingPower finality providers, generate a random number of BTC delegations numBTCDels := datagen.RandomInt(r, 10) + 1 stakingValue := datagen.RandomInt(r, 100000) + 100000 - for _, btcVal := range btcValsWithVotingPowerMap { + for _, fp := range fpsWithVotingPowerMap { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*btcVal.BtcPk}, + []bbn.BIP340PubKey{*fp.BtcPk}, delSK, covenantSKs, covenantQuorum, @@ -92,14 +92,14 @@ func FuzzRecordRewardDistCache(f *testing.F) { // assert reward distribution cache is correct rdc, err := keeper.GetRewardDistCache(ctx, babylonHeight) require.NoError(t, err) - require.Equal(t, rdc.TotalVotingPower, numBTCValsWithVotingPower*numBTCDels*stakingValue) - for _, valDistInfo := range rdc.BtcVals { - require.Equal(t, valDistInfo.TotalVotingPower, numBTCDels*stakingValue) - btcVal, ok := btcValsWithVotingPowerMap[valDistInfo.BabylonPk.String()] + require.Equal(t, rdc.TotalVotingPower, numFpsWithVotingPower*numBTCDels*stakingValue) + for _, fpDistInfo := range rdc.FinalityProviders { + require.Equal(t, fpDistInfo.TotalVotingPower, numBTCDels*stakingValue) + fp, ok := fpsWithVotingPowerMap[fpDistInfo.BabylonPk.String()] require.True(t, ok) - require.Equal(t, valDistInfo.Commission, btcVal.Commission) - require.Len(t, valDistInfo.BtcDels, int(numBTCDels)) - for _, delDistInfo := range valDistInfo.BtcDels { + require.Equal(t, fpDistInfo.Commission, fp.Commission) + require.Len(t, fpDistInfo.BtcDels, int(numBTCDels)) + for _, delDistInfo := range fpDistInfo.BtcDels { require.Equal(t, delDistInfo.VotingPower, stakingValue) } } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index a0d6760a3..265c744cd 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -49,9 +49,9 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara return &types.MsgUpdateParamsResponse{}, nil } -// CreateBTCValidator creates a BTC validator -func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCreateBTCValidator) (*types.MsgCreateBTCValidatorResponse, error) { - // ensure the validator address does not exist before +// CreateFinalityProvider creates a finality provider +func (ms msgServer) CreateFinalityProvider(goCtx context.Context, req *types.MsgCreateFinalityProvider) (*types.MsgCreateFinalityProviderResponse, error) { + // ensure the finality provider address does not already exist ctx := sdk.UnwrapSDKContext(goCtx) // basic stateless checks if err := req.ValidateBasic(); err != nil { @@ -65,34 +65,34 @@ func (ms msgServer) CreateBTCValidator(goCtx context.Context, req *types.MsgCrea // ensure commission rate is at least the minimum commission rate in parameters if req.Commission.LT(ms.MinCommissionRate(ctx)) { - return nil, types.ErrCommissionLTMinRate.Wrapf("cannot set validator commission to less than minimum rate of %s", ms.MinCommissionRate(ctx)) + return nil, types.ErrCommissionLTMinRate.Wrapf("cannot set finality provider commission to less than minimum rate of %s", ms.MinCommissionRate(ctx)) } if req.Commission.GT(sdkmath.LegacyOneDec()) { return nil, types.ErrCommissionGTMaxRate } - // ensure BTC validator does not exist before - if ms.HasBTCValidator(ctx, *req.BtcPk) { - return nil, types.ErrDuplicatedBTCVal + // ensure finality provider does not already exist + if ms.HasFinalityProvider(ctx, *req.BtcPk) { + return nil, types.ErrFpRegistered } - // all good, add this validator - btcVal := types.BTCValidator{ + // all good, add this finality provider + fp := types.FinalityProvider{ Description: req.Description, Commission: req.Commission, BabylonPk: req.BabylonPk, BtcPk: req.BtcPk, Pop: req.Pop, } - ms.SetBTCValidator(ctx, &btcVal) + ms.SetFinalityProvider(ctx, &fp) // notify subscriber - if err := ctx.EventManager().EmitTypedEvent(&types.EventNewBTCValidator{BtcVal: &btcVal}); err != nil { + if err := ctx.EventManager().EmitTypedEvent(&types.EventNewFinalityProvider{Fp: &fp}); err != nil { return nil, err } - return &types.MsgCreateBTCValidatorResponse{}, nil + return &types.MsgCreateFinalityProviderResponse{}, nil } // CreateBTCDelegation creates a BTC delegation @@ -113,10 +113,10 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidProofOfPossession.Wrapf("error while validating proof of posession: %v", err) } - // Ensure all validators are known to Babylon - for _, valBTCPK := range req.ValBtcPkList { - if !ms.HasBTCValidator(ctx, valBTCPK) { - return nil, types.ErrBTCValNotFound.Wrapf("validator pk: %s", valBTCPK.MarshalHex()) + // Ensure all finality providers are known to Babylon + for _, fpBTCPK := range req.FpBtcPkList { + if !ms.HasFinalityProvider(ctx, fpBTCPK) { + return nil, types.ErrFpNotFound.Wrapf("finality provider pk: %s", fpBTCPK.MarshalHex()) } } @@ -134,9 +134,9 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre } // Check if data provided in request, matches data to which staking tx is committed - valPKs, err := bbn.NewBTCPKsFromBIP340PKs(req.ValBtcPkList) + fpPKs, err := bbn.NewBTCPKsFromBIP340PKs(req.FpBtcPkList) if err != nil { - return nil, types.ErrInvalidStakingTx.Wrapf("cannot parse validator PK list: %v", err) + return nil, types.ErrInvalidStakingTx.Wrapf("cannot parse finality provider PK list: %v", err) } covenantPKs, err := bbn.NewBTCPKsFromBIP340PKs(params.CovenantPks) if err != nil { @@ -146,7 +146,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre stakingInfo, err := btcstaking.BuildStakingInfo( req.BtcPk.MustToBTCPK(), - valPKs, + fpPKs, covenantPKs, params.CovenantQuorum, uint16(req.StakingTime), @@ -238,7 +238,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre BabylonPk: req.BabylonPk, BtcPk: req.BtcPk, Pop: req.Pop, - ValBtcPkList: req.ValBtcPkList, + FpBtcPkList: req.FpBtcPkList, StartHeight: startHeight, EndHeight: endHeight, TotalSat: uint64(stakingInfo.StakingOutput.Value), @@ -273,7 +273,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // building unbonding info unbondingInfo, err := btcstaking.BuildUnbondingInfo( newBTCDel.BtcPk.MustToBTCPK(), - valPKs, + fpPKs, covenantPKs, params.CovenantQuorum, uint16(req.UnbondingTime), @@ -331,7 +331,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // Given that unbonding tx must not be replacable and we do not allow sending it second time, it places // burden on staker to choose right fee. // Unbonding tx should not be replaceable at babylon level (and by extension on btc level), as this would - // allow staker to spam the network with unbonding txs, which would force covenant and validator to send signatures. + // allow staker to spam the network with unbonding txs, which would force covenant and finality provider to send signatures. return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding tx fee must be larger that 0") } @@ -381,13 +381,13 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove } // Note: we assume the order of adaptor sigs is matched to the - // order of validators in the delegation - // TODO: ensure the order for restaking, currently, we only have one validator + // order of finality providers in the delegation + // TODO: ensure the order for restaking, currently, we only have one finality provider // one covenant emulator - if len(req.SlashingTxSigs) != len(btcDel.ValBtcPkList) { + if len(req.SlashingTxSigs) != len(btcDel.FpBtcPkList) { return nil, types.ErrInvalidCovenantSig.Wrapf( - "number of covenant signatures: %d, number of validators being staked to: %d", - len(req.SlashingTxSigs), len(btcDel.ValBtcPkList)) + "number of covenant signatures: %d, number of finality providers being staked to: %d", + len(req.SlashingTxSigs), len(btcDel.FpBtcPkList)) } /* @@ -408,7 +408,7 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove slashingSpendInfo, btcDel.SlashingTx, req.Pk, - btcDel.ValBtcPkList, + btcDel.FpBtcPkList, req.SlashingTxSigs, ) if err != nil { @@ -416,15 +416,15 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove } // Check that the number of covenant sigs and number of the - // validators are matched + // finality providers are matched // Note: we assume the order of adaptor sigs is matched to the - // order of validators in the delegation - // TODO: ensure the order for restaking, currently, we only have one validator + // order of finality providers in the delegation + // TODO: ensure the order for restaking, currently, we only have one finality provider // one covenant emulator - if len(req.SlashingUnbondingTxSigs) != len(btcDel.ValBtcPkList) { + if len(req.SlashingUnbondingTxSigs) != len(btcDel.FpBtcPkList) { return nil, types.ErrInvalidCovenantSig.Wrapf( - "number of covenant signatures: %d, number of validators being staked to: %d", - len(req.SlashingUnbondingTxSigs), len(btcDel.ValBtcPkList)) + "number of covenant signatures: %d, number of finality providers being staked to: %d", + len(req.SlashingUnbondingTxSigs), len(btcDel.FpBtcPkList)) } /* @@ -470,7 +470,7 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove unbondingSlashingSpendInfo, btcDel.BtcUndelegation.SlashingTx, req.Pk, - btcDel.ValBtcPkList, + btcDel.FpBtcPkList, req.SlashingUnbondingTxSigs, ) if err != nil { @@ -559,7 +559,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele // notify subscriber about this unbonded BTC delegation event := &types.EventUnbondedBTCDelegation{ BtcPk: btcDel.BtcPk, - ValBtcPkList: btcDel.ValBtcPkList, + FpBtcPkList: btcDel.FpBtcPkList, StakingTxHash: req.StakingTxHash, UnbondingTxHash: unbondingMsgTx.TxHash().String(), FromState: types.BTCDelegationStatus_ACTIVE, @@ -572,7 +572,7 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele } // verifySlashingTxAdaptorSigs verifies a list of adaptor signatures, each -// encrypted by a restaked validator PK and signed by the given PK, w.r.t. the +// encrypted by a restaked finality provider PK and signed by the given PK, w.r.t. the // given funding output (in staking or unbonding tx), slashing spend info and // slashing tx func verifySlashingTxAdaptorSigs( @@ -580,7 +580,7 @@ func verifySlashingTxAdaptorSigs( slashingSpendInfo *btcstaking.SpendInfo, slashingTx *types.BTCSlashingTx, pk *bbn.BIP340PubKey, - valPKs []bbn.BIP340PubKey, + fpPKs []bbn.BIP340PubKey, sigs [][]byte, ) error { for i, sig := range sigs { @@ -588,7 +588,7 @@ func verifySlashingTxAdaptorSigs( if err != nil { return err } - encKey, err := asig.NewEncryptionKeyFromBTCPK(valPKs[i].MustToBTCPK()) + encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPKs[i].MustToBTCPK()) if err != nil { return err } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 88f724b02..f5fd7d825 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -66,39 +66,39 @@ func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec. slashingAddress, err := datagen.GenRandomBTCAddress(r, h.Net) h.NoError(err) err = h.BTCStakingKeeper.SetParams(h.Ctx, types.Params{ - CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), - CovenantQuorum: 3, - SlashingAddress: slashingAddress.EncodeAddress(), - MinSlashingTxFeeSat: 10, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), - SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), - MaxActiveBtcValidators: 100, + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: 3, + SlashingAddress: slashingAddress.EncodeAddress(), + MinSlashingTxFeeSat: 10, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), + SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), + MaxActiveFinalityProviders: 100, }) h.NoError(err) return covenantSKs, covenantPKs } -func (h *Helper) CreateValidator(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, *types.BTCValidator) { - validatorSK, validatorPK, err := datagen.GenRandomBTCKeyPair(r) +func (h *Helper) CreateFinalityProvider(r *rand.Rand) (*btcec.PrivateKey, *btcec.PublicKey, *types.FinalityProvider) { + fpSK, fpPK, err := datagen.GenRandomBTCKeyPair(r) h.NoError(err) - btcVal, err := datagen.GenRandomBTCValidatorWithBTCSK(r, validatorSK) + fp, err := datagen.GenRandomFinalityProviderWithBTCSK(r, fpSK) h.NoError(err) - msgNewVal := types.MsgCreateBTCValidator{ + msgNewFp := types.MsgCreateFinalityProvider{ Signer: datagen.GenRandomAccount().Address, - Description: btcVal.Description, - Commission: btcVal.Commission, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, + Description: fp.Description, + Commission: fp.Commission, + BabylonPk: fp.BabylonPk, + BtcPk: fp.BtcPk, + Pop: fp.Pop, } - _, err = h.MsgServer.CreateBTCValidator(h.Ctx, &msgNewVal) + _, err = h.MsgServer.CreateFinalityProvider(h.Ctx, &msgNewFp) h.NoError(err) - return validatorSK, validatorPK, btcVal + return fpSK, fpPK, fp } func (h *Helper) CreateDelegation( r *rand.Rand, - validatorPK *btcec.PublicKey, + fpPK *btcec.PublicKey, changeAddress string, stakingValue int64, stakingTime uint16, @@ -114,7 +114,7 @@ func (h *Helper) CreateDelegation( h.t, h.Net, delSK, - []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{fpPK}, covPKs, bsParams.CovenantQuorum, stakingTimeBlocks, @@ -176,7 +176,7 @@ func (h *Helper) CreateDelegation( h.t, h.Net, delSK, - []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{fpPK}, covPKs, bsParams.CovenantQuorum, wire.NewOutPoint(&stkTxHash, stkOutputIdx), @@ -199,7 +199,7 @@ func (h *Helper) CreateDelegation( Signer: signer, BabylonPk: delBabylonPK.(*secp256k1.PubKey), BtcPk: stPk, - ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + FpBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(fpPK)}, Pop: pop, StakingTime: uint32(stakingTimeBlocks), StakingValue: stakingValue, @@ -230,7 +230,7 @@ func (h *Helper) CreateCovenantSigs( bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) - vPKs, err := bbn.NewBTCPKsFromBIP340PKs(del.ValBtcPkList) + vPKs, err := bbn.NewBTCPKsFromBIP340PKs(del.FpBtcPkList) h.NoError(err) stakingInfo, err := del.GetStakingInfo(&bsParams, h.Net) @@ -310,7 +310,7 @@ func (h *Helper) CreateCovenantSigs( func (h *Helper) GetDelegationAndCheckValues( r *rand.Rand, msgCreateBTCDel *types.MsgCreateBTCDelegation, - validatorPK *btcec.PublicKey, + fpPK *btcec.PublicKey, delegatorPK *btcec.PublicKey, stakingTxHash string, ) *types.BTCDelegation { @@ -328,48 +328,48 @@ func (h *Helper) GetDelegationAndCheckValues( return actualDel } -func FuzzMsgCreateBTCValidator(f *testing.F) { +func FuzzMsgCreateFinalityProvider(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) h := NewHelper(t, nil, nil) - // generate new BTC validators - btcVals := []*types.BTCValidator{} + // generate new finality providers + fps := []*types.FinalityProvider{} for i := 0; i < int(datagen.RandomInt(r, 10)); i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - msg := &types.MsgCreateBTCValidator{ + msg := &types.MsgCreateFinalityProvider{ Signer: datagen.GenRandomAccount().Address, - Description: btcVal.Description, - Commission: btcVal.Commission, - BabylonPk: btcVal.BabylonPk, - BtcPk: btcVal.BtcPk, - Pop: btcVal.Pop, + Description: fp.Description, + Commission: fp.Commission, + BabylonPk: fp.BabylonPk, + BtcPk: fp.BtcPk, + Pop: fp.Pop, } - _, err = h.MsgServer.CreateBTCValidator(h.Ctx, msg) + _, err = h.MsgServer.CreateFinalityProvider(h.Ctx, msg) require.NoError(t, err) - btcVals = append(btcVals, btcVal) + fps = append(fps, fp) } - // assert these validators exist in KVStore - for _, btcVal := range btcVals { - btcPK := *btcVal.BtcPk - require.True(t, h.BTCStakingKeeper.HasBTCValidator(h.Ctx, btcPK)) + // assert these finality providers exist in KVStore + for _, fp := range fps { + btcPK := *fp.BtcPk + require.True(t, h.BTCStakingKeeper.HasFinalityProvider(h.Ctx, btcPK)) } - // duplicated BTC validators should not pass - for _, btcVal2 := range btcVals { - msg := &types.MsgCreateBTCValidator{ + // duplicated finality providers should not pass + for _, fp2 := range fps { + msg := &types.MsgCreateFinalityProvider{ Signer: datagen.GenRandomAccount().Address, - Description: btcVal2.Description, - Commission: btcVal2.Commission, - BabylonPk: btcVal2.BabylonPk, - BtcPk: btcVal2.BtcPk, - Pop: btcVal2.Pop, + Description: fp2.Description, + Commission: fp2.Commission, + BabylonPk: fp2.BabylonPk, + BtcPk: fp2.BtcPk, + Pop: fp2.Pop, } - _, err := h.MsgServer.CreateBTCValidator(h.Ctx, msg) + _, err := h.MsgServer.CreateFinalityProvider(h.Ctx, msg) require.Error(t, err) } }) @@ -394,14 +394,14 @@ func FuzzCreateBTCDelegationAndAddCovenantSigs(f *testing.F) { changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) require.NoError(t, err) - // generate and insert new BTC validator - _, validatorPK, _ := h.CreateValidator(r) + // generate and insert new finality provider + _, fpPK, _ := h.CreateFinalityProvider(r) // generate and insert new BTC delegation stakingValue := int64(2 * 10e8) stakingTxHash, _, _, msgCreateBTCDel := h.CreateDelegation( r, - validatorPK, + fpPK, changeAddress.EncodeAddress(), stakingValue, 1000, @@ -454,14 +454,14 @@ func FuzzBTCUndelegate(f *testing.F) { changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) require.NoError(t, err) - // generate and insert new BTC validator - _, validatorPK, _ := h.CreateValidator(r) + // generate and insert new finality provider + _, fpPK, _ := h.CreateFinalityProvider(r) // generate and insert new BTC delegation stakingValue := int64(2 * 10e8) stakingTxHash, delSK, _, msgCreateBTCDel := h.CreateDelegation( r, - validatorPK, + fpPK, changeAddress.EncodeAddress(), stakingValue, 1000, @@ -497,7 +497,7 @@ func FuzzBTCUndelegate(f *testing.F) { }) } -func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { +func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -515,9 +515,9 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) require.NoError(t, err) - // We only generate a validator, but not insert it into KVStore. So later + // We only generate a finality provider, but not insert it into KVStore. So later // insertion of delegation should fail. - _, validatorPK, err := datagen.GenRandomBTCKeyPair(r) + _, fpPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) /* @@ -532,7 +532,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { t, h.Net, delSK, - []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{fpPK}, covenantPKs, bsParams.CovenantQuorum, stakingTimeBlocks, @@ -583,7 +583,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { t, h.Net, delSK, - []*btcec.PublicKey{validatorPK}, + []*btcec.PublicKey{fpPK}, covenantPKs, bsParams.CovenantQuorum, wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), @@ -602,7 +602,7 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { msgCreateBTCDel := &types.MsgCreateBTCDelegation{ Signer: signer, BabylonPk: delBabylonPK.(*secp256k1.PubKey), - ValBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(validatorPK)}, + FpBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(fpPK)}, BtcPk: bbn.NewBIP340PubKeyFromBTCPK(delSK.PubKey()), Pop: pop, StakingTime: uint32(stakingTimeBlocks), @@ -618,5 +618,5 @@ func TestDoNotAllowDelegationWithoutValidator(t *testing.T) { } _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) require.Error(t, err) - require.True(t, errors.Is(err, types.ErrBTCValNotFound)) + require.True(t, errors.Is(err, types.ErrFpNotFound)) } diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index bf50bf807..4859efb28 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -30,7 +30,7 @@ func (k Keeper) GetParams(ctx context.Context) (p types.Params) { return p } -// MinCommissionRate returns the minimal commission rate of BTC validators +// MinCommissionRate returns the minimal commission rate of finality providers func (k Keeper) MinCommissionRate(ctx context.Context) math.LegacyDec { return k.GetParams(ctx).MinCommissionRate } diff --git a/x/btcstaking/keeper/voting_power_table.go b/x/btcstaking/keeper/voting_power_table.go index 226dbe049..79f3ff4bd 100644 --- a/x/btcstaking/keeper/voting_power_table.go +++ b/x/btcstaking/keeper/voting_power_table.go @@ -26,81 +26,81 @@ func (k Keeper) RecordVotingPowerTable(ctx context.Context) { // get value of w wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout - // filter out all BTC validators with positive voting power - activeBTCVals := []*types.BTCValidatorWithMeta{} - btcValIter := k.btcValidatorStore(ctx).Iterator(nil, nil) - for ; btcValIter.Valid(); btcValIter.Next() { - valBTCPKBytes := btcValIter.Key() - valBTCPK, err := bbn.NewBIP340PubKey(valBTCPKBytes) + // filter out all finality providers with positive voting power + activeFps := []*types.FinalityProviderWithMeta{} + fpIter := k.finalityProviderStore(ctx).Iterator(nil, nil) + for ; fpIter.Valid(); fpIter.Next() { + fpBTCPKBytes := fpIter.Key() + fpBTCPK, err := bbn.NewBIP340PubKey(fpBTCPKBytes) if err != nil { - // failed to unmarshal BTC validator PK in KVStore is a programming error + // failed to unmarshal finality provider PK in KVStore is a programming error panic(err) } - btcVal, err := k.GetBTCValidator(ctx, valBTCPKBytes) + fp, err := k.GetFinalityProvider(ctx, fpBTCPKBytes) if err != nil { - // failed to get a BTC validator with voting power is a programming error + // failed to get a finality provider with voting power is a programming error panic(err) } - if btcVal.IsSlashed() { - // slashed BTC validator is removed from BTC validator set + if fp.IsSlashed() { + // slashed finality provider is removed from finality provider set continue } - valPower := uint64(0) + fpPower := uint64(0) - // iterate all BTC delegations under this validator - // to calculate this validator's total voting power - btcDelIter := k.btcDelegatorStore(ctx, valBTCPK).Iterator(nil, nil) + // iterate all BTC delegations under this finality provider + // to calculate this finality provider's total voting power + btcDelIter := k.btcDelegatorStore(ctx, fpBTCPK).Iterator(nil, nil) for ; btcDelIter.Valid(); btcDelIter.Next() { delBTCPK, err := bbn.NewBIP340PubKey(btcDelIter.Key()) if err != nil { panic(err) // only programming error is possible } - btcDels, err := k.getBTCDelegatorDelegations(ctx, valBTCPK, delBTCPK) + btcDels, err := k.getBTCDelegatorDelegations(ctx, fpBTCPK, delBTCPK) if err != nil { panic(err) // only programming error is possible } - valPower += btcDels.VotingPower(btcTipHeight, wValue, covenantQuorum) + fpPower += btcDels.VotingPower(btcTipHeight, wValue, covenantQuorum) } btcDelIter.Close() - if valPower > 0 { - activeBTCVals = append(activeBTCVals, &types.BTCValidatorWithMeta{ - BtcPk: valBTCPK, - VotingPower: valPower, + if fpPower > 0 { + activeFps = append(activeFps, &types.FinalityProviderWithMeta{ + BtcPk: fpBTCPK, + VotingPower: fpPower, // other fields do not matter }) } } - btcValIter.Close() + fpIter.Close() - // return directly if there is no active BTC validator - if len(activeBTCVals) == 0 { + // return directly if there is no active finality provider + if len(activeFps) == 0 { return } - // filter out top `MaxActiveBtcValidators` active validators in terms of voting power - activeBTCVals = types.FilterTopNBTCValidators(activeBTCVals, k.GetParams(ctx).MaxActiveBtcValidators) + // filter out top `MaxActiveFinalityProviders` active finality providers in terms of voting power + activeFps = types.FilterTopNFinalityProviders(activeFps, k.GetParams(ctx).MaxActiveFinalityProviders) - // set voting power for each active BTC validators - for _, btcVal := range activeBTCVals { - k.SetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonTipHeight, btcVal.VotingPower) + // set voting power for each active finality providers + for _, fp := range activeFps { + k.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonTipHeight, fp.VotingPower) } } -// SetVotingPower sets the voting power of a given BTC validator at a given Babylon height -func (k Keeper) SetVotingPower(ctx context.Context, valBTCPK []byte, height uint64, power uint64) { +// SetVotingPower sets the voting power of a given finality provider at a given Babylon height +func (k Keeper) SetVotingPower(ctx context.Context, fpBTCPK []byte, height uint64, power uint64) { store := k.votingPowerStore(ctx, height) - store.Set(valBTCPK, sdk.Uint64ToBigEndian(power)) + store.Set(fpBTCPK, sdk.Uint64ToBigEndian(power)) } -// GetVotingPower gets the voting power of a given BTC validator at a given Babylon height -func (k Keeper) GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 { - if !k.HasBTCValidator(ctx, valBTCPK) { +// GetVotingPower gets the voting power of a given finality provider at a given Babylon height +func (k Keeper) GetVotingPower(ctx context.Context, fpBTCPK []byte, height uint64) uint64 { + if !k.HasFinalityProvider(ctx, fpBTCPK) { return 0 } store := k.votingPowerStore(ctx, height) - powerBytes := store.Get(valBTCPK) + powerBytes := store.Get(fpBTCPK) if len(powerBytes) == 0 { return 0 } @@ -115,40 +115,40 @@ func (k Keeper) HasVotingPowerTable(ctx context.Context, height uint64) bool { return iter.Valid() } -// GetVotingPowerTable gets the voting power table, i.e., validator set at a given height +// GetVotingPowerTable gets the voting power table, i.e., finality provider set at a given height func (k Keeper) GetVotingPowerTable(ctx context.Context, height uint64) map[string]uint64 { store := k.votingPowerStore(ctx, height) iter := store.Iterator(nil, nil) defer iter.Close() - // if no validator at this height, return nil + // if no finality provider at this height, return nil if !iter.Valid() { return nil } - // get all validators at this height - valSet := map[string]uint64{} + // get all finality providers at this height + fpSet := map[string]uint64{} for ; iter.Valid(); iter.Next() { - valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + fpBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) if err != nil { - // failing to unmarshal validator BTC PK in KVStore is a programming error + // failing to unmarshal finality provider BTC PK in KVStore is a programming error panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } - valSet[valBTCPK.MarshalHex()] = sdk.BigEndianToUint64(iter.Value()) + fpSet[fpBTCPK.MarshalHex()] = sdk.BigEndianToUint64(iter.Value()) } - return valSet + return fpSet } // GetBTCStakingActivatedHeight returns the height when the BTC staking protocol is activated -// i.e., the first height where a BTC validator has voting power +// i.e., the first height where a finality provider has voting power // Before the BTC staking protocol is activated, we don't index or tally any block func (k Keeper) GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) votingPowerStore := prefix.NewStore(storeAdapter, types.VotingPowerKey) iter := votingPowerStore.Iterator(nil, nil) defer iter.Close() - // if the iterator is valid, then there exists a height that has a BTC validator with voting power + // if the iterator is valid, then there exists a height that has a finality provider with voting power if iter.Valid() { return sdk.BigEndianToUint64(iter.Key()), nil } else { @@ -165,7 +165,7 @@ func (k Keeper) IsBTCStakingActivated(ctx context.Context) bool { return iter.Valid() } -// votingPowerStore returns the KVStore of the BTC validators' voting power +// votingPowerStore returns the KVStore of the finality providers' voting power // prefix: (VotingPowerKey || Babylon block height) // key: Bitcoin secp256k1 PK // value: voting power quantified in Satoshi diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index d0895a845..3edc038eb 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -44,28 +44,28 @@ func FuzzVotingPowerTable(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // generate a random batch of validators - btcVals := []*types.BTCValidator{} - numBTCValsWithVotingPower := datagen.RandomInt(r, 10) + 2 - numBTCVals := numBTCValsWithVotingPower + datagen.RandomInt(r, 10) - for i := uint64(0); i < numBTCVals; i++ { - btcVal, err := datagen.GenRandomBTCValidator(r) + // generate a random batch of finality providers + fps := []*types.FinalityProvider{} + numFpsWithVotingPower := datagen.RandomInt(r, 10) + 2 + numFps := numFpsWithVotingPower + datagen.RandomInt(r, 10) + for i := uint64(0); i < numFps; i++ { + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) - btcVals = append(btcVals, btcVal) + keeper.SetFinalityProvider(ctx, fp) + fps = append(fps, fp) } - // for the first numBTCValsWithVotingPower validators, generate a random number of BTC delegations + // for the first numFpsWithVotingPower finality providers, generate a random number of BTC delegations numBTCDels := datagen.RandomInt(r, 10) + 1 stakingValue := datagen.RandomInt(r, 100000) + 100000 - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { + for i := uint64(0); i < numFpsWithVotingPower; i++ { for j := uint64(0); j < numBTCDels; j++ { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*btcVals[i].BtcPk}, + []bbn.BIP340PubKey{*fps[i].BtcPk}, delSK, covenantSKs, covenantQuorum, @@ -81,41 +81,41 @@ func FuzzVotingPowerTable(f *testing.F) { /* Case 1: BTC height is 0 that is smaller than start height 1. - No BTC validator will have voting power + No finality privater will have voting power */ babylonHeight := datagen.RandomInt(r, 10) + 1 ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - for i := uint64(0); i < numBTCVals; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + for i := uint64(0); i < numFps; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) require.Zero(t, power) } /* - Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power + Case 2: move to 1st BTC block, then assert the first numFpsWithVotingPower finality providers have voting power */ babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + for i := uint64(0); i < numFpsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) require.Equal(t, numBTCDels*stakingValue, power) } - for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + for i := numFpsWithVotingPower; i < numFps; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) require.Zero(t, power) } // also, get voting power table and assert consistency powerTable := keeper.GetVotingPowerTable(ctx, babylonHeight) require.NotNil(t, powerTable) - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) - require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power) + for i := uint64(0); i < numFpsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) + require.Equal(t, powerTable[fps[i].BtcPk.MarshalHex()], power) } // the activation height should be the current Babylon height as well activatedHeight, err := keeper.GetBTCStakingActivatedHeight(ctx) @@ -123,15 +123,15 @@ func FuzzVotingPowerTable(f *testing.F) { require.Equal(t, babylonHeight, activatedHeight) /* - Case 3: slash a random BTC validator and move on - then assert the slashed BTC validator does not have voting power + Case 3: slash a random finality provider and move on + then assert the slashed finality provider does not have voting power */ - // slash a random BTC validator - slashedIdx := datagen.RandomInt(r, int(numBTCValsWithVotingPower)) - slashedVal := btcVals[slashedIdx] + // slash a random finality provider + slashedIdx := datagen.RandomInt(r, int(numFpsWithVotingPower)) + slashedFp := fps[slashedIdx] // This will be called to get the slashed height btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1) - err = keeper.SlashBTCValidator(ctx, slashedVal.BtcPk.MustMarshal()) + err = keeper.SlashFinalityProvider(ctx, slashedFp.BtcPk.MustMarshal()) require.NoError(t, err) // move to later Babylon height and 2nd BTC height babylonHeight += datagen.RandomInt(r, 10) + 1 @@ -140,41 +140,41 @@ func FuzzVotingPowerTable(f *testing.F) { // index height and record power table keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - // check if the slashed BTC validator's voting power becomes zero - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + // check if the slashed finality provider's voting power becomes zero + for i := uint64(0); i < numFpsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) if i == slashedIdx { require.Zero(t, power) } else { require.Equal(t, numBTCDels*stakingValue, power) } } - for i := numBTCValsWithVotingPower; i < numBTCVals; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + for i := numFpsWithVotingPower; i < numFps; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) require.Zero(t, power) } // also, get voting power table and assert consistency powerTable = keeper.GetVotingPowerTable(ctx, babylonHeight) require.NotNil(t, powerTable) - for i := uint64(0); i < numBTCValsWithVotingPower; i++ { - power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight) + for i := uint64(0); i < numFpsWithVotingPower; i++ { + power := keeper.GetVotingPower(ctx, *fps[i].BtcPk, babylonHeight) if i == slashedIdx { require.Zero(t, power) } - require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power) + require.Equal(t, powerTable[fps[i].BtcPk.MarshalHex()], power) } /* - Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height) + Case 4: move to 999th BTC block, then assert none of finality providers has voting power (since end height - w < BTC height) */ babylonHeight += datagen.RandomInt(r, 10) + 1 ctx = datagen.WithCtxHeight(ctx, babylonHeight) btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1) keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - for _, btcVal := range btcVals { - power := keeper.GetVotingPower(ctx, *btcVal.BtcPk, babylonHeight) + for _, fp := range fps { + power := keeper.GetVotingPower(ctx, *fp.BtcPk, babylonHeight) require.Zero(t, power) } @@ -185,7 +185,7 @@ func FuzzVotingPowerTable(f *testing.F) { }) } -func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { +func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -212,24 +212,24 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // generate a random batch of validators, each with a BTC delegation with random power - btcValsWithMeta := []*types.BTCValidatorWithMeta{} - numBTCVals := datagen.RandomInt(r, 300) + 1 - for i := uint64(0); i < numBTCVals; i++ { - // generate BTC validator - btcVal, err := datagen.GenRandomBTCValidator(r) + // generate a random batch of finality providers, each with a BTC delegation with random power + fpsWithMeta := []*types.FinalityProviderWithMeta{} + numFps := datagen.RandomInt(r, 300) + 1 + for i := uint64(0); i < numFps; i++ { + // generate finality provider + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) + keeper.SetFinalityProvider(ctx, fp) - // delegate to this BTC validator + // delegate to this finality provider stakingValue := datagen.RandomInt(r, 100000) + 100000 - valBTCPK := btcVal.BtcPk + fpBTCPK := fp.BtcPk delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*valBTCPK}, + []bbn.BIP340PubKey{*fpBTCPK}, delSK, covenantSKs, covenantQuorum, @@ -242,18 +242,18 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { require.NoError(t, err) // record voting power - btcValsWithMeta = append(btcValsWithMeta, &types.BTCValidatorWithMeta{ - BtcPk: btcVal.BtcPk, + fpsWithMeta = append(fpsWithMeta, &types.FinalityProviderWithMeta{ + BtcPk: fp.BtcPk, VotingPower: stakingValue, }) } - maxActiveBTCValsParam := keeper.GetParams(ctx).MaxActiveBtcValidators - // get a map of expected active BTC validators - expectedActiveBTCVals := types.FilterTopNBTCValidators(btcValsWithMeta, maxActiveBTCValsParam) - expectedActiveBTCValMap := map[string]uint64{} - for _, btcVal := range expectedActiveBTCVals { - expectedActiveBTCValMap[btcVal.BtcPk.MarshalHex()] = btcVal.VotingPower + maxActiveFpsParam := keeper.GetParams(ctx).MaxActiveFinalityProviders + // get a map of expected active finality providers + expectedActiveFps := types.FilterTopNFinalityProviders(fpsWithMeta, maxActiveFpsParam) + expectedActiveFpsMap := map[string]uint64{} + for _, fp := range expectedActiveFps { + expectedActiveFpsMap[fp.BtcPk.MarshalHex()] = fp.VotingPower } // record voting power table @@ -263,10 +263,10 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - // only BTC validators in expectedActiveBTCValMap have voting power - for _, btcVal := range btcValsWithMeta { - power := keeper.GetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonHeight) - if expectedPower, ok := expectedActiveBTCValMap[btcVal.BtcPk.MarshalHex()]; ok { + // only finality providers in expectedActiveFpsMap have voting power + for _, fp := range fpsWithMeta { + power := keeper.GetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonHeight) + if expectedPower, ok := expectedActiveFpsMap[fp.BtcPk.MarshalHex()]; ok { require.Equal(t, expectedPower, power) } else { require.Equal(t, uint64(0), power) @@ -274,21 +274,21 @@ func FuzzVotingPowerTable_ActiveBTCValidators(f *testing.F) { } // also, get voting power table and assert there is - // min(len(expectedActiveBTCVals), MaxActiveBtcValidators) active BTC validators + // min(len(expectedActiveFps), MaxActiveFinalityProviders) active finality providers powerTable := keeper.GetVotingPowerTable(ctx, babylonHeight) - expectedNumActiveBTCVals := len(expectedActiveBTCValMap) - if expectedNumActiveBTCVals > int(maxActiveBTCValsParam) { - expectedNumActiveBTCVals = int(maxActiveBTCValsParam) + expectedNumActiveFps := len(expectedActiveFpsMap) + if expectedNumActiveFps > int(maxActiveFpsParam) { + expectedNumActiveFps = int(maxActiveFpsParam) } - require.Len(t, powerTable, expectedNumActiveBTCVals) + require.Len(t, powerTable, expectedNumActiveFps) // assert consistency of voting power - for pkHex, expectedPower := range expectedActiveBTCValMap { + for pkHex, expectedPower := range expectedActiveFpsMap { require.Equal(t, powerTable[pkHex], expectedPower) } }) } -func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { +func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -315,24 +315,24 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { // this is already covered in FuzzGeneratingValidStakingSlashingTx slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) - // generate a random batch of validators, each with a BTC delegation with random power - btcValsWithMeta := []*types.BTCValidatorWithMeta{} - numBTCVals := uint64(200) // there has to be more than `maxActiveBtcValidators` validators - for i := uint64(0); i < numBTCVals; i++ { - // generate BTC validator - btcVal, err := datagen.GenRandomBTCValidator(r) + // generate a random batch of finality providers, each with a BTC delegation with random power + fpsWithMeta := []*types.FinalityProviderWithMeta{} + numFps := uint64(200) // there has to be more than `maxActiveFinalityProviders` finality providers + for i := uint64(0); i < numFps; i++ { + // generate finality provider + fp, err := datagen.GenRandomFinalityProvider(r) require.NoError(t, err) - keeper.SetBTCValidator(ctx, btcVal) + keeper.SetFinalityProvider(ctx, fp) - // delegate to this BTC validator + // delegate to this finality provider stakingValue := datagen.RandomInt(r, 100000) + 100000 - valBTCPK := btcVal.BtcPk + fpBTCPK := fp.BtcPk delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*valBTCPK}, + []bbn.BIP340PubKey{*fpBTCPK}, delSK, covenantSKs, covenantQuorum, @@ -345,8 +345,8 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { require.NoError(t, err) // record voting power - btcValsWithMeta = append(btcValsWithMeta, &types.BTCValidatorWithMeta{ - BtcPk: btcVal.BtcPk, + fpsWithMeta = append(fpsWithMeta, &types.FinalityProviderWithMeta{ + BtcPk: fp.BtcPk, VotingPower: stakingValue, }) } @@ -358,29 +358,29 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - // get maps of active/inactive BTC validators - activeBTCValMap := map[string]uint64{} - inactiveBTCValMap := map[string]uint64{} - for _, btcVal := range btcValsWithMeta { - power := keeper.GetVotingPower(ctx, btcVal.BtcPk.MustMarshal(), babylonHeight) + // get maps of active/inactive finality providers + activeFpsMap := map[string]uint64{} + inactiveFpsMap := map[string]uint64{} + for _, fp := range fpsWithMeta { + power := keeper.GetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonHeight) if power > 0 { - activeBTCValMap[btcVal.BtcPk.MarshalHex()] = power + activeFpsMap[fp.BtcPk.MarshalHex()] = power } else { - inactiveBTCValMap[btcVal.BtcPk.MarshalHex()] = power + inactiveFpsMap[fp.BtcPk.MarshalHex()] = power } } - // delegate a huge amount of tokens to one of the inactive BTC validator - var activatedValBTCPK *bbn.BIP340PubKey - for valBTCPKHex := range inactiveBTCValMap { + // delegate a huge amount of tokens to one of the inactive finality provider + var activatedFpBTCPK *bbn.BIP340PubKey + for fpBTCPKHex := range inactiveFpsMap { stakingValue := uint64(10000000) - activatedValBTCPK, _ = bbn.NewBIP340PubKeyFromHex(valBTCPKHex) + activatedFpBTCPK, _ = bbn.NewBIP340PubKeyFromHex(fpBTCPKHex) delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) btcDel, err := datagen.GenRandomBTCDelegation( r, t, - []bbn.BIP340PubKey{*activatedValBTCPK}, + []bbn.BIP340PubKey{*activatedFpBTCPK}, delSK, covenantSKs, covenantQuorum, @@ -402,9 +402,9 @@ func FuzzVotingPowerTable_ActiveBTCValidatorRotation(f *testing.F) { keeper.IndexBTCHeight(ctx) keeper.RecordVotingPowerTable(ctx) - // ensure that the activated BTC validator now has entered the active validator set + // ensure that the activated finality provider now has entered the active finality provider set // i.e., has voting power - power := keeper.GetVotingPower(ctx, activatedValBTCPK.MustMarshal(), babylonHeight) + power := keeper.GetVotingPower(ctx, activatedFpBTCPK.MustMarshal(), babylonHeight) require.Positive(t, power) }) } diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index 3b037fc23..91ca3d2ae 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -116,11 +116,11 @@ func (d *BTCDelegation) ValidateBasic() error { if d.Pop == nil { return fmt.Errorf("empty proof of possession") } - if len(d.ValBtcPkList) == 0 { - return fmt.Errorf("empty list of BTC validator PKs") + if len(d.FpBtcPkList) == 0 { + return fmt.Errorf("empty list of finality provider PKs") } - if ExistsDup(d.ValBtcPkList) { - return fmt.Errorf("list of BTC validator PKs has duplication") + if ExistsDup(d.FpBtcPkList) { + return fmt.Errorf("list of finality provider PKs has duplication") } if d.StakingTx == nil { return fmt.Errorf("empty staking tx") @@ -165,7 +165,7 @@ func (d *BTCDelegation) IsSignedByCovMember(covPk *bbn.BIP340PubKey) bool { // AddCovenantSigs adds signatures on the slashing tx from the given // covenant, where each signature is an adaptor signature encrypted by -// each BTC validator's PK this BTC delegation restakes to +// each finality provider's PK this BTC delegation restakes to func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.AdaptorSignature, quorum uint32) error { // we can ignore the covenant sig if quorum is already reached if d.HasCovenantQuorums(quorum) { @@ -189,11 +189,11 @@ func (d *BTCDelegation) AddCovenantSigs(covPk *bbn.BIP340PubKey, sigs []asig.Ada // GetStakingInfo returns the staking info of the BTC delegation // the staking info can be used for constructing witness of slashing tx -// with access to a BTC validator's SK +// with access to a finality provider's SK func (d *BTCDelegation) GetStakingInfo(bsParams *Params, btcNet *chaincfg.Params) (*btcstaking.StakingInfo, error) { - valBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.ValBtcPkList) + fpBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.FpBtcPkList) if err != nil { - return nil, fmt.Errorf("failed to convert validator pks to BTC pks %v", err) + return nil, fmt.Errorf("failed to convert finality provider pks to BTC pks %v", err) } covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) if err != nil { @@ -201,7 +201,7 @@ func (d *BTCDelegation) GetStakingInfo(bsParams *Params, btcNet *chaincfg.Params } stakingInfo, err := btcstaking.BuildStakingInfo( d.BtcPk.MustToBTCPK(), - valBtcPkList, + fpBtcPkList, covenantBtcPkList, bsParams.CovenantQuorum, d.GetStakingTime(), @@ -247,11 +247,11 @@ func (d *BTCDelegation) SignUnbondingTx(bsParams *Params, btcNet *chaincfg.Param // GetUnbondingInfo returns the unbonding info of the BTC delegation // the unbonding info can be used for constructing witness of unbonding slashing -// tx with access to a BTC validator's SK +// tx with access to a finality provider's SK func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Params) (*btcstaking.UnbondingInfo, error) { - valBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.ValBtcPkList) + fpBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.FpBtcPkList) if err != nil { - return nil, fmt.Errorf("failed to convert validator pks to BTC pks: %v", err) + return nil, fmt.Errorf("failed to convert finality provider pks to BTC pks: %v", err) } covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) @@ -265,7 +265,7 @@ func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Para unbondingInfo, err := btcstaking.BuildUnbondingInfo( d.BtcPk.MustToBTCPK(), - valBtcPkList, + fpBtcPkList, covenantBtcPkList, bsParams.CovenantQuorum, uint16(d.BtcUndelegation.GetUnbondingTime()), @@ -279,7 +279,7 @@ func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Para return unbondingInfo, nil } -func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, valSK *btcec.PrivateKey) (*wire.MsgTx, error) { +func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, fpSK *btcec.PrivateKey) (*wire.MsgTx, error) { stakingMsgTx, err := bbn.NewBTCTxFromBytes(d.StakingTx) if err != nil { return nil, fmt.Errorf("failed to convert a Babylon staking tx to wire.MsgTx: %w", err) @@ -303,7 +303,7 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha // assemble witness for slashing tx slashingMsgTxWithWitness, err := d.SlashingTx.BuildSlashingTxWithWitness( - valSK, + fpSK, stakingMsgTx, d.StakingOutputIdx, d.DelegatorSig, @@ -312,9 +312,9 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha ) if err != nil { return nil, fmt.Errorf( - "failed to build witness for BTC delegation of %s under BTC validator %s: %v", + "failed to build witness for BTC delegation of %s under finality provider %s: %v", d.BtcPk.MarshalHex(), - bbn.NewBIP340PubKeyFromBTCPK(valSK.PubKey()).MarshalHex(), + bbn.NewBIP340PubKeyFromBTCPK(fpSK.PubKey()).MarshalHex(), err, ) } @@ -322,7 +322,7 @@ func (d *BTCDelegation) BuildSlashingTxWithWitness(bsParams *Params, btcNet *cha return slashingMsgTxWithWitness, nil } -func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, valSK *btcec.PrivateKey) (*wire.MsgTx, error) { +func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, btcNet *chaincfg.Params, fpSK *btcec.PrivateKey) (*wire.MsgTx, error) { unbondingMsgTx, err := bbn.NewBTCTxFromBytes(d.BtcUndelegation.UnbondingTx) if err != nil { return nil, fmt.Errorf("failed to convert a Babylon unbonding tx to wire.MsgTx: %w", err) @@ -346,7 +346,7 @@ func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, bt // assemble witness for unbonding slashing tx slashingMsgTxWithWitness, err := d.BtcUndelegation.SlashingTx.BuildSlashingTxWithWitness( - valSK, + fpSK, unbondingMsgTx, 0, d.BtcUndelegation.DelegatorSlashingSig, @@ -355,9 +355,9 @@ func (d *BTCDelegation) BuildUnbondingSlashingTxWithWitness(bsParams *Params, bt ) if err != nil { return nil, fmt.Errorf( - "failed to build witness for unbonding BTC delegation %s under BTC validator %s: %v", + "failed to build witness for unbonding BTC delegation %s under finality provider %s: %v", d.BtcPk.MarshalHex(), - bbn.NewBIP340PubKeyFromBTCPK(valSK.PubKey()).MarshalHex(), + bbn.NewBIP340PubKeyFromBTCPK(fpSK.PubKey()).MarshalHex(), err, ) } diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index 59fed517b..ff2c6e0d1 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -78,9 +78,9 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { require.NoError(t, err) delBTCPK := bbn.NewBIP340PubKeyFromBTCPK(delPK) - valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + fpSK, fpPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - valPKList := []*btcec.PublicKey{valPK} + fpPKList := []*btcec.PublicKey{fpPK} // (3, 5) covenant committee covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) @@ -104,7 +104,7 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { t, net, delSK, - valPKList, + fpPKList, covenantPKs, covenantQuorum, stakingTimeBlocks, @@ -124,7 +124,7 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { delSig, err := testInfo.SlashingTx.Sign(testInfo.StakingTx, 0, slashingSpendInfo.GetPkScriptPath(), delSK) require.NoError(t, err) // covenant signs (using adaptor signature) the slashing tx - covenantSigs, err := datagen.GenCovenantAdaptorSigs(covenantSKs, []*btcec.PublicKey{valPK}, testInfo.StakingTx, slashingSpendInfo.GetPkScriptPath(), testInfo.SlashingTx) + covenantSigs, err := datagen.GenCovenantAdaptorSigs(covenantSKs, []*btcec.PublicKey{fpPK}, testInfo.StakingTx, slashingSpendInfo.GetPkScriptPath(), testInfo.SlashingTx) require.NoError(t, err) covenantSigs = covenantSigs[2:] // discard 2 out of 5 signatures @@ -133,7 +133,7 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { BabylonPk: nil, // not relevant here BtcPk: delBTCPK, Pop: nil, // not relevant here - ValBtcPkList: bbn.NewBIP340PKsFromBTCPKs(valPKList), + FpBtcPkList: bbn.NewBIP340PKsFromBTCPKs(fpPKList), StartHeight: 1000, // not relevant here EndHeight: uint64(1000 + stakingTimeBlocks), TotalSat: uint64(stakingValue), @@ -151,7 +151,7 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { btcNet := &chaincfg.SimNetParams // build slashing tx with witness for spending the staking tx - slashingTxWithWitness, err := btcDel.BuildSlashingTxWithWitness(bsParams, btcNet, valSK) + slashingTxWithWitness, err := btcDel.BuildSlashingTxWithWitness(bsParams, btcNet, fpSK) require.NoError(t, err) // assert execution diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 4e51ade3c..c1a556c07 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -133,7 +133,7 @@ func (tx *BTCSlashingTx) Sign( return bbn.NewBIP340SignatureFromBTCSig(schnorrSig), nil } -// VerifySignature verifies a signature on the slashing tx signed by staker, validator or covenant +// VerifySignature verifies a signature on the slashing tx signed by staker, finality provider, or covenant func (tx *BTCSlashingTx) VerifySignature( stakingPkScript []byte, stakingAmount int64, @@ -154,7 +154,7 @@ func (tx *BTCSlashingTx) VerifySignature( ) } -// EncSign generates an adaptor signature on the slashing tx with validator's +// EncSign generates an adaptor signature on the slashing tx with finality provider's // public key as encryption key func (tx *BTCSlashingTx) EncSign( stakingMsgTx *wire.MsgTx, @@ -183,7 +183,7 @@ func (tx *BTCSlashingTx) EncSign( } // EncVerifyAdaptorSignature verifies an adaptor signature on the slashing tx -// with the validator's public key as encryption key +// with the finality provider's public key as encryption key func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( stakingPkScript []byte, stakingAmount int64, @@ -208,21 +208,21 @@ func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( } func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( - valSK *btcec.PrivateKey, + fpSK *btcec.PrivateKey, fundingMsgTx *wire.MsgTx, outputIdx uint32, delegatorSig *bbn.BIP340Signature, covenantSigs []*asig.AdaptorSignature, slashingPathSpendInfo *btcstaking.SpendInfo, ) (*wire.MsgTx, error) { - valSig, err := tx.Sign(fundingMsgTx, outputIdx, slashingPathSpendInfo.GetPkScriptPath(), valSK) + fpSig, err := tx.Sign(fundingMsgTx, outputIdx, slashingPathSpendInfo.GetPkScriptPath(), fpSK) if err != nil { - return nil, fmt.Errorf("failed to sign slashing tx for the BTC validator: %w", err) + return nil, fmt.Errorf("failed to sign slashing tx for the finality provider: %w", err) } - // decrypt covenant adaptor signature to Schnorr signature using validator's SK, + // decrypt covenant adaptor signature to Schnorr signature using finality provider's SK, // then marshal - decKey, err := asig.NewDecyptionKeyFromBTCSK(valSK) + decKey, err := asig.NewDecyptionKeyFromBTCSK(fpSK) if err != nil { return nil, fmt.Errorf("failed to get decryption key from BTC SK: %w", err) } @@ -239,7 +239,7 @@ func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( // construct witness witness, err := slashingPathSpendInfo.CreateSlashingPathWitness( covSigs, - []*schnorr.Signature{valSig.MustToBTCSig()}, // TODO: work with restaking + []*schnorr.Signature{fpSig.MustToBTCSig()}, // TODO: work with restaking delegatorSig.MustToBTCSig(), ) if err != nil { diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index f3a225cc2..e5af93185 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -38,7 +38,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) // TODO: test restaking - valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + fpSK, fpPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) delSK, _, err := datagen.GenRandomBTCKeyPair(r) @@ -54,7 +54,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { t, net, delSK, - []*btcec.PublicKey{valPK}, + []*btcec.PublicKey{fpPK}, covenantPKs, covenantQuorum, stakingTimeBlocks, @@ -76,7 +76,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { covenantSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, - []*btcec.PublicKey{valPK}, + []*btcec.PublicKey{fpPK}, stakingMsgTx, slashingPkScriptPath, slashingTx, @@ -91,7 +91,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { require.NoError(t, err) // create slashing tx with witness - slashingMsgTxWithWitness, err := slashingTx.BuildSlashingTxWithWitness(valSK, stakingMsgTx, 0, delSig, covSigs, slashingSpendInfo) + slashingMsgTxWithWitness, err := slashingTx.BuildSlashingTxWithWitness(fpSK, stakingMsgTx, 0, delSig, covSigs, slashingSpendInfo) require.NoError(t, err) // verify slashing tx with witness diff --git a/x/btcstaking/types/btc_undelegation.go b/x/btcstaking/types/btc_undelegation.go index 61063efa4..0e1237498 100644 --- a/x/btcstaking/types/btc_undelegation.go +++ b/x/btcstaking/types/btc_undelegation.go @@ -44,7 +44,7 @@ func (ud *BTCUndelegation) HasCovenantQuorums(covenantQuorum uint32) bool { // AddCovenantSigs adds a Schnorr signature on the unbonding tx, and // a list of adaptor signatures on the unbonding slashing tx, each encrypted -// by a BTC validator's PK this BTC delegation restakes to, from the given +// by a finality provider's PK this BTC delegation restakes to, from the given // covenant func (ud *BTCUndelegation) AddCovenantSigs( covPk *bbn.BIP340PubKey, diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go index 49383630f..df3063fae 100644 --- a/x/btcstaking/types/btc_undelegation_test.go +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -25,9 +25,9 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { delSK, _, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - valSK, valPK, err := datagen.GenRandomBTCKeyPair(r) + fpSK, fpPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - valPKList := []*btcec.PublicKey{valPK} + fpPKList := []*btcec.PublicKey{fpPK} // (3, 5) covenant committee covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) @@ -47,7 +47,7 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { btcDel, err := datagen.GenRandomBTCDelegation( r, t, - bbn.NewBIP340PKsFromBTCPKs(valPKList), + bbn.NewBIP340PKsFromBTCPKs(fpPKList), delSK, covenantSKs, covenantQuorum, @@ -69,7 +69,7 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { t, net, delSK, - valPKList, + fpPKList, covenantPKs, covenantQuorum, wire.NewOutPoint(&stakingTxHash, 0), @@ -94,7 +94,7 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { // covenant signs (using adaptor signature) the slashing tx covenantSigs, err := datagen.GenCovenantAdaptorSigs( covenantSKs, - []*btcec.PublicKey{valPK}, + []*btcec.PublicKey{fpPK}, testInfo.UnbondingTx, unbondingSlashingSpendInfo.GetPkScriptPath(), testInfo.SlashingTx, @@ -117,7 +117,7 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { } // build slashing tx with witness for spending the unbonding tx - unbondingSlashingTxWithWitness, err := btcDel.BuildUnbondingSlashingTxWithWitness(bsParams, net, valSK) + unbondingSlashingTxWithWitness, err := btcDel.BuildUnbondingSlashingTxWithWitness(bsParams, net, fpSK) require.NoError(t, err) // assert the execution diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7f9b80bb8..ff5c0d786 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -8,47 +8,47 @@ import ( bbn "github.com/babylonchain/babylon/types" ) -func (v *BTCValidator) IsSlashed() bool { - return v.SlashedBabylonHeight > 0 +func (fp *FinalityProvider) IsSlashed() bool { + return fp.SlashedBabylonHeight > 0 } -func (v *BTCValidator) ValidateBasic() error { +func (fp *FinalityProvider) ValidateBasic() error { // ensure fields are non-empty and well-formatted - if v.BabylonPk == nil { + if fp.BabylonPk == nil { return fmt.Errorf("empty Babylon public key") } - if v.BtcPk == nil { + if fp.BtcPk == nil { return fmt.Errorf("empty BTC public key") } - if _, err := v.BtcPk.ToBTCPK(); err != nil { + if _, err := fp.BtcPk.ToBTCPK(); err != nil { return fmt.Errorf("BtcPk is not correctly formatted: %w", err) } - if v.Pop == nil { + if fp.Pop == nil { return fmt.Errorf("empty proof of possession") } - if err := v.Pop.ValidateBasic(); err != nil { + if err := fp.Pop.ValidateBasic(); err != nil { return err } return nil } -// FilterTopNBTCValidators returns the top n validators based on VotingPower. -func FilterTopNBTCValidators(validators []*BTCValidatorWithMeta, n uint32) []*BTCValidatorWithMeta { - numVals := uint32(len(validators)) +// FilterTopNFinalityProviders returns the top n finality providers based on VotingPower. +func FilterTopNFinalityProviders(fps []*FinalityProviderWithMeta, n uint32) []*FinalityProviderWithMeta { + numFps := uint32(len(fps)) - // if the given validator set is no bigger than n, no need to do anything - if numVals <= n { - return validators + // if the given finality provider set is no bigger than n, no need to do anything + if numFps <= n { + return fps } - // Sort the validators slice, from higher to lower voting power - sort.SliceStable(validators, func(i, j int) bool { - return validators[i].VotingPower > validators[j].VotingPower + // Sort the finality providers slice, from higher to lower voting power + sort.SliceStable(fps, func(i, j int) bool { + return fps[i].VotingPower > fps[j].VotingPower }) // Return the top n elements - return validators[:n] + return fps[:n] } func ExistsDup(btcPKs []bbn.BIP340PubKey) bool { @@ -74,20 +74,20 @@ func NewSignatureInfo(pk *bbn.BIP340PubKey, sig *bbn.BIP340Signature) *Signature } // GetOrderedCovenantSignatures returns the ordered covenant adaptor signatures -// encrypted by the BTC validator's PK at the given index from the given list of +// encrypted by the finality provider's PK at the given index from the given list of // covenant signatures // the order of covenant adaptor signatures will follow the reverse lexicographical order // of signing public keys, in order to be used as tx witness -func GetOrderedCovenantSignatures(valIdx int, covSigsList []*CovenantAdaptorSignatures, params *Params) ([]*asig.AdaptorSignature, error) { +func GetOrderedCovenantSignatures(fpIdx int, covSigsList []*CovenantAdaptorSignatures, params *Params) ([]*asig.AdaptorSignature, error) { // construct the map where key is the covenant PK and value is this - // covenant member's adaptor signature encrypted by the given validator's PK + // covenant member's adaptor signature encrypted by the given finality provider's PK covSigsMap := map[string]*asig.AdaptorSignature{} for _, covSigs := range covSigsList { - // find the adaptor signature at the corresponding BTC validator's index - if valIdx >= len(covSigs.AdaptorSigs) { - return nil, fmt.Errorf("validator index is out of the scope") + // find the adaptor signature at the corresponding finality provider's index + if fpIdx >= len(covSigs.AdaptorSigs) { + return nil, fmt.Errorf("finality provider index is out of the scope") } - covSigBytes := covSigs.AdaptorSigs[valIdx] + covSigBytes := covSigs.AdaptorSigs[fpIdx] // decode the adaptor signature bytes covSig, err := asig.NewAdaptorSignatureFromBytes(covSigBytes) if err != nil { diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 139a02109..ead40de9b 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -69,41 +69,41 @@ func (BTCDelegationStatus) EnumDescriptor() ([]byte, []int) { return fileDescriptor_3851ae95ccfaf7db, []int{0} } -// BTCValidator defines a BTC validator -type BTCValidator struct { - // description defines the description terms for the BTC validator. +// FinalityProvider defines a finality provider +type FinalityProvider struct { + // description defines the description terms for the finality provider. Description *types.Description `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` - // commission defines the commission rate of BTC validator. + // commission defines the commission rate of the finality provider. Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` - // babylon_pk is the Babylon secp256k1 PK of this BTC validator + // babylon_pk is the Babylon secp256k1 PK of this finality provider BabylonPk *secp256k1.PubKey `protobuf:"bytes,3,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,5,opt,name=pop,proto3" json:"pop,omitempty"` // slashed_babylon_height indicates the Babylon height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed SlashedBabylonHeight uint64 `protobuf:"varint,6,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` // slashed_btc_height indicates the BTC height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed SlashedBtcHeight uint64 `protobuf:"varint,7,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` } -func (m *BTCValidator) Reset() { *m = BTCValidator{} } -func (m *BTCValidator) String() string { return proto.CompactTextString(m) } -func (*BTCValidator) ProtoMessage() {} -func (*BTCValidator) Descriptor() ([]byte, []int) { +func (m *FinalityProvider) Reset() { *m = FinalityProvider{} } +func (m *FinalityProvider) String() string { return proto.CompactTextString(m) } +func (*FinalityProvider) ProtoMessage() {} +func (*FinalityProvider) Descriptor() ([]byte, []int) { return fileDescriptor_3851ae95ccfaf7db, []int{0} } -func (m *BTCValidator) XXX_Unmarshal(b []byte) error { +func (m *FinalityProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *FinalityProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BTCValidator.Marshal(b, m, deterministic) + return xxx_messageInfo_FinalityProvider.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -113,84 +113,84 @@ func (m *BTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return b[:n], nil } } -func (m *BTCValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCValidator.Merge(m, src) +func (m *FinalityProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_FinalityProvider.Merge(m, src) } -func (m *BTCValidator) XXX_Size() int { +func (m *FinalityProvider) XXX_Size() int { return m.Size() } -func (m *BTCValidator) XXX_DiscardUnknown() { - xxx_messageInfo_BTCValidator.DiscardUnknown(m) +func (m *FinalityProvider) XXX_DiscardUnknown() { + xxx_messageInfo_FinalityProvider.DiscardUnknown(m) } -var xxx_messageInfo_BTCValidator proto.InternalMessageInfo +var xxx_messageInfo_FinalityProvider proto.InternalMessageInfo -func (m *BTCValidator) GetDescription() *types.Description { +func (m *FinalityProvider) GetDescription() *types.Description { if m != nil { return m.Description } return nil } -func (m *BTCValidator) GetBabylonPk() *secp256k1.PubKey { +func (m *FinalityProvider) GetBabylonPk() *secp256k1.PubKey { if m != nil { return m.BabylonPk } return nil } -func (m *BTCValidator) GetPop() *ProofOfPossession { +func (m *FinalityProvider) GetPop() *ProofOfPossession { if m != nil { return m.Pop } return nil } -func (m *BTCValidator) GetSlashedBabylonHeight() uint64 { +func (m *FinalityProvider) GetSlashedBabylonHeight() uint64 { if m != nil { return m.SlashedBabylonHeight } return 0 } -func (m *BTCValidator) GetSlashedBtcHeight() uint64 { +func (m *FinalityProvider) GetSlashedBtcHeight() uint64 { if m != nil { return m.SlashedBtcHeight } return 0 } -// BTCValidatorWithMeta wraps the BTCValidator with meta data. -type BTCValidatorWithMeta struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator +// FinalityProviderWithMeta wraps the FinalityProvider with meta data. +type FinalityProviderWithMeta struct { + // btc_pk is the Bitcoin secp256k1 PK of thisfinality provider // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // height is the queried Babylon height Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` - // voting_power is the voting power of this BTC validator at the given height + // voting_power is the voting power of this finality provider at the given height VotingPower uint64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` // slashed_babylon_height indicates the Babylon height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed SlashedBabylonHeight uint64 `protobuf:"varint,4,opt,name=slashed_babylon_height,json=slashedBabylonHeight,proto3" json:"slashed_babylon_height,omitempty"` // slashed_btc_height indicates the BTC height when - // the BTC validator is slashed. - // if it's 0 then the BTC validator is not slashed + // the finality provider is slashed. + // if it's 0 then the finality provider is not slashed SlashedBtcHeight uint64 `protobuf:"varint,5,opt,name=slashed_btc_height,json=slashedBtcHeight,proto3" json:"slashed_btc_height,omitempty"` } -func (m *BTCValidatorWithMeta) Reset() { *m = BTCValidatorWithMeta{} } -func (m *BTCValidatorWithMeta) String() string { return proto.CompactTextString(m) } -func (*BTCValidatorWithMeta) ProtoMessage() {} -func (*BTCValidatorWithMeta) Descriptor() ([]byte, []int) { +func (m *FinalityProviderWithMeta) Reset() { *m = FinalityProviderWithMeta{} } +func (m *FinalityProviderWithMeta) String() string { return proto.CompactTextString(m) } +func (*FinalityProviderWithMeta) ProtoMessage() {} +func (*FinalityProviderWithMeta) Descriptor() ([]byte, []int) { return fileDescriptor_3851ae95ccfaf7db, []int{1} } -func (m *BTCValidatorWithMeta) XXX_Unmarshal(b []byte) error { +func (m *FinalityProviderWithMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BTCValidatorWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *FinalityProviderWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BTCValidatorWithMeta.Marshal(b, m, deterministic) + return xxx_messageInfo_FinalityProviderWithMeta.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -200,40 +200,40 @@ func (m *BTCValidatorWithMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte return b[:n], nil } } -func (m *BTCValidatorWithMeta) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCValidatorWithMeta.Merge(m, src) +func (m *FinalityProviderWithMeta) XXX_Merge(src proto.Message) { + xxx_messageInfo_FinalityProviderWithMeta.Merge(m, src) } -func (m *BTCValidatorWithMeta) XXX_Size() int { +func (m *FinalityProviderWithMeta) XXX_Size() int { return m.Size() } -func (m *BTCValidatorWithMeta) XXX_DiscardUnknown() { - xxx_messageInfo_BTCValidatorWithMeta.DiscardUnknown(m) +func (m *FinalityProviderWithMeta) XXX_DiscardUnknown() { + xxx_messageInfo_FinalityProviderWithMeta.DiscardUnknown(m) } -var xxx_messageInfo_BTCValidatorWithMeta proto.InternalMessageInfo +var xxx_messageInfo_FinalityProviderWithMeta proto.InternalMessageInfo -func (m *BTCValidatorWithMeta) GetHeight() uint64 { +func (m *FinalityProviderWithMeta) GetHeight() uint64 { if m != nil { return m.Height } return 0 } -func (m *BTCValidatorWithMeta) GetVotingPower() uint64 { +func (m *FinalityProviderWithMeta) GetVotingPower() uint64 { if m != nil { return m.VotingPower } return 0 } -func (m *BTCValidatorWithMeta) GetSlashedBabylonHeight() uint64 { +func (m *FinalityProviderWithMeta) GetSlashedBabylonHeight() uint64 { if m != nil { return m.SlashedBabylonHeight } return 0 } -func (m *BTCValidatorWithMeta) GetSlashedBtcHeight() uint64 { +func (m *FinalityProviderWithMeta) GetSlashedBtcHeight() uint64 { if m != nil { return m.SlashedBtcHeight } @@ -249,11 +249,11 @@ type BTCDelegation struct { BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + // to multiple finality providers + FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock StartHeight uint64 `protobuf:"varint,5,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` @@ -269,7 +269,7 @@ type BTCDelegation struct { StakingOutputIdx uint32 `protobuf:"varint,9,opt,name=staking_output_idx,json=stakingOutputIdx,proto3" json:"staking_output_idx,omitempty"` // slashing_tx is the slashing tx // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or covenant yet. + // finality provider or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,10,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). @@ -392,7 +392,7 @@ type BTCUndelegation struct { UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by - // validator or covenant yet. + // finality provider or covenant yet. SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_unbonding_sig is the signature on the unbonding tx // by the delegator (i.e., SK corresponding to btc_pk). @@ -672,11 +672,11 @@ func (m *SignatureInfo) XXX_DiscardUnknown() { var xxx_messageInfo_SignatureInfo proto.InternalMessageInfo // CovenantAdaptorSignatures is a list adaptor signatures signed by the -// covenant with different validator's public keys as encryption keys +// covenant with different finality provider's public keys as encryption keys type CovenantAdaptorSignatures struct { // cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature CovPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=cov_pk,json=covPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"cov_pk,omitempty"` - // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC validator's public key + // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key AdaptorSigs [][]byte `protobuf:"bytes,2,rep,name=adaptor_sigs,json=adaptorSigs,proto3" json:"adaptor_sigs,omitempty"` } @@ -722,8 +722,8 @@ func (m *CovenantAdaptorSignatures) GetAdaptorSigs() [][]byte { func init() { proto.RegisterEnum("babylon.btcstaking.v1.BTCDelegationStatus", BTCDelegationStatus_name, BTCDelegationStatus_value) - proto.RegisterType((*BTCValidator)(nil), "babylon.btcstaking.v1.BTCValidator") - proto.RegisterType((*BTCValidatorWithMeta)(nil), "babylon.btcstaking.v1.BTCValidatorWithMeta") + proto.RegisterType((*FinalityProvider)(nil), "babylon.btcstaking.v1.FinalityProvider") + proto.RegisterType((*FinalityProviderWithMeta)(nil), "babylon.btcstaking.v1.FinalityProviderWithMeta") proto.RegisterType((*BTCDelegation)(nil), "babylon.btcstaking.v1.BTCDelegation") proto.RegisterType((*BTCUndelegation)(nil), "babylon.btcstaking.v1.BTCUndelegation") proto.RegisterType((*BTCUndelegationInfo)(nil), "babylon.btcstaking.v1.BTCUndelegationInfo") @@ -738,81 +738,81 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1123 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4d, 0x6f, 0x1a, 0x47, - 0x18, 0xf6, 0x02, 0xc6, 0xe1, 0x05, 0x1a, 0x32, 0x25, 0xce, 0x26, 0x56, 0x81, 0xd2, 0x34, 0x42, - 0x55, 0xb3, 0xc4, 0xce, 0x87, 0xda, 0x1e, 0x2a, 0x05, 0xe3, 0x36, 0x28, 0x09, 0xa1, 0x0b, 0x4e, - 0xd5, 0x4a, 0x2d, 0x1a, 0x76, 0xc7, 0xcb, 0x0a, 0xd8, 0xd9, 0x32, 0x03, 0x85, 0x7b, 0xaf, 0x95, - 0xfa, 0x1f, 0xda, 0x9f, 0xd0, 0x1f, 0xd0, 0x43, 0x0f, 0x3d, 0x46, 0xed, 0xa5, 0xf2, 0xc1, 0xaa, - 0xec, 0x3f, 0x52, 0xed, 0xec, 0xb0, 0xbb, 0xb8, 0x76, 0xd2, 0x04, 0xdf, 0x98, 0xf7, 0xe3, 0x79, - 0x3f, 0x9e, 0x87, 0x99, 0x85, 0x5b, 0x3d, 0xdc, 0x9b, 0x0f, 0xa9, 0x53, 0xed, 0x71, 0x83, 0x71, - 0x3c, 0xb0, 0x1d, 0xab, 0x3a, 0xdd, 0x8e, 0x9c, 0x34, 0x77, 0x4c, 0x39, 0x45, 0x57, 0x65, 0x9c, - 0x16, 0xf1, 0x4c, 0xb7, 0x6f, 0xe4, 0x2d, 0x6a, 0x51, 0x11, 0x51, 0xf5, 0x7e, 0xf9, 0xc1, 0x37, - 0xae, 0x1b, 0x94, 0x8d, 0x28, 0xeb, 0xfa, 0x0e, 0xff, 0x20, 0x5d, 0x65, 0xff, 0x54, 0x35, 0xc6, - 0x73, 0x97, 0xd3, 0x2a, 0x23, 0x86, 0xbb, 0x73, 0xff, 0xc1, 0x60, 0xbb, 0x3a, 0x20, 0xf3, 0x45, - 0xcc, 0x4d, 0x19, 0x13, 0xf6, 0xd3, 0x23, 0x1c, 0x6f, 0x57, 0x97, 0x3a, 0xba, 0x51, 0x3c, 0xbb, - 0x73, 0x97, 0xba, 0x7e, 0x40, 0xf9, 0xaf, 0x38, 0x64, 0x6a, 0x9d, 0xdd, 0xe7, 0x78, 0x68, 0x9b, - 0x98, 0xd3, 0x31, 0xda, 0x83, 0xb4, 0x49, 0x98, 0x31, 0xb6, 0x5d, 0x6e, 0x53, 0x47, 0x55, 0x4a, - 0x4a, 0x25, 0xbd, 0xf3, 0x9e, 0x26, 0xfb, 0x0b, 0xa7, 0x12, 0xd5, 0xb4, 0x7a, 0x18, 0xaa, 0x47, - 0xf3, 0xd0, 0x53, 0x00, 0x83, 0x8e, 0x46, 0x36, 0x63, 0x1e, 0x4a, 0xac, 0xa4, 0x54, 0x52, 0xb5, - 0xdb, 0x87, 0x47, 0xc5, 0x2d, 0x1f, 0x88, 0x99, 0x03, 0xcd, 0xa6, 0xd5, 0x11, 0xe6, 0x7d, 0xed, - 0x09, 0xb1, 0xb0, 0x31, 0xaf, 0x13, 0xe3, 0xcf, 0x5f, 0x6f, 0x83, 0xac, 0x53, 0x27, 0x86, 0x1e, - 0x01, 0x40, 0x9f, 0x02, 0xc8, 0x49, 0xba, 0xee, 0x40, 0x8d, 0x8b, 0xa6, 0x8a, 0x8b, 0xa6, 0xfc, - 0x35, 0x69, 0xc1, 0x9a, 0xb4, 0xd6, 0xa4, 0xf7, 0x98, 0xcc, 0xf5, 0x94, 0x4c, 0x69, 0x0d, 0xd0, - 0x53, 0x48, 0xf6, 0xb8, 0xe1, 0xe5, 0x26, 0x4a, 0x4a, 0x25, 0x53, 0x7b, 0x70, 0x78, 0x54, 0xdc, - 0xb1, 0x6c, 0xde, 0x9f, 0xf4, 0x34, 0x83, 0x8e, 0xaa, 0x32, 0xd2, 0xe8, 0x63, 0xdb, 0x59, 0x1c, - 0xaa, 0x7c, 0xee, 0x12, 0xa6, 0xd5, 0x1a, 0xad, 0xbb, 0xf7, 0xee, 0x48, 0xc8, 0xf5, 0x1e, 0x37, - 0x5a, 0x03, 0xf4, 0x09, 0xc4, 0x5d, 0xea, 0xaa, 0xeb, 0xa2, 0x8f, 0x8a, 0x76, 0x26, 0xed, 0x5a, - 0x6b, 0x4c, 0xe9, 0xc1, 0xb3, 0x83, 0x16, 0x65, 0x8c, 0x88, 0x29, 0x74, 0x2f, 0x09, 0xdd, 0x83, - 0x4d, 0x36, 0xc4, 0xac, 0x4f, 0xcc, 0xee, 0x62, 0xa4, 0x3e, 0xb1, 0xad, 0x3e, 0x57, 0x93, 0x25, - 0xa5, 0x92, 0xd0, 0xf3, 0xd2, 0x5b, 0xf3, 0x9d, 0x8f, 0x84, 0x0f, 0x7d, 0x08, 0x28, 0xc8, 0xe2, - 0xc6, 0x22, 0x63, 0x43, 0x64, 0xe4, 0x16, 0x19, 0xdc, 0xf0, 0xa3, 0xcb, 0x3f, 0xc4, 0x20, 0x1f, - 0x65, 0xf5, 0x4b, 0x9b, 0xf7, 0x9f, 0x12, 0x8e, 0x23, 0x7b, 0x50, 0x2e, 0x62, 0x0f, 0x9b, 0x90, - 0x94, 0x9d, 0xc4, 0x44, 0x27, 0xf2, 0x84, 0xde, 0x85, 0xcc, 0x94, 0x72, 0xdb, 0xb1, 0xba, 0x2e, - 0xfd, 0x9e, 0x8c, 0x05, 0x61, 0x09, 0x3d, 0xed, 0xdb, 0x5a, 0x9e, 0xe9, 0x25, 0x6b, 0x48, 0xbc, - 0xf6, 0x1a, 0xd6, 0xcf, 0x59, 0xc3, 0xcf, 0x49, 0xc8, 0xd6, 0x3a, 0xbb, 0x75, 0x32, 0x24, 0x16, - 0xe6, 0xff, 0xd5, 0x91, 0xb2, 0x82, 0x8e, 0x62, 0x17, 0xa8, 0xa3, 0xf8, 0x9b, 0xe8, 0xe8, 0x1b, - 0xb8, 0x3c, 0xc5, 0xc3, 0xae, 0xdf, 0x4e, 0x77, 0x68, 0x33, 0x6f, 0x73, 0xf1, 0x15, 0x7a, 0xca, - 0x4c, 0xf1, 0xb0, 0xe6, 0xb5, 0xf5, 0xc4, 0x66, 0x82, 0x42, 0xc6, 0xf1, 0x98, 0x2f, 0xef, 0x38, - 0x2d, 0x6c, 0x92, 0x8c, 0x77, 0x00, 0x88, 0x63, 0x2e, 0xab, 0x37, 0x45, 0x1c, 0x53, 0xba, 0xb7, - 0x20, 0xc5, 0x29, 0xc7, 0xc3, 0x2e, 0xc3, 0x0b, 0xa5, 0x5e, 0x12, 0x86, 0x36, 0x16, 0xb9, 0x72, - 0xc4, 0x2e, 0x9f, 0xa9, 0x97, 0xbc, 0x65, 0xea, 0x29, 0x69, 0xe9, 0xcc, 0x04, 0xcf, 0xd2, 0x4d, - 0x27, 0xdc, 0x9d, 0xf0, 0xae, 0x6d, 0xce, 0xd4, 0x54, 0x49, 0xa9, 0x64, 0xf5, 0x9c, 0xf4, 0x3c, - 0x13, 0x8e, 0x86, 0x39, 0x43, 0x3b, 0x90, 0x16, 0xdc, 0x4b, 0x34, 0x10, 0xd4, 0x5c, 0x39, 0x3c, - 0x2a, 0x7a, 0xec, 0xb7, 0xa5, 0xa7, 0x33, 0xd3, 0x81, 0x05, 0xbf, 0xd1, 0xb7, 0x90, 0x35, 0x7d, - 0x5d, 0xd0, 0x71, 0x97, 0xd9, 0x96, 0x9a, 0x16, 0x59, 0x1f, 0x1f, 0x1e, 0x15, 0xef, 0xbf, 0xce, - 0xf2, 0xda, 0xb6, 0xe5, 0x60, 0x3e, 0x19, 0x13, 0x3d, 0x13, 0xe0, 0xb5, 0x6d, 0x0b, 0xed, 0x43, - 0xd6, 0xa0, 0x53, 0xe2, 0x60, 0x87, 0x7b, 0xf0, 0x4c, 0xcd, 0x94, 0xe2, 0x95, 0xf4, 0xce, 0x9d, - 0x73, 0x48, 0xde, 0x95, 0xb1, 0x0f, 0x4d, 0xec, 0xfa, 0x08, 0x3e, 0x2a, 0xd3, 0x33, 0x0b, 0x98, - 0xb6, 0x6d, 0x31, 0xf4, 0x05, 0xe4, 0x3c, 0xc6, 0x27, 0x8e, 0x19, 0x88, 0x5a, 0xcd, 0x0a, 0xf9, - 0xdc, 0x3a, 0x07, 0xb9, 0xd6, 0xd9, 0xdd, 0x8f, 0x44, 0xeb, 0x97, 0x7b, 0xdc, 0x88, 0x1a, 0xca, - 0xbf, 0x25, 0xe0, 0xf2, 0xa9, 0x20, 0x8f, 0xfd, 0x89, 0xd3, 0xa3, 0x8e, 0x29, 0x57, 0x2a, 0x6e, - 0x0b, 0x3d, 0x1d, 0xd8, 0x3a, 0x33, 0xf4, 0x3e, 0xbc, 0x15, 0x09, 0xb1, 0x47, 0x44, 0xfc, 0x25, - 0xb2, 0x7a, 0x36, 0x0c, 0xb2, 0x47, 0xe4, 0x34, 0x37, 0xf1, 0xff, 0xc3, 0xcd, 0x77, 0x70, 0x2d, - 0xe4, 0x26, 0x2c, 0xe2, 0xb1, 0x94, 0x58, 0x95, 0xa5, 0xab, 0x01, 0xf2, 0xfe, 0x02, 0xd8, 0xa3, - 0x8b, 0xc2, 0x66, 0x44, 0x0e, 0x8b, 0x86, 0xbd, 0x8a, 0xeb, 0xab, 0x56, 0xcc, 0x87, 0xba, 0x90, - 0xb8, 0x5e, 0xc1, 0x03, 0xd8, 0x0c, 0xf5, 0x11, 0xa9, 0xc7, 0xd4, 0xe4, 0x1b, 0x0a, 0x25, 0x1f, - 0x08, 0x25, 0x2c, 0xc3, 0x90, 0x01, 0x5b, 0x41, 0x9d, 0xa5, 0x55, 0xfa, 0x57, 0xc6, 0x86, 0x28, - 0x76, 0xf3, 0x9c, 0x62, 0x01, 0x7a, 0xc3, 0x39, 0xa0, 0xba, 0xba, 0x00, 0x8a, 0x6e, 0xce, 0xbb, - 0x2c, 0xca, 0xbf, 0x2b, 0xf0, 0xf6, 0x29, 0x09, 0x79, 0x19, 0x17, 0x28, 0xa3, 0x57, 0x8c, 0x11, - 0xbf, 0x90, 0x31, 0xda, 0x70, 0x2d, 0x7c, 0x2e, 0xe8, 0x38, 0x7c, 0x37, 0x18, 0xfa, 0x08, 0x12, - 0x26, 0x19, 0x32, 0x55, 0x79, 0x69, 0xa1, 0xa5, 0xc7, 0x46, 0x17, 0x19, 0xe5, 0x26, 0x6c, 0x9d, - 0x0d, 0xda, 0x70, 0x4c, 0x32, 0x43, 0x55, 0xc8, 0x87, 0x17, 0x61, 0xb7, 0x8f, 0x59, 0xdf, 0x9f, - 0xc8, 0x2b, 0x94, 0xd1, 0xaf, 0x04, 0x57, 0xe2, 0x23, 0xcc, 0xfa, 0xa2, 0xc9, 0x5f, 0x14, 0xc8, - 0x2e, 0x0d, 0x84, 0x3e, 0x83, 0xd8, 0xca, 0x0f, 0x7a, 0xcc, 0x1d, 0xa0, 0xc7, 0x10, 0xf7, 0x04, - 0x1f, 0x5b, 0x55, 0xf0, 0x1e, 0x4a, 0xf9, 0x47, 0x05, 0xae, 0x9f, 0xab, 0x55, 0xef, 0x1d, 0x35, - 0xe8, 0xf4, 0x02, 0xbe, 0x43, 0x0c, 0x3a, 0x6d, 0x0d, 0x3c, 0x9d, 0x61, 0xbf, 0x86, 0xff, 0x17, - 0x8a, 0x89, 0xe5, 0xa5, 0x71, 0x50, 0x97, 0x7d, 0xb0, 0x27, 0x14, 0x1a, 0x6e, 0xbf, 0xcd, 0x31, - 0x9f, 0x30, 0x94, 0x86, 0x8d, 0xd6, 0x5e, 0xb3, 0xde, 0x68, 0x7e, 0x9e, 0x5b, 0x43, 0x00, 0xc9, - 0x87, 0xbb, 0x9d, 0xc6, 0xf3, 0xbd, 0x9c, 0x82, 0x32, 0x70, 0x69, 0xbf, 0x59, 0x7b, 0xd6, 0xac, - 0xef, 0xd5, 0x73, 0x31, 0xb4, 0x01, 0xf1, 0x87, 0xcd, 0xaf, 0x72, 0xf1, 0xda, 0x93, 0x3f, 0x8e, - 0x0b, 0xca, 0x8b, 0xe3, 0x82, 0xf2, 0xcf, 0x71, 0x41, 0xf9, 0xe9, 0xa4, 0xb0, 0xf6, 0xe2, 0xa4, - 0xb0, 0xf6, 0xf7, 0x49, 0x61, 0xed, 0xeb, 0x57, 0xb6, 0x3f, 0x8b, 0x7e, 0x84, 0x8b, 0x59, 0x7a, - 0x49, 0xf1, 0x11, 0x7e, 0xf7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x8e, 0xcb, 0xae, 0x61, - 0x0c, 0x00, 0x00, -} - -func (m *BTCValidator) Marshal() (dAtA []byte, err error) { + // 1127 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0x1a, 0x47, + 0x1b, 0xf6, 0x02, 0xc6, 0xe1, 0x05, 0x12, 0x32, 0x9f, 0xe3, 0x6c, 0x62, 0x7d, 0x40, 0x69, 0x1a, + 0xa1, 0xaa, 0x59, 0x62, 0xe7, 0x47, 0x6d, 0x0f, 0x2a, 0x05, 0xe3, 0x34, 0x28, 0x09, 0xa1, 0x0b, + 0x6e, 0xd5, 0x56, 0x2a, 0x5a, 0x76, 0x87, 0x65, 0x05, 0xec, 0x6c, 0x99, 0x81, 0xc2, 0x1d, 0xf4, + 0xa4, 0x52, 0x6f, 0xa1, 0x52, 0x2f, 0xa1, 0x17, 0xd0, 0x83, 0x1e, 0xf4, 0x30, 0xea, 0x49, 0x2b, + 0x1f, 0x58, 0x95, 0x7d, 0x23, 0xd5, 0xcc, 0x0e, 0xbb, 0x8b, 0x6b, 0x27, 0x4d, 0xf0, 0x19, 0xf3, + 0xfe, 0x3c, 0xef, 0xcf, 0xf3, 0x30, 0xb3, 0x70, 0xbb, 0x6b, 0x74, 0xe7, 0x43, 0xe2, 0x56, 0xba, + 0xcc, 0xa4, 0xcc, 0x18, 0x38, 0xae, 0x5d, 0x99, 0xee, 0x44, 0x4e, 0x9a, 0x37, 0x26, 0x8c, 0xa0, + 0x6b, 0x32, 0x4e, 0x8b, 0x78, 0xa6, 0x3b, 0x37, 0x37, 0x6d, 0x62, 0x13, 0x11, 0x51, 0xe1, 0xbf, + 0xfc, 0xe0, 0x9b, 0x37, 0x4c, 0x42, 0x47, 0x84, 0x76, 0x7c, 0x87, 0x7f, 0x90, 0xae, 0x92, 0x7f, + 0xaa, 0x98, 0xe3, 0xb9, 0xc7, 0x48, 0x85, 0x62, 0xd3, 0xdb, 0x7d, 0xf0, 0x70, 0xb0, 0x53, 0x19, + 0xe0, 0xf9, 0x22, 0xe6, 0x96, 0x8c, 0x09, 0xfb, 0xe9, 0x62, 0x66, 0xec, 0x54, 0x96, 0x3a, 0xba, + 0x59, 0x38, 0xbb, 0x73, 0x8f, 0x78, 0x7e, 0x40, 0xe9, 0xcf, 0x38, 0xe4, 0x1e, 0x3b, 0xae, 0x31, + 0x74, 0xd8, 0xbc, 0x39, 0x26, 0x53, 0xc7, 0xc2, 0x63, 0xb4, 0x0f, 0x69, 0x0b, 0x53, 0x73, 0xec, + 0x78, 0xcc, 0x21, 0xae, 0xaa, 0x14, 0x95, 0x72, 0x7a, 0xf7, 0x5d, 0x4d, 0xf6, 0x18, 0x4e, 0x26, + 0x2a, 0x6a, 0xb5, 0x30, 0x54, 0x8f, 0xe6, 0xa1, 0xe7, 0x00, 0x26, 0x19, 0x8d, 0x1c, 0x4a, 0x39, + 0x4a, 0xac, 0xa8, 0x94, 0x53, 0xd5, 0x3b, 0x87, 0x47, 0x85, 0x6d, 0x1f, 0x88, 0x5a, 0x03, 0xcd, + 0x21, 0x95, 0x91, 0xc1, 0xfa, 0xda, 0x33, 0x6c, 0x1b, 0xe6, 0xbc, 0x86, 0xcd, 0x3f, 0x7e, 0xb9, + 0x03, 0xb2, 0x4e, 0x0d, 0x9b, 0x7a, 0x04, 0x00, 0x7d, 0x02, 0x20, 0xa7, 0xe9, 0x78, 0x03, 0x35, + 0x2e, 0x9a, 0x2a, 0x2c, 0x9a, 0xf2, 0x57, 0xa5, 0x05, 0xab, 0xd2, 0x9a, 0x93, 0xee, 0x53, 0x3c, + 0xd7, 0x53, 0x32, 0xa5, 0x39, 0x40, 0xcf, 0x21, 0xd9, 0x65, 0x26, 0xcf, 0x4d, 0x14, 0x95, 0x72, + 0xa6, 0xfa, 0xf0, 0xf0, 0xa8, 0xb0, 0x6b, 0x3b, 0xac, 0x3f, 0xe9, 0x6a, 0x26, 0x19, 0x55, 0x64, + 0xa4, 0xd9, 0x37, 0x1c, 0x77, 0x71, 0xa8, 0xb0, 0xb9, 0x87, 0xa9, 0x56, 0xad, 0x37, 0xef, 0xdd, + 0xbf, 0x2b, 0x21, 0xd7, 0xbb, 0xcc, 0x6c, 0x0e, 0xd0, 0xc7, 0x10, 0xf7, 0x88, 0xa7, 0xae, 0x8b, + 0x3e, 0xca, 0xda, 0x99, 0xd4, 0x6b, 0xcd, 0x31, 0x21, 0xbd, 0x17, 0xbd, 0x26, 0xa1, 0x14, 0x8b, + 0x29, 0x74, 0x9e, 0x84, 0xee, 0xc3, 0x16, 0x1d, 0x1a, 0xb4, 0x8f, 0xad, 0xce, 0x62, 0xa4, 0x3e, + 0x76, 0xec, 0x3e, 0x53, 0x93, 0x45, 0xa5, 0x9c, 0xd0, 0x37, 0xa5, 0xb7, 0xea, 0x3b, 0x9f, 0x08, + 0x1f, 0xfa, 0x00, 0x50, 0x90, 0xc5, 0xcc, 0x45, 0xc6, 0x86, 0xc8, 0xc8, 0x2d, 0x32, 0x98, 0xe9, + 0x47, 0x97, 0xbe, 0x8f, 0x81, 0x7a, 0x9a, 0xd9, 0x2f, 0x1c, 0xd6, 0x7f, 0x8e, 0x99, 0x11, 0xd9, + 0x85, 0x72, 0x11, 0xbb, 0xd8, 0x82, 0xa4, 0xec, 0x26, 0x26, 0xba, 0x91, 0x27, 0xf4, 0x0e, 0x64, + 0xa6, 0x84, 0x39, 0xae, 0xdd, 0xf1, 0xc8, 0x77, 0x78, 0x2c, 0x48, 0x4b, 0xe8, 0x69, 0xdf, 0xd6, + 0xe4, 0xa6, 0x57, 0xac, 0x22, 0xf1, 0xc6, 0xab, 0x58, 0x3f, 0x67, 0x15, 0x3f, 0x25, 0x21, 0x5b, + 0x6d, 0xef, 0xd5, 0xf0, 0x10, 0xdb, 0x06, 0xfb, 0xb7, 0x96, 0x94, 0x15, 0xb4, 0x14, 0xbb, 0x40, + 0x2d, 0xc5, 0xdf, 0x46, 0x4b, 0x5f, 0xc3, 0xe5, 0x9e, 0xd7, 0xf1, 0xbb, 0xe9, 0x0c, 0x1d, 0xca, + 0x17, 0x17, 0x5f, 0xa1, 0xa5, 0x74, 0xcf, 0xab, 0xf2, 0xa6, 0x9e, 0x39, 0x54, 0x10, 0x48, 0x99, + 0x31, 0x66, 0xcb, 0x1b, 0x4e, 0x0b, 0x9b, 0xa4, 0xe2, 0xff, 0x00, 0xd8, 0xb5, 0x96, 0xf5, 0x9b, + 0xc2, 0xae, 0x25, 0xdd, 0xdb, 0x90, 0x62, 0x84, 0x19, 0xc3, 0x0e, 0x35, 0x16, 0x5a, 0xbd, 0x24, + 0x0c, 0x2d, 0x43, 0xe4, 0xca, 0x01, 0x3b, 0x6c, 0xa6, 0x5e, 0xe2, 0xab, 0xd4, 0x53, 0xd2, 0xd2, + 0x9e, 0x09, 0x96, 0xa5, 0x9b, 0x4c, 0x98, 0x37, 0x61, 0x1d, 0xc7, 0x9a, 0xa9, 0xa9, 0xa2, 0x52, + 0xce, 0xea, 0x39, 0xe9, 0x79, 0x21, 0x1c, 0x75, 0x6b, 0x86, 0x76, 0x21, 0x2d, 0x98, 0x97, 0x68, + 0x20, 0x88, 0xb9, 0x7a, 0x78, 0x54, 0xe0, 0xdc, 0xb7, 0xa4, 0xa7, 0x3d, 0xd3, 0x81, 0x06, 0xbf, + 0xd1, 0x37, 0x90, 0xb5, 0x7c, 0x55, 0x90, 0x71, 0x87, 0x3a, 0xb6, 0x9a, 0x16, 0x59, 0x1f, 0x1d, + 0x1e, 0x15, 0x1e, 0xbc, 0xc9, 0xee, 0x5a, 0x8e, 0xed, 0x1a, 0x6c, 0x32, 0xc6, 0x7a, 0x26, 0xc0, + 0x6b, 0x39, 0x36, 0x3a, 0x80, 0xac, 0x49, 0xa6, 0xd8, 0x35, 0x5c, 0xc6, 0xe1, 0xa9, 0x9a, 0x29, + 0xc6, 0xcb, 0xe9, 0xdd, 0xbb, 0xe7, 0x50, 0xbc, 0x27, 0x63, 0x1f, 0x59, 0x86, 0xe7, 0x23, 0xf8, + 0xa8, 0x54, 0xcf, 0x2c, 0x60, 0x5a, 0x8e, 0x4d, 0xd1, 0x67, 0x90, 0xe3, 0x84, 0x4f, 0x5c, 0x2b, + 0x90, 0xb4, 0x9a, 0x15, 0xe2, 0xb9, 0x7d, 0x0e, 0x72, 0xb5, 0xbd, 0x77, 0x10, 0x89, 0xd6, 0xaf, + 0x74, 0x99, 0x19, 0x35, 0x94, 0x7e, 0x4d, 0xc0, 0x95, 0x53, 0x41, 0x9c, 0xfd, 0x89, 0xdb, 0x25, + 0xae, 0x25, 0x57, 0x2a, 0xee, 0x0a, 0x3d, 0x1d, 0xd8, 0xda, 0x33, 0xf4, 0x1e, 0x5c, 0x8e, 0x84, + 0x38, 0x23, 0x2c, 0xfe, 0x10, 0x59, 0x3d, 0x1b, 0x06, 0x39, 0x23, 0x7c, 0x9a, 0x9b, 0xf8, 0x7f, + 0xe1, 0xe6, 0x5b, 0xb8, 0x1e, 0x72, 0x13, 0x16, 0xe1, 0x2c, 0x25, 0x56, 0x65, 0xe9, 0x5a, 0x80, + 0x7c, 0xb0, 0x00, 0xe6, 0x74, 0x11, 0xd8, 0x8a, 0xc8, 0x61, 0xd1, 0x30, 0xaf, 0xb8, 0xbe, 0x6a, + 0xc5, 0xcd, 0x50, 0x17, 0x12, 0x97, 0x17, 0xec, 0xc1, 0x56, 0xa8, 0x8f, 0x48, 0x3d, 0xaa, 0x26, + 0xdf, 0x52, 0x28, 0x9b, 0x81, 0x50, 0xc2, 0x32, 0x14, 0x99, 0xb0, 0x1d, 0xd4, 0x59, 0x5a, 0xa5, + 0x7f, 0x63, 0x6c, 0x88, 0x62, 0xb7, 0xce, 0x29, 0x16, 0xa0, 0xd7, 0xdd, 0x1e, 0xd1, 0xd5, 0x05, + 0x50, 0x74, 0x73, 0xfc, 0xb2, 0x28, 0xfd, 0xa6, 0xc0, 0xff, 0x4e, 0x49, 0x88, 0x67, 0x5c, 0xa0, + 0x8c, 0x5e, 0x33, 0x46, 0xfc, 0x42, 0xc6, 0x68, 0xc1, 0xf5, 0xf0, 0xb1, 0x20, 0xe3, 0xf0, 0xd5, + 0xa0, 0xe8, 0x43, 0x48, 0x58, 0x78, 0x48, 0x55, 0xe5, 0x95, 0x85, 0x96, 0x9e, 0x1a, 0x5d, 0x64, + 0x94, 0x1a, 0xb0, 0x7d, 0x36, 0x68, 0xdd, 0xb5, 0xf0, 0x0c, 0x55, 0x60, 0x33, 0xbc, 0x08, 0x3b, + 0x7d, 0x83, 0xf6, 0xfd, 0x89, 0x78, 0xa1, 0x8c, 0x7e, 0x35, 0xb8, 0x12, 0x9f, 0x18, 0xb4, 0x2f, + 0x9a, 0xfc, 0x59, 0x81, 0xec, 0xd2, 0x40, 0xe8, 0x31, 0xc4, 0x56, 0x7e, 0xce, 0x63, 0xde, 0x00, + 0x3d, 0x85, 0x38, 0x17, 0x7c, 0x6c, 0x55, 0xc1, 0x73, 0x94, 0xd2, 0x0f, 0x0a, 0xdc, 0x38, 0x57, + 0xab, 0xfc, 0x15, 0x35, 0xc9, 0xf4, 0x02, 0xbe, 0x42, 0x4c, 0x32, 0x6d, 0x0e, 0xb8, 0xce, 0x0c, + 0xbf, 0x86, 0xff, 0x17, 0x8a, 0x89, 0xe5, 0xa5, 0x8d, 0xa0, 0x2e, 0x7d, 0x7f, 0x5f, 0x28, 0x34, + 0xdc, 0x7e, 0x8b, 0x19, 0x6c, 0x42, 0x51, 0x1a, 0x36, 0x9a, 0xfb, 0x8d, 0x5a, 0xbd, 0xf1, 0x69, + 0x6e, 0x0d, 0x01, 0x24, 0x1f, 0xed, 0xb5, 0xeb, 0x9f, 0xef, 0xe7, 0x14, 0x94, 0x81, 0x4b, 0x07, + 0x8d, 0xea, 0x8b, 0x46, 0x6d, 0xbf, 0x96, 0x8b, 0xa1, 0x0d, 0x88, 0x3f, 0x6a, 0x7c, 0x99, 0x8b, + 0x57, 0x9f, 0xfd, 0x7e, 0x9c, 0x57, 0x5e, 0x1e, 0xe7, 0x95, 0xbf, 0x8f, 0xf3, 0xca, 0x8f, 0x27, + 0xf9, 0xb5, 0x97, 0x27, 0xf9, 0xb5, 0xbf, 0x4e, 0xf2, 0x6b, 0x5f, 0xbd, 0xb6, 0xfd, 0x59, 0xf4, + 0x53, 0x5c, 0xcc, 0xd2, 0x4d, 0x8a, 0x4f, 0xf1, 0x7b, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x8a, + 0xa9, 0x66, 0xc0, 0x67, 0x0c, 0x00, 0x00, +} + +func (m *FinalityProvider) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -822,12 +822,12 @@ func (m *BTCValidator) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BTCValidator) MarshalTo(dAtA []byte) (int, error) { +func (m *FinalityProvider) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *FinalityProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -905,7 +905,7 @@ func (m *BTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *BTCValidatorWithMeta) Marshal() (dAtA []byte, err error) { +func (m *FinalityProviderWithMeta) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -915,12 +915,12 @@ func (m *BTCValidatorWithMeta) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BTCValidatorWithMeta) MarshalTo(dAtA []byte) (int, error) { +func (m *FinalityProviderWithMeta) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BTCValidatorWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *FinalityProviderWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1057,12 +1057,12 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - if len(m.ValBtcPkList) > 0 { - for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FpBtcPkList) > 0 { + for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { { - size := m.ValBtcPkList[iNdEx].Size() + size := m.FpBtcPkList[iNdEx].Size() i -= size - if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtcstaking(dAtA, i, uint64(size)) @@ -1429,7 +1429,7 @@ func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *BTCValidator) Size() (n int) { +func (m *FinalityProvider) Size() (n int) { if m == nil { return 0 } @@ -1464,7 +1464,7 @@ func (m *BTCValidator) Size() (n int) { return n } -func (m *BTCValidatorWithMeta) Size() (n int) { +func (m *FinalityProviderWithMeta) Size() (n int) { if m == nil { return 0 } @@ -1507,8 +1507,8 @@ func (m *BTCDelegation) Size() (n int) { l = m.Pop.Size() n += 1 + l + sovBtcstaking(uint64(l)) } - if len(m.ValBtcPkList) > 0 { - for _, e := range m.ValBtcPkList { + if len(m.FpBtcPkList) > 0 { + for _, e := range m.FpBtcPkList { l = e.Size() n += 1 + l + sovBtcstaking(uint64(l)) } @@ -1684,7 +1684,7 @@ func sovBtcstaking(x uint64) (n int) { func sozBtcstaking(x uint64) (n int) { return sovBtcstaking(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *BTCValidator) Unmarshal(dAtA []byte) error { +func (m *FinalityProvider) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1707,10 +1707,10 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BTCValidator: wiretype end group for non-group") + return fmt.Errorf("proto: FinalityProvider: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: FinalityProvider: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1951,7 +1951,7 @@ func (m *BTCValidator) Unmarshal(dAtA []byte) error { } return nil } -func (m *BTCValidatorWithMeta) Unmarshal(dAtA []byte) error { +func (m *FinalityProviderWithMeta) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1974,10 +1974,10 @@ func (m *BTCValidatorWithMeta) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BTCValidatorWithMeta: wiretype end group for non-group") + return fmt.Errorf("proto: FinalityProviderWithMeta: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BTCValidatorWithMeta: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: FinalityProviderWithMeta: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2250,7 +2250,7 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2278,8 +2278,8 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPkList = append(m.ValBtcPkList, v) - if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPkList = append(m.FpBtcPkList, v) + if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/codec.go b/x/btcstaking/types/codec.go index b272c8b50..256b38291 100644 --- a/x/btcstaking/types/codec.go +++ b/x/btcstaking/types/codec.go @@ -8,7 +8,7 @@ import ( ) func RegisterCodec(cdc *codec.LegacyAmino) { - cdc.RegisterConcrete(&MsgCreateBTCValidator{}, "btcstaking/MsgCreateBTCValidator", nil) + cdc.RegisterConcrete(&MsgCreateFinalityProvider{}, "btcstaking/MsgCreateFinalityProvider", nil) cdc.RegisterConcrete(&MsgCreateBTCDelegation{}, "btcstaking/MsgCreateBTCDelegation", nil) cdc.RegisterConcrete(&MsgAddCovenantSigs{}, "btcstaking/MsgAddCovenantSigs", nil) cdc.RegisterConcrete(&MsgUpdateParams{}, "btcstaking/MsgUpdateParams", nil) @@ -18,7 +18,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { // Register messages registry.RegisterImplementations( (*sdk.Msg)(nil), - &MsgCreateBTCValidator{}, + &MsgCreateFinalityProvider{}, &MsgCreateBTCDelegation{}, &MsgAddCovenantSigs{}, &MsgUpdateParams{}, diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index d3409914e..f0c2797b1 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -6,11 +6,11 @@ import ( // x/btcstaking module sentinel errors var ( - ErrBTCValNotFound = errorsmod.Register(ModuleName, 1100, "the BTC validator is not found") + ErrFpNotFound = errorsmod.Register(ModuleName, 1100, "the finality provider is not found") ErrBTCDelegatorNotFound = errorsmod.Register(ModuleName, 1101, "the BTC delegator is not found") ErrBTCDelegationNotFound = errorsmod.Register(ModuleName, 1102, "the BTC delegation is not found") - ErrDuplicatedBTCVal = errorsmod.Register(ModuleName, 1103, "the BTC validator has already been registered") - ErrBTCValAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the BTC validator has already been slashed") + ErrFpRegistered = errorsmod.Register(ModuleName, 1103, "the finality provider has already been registered") + ErrFpAlreadySlashed = errorsmod.Register(ModuleName, 1104, "the finality provider has already been slashed") ErrBTCStakingNotActivated = errorsmod.Register(ModuleName, 1105, "the BTC staking protocol is not activated yet") ErrBTCHeightNotFound = errorsmod.Register(ModuleName, 1106, "the BTC height is not found") ErrReusedStakingTx = errorsmod.Register(ModuleName, 1107, "the BTC staking tx is already used") @@ -24,8 +24,8 @@ var ( ErrInvalidDelegationState = errorsmod.Register(ModuleName, 1115, "Unexpected delegation state") ErrInvalidUnbondingTx = errorsmod.Register(ModuleName, 1116, "the BTC unbonding tx is not valid") ErrRewardDistCacheNotFound = errorsmod.Register(ModuleName, 1117, "the reward distribution cache is not found") - ErrEmptyValidatorList = errorsmod.Register(ModuleName, 1118, "the validator list is empty") + ErrEmptyFpList = errorsmod.Register(ModuleName, 1118, "the finality provider list is empty") ErrInvalidProofOfPossession = errorsmod.Register(ModuleName, 1119, "the proof of possession is not valid") - ErrDuplicatedValidator = errorsmod.Register(ModuleName, 1120, "the staking request contains duplicated validator public key") + ErrDuplicatedFp = errorsmod.Register(ModuleName, 1120, "the staking request contains duplicated finality provider public key") ErrInvalidBTCUndelegateReq = errorsmod.Register(ModuleName, 1121, "invalid undelegation request") ) diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index 4cb448a80..25b1dd73e 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -24,23 +24,23 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// EventNewBTCValidator is the event emitted when a BTC validator is created -type EventNewBTCValidator struct { - BtcVal *BTCValidator `protobuf:"bytes,1,opt,name=btc_val,json=btcVal,proto3" json:"btc_val,omitempty"` +// EventNewFinalityProvider is the event emitted when a finality provider is created +type EventNewFinalityProvider struct { + Fp *FinalityProvider `protobuf:"bytes,1,opt,name=fp,proto3" json:"fp,omitempty"` } -func (m *EventNewBTCValidator) Reset() { *m = EventNewBTCValidator{} } -func (m *EventNewBTCValidator) String() string { return proto.CompactTextString(m) } -func (*EventNewBTCValidator) ProtoMessage() {} -func (*EventNewBTCValidator) Descriptor() ([]byte, []int) { +func (m *EventNewFinalityProvider) Reset() { *m = EventNewFinalityProvider{} } +func (m *EventNewFinalityProvider) String() string { return proto.CompactTextString(m) } +func (*EventNewFinalityProvider) ProtoMessage() {} +func (*EventNewFinalityProvider) Descriptor() ([]byte, []int) { return fileDescriptor_74118427820fff75, []int{0} } -func (m *EventNewBTCValidator) XXX_Unmarshal(b []byte) error { +func (m *EventNewFinalityProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *EventNewBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *EventNewFinalityProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_EventNewBTCValidator.Marshal(b, m, deterministic) + return xxx_messageInfo_EventNewFinalityProvider.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -50,21 +50,21 @@ func (m *EventNewBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte return b[:n], nil } } -func (m *EventNewBTCValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventNewBTCValidator.Merge(m, src) +func (m *EventNewFinalityProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventNewFinalityProvider.Merge(m, src) } -func (m *EventNewBTCValidator) XXX_Size() int { +func (m *EventNewFinalityProvider) XXX_Size() int { return m.Size() } -func (m *EventNewBTCValidator) XXX_DiscardUnknown() { - xxx_messageInfo_EventNewBTCValidator.DiscardUnknown(m) +func (m *EventNewFinalityProvider) XXX_DiscardUnknown() { + xxx_messageInfo_EventNewFinalityProvider.DiscardUnknown(m) } -var xxx_messageInfo_EventNewBTCValidator proto.InternalMessageInfo +var xxx_messageInfo_EventNewFinalityProvider proto.InternalMessageInfo -func (m *EventNewBTCValidator) GetBtcVal() *BTCValidator { +func (m *EventNewFinalityProvider) GetFp() *FinalityProvider { if m != nil { - return m.BtcVal + return m.Fp } return nil } @@ -168,13 +168,13 @@ type EventUnbondingBTCDelegation struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the BTC finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + // to multiple finality providers + FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // unbonding_tx_hash is the hash of the unbonding tx. UnbondingTxHash string `protobuf:"bytes,4,opt,name=unbonding_tx_hash,json=unbondingTxHash,proto3" json:"unbonding_tx_hash,omitempty"` @@ -233,13 +233,13 @@ type EventUnbondedBTCDelegation struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple BTC validators - ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + // to multiple finality providers + FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` // staking_tx_hash is the hash of the staking tx. - // (val_pk, del_pk, staking_tx_hash) uniquely identifies a BTC delegation + // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // unbonding_tx_hash is the hash of the unbonding tx. UnbondingTxHash string `protobuf:"bytes,4,opt,name=unbonding_tx_hash,json=unbondingTxHash,proto3" json:"unbonding_tx_hash,omitempty"` @@ -302,7 +302,7 @@ func (m *EventUnbondedBTCDelegation) GetFromState() BTCDelegationStatus { } func init() { - proto.RegisterType((*EventNewBTCValidator)(nil), "babylon.btcstaking.v1.EventNewBTCValidator") + proto.RegisterType((*EventNewFinalityProvider)(nil), "babylon.btcstaking.v1.EventNewFinalityProvider") proto.RegisterType((*EventNewBTCDelegation)(nil), "babylon.btcstaking.v1.EventNewBTCDelegation") proto.RegisterType((*EventActivateBTCDelegation)(nil), "babylon.btcstaking.v1.EventActivateBTCDelegation") proto.RegisterType((*EventUnbondingBTCDelegation)(nil), "babylon.btcstaking.v1.EventUnbondingBTCDelegation") @@ -314,38 +314,39 @@ func init() { } var fileDescriptor_74118427820fff75 = []byte{ - // 447 bytes of a gzipped FileDescriptorProto + // 450 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x93, 0xc1, 0x6a, 0xd4, 0x40, - 0x18, 0xc7, 0x37, 0x5b, 0x5b, 0xe9, 0x58, 0x5d, 0x0c, 0x2d, 0x84, 0x15, 0x62, 0x88, 0x52, 0x96, - 0x1e, 0x12, 0xbb, 0x15, 0x4f, 0x7a, 0x30, 0x56, 0xb0, 0x58, 0x65, 0x89, 0xeb, 0x1e, 0x14, 0x09, - 0x33, 0xc9, 0x98, 0x0c, 0x3b, 0x9d, 0x59, 0x76, 0xbe, 0xc4, 0xdd, 0xb7, 0xf0, 0x0d, 0x7c, 0x1d, - 0x8f, 0x3d, 0x8a, 0x07, 0x91, 0xdd, 0x93, 0x6f, 0x21, 0x99, 0x64, 0xdb, 0x14, 0x2a, 0x0a, 0x7a, - 0xf4, 0x36, 0x49, 0x7e, 0xff, 0xdf, 0xc7, 0xf7, 0x27, 0x83, 0x5c, 0x82, 0xc9, 0x9c, 0x4b, 0xe1, - 0x13, 0x88, 0x15, 0xe0, 0x31, 0x13, 0xa9, 0x5f, 0xec, 0xfb, 0xb4, 0xa0, 0x02, 0x94, 0x37, 0x99, - 0x4a, 0x90, 0xe6, 0x4e, 0xcd, 0x78, 0xe7, 0x8c, 0x57, 0xec, 0x77, 0xb7, 0x53, 0x99, 0x4a, 0x4d, - 0xf8, 0xe5, 0xa9, 0x82, 0xbb, 0xbb, 0x97, 0x0b, 0x1b, 0x51, 0xcd, 0xb9, 0x43, 0xb4, 0xfd, 0xb4, - 0x1c, 0xf2, 0x92, 0x7e, 0x08, 0x86, 0x4f, 0x46, 0x98, 0xb3, 0x04, 0x83, 0x9c, 0x9a, 0x0f, 0xd1, - 0x55, 0x02, 0x71, 0x54, 0x60, 0x6e, 0x19, 0x8e, 0xd1, 0xbb, 0xd6, 0xbf, 0xe3, 0x5d, 0x3a, 0xde, - 0x6b, 0xa6, 0xc2, 0x0d, 0x02, 0xf1, 0x08, 0x73, 0x77, 0x84, 0x76, 0x1a, 0xd6, 0x43, 0xca, 0x69, - 0x8a, 0x81, 0x49, 0x61, 0x3e, 0xaa, 0xb4, 0x09, 0x5d, 0x69, 0xef, 0xfe, 0x5a, 0x7b, 0x1e, 0xd3, - 0xde, 0x43, 0xca, 0xdd, 0xb7, 0xa8, 0xab, 0xbd, 0x8f, 0x63, 0x60, 0x05, 0x06, 0xfa, 0x4f, 0xe5, - 0x9f, 0xda, 0xe8, 0x96, 0xb6, 0xbf, 0x16, 0x44, 0x8a, 0x84, 0x89, 0xf4, 0xa2, 0xfe, 0x05, 0x2a, - 0xc9, 0x68, 0x32, 0xd6, 0xf6, 0xad, 0xe0, 0xc1, 0xd7, 0x6f, 0xb7, 0xfb, 0x29, 0x83, 0x2c, 0x27, - 0x5e, 0x2c, 0x4f, 0xfc, 0x7a, 0x56, 0x9c, 0x61, 0x26, 0x56, 0x0f, 0x3e, 0xcc, 0x27, 0x54, 0x79, - 0xc1, 0xd1, 0xe0, 0xe0, 0xfe, 0xbd, 0x41, 0x4e, 0x9e, 0xd3, 0x79, 0xb8, 0x4e, 0x20, 0x1e, 0x8c, - 0xcd, 0x77, 0xa8, 0x53, 0x60, 0x1e, 0x55, 0xca, 0x88, 0x33, 0x05, 0x56, 0xdb, 0x59, 0xfb, 0x0b, - 0xef, 0x56, 0x81, 0x79, 0x50, 0xaa, 0x8f, 0x99, 0x02, 0x73, 0x17, 0x75, 0xea, 0x8d, 0x23, 0x98, - 0x45, 0x19, 0x56, 0x99, 0xb5, 0xe6, 0x18, 0xbd, 0xcd, 0xf0, 0x7a, 0xfd, 0x7a, 0x38, 0x7b, 0x86, - 0x55, 0x66, 0xee, 0xa1, 0x9b, 0xf9, 0x6a, 0xdf, 0x33, 0xf2, 0x8a, 0x26, 0x3b, 0x67, 0x1f, 0x2a, - 0xd6, 0xfd, 0xd1, 0xae, 0xfb, 0xaf, 0x1a, 0xa2, 0xc9, 0xff, 0x82, 0x2e, 0x14, 0x64, 0x1e, 0x21, - 0xf4, 0x7e, 0x2a, 0x4f, 0x22, 0x05, 0x18, 0xa8, 0xb5, 0xee, 0x18, 0xbd, 0x1b, 0xfd, 0xbd, 0x3f, - 0xf9, 0x09, 0x5f, 0x01, 0x86, 0x5c, 0x85, 0x9b, 0x65, 0xba, 0x3c, 0xd3, 0xe0, 0xf8, 0xf3, 0xc2, - 0x36, 0x4e, 0x17, 0xb6, 0xf1, 0x7d, 0x61, 0x1b, 0x1f, 0x97, 0x76, 0xeb, 0x74, 0x69, 0xb7, 0xbe, - 0x2c, 0xed, 0xd6, 0x9b, 0xdf, 0xae, 0x3e, 0x6b, 0x5e, 0x7a, 0xdd, 0x03, 0xd9, 0xd0, 0xb7, 0xfd, - 0xe0, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xc7, 0xd4, 0x63, 0x68, 0x04, 0x00, 0x00, + 0x18, 0xc7, 0x37, 0xa9, 0xad, 0x74, 0xaa, 0x2d, 0x06, 0x0b, 0x61, 0x85, 0xb8, 0x04, 0xa9, 0x4b, + 0x0f, 0x89, 0xdd, 0x8a, 0x9e, 0x3c, 0x18, 0xab, 0x58, 0xac, 0xb2, 0xa4, 0xd5, 0x83, 0x3d, 0x84, + 0x99, 0xec, 0x24, 0x19, 0x36, 0x9d, 0x09, 0x99, 0x2f, 0x71, 0xf3, 0x16, 0xbe, 0x80, 0xef, 0xe3, + 0xb1, 0x47, 0xf1, 0x20, 0xb2, 0x7b, 0xf0, 0x35, 0x24, 0x93, 0x6c, 0xdd, 0xca, 0x8a, 0x82, 0x1e, + 0xbd, 0x4d, 0x92, 0xdf, 0xff, 0xf7, 0xf1, 0xfd, 0xc9, 0x20, 0x9b, 0x60, 0x52, 0xa5, 0x82, 0xbb, + 0x04, 0x42, 0x09, 0x78, 0xcc, 0x78, 0xec, 0x96, 0x7b, 0x2e, 0x2d, 0x29, 0x07, 0xe9, 0x64, 0xb9, + 0x00, 0x61, 0x6c, 0xb7, 0x8c, 0xf3, 0x83, 0x71, 0xca, 0xbd, 0xee, 0xcd, 0x58, 0xc4, 0x42, 0x11, + 0x6e, 0x7d, 0x6a, 0xe0, 0xee, 0xce, 0x72, 0xe1, 0x42, 0x54, 0x71, 0xf6, 0x31, 0x32, 0x9f, 0xd6, + 0x43, 0x5e, 0xd1, 0x77, 0xcf, 0x18, 0xc7, 0x29, 0x83, 0x6a, 0x98, 0x8b, 0x92, 0x8d, 0x68, 0x6e, + 0x3c, 0x44, 0x7a, 0x94, 0x99, 0x5a, 0x4f, 0xeb, 0x6f, 0x0c, 0xee, 0x3a, 0x4b, 0xa7, 0x3b, 0x3f, + 0x87, 0x7c, 0x3d, 0xca, 0xec, 0x37, 0x68, 0x7b, 0x2e, 0xf5, 0x4e, 0x9e, 0x1c, 0xd0, 0x94, 0xc6, + 0x18, 0x98, 0xe0, 0xc6, 0x23, 0x74, 0x95, 0x40, 0x18, 0x8c, 0x68, 0xda, 0x6a, 0xef, 0xfc, 0x42, + 0x7b, 0x29, 0xe6, 0xaf, 0x11, 0x08, 0x0f, 0x68, 0x6a, 0x9f, 0xa2, 0xae, 0xf2, 0x3e, 0x0e, 0x81, + 0x95, 0x18, 0xe8, 0x3f, 0x95, 0x7f, 0xd0, 0xd1, 0x2d, 0x65, 0x7f, 0xcd, 0x89, 0xe0, 0x23, 0xc6, + 0xe3, 0xcb, 0xfa, 0x97, 0xa8, 0x26, 0x83, 0x6c, 0xac, 0xec, 0xd7, 0xbc, 0x07, 0x9f, 0xbf, 0xdc, + 0x1e, 0xc4, 0x0c, 0x92, 0x82, 0x38, 0xa1, 0x38, 0x73, 0xdb, 0x59, 0x61, 0x82, 0x19, 0x9f, 0x3f, + 0xb8, 0x50, 0x65, 0x54, 0x3a, 0xde, 0xe1, 0x70, 0xff, 0xfe, 0xbd, 0x61, 0x41, 0x5e, 0xd0, 0xca, + 0x5f, 0x25, 0x10, 0x0e, 0xc7, 0xc6, 0x29, 0xda, 0x8c, 0xb2, 0xa0, 0x31, 0x06, 0x29, 0x93, 0x60, + 0xea, 0xbd, 0x95, 0xbf, 0xd0, 0x6e, 0x44, 0x99, 0x57, 0x8b, 0x8f, 0x98, 0x04, 0x63, 0x07, 0x6d, + 0xb5, 0xfb, 0x06, 0x30, 0x09, 0x12, 0x2c, 0x13, 0x73, 0xa5, 0xa7, 0xf5, 0xd7, 0xfd, 0xeb, 0xed, + 0xeb, 0x93, 0xc9, 0x73, 0x2c, 0x13, 0x63, 0x17, 0xdd, 0x28, 0xe6, 0xdb, 0x5e, 0x90, 0x57, 0x14, + 0xb9, 0x75, 0xf1, 0xa1, 0x61, 0xed, 0x6f, 0x7a, 0xdb, 0x7e, 0xd3, 0x0f, 0x1d, 0xfd, 0xaf, 0x67, + 0xa1, 0x1e, 0xe3, 0x10, 0xa1, 0x28, 0x17, 0x67, 0x81, 0x04, 0x0c, 0xd4, 0x5c, 0xed, 0x69, 0xfd, + 0xcd, 0xc1, 0xee, 0x9f, 0xfc, 0x80, 0xc7, 0x80, 0xa1, 0x90, 0xfe, 0x7a, 0x9d, 0xae, 0xcf, 0xd4, + 0x3b, 0xfa, 0x38, 0xb5, 0xb4, 0xf3, 0xa9, 0xa5, 0x7d, 0x9d, 0x5a, 0xda, 0xfb, 0x99, 0xd5, 0x39, + 0x9f, 0x59, 0x9d, 0x4f, 0x33, 0xab, 0xf3, 0xf6, 0xb7, 0x9b, 0x4f, 0x16, 0xef, 0xbb, 0xaa, 0x81, + 0xac, 0xa9, 0x8b, 0xbe, 0xff, 0x3d, 0x00, 0x00, 0xff, 0xff, 0x4a, 0x42, 0x90, 0x3e, 0x63, 0x04, + 0x00, 0x00, } -func (m *EventNewBTCValidator) Marshal() (dAtA []byte, err error) { +func (m *EventNewFinalityProvider) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -355,19 +356,19 @@ func (m *EventNewBTCValidator) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *EventNewBTCValidator) MarshalTo(dAtA []byte) (int, error) { +func (m *EventNewFinalityProvider) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *EventNewBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *EventNewFinalityProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.BtcVal != nil { + if m.Fp != nil { { - size, err := m.BtcVal.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Fp.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -484,12 +485,12 @@ func (m *EventUnbondingBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, er i-- dAtA[i] = 0x1a } - if len(m.ValBtcPkList) > 0 { - for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FpBtcPkList) > 0 { + for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { { - size := m.ValBtcPkList[iNdEx].Size() + size := m.FpBtcPkList[iNdEx].Size() i -= size - if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintEvents(dAtA, i, uint64(size)) @@ -552,12 +553,12 @@ func (m *EventUnbondedBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x1a } - if len(m.ValBtcPkList) > 0 { - for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FpBtcPkList) > 0 { + for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { { - size := m.ValBtcPkList[iNdEx].Size() + size := m.FpBtcPkList[iNdEx].Size() i -= size - if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintEvents(dAtA, i, uint64(size)) @@ -592,14 +593,14 @@ func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *EventNewBTCValidator) Size() (n int) { +func (m *EventNewFinalityProvider) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.BtcVal != nil { - l = m.BtcVal.Size() + if m.Fp != nil { + l = m.Fp.Size() n += 1 + l + sovEvents(uint64(l)) } return n @@ -641,8 +642,8 @@ func (m *EventUnbondingBTCDelegation) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovEvents(uint64(l)) } - if len(m.ValBtcPkList) > 0 { - for _, e := range m.ValBtcPkList { + if len(m.FpBtcPkList) > 0 { + for _, e := range m.FpBtcPkList { l = e.Size() n += 1 + l + sovEvents(uint64(l)) } @@ -668,8 +669,8 @@ func (m *EventUnbondedBTCDelegation) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovEvents(uint64(l)) } - if len(m.ValBtcPkList) > 0 { - for _, e := range m.ValBtcPkList { + if len(m.FpBtcPkList) > 0 { + for _, e := range m.FpBtcPkList { l = e.Size() n += 1 + l + sovEvents(uint64(l)) } @@ -694,7 +695,7 @@ func sovEvents(x uint64) (n int) { func sozEvents(x uint64) (n int) { return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *EventNewBTCValidator) Unmarshal(dAtA []byte) error { +func (m *EventNewFinalityProvider) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -717,15 +718,15 @@ func (m *EventNewBTCValidator) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventNewBTCValidator: wiretype end group for non-group") + return fmt.Errorf("proto: EventNewFinalityProvider: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventNewBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventNewFinalityProvider: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcVal", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Fp", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -752,10 +753,10 @@ func (m *EventNewBTCValidator) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.BtcVal == nil { - m.BtcVal = &BTCValidator{} + if m.Fp == nil { + m.Fp = &FinalityProvider{} } - if err := m.BtcVal.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Fp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1018,7 +1019,7 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1046,8 +1047,8 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPkList = append(m.ValBtcPkList, v) - if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPkList = append(m.FpBtcPkList, v) + if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1202,7 +1203,7 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1230,8 +1231,8 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPkList = append(m.ValBtcPkList, v) - if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPkList = append(m.FpBtcPkList, v) + if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index f3cfae1f4..00ee5d73d 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -24,13 +24,13 @@ func TestGenesisState_Validate(t *testing.T) { desc: "valid genesis state", genState: &types.GenesisState{ Params: types.Params{ - CovenantPks: types.DefaultParams().CovenantPks, - CovenantQuorum: types.DefaultParams().CovenantQuorum, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), - MaxActiveBtcValidators: 100, + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), + MaxActiveFinalityProviders: 100, }, }, valid: true, @@ -39,13 +39,13 @@ func TestGenesisState_Validate(t *testing.T) { desc: "invalid slashing rate in genesis", genState: &types.GenesisState{ Params: types.Params{ - CovenantPks: types.DefaultParams().CovenantPks, - CovenantQuorum: types.DefaultParams().CovenantQuorum, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate - MaxActiveBtcValidators: 100, + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, + SlashingAddress: types.DefaultParams().SlashingAddress, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate + MaxActiveFinalityProviders: 100, }, }, valid: false, diff --git a/x/btcstaking/types/incentive.go b/x/btcstaking/types/incentive.go index 92a4df6f7..6b2d9dd9f 100644 --- a/x/btcstaking/types/incentive.go +++ b/x/btcstaking/types/incentive.go @@ -7,54 +7,54 @@ import ( func NewRewardDistCache() *RewardDistCache { return &RewardDistCache{ - TotalVotingPower: 0, - BtcVals: []*BTCValDistInfo{}, + TotalVotingPower: 0, + FinalityProviders: []*FinalityProviderDistInfo{}, } } -func (rdc *RewardDistCache) AddBTCValDistInfo(v *BTCValDistInfo) { +func (rdc *RewardDistCache) AddFinalityProviderDistInfo(v *FinalityProviderDistInfo) { if v.TotalVotingPower > 0 { - // append BTC validator dist info and accumulate voting power - rdc.BtcVals = append(rdc.BtcVals, v) + // append finality provider dist info and accumulate voting power + rdc.FinalityProviders = append(rdc.FinalityProviders, v) rdc.TotalVotingPower += v.TotalVotingPower } } -// FilterVotedBTCVals filters out BTC validators that have voted according to a map of given voters +// FilterVotedFinalityProviders filters out finality providers that have voted according to a map of given voters // and update total voted power accordingly -func (rdc *RewardDistCache) FilterVotedBTCVals(voterBTCPKs map[string]struct{}) { - filteredBTCVals := []*BTCValDistInfo{} +func (rdc *RewardDistCache) FilterVotedFinalityProviders(voterBTCPKs map[string]struct{}) { + filteredFps := []*FinalityProviderDistInfo{} totalVotingPower := uint64(0) - for _, v := range rdc.BtcVals { + for _, v := range rdc.FinalityProviders { if _, ok := voterBTCPKs[v.BtcPk.MarshalHex()]; ok { - filteredBTCVals = append(filteredBTCVals, v) + filteredFps = append(filteredFps, v) totalVotingPower += v.TotalVotingPower } } - rdc.BtcVals = filteredBTCVals + rdc.FinalityProviders = filteredFps rdc.TotalVotingPower = totalVotingPower } -// GetBTCValPortion returns the portion of a BTC validator's voting power out of the total voting power -func (rdc *RewardDistCache) GetBTCValPortion(v *BTCValDistInfo) sdkmath.LegacyDec { +// GetFinalityProviderPortion returns the portion of a finality provider's voting power out of the total voting power +func (rdc *RewardDistCache) GetFinalityProviderPortion(v *FinalityProviderDistInfo) sdkmath.LegacyDec { return sdkmath.LegacyNewDec(int64(v.TotalVotingPower)).QuoTruncate(sdkmath.LegacyNewDec(int64(rdc.TotalVotingPower))) } -func NewBTCValDistInfo(btcVal *BTCValidator) *BTCValDistInfo { - return &BTCValDistInfo{ - BtcPk: btcVal.BtcPk, - BabylonPk: btcVal.BabylonPk, - Commission: btcVal.Commission, +func NewFinalityProviderDistInfo(fp *FinalityProvider) *FinalityProviderDistInfo { + return &FinalityProviderDistInfo{ + BtcPk: fp.BtcPk, + BabylonPk: fp.BabylonPk, + Commission: fp.Commission, TotalVotingPower: 0, BtcDels: []*BTCDelDistInfo{}, } } -func (v *BTCValDistInfo) GetAddress() sdk.AccAddress { +func (v *FinalityProviderDistInfo) GetAddress() sdk.AccAddress { return sdk.AccAddress(v.BabylonPk.Address()) } -func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wValue uint64, covenantQuorum uint32) { +func (v *FinalityProviderDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wValue uint64, covenantQuorum uint32) { btcDelDistInfo := &BTCDelDistInfo{ BabylonPk: btcDel.BabylonPk, VotingPower: btcDel.VotingPower(btcHeight, wValue, covenantQuorum), @@ -68,8 +68,8 @@ func (v *BTCValDistInfo) AddBTCDel(btcDel *BTCDelegation, btcHeight uint64, wVal } // GetBTCDelPortion returns the portion of a BTC delegation's voting power out of -// the BTC validator's total voting power -func (v *BTCValDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdkmath.LegacyDec { +// the finality provider's total voting power +func (v *FinalityProviderDistInfo) GetBTCDelPortion(d *BTCDelDistInfo) sdkmath.LegacyDec { return sdkmath.LegacyNewDec(int64(d.VotingPower)).QuoTruncate(sdkmath.LegacyNewDec(int64(v.TotalVotingPower))) } diff --git a/x/btcstaking/types/incentive.pb.go b/x/btcstaking/types/incentive.pb.go index 7cda115fd..2ecf02d12 100644 --- a/x/btcstaking/types/incentive.pb.go +++ b/x/btcstaking/types/incentive.pb.go @@ -27,11 +27,11 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// RewardDistCache is the cache for reward distribution of BTC validators at a height +// RewardDistCache is the cache for reward distribution of finality providers at a height type RewardDistCache struct { TotalVotingPower uint64 `protobuf:"varint,1,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` - // btc_vals is a list of BTC validators' voting power information - BtcVals []*BTCValDistInfo `protobuf:"bytes,2,rep,name=btc_vals,json=btcVals,proto3" json:"btc_vals,omitempty"` + // finality_providers is a list of finality providers' voting power information + FinalityProviders []*FinalityProviderDistInfo `protobuf:"bytes,2,rep,name=finality_providers,json=finalityProviders,proto3" json:"finality_providers,omitempty"` } func (m *RewardDistCache) Reset() { *m = RewardDistCache{} } @@ -74,40 +74,40 @@ func (m *RewardDistCache) GetTotalVotingPower() uint64 { return 0 } -func (m *RewardDistCache) GetBtcVals() []*BTCValDistInfo { +func (m *RewardDistCache) GetFinalityProviders() []*FinalityProviderDistInfo { if m != nil { - return m.BtcVals + return m.FinalityProviders } return nil } -// BTCValDistInfo is the reward distribution of a BTC validator and its BTC delegations -type BTCValDistInfo struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator +// FinalityProviderDistInfo is the reward distribution of a finality provider and its BTC delegations +type FinalityProviderDistInfo struct { + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // babylon_pk is the Babylon public key of the BTC validator + // babylon_pk is the Babylon public key of the finality provider BabylonPk *secp256k1.PubKey `protobuf:"bytes,2,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` - // commission defines the commission rate of BTC validator + // commission defines the commission rate of finality provider Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` - // total_voting_power is the total voting power of the BTC validator + // total_voting_power is the total voting power of the finality provider TotalVotingPower uint64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3" json:"total_voting_power,omitempty"` - // btc_dels is a list of BTC delegations' voting power information under this BTC validator + // btc_dels is a list of BTC delegations' voting power information under this finality provider BtcDels []*BTCDelDistInfo `protobuf:"bytes,5,rep,name=btc_dels,json=btcDels,proto3" json:"btc_dels,omitempty"` } -func (m *BTCValDistInfo) Reset() { *m = BTCValDistInfo{} } -func (m *BTCValDistInfo) String() string { return proto.CompactTextString(m) } -func (*BTCValDistInfo) ProtoMessage() {} -func (*BTCValDistInfo) Descriptor() ([]byte, []int) { +func (m *FinalityProviderDistInfo) Reset() { *m = FinalityProviderDistInfo{} } +func (m *FinalityProviderDistInfo) String() string { return proto.CompactTextString(m) } +func (*FinalityProviderDistInfo) ProtoMessage() {} +func (*FinalityProviderDistInfo) Descriptor() ([]byte, []int) { return fileDescriptor_ac354c3bd6d7a66b, []int{1} } -func (m *BTCValDistInfo) XXX_Unmarshal(b []byte) error { +func (m *FinalityProviderDistInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BTCValDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *FinalityProviderDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BTCValDistInfo.Marshal(b, m, deterministic) + return xxx_messageInfo_FinalityProviderDistInfo.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -117,33 +117,33 @@ func (m *BTCValDistInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro return b[:n], nil } } -func (m *BTCValDistInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_BTCValDistInfo.Merge(m, src) +func (m *FinalityProviderDistInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_FinalityProviderDistInfo.Merge(m, src) } -func (m *BTCValDistInfo) XXX_Size() int { +func (m *FinalityProviderDistInfo) XXX_Size() int { return m.Size() } -func (m *BTCValDistInfo) XXX_DiscardUnknown() { - xxx_messageInfo_BTCValDistInfo.DiscardUnknown(m) +func (m *FinalityProviderDistInfo) XXX_DiscardUnknown() { + xxx_messageInfo_FinalityProviderDistInfo.DiscardUnknown(m) } -var xxx_messageInfo_BTCValDistInfo proto.InternalMessageInfo +var xxx_messageInfo_FinalityProviderDistInfo proto.InternalMessageInfo -func (m *BTCValDistInfo) GetBabylonPk() *secp256k1.PubKey { +func (m *FinalityProviderDistInfo) GetBabylonPk() *secp256k1.PubKey { if m != nil { return m.BabylonPk } return nil } -func (m *BTCValDistInfo) GetTotalVotingPower() uint64 { +func (m *FinalityProviderDistInfo) GetTotalVotingPower() uint64 { if m != nil { return m.TotalVotingPower } return 0 } -func (m *BTCValDistInfo) GetBtcDels() []*BTCDelDistInfo { +func (m *FinalityProviderDistInfo) GetBtcDels() []*BTCDelDistInfo { if m != nil { return m.BtcDels } @@ -207,7 +207,7 @@ func (m *BTCDelDistInfo) GetVotingPower() uint64 { func init() { proto.RegisterType((*RewardDistCache)(nil), "babylon.btcstaking.v1.RewardDistCache") - proto.RegisterType((*BTCValDistInfo)(nil), "babylon.btcstaking.v1.BTCValDistInfo") + proto.RegisterType((*FinalityProviderDistInfo)(nil), "babylon.btcstaking.v1.FinalityProviderDistInfo") proto.RegisterType((*BTCDelDistInfo)(nil), "babylon.btcstaking.v1.BTCDelDistInfo") } @@ -216,37 +216,38 @@ func init() { } var fileDescriptor_ac354c3bd6d7a66b = []byte{ - // 468 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0xeb, 0xee, 0x0f, 0xcc, 0x9d, 0x00, 0x45, 0x20, 0x95, 0x21, 0xa5, 0xa5, 0xd2, 0xa4, - 0x1e, 0x98, 0x4d, 0x3b, 0xd8, 0x11, 0xa1, 0x2c, 0x97, 0x89, 0x4d, 0x8a, 0x22, 0xd4, 0x03, 0x97, - 0xca, 0x76, 0x4d, 0x6a, 0xe5, 0x8f, 0xa3, 0xda, 0xcb, 0xc8, 0x91, 0x6f, 0xc0, 0x87, 0xe1, 0x43, - 0x70, 0x9c, 0x38, 0xa1, 0x1d, 0x2a, 0xd4, 0x7e, 0x11, 0x14, 0xc7, 0xb0, 0x16, 0x01, 0x15, 0xb7, - 0xbc, 0x79, 0x1e, 0xbf, 0xcf, 0x9b, 0xdf, 0x1b, 0xc3, 0x43, 0x4a, 0x68, 0x99, 0xc8, 0x0c, 0x53, - 0xcd, 0x94, 0x26, 0xb1, 0xc8, 0x22, 0x5c, 0x0c, 0xb0, 0xc8, 0x18, 0xcf, 0xb4, 0x28, 0x38, 0xca, - 0x67, 0x52, 0x4b, 0xe7, 0x91, 0xb5, 0xa1, 0x5b, 0x1b, 0x2a, 0x06, 0x07, 0x0f, 0x23, 0x19, 0x49, - 0xe3, 0xc0, 0xd5, 0x53, 0x6d, 0x3e, 0x78, 0xcc, 0xa4, 0x4a, 0xa5, 0x1a, 0xd7, 0x42, 0x5d, 0x58, - 0xa9, 0x57, 0x57, 0x98, 0xcd, 0xca, 0x5c, 0x4b, 0xac, 0x38, 0xcb, 0x87, 0x2f, 0x4f, 0xe2, 0x01, - 0x8e, 0x79, 0x69, 0x3d, 0xbd, 0x8f, 0x00, 0xde, 0x0f, 0xf9, 0x15, 0x99, 0x4d, 0x7c, 0xa1, 0xf4, - 0x29, 0x61, 0x53, 0xee, 0x3c, 0x83, 0x8e, 0x96, 0x9a, 0x24, 0xe3, 0x42, 0x6a, 0x91, 0x45, 0xe3, - 0x5c, 0x5e, 0xf1, 0x59, 0x1b, 0x74, 0x41, 0x7f, 0x3b, 0x7c, 0x60, 0x94, 0x91, 0x11, 0x82, 0xea, - 0xbd, 0xf3, 0x1a, 0xde, 0xa5, 0x9a, 0x8d, 0x0b, 0x92, 0xa8, 0x76, 0xb3, 0xbb, 0xd5, 0x6f, 0x0d, - 0x0f, 0xd1, 0x1f, 0x3f, 0x00, 0x79, 0x6f, 0x4f, 0x47, 0x24, 0xa9, 0x72, 0xce, 0xb2, 0xf7, 0x32, - 0xbc, 0x43, 0x35, 0x1b, 0x91, 0x44, 0xf5, 0xe6, 0x4d, 0x78, 0x6f, 0x5d, 0x73, 0x2e, 0xe0, 0x6e, - 0xd5, 0x34, 0x8f, 0x4d, 0xec, 0xbe, 0x77, 0x72, 0x33, 0xef, 0x0c, 0x23, 0xa1, 0xa7, 0x97, 0x14, - 0x31, 0x99, 0x62, 0x1b, 0xc0, 0xa6, 0x44, 0x64, 0x3f, 0x0b, 0xac, 0xcb, 0x9c, 0x2b, 0xe4, 0x9d, - 0x05, 0xc7, 0x2f, 0x9e, 0x07, 0x97, 0xf4, 0x0d, 0x2f, 0xc3, 0x1d, 0xaa, 0x59, 0x10, 0x3b, 0xaf, - 0x20, 0xb4, 0xa6, 0xaa, 0x65, 0xb3, 0x0b, 0xfa, 0xad, 0x61, 0x07, 0x59, 0x58, 0x35, 0x1e, 0xf4, - 0x0b, 0x0f, 0xb2, 0x67, 0xf7, 0xec, 0x91, 0x20, 0x76, 0x2e, 0x20, 0x64, 0x32, 0x4d, 0x85, 0x52, - 0x42, 0x66, 0xed, 0xad, 0x2e, 0xe8, 0xef, 0x79, 0x47, 0x37, 0xf3, 0xce, 0x93, 0xba, 0x85, 0x9a, - 0xc4, 0x48, 0x48, 0x9c, 0x12, 0x3d, 0x45, 0xe7, 0x3c, 0x22, 0xac, 0xf4, 0x39, 0xfb, 0xfa, 0xf9, - 0x08, 0xda, 0x04, 0x9f, 0xb3, 0x70, 0xa5, 0xc1, 0x5f, 0x00, 0x6f, 0xff, 0x1b, 0xf0, 0x84, 0x27, - 0xaa, 0xbd, 0xb3, 0x09, 0xb0, 0xcf, 0xd7, 0x01, 0xfb, 0x3c, 0x51, 0x3d, 0x65, 0xf8, 0xae, 0x48, - 0xbf, 0x01, 0x01, 0xff, 0x0d, 0xe4, 0x29, 0xdc, 0x5f, 0x9b, 0xbd, 0x69, 0x66, 0x6f, 0x15, 0xb7, - 0x63, 0x7b, 0xe7, 0x5f, 0x16, 0x2e, 0xb8, 0x5e, 0xb8, 0xe0, 0xfb, 0xc2, 0x05, 0x9f, 0x96, 0x6e, - 0xe3, 0x7a, 0xe9, 0x36, 0xbe, 0x2d, 0xdd, 0xc6, 0xbb, 0x8d, 0x8b, 0xfc, 0xb0, 0x7a, 0x41, 0xcc, - 0x56, 0xe9, 0xae, 0xf9, 0x5d, 0x8f, 0x7f, 0x04, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x41, 0xf4, 0x9f, - 0x43, 0x03, 0x00, 0x00, + // 491 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0xcb, 0x6e, 0xd3, 0x40, + 0x14, 0x86, 0xe3, 0xf4, 0x02, 0x9d, 0x54, 0x5c, 0x2c, 0x90, 0x4c, 0x91, 0x9c, 0x10, 0xa9, 0x52, + 0x16, 0x74, 0x86, 0xa4, 0xd0, 0x25, 0x42, 0xae, 0x85, 0x54, 0xd1, 0x4a, 0x96, 0x85, 0x58, 0xb0, + 0x20, 0x9a, 0x99, 0x4c, 0x9c, 0x91, 0xed, 0x19, 0xcb, 0x33, 0x75, 0xf1, 0x5b, 0xf0, 0x06, 0xbc, + 0x04, 0x0f, 0xc1, 0xb2, 0x62, 0x85, 0xba, 0xa8, 0x50, 0xb2, 0xe1, 0x31, 0x90, 0xed, 0xa1, 0x37, + 0x11, 0xa1, 0xee, 0x72, 0xf2, 0xff, 0xe7, 0x9c, 0xdf, 0xdf, 0xb1, 0xc1, 0x36, 0xc1, 0xa4, 0x4c, + 0xa4, 0x40, 0x44, 0x53, 0xa5, 0x71, 0xcc, 0x45, 0x84, 0x8a, 0x21, 0xe2, 0x82, 0x32, 0xa1, 0x79, + 0xc1, 0x60, 0x96, 0x4b, 0x2d, 0xed, 0xc7, 0xc6, 0x06, 0x2f, 0x6d, 0xb0, 0x18, 0x6e, 0x3d, 0x8a, + 0x64, 0x24, 0x6b, 0x07, 0xaa, 0x7e, 0x35, 0xe6, 0xad, 0x27, 0x54, 0xaa, 0x54, 0xaa, 0x71, 0x23, + 0x34, 0x85, 0x91, 0xfa, 0x4d, 0x85, 0x68, 0x5e, 0x66, 0x5a, 0x22, 0xc5, 0x68, 0x36, 0x7a, 0xb5, + 0x17, 0x0f, 0x51, 0xcc, 0x4a, 0xe3, 0xe9, 0x7f, 0xb5, 0xc0, 0xfd, 0x90, 0x9d, 0xe0, 0x7c, 0xe2, + 0x73, 0xa5, 0xf7, 0x31, 0x9d, 0x31, 0xfb, 0x39, 0xb0, 0xb5, 0xd4, 0x38, 0x19, 0x17, 0x52, 0x73, + 0x11, 0x8d, 0x33, 0x79, 0xc2, 0x72, 0xc7, 0xea, 0x59, 0x83, 0xd5, 0xf0, 0x41, 0xad, 0x7c, 0xa8, + 0x85, 0xa0, 0xfa, 0xdf, 0xfe, 0x04, 0xec, 0x29, 0x17, 0x38, 0xe1, 0xba, 0xac, 0x42, 0x14, 0x7c, + 0xc2, 0x72, 0xe5, 0xb4, 0x7b, 0x2b, 0x83, 0xce, 0x08, 0xc1, 0x7f, 0x3e, 0x0a, 0x7c, 0x6b, 0x1a, + 0x02, 0xe3, 0xaf, 0x76, 0x1f, 0x88, 0xa9, 0x0c, 0x1f, 0x4e, 0x6f, 0x28, 0xaa, 0xff, 0xbb, 0x0d, + 0x9c, 0x65, 0x7e, 0xfb, 0x08, 0xac, 0x13, 0x4d, 0xc7, 0x59, 0x5c, 0xc7, 0xdb, 0xf4, 0xf6, 0xce, + 0xce, 0xbb, 0xa3, 0x88, 0xeb, 0xd9, 0x31, 0x81, 0x54, 0xa6, 0xc8, 0xac, 0xa7, 0x33, 0xcc, 0xc5, + 0xdf, 0x02, 0xe9, 0x32, 0x63, 0x0a, 0x7a, 0x07, 0xc1, 0xee, 0xcb, 0x17, 0xc1, 0x31, 0x79, 0xc7, + 0xca, 0x70, 0x8d, 0x68, 0x1a, 0xc4, 0xf6, 0x6b, 0x00, 0x8c, 0xa9, 0x1a, 0xd9, 0xee, 0x59, 0x83, + 0xce, 0xa8, 0x0b, 0x0d, 0xd4, 0x06, 0x23, 0xbc, 0xc0, 0x08, 0x4d, 0xef, 0x86, 0x69, 0x09, 0x62, + 0xfb, 0x08, 0x00, 0x2a, 0xd3, 0x94, 0x2b, 0xc5, 0xa5, 0x70, 0x56, 0x7a, 0xd6, 0x60, 0xc3, 0xdb, + 0x39, 0x3b, 0xef, 0x3e, 0x6d, 0x46, 0xa8, 0x49, 0x0c, 0xb9, 0x44, 0x29, 0xd6, 0x33, 0x78, 0xc8, + 0x22, 0x4c, 0x4b, 0x9f, 0xd1, 0x1f, 0xdf, 0x76, 0x80, 0xd9, 0xe0, 0x33, 0x1a, 0x5e, 0x19, 0xb0, + 0xe4, 0x10, 0xab, 0x4b, 0x0e, 0xf1, 0x06, 0xdc, 0xad, 0x58, 0x4c, 0x58, 0xa2, 0x9c, 0xb5, 0x1a, + 0xff, 0xf6, 0x12, 0xfc, 0xde, 0xfb, 0x7d, 0x9f, 0x25, 0x17, 0xd0, 0xef, 0x10, 0x4d, 0x7d, 0x96, + 0xa8, 0xbe, 0x02, 0xf7, 0xae, 0x4b, 0x37, 0x80, 0x58, 0xb7, 0x06, 0xf2, 0x0c, 0x6c, 0x5e, 0xcb, + 0xde, 0xae, 0xb3, 0x77, 0x8a, 0xcb, 0xd8, 0xde, 0xe1, 0xf7, 0xb9, 0x6b, 0x9d, 0xce, 0x5d, 0xeb, + 0xd7, 0xdc, 0xb5, 0xbe, 0x2c, 0xdc, 0xd6, 0xe9, 0xc2, 0x6d, 0xfd, 0x5c, 0xb8, 0xad, 0x8f, 0xff, + 0x3d, 0xe4, 0xe7, 0xab, 0x1f, 0x52, 0x7d, 0x55, 0xb2, 0x5e, 0xbf, 0xd6, 0xbb, 0x7f, 0x02, 0x00, + 0x00, 0xff, 0xff, 0xef, 0xcd, 0x33, 0x90, 0x6b, 0x03, 0x00, 0x00, } func (m *RewardDistCache) Marshal() (dAtA []byte, err error) { @@ -269,10 +270,10 @@ func (m *RewardDistCache) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.BtcVals) > 0 { - for iNdEx := len(m.BtcVals) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FinalityProviders) > 0 { + for iNdEx := len(m.FinalityProviders) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.BtcVals[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.FinalityProviders[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -291,7 +292,7 @@ func (m *RewardDistCache) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *BTCValDistInfo) Marshal() (dAtA []byte, err error) { +func (m *FinalityProviderDistInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -301,12 +302,12 @@ func (m *BTCValDistInfo) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BTCValDistInfo) MarshalTo(dAtA []byte) (int, error) { +func (m *FinalityProviderDistInfo) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BTCValDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *FinalityProviderDistInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -429,8 +430,8 @@ func (m *RewardDistCache) Size() (n int) { if m.TotalVotingPower != 0 { n += 1 + sovIncentive(uint64(m.TotalVotingPower)) } - if len(m.BtcVals) > 0 { - for _, e := range m.BtcVals { + if len(m.FinalityProviders) > 0 { + for _, e := range m.FinalityProviders { l = e.Size() n += 1 + l + sovIncentive(uint64(l)) } @@ -438,7 +439,7 @@ func (m *RewardDistCache) Size() (n int) { return n } -func (m *BTCValDistInfo) Size() (n int) { +func (m *FinalityProviderDistInfo) Size() (n int) { if m == nil { return 0 } @@ -540,7 +541,7 @@ func (m *RewardDistCache) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcVals", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FinalityProviders", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -567,8 +568,8 @@ func (m *RewardDistCache) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcVals = append(m.BtcVals, &BTCValDistInfo{}) - if err := m.BtcVals[len(m.BtcVals)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FinalityProviders = append(m.FinalityProviders, &FinalityProviderDistInfo{}) + if err := m.FinalityProviders[len(m.FinalityProviders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -593,7 +594,7 @@ func (m *RewardDistCache) Unmarshal(dAtA []byte) error { } return nil } -func (m *BTCValDistInfo) Unmarshal(dAtA []byte) error { +func (m *FinalityProviderDistInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -616,10 +617,10 @@ func (m *BTCValDistInfo) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BTCValDistInfo: wiretype end group for non-group") + return fmt.Errorf("proto: FinalityProviderDistInfo: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BTCValDistInfo: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: FinalityProviderDistInfo: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/x/btcstaking/types/keys.go b/x/btcstaking/types/keys.go index c2ba33a68..4f291ad14 100644 --- a/x/btcstaking/types/keys.go +++ b/x/btcstaking/types/keys.go @@ -15,13 +15,13 @@ const ( ) var ( - ParamsKey = []byte{0x01} // key prefix for the parameters - BTCValidatorKey = []byte{0x02} // key prefix for the BTC validators - BTCDelegatorKey = []byte{0x03} // key prefix for the BTC delegators - BTCDelegationKey = []byte{0x04} // key prefix for the BTC delegations - VotingPowerKey = []byte{0x05} // key prefix for the voting power - BTCHeightKey = []byte{0x06} // key prefix for the BTC heights - RewardDistCacheKey = []byte{0x07} // key prefix for reward distribution cache + ParamsKey = []byte{0x01} // key prefix for the parameters + FinalityProviderKey = []byte{0x02} // key prefix for the finality providers + BTCDelegatorKey = []byte{0x03} // key prefix for the BTC delegators + BTCDelegationKey = []byte{0x04} // key prefix for the BTC delegations + VotingPowerKey = []byte{0x05} // key prefix for the voting power + BTCHeightKey = []byte{0x06} // key prefix for the BTC heights + RewardDistCacheKey = []byte{0x07} // key prefix for reward distribution cache ) func KeyPrefix(p string) []byte { diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index a0ce94fd7..a0ed64f07 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -13,13 +13,13 @@ import ( // ensure that these message types implement the sdk.Msg interface var ( _ sdk.Msg = &MsgUpdateParams{} - _ sdk.Msg = &MsgCreateBTCValidator{} + _ sdk.Msg = &MsgCreateFinalityProvider{} _ sdk.Msg = &MsgCreateBTCDelegation{} _ sdk.Msg = &MsgAddCovenantSigs{} _ sdk.Msg = &MsgBTCUndelegate{} ) -func (m *MsgCreateBTCValidator) ValidateBasic() error { +func (m *MsgCreateFinalityProvider) ValidateBasic() error { if m.Commission == nil { return fmt.Errorf("empty commission") } @@ -78,13 +78,13 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { if m.StakingTime > math.MaxUint16 { return ErrInvalidStakingTx.Wrapf("invalid lock time: %d, max: %d", m.StakingTime, math.MaxUint16) } - // Ensure list of validator BTC PKs is not empty - if len(m.ValBtcPkList) == 0 { - return ErrEmptyValidatorList + // Ensure list of finality provider BTC PKs is not empty + if len(m.FpBtcPkList) == 0 { + return ErrEmptyFpList } - // Ensure list of validator BTC PKs is not duplicated - if ExistsDup(m.ValBtcPkList) { - return ErrDuplicatedValidator + // Ensure list of finality provider BTC PKs is not duplicated + if ExistsDup(m.FpBtcPkList) { + return ErrDuplicatedFp } // staking tx should be correctly formatted diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index 5eab880d9..bc36c4888 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -15,7 +15,7 @@ import ( ) const ( - defaultMaxActiveBtcValidators uint32 = 100 + defaultMaxActiveFinalityProviders uint32 = 100 ) var _ paramtypes.ParamSet = (*Params)(nil) @@ -58,8 +58,8 @@ func DefaultParams() Params { MinSlashingTxFeeSat: 1000, MinCommissionRate: sdkmath.LegacyZeroDec(), // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. - SlashingRate: sdkmath.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 - MaxActiveBtcValidators: defaultMaxActiveBtcValidators, + SlashingRate: sdkmath.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 + MaxActiveFinalityProviders: defaultMaxActiveFinalityProviders, } } @@ -90,11 +90,11 @@ func validateMinCommissionRate(rate sdkmath.LegacyDec) error { return nil } -// validateMaxActiveBTCValidators checks if the maximum number of -// active BTC validators is at least the default value -func validateMaxActiveBTCValidators(maxActiveBtcValidators uint32) error { - if maxActiveBtcValidators == 0 { - return fmt.Errorf("max validators must be positive") +// validateMaxActiveFinalityProviders checks if the maximum number of +// active finality providers is at least the default value +func validateMaxActiveFinalityProviders(maxActiveFinalityProviders uint32) error { + if maxActiveFinalityProviders == 0 { + return fmt.Errorf("max finality providers must be positive") } return nil } @@ -130,7 +130,7 @@ func (p Params) Validate() error { return btcstaking.ErrInvalidSlashingRate } - if err := validateMaxActiveBTCValidators(p.MaxActiveBtcValidators); err != nil { + if err := validateMaxActiveFinalityProviders(p.MaxActiveFinalityProviders); err != nil { return err } return nil diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 8b471927a..567da0ca5 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -41,13 +41,13 @@ type Params struct { // in Satoshi) needed for the pre-signed slashing tx // TODO: change to satoshi per byte? MinSlashingTxFeeSat int64 `protobuf:"varint,4,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` - // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators + // min_commission_rate is the chain-wide minimum commission rate that a finality provider can charge their delegators MinCommissionRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_commission_rate"` // slashing_rate determines the portion of the staked amount to be slashed, // expressed as a decimal (e.g., 0.5 for 50%). SlashingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slashing_rate"` - // max_active_btc_validators is the maximum number of active BTC validators in the BTC staking protocol - MaxActiveBtcValidators uint32 `protobuf:"varint,7,opt,name=max_active_btc_validators,json=maxActiveBtcValidators,proto3" json:"max_active_btc_validators,omitempty"` + // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol + MaxActiveFinalityProviders uint32 `protobuf:"varint,7,opt,name=max_active_finality_providers,json=maxActiveFinalityProviders,proto3" json:"max_active_finality_providers,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -103,9 +103,9 @@ func (m *Params) GetMinSlashingTxFeeSat() int64 { return 0 } -func (m *Params) GetMaxActiveBtcValidators() uint32 { +func (m *Params) GetMaxActiveFinalityProviders() uint32 { if m != nil { - return m.MaxActiveBtcValidators + return m.MaxActiveFinalityProviders } return 0 } @@ -119,36 +119,36 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 454 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x6d, 0x52, 0x82, 0x58, 0x52, 0x0a, 0xe6, 0x9f, 0x5b, 0x24, 0x27, 0x2a, 0x07, 0xc2, - 0x01, 0x9b, 0xd0, 0x0a, 0x09, 0x6e, 0x35, 0x08, 0x09, 0xd1, 0x43, 0x70, 0x50, 0x25, 0xb8, 0xac, - 0xc6, 0x9b, 0xc5, 0x59, 0x25, 0xbb, 0x1b, 0xbc, 0x1b, 0xcb, 0x79, 0x0b, 0x8e, 0x1c, 0x79, 0x08, - 0x1e, 0xa2, 0xc7, 0x8a, 0x13, 0x2a, 0x52, 0x84, 0x92, 0x17, 0x41, 0x5e, 0xff, 0x81, 0x1b, 0xbd, - 0x79, 0xbe, 0xef, 0xf3, 0x6f, 0x56, 0x33, 0x83, 0xf6, 0x63, 0x88, 0x97, 0x33, 0x29, 0x82, 0x58, - 0x13, 0xa5, 0x61, 0xca, 0x44, 0x12, 0x64, 0x83, 0x60, 0x0e, 0x29, 0x70, 0xe5, 0xcf, 0x53, 0xa9, - 0xa5, 0x73, 0xa7, 0xca, 0xf8, 0x7f, 0x33, 0x7e, 0x36, 0xd8, 0xbb, 0x9d, 0xc8, 0x44, 0x9a, 0x44, - 0x50, 0x7c, 0x95, 0xe1, 0xbd, 0x5d, 0x22, 0x15, 0x97, 0x0a, 0x97, 0x46, 0x59, 0x94, 0xd6, 0xfe, - 0xaf, 0x16, 0x6a, 0x0f, 0x0d, 0xd8, 0xf9, 0x80, 0x3a, 0x44, 0x66, 0x54, 0x80, 0xd0, 0x78, 0x3e, - 0x55, 0xae, 0xdd, 0x6b, 0xf5, 0x3b, 0xe1, 0xb3, 0xf3, 0x55, 0xf7, 0x69, 0xc2, 0xf4, 0x64, 0x11, - 0xfb, 0x44, 0xf2, 0xa0, 0xea, 0x4b, 0x26, 0xc0, 0x44, 0x5d, 0x04, 0x7a, 0x39, 0xa7, 0xca, 0x0f, - 0xdf, 0x0c, 0x0f, 0x0e, 0x9f, 0x0c, 0x17, 0xf1, 0x5b, 0xba, 0x8c, 0xae, 0xd5, 0xac, 0xe1, 0x54, - 0x39, 0x0f, 0xd1, 0x4e, 0x83, 0xfe, 0xbc, 0x90, 0xe9, 0x82, 0xbb, 0x97, 0x7a, 0x76, 0x7f, 0x3b, - 0xba, 0x5e, 0xcb, 0xef, 0x8c, 0xea, 0x3c, 0x42, 0x37, 0xd4, 0x0c, 0xd4, 0x84, 0x89, 0x04, 0xc3, - 0x78, 0x9c, 0x52, 0xa5, 0xdc, 0x56, 0xcf, 0xee, 0x5f, 0x8d, 0x76, 0x6a, 0xfd, 0xa8, 0x94, 0x9d, - 0x43, 0x74, 0x8f, 0x33, 0x81, 0x9b, 0xb8, 0xce, 0xf1, 0x27, 0x4a, 0xb1, 0x02, 0xed, 0x6e, 0xf5, - 0xec, 0x7e, 0x2b, 0xba, 0xc5, 0x99, 0x18, 0x55, 0xee, 0xfb, 0xfc, 0x35, 0xa5, 0x23, 0xd0, 0xce, - 0x08, 0x15, 0x32, 0x26, 0x92, 0x73, 0xa6, 0x14, 0x93, 0x02, 0xa7, 0xa0, 0xa9, 0x7b, 0xb9, 0xe8, - 0x11, 0x3e, 0x38, 0x5d, 0x75, 0xad, 0xf3, 0x55, 0xf7, 0x7e, 0x39, 0x22, 0x35, 0x9e, 0xfa, 0x4c, - 0x06, 0x1c, 0xf4, 0xc4, 0x3f, 0xa6, 0x09, 0x90, 0xe5, 0x2b, 0x4a, 0xa2, 0x9b, 0x9c, 0x89, 0x97, - 0xcd, 0xef, 0x11, 0x68, 0xea, 0x9c, 0xa0, 0xed, 0xe6, 0x19, 0x06, 0xd7, 0x36, 0xb8, 0xc1, 0x05, - 0x70, 0x3f, 0xbe, 0x3f, 0x46, 0xd5, 0x42, 0x0a, 0x78, 0xa7, 0xe6, 0x18, 0xee, 0x73, 0xb4, 0xcb, - 0x21, 0xc7, 0x40, 0x34, 0xcb, 0x28, 0x8e, 0x35, 0xc1, 0x19, 0xcc, 0xd8, 0x18, 0xb4, 0x4c, 0x95, - 0x7b, 0xc5, 0x0c, 0xf0, 0x2e, 0x87, 0xfc, 0xc8, 0xf8, 0xa1, 0x26, 0x27, 0x8d, 0xfb, 0x62, 0xeb, - 0xeb, 0xb7, 0xae, 0x15, 0x1e, 0x9f, 0xae, 0x3d, 0xfb, 0x6c, 0xed, 0xd9, 0xbf, 0xd7, 0x9e, 0xfd, - 0x65, 0xe3, 0x59, 0x67, 0x1b, 0xcf, 0xfa, 0xb9, 0xf1, 0xac, 0x8f, 0xff, 0x5d, 0x69, 0xfe, 0xef, - 0xf5, 0x99, 0xfd, 0xc6, 0x6d, 0x73, 0x32, 0x07, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x98, 0x41, - 0x8e, 0x46, 0xa0, 0x02, 0x00, 0x00, + // 456 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xb1, 0x8e, 0xd3, 0x3e, + 0x1c, 0xc7, 0x93, 0x7f, 0xef, 0x5f, 0x84, 0xe9, 0x71, 0x10, 0x40, 0x84, 0x22, 0xd2, 0xea, 0x18, + 0x28, 0x03, 0x09, 0xe5, 0x4e, 0x0c, 0x6c, 0x2d, 0xe8, 0x24, 0xc4, 0x0d, 0x21, 0x45, 0x48, 0xb0, + 0x58, 0xbf, 0xb8, 0xbe, 0xd4, 0x6a, 0x6d, 0x97, 0xd8, 0x8d, 0x92, 0xb7, 0x60, 0x64, 0xe4, 0x21, + 0x78, 0x88, 0x1b, 0x4f, 0x4c, 0xe8, 0x86, 0x0a, 0xb5, 0x2f, 0x82, 0xe2, 0x24, 0x85, 0x0d, 0xb6, + 0xf8, 0xfb, 0xfb, 0xe4, 0x63, 0xcb, 0x5f, 0xa3, 0xc3, 0x18, 0xe2, 0x62, 0x21, 0x45, 0x10, 0x6b, + 0xa2, 0x34, 0xcc, 0x99, 0x48, 0x82, 0x6c, 0x18, 0x2c, 0x21, 0x05, 0xae, 0xfc, 0x65, 0x2a, 0xb5, + 0x74, 0xee, 0xd4, 0x8c, 0xff, 0x9b, 0xf1, 0xb3, 0x61, 0xf7, 0x76, 0x22, 0x13, 0x69, 0x88, 0xa0, + 0xfc, 0xaa, 0xe0, 0xee, 0x3d, 0x22, 0x15, 0x97, 0x0a, 0x57, 0x83, 0x6a, 0x51, 0x8d, 0x0e, 0xb7, + 0x2d, 0xd4, 0x0e, 0x8d, 0xd8, 0xf9, 0x80, 0x3a, 0x44, 0x66, 0x54, 0x80, 0xd0, 0x78, 0x39, 0x57, + 0xae, 0xdd, 0x6f, 0x0d, 0x3a, 0xe3, 0xe7, 0x97, 0xeb, 0xde, 0xb3, 0x84, 0xe9, 0xd9, 0x2a, 0xf6, + 0x89, 0xe4, 0x41, 0xbd, 0x2f, 0x99, 0x01, 0x13, 0xcd, 0x22, 0xd0, 0xc5, 0x92, 0x2a, 0x7f, 0xfc, + 0x3a, 0x3c, 0x3a, 0x7e, 0x1a, 0xae, 0xe2, 0x37, 0xb4, 0x88, 0xae, 0x35, 0xae, 0x70, 0xae, 0x9c, + 0x47, 0xe8, 0x60, 0xa7, 0xfe, 0xb4, 0x92, 0xe9, 0x8a, 0xbb, 0xff, 0xf5, 0xed, 0xc1, 0x7e, 0x74, + 0xbd, 0x89, 0xdf, 0x9a, 0xd4, 0x79, 0x8c, 0x6e, 0xa8, 0x05, 0xa8, 0x19, 0x13, 0x09, 0x86, 0xe9, + 0x34, 0xa5, 0x4a, 0xb9, 0xad, 0xbe, 0x3d, 0xb8, 0x1a, 0x1d, 0x34, 0xf9, 0xa8, 0x8a, 0x9d, 0x63, + 0x74, 0x97, 0x33, 0x81, 0x77, 0xb8, 0xce, 0xf1, 0x19, 0xa5, 0x58, 0x81, 0x76, 0xf7, 0xfa, 0xf6, + 0xa0, 0x15, 0xdd, 0xe2, 0x4c, 0x4c, 0xea, 0xe9, 0xbb, 0xfc, 0x84, 0xd2, 0x09, 0x68, 0x67, 0x82, + 0xca, 0x18, 0x13, 0xc9, 0x39, 0x53, 0x8a, 0x49, 0x81, 0x53, 0xd0, 0xd4, 0xfd, 0xbf, 0xdc, 0x63, + 0xfc, 0xf0, 0x7c, 0xdd, 0xb3, 0x2e, 0xd7, 0xbd, 0xfb, 0xd5, 0x15, 0xa9, 0xe9, 0xdc, 0x67, 0x32, + 0xe0, 0xa0, 0x67, 0xfe, 0x29, 0x4d, 0x80, 0x14, 0xaf, 0x28, 0x89, 0x6e, 0x72, 0x26, 0x5e, 0xee, + 0x7e, 0x8f, 0x40, 0x53, 0xe7, 0x3d, 0xda, 0xdf, 0x1d, 0xc3, 0xe8, 0xda, 0x46, 0x37, 0xfc, 0x07, + 0xdd, 0xf7, 0x6f, 0x4f, 0x50, 0x5d, 0x48, 0x29, 0xef, 0x34, 0x1e, 0xe3, 0x1d, 0xa1, 0x07, 0x1c, + 0x72, 0x0c, 0x44, 0xb3, 0x8c, 0xe2, 0x33, 0x26, 0x60, 0xc1, 0x74, 0x51, 0xd6, 0x98, 0xb1, 0x29, + 0x4d, 0x95, 0x7b, 0xc5, 0x5c, 0x62, 0x97, 0x43, 0x3e, 0x32, 0xcc, 0x49, 0x8d, 0x84, 0x0d, 0xf1, + 0x62, 0xef, 0xcb, 0xd7, 0x9e, 0x35, 0x3e, 0x3d, 0xdf, 0x78, 0xf6, 0xc5, 0xc6, 0xb3, 0x7f, 0x6e, + 0x3c, 0xfb, 0xf3, 0xd6, 0xb3, 0x2e, 0xb6, 0x9e, 0xf5, 0x63, 0xeb, 0x59, 0x1f, 0xff, 0x5a, 0x6d, + 0xfe, 0xe7, 0x2b, 0x34, 0x3d, 0xc7, 0x6d, 0xf3, 0x74, 0x8e, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, + 0x86, 0x1c, 0x80, 0x7e, 0xa8, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -171,8 +171,8 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.MaxActiveBtcValidators != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveBtcValidators)) + if m.MaxActiveFinalityProviders != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveFinalityProviders)) i-- dAtA[i] = 0x38 } @@ -267,8 +267,8 @@ func (m *Params) Size() (n int) { n += 1 + l + sovParams(uint64(l)) l = m.SlashingRate.Size() n += 1 + l + sovParams(uint64(l)) - if m.MaxActiveBtcValidators != 0 { - n += 1 + sovParams(uint64(m.MaxActiveBtcValidators)) + if m.MaxActiveFinalityProviders != 0 { + n += 1 + sovParams(uint64(m.MaxActiveFinalityProviders)) } return n } @@ -483,9 +483,9 @@ func (m *Params) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 7: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveBtcValidators", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveFinalityProviders", wireType) } - m.MaxActiveBtcValidators = 0 + m.MaxActiveFinalityProviders = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -495,7 +495,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MaxActiveBtcValidators |= uint32(b&0x7F) << shift + m.MaxActiveFinalityProviders |= uint32(b&0x7F) << shift if b < 0x80 { break } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 59fc7761c..05db57f83 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -114,25 +114,25 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } -// QueryBTCValidatorsRequest is the request type for the -// Query/BTCValidators RPC method. -type QueryBTCValidatorsRequest struct { +// QueryFinalityProvidersRequest is the request type for the +// Query/FinalityProviders RPC method. +type QueryFinalityProvidersRequest struct { // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorsRequest) Reset() { *m = QueryBTCValidatorsRequest{} } -func (m *QueryBTCValidatorsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorsRequest) ProtoMessage() {} -func (*QueryBTCValidatorsRequest) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProvidersRequest) Reset() { *m = QueryFinalityProvidersRequest{} } +func (m *QueryFinalityProvidersRequest) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProvidersRequest) ProtoMessage() {} +func (*QueryFinalityProvidersRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{2} } -func (m *QueryBTCValidatorsRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProvidersRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProvidersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorsRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProvidersRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -142,46 +142,46 @@ func (m *QueryBTCValidatorsRequest) XXX_Marshal(b []byte, deterministic bool) ([ return b[:n], nil } } -func (m *QueryBTCValidatorsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorsRequest.Merge(m, src) +func (m *QueryFinalityProvidersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProvidersRequest.Merge(m, src) } -func (m *QueryBTCValidatorsRequest) XXX_Size() int { +func (m *QueryFinalityProvidersRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorsRequest.DiscardUnknown(m) +func (m *QueryFinalityProvidersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProvidersRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorsRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProvidersRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorsRequest) GetPagination() *query.PageRequest { +func (m *QueryFinalityProvidersRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination } return nil } -// QueryBTCValidatorsResponse is the response type for the -// Query/BTCValidators RPC method. -type QueryBTCValidatorsResponse struct { - // btc_validators contains all the BTC validators - BtcValidators []*BTCValidator `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` +// QueryFinalityProvidersResponse is the response type for the +// Query/FinalityProviders RPC method. +type QueryFinalityProvidersResponse struct { + // finality_providers contains all the finality providers + FinalityProviders []*FinalityProvider `protobuf:"bytes,1,rep,name=finality_providers,json=finalityProviders,proto3" json:"finality_providers,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorsResponse) Reset() { *m = QueryBTCValidatorsResponse{} } -func (m *QueryBTCValidatorsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorsResponse) ProtoMessage() {} -func (*QueryBTCValidatorsResponse) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProvidersResponse) Reset() { *m = QueryFinalityProvidersResponse{} } +func (m *QueryFinalityProvidersResponse) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProvidersResponse) ProtoMessage() {} +func (*QueryFinalityProvidersResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{3} } -func (m *QueryBTCValidatorsResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProvidersResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProvidersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorsResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProvidersResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -191,50 +191,50 @@ func (m *QueryBTCValidatorsResponse) XXX_Marshal(b []byte, deterministic bool) ( return b[:n], nil } } -func (m *QueryBTCValidatorsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorsResponse.Merge(m, src) +func (m *QueryFinalityProvidersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProvidersResponse.Merge(m, src) } -func (m *QueryBTCValidatorsResponse) XXX_Size() int { +func (m *QueryFinalityProvidersResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorsResponse.DiscardUnknown(m) +func (m *QueryFinalityProvidersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProvidersResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorsResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProvidersResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorsResponse) GetBtcValidators() []*BTCValidator { +func (m *QueryFinalityProvidersResponse) GetFinalityProviders() []*FinalityProvider { if m != nil { - return m.BtcValidators + return m.FinalityProviders } return nil } -func (m *QueryBTCValidatorsResponse) GetPagination() *query.PageResponse { +func (m *QueryFinalityProvidersResponse) GetPagination() *query.PageResponse { if m != nil { return m.Pagination } return nil } -// QueryBTCValidatorsRequest requests information about a BTC validator -type QueryBTCValidatorRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` +// QueryFinalityProviderRequest requests information about a finality provider +type QueryFinalityProviderRequest struct { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` } -func (m *QueryBTCValidatorRequest) Reset() { *m = QueryBTCValidatorRequest{} } -func (m *QueryBTCValidatorRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorRequest) ProtoMessage() {} -func (*QueryBTCValidatorRequest) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderRequest) Reset() { *m = QueryFinalityProviderRequest{} } +func (m *QueryFinalityProviderRequest) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProviderRequest) ProtoMessage() {} +func (*QueryFinalityProviderRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{4} } -func (m *QueryBTCValidatorRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -244,43 +244,43 @@ func (m *QueryBTCValidatorRequest) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *QueryBTCValidatorRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorRequest.Merge(m, src) +func (m *QueryFinalityProviderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderRequest.Merge(m, src) } -func (m *QueryBTCValidatorRequest) XXX_Size() int { +func (m *QueryFinalityProviderRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorRequest.DiscardUnknown(m) +func (m *QueryFinalityProviderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorRequest) GetValBtcPkHex() string { +func (m *QueryFinalityProviderRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } -// QueryBTCValidatorsResponse resoponse contains information about a BTC validator -type QueryBTCValidatorResponse struct { - // btc_validator contains the BTC validator - BtcValidator *BTCValidator `protobuf:"bytes,1,opt,name=btc_validator,json=btcValidator,proto3" json:"btc_validator,omitempty"` +// QueryFinalityProviderResponse contains information about a finality provider +type QueryFinalityProviderResponse struct { + // finality_provider contains the FinalityProvider + FinalityProvider *FinalityProvider `protobuf:"bytes,1,opt,name=finality_provider,json=finalityProvider,proto3" json:"finality_provider,omitempty"` } -func (m *QueryBTCValidatorResponse) Reset() { *m = QueryBTCValidatorResponse{} } -func (m *QueryBTCValidatorResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorResponse) ProtoMessage() {} -func (*QueryBTCValidatorResponse) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderResponse) Reset() { *m = QueryFinalityProviderResponse{} } +func (m *QueryFinalityProviderResponse) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProviderResponse) ProtoMessage() {} +func (*QueryFinalityProviderResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{5} } -func (m *QueryBTCValidatorResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -290,21 +290,21 @@ func (m *QueryBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool) ([ return b[:n], nil } } -func (m *QueryBTCValidatorResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorResponse.Merge(m, src) +func (m *QueryFinalityProviderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderResponse.Merge(m, src) } -func (m *QueryBTCValidatorResponse) XXX_Size() int { +func (m *QueryFinalityProviderResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorResponse.DiscardUnknown(m) +func (m *QueryFinalityProviderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorResponse) GetBtcValidator() *BTCValidator { +func (m *QueryFinalityProviderResponse) GetFinalityProvider() *FinalityProvider { if m != nil { - return m.BtcValidator + return m.FinalityProvider } return nil } @@ -421,29 +421,33 @@ func (m *QueryBTCDelegationsResponse) GetPagination() *query.PageResponse { return nil } -// QueryBTCValidatorPowerAtHeightRequest is the request type for the -// Query/BTCValidatorPowerAtHeight RPC method. -type QueryBTCValidatorPowerAtHeightRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderPowerAtHeightRequest is the request type for the +// Query/FinalityProviderPowerAtHeight RPC method. +type QueryFinalityProviderPowerAtHeightRequest struct { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider that // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` - // height is used for querying the given validator's voting power at this height + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` + // height is used for querying the given finality provider's voting power at this height Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` } -func (m *QueryBTCValidatorPowerAtHeightRequest) Reset() { *m = QueryBTCValidatorPowerAtHeightRequest{} } -func (m *QueryBTCValidatorPowerAtHeightRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorPowerAtHeightRequest) ProtoMessage() {} -func (*QueryBTCValidatorPowerAtHeightRequest) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderPowerAtHeightRequest) Reset() { + *m = QueryFinalityProviderPowerAtHeightRequest{} +} +func (m *QueryFinalityProviderPowerAtHeightRequest) String() string { + return proto.CompactTextString(m) +} +func (*QueryFinalityProviderPowerAtHeightRequest) ProtoMessage() {} +func (*QueryFinalityProviderPowerAtHeightRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{8} } -func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderPowerAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderPowerAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderPowerAtHeightRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -453,53 +457,55 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Marshal(b []byte, determinis return b[:n], nil } } -func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.Merge(m, src) +func (m *QueryFinalityProviderPowerAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderPowerAtHeightRequest.Merge(m, src) } -func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_Size() int { +func (m *QueryFinalityProviderPowerAtHeightRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorPowerAtHeightRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest.DiscardUnknown(m) +func (m *QueryFinalityProviderPowerAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderPowerAtHeightRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorPowerAtHeightRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderPowerAtHeightRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorPowerAtHeightRequest) GetValBtcPkHex() string { +func (m *QueryFinalityProviderPowerAtHeightRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } -func (m *QueryBTCValidatorPowerAtHeightRequest) GetHeight() uint64 { +func (m *QueryFinalityProviderPowerAtHeightRequest) GetHeight() uint64 { if m != nil { return m.Height } return 0 } -// QueryBTCValidatorPowerAtHeightResponse is the response type for the -// Query/BTCValidatorPowerAtHeight RPC method. -type QueryBTCValidatorPowerAtHeightResponse struct { - // voting_power is the voting power of the BTC validator +// QueryFinalityProviderPowerAtHeightResponse is the response type for the +// Query/FinalityProviderPowerAtHeight RPC method. +type QueryFinalityProviderPowerAtHeightResponse struct { + // voting_power is the voting power of the finality provider VotingPower uint64 `protobuf:"varint,1,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } -func (m *QueryBTCValidatorPowerAtHeightResponse) Reset() { - *m = QueryBTCValidatorPowerAtHeightResponse{} +func (m *QueryFinalityProviderPowerAtHeightResponse) Reset() { + *m = QueryFinalityProviderPowerAtHeightResponse{} +} +func (m *QueryFinalityProviderPowerAtHeightResponse) String() string { + return proto.CompactTextString(m) } -func (m *QueryBTCValidatorPowerAtHeightResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorPowerAtHeightResponse) ProtoMessage() {} -func (*QueryBTCValidatorPowerAtHeightResponse) Descriptor() ([]byte, []int) { +func (*QueryFinalityProviderPowerAtHeightResponse) ProtoMessage() {} +func (*QueryFinalityProviderPowerAtHeightResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{9} } -func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderPowerAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderPowerAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderPowerAtHeightResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -509,46 +515,48 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Marshal(b []byte, determini return b[:n], nil } } -func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.Merge(m, src) +func (m *QueryFinalityProviderPowerAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderPowerAtHeightResponse.Merge(m, src) } -func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_Size() int { +func (m *QueryFinalityProviderPowerAtHeightResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorPowerAtHeightResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse.DiscardUnknown(m) +func (m *QueryFinalityProviderPowerAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderPowerAtHeightResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorPowerAtHeightResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderPowerAtHeightResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorPowerAtHeightResponse) GetVotingPower() uint64 { +func (m *QueryFinalityProviderPowerAtHeightResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower } return 0 } -// QueryBTCValidatorCurrentPowerRequest is the request type for the -// Query/BTCValidatorCurrentPower RPC method. -type QueryBTCValidatorCurrentPowerRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderCurrentPowerRequest is the request type for the +// Query/FinalityProviderCurrentPower RPC method. +type QueryFinalityProviderCurrentPowerRequest struct { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider that // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` } -func (m *QueryBTCValidatorCurrentPowerRequest) Reset() { *m = QueryBTCValidatorCurrentPowerRequest{} } -func (m *QueryBTCValidatorCurrentPowerRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorCurrentPowerRequest) ProtoMessage() {} -func (*QueryBTCValidatorCurrentPowerRequest) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderCurrentPowerRequest) Reset() { + *m = QueryFinalityProviderCurrentPowerRequest{} +} +func (m *QueryFinalityProviderCurrentPowerRequest) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProviderCurrentPowerRequest) ProtoMessage() {} +func (*QueryFinalityProviderCurrentPowerRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{10} } -func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderCurrentPowerRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderCurrentPowerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderCurrentPowerRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -558,46 +566,50 @@ func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Marshal(b []byte, determinist return b[:n], nil } } -func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.Merge(m, src) +func (m *QueryFinalityProviderCurrentPowerRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderCurrentPowerRequest.Merge(m, src) } -func (m *QueryBTCValidatorCurrentPowerRequest) XXX_Size() int { +func (m *QueryFinalityProviderCurrentPowerRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorCurrentPowerRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest.DiscardUnknown(m) +func (m *QueryFinalityProviderCurrentPowerRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderCurrentPowerRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorCurrentPowerRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderCurrentPowerRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorCurrentPowerRequest) GetValBtcPkHex() string { +func (m *QueryFinalityProviderCurrentPowerRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } -// QueryBTCValidatorCurrentPowerResponse is the response type for the -// Query/BTCValidatorCurrentPower RPC method. -type QueryBTCValidatorCurrentPowerResponse struct { +// QueryFinalityProviderCurrentPowerResponse is the response type for the +// Query/FinalityProviderCurrentPower RPC method. +type QueryFinalityProviderCurrentPowerResponse struct { // height is the current height Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` - // voting_power is the voting power of the BTC validator + // voting_power is the voting power of the finality provider VotingPower uint64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` } -func (m *QueryBTCValidatorCurrentPowerResponse) Reset() { *m = QueryBTCValidatorCurrentPowerResponse{} } -func (m *QueryBTCValidatorCurrentPowerResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorCurrentPowerResponse) ProtoMessage() {} -func (*QueryBTCValidatorCurrentPowerResponse) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderCurrentPowerResponse) Reset() { + *m = QueryFinalityProviderCurrentPowerResponse{} +} +func (m *QueryFinalityProviderCurrentPowerResponse) String() string { + return proto.CompactTextString(m) +} +func (*QueryFinalityProviderCurrentPowerResponse) ProtoMessage() {} +func (*QueryFinalityProviderCurrentPowerResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{11} } -func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderCurrentPowerResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderCurrentPowerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderCurrentPowerResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -607,55 +619,57 @@ func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Marshal(b []byte, determinis return b[:n], nil } } -func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.Merge(m, src) +func (m *QueryFinalityProviderCurrentPowerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderCurrentPowerResponse.Merge(m, src) } -func (m *QueryBTCValidatorCurrentPowerResponse) XXX_Size() int { +func (m *QueryFinalityProviderCurrentPowerResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorCurrentPowerResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse.DiscardUnknown(m) +func (m *QueryFinalityProviderCurrentPowerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderCurrentPowerResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorCurrentPowerResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderCurrentPowerResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorCurrentPowerResponse) GetHeight() uint64 { +func (m *QueryFinalityProviderCurrentPowerResponse) GetHeight() uint64 { if m != nil { return m.Height } return 0 } -func (m *QueryBTCValidatorCurrentPowerResponse) GetVotingPower() uint64 { +func (m *QueryFinalityProviderCurrentPowerResponse) GetVotingPower() uint64 { if m != nil { return m.VotingPower } return 0 } -// QueryActiveBTCValidatorsAtHeightRequest is the request type for the -// Query/ActiveBTCValidatorsAtHeight RPC method. -type QueryActiveBTCValidatorsAtHeightRequest struct { - // height defines at which Babylon height to query the BTC validators info. +// QueryActiveFinalityProvidersAtHeightRequest is the request type for the +// Query/ActiveFinalityProvidersAtHeight RPC method. +type QueryActiveFinalityProvidersAtHeightRequest struct { + // height defines at which Babylon height to query the finality providers info. Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryActiveBTCValidatorsAtHeightRequest) Reset() { - *m = QueryActiveBTCValidatorsAtHeightRequest{} +func (m *QueryActiveFinalityProvidersAtHeightRequest) Reset() { + *m = QueryActiveFinalityProvidersAtHeightRequest{} +} +func (m *QueryActiveFinalityProvidersAtHeightRequest) String() string { + return proto.CompactTextString(m) } -func (m *QueryActiveBTCValidatorsAtHeightRequest) String() string { return proto.CompactTextString(m) } -func (*QueryActiveBTCValidatorsAtHeightRequest) ProtoMessage() {} -func (*QueryActiveBTCValidatorsAtHeightRequest) Descriptor() ([]byte, []int) { +func (*QueryActiveFinalityProvidersAtHeightRequest) ProtoMessage() {} +func (*QueryActiveFinalityProvidersAtHeightRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{12} } -func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryActiveFinalityProvidersAtHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryActiveFinalityProvidersAtHeightRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryActiveFinalityProvidersAtHeightRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -665,55 +679,57 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Marshal(b []byte, determin return b[:n], nil } } -func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.Merge(m, src) +func (m *QueryActiveFinalityProvidersAtHeightRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActiveFinalityProvidersAtHeightRequest.Merge(m, src) } -func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_Size() int { +func (m *QueryActiveFinalityProvidersAtHeightRequest) XXX_Size() int { return m.Size() } -func (m *QueryActiveBTCValidatorsAtHeightRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest.DiscardUnknown(m) +func (m *QueryActiveFinalityProvidersAtHeightRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActiveFinalityProvidersAtHeightRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryActiveBTCValidatorsAtHeightRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryActiveFinalityProvidersAtHeightRequest proto.InternalMessageInfo -func (m *QueryActiveBTCValidatorsAtHeightRequest) GetHeight() uint64 { +func (m *QueryActiveFinalityProvidersAtHeightRequest) GetHeight() uint64 { if m != nil { return m.Height } return 0 } -func (m *QueryActiveBTCValidatorsAtHeightRequest) GetPagination() *query.PageRequest { +func (m *QueryActiveFinalityProvidersAtHeightRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination } return nil } -// QueryActiveBTCValidatorsAtHeightResponse is the response type for the -// Query/ActiveBTCValidatorsAtHeight RPC method. -type QueryActiveBTCValidatorsAtHeightResponse struct { - // btc_validators contains all the queried BTC validators. - BtcValidators []*BTCValidatorWithMeta `protobuf:"bytes,1,rep,name=btc_validators,json=btcValidators,proto3" json:"btc_validators,omitempty"` +// QueryActiveFinalityProvidersAtHeightResponse is the response type for the +// Query/ActiveFinalityProvidersAtHeight RPC method. +type QueryActiveFinalityProvidersAtHeightResponse struct { + // finality_providers contains all the queried finality providersn. + FinalityProviders []*FinalityProviderWithMeta `protobuf:"bytes,1,rep,name=finality_providers,json=finalityProviders,proto3" json:"finality_providers,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryActiveBTCValidatorsAtHeightResponse) Reset() { - *m = QueryActiveBTCValidatorsAtHeightResponse{} +func (m *QueryActiveFinalityProvidersAtHeightResponse) Reset() { + *m = QueryActiveFinalityProvidersAtHeightResponse{} +} +func (m *QueryActiveFinalityProvidersAtHeightResponse) String() string { + return proto.CompactTextString(m) } -func (m *QueryActiveBTCValidatorsAtHeightResponse) String() string { return proto.CompactTextString(m) } -func (*QueryActiveBTCValidatorsAtHeightResponse) ProtoMessage() {} -func (*QueryActiveBTCValidatorsAtHeightResponse) Descriptor() ([]byte, []int) { +func (*QueryActiveFinalityProvidersAtHeightResponse) ProtoMessage() {} +func (*QueryActiveFinalityProvidersAtHeightResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{13} } -func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryActiveFinalityProvidersAtHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryActiveFinalityProvidersAtHeightResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryActiveFinalityProvidersAtHeightResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -723,26 +739,26 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Marshal(b []byte, determi return b[:n], nil } } -func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.Merge(m, src) +func (m *QueryActiveFinalityProvidersAtHeightResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActiveFinalityProvidersAtHeightResponse.Merge(m, src) } -func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_Size() int { +func (m *QueryActiveFinalityProvidersAtHeightResponse) XXX_Size() int { return m.Size() } -func (m *QueryActiveBTCValidatorsAtHeightResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse.DiscardUnknown(m) +func (m *QueryActiveFinalityProvidersAtHeightResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActiveFinalityProvidersAtHeightResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryActiveBTCValidatorsAtHeightResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryActiveFinalityProvidersAtHeightResponse proto.InternalMessageInfo -func (m *QueryActiveBTCValidatorsAtHeightResponse) GetBtcValidators() []*BTCValidatorWithMeta { +func (m *QueryActiveFinalityProvidersAtHeightResponse) GetFinalityProviders() []*FinalityProviderWithMeta { if m != nil { - return m.BtcValidators + return m.FinalityProviders } return nil } -func (m *QueryActiveBTCValidatorsAtHeightResponse) GetPagination() *query.PageResponse { +func (m *QueryActiveFinalityProvidersAtHeightResponse) GetPagination() *query.PageResponse { if m != nil { return m.Pagination } @@ -831,29 +847,31 @@ func (m *QueryActivatedHeightResponse) GetHeight() uint64 { return 0 } -// QueryBTCValidatorDelegationsRequest is the request type for the -// Query/BTCValidatorDelegations RPC method. -type QueryBTCValidatorDelegationsRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator that +// QueryFinalityProviderDelegationsRequest is the request type for the +// Query/FinalityProviderDelegations RPC method. +type QueryFinalityProviderDelegationsRequest struct { + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality providerthat // this BTC delegation delegates to // the PK follows encoding in BIP-340 spec - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorDelegationsRequest) Reset() { *m = QueryBTCValidatorDelegationsRequest{} } -func (m *QueryBTCValidatorDelegationsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorDelegationsRequest) ProtoMessage() {} -func (*QueryBTCValidatorDelegationsRequest) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderDelegationsRequest) Reset() { + *m = QueryFinalityProviderDelegationsRequest{} +} +func (m *QueryFinalityProviderDelegationsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProviderDelegationsRequest) ProtoMessage() {} +func (*QueryFinalityProviderDelegationsRequest) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{16} } -func (m *QueryBTCValidatorDelegationsRequest) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderDelegationsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderDelegationsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorDelegationsRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderDelegationsRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -863,53 +881,55 @@ func (m *QueryBTCValidatorDelegationsRequest) XXX_Marshal(b []byte, deterministi return b[:n], nil } } -func (m *QueryBTCValidatorDelegationsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorDelegationsRequest.Merge(m, src) +func (m *QueryFinalityProviderDelegationsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderDelegationsRequest.Merge(m, src) } -func (m *QueryBTCValidatorDelegationsRequest) XXX_Size() int { +func (m *QueryFinalityProviderDelegationsRequest) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorDelegationsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorDelegationsRequest.DiscardUnknown(m) +func (m *QueryFinalityProviderDelegationsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderDelegationsRequest.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorDelegationsRequest proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderDelegationsRequest proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsRequest) GetValBtcPkHex() string { +func (m *QueryFinalityProviderDelegationsRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } -func (m *QueryBTCValidatorDelegationsRequest) GetPagination() *query.PageRequest { +func (m *QueryFinalityProviderDelegationsRequest) GetPagination() *query.PageRequest { if m != nil { return m.Pagination } return nil } -// QueryBTCValidatorDelegationsResponse is the response type for the -// Query/BTCValidatorDelegations RPC method. -type QueryBTCValidatorDelegationsResponse struct { +// QueryFinalityProviderDelegationsResponse is the response type for the +// Query/FinalityProviderDelegations RPC method. +type QueryFinalityProviderDelegationsResponse struct { // btc_delegator_delegations contains all the queried BTC delegations. BtcDelegatorDelegations []*BTCDelegatorDelegations `protobuf:"bytes,1,rep,name=btc_delegator_delegations,json=btcDelegatorDelegations,proto3" json:"btc_delegator_delegations,omitempty"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } -func (m *QueryBTCValidatorDelegationsResponse) Reset() { *m = QueryBTCValidatorDelegationsResponse{} } -func (m *QueryBTCValidatorDelegationsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryBTCValidatorDelegationsResponse) ProtoMessage() {} -func (*QueryBTCValidatorDelegationsResponse) Descriptor() ([]byte, []int) { +func (m *QueryFinalityProviderDelegationsResponse) Reset() { + *m = QueryFinalityProviderDelegationsResponse{} +} +func (m *QueryFinalityProviderDelegationsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryFinalityProviderDelegationsResponse) ProtoMessage() {} +func (*QueryFinalityProviderDelegationsResponse) Descriptor() ([]byte, []int) { return fileDescriptor_74d49d26f7429697, []int{17} } -func (m *QueryBTCValidatorDelegationsResponse) XXX_Unmarshal(b []byte) error { +func (m *QueryFinalityProviderDelegationsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *QueryBTCValidatorDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *QueryFinalityProviderDelegationsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_QueryBTCValidatorDelegationsResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_QueryFinalityProviderDelegationsResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -919,26 +939,26 @@ func (m *QueryBTCValidatorDelegationsResponse) XXX_Marshal(b []byte, determinist return b[:n], nil } } -func (m *QueryBTCValidatorDelegationsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryBTCValidatorDelegationsResponse.Merge(m, src) +func (m *QueryFinalityProviderDelegationsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFinalityProviderDelegationsResponse.Merge(m, src) } -func (m *QueryBTCValidatorDelegationsResponse) XXX_Size() int { +func (m *QueryFinalityProviderDelegationsResponse) XXX_Size() int { return m.Size() } -func (m *QueryBTCValidatorDelegationsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryBTCValidatorDelegationsResponse.DiscardUnknown(m) +func (m *QueryFinalityProviderDelegationsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFinalityProviderDelegationsResponse.DiscardUnknown(m) } -var xxx_messageInfo_QueryBTCValidatorDelegationsResponse proto.InternalMessageInfo +var xxx_messageInfo_QueryFinalityProviderDelegationsResponse proto.InternalMessageInfo -func (m *QueryBTCValidatorDelegationsResponse) GetBtcDelegatorDelegations() []*BTCDelegatorDelegations { +func (m *QueryFinalityProviderDelegationsResponse) GetBtcDelegatorDelegations() []*BTCDelegatorDelegations { if m != nil { return m.BtcDelegatorDelegations } return nil } -func (m *QueryBTCValidatorDelegationsResponse) GetPagination() *query.PageResponse { +func (m *QueryFinalityProviderDelegationsResponse) GetPagination() *query.PageResponse { if m != nil { return m.Pagination } @@ -998,9 +1018,9 @@ type QueryBTCDelegationResponse struct { // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk_list is the list of BIP-340 PKs of the BTC validators that + // fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that // this BTC delegation delegates to - ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` // start_height is the start BTC height of the BTC delegation // it is the start BTC height of the timelock StartHeight uint64 `protobuf:"varint,3,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` @@ -1097,22 +1117,22 @@ func (m *QueryBTCDelegationResponse) GetUndelegationInfo() *BTCUndelegationInfo func init() { proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btcstaking.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btcstaking.v1.QueryParamsResponse") - proto.RegisterType((*QueryBTCValidatorsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsRequest") - proto.RegisterType((*QueryBTCValidatorsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorsResponse") - proto.RegisterType((*QueryBTCValidatorRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorRequest") - proto.RegisterType((*QueryBTCValidatorResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorResponse") + proto.RegisterType((*QueryFinalityProvidersRequest)(nil), "babylon.btcstaking.v1.QueryFinalityProvidersRequest") + proto.RegisterType((*QueryFinalityProvidersResponse)(nil), "babylon.btcstaking.v1.QueryFinalityProvidersResponse") + proto.RegisterType((*QueryFinalityProviderRequest)(nil), "babylon.btcstaking.v1.QueryFinalityProviderRequest") + proto.RegisterType((*QueryFinalityProviderResponse)(nil), "babylon.btcstaking.v1.QueryFinalityProviderResponse") proto.RegisterType((*QueryBTCDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCDelegationsRequest") proto.RegisterType((*QueryBTCDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCDelegationsResponse") - proto.RegisterType((*QueryBTCValidatorPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightRequest") - proto.RegisterType((*QueryBTCValidatorPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorPowerAtHeightResponse") - proto.RegisterType((*QueryBTCValidatorCurrentPowerRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerRequest") - proto.RegisterType((*QueryBTCValidatorCurrentPowerResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorCurrentPowerResponse") - proto.RegisterType((*QueryActiveBTCValidatorsAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightRequest") - proto.RegisterType((*QueryActiveBTCValidatorsAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryActiveBTCValidatorsAtHeightResponse") + proto.RegisterType((*QueryFinalityProviderPowerAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryFinalityProviderPowerAtHeightRequest") + proto.RegisterType((*QueryFinalityProviderPowerAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryFinalityProviderPowerAtHeightResponse") + proto.RegisterType((*QueryFinalityProviderCurrentPowerRequest)(nil), "babylon.btcstaking.v1.QueryFinalityProviderCurrentPowerRequest") + proto.RegisterType((*QueryFinalityProviderCurrentPowerResponse)(nil), "babylon.btcstaking.v1.QueryFinalityProviderCurrentPowerResponse") + proto.RegisterType((*QueryActiveFinalityProvidersAtHeightRequest)(nil), "babylon.btcstaking.v1.QueryActiveFinalityProvidersAtHeightRequest") + proto.RegisterType((*QueryActiveFinalityProvidersAtHeightResponse)(nil), "babylon.btcstaking.v1.QueryActiveFinalityProvidersAtHeightResponse") proto.RegisterType((*QueryActivatedHeightRequest)(nil), "babylon.btcstaking.v1.QueryActivatedHeightRequest") proto.RegisterType((*QueryActivatedHeightResponse)(nil), "babylon.btcstaking.v1.QueryActivatedHeightResponse") - proto.RegisterType((*QueryBTCValidatorDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsRequest") - proto.RegisterType((*QueryBTCValidatorDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryBTCValidatorDelegationsResponse") + proto.RegisterType((*QueryFinalityProviderDelegationsRequest)(nil), "babylon.btcstaking.v1.QueryFinalityProviderDelegationsRequest") + proto.RegisterType((*QueryFinalityProviderDelegationsResponse)(nil), "babylon.btcstaking.v1.QueryFinalityProviderDelegationsResponse") proto.RegisterType((*QueryBTCDelegationRequest)(nil), "babylon.btcstaking.v1.QueryBTCDelegationRequest") proto.RegisterType((*QueryBTCDelegationResponse)(nil), "babylon.btcstaking.v1.QueryBTCDelegationResponse") } @@ -1120,85 +1140,86 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1246 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xce, 0x24, 0xa9, 0x69, 0x5f, 0x9c, 0xa4, 0x4c, 0x0b, 0x75, 0x36, 0x8d, 0x93, 0x6e, 0x7e, - 0x36, 0xa8, 0xbb, 0xb1, 0x83, 0x72, 0x20, 0xa1, 0xa5, 0x6e, 0xa0, 0x69, 0xd2, 0x48, 0x66, 0x5b, - 0xa8, 0x84, 0x84, 0xac, 0x59, 0x67, 0xba, 0x5e, 0xe2, 0xec, 0xba, 0xde, 0xb1, 0x71, 0x54, 0xe5, - 0xc2, 0x01, 0x09, 0x71, 0x41, 0xf0, 0x27, 0x70, 0x00, 0x89, 0x13, 0x07, 0x2e, 0x70, 0xe1, 0x46, - 0x8f, 0x95, 0x90, 0x10, 0xa2, 0x52, 0x84, 0x12, 0x24, 0xfe, 0x0d, 0xe4, 0xd9, 0x71, 0x76, 0x37, - 0x5e, 0xdb, 0x6b, 0x37, 0xdc, 0xe2, 0x9d, 0xf7, 0xe3, 0xfb, 0xde, 0x7c, 0xfb, 0xed, 0x53, 0xe0, - 0x9a, 0x4e, 0xf4, 0xfd, 0xa2, 0x6d, 0xa9, 0x3a, 0xcb, 0x3b, 0x8c, 0xec, 0x9a, 0x96, 0xa1, 0x56, - 0x53, 0xea, 0x93, 0x0a, 0x2d, 0xef, 0x2b, 0xa5, 0xb2, 0xcd, 0x6c, 0xfc, 0x9a, 0x08, 0x51, 0xbc, - 0x10, 0xa5, 0x9a, 0x92, 0x2e, 0x1b, 0xb6, 0x61, 0xf3, 0x08, 0xb5, 0xfe, 0x97, 0x1b, 0x2c, 0x5d, - 0x35, 0x6c, 0xdb, 0x28, 0x52, 0x95, 0x94, 0x4c, 0x95, 0x58, 0x96, 0xcd, 0x08, 0x33, 0x6d, 0xcb, - 0x11, 0xa7, 0x8b, 0x79, 0xdb, 0xd9, 0xb3, 0x1d, 0x55, 0x27, 0x0e, 0x75, 0x7b, 0xa8, 0xd5, 0x94, - 0x4e, 0x19, 0x49, 0xa9, 0x25, 0x62, 0x98, 0x16, 0x0f, 0x16, 0xb1, 0x72, 0x38, 0xb2, 0x12, 0x29, - 0x93, 0xbd, 0x46, 0xbd, 0xb9, 0xf0, 0x18, 0x1f, 0x50, 0x1e, 0x27, 0x5f, 0x06, 0xfc, 0x7e, 0xbd, - 0x5b, 0x96, 0x27, 0x6b, 0xf4, 0x49, 0x85, 0x3a, 0x4c, 0xd6, 0xe0, 0x52, 0xe0, 0xa9, 0x53, 0xb2, - 0x2d, 0x87, 0xe2, 0x55, 0x88, 0xb9, 0x4d, 0x12, 0x68, 0x0a, 0x2d, 0x0c, 0xa5, 0x27, 0x94, 0xd0, - 0x01, 0x28, 0x6e, 0x5a, 0x66, 0xf0, 0xd9, 0xe1, 0x64, 0x9f, 0x26, 0x52, 0xe4, 0x3c, 0x8c, 0xf1, - 0x9a, 0x99, 0x87, 0x77, 0x3e, 0x24, 0x45, 0x73, 0x87, 0x30, 0xbb, 0xdc, 0x68, 0x88, 0xdf, 0x03, - 0xf0, 0x68, 0x8a, 0xea, 0x73, 0x8a, 0x3b, 0x13, 0xa5, 0x3e, 0x13, 0xc5, 0x9d, 0xbb, 0x98, 0x89, - 0x92, 0x25, 0x06, 0x15, 0xb9, 0x9a, 0x2f, 0x53, 0xfe, 0x11, 0x81, 0x14, 0xd6, 0x45, 0x10, 0xd8, - 0x84, 0x11, 0x9d, 0xe5, 0x73, 0xd5, 0x93, 0x93, 0x04, 0x9a, 0x1a, 0x58, 0x18, 0x4a, 0x4f, 0xb7, - 0x20, 0xe2, 0xaf, 0xa2, 0x0d, 0xeb, 0x2c, 0xef, 0xd5, 0xc4, 0x77, 0x03, 0x90, 0xfb, 0x39, 0xe4, - 0xf9, 0x8e, 0x90, 0x5d, 0x20, 0x01, 0xcc, 0xb7, 0x20, 0xd1, 0x04, 0xb9, 0x31, 0x97, 0x69, 0x18, - 0xa9, 0x92, 0x62, 0xae, 0x0e, 0xba, 0xb4, 0x9b, 0x2b, 0xd0, 0x1a, 0x9f, 0xcd, 0x05, 0x6d, 0xa8, - 0x4a, 0x8a, 0x19, 0x96, 0xcf, 0xee, 0x6e, 0xd0, 0x9a, 0x4c, 0x43, 0x26, 0x7b, 0x42, 0x79, 0x03, - 0x86, 0x03, 0x94, 0xc5, 0x70, 0x23, 0x31, 0x8e, 0xfb, 0x19, 0xcb, 0xdf, 0xfb, 0x66, 0xbb, 0x4e, - 0x8b, 0xd4, 0x70, 0x05, 0xdc, 0x80, 0x9a, 0x81, 0x98, 0xc3, 0x08, 0xab, 0xb8, 0xe2, 0x18, 0x49, - 0x2f, 0xb6, 0xee, 0xe0, 0x65, 0x3f, 0xe0, 0x19, 0x9a, 0xc8, 0x3c, 0x25, 0x83, 0xfe, 0x9e, 0x65, - 0xf0, 0x13, 0x82, 0xf1, 0x50, 0xa8, 0x62, 0x28, 0xdb, 0x30, 0x5a, 0x1f, 0xca, 0x8e, 0x77, 0x24, - 0x84, 0x30, 0x13, 0x05, 0xb4, 0x56, 0x17, 0x91, 0xaf, 0xec, 0xd9, 0x49, 0x61, 0x07, 0x66, 0x9b, - 0x6e, 0x32, 0x6b, 0x7f, 0x4a, 0xcb, 0xb7, 0xd9, 0x06, 0x35, 0x8d, 0x02, 0xeb, 0x46, 0x17, 0xf8, - 0x75, 0x88, 0x15, 0x78, 0x16, 0x87, 0x34, 0xa8, 0x89, 0x5f, 0xf2, 0x16, 0xcc, 0x75, 0xea, 0x22, - 0xe6, 0x74, 0x0d, 0xe2, 0x55, 0x9b, 0x99, 0x96, 0x91, 0x2b, 0xd5, 0xcf, 0x79, 0x93, 0x41, 0x6d, - 0xc8, 0x7d, 0xc6, 0x53, 0xe4, 0x2d, 0x98, 0x69, 0x2a, 0x76, 0xa7, 0x52, 0x2e, 0x53, 0x8b, 0xf1, - 0x80, 0xae, 0x94, 0xac, 0x87, 0xf0, 0x0f, 0x16, 0x13, 0xc0, 0x3c, 0x6a, 0xc8, 0x4f, 0xad, 0x09, - 0x70, 0x7f, 0x33, 0xe0, 0x2f, 0x10, 0xcc, 0xf3, 0x26, 0xb7, 0xf3, 0xcc, 0xac, 0xd2, 0x80, 0x51, - 0x9c, 0x1e, 0x73, 0xab, 0x36, 0x67, 0xa5, 0xd3, 0xdf, 0x10, 0x2c, 0x74, 0xc6, 0x22, 0x38, 0x6b, - 0x2d, 0xcc, 0xeb, 0x8d, 0x08, 0xaf, 0xf2, 0x23, 0x93, 0x15, 0xb6, 0x29, 0x23, 0xff, 0x9b, 0x89, - 0x4d, 0x88, 0x17, 0x8e, 0x13, 0x21, 0x8c, 0xee, 0x04, 0x06, 0x29, 0xaf, 0xc0, 0xd5, 0xf0, 0xe3, - 0xf6, 0xf7, 0x29, 0x7f, 0x8d, 0x60, 0xba, 0x49, 0x11, 0x21, 0xe6, 0x13, 0xe9, 0x7d, 0x38, 0xab, - 0x5b, 0x7b, 0x81, 0x42, 0x34, 0x1f, 0x66, 0x33, 0x9f, 0xc0, 0x98, 0xcf, 0x66, 0xec, 0x72, 0x88, - 0xe1, 0x28, 0x1d, 0x0d, 0x27, 0x58, 0xfa, 0x8a, 0x67, 0x3d, 0x81, 0x83, 0xb3, 0xbb, 0xc9, 0x4d, - 0xef, 0x6b, 0xe2, 0xb3, 0x3c, 0x31, 0xe7, 0x1b, 0x70, 0x49, 0x80, 0xcc, 0xb1, 0x5a, 0xae, 0x40, - 0x9c, 0x82, 0x6f, 0xd8, 0x17, 0xc5, 0xd1, 0xc3, 0xda, 0x06, 0x71, 0x0a, 0xf5, 0xf7, 0xf9, 0xd7, - 0x81, 0xb0, 0x4f, 0x86, 0xcf, 0x86, 0x63, 0xee, 0x8d, 0xf1, 0x02, 0xf1, 0xcc, 0xca, 0x5f, 0x87, - 0x93, 0x69, 0xc3, 0x64, 0x85, 0x8a, 0xae, 0xe4, 0xed, 0x3d, 0x55, 0x8c, 0x26, 0x5f, 0x20, 0xa6, - 0xd5, 0xf8, 0xa1, 0xb2, 0xfd, 0x12, 0x75, 0x94, 0xcc, 0xbd, 0xec, 0xf2, 0x9b, 0x4b, 0xd9, 0x8a, - 0xbe, 0x45, 0xf7, 0xb5, 0x73, 0x7a, 0xfd, 0x8a, 0xf1, 0xc7, 0x30, 0xea, 0x13, 0x41, 0xd1, 0x74, - 0xea, 0xc6, 0x37, 0xf0, 0x12, 0x75, 0xe3, 0x0d, 0xf5, 0xdc, 0x37, 0x1d, 0xee, 0x2d, 0x0e, 0x23, - 0x65, 0x96, 0x13, 0x4a, 0x1d, 0x70, 0xbd, 0x85, 0x3f, 0x73, 0xe5, 0x8c, 0x27, 0x00, 0xa8, 0xb5, - 0xd3, 0x08, 0x18, 0xe4, 0x01, 0x17, 0xa8, 0x25, 0xd4, 0x8e, 0xc7, 0xe1, 0x02, 0xb3, 0x19, 0x29, - 0xe6, 0x1c, 0xc2, 0x12, 0xe7, 0xf8, 0xe9, 0x79, 0xfe, 0xe0, 0x01, 0x61, 0x78, 0x06, 0x46, 0xfc, - 0xa3, 0xa5, 0xb5, 0x44, 0x8c, 0x4f, 0x35, 0xee, 0x4d, 0xd5, 0xf5, 0x74, 0xc2, 0xbd, 0x22, 0xf1, - 0xca, 0x14, 0x5a, 0x38, 0xaf, 0x89, 0x5f, 0xf8, 0x11, 0xbc, 0x5a, 0xb1, 0x3c, 0x79, 0xe5, 0x4c, - 0xeb, 0xb1, 0x9d, 0x38, 0xcf, 0x55, 0xd0, 0xe6, 0x43, 0xfc, 0x81, 0x2f, 0xe5, 0x9e, 0xf5, 0xd8, - 0xd6, 0x2e, 0x56, 0x4e, 0x3d, 0x49, 0x7f, 0x39, 0x0a, 0xe7, 0xf8, 0x15, 0xe2, 0xcf, 0x11, 0xc4, - 0xdc, 0xcd, 0x0e, 0x5f, 0x6f, 0x51, 0xb2, 0x79, 0x95, 0x94, 0x16, 0xa3, 0x84, 0xba, 0x7a, 0x90, - 0x67, 0x3f, 0xfb, 0xfd, 0x9f, 0x6f, 0xfa, 0x27, 0xf1, 0x84, 0xda, 0x6e, 0xc3, 0xc5, 0xdf, 0x22, - 0x18, 0x0e, 0x58, 0x25, 0x5e, 0x6a, 0xd7, 0x24, 0x6c, 0xe1, 0x94, 0x52, 0x5d, 0x64, 0x08, 0x74, - 0x37, 0x38, 0xba, 0x79, 0x3c, 0xab, 0xb6, 0xdc, 0xad, 0x7d, 0xe6, 0x8c, 0x7f, 0x41, 0x10, 0xf7, - 0x17, 0xc2, 0x6a, 0xd4, 0x96, 0x0d, 0x8c, 0x4b, 0xd1, 0x13, 0x04, 0xc4, 0x0d, 0x0e, 0x31, 0x83, - 0xdf, 0x89, 0x04, 0x51, 0x7d, 0x1a, 0xf4, 0xcc, 0x03, 0xf5, 0xe4, 0x0c, 0x7f, 0x87, 0x60, 0x24, - 0xb8, 0x3c, 0xe1, 0x4e, 0x23, 0x6b, 0xb6, 0x65, 0x29, 0xdd, 0x4d, 0x8a, 0xe0, 0xa0, 0x70, 0x0e, - 0x0b, 0x78, 0xae, 0x0d, 0x07, 0x9f, 0x8f, 0xe2, 0x3f, 0x10, 0x8c, 0xb7, 0xf9, 0x7c, 0xe2, 0x9b, - 0xed, 0x30, 0x74, 0xde, 0x01, 0xa4, 0x5b, 0x3d, 0xe7, 0x0b, 0x42, 0x2b, 0x9c, 0xd0, 0x12, 0x56, - 0x22, 0x5e, 0x8a, 0xeb, 0x1e, 0x07, 0xf8, 0x5f, 0x04, 0x63, 0x2d, 0x57, 0x34, 0xbc, 0x16, 0x55, - 0x1c, 0x61, 0xfb, 0xa3, 0xf4, 0x76, 0x8f, 0xd9, 0x82, 0xd2, 0x36, 0xa7, 0x74, 0x17, 0xbf, 0xdb, - 0xa3, 0xce, 0xf8, 0x72, 0xe6, 0x31, 0x7d, 0x81, 0x20, 0xd1, 0x6a, 0xe5, 0xc3, 0xab, 0x51, 0xa1, - 0x86, 0x6c, 0x9d, 0xd2, 0x5a, 0x6f, 0xc9, 0x82, 0xe6, 0x3a, 0xa7, 0x79, 0x13, 0xaf, 0xbd, 0x0c, - 0x4d, 0xfc, 0x03, 0x82, 0xd1, 0x53, 0x7b, 0x0f, 0x4e, 0x77, 0x14, 0x55, 0xd3, 0x0e, 0x25, 0x2d, - 0x77, 0x95, 0x23, 0x28, 0xa8, 0x9c, 0xc2, 0x75, 0x3c, 0xdf, 0x82, 0x02, 0x69, 0xe4, 0x89, 0x8f, - 0x16, 0x3e, 0x44, 0x70, 0xa5, 0xc5, 0x5e, 0x83, 0xdf, 0x8a, 0x3a, 0xcd, 0x10, 0x2b, 0x58, 0xed, - 0x29, 0x57, 0xb0, 0xd8, 0xe4, 0x2c, 0xd6, 0x71, 0xa6, 0xc7, 0x8b, 0xf0, 0xfb, 0xc5, 0xcf, 0xee, - 0xd7, 0xc3, 0x6b, 0xd3, 0xf1, 0xeb, 0xd1, 0xb4, 0x06, 0x49, 0xa9, 0x2e, 0x32, 0xba, 0xd0, 0x92, - 0x0f, 0xa6, 0xfa, 0x34, 0x64, 0xcf, 0x3a, 0xc8, 0xdc, 0x7f, 0x76, 0x94, 0x44, 0xcf, 0x8f, 0x92, - 0xe8, 0xef, 0xa3, 0x24, 0xfa, 0xea, 0x38, 0xd9, 0xf7, 0xfc, 0x38, 0xd9, 0xf7, 0xe7, 0x71, 0xb2, - 0xef, 0xa3, 0x8e, 0xfb, 0x4d, 0xcd, 0xdf, 0x90, 0x2f, 0x3b, 0x7a, 0x8c, 0xff, 0x0f, 0x68, 0xf9, - 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x99, 0x87, 0xd2, 0xeb, 0x12, 0x00, 0x00, + // 1262 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0x45, + 0x1c, 0xce, 0xe4, 0x61, 0x92, 0x5f, 0xd2, 0x3c, 0xa6, 0x05, 0x5c, 0x27, 0x76, 0x92, 0x55, 0xc9, + 0xab, 0xb0, 0x5b, 0x3b, 0xa1, 0x07, 0x40, 0x4d, 0xe3, 0x94, 0x26, 0x7d, 0x44, 0x98, 0x4d, 0xa0, + 0x12, 0x48, 0x58, 0x63, 0x67, 0xbc, 0x5e, 0xe2, 0xec, 0xba, 0xde, 0xb1, 0x71, 0x54, 0xe5, 0xc2, + 0x81, 0x1b, 0x12, 0x12, 0xfc, 0x0f, 0x20, 0x71, 0x44, 0x5c, 0x38, 0x20, 0x71, 0xeb, 0xb1, 0x82, + 0x03, 0x88, 0x43, 0x41, 0x09, 0xe2, 0x80, 0x84, 0xb8, 0x71, 0x46, 0x9e, 0x1d, 0xe3, 0xb5, 0x77, + 0xd7, 0xd9, 0x4d, 0xd2, 0x5b, 0xbd, 0xf3, 0x7b, 0x7d, 0xdf, 0x7c, 0xfb, 0xed, 0xaf, 0x81, 0xd9, + 0x1c, 0xc9, 0x1d, 0x94, 0x4c, 0x43, 0xc9, 0xb1, 0xbc, 0xc5, 0xc8, 0x9e, 0x6e, 0x68, 0x4a, 0x2d, + 0xa9, 0x3c, 0xac, 0xd2, 0xca, 0x81, 0x5c, 0xae, 0x98, 0xcc, 0xc4, 0xcf, 0x8b, 0x10, 0xb9, 0x15, + 0x22, 0xd7, 0x92, 0xb1, 0x4b, 0x9a, 0xa9, 0x99, 0x3c, 0x42, 0x69, 0xfc, 0xcb, 0x0e, 0x8e, 0x4d, + 0x69, 0xa6, 0xa9, 0x95, 0xa8, 0x42, 0xca, 0xba, 0x42, 0x0c, 0xc3, 0x64, 0x84, 0xe9, 0xa6, 0x61, + 0x89, 0xd3, 0xa5, 0xbc, 0x69, 0xed, 0x9b, 0x96, 0x92, 0x23, 0x16, 0xb5, 0x7b, 0x28, 0xb5, 0x64, + 0x8e, 0x32, 0x92, 0x54, 0xca, 0x44, 0xd3, 0x0d, 0x1e, 0x2c, 0x62, 0x25, 0xef, 0xc9, 0xca, 0xa4, + 0x42, 0xf6, 0x9b, 0xf5, 0xe6, 0xbc, 0x63, 0x1c, 0x83, 0xf2, 0x38, 0xe9, 0x12, 0xe0, 0xb7, 0x1b, + 0xdd, 0x32, 0x3c, 0x59, 0xa5, 0x0f, 0xab, 0xd4, 0x62, 0x92, 0x0a, 0x17, 0xdb, 0x9e, 0x5a, 0x65, + 0xd3, 0xb0, 0x28, 0x7e, 0x1d, 0x22, 0x76, 0x93, 0x28, 0x9a, 0x41, 0x0b, 0xc3, 0xa9, 0xb8, 0xec, + 0x49, 0x80, 0x6c, 0xa7, 0xa5, 0xfb, 0x1f, 0x3f, 0x9d, 0xee, 0x51, 0x45, 0x8a, 0xa4, 0x41, 0x9c, + 0xd7, 0xbc, 0xad, 0x1b, 0xa4, 0xa4, 0xb3, 0x83, 0x4c, 0xc5, 0xac, 0xe9, 0xbb, 0xb4, 0xd2, 0x6c, + 0x8a, 0x6f, 0x03, 0xb4, 0xa0, 0x8a, 0x0e, 0x73, 0xb2, 0xcd, 0x8b, 0xdc, 0xe0, 0x45, 0xb6, 0xb9, + 0x17, 0xbc, 0xc8, 0x19, 0xa2, 0x51, 0x91, 0xab, 0x3a, 0x32, 0xa5, 0x1f, 0x10, 0x24, 0xfc, 0x3a, + 0x09, 0x20, 0xef, 0x02, 0x2e, 0x88, 0xc3, 0x6c, 0xb9, 0x79, 0x1a, 0x45, 0x33, 0x7d, 0x0b, 0xc3, + 0xa9, 0x79, 0x1f, 0x50, 0x9d, 0xd5, 0xd4, 0x89, 0x42, 0x67, 0x7d, 0xbc, 0xd1, 0x06, 0xa1, 0x97, + 0x43, 0x98, 0x3f, 0x11, 0x82, 0x3d, 0x54, 0x1b, 0x86, 0x35, 0x98, 0xf2, 0x84, 0xd0, 0xe4, 0x6a, + 0x16, 0x2e, 0x14, 0xca, 0xd9, 0x1c, 0xcb, 0x67, 0xcb, 0x7b, 0xd9, 0x22, 0xad, 0x73, 0xba, 0x86, + 0x54, 0x28, 0x94, 0xd3, 0x2c, 0x9f, 0xd9, 0xdb, 0xa4, 0x75, 0xa9, 0xea, 0xc3, 0xf7, 0xff, 0x24, + 0xec, 0xc0, 0x84, 0x8b, 0x04, 0x41, 0x7b, 0x60, 0x0e, 0xc6, 0x3b, 0x39, 0x90, 0xbe, 0x42, 0x10, + 0xe3, 0x7d, 0xd3, 0x3b, 0xeb, 0xb7, 0x68, 0x89, 0x6a, 0xb6, 0xcc, 0x9b, 0x83, 0xa7, 0x21, 0x62, + 0x31, 0xc2, 0xaa, 0xb6, 0x84, 0x46, 0x53, 0x4b, 0x3e, 0x9d, 0xda, 0xb2, 0xb7, 0x79, 0x86, 0x2a, + 0x32, 0x3b, 0x84, 0xd2, 0x7b, 0x6a, 0xa1, 0x7c, 0x8b, 0x60, 0xd2, 0x73, 0x54, 0x41, 0xd0, 0x16, + 0x8c, 0x35, 0x18, 0xde, 0x6d, 0x1d, 0x09, 0x89, 0x5c, 0x09, 0x32, 0xb4, 0x3a, 0x9a, 0x63, 0x79, + 0x47, 0xd9, 0xf3, 0x13, 0x47, 0x01, 0x16, 0x3d, 0x6f, 0x36, 0x63, 0x7e, 0x44, 0x2b, 0x6b, 0x6c, + 0x93, 0xea, 0x5a, 0x91, 0x05, 0x57, 0x0a, 0x7e, 0x01, 0x22, 0x45, 0x9e, 0xc3, 0x87, 0xea, 0x57, + 0xc5, 0x2f, 0xe9, 0x2d, 0x58, 0x0a, 0xd2, 0x47, 0xb0, 0x35, 0x0b, 0x23, 0x35, 0x93, 0xe9, 0x86, + 0x96, 0x2d, 0x37, 0xce, 0x79, 0x9f, 0x7e, 0x75, 0xd8, 0x7e, 0xc6, 0x53, 0xa4, 0x2d, 0x58, 0xf0, + 0x2c, 0xb8, 0x5e, 0xad, 0x54, 0xa8, 0xc1, 0x78, 0x50, 0x08, 0x85, 0xfb, 0xf1, 0xd0, 0x5e, 0x4e, + 0x8c, 0xd7, 0x02, 0x89, 0x9c, 0x20, 0x5d, 0x63, 0xf7, 0xba, 0xc7, 0xfe, 0x14, 0xc1, 0x55, 0xde, + 0x68, 0x2d, 0xcf, 0xf4, 0x1a, 0x75, 0xd9, 0x4a, 0x27, 0xe5, 0x7e, 0xad, 0xce, 0x4b, 0xb7, 0x3f, + 0x23, 0x78, 0x39, 0xd8, 0x3c, 0x02, 0xfb, 0x07, 0x5d, 0xec, 0x4e, 0x09, 0xf8, 0xaa, 0x3f, 0xd0, + 0x59, 0x71, 0x8b, 0x32, 0xf2, 0x4c, 0x6d, 0x2f, 0x2e, 0x5e, 0x48, 0x0e, 0x8c, 0x30, 0xba, 0xdb, + 0x46, 0xac, 0x74, 0x5d, 0xb8, 0xa2, 0xeb, 0xb8, 0xfb, 0x1d, 0x4b, 0x5f, 0x20, 0x98, 0xf7, 0x54, + 0x8a, 0x87, 0x41, 0x05, 0x78, 0x5f, 0xce, 0xeb, 0x1e, 0x7f, 0x43, 0x3e, 0xef, 0x83, 0x97, 0x19, + 0x7d, 0x08, 0x97, 0x1d, 0x66, 0x64, 0x56, 0x3c, 0x6c, 0x49, 0x3e, 0xd1, 0x96, 0xcc, 0xb6, 0xd2, + 0x2f, 0xb6, 0x0c, 0xaa, 0xed, 0xe0, 0xfc, 0xee, 0xf3, 0x2e, 0x5c, 0x76, 0x1b, 0x6c, 0x93, 0xe9, + 0x57, 0xe0, 0xa2, 0x18, 0x32, 0xcb, 0xea, 0xd9, 0x22, 0xb1, 0x8a, 0x0e, 0xbe, 0xc7, 0xc5, 0xd1, + 0x4e, 0x7d, 0x93, 0x58, 0xc5, 0xc6, 0xdb, 0xfe, 0x7d, 0x9f, 0xd7, 0x87, 0xc5, 0x61, 0xd6, 0x11, + 0xfb, 0xd2, 0x78, 0x81, 0x91, 0xf4, 0xf5, 0x5f, 0x9f, 0x4e, 0xa7, 0x34, 0x9d, 0x15, 0xab, 0x39, + 0x39, 0x6f, 0xee, 0x2b, 0x82, 0x9a, 0x7c, 0x91, 0xe8, 0x46, 0xf3, 0x87, 0xc2, 0x0e, 0xca, 0xd4, + 0x92, 0xd3, 0x77, 0x32, 0xcb, 0x2b, 0xd7, 0x32, 0xd5, 0xdc, 0x3d, 0x7a, 0xa0, 0x0e, 0xe4, 0x1a, + 0xd7, 0x8c, 0xdf, 0x87, 0xd1, 0x96, 0x0c, 0x4a, 0xba, 0xd5, 0xf0, 0xc6, 0xbe, 0x33, 0x94, 0x1d, + 0x16, 0xfa, 0xb9, 0xaf, 0x73, 0x8d, 0x8d, 0x58, 0x8c, 0x54, 0x58, 0x56, 0xa8, 0xb5, 0xcf, 0xf6, + 0x1c, 0xfe, 0xcc, 0x96, 0x34, 0x8e, 0x03, 0x50, 0x63, 0xb7, 0x19, 0xd0, 0xcf, 0x03, 0x86, 0xa8, + 0x21, 0x14, 0x8f, 0x27, 0x61, 0x88, 0x99, 0x8c, 0x94, 0xb2, 0x16, 0x61, 0xd1, 0x01, 0x7e, 0x3a, + 0xc8, 0x1f, 0x6c, 0x13, 0x86, 0xaf, 0xc0, 0xa8, 0x93, 0x58, 0x5a, 0x8f, 0x46, 0x38, 0xa7, 0x23, + 0x2d, 0x4e, 0x6d, 0xd7, 0x27, 0xdc, 0x3f, 0xa2, 0xcf, 0xcd, 0xa0, 0x85, 0x41, 0x55, 0xfc, 0xc2, + 0x0f, 0x60, 0xa2, 0x6a, 0xb4, 0xc4, 0x95, 0xd5, 0x8d, 0x82, 0x19, 0x1d, 0xe4, 0x1a, 0xe8, 0xf2, + 0xb1, 0x7e, 0xc7, 0x91, 0x72, 0xc7, 0x28, 0x98, 0xea, 0x78, 0xb5, 0xe3, 0x49, 0xea, 0x9f, 0x31, + 0x18, 0xe0, 0x17, 0x88, 0x3f, 0x41, 0x10, 0xb1, 0x77, 0x44, 0xbc, 0xe8, 0x53, 0xd2, 0xbd, 0x94, + 0xc6, 0x96, 0x82, 0x84, 0xda, 0x6a, 0x90, 0x5e, 0xfa, 0xf8, 0xa7, 0x3f, 0x3e, 0xef, 0x9d, 0xc6, + 0x71, 0xa5, 0xdb, 0xae, 0x8c, 0xbf, 0x41, 0x30, 0xe1, 0xb2, 0x4f, 0xbc, 0xd2, 0xad, 0x91, 0xdf, + 0xfa, 0x1a, 0x7b, 0x35, 0x64, 0x96, 0x98, 0x34, 0xc9, 0x27, 0xbd, 0x8a, 0x17, 0x7d, 0x26, 0x75, + 0x1b, 0x37, 0xfe, 0x11, 0xc1, 0x78, 0x67, 0x41, 0xbc, 0x1c, 0xa6, 0x7d, 0x73, 0xe6, 0x95, 0x70, + 0x49, 0x62, 0xe4, 0x6d, 0x3e, 0xf2, 0x16, 0xbe, 0x17, 0x78, 0x64, 0xe5, 0x51, 0x9b, 0xa7, 0x1e, + 0xba, 0x43, 0xf0, 0x97, 0x08, 0x46, 0xdb, 0xf7, 0x30, 0x9c, 0xec, 0x36, 0x9d, 0xe7, 0x7a, 0x19, + 0x4b, 0x85, 0x49, 0x11, 0x70, 0x64, 0x0e, 0x67, 0x01, 0xcf, 0x29, 0xbe, 0xff, 0x67, 0x72, 0x9a, + 0x2d, 0xfe, 0x13, 0xc1, 0xf4, 0x09, 0x5f, 0x5e, 0x9c, 0xee, 0x36, 0x47, 0xb0, 0x35, 0x22, 0xb6, + 0x7e, 0xa6, 0x1a, 0x02, 0xdc, 0x6b, 0x1c, 0xdc, 0x0a, 0x4e, 0x85, 0xb8, 0x2b, 0xdb, 0x74, 0x0e, + 0xf1, 0xbf, 0x08, 0xe2, 0x5d, 0x77, 0x3f, 0x7c, 0x33, 0x8c, 0x7e, 0xbc, 0xd6, 0xd3, 0xd8, 0xda, + 0x19, 0x2a, 0x08, 0x88, 0x19, 0x0e, 0xf1, 0x2e, 0xde, 0x3c, 0xbd, 0x1c, 0xf9, 0xea, 0xd7, 0x02, + 0xfe, 0x17, 0x82, 0xa9, 0x6e, 0x4b, 0x25, 0x5e, 0x0d, 0x33, 0xb5, 0xc7, 0x76, 0x1b, 0xbb, 0x79, + 0xfa, 0x02, 0x02, 0xf5, 0x06, 0x47, 0xbd, 0x86, 0x57, 0xcf, 0x88, 0x1a, 0x7f, 0x8d, 0x60, 0xac, + 0x63, 0xa1, 0xc2, 0xa9, 0x13, 0xa5, 0xe7, 0x5a, 0xce, 0x62, 0xcb, 0xa1, 0x72, 0x04, 0x0a, 0x85, + 0xa3, 0x58, 0xc4, 0xf3, 0x3e, 0x28, 0x48, 0x33, 0x4f, 0x7c, 0x09, 0xf1, 0xdf, 0x08, 0x26, 0xbb, + 0xac, 0x4b, 0xf8, 0x46, 0x18, 0x62, 0x3d, 0x0c, 0x64, 0xf5, 0xd4, 0xf9, 0x02, 0xd1, 0x16, 0x47, + 0xb4, 0x81, 0xdf, 0x3c, 0xfd, 0xbd, 0x38, 0xcd, 0xe6, 0x3b, 0x04, 0x17, 0xda, 0x7c, 0x0b, 0x5f, + 0x0b, 0x6c, 0x71, 0x4d, 0x4c, 0xc9, 0x10, 0x19, 0x02, 0xc5, 0x2d, 0x8e, 0xe2, 0x06, 0x7e, 0x23, + 0x98, 0x27, 0x2a, 0x8f, 0x3c, 0x36, 0xb9, 0xc3, 0xf4, 0xfd, 0xc7, 0x47, 0x09, 0xf4, 0xe4, 0x28, + 0x81, 0x7e, 0x3f, 0x4a, 0xa0, 0xcf, 0x8e, 0x13, 0x3d, 0x4f, 0x8e, 0x13, 0x3d, 0xbf, 0x1c, 0x27, + 0x7a, 0xde, 0x3b, 0x71, 0x85, 0xaa, 0x3b, 0x1b, 0xf2, 0x7d, 0x2a, 0x17, 0xe1, 0x7f, 0xb1, 0x5a, + 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xe1, 0xf1, 0x97, 0x99, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1215,23 +1236,23 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // BTCValidators queries all BTC validators - BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) - // BTCValidator info about one validator - BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) + // FinalityProviders queries all finality providers + FinalityProviders(ctx context.Context, in *QueryFinalityProvidersRequest, opts ...grpc.CallOption) (*QueryFinalityProvidersResponse, error) + // FinalityProvider info about one finality provider + FinalityProvider(ctx context.Context, in *QueryFinalityProviderRequest, opts ...grpc.CallOption) (*QueryFinalityProviderResponse, error) // BTCDelegations queries all BTC delegations under a given status BTCDelegations(ctx context.Context, in *QueryBTCDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCDelegationsResponse, error) - // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. - ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height - BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) - // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height - BTCValidatorCurrentPower(ctx context.Context, in *QueryBTCValidatorCurrentPowerRequest, opts ...grpc.CallOption) (*QueryBTCValidatorCurrentPowerResponse, error) + // ActiveFinalityProvidersAtHeight queries finality providers with non zero voting power at given height. + ActiveFinalityProvidersAtHeight(ctx context.Context, in *QueryActiveFinalityProvidersAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveFinalityProvidersAtHeightResponse, error) + // FinalityProviderPowerAtHeight queries the voting power of a finality provider at a given height + FinalityProviderPowerAtHeight(ctx context.Context, in *QueryFinalityProviderPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryFinalityProviderPowerAtHeightResponse, error) + // FinalityProviderCurrentPower queries the voting power of a finality provider at the current height + FinalityProviderCurrentPower(ctx context.Context, in *QueryFinalityProviderCurrentPowerRequest, opts ...grpc.CallOption) (*QueryFinalityProviderCurrentPowerResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when - // there exists 1 BTC validator with voting power + // there exists 1 finality provider with voting power ActivatedHeight(ctx context.Context, in *QueryActivatedHeightRequest, opts ...grpc.CallOption) (*QueryActivatedHeightResponse, error) - // BTCValidatorDelegations queries all BTC delegations of the given BTC validator - BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) + // FinalityProviderDelegations queries all BTC delegations of the given finality provider + FinalityProviderDelegations(ctx context.Context, in *QueryFinalityProviderDelegationsRequest, opts ...grpc.CallOption) (*QueryFinalityProviderDelegationsResponse, error) // BTCDelegation retrieves delegation by corresponding staking tx hash BTCDelegation(ctx context.Context, in *QueryBTCDelegationRequest, opts ...grpc.CallOption) (*QueryBTCDelegationResponse, error) } @@ -1253,18 +1274,18 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } -func (c *queryClient) BTCValidators(ctx context.Context, in *QueryBTCValidatorsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorsResponse, error) { - out := new(QueryBTCValidatorsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidators", in, out, opts...) +func (c *queryClient) FinalityProviders(ctx context.Context, in *QueryFinalityProvidersRequest, opts ...grpc.CallOption) (*QueryFinalityProvidersResponse, error) { + out := new(QueryFinalityProvidersResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/FinalityProviders", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *queryClient) BTCValidator(ctx context.Context, in *QueryBTCValidatorRequest, opts ...grpc.CallOption) (*QueryBTCValidatorResponse, error) { - out := new(QueryBTCValidatorResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidator", in, out, opts...) +func (c *queryClient) FinalityProvider(ctx context.Context, in *QueryFinalityProviderRequest, opts ...grpc.CallOption) (*QueryFinalityProviderResponse, error) { + out := new(QueryFinalityProviderResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/FinalityProvider", in, out, opts...) if err != nil { return nil, err } @@ -1280,27 +1301,27 @@ func (c *queryClient) BTCDelegations(ctx context.Context, in *QueryBTCDelegation return out, nil } -func (c *queryClient) ActiveBTCValidatorsAtHeight(ctx context.Context, in *QueryActiveBTCValidatorsAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveBTCValidatorsAtHeightResponse, error) { - out := new(QueryActiveBTCValidatorsAtHeightResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", in, out, opts...) +func (c *queryClient) ActiveFinalityProvidersAtHeight(ctx context.Context, in *QueryActiveFinalityProvidersAtHeightRequest, opts ...grpc.CallOption) (*QueryActiveFinalityProvidersAtHeightResponse, error) { + out := new(QueryActiveFinalityProvidersAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/ActiveFinalityProvidersAtHeight", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *queryClient) BTCValidatorPowerAtHeight(ctx context.Context, in *QueryBTCValidatorPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryBTCValidatorPowerAtHeightResponse, error) { - out := new(QueryBTCValidatorPowerAtHeightResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorPowerAtHeight", in, out, opts...) +func (c *queryClient) FinalityProviderPowerAtHeight(ctx context.Context, in *QueryFinalityProviderPowerAtHeightRequest, opts ...grpc.CallOption) (*QueryFinalityProviderPowerAtHeightResponse, error) { + out := new(QueryFinalityProviderPowerAtHeightResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/FinalityProviderPowerAtHeight", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *queryClient) BTCValidatorCurrentPower(ctx context.Context, in *QueryBTCValidatorCurrentPowerRequest, opts ...grpc.CallOption) (*QueryBTCValidatorCurrentPowerResponse, error) { - out := new(QueryBTCValidatorCurrentPowerResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorCurrentPower", in, out, opts...) +func (c *queryClient) FinalityProviderCurrentPower(ctx context.Context, in *QueryFinalityProviderCurrentPowerRequest, opts ...grpc.CallOption) (*QueryFinalityProviderCurrentPowerResponse, error) { + out := new(QueryFinalityProviderCurrentPowerResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/FinalityProviderCurrentPower", in, out, opts...) if err != nil { return nil, err } @@ -1316,9 +1337,9 @@ func (c *queryClient) ActivatedHeight(ctx context.Context, in *QueryActivatedHei return out, nil } -func (c *queryClient) BTCValidatorDelegations(ctx context.Context, in *QueryBTCValidatorDelegationsRequest, opts ...grpc.CallOption) (*QueryBTCValidatorDelegationsResponse, error) { - out := new(QueryBTCValidatorDelegationsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/BTCValidatorDelegations", in, out, opts...) +func (c *queryClient) FinalityProviderDelegations(ctx context.Context, in *QueryFinalityProviderDelegationsRequest, opts ...grpc.CallOption) (*QueryFinalityProviderDelegationsResponse, error) { + out := new(QueryFinalityProviderDelegationsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Query/FinalityProviderDelegations", in, out, opts...) if err != nil { return nil, err } @@ -1338,23 +1359,23 @@ func (c *queryClient) BTCDelegation(ctx context.Context, in *QueryBTCDelegationR type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // BTCValidators queries all BTC validators - BTCValidators(context.Context, *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) - // BTCValidator info about one validator - BTCValidator(context.Context, *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) + // FinalityProviders queries all finality providers + FinalityProviders(context.Context, *QueryFinalityProvidersRequest) (*QueryFinalityProvidersResponse, error) + // FinalityProvider info about one finality provider + FinalityProvider(context.Context, *QueryFinalityProviderRequest) (*QueryFinalityProviderResponse, error) // BTCDelegations queries all BTC delegations under a given status BTCDelegations(context.Context, *QueryBTCDelegationsRequest) (*QueryBTCDelegationsResponse, error) - // ActiveBTCValidatorsAtHeight queries BTC validators with non zero voting power at given height. - ActiveBTCValidatorsAtHeight(context.Context, *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) - // BTCValidatorPowerAtHeight queries the voting power of a BTC validator at a given height - BTCValidatorPowerAtHeight(context.Context, *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) - // BTCValidatorCurrentPower queries the voting power of a BTC validator at the current height - BTCValidatorCurrentPower(context.Context, *QueryBTCValidatorCurrentPowerRequest) (*QueryBTCValidatorCurrentPowerResponse, error) + // ActiveFinalityProvidersAtHeight queries finality providers with non zero voting power at given height. + ActiveFinalityProvidersAtHeight(context.Context, *QueryActiveFinalityProvidersAtHeightRequest) (*QueryActiveFinalityProvidersAtHeightResponse, error) + // FinalityProviderPowerAtHeight queries the voting power of a finality provider at a given height + FinalityProviderPowerAtHeight(context.Context, *QueryFinalityProviderPowerAtHeightRequest) (*QueryFinalityProviderPowerAtHeightResponse, error) + // FinalityProviderCurrentPower queries the voting power of a finality provider at the current height + FinalityProviderCurrentPower(context.Context, *QueryFinalityProviderCurrentPowerRequest) (*QueryFinalityProviderCurrentPowerResponse, error) // ActivatedHeight queries the height when BTC staking protocol is activated, i.e., the first height when - // there exists 1 BTC validator with voting power + // there exists 1 finality provider with voting power ActivatedHeight(context.Context, *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) - // BTCValidatorDelegations queries all BTC delegations of the given BTC validator - BTCValidatorDelegations(context.Context, *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) + // FinalityProviderDelegations queries all BTC delegations of the given finality provider + FinalityProviderDelegations(context.Context, *QueryFinalityProviderDelegationsRequest) (*QueryFinalityProviderDelegationsResponse, error) // BTCDelegation retrieves delegation by corresponding staking tx hash BTCDelegation(context.Context, *QueryBTCDelegationRequest) (*QueryBTCDelegationResponse, error) } @@ -1366,29 +1387,29 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } -func (*UnimplementedQueryServer) BTCValidators(ctx context.Context, req *QueryBTCValidatorsRequest) (*QueryBTCValidatorsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidators not implemented") +func (*UnimplementedQueryServer) FinalityProviders(ctx context.Context, req *QueryFinalityProvidersRequest) (*QueryFinalityProvidersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalityProviders not implemented") } -func (*UnimplementedQueryServer) BTCValidator(ctx context.Context, req *QueryBTCValidatorRequest) (*QueryBTCValidatorResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidator not implemented") +func (*UnimplementedQueryServer) FinalityProvider(ctx context.Context, req *QueryFinalityProviderRequest) (*QueryFinalityProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalityProvider not implemented") } func (*UnimplementedQueryServer) BTCDelegations(ctx context.Context, req *QueryBTCDelegationsRequest) (*QueryBTCDelegationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCDelegations not implemented") } -func (*UnimplementedQueryServer) ActiveBTCValidatorsAtHeight(ctx context.Context, req *QueryActiveBTCValidatorsAtHeightRequest) (*QueryActiveBTCValidatorsAtHeightResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ActiveBTCValidatorsAtHeight not implemented") +func (*UnimplementedQueryServer) ActiveFinalityProvidersAtHeight(ctx context.Context, req *QueryActiveFinalityProvidersAtHeightRequest) (*QueryActiveFinalityProvidersAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ActiveFinalityProvidersAtHeight not implemented") } -func (*UnimplementedQueryServer) BTCValidatorPowerAtHeight(ctx context.Context, req *QueryBTCValidatorPowerAtHeightRequest) (*QueryBTCValidatorPowerAtHeightResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorPowerAtHeight not implemented") +func (*UnimplementedQueryServer) FinalityProviderPowerAtHeight(ctx context.Context, req *QueryFinalityProviderPowerAtHeightRequest) (*QueryFinalityProviderPowerAtHeightResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalityProviderPowerAtHeight not implemented") } -func (*UnimplementedQueryServer) BTCValidatorCurrentPower(ctx context.Context, req *QueryBTCValidatorCurrentPowerRequest) (*QueryBTCValidatorCurrentPowerResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorCurrentPower not implemented") +func (*UnimplementedQueryServer) FinalityProviderCurrentPower(ctx context.Context, req *QueryFinalityProviderCurrentPowerRequest) (*QueryFinalityProviderCurrentPowerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalityProviderCurrentPower not implemented") } func (*UnimplementedQueryServer) ActivatedHeight(ctx context.Context, req *QueryActivatedHeightRequest) (*QueryActivatedHeightResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ActivatedHeight not implemented") } -func (*UnimplementedQueryServer) BTCValidatorDelegations(ctx context.Context, req *QueryBTCValidatorDelegationsRequest) (*QueryBTCValidatorDelegationsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method BTCValidatorDelegations not implemented") +func (*UnimplementedQueryServer) FinalityProviderDelegations(ctx context.Context, req *QueryFinalityProviderDelegationsRequest) (*QueryFinalityProviderDelegationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalityProviderDelegations not implemented") } func (*UnimplementedQueryServer) BTCDelegation(ctx context.Context, req *QueryBTCDelegationRequest) (*QueryBTCDelegationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCDelegation not implemented") @@ -1416,38 +1437,38 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } -func _Query_BTCValidators_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorsRequest) +func _Query_FinalityProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFinalityProvidersRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidators(ctx, in) + return srv.(QueryServer).FinalityProviders(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidators", + FullMethod: "/babylon.btcstaking.v1.Query/FinalityProviders", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidators(ctx, req.(*QueryBTCValidatorsRequest)) + return srv.(QueryServer).FinalityProviders(ctx, req.(*QueryFinalityProvidersRequest)) } return interceptor(ctx, in, info, handler) } -func _Query_BTCValidator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorRequest) +func _Query_FinalityProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFinalityProviderRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidator(ctx, in) + return srv.(QueryServer).FinalityProvider(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidator", + FullMethod: "/babylon.btcstaking.v1.Query/FinalityProvider", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidator(ctx, req.(*QueryBTCValidatorRequest)) + return srv.(QueryServer).FinalityProvider(ctx, req.(*QueryFinalityProviderRequest)) } return interceptor(ctx, in, info, handler) } @@ -1470,56 +1491,56 @@ func _Query_BTCDelegations_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -func _Query_ActiveBTCValidatorsAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryActiveBTCValidatorsAtHeightRequest) +func _Query_ActiveFinalityProvidersAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryActiveFinalityProvidersAtHeightRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).ActiveBTCValidatorsAtHeight(ctx, in) + return srv.(QueryServer).ActiveFinalityProvidersAtHeight(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/ActiveBTCValidatorsAtHeight", + FullMethod: "/babylon.btcstaking.v1.Query/ActiveFinalityProvidersAtHeight", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).ActiveBTCValidatorsAtHeight(ctx, req.(*QueryActiveBTCValidatorsAtHeightRequest)) + return srv.(QueryServer).ActiveFinalityProvidersAtHeight(ctx, req.(*QueryActiveFinalityProvidersAtHeightRequest)) } return interceptor(ctx, in, info, handler) } -func _Query_BTCValidatorPowerAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorPowerAtHeightRequest) +func _Query_FinalityProviderPowerAtHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFinalityProviderPowerAtHeightRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidatorPowerAtHeight(ctx, in) + return srv.(QueryServer).FinalityProviderPowerAtHeight(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorPowerAtHeight", + FullMethod: "/babylon.btcstaking.v1.Query/FinalityProviderPowerAtHeight", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidatorPowerAtHeight(ctx, req.(*QueryBTCValidatorPowerAtHeightRequest)) + return srv.(QueryServer).FinalityProviderPowerAtHeight(ctx, req.(*QueryFinalityProviderPowerAtHeightRequest)) } return interceptor(ctx, in, info, handler) } -func _Query_BTCValidatorCurrentPower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorCurrentPowerRequest) +func _Query_FinalityProviderCurrentPower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFinalityProviderCurrentPowerRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidatorCurrentPower(ctx, in) + return srv.(QueryServer).FinalityProviderCurrentPower(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorCurrentPower", + FullMethod: "/babylon.btcstaking.v1.Query/FinalityProviderCurrentPower", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidatorCurrentPower(ctx, req.(*QueryBTCValidatorCurrentPowerRequest)) + return srv.(QueryServer).FinalityProviderCurrentPower(ctx, req.(*QueryFinalityProviderCurrentPowerRequest)) } return interceptor(ctx, in, info, handler) } @@ -1542,20 +1563,20 @@ func _Query_ActivatedHeight_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } -func _Query_BTCValidatorDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryBTCValidatorDelegationsRequest) +func _Query_FinalityProviderDelegations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFinalityProviderDelegationsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(QueryServer).BTCValidatorDelegations(ctx, in) + return srv.(QueryServer).FinalityProviderDelegations(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Query/BTCValidatorDelegations", + FullMethod: "/babylon.btcstaking.v1.Query/FinalityProviderDelegations", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).BTCValidatorDelegations(ctx, req.(*QueryBTCValidatorDelegationsRequest)) + return srv.(QueryServer).FinalityProviderDelegations(ctx, req.(*QueryFinalityProviderDelegationsRequest)) } return interceptor(ctx, in, info, handler) } @@ -1587,36 +1608,36 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Handler: _Query_Params_Handler, }, { - MethodName: "BTCValidators", - Handler: _Query_BTCValidators_Handler, + MethodName: "FinalityProviders", + Handler: _Query_FinalityProviders_Handler, }, { - MethodName: "BTCValidator", - Handler: _Query_BTCValidator_Handler, + MethodName: "FinalityProvider", + Handler: _Query_FinalityProvider_Handler, }, { MethodName: "BTCDelegations", Handler: _Query_BTCDelegations_Handler, }, { - MethodName: "ActiveBTCValidatorsAtHeight", - Handler: _Query_ActiveBTCValidatorsAtHeight_Handler, + MethodName: "ActiveFinalityProvidersAtHeight", + Handler: _Query_ActiveFinalityProvidersAtHeight_Handler, }, { - MethodName: "BTCValidatorPowerAtHeight", - Handler: _Query_BTCValidatorPowerAtHeight_Handler, + MethodName: "FinalityProviderPowerAtHeight", + Handler: _Query_FinalityProviderPowerAtHeight_Handler, }, { - MethodName: "BTCValidatorCurrentPower", - Handler: _Query_BTCValidatorCurrentPower_Handler, + MethodName: "FinalityProviderCurrentPower", + Handler: _Query_FinalityProviderCurrentPower_Handler, }, { MethodName: "ActivatedHeight", Handler: _Query_ActivatedHeight_Handler, }, { - MethodName: "BTCValidatorDelegations", - Handler: _Query_BTCValidatorDelegations_Handler, + MethodName: "FinalityProviderDelegations", + Handler: _Query_FinalityProviderDelegations_Handler, }, { MethodName: "BTCDelegation", @@ -1683,7 +1704,7 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryBTCValidatorsRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProvidersRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1693,12 +1714,12 @@ func (m *QueryBTCValidatorsRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryBTCValidatorsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProvidersRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProvidersRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1718,7 +1739,7 @@ func (m *QueryBTCValidatorsRequest) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } -func (m *QueryBTCValidatorsResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProvidersResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1728,12 +1749,12 @@ func (m *QueryBTCValidatorsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryBTCValidatorsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProvidersResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProvidersResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1750,10 +1771,10 @@ func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x12 } - if len(m.BtcValidators) > 0 { - for iNdEx := len(m.BtcValidators) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FinalityProviders) > 0 { + for iNdEx := len(m.FinalityProviders) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.BtcValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.FinalityProviders[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1767,7 +1788,7 @@ func (m *QueryBTCValidatorsResponse) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } -func (m *QueryBTCValidatorRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1777,27 +1798,27 @@ func (m *QueryBTCValidatorRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryBTCValidatorRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1807,19 +1828,19 @@ func (m *QueryBTCValidatorResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *QueryBTCValidatorResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.BtcValidator != nil { + if m.FinalityProvider != nil { { - size, err := m.BtcValidator.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.FinalityProvider.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1921,7 +1942,7 @@ func (m *QueryBTCDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } -func (m *QueryBTCValidatorPowerAtHeightRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderPowerAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1931,12 +1952,12 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Marshal() (dAtA []byte, err erro return dAtA[:n], nil } -func (m *QueryBTCValidatorPowerAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderPowerAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorPowerAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderPowerAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1946,17 +1967,17 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) MarshalToSizedBuffer(dAtA []byte i-- dAtA[i] = 0x10 } - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorPowerAtHeightResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderPowerAtHeightResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1966,12 +1987,12 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Marshal() (dAtA []byte, err err return dAtA[:n], nil } -func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderPowerAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1984,7 +2005,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } -func (m *QueryBTCValidatorCurrentPowerRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderCurrentPowerRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1994,27 +2015,27 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *QueryBTCValidatorCurrentPowerRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderCurrentPowerRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorCurrentPowerRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderCurrentPowerRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorCurrentPowerResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderCurrentPowerResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2024,12 +2045,12 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Marshal() (dAtA []byte, err erro return dAtA[:n], nil } -func (m *QueryBTCValidatorCurrentPowerResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderCurrentPowerResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2047,7 +2068,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) MarshalToSizedBuffer(dAtA []byte return len(dAtA) - i, nil } -func (m *QueryActiveBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryActiveFinalityProvidersAtHeightRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2057,12 +2078,12 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Marshal() (dAtA []byte, err er return dAtA[:n], nil } -func (m *QueryActiveBTCValidatorsAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActiveFinalityProvidersAtHeightRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryActiveBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActiveFinalityProvidersAtHeightRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2087,7 +2108,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) MarshalToSizedBuffer(dAtA []by return len(dAtA) - i, nil } -func (m *QueryActiveBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryActiveFinalityProvidersAtHeightResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2097,12 +2118,12 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Marshal() (dAtA []byte, err e return dAtA[:n], nil } -func (m *QueryActiveBTCValidatorsAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryActiveFinalityProvidersAtHeightResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryActiveBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryActiveFinalityProvidersAtHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2119,10 +2140,10 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) MarshalToSizedBuffer(dAtA []b i-- dAtA[i] = 0x12 } - if len(m.BtcValidators) > 0 { - for iNdEx := len(m.BtcValidators) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FinalityProviders) > 0 { + for iNdEx := len(m.FinalityProviders) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.BtcValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.FinalityProviders[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -2187,7 +2208,7 @@ func (m *QueryActivatedHeightResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderDelegationsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2197,12 +2218,12 @@ func (m *QueryBTCValidatorDelegationsRequest) Marshal() (dAtA []byte, err error) return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderDelegationsRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2219,17 +2240,17 @@ func (m *QueryBTCValidatorDelegationsRequest) MarshalToSizedBuffer(dAtA []byte) i-- dAtA[i] = 0x12 } - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error) { +func (m *QueryFinalityProviderDelegationsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2239,12 +2260,12 @@ func (m *QueryBTCValidatorDelegationsResponse) Marshal() (dAtA []byte, err error return dAtA[:n], nil } -func (m *QueryBTCValidatorDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderDelegationsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryBTCValidatorDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *QueryFinalityProviderDelegationsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2372,12 +2393,12 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i-- dAtA[i] = 0x18 } - if len(m.ValBtcPkList) > 0 { - for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FpBtcPkList) > 0 { + for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { { - size := m.ValBtcPkList[iNdEx].Size() + size := m.FpBtcPkList[iNdEx].Size() i -= size - if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintQuery(dAtA, i, uint64(size)) @@ -2432,7 +2453,7 @@ func (m *QueryParamsResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorsRequest) Size() (n int) { +func (m *QueryFinalityProvidersRequest) Size() (n int) { if m == nil { return 0 } @@ -2445,14 +2466,14 @@ func (m *QueryBTCValidatorsRequest) Size() (n int) { return n } -func (m *QueryBTCValidatorsResponse) Size() (n int) { +func (m *QueryFinalityProvidersResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l - if len(m.BtcValidators) > 0 { - for _, e := range m.BtcValidators { + if len(m.FinalityProviders) > 0 { + for _, e := range m.FinalityProviders { l = e.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -2464,27 +2485,27 @@ func (m *QueryBTCValidatorsResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorRequest) Size() (n int) { +func (m *QueryFinalityProviderRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } return n } -func (m *QueryBTCValidatorResponse) Size() (n int) { +func (m *QueryFinalityProviderResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.BtcValidator != nil { - l = m.BtcValidator.Size() + if m.FinalityProvider != nil { + l = m.FinalityProvider.Size() n += 1 + l + sovQuery(uint64(l)) } return n @@ -2525,13 +2546,13 @@ func (m *QueryBTCDelegationsResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorPowerAtHeightRequest) Size() (n int) { +func (m *QueryFinalityProviderPowerAtHeightRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -2541,7 +2562,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Size() (n int) { return n } -func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { +func (m *QueryFinalityProviderPowerAtHeightResponse) Size() (n int) { if m == nil { return 0 } @@ -2553,20 +2574,20 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorCurrentPowerRequest) Size() (n int) { +func (m *QueryFinalityProviderCurrentPowerRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } return n } -func (m *QueryBTCValidatorCurrentPowerResponse) Size() (n int) { +func (m *QueryFinalityProviderCurrentPowerResponse) Size() (n int) { if m == nil { return 0 } @@ -2581,7 +2602,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Size() (n int) { return n } -func (m *QueryActiveBTCValidatorsAtHeightRequest) Size() (n int) { +func (m *QueryActiveFinalityProvidersAtHeightRequest) Size() (n int) { if m == nil { return 0 } @@ -2597,14 +2618,14 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Size() (n int) { return n } -func (m *QueryActiveBTCValidatorsAtHeightResponse) Size() (n int) { +func (m *QueryActiveFinalityProvidersAtHeightResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l - if len(m.BtcValidators) > 0 { - for _, e := range m.BtcValidators { + if len(m.FinalityProviders) > 0 { + for _, e := range m.FinalityProviders { l = e.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -2637,13 +2658,13 @@ func (m *QueryActivatedHeightResponse) Size() (n int) { return n } -func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { +func (m *QueryFinalityProviderDelegationsRequest) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -2654,7 +2675,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Size() (n int) { return n } -func (m *QueryBTCValidatorDelegationsResponse) Size() (n int) { +func (m *QueryFinalityProviderDelegationsResponse) Size() (n int) { if m == nil { return 0 } @@ -2696,8 +2717,8 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovQuery(uint64(l)) } - if len(m.ValBtcPkList) > 0 { - for _, e := range m.ValBtcPkList { + if len(m.FpBtcPkList) > 0 { + for _, e := range m.FpBtcPkList { l = e.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -2864,7 +2885,7 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorsRequest) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProvidersRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2887,10 +2908,10 @@ func (m *QueryBTCValidatorsRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorsRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProvidersRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProvidersRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2950,7 +2971,7 @@ func (m *QueryBTCValidatorsRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProvidersResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2973,15 +2994,15 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProvidersResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProvidersResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcValidators", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FinalityProviders", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3008,8 +3029,8 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcValidators = append(m.BtcValidators, &BTCValidator{}) - if err := m.BtcValidators[len(m.BtcValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FinalityProviders = append(m.FinalityProviders, &FinalityProvider{}) + if err := m.FinalityProviders[len(m.FinalityProviders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3070,7 +3091,7 @@ func (m *QueryBTCValidatorsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorRequest) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3093,15 +3114,15 @@ func (m *QueryBTCValidatorRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3129,7 +3150,7 @@ func (m *QueryBTCValidatorRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -3152,7 +3173,7 @@ func (m *QueryBTCValidatorRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorResponse) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3175,15 +3196,15 @@ func (m *QueryBTCValidatorResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcValidator", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FinalityProvider", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3210,10 +3231,10 @@ func (m *QueryBTCValidatorResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.BtcValidator == nil { - m.BtcValidator = &BTCValidator{} + if m.FinalityProvider == nil { + m.FinalityProvider = &FinalityProvider{} } - if err := m.BtcValidator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.FinalityProvider.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3463,7 +3484,7 @@ func (m *QueryBTCDelegationsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderPowerAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3486,15 +3507,15 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderPowerAtHeightRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderPowerAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3522,7 +3543,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 0 { @@ -3564,7 +3585,7 @@ func (m *QueryBTCValidatorPowerAtHeightRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderPowerAtHeightResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3587,10 +3608,10 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderPowerAtHeightResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorPowerAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderPowerAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3633,7 +3654,7 @@ func (m *QueryBTCValidatorPowerAtHeightResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorCurrentPowerRequest) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderCurrentPowerRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3656,15 +3677,15 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderCurrentPowerRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderCurrentPowerRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3692,7 +3713,7 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -3715,7 +3736,7 @@ func (m *QueryBTCValidatorCurrentPowerRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderCurrentPowerResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3738,10 +3759,10 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderCurrentPowerResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorCurrentPowerResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderCurrentPowerResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3803,7 +3824,7 @@ func (m *QueryBTCValidatorCurrentPowerResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryActiveBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { +func (m *QueryActiveFinalityProvidersAtHeightRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3826,10 +3847,10 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryActiveFinalityProvidersAtHeightRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryActiveFinalityProvidersAtHeightRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3908,7 +3929,7 @@ func (m *QueryActiveBTCValidatorsAtHeightRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryActiveBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error { +func (m *QueryActiveFinalityProvidersAtHeightResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3931,15 +3952,15 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryActiveFinalityProvidersAtHeightResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryActiveBTCValidatorsAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryActiveFinalityProvidersAtHeightResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcValidators", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FinalityProviders", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3966,8 +3987,8 @@ func (m *QueryActiveBTCValidatorsAtHeightResponse) Unmarshal(dAtA []byte) error if postIndex > l { return io.ErrUnexpectedEOF } - m.BtcValidators = append(m.BtcValidators, &BTCValidatorWithMeta{}) - if err := m.BtcValidators[len(m.BtcValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FinalityProviders = append(m.FinalityProviders, &FinalityProviderWithMeta{}) + if err := m.FinalityProviders[len(m.FinalityProviders)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4147,7 +4168,7 @@ func (m *QueryActivatedHeightResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderDelegationsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4170,15 +4191,15 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsRequest: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderDelegationsRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderDelegationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4206,7 +4227,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -4265,7 +4286,7 @@ func (m *QueryBTCValidatorDelegationsRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { +func (m *QueryFinalityProviderDelegationsResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4288,10 +4309,10 @@ func (m *QueryBTCValidatorDelegationsResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsResponse: wiretype end group for non-group") + return fmt.Errorf("proto: QueryFinalityProviderDelegationsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: QueryBTCValidatorDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: QueryFinalityProviderDelegationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -4533,7 +4554,7 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -4561,8 +4582,8 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPkList = append(m.ValBtcPkList, v) - if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPkList = append(m.FpBtcPkList, v) + if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/btcstaking/types/query.pb.gw.go b/x/btcstaking/types/query.pb.gw.go index 07aff7dba..01da4f93d 100644 --- a/x/btcstaking/types/query.pb.gw.go +++ b/x/btcstaking/types/query.pb.gw.go @@ -52,43 +52,43 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } var ( - filter_Query_BTCValidators_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_Query_FinalityProviders_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_Query_BTCValidators_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorsRequest +func request_Query_FinalityProviders_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProvidersRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidators_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_FinalityProviders_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.BTCValidators(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.FinalityProviders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidators_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorsRequest +func local_request_Query_FinalityProviders_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProvidersRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidators_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_FinalityProviders_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.BTCValidators(ctx, &protoReq) + msg, err := server.FinalityProviders(ctx, &protoReq) return msg, metadata, err } -func request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorRequest +func request_Query_FinalityProvider_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderRequest var metadata runtime.ServerMetadata var ( @@ -98,24 +98,24 @@ func request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.Marshal _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } - msg, err := client.BTCValidator(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.FinalityProvider(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorRequest +func local_request_Query_FinalityProvider_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderRequest var metadata runtime.ServerMetadata var ( @@ -125,18 +125,18 @@ func local_request_Query_BTCValidator_0(ctx context.Context, marshaler runtime.M _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } - msg, err := server.BTCValidator(ctx, &protoReq) + msg, err := server.FinalityProvider(ctx, &protoReq) return msg, metadata, err } @@ -178,11 +178,11 @@ func local_request_Query_BTCDelegations_0(ctx context.Context, marshaler runtime } var ( - filter_Query_ActiveBTCValidatorsAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + filter_Query_ActiveFinalityProvidersAtHeight_0 = &utilities.DoubleArray{Encoding: map[string]int{"height": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) -func request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryActiveBTCValidatorsAtHeightRequest +func request_Query_ActiveFinalityProvidersAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActiveFinalityProvidersAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -206,17 +206,17 @@ func request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, marshaler if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveBTCValidatorsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveFinalityProvidersAtHeight_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.ActiveBTCValidatorsAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ActiveFinalityProvidersAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryActiveBTCValidatorsAtHeightRequest +func local_request_Query_ActiveFinalityProvidersAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActiveFinalityProvidersAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -240,17 +240,17 @@ func local_request_Query_ActiveBTCValidatorsAtHeight_0(ctx context.Context, mars if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveBTCValidatorsAtHeight_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ActiveFinalityProvidersAtHeight_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.ActiveBTCValidatorsAtHeight(ctx, &protoReq) + msg, err := server.ActiveFinalityProvidersAtHeight(ctx, &protoReq) return msg, metadata, err } -func request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorPowerAtHeightRequest +func request_Query_FinalityProviderPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderPowerAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -260,15 +260,15 @@ func request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler ru _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } val, ok = pathParams["height"] @@ -282,13 +282,13 @@ func request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler ru return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) } - msg, err := client.BTCValidatorPowerAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.FinalityProviderPowerAtHeight(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorPowerAtHeightRequest +func local_request_Query_FinalityProviderPowerAtHeight_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderPowerAtHeightRequest var metadata runtime.ServerMetadata var ( @@ -298,15 +298,15 @@ func local_request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marsha _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } val, ok = pathParams["height"] @@ -320,13 +320,13 @@ func local_request_Query_BTCValidatorPowerAtHeight_0(ctx context.Context, marsha return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err) } - msg, err := server.BTCValidatorPowerAtHeight(ctx, &protoReq) + msg, err := server.FinalityProviderPowerAtHeight(ctx, &protoReq) return msg, metadata, err } -func request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorCurrentPowerRequest +func request_Query_FinalityProviderCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderCurrentPowerRequest var metadata runtime.ServerMetadata var ( @@ -336,24 +336,24 @@ func request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshaler run _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } - msg, err := client.BTCValidatorCurrentPower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.FinalityProviderCurrentPower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorCurrentPowerRequest +func local_request_Query_FinalityProviderCurrentPower_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderCurrentPowerRequest var metadata runtime.ServerMetadata var ( @@ -363,18 +363,18 @@ func local_request_Query_BTCValidatorCurrentPower_0(ctx context.Context, marshal _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } - msg, err := server.BTCValidatorCurrentPower(ctx, &protoReq) + msg, err := server.FinalityProviderCurrentPower(ctx, &protoReq) return msg, metadata, err } @@ -398,11 +398,11 @@ func local_request_Query_ActivatedHeight_0(ctx context.Context, marshaler runtim } var ( - filter_Query_BTCValidatorDelegations_0 = &utilities.DoubleArray{Encoding: map[string]int{"val_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + filter_Query_FinalityProviderDelegations_0 = &utilities.DoubleArray{Encoding: map[string]int{"fp_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) -func request_Query_BTCValidatorDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorDelegationsRequest +func request_Query_FinalityProviderDelegations_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderDelegationsRequest var metadata runtime.ServerMetadata var ( @@ -412,31 +412,31 @@ func request_Query_BTCValidatorDelegations_0(ctx context.Context, marshaler runt _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegations_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_FinalityProviderDelegations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.BTCValidatorDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.FinalityProviderDelegations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_Query_BTCValidatorDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryBTCValidatorDelegationsRequest +func local_request_Query_FinalityProviderDelegations_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFinalityProviderDelegationsRequest var metadata runtime.ServerMetadata var ( @@ -446,25 +446,25 @@ func local_request_Query_BTCValidatorDelegations_0(ctx context.Context, marshale _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_BTCValidatorDelegations_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_FinalityProviderDelegations_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.BTCValidatorDelegations(ctx, &protoReq) + msg, err := server.FinalityProviderDelegations(ctx, &protoReq) return msg, metadata, err } @@ -552,7 +552,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_BTCValidators_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -563,7 +563,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidators_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_FinalityProviders_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -571,11 +571,11 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidators_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProvider_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -586,7 +586,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidator_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_FinalityProvider_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -594,7 +594,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProvider_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -621,7 +621,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_ActiveFinalityProvidersAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -632,7 +632,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_ActiveBTCValidatorsAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_ActiveFinalityProvidersAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -640,11 +640,11 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_ActiveBTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_ActiveFinalityProvidersAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidatorPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -655,7 +655,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidatorPowerAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_FinalityProviderPowerAtHeight_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -663,11 +663,11 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidatorPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidatorCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -678,7 +678,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidatorCurrentPower_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_FinalityProviderCurrentPower_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -686,7 +686,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidatorCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -713,7 +713,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -724,7 +724,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_BTCValidatorDelegations_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_FinalityProviderDelegations_0(rctx, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { @@ -732,7 +732,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv return } - forward_Query_BTCValidatorDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -820,7 +820,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_BTCValidators_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -829,18 +829,18 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidators_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_FinalityProviders_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidators_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProvider_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -849,14 +849,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidator_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_FinalityProvider_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProvider_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -880,7 +880,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_ActiveBTCValidatorsAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_ActiveFinalityProvidersAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -889,18 +889,18 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_ActiveBTCValidatorsAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_ActiveFinalityProvidersAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_ActiveBTCValidatorsAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_ActiveFinalityProvidersAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidatorPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderPowerAtHeight_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -909,18 +909,18 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidatorPowerAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_FinalityProviderPowerAtHeight_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidatorPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderPowerAtHeight_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("GET", pattern_Query_BTCValidatorCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderCurrentPower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -929,14 +929,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidatorCurrentPower_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_FinalityProviderCurrentPower_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidatorCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderCurrentPower_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -960,7 +960,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_BTCValidatorDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_Query_FinalityProviderDelegations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -969,14 +969,14 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_Query_BTCValidatorDelegations_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_Query_FinalityProviderDelegations_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_Query_BTCValidatorDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_FinalityProviderDelegations_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1006,21 +1006,21 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidators_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_validators"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_FinalityProviders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "finality_providers"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "validator"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_FinalityProvider_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "finality_providers", "fp_btc_pk_hex", "finality_provider"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "btc_delegations"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ActiveBTCValidatorsAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_validators", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ActiveFinalityProvidersAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "finality_providers", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_FinalityProviderPowerAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"babylon", "btcstaking", "v1", "finality_providers", "fp_btc_pk_hex", "power", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorCurrentPower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "power"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_FinalityProviderCurrentPower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "finality_providers", "fp_btc_pk_hex", "power"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_ActivatedHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btcstaking", "v1", "activated_height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_BTCValidatorDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "btc_validators", "val_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_FinalityProviderDelegations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "btcstaking", "v1", "finality_providers", "fp_btc_pk_hex", "delegations"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_BTCDelegation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "btcstaking", "v1", "btc_delegations", "staking_tx_hash_hex"}, "", runtime.AssumeColonVerbOpt(false))) ) @@ -1028,21 +1028,21 @@ var ( var ( forward_Query_Params_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidators_0 = runtime.ForwardResponseMessage + forward_Query_FinalityProviders_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidator_0 = runtime.ForwardResponseMessage + forward_Query_FinalityProvider_0 = runtime.ForwardResponseMessage forward_Query_BTCDelegations_0 = runtime.ForwardResponseMessage - forward_Query_ActiveBTCValidatorsAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_ActiveFinalityProvidersAtHeight_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidatorPowerAtHeight_0 = runtime.ForwardResponseMessage + forward_Query_FinalityProviderPowerAtHeight_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidatorCurrentPower_0 = runtime.ForwardResponseMessage + forward_Query_FinalityProviderCurrentPower_0 = runtime.ForwardResponseMessage forward_Query_ActivatedHeight_0 = runtime.ForwardResponseMessage - forward_Query_BTCValidatorDelegations_0 = runtime.ForwardResponseMessage + forward_Query_FinalityProviderDelegations_0 = runtime.ForwardResponseMessage forward_Query_BTCDelegation_0 = runtime.ForwardResponseMessage ) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 45459b2e9..88f459aa4 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -35,34 +35,34 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// MsgCreateBTCValidator is the message for creating a BTC validator -type MsgCreateBTCValidator struct { +// MsgCreateFinalityProvider is the message for creating a finality provider +type MsgCreateFinalityProvider struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // description defines the description terms for the BTC validator. + // description defines the description terms for the finality provider. Description *types.Description `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - // commission defines the commission rate of BTC validator. + // commission defines the commission rate of finality provider. Commission *cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=commission,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"commission,omitempty"` - // babylon_pk is the Babylon secp256k1 PK of this BTC validator + // babylon_pk is the Babylon secp256k1 PK of this finality provider BabylonPk *secp256k1.PubKey `protobuf:"bytes,4,opt,name=babylon_pk,json=babylonPk,proto3" json:"babylon_pk,omitempty"` - // btc_pk is the Bitcoin secp256k1 PK of this BTC validator + // btc_pk is the Bitcoin secp256k1 PK of this finality provider // the PK follows encoding in BIP-340 spec BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` // pop is the proof of possession of babylon_pk and btc_pk Pop *ProofOfPossession `protobuf:"bytes,6,opt,name=pop,proto3" json:"pop,omitempty"` } -func (m *MsgCreateBTCValidator) Reset() { *m = MsgCreateBTCValidator{} } -func (m *MsgCreateBTCValidator) String() string { return proto.CompactTextString(m) } -func (*MsgCreateBTCValidator) ProtoMessage() {} -func (*MsgCreateBTCValidator) Descriptor() ([]byte, []int) { +func (m *MsgCreateFinalityProvider) Reset() { *m = MsgCreateFinalityProvider{} } +func (m *MsgCreateFinalityProvider) String() string { return proto.CompactTextString(m) } +func (*MsgCreateFinalityProvider) ProtoMessage() {} +func (*MsgCreateFinalityProvider) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{0} } -func (m *MsgCreateBTCValidator) XXX_Unmarshal(b []byte) error { +func (m *MsgCreateFinalityProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgCreateBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgCreateFinalityProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgCreateBTCValidator.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgCreateFinalityProvider.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -72,62 +72,62 @@ func (m *MsgCreateBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byt return b[:n], nil } } -func (m *MsgCreateBTCValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCreateBTCValidator.Merge(m, src) +func (m *MsgCreateFinalityProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateFinalityProvider.Merge(m, src) } -func (m *MsgCreateBTCValidator) XXX_Size() int { +func (m *MsgCreateFinalityProvider) XXX_Size() int { return m.Size() } -func (m *MsgCreateBTCValidator) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCreateBTCValidator.DiscardUnknown(m) +func (m *MsgCreateFinalityProvider) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateFinalityProvider.DiscardUnknown(m) } -var xxx_messageInfo_MsgCreateBTCValidator proto.InternalMessageInfo +var xxx_messageInfo_MsgCreateFinalityProvider proto.InternalMessageInfo -func (m *MsgCreateBTCValidator) GetSigner() string { +func (m *MsgCreateFinalityProvider) GetSigner() string { if m != nil { return m.Signer } return "" } -func (m *MsgCreateBTCValidator) GetDescription() *types.Description { +func (m *MsgCreateFinalityProvider) GetDescription() *types.Description { if m != nil { return m.Description } return nil } -func (m *MsgCreateBTCValidator) GetBabylonPk() *secp256k1.PubKey { +func (m *MsgCreateFinalityProvider) GetBabylonPk() *secp256k1.PubKey { if m != nil { return m.BabylonPk } return nil } -func (m *MsgCreateBTCValidator) GetPop() *ProofOfPossession { +func (m *MsgCreateFinalityProvider) GetPop() *ProofOfPossession { if m != nil { return m.Pop } return nil } -// MsgCreateBTCValidatorResponse is the response for MsgCreateBTCValidator -type MsgCreateBTCValidatorResponse struct { +// MsgCreateFinalityProviderResponse is the response for MsgCreateFinalityProvider +type MsgCreateFinalityProviderResponse struct { } -func (m *MsgCreateBTCValidatorResponse) Reset() { *m = MsgCreateBTCValidatorResponse{} } -func (m *MsgCreateBTCValidatorResponse) String() string { return proto.CompactTextString(m) } -func (*MsgCreateBTCValidatorResponse) ProtoMessage() {} -func (*MsgCreateBTCValidatorResponse) Descriptor() ([]byte, []int) { +func (m *MsgCreateFinalityProviderResponse) Reset() { *m = MsgCreateFinalityProviderResponse{} } +func (m *MsgCreateFinalityProviderResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateFinalityProviderResponse) ProtoMessage() {} +func (*MsgCreateFinalityProviderResponse) Descriptor() ([]byte, []int) { return fileDescriptor_4baddb53e97f38f2, []int{1} } -func (m *MsgCreateBTCValidatorResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgCreateFinalityProviderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgCreateBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgCreateFinalityProviderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgCreateBTCValidatorResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgCreateFinalityProviderResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -137,17 +137,17 @@ func (m *MsgCreateBTCValidatorResponse) XXX_Marshal(b []byte, deterministic bool return b[:n], nil } } -func (m *MsgCreateBTCValidatorResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCreateBTCValidatorResponse.Merge(m, src) +func (m *MsgCreateFinalityProviderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateFinalityProviderResponse.Merge(m, src) } -func (m *MsgCreateBTCValidatorResponse) XXX_Size() int { +func (m *MsgCreateFinalityProviderResponse) XXX_Size() int { return m.Size() } -func (m *MsgCreateBTCValidatorResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCreateBTCValidatorResponse.DiscardUnknown(m) +func (m *MsgCreateFinalityProviderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateFinalityProviderResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgCreateBTCValidatorResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgCreateFinalityProviderResponse proto.InternalMessageInfo // MsgCreateBTCDelegation is the message for creating a BTC delegation type MsgCreateBTCDelegation struct { @@ -158,25 +158,25 @@ type MsgCreateBTCDelegation struct { Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` // btc_pk is the Bitcoin secp256k1 PK of the BTC delegator BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,4,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // val_btc_pk_list is the list of Bitcoin secp256k1 PKs of the BTC validators, if there is more than one - // validator pk it means that delegation is re-staked - ValBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,rep,name=val_btc_pk_list,json=valBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk_list,omitempty"` + // fp_btc_pk_list is the list of Bitcoin secp256k1 PKs of the finality providers, if there is more than one + // finality provider pk it means that delegation is re-staked + FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,5,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` // staking_time is the time lock used in staking transaction StakingTime uint32 `protobuf:"varint,6,opt,name=staking_time,json=stakingTime,proto3" json:"staking_time,omitempty"` - // staking_value is amout of satoshis locked in staking output + // staking_value is the amount of satoshis locked in staking output StakingValue int64 `protobuf:"varint,7,opt,name=staking_value,json=stakingValue,proto3" json:"staking_value,omitempty"` - // staking_tx is the staking tx along with the merekle proof of inclusion in btc block + // staking_tx is the staking tx along with the merkle proof of inclusion in btc block StakingTx *types1.TransactionInfo `protobuf:"bytes,8,opt,name=staking_tx,json=stakingTx,proto3" json:"staking_tx,omitempty"` // slashing_tx is the slashing tx // Note that the tx itself does not contain signatures, which are off-chain. SlashingTx *BTCSlashingTx `protobuf:"bytes,9,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the staking tx output. - // The staking tx output further needs signatures from covenant and validator in + // The staking tx output further needs signatures from covenant and finality provider in // order to be spendable. DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` /// fields related to on-demand unbonding - // unbonding_tx is bitcoin unbonding transaction i.e transaction that spends + // unbonding_tx is a bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output UnbondingTx []byte `protobuf:"bytes,11,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // unbonding_time is the time lock used in unbonding transaction @@ -333,7 +333,7 @@ type MsgAddCovenantSigs struct { // It uniquely identifies a BTC delegation StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` // sigs is a list of adaptor signatures of the covenant - // the order of sigs should respect the order of validators + // the order of sigs should respect the order of finality providers // of the corresponding delegation SlashingTxSigs [][]byte `protobuf:"bytes,4,rep,name=slashing_tx_sigs,json=slashingTxSigs,proto3" json:"slashing_tx_sigs,omitempty"` // unbonding_tx_sig is the signature of the covenant on the unbonding tx submitted to babylon @@ -341,7 +341,7 @@ type MsgAddCovenantSigs struct { UnbondingTxSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=unbonding_tx_sig,json=unbondingTxSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"unbonding_tx_sig,omitempty"` // slashing_unbonding_tx_sigs is a list of adaptor signatures of the covenant // on slashing tx corresponding to unbonding tx submitted to babylon - // the order of sigs should respect the order of validators + // the order of sigs should respect the order of finality providers // of the corresponding delegation SlashingUnbondingTxSigs [][]byte `protobuf:"bytes,6,rep,name=slashing_unbonding_tx_sigs,json=slashingUnbondingTxSigs,proto3" json:"slashing_unbonding_tx_sigs,omitempty"` } @@ -639,8 +639,8 @@ func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo func init() { - proto.RegisterType((*MsgCreateBTCValidator)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidator") - proto.RegisterType((*MsgCreateBTCValidatorResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCValidatorResponse") + proto.RegisterType((*MsgCreateFinalityProvider)(nil), "babylon.btcstaking.v1.MsgCreateFinalityProvider") + proto.RegisterType((*MsgCreateFinalityProviderResponse)(nil), "babylon.btcstaking.v1.MsgCreateFinalityProviderResponse") proto.RegisterType((*MsgCreateBTCDelegation)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegation") proto.RegisterType((*MsgCreateBTCDelegationResponse)(nil), "babylon.btcstaking.v1.MsgCreateBTCDelegationResponse") proto.RegisterType((*MsgAddCovenantSigs)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigs") @@ -654,79 +654,79 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1140 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdf, 0x6e, 0x1a, 0xc7, - 0x17, 0xf6, 0x82, 0x4d, 0x7e, 0x3e, 0x18, 0x93, 0xdf, 0x26, 0xb6, 0x37, 0xb4, 0x06, 0x4a, 0xda, - 0x84, 0x44, 0xf1, 0x52, 0x9c, 0xd8, 0x52, 0x1d, 0xa9, 0x52, 0xb0, 0x53, 0x25, 0xaa, 0x51, 0xd1, - 0x82, 0x73, 0x51, 0xa9, 0x42, 0xc3, 0x32, 0x5e, 0x46, 0xc0, 0xce, 0x6a, 0x67, 0x40, 0xa0, 0xde, - 0x54, 0x79, 0x82, 0x5e, 0xf5, 0xae, 0xef, 0x90, 0x8b, 0x3c, 0x42, 0xa5, 0xe6, 0x32, 0xca, 0x55, - 0x65, 0xa9, 0x56, 0x65, 0x5f, 0xe4, 0x09, 0x7a, 0x5f, 0xed, 0xee, 0xec, 0x1f, 0x28, 0xa8, 0x76, - 0x9c, 0xbb, 0xdd, 0x99, 0xef, 0x7c, 0xe7, 0x9c, 0xef, 0x9c, 0x33, 0x33, 0x90, 0x6d, 0xa1, 0xd6, - 0xb8, 0x47, 0xcd, 0x52, 0x8b, 0xeb, 0x8c, 0xa3, 0x2e, 0x31, 0x8d, 0xd2, 0xb0, 0x5c, 0xe2, 0x23, - 0xd5, 0xb2, 0x29, 0xa7, 0xf2, 0x9a, 0xd8, 0x57, 0xc3, 0x7d, 0x75, 0x58, 0xce, 0xdc, 0x34, 0xa8, - 0x41, 0x5d, 0x44, 0xc9, 0xf9, 0xf2, 0xc0, 0x99, 0x5b, 0x3a, 0x65, 0x7d, 0xca, 0x9a, 0xde, 0x86, - 0xf7, 0x23, 0xb6, 0x36, 0xbc, 0xbf, 0x52, 0x9f, 0xb9, 0xfc, 0x7d, 0x66, 0x88, 0x8d, 0x82, 0xd8, - 0xd0, 0xed, 0xb1, 0xc5, 0x69, 0x89, 0x61, 0xdd, 0xda, 0xde, 0xd9, 0xed, 0x96, 0x4b, 0x5d, 0x3c, - 0xf6, 0x8d, 0x0b, 0xb3, 0x83, 0xb4, 0x90, 0x8d, 0xfa, 0x3e, 0xe6, 0x41, 0x04, 0xa3, 0x77, 0xb0, - 0xde, 0xb5, 0x28, 0x31, 0xb9, 0x03, 0x9b, 0x58, 0x10, 0xe8, 0xcf, 0x85, 0xd7, 0x90, 0xad, 0x85, - 0x39, 0x2a, 0xfb, 0xff, 0x02, 0x95, 0x9b, 0xe3, 0x97, 0x5a, 0x1e, 0xa0, 0xf0, 0x6b, 0x1c, 0xd6, - 0xaa, 0xcc, 0xd8, 0xb7, 0x31, 0xe2, 0xb8, 0xd2, 0xd8, 0x7f, 0x81, 0x7a, 0xa4, 0x8d, 0x38, 0xb5, - 0xe5, 0x75, 0x48, 0x30, 0x62, 0x98, 0xd8, 0x56, 0xa4, 0xbc, 0x54, 0x5c, 0xd6, 0xc4, 0x9f, 0xfc, - 0x14, 0x92, 0x6d, 0xcc, 0x74, 0x9b, 0x58, 0x9c, 0x50, 0x53, 0x89, 0xe5, 0xa5, 0x62, 0x72, 0xfb, - 0xb6, 0x2a, 0xb4, 0x0a, 0x15, 0x76, 0xc3, 0x51, 0x0f, 0x42, 0xa8, 0x16, 0xb5, 0x93, 0xab, 0x00, - 0x3a, 0xed, 0xf7, 0x09, 0x63, 0x0e, 0x4b, 0xdc, 0x71, 0x51, 0xd9, 0x3a, 0x39, 0xcd, 0x7d, 0xe2, - 0x11, 0xb1, 0x76, 0x57, 0x25, 0xb4, 0xd4, 0x47, 0xbc, 0xa3, 0x1e, 0x62, 0x03, 0xe9, 0xe3, 0x03, - 0xac, 0xbf, 0x7b, 0xbd, 0x05, 0xc2, 0xcf, 0x01, 0xd6, 0xb5, 0x08, 0x81, 0xfc, 0x35, 0x80, 0x48, - 0xb5, 0x69, 0x75, 0x95, 0x45, 0x37, 0xa8, 0x9c, 0x1f, 0x94, 0x57, 0x19, 0x35, 0xa8, 0x8c, 0x5a, - 0x1b, 0xb4, 0xbe, 0xc5, 0x63, 0x6d, 0x59, 0x98, 0xd4, 0xba, 0x72, 0x15, 0x12, 0x2d, 0xae, 0x3b, - 0xb6, 0x4b, 0x79, 0xa9, 0xb8, 0x52, 0xd9, 0x3d, 0x39, 0xcd, 0x6d, 0x1b, 0x84, 0x77, 0x06, 0x2d, - 0x55, 0xa7, 0xfd, 0x92, 0x40, 0xea, 0x1d, 0x44, 0x4c, 0xff, 0xa7, 0xc4, 0xc7, 0x16, 0x66, 0x6a, - 0xe5, 0x79, 0xed, 0xe1, 0xa3, 0x2f, 0x05, 0xe5, 0x52, 0x8b, 0xeb, 0xb5, 0xae, 0xbc, 0x07, 0x71, - 0x8b, 0x5a, 0x4a, 0xc2, 0x8d, 0xa3, 0xa8, 0xce, 0x6c, 0x41, 0xb5, 0x66, 0x53, 0x7a, 0xfc, 0xdd, - 0x71, 0x8d, 0x32, 0x86, 0xdd, 0x2c, 0x34, 0xc7, 0x68, 0x2f, 0xf9, 0xf2, 0xfd, 0xab, 0xfb, 0x42, - 0xed, 0x42, 0x0e, 0x36, 0x67, 0x96, 0x47, 0xc3, 0xcc, 0xa2, 0x26, 0xc3, 0x85, 0x3f, 0xaf, 0xc1, - 0x7a, 0x14, 0x71, 0x80, 0x7b, 0xd8, 0x40, 0xae, 0xc4, 0xf3, 0x2a, 0x38, 0xa9, 0x55, 0xec, 0xd2, - 0x5a, 0x89, 0xe4, 0xe2, 0x1f, 0x90, 0x5c, 0x44, 0xe7, 0xc5, 0x8f, 0xa1, 0xf3, 0x0f, 0x90, 0x1e, - 0xa2, 0x5e, 0xd3, 0xa3, 0x6c, 0xf6, 0x08, 0xe3, 0xca, 0x52, 0x3e, 0x7e, 0x05, 0xde, 0x95, 0x21, - 0xea, 0x55, 0x1c, 0xea, 0x43, 0xc2, 0xb8, 0xfc, 0x19, 0xac, 0x88, 0x94, 0x9a, 0x9c, 0xf4, 0xb1, - 0x5b, 0xcf, 0x94, 0x96, 0x14, 0x6b, 0x0d, 0xd2, 0xc7, 0xf2, 0x6d, 0x48, 0xf9, 0x90, 0x21, 0xea, - 0x0d, 0xb0, 0x72, 0x2d, 0x2f, 0x15, 0xe3, 0x9a, 0x6f, 0xf7, 0xc2, 0x59, 0x93, 0x9f, 0x01, 0x04, - 0x3c, 0x23, 0xe5, 0x7f, 0xae, 0x70, 0xf7, 0xa2, 0xc2, 0x45, 0xc6, 0x7b, 0x58, 0x56, 0x1b, 0x36, - 0x32, 0x19, 0xd2, 0x9d, 0x22, 0x3e, 0x37, 0x8f, 0xa9, 0xb6, 0xec, 0x3b, 0x1c, 0xc9, 0xdb, 0x90, - 0x64, 0x3d, 0xc4, 0x3a, 0x82, 0x6a, 0xd9, 0x15, 0xf1, 0xff, 0x27, 0xa7, 0xb9, 0x54, 0xa5, 0xb1, - 0x5f, 0x17, 0x3b, 0x8d, 0x91, 0x06, 0x2c, 0xf8, 0x96, 0x29, 0xac, 0xb7, 0xbd, 0xae, 0xa0, 0x76, - 0x33, 0xb0, 0x66, 0xc4, 0x50, 0xc0, 0x35, 0xff, 0xea, 0xe4, 0x34, 0xb7, 0x73, 0x19, 0xad, 0xea, - 0xc4, 0x30, 0x11, 0x1f, 0xd8, 0x58, 0xbb, 0x19, 0x10, 0xfb, 0xbe, 0xeb, 0xc4, 0x70, 0x64, 0x1b, - 0x98, 0x2d, 0x6a, 0xb6, 0x45, 0x94, 0x49, 0xc7, 0x8d, 0x96, 0x0c, 0xd6, 0x1a, 0x23, 0xf9, 0x0b, - 0x58, 0x8d, 0x40, 0x1c, 0x6d, 0x57, 0x5c, 0x6d, 0x53, 0x21, 0xc8, 0x51, 0xf7, 0x2e, 0xa4, 0x43, - 0x98, 0xa7, 0x6f, 0xca, 0xd5, 0x37, 0xb4, 0xf6, 0x14, 0x7e, 0x0a, 0x6b, 0x21, 0x30, 0xaa, 0xd0, - 0xea, 0x3c, 0x85, 0x6e, 0x04, 0xf8, 0x70, 0x51, 0x7e, 0x29, 0x41, 0x3e, 0xd4, 0x6a, 0x06, 0xa3, - 0xa3, 0x5a, 0xfa, 0xaa, 0xaa, 0x6d, 0x06, 0x2e, 0x8e, 0xa6, 0x63, 0xa8, 0x13, 0x63, 0xf2, 0x00, - 0xc8, 0x43, 0x76, 0xf6, 0x78, 0x07, 0x27, 0xc0, 0xdf, 0x31, 0x90, 0xab, 0xcc, 0x78, 0xd2, 0x6e, - 0xef, 0xd3, 0x21, 0x36, 0x91, 0xc9, 0xeb, 0xc4, 0x60, 0x73, 0xa7, 0xff, 0x1b, 0x88, 0x89, 0xa9, - 0xff, 0xf0, 0x29, 0x89, 0x59, 0x5d, 0xf9, 0x0e, 0xa4, 0xc3, 0x9e, 0x6e, 0x76, 0x10, 0xeb, 0x78, - 0xa7, 0xb8, 0x96, 0x0a, 0xba, 0xf5, 0x19, 0x62, 0x1d, 0xb9, 0x08, 0xd7, 0x23, 0xf5, 0x70, 0x04, - 0x64, 0xca, 0xa2, 0x33, 0xa3, 0xda, 0x6a, 0xd8, 0xa3, 0x6e, 0xc4, 0x3a, 0x5c, 0x8f, 0xb6, 0x8d, - 0xab, 0xf5, 0xd2, 0x55, 0xb5, 0x5e, 0x8d, 0x74, 0x9d, 0xd3, 0x9b, 0x8f, 0x21, 0x13, 0x84, 0x33, - 0xed, 0x8d, 0x29, 0x09, 0x37, 0xb0, 0x0d, 0x1f, 0x71, 0x34, 0x61, 0xcb, 0x26, 0x2b, 0xf3, 0x29, - 0x64, 0xfe, 0x2d, 0x7b, 0x50, 0x95, 0xdf, 0x24, 0xb8, 0x5e, 0x65, 0x46, 0xa5, 0xb1, 0x7f, 0x64, - 0x8a, 0x72, 0xe3, 0xb9, 0x35, 0x99, 0xa1, 0x65, 0x6c, 0x96, 0x96, 0xb3, 0x14, 0x8a, 0x7f, 0x64, - 0x85, 0x26, 0x93, 0xcc, 0x80, 0x32, 0x9d, 0x45, 0x90, 0xe2, 0x2f, 0x12, 0xa4, 0xab, 0xcc, 0x38, - 0xb2, 0xda, 0x88, 0xe3, 0x9a, 0xfb, 0x94, 0x91, 0x77, 0x61, 0x19, 0x0d, 0x78, 0x87, 0xda, 0x84, - 0x8f, 0xbd, 0x24, 0x2b, 0xca, 0xbb, 0xd7, 0x5b, 0x37, 0xc5, 0xed, 0xf2, 0xa4, 0xdd, 0xb6, 0x31, - 0x63, 0x75, 0x6e, 0x13, 0xd3, 0xd0, 0x42, 0xa8, 0xfc, 0x18, 0x12, 0xde, 0x63, 0x48, 0xdc, 0x47, - 0x9b, 0xf3, 0xae, 0x15, 0x17, 0x54, 0x59, 0x7c, 0x73, 0x9a, 0x5b, 0xd0, 0x84, 0xc9, 0xde, 0xaa, - 0x13, 0x71, 0x48, 0x56, 0xb8, 0x05, 0x1b, 0x53, 0x71, 0xf9, 0x31, 0x6f, 0xff, 0xbe, 0x08, 0xf1, - 0x2a, 0x33, 0xe4, 0x11, 0xc8, 0x33, 0xde, 0x3c, 0x0f, 0xe6, 0x78, 0x9d, 0x79, 0x05, 0x67, 0x1e, - 0x5d, 0x06, 0xed, 0x47, 0x20, 0xff, 0x08, 0x37, 0x66, 0x5d, 0xd6, 0x5b, 0x17, 0x20, 0x0b, 0xe1, - 0x99, 0x9d, 0x4b, 0xc1, 0x03, 0xe7, 0x14, 0xd2, 0xd3, 0xe7, 0xc4, 0xbd, 0xf9, 0x4c, 0x53, 0xd0, - 0x4c, 0xf9, 0xc2, 0xd0, 0xc0, 0xe1, 0x31, 0xac, 0x4c, 0xf4, 0xc7, 0x9d, 0xf9, 0x14, 0x51, 0x5c, - 0x46, 0xbd, 0x18, 0x2e, 0xf0, 0x43, 0x20, 0x35, 0x39, 0x6a, 0x77, 0xe7, 0x13, 0x4c, 0x00, 0x33, - 0xa5, 0x0b, 0x02, 0x7d, 0x57, 0x99, 0xa5, 0x9f, 0xde, 0xbf, 0xba, 0x2f, 0x55, 0x0e, 0xdf, 0x9c, - 0x65, 0xa5, 0xb7, 0x67, 0x59, 0xe9, 0xaf, 0xb3, 0xac, 0xf4, 0xf3, 0x79, 0x76, 0xe1, 0xed, 0x79, - 0x76, 0xe1, 0x8f, 0xf3, 0xec, 0xc2, 0xf7, 0xff, 0x79, 0xa2, 0x8e, 0xa2, 0xcf, 0x71, 0x77, 0x28, - 0x5b, 0x09, 0xf7, 0x39, 0xfe, 0xf0, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x52, 0xfc, 0xf9, 0xf0, - 0xce, 0x0c, 0x00, 0x00, + // 1145 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1a, 0x47, + 0x14, 0xf7, 0x82, 0x4d, 0xea, 0x87, 0xc1, 0xe9, 0x26, 0xb1, 0xd7, 0xb4, 0x01, 0x82, 0xdb, 0x84, + 0x44, 0xf5, 0x12, 0x9c, 0xda, 0x6a, 0x1d, 0xa9, 0x52, 0xb0, 0x13, 0x25, 0xaa, 0x51, 0xd1, 0x82, + 0x7b, 0x68, 0x0f, 0x68, 0x59, 0x86, 0x65, 0x04, 0xec, 0xac, 0x76, 0x06, 0x04, 0xea, 0xa5, 0x8a, + 0xfa, 0x01, 0x7a, 0xea, 0xbd, 0xdf, 0x20, 0x87, 0x7c, 0x84, 0x1e, 0x72, 0x8c, 0xa2, 0x1e, 0x2a, + 0x57, 0xb2, 0x2a, 0xfb, 0x90, 0x4f, 0xd0, 0x7b, 0xb5, 0xbb, 0xb3, 0x7f, 0xa0, 0xac, 0x62, 0xc7, + 0xb9, 0xed, 0xcc, 0xfc, 0xde, 0xef, 0xbd, 0xf7, 0x7b, 0xef, 0xed, 0x0c, 0x64, 0x5b, 0x6a, 0x6b, + 0xd2, 0x27, 0x46, 0xa9, 0xc5, 0x34, 0xca, 0xd4, 0x1e, 0x36, 0xf4, 0xd2, 0xa8, 0x5c, 0x62, 0x63, + 0xd9, 0xb4, 0x08, 0x23, 0xe2, 0x0d, 0x7e, 0x2e, 0x07, 0xe7, 0xf2, 0xa8, 0x9c, 0xb9, 0xae, 0x13, + 0x9d, 0x38, 0x88, 0x92, 0xfd, 0xe5, 0x82, 0x33, 0x1b, 0x1a, 0xa1, 0x03, 0x42, 0x9b, 0xee, 0x81, + 0xbb, 0xe0, 0x47, 0xeb, 0xee, 0xaa, 0x34, 0xa0, 0x0e, 0xff, 0x80, 0xea, 0xfc, 0xa0, 0xc0, 0x0f, + 0x34, 0x6b, 0x62, 0x32, 0x52, 0xa2, 0x48, 0x33, 0xb7, 0x77, 0x76, 0x7b, 0xe5, 0x52, 0x0f, 0x4d, + 0x3c, 0xe3, 0xc2, 0xfc, 0x20, 0x4d, 0xd5, 0x52, 0x07, 0x1e, 0xe6, 0x8b, 0x10, 0x46, 0xeb, 0x22, + 0xad, 0x67, 0x12, 0x6c, 0x30, 0x1b, 0x36, 0xb5, 0xc1, 0xd1, 0x9f, 0x71, 0xaf, 0x01, 0x5b, 0x0b, + 0x31, 0xb5, 0xec, 0xad, 0x39, 0x2a, 0x17, 0xe1, 0x97, 0x98, 0x2e, 0xa0, 0xf0, 0x7b, 0x1c, 0x36, + 0xaa, 0x54, 0xdf, 0xb7, 0x90, 0xca, 0xd0, 0x13, 0x6c, 0xa8, 0x7d, 0xcc, 0x26, 0x35, 0x8b, 0x8c, + 0x70, 0x1b, 0x59, 0xe2, 0x1a, 0x24, 0x28, 0xd6, 0x0d, 0x64, 0x49, 0x42, 0x5e, 0x28, 0x2e, 0x2b, + 0x7c, 0x25, 0x3e, 0x86, 0x64, 0x1b, 0x51, 0xcd, 0xc2, 0x26, 0xc3, 0xc4, 0x90, 0x62, 0x79, 0xa1, + 0x98, 0xdc, 0xde, 0x94, 0xb9, 0x5e, 0x81, 0xca, 0x4e, 0x48, 0xf2, 0x41, 0x00, 0x55, 0xc2, 0x76, + 0x62, 0x15, 0x40, 0x23, 0x83, 0x01, 0xa6, 0xd4, 0x66, 0x89, 0xdb, 0x2e, 0x2a, 0x5b, 0xc7, 0x27, + 0xb9, 0x4f, 0x5c, 0x22, 0xda, 0xee, 0xc9, 0x98, 0x94, 0x06, 0x2a, 0xeb, 0xca, 0x87, 0x48, 0x57, + 0xb5, 0xc9, 0x01, 0xd2, 0xde, 0xbc, 0xdc, 0x02, 0xee, 0xe7, 0x00, 0x69, 0x4a, 0x88, 0x40, 0xfc, + 0x06, 0x80, 0xa7, 0xdb, 0x34, 0x7b, 0xd2, 0xa2, 0x13, 0x54, 0xce, 0x0b, 0xca, 0xad, 0x8e, 0xec, + 0x57, 0x47, 0xae, 0x0d, 0x5b, 0xdf, 0xa2, 0x89, 0xb2, 0xcc, 0x4d, 0x6a, 0x3d, 0xb1, 0x0a, 0x89, + 0x16, 0xd3, 0x6c, 0xdb, 0xa5, 0xbc, 0x50, 0x5c, 0xa9, 0xec, 0x1e, 0x9f, 0xe4, 0xb6, 0x75, 0xcc, + 0xba, 0xc3, 0x96, 0xac, 0x91, 0x41, 0x89, 0x23, 0xb5, 0xae, 0x8a, 0x0d, 0x6f, 0x51, 0x62, 0x13, + 0x13, 0x51, 0xb9, 0xf2, 0xac, 0xf6, 0xe0, 0xcb, 0xfb, 0x9c, 0x72, 0xa9, 0xc5, 0xb4, 0x5a, 0x4f, + 0xdc, 0x83, 0xb8, 0x49, 0x4c, 0x29, 0xe1, 0xc4, 0x51, 0x94, 0xe7, 0xb6, 0xa1, 0x5c, 0xb3, 0x08, + 0xe9, 0x7c, 0xd7, 0xa9, 0x11, 0x4a, 0x91, 0x93, 0x85, 0x62, 0x1b, 0xed, 0x25, 0x9f, 0xbf, 0x7d, + 0x71, 0x8f, 0xab, 0x5d, 0xd8, 0x84, 0x5b, 0x91, 0x25, 0x52, 0x10, 0x35, 0x89, 0x41, 0x51, 0xe1, + 0xef, 0x2b, 0xb0, 0xe6, 0xa3, 0x2a, 0x8d, 0xfd, 0x03, 0xd4, 0x47, 0xba, 0xea, 0xc8, 0x1c, 0x55, + 0xc5, 0x69, 0xbd, 0x62, 0x17, 0xd6, 0x8b, 0x27, 0x18, 0x7f, 0x8f, 0x04, 0x43, 0x5a, 0x2f, 0x7e, + 0x08, 0xad, 0x7f, 0x84, 0x74, 0xc7, 0x6c, 0xba, 0x8c, 0xcd, 0x3e, 0xa6, 0x4c, 0x5a, 0xca, 0xc7, + 0x2f, 0x41, 0x9b, 0xec, 0x98, 0x15, 0x9b, 0xf8, 0x10, 0x53, 0x26, 0xde, 0x82, 0x15, 0x9e, 0x50, + 0x93, 0xe1, 0x01, 0x72, 0x2a, 0x9a, 0x52, 0x92, 0x7c, 0xaf, 0x81, 0x07, 0x48, 0xdc, 0x84, 0x94, + 0x07, 0x19, 0xa9, 0xfd, 0x21, 0x92, 0xae, 0xe4, 0x85, 0x62, 0x5c, 0xf1, 0xec, 0xbe, 0xb7, 0xf7, + 0xc4, 0xa7, 0x00, 0x3e, 0xcf, 0x58, 0xfa, 0xc8, 0x91, 0xed, 0x6e, 0x58, 0xb6, 0xd0, 0x90, 0x8f, + 0xca, 0x72, 0xc3, 0x52, 0x0d, 0xaa, 0x6a, 0x76, 0x09, 0x9f, 0x19, 0x1d, 0xa2, 0x2c, 0x7b, 0x0e, + 0xc7, 0xe2, 0x36, 0x24, 0x69, 0x5f, 0xa5, 0x5d, 0x4e, 0xb5, 0xec, 0x48, 0xf8, 0xf1, 0xf1, 0x49, + 0x2e, 0x55, 0x69, 0xec, 0xd7, 0xf9, 0x49, 0x63, 0xac, 0x00, 0xf5, 0xbf, 0x45, 0x02, 0x6b, 0x6d, + 0xb7, 0x27, 0x88, 0xd5, 0xf4, 0xad, 0x29, 0xd6, 0x25, 0x70, 0xcc, 0xbf, 0x3e, 0x3e, 0xc9, 0xed, + 0x5c, 0x44, 0xaa, 0x3a, 0xd6, 0x0d, 0x95, 0x0d, 0x2d, 0xa4, 0x5c, 0xf7, 0x89, 0x3d, 0xdf, 0x75, + 0xac, 0xdb, 0xb2, 0x0d, 0x8d, 0x16, 0x31, 0xda, 0x3c, 0xca, 0xa4, 0xed, 0x46, 0x49, 0xfa, 0x7b, + 0x8d, 0xb1, 0xf8, 0x39, 0xa4, 0x43, 0x10, 0x5b, 0xdb, 0x15, 0x47, 0xdb, 0x54, 0x00, 0xb2, 0xd5, + 0xbd, 0x03, 0xab, 0x01, 0xcc, 0xd5, 0x37, 0xe5, 0xe8, 0x1b, 0x58, 0xbb, 0x0a, 0x3f, 0x86, 0x1b, + 0x01, 0x30, 0xac, 0x50, 0x3a, 0x4a, 0xa1, 0x6b, 0x3e, 0x3e, 0xd8, 0x14, 0x9f, 0x0b, 0x90, 0x0f, + 0xb4, 0x9a, 0xc3, 0x68, 0xab, 0xb6, 0x7a, 0x59, 0xd5, 0x6e, 0xfa, 0x2e, 0x8e, 0x66, 0x63, 0xa8, + 0x63, 0x7d, 0xfa, 0x17, 0x90, 0x87, 0xec, 0xfc, 0xe1, 0xf6, 0xe7, 0xff, 0xdf, 0x18, 0x88, 0x55, + 0xaa, 0x3f, 0x6a, 0xb7, 0xf7, 0xc9, 0x08, 0x19, 0xaa, 0xc1, 0xea, 0x58, 0xa7, 0x91, 0xb3, 0xff, + 0x04, 0x62, 0x7c, 0xe6, 0xdf, 0x7f, 0x48, 0x62, 0x66, 0x4f, 0xbc, 0x0d, 0xab, 0x41, 0x4f, 0x37, + 0xbb, 0x2a, 0xed, 0xba, 0xff, 0x71, 0x25, 0xe5, 0x77, 0xeb, 0x53, 0x95, 0x76, 0xc5, 0x22, 0x5c, + 0x0d, 0xd5, 0xc3, 0x16, 0x90, 0x4a, 0x8b, 0xf6, 0x88, 0x2a, 0xe9, 0xa0, 0x47, 0x9d, 0x88, 0x35, + 0xb8, 0x1a, 0x6e, 0x1b, 0x47, 0xeb, 0xa5, 0xcb, 0x6a, 0x9d, 0x0e, 0x75, 0x9d, 0xdd, 0x9b, 0x0f, + 0x21, 0xe3, 0x87, 0x33, 0xeb, 0x8d, 0x4a, 0x09, 0x27, 0xb0, 0x75, 0x0f, 0x71, 0x34, 0x65, 0x4b, + 0xa7, 0x2b, 0xf3, 0x29, 0x64, 0xfe, 0x2f, 0xbb, 0x5f, 0x95, 0x3f, 0x04, 0xb8, 0x5a, 0xa5, 0x7a, + 0xa5, 0xb1, 0x7f, 0x64, 0xf0, 0x72, 0xa3, 0xc8, 0x9a, 0xcc, 0xd1, 0x32, 0x36, 0x4f, 0xcb, 0x79, + 0x0a, 0xc5, 0x3f, 0xb0, 0x42, 0xd3, 0x49, 0x66, 0x40, 0x9a, 0xcd, 0xc2, 0x4f, 0xf1, 0x37, 0x01, + 0x56, 0xab, 0x54, 0x3f, 0x32, 0xdb, 0x2a, 0x43, 0x35, 0xe7, 0x41, 0x23, 0xee, 0xc2, 0xb2, 0x3a, + 0x64, 0x5d, 0x62, 0x61, 0x36, 0x71, 0x93, 0xac, 0x48, 0x6f, 0x5e, 0x6e, 0x5d, 0xe7, 0x77, 0xcb, + 0xa3, 0x76, 0xdb, 0x42, 0x94, 0xd6, 0x99, 0x85, 0x0d, 0x5d, 0x09, 0xa0, 0xe2, 0x43, 0x48, 0xb8, + 0x4f, 0x22, 0x7e, 0x1b, 0xdd, 0x8c, 0xba, 0x54, 0x1c, 0x50, 0x65, 0xf1, 0xd5, 0x49, 0x6e, 0x41, + 0xe1, 0x26, 0x7b, 0x69, 0x3b, 0xe2, 0x80, 0xac, 0xb0, 0x01, 0xeb, 0x33, 0x71, 0x79, 0x31, 0x6f, + 0xff, 0xb9, 0x08, 0xf1, 0x2a, 0xd5, 0xc5, 0x5f, 0x04, 0x58, 0x8b, 0x78, 0xfa, 0xdc, 0x8f, 0x70, + 0x1d, 0x79, 0x13, 0x67, 0xbe, 0xba, 0xa8, 0x85, 0x17, 0x8e, 0xf8, 0x13, 0x5c, 0x9b, 0x77, 0x6f, + 0x6f, 0xbd, 0x8b, 0x70, 0x0a, 0x9e, 0xd9, 0xb9, 0x10, 0xdc, 0x77, 0x4e, 0x60, 0x75, 0xf6, 0xa7, + 0x71, 0x37, 0x9a, 0x69, 0x06, 0x9a, 0x29, 0x9f, 0x1b, 0xea, 0x3b, 0xec, 0xc0, 0xca, 0x54, 0xb3, + 0xdc, 0x8e, 0xa6, 0x08, 0xe3, 0x32, 0xf2, 0xf9, 0x70, 0xbe, 0x1f, 0x0c, 0xa9, 0xe9, 0xb9, 0xbb, + 0x13, 0x4d, 0x30, 0x05, 0xcc, 0x94, 0xce, 0x09, 0xf4, 0x5c, 0x65, 0x96, 0x7e, 0x7e, 0xfb, 0xe2, + 0x9e, 0x50, 0x39, 0x7c, 0x75, 0x9a, 0x15, 0x5e, 0x9f, 0x66, 0x85, 0x7f, 0x4e, 0xb3, 0xc2, 0xaf, + 0x67, 0xd9, 0x85, 0xd7, 0x67, 0xd9, 0x85, 0xbf, 0xce, 0xb2, 0x0b, 0x3f, 0xbc, 0xf3, 0xf7, 0x3a, + 0x0e, 0xbf, 0xd0, 0x9d, 0x09, 0x6d, 0x25, 0x9c, 0x17, 0xfa, 0x83, 0xff, 0x02, 0x00, 0x00, 0xff, + 0xff, 0x77, 0xf3, 0xc7, 0x94, 0xe1, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -741,8 +741,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // CreateBTCValidator creates a new BTC validator - CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) + // CreateFinalityProvider creates a new finality provider + CreateFinalityProvider(ctx context.Context, in *MsgCreateFinalityProvider, opts ...grpc.CallOption) (*MsgCreateFinalityProviderResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // AddCovenantSigs handles signatures from a covenant member @@ -761,9 +761,9 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } -func (c *msgClient) CreateBTCValidator(ctx context.Context, in *MsgCreateBTCValidator, opts ...grpc.CallOption) (*MsgCreateBTCValidatorResponse, error) { - out := new(MsgCreateBTCValidatorResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/CreateBTCValidator", in, out, opts...) +func (c *msgClient) CreateFinalityProvider(ctx context.Context, in *MsgCreateFinalityProvider, opts ...grpc.CallOption) (*MsgCreateFinalityProviderResponse, error) { + out := new(MsgCreateFinalityProviderResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/CreateFinalityProvider", in, out, opts...) if err != nil { return nil, err } @@ -808,8 +808,8 @@ func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opt // MsgServer is the server API for Msg service. type MsgServer interface { - // CreateBTCValidator creates a new BTC validator - CreateBTCValidator(context.Context, *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) + // CreateFinalityProvider creates a new finality provider + CreateFinalityProvider(context.Context, *MsgCreateFinalityProvider) (*MsgCreateFinalityProviderResponse, error) // CreateBTCDelegation creates a new BTC delegation CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // AddCovenantSigs handles signatures from a covenant member @@ -824,8 +824,8 @@ type MsgServer interface { type UnimplementedMsgServer struct { } -func (*UnimplementedMsgServer) CreateBTCValidator(ctx context.Context, req *MsgCreateBTCValidator) (*MsgCreateBTCValidatorResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateBTCValidator not implemented") +func (*UnimplementedMsgServer) CreateFinalityProvider(ctx context.Context, req *MsgCreateFinalityProvider) (*MsgCreateFinalityProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateFinalityProvider not implemented") } func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateBTCDelegation not implemented") @@ -844,20 +844,20 @@ func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } -func _Msg_CreateBTCValidator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgCreateBTCValidator) +func _Msg_CreateFinalityProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateFinalityProvider) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).CreateBTCValidator(ctx, in) + return srv.(MsgServer).CreateFinalityProvider(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/CreateBTCValidator", + FullMethod: "/babylon.btcstaking.v1.Msg/CreateFinalityProvider", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).CreateBTCValidator(ctx, req.(*MsgCreateBTCValidator)) + return srv.(MsgServer).CreateFinalityProvider(ctx, req.(*MsgCreateFinalityProvider)) } return interceptor(ctx, in, info, handler) } @@ -939,8 +939,8 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ HandlerType: (*MsgServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "CreateBTCValidator", - Handler: _Msg_CreateBTCValidator_Handler, + MethodName: "CreateFinalityProvider", + Handler: _Msg_CreateFinalityProvider_Handler, }, { MethodName: "CreateBTCDelegation", @@ -963,7 +963,7 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Metadata: "babylon/btcstaking/v1/tx.proto", } -func (m *MsgCreateBTCValidator) Marshal() (dAtA []byte, err error) { +func (m *MsgCreateFinalityProvider) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -973,12 +973,12 @@ func (m *MsgCreateBTCValidator) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgCreateBTCValidator) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgCreateFinalityProvider) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgCreateFinalityProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1053,7 +1053,7 @@ func (m *MsgCreateBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgCreateBTCValidatorResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgCreateFinalityProviderResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1063,12 +1063,12 @@ func (m *MsgCreateBTCValidatorResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgCreateBTCValidatorResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgCreateFinalityProviderResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgCreateBTCValidatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgCreateFinalityProviderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1183,12 +1183,12 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x30 } - if len(m.ValBtcPkList) > 0 { - for iNdEx := len(m.ValBtcPkList) - 1; iNdEx >= 0; iNdEx-- { + if len(m.FpBtcPkList) > 0 { + for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { { - size := m.ValBtcPkList[iNdEx].Size() + size := m.FpBtcPkList[iNdEx].Size() i -= size - if _, err := m.ValBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -1514,7 +1514,7 @@ func encodeVarintTx(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MsgCreateBTCValidator) Size() (n int) { +func (m *MsgCreateFinalityProvider) Size() (n int) { if m == nil { return 0 } @@ -1547,7 +1547,7 @@ func (m *MsgCreateBTCValidator) Size() (n int) { return n } -func (m *MsgCreateBTCValidatorResponse) Size() (n int) { +func (m *MsgCreateFinalityProviderResponse) Size() (n int) { if m == nil { return 0 } @@ -1578,8 +1578,8 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.BtcPk.Size() n += 1 + l + sovTx(uint64(l)) } - if len(m.ValBtcPkList) > 0 { - for _, e := range m.ValBtcPkList { + if len(m.FpBtcPkList) > 0 { + for _, e := range m.FpBtcPkList { l = e.Size() n += 1 + l + sovTx(uint64(l)) } @@ -1738,7 +1738,7 @@ func sovTx(x uint64) (n int) { func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { +func (m *MsgCreateFinalityProvider) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1761,10 +1761,10 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCValidator: wiretype end group for non-group") + return fmt.Errorf("proto: MsgCreateFinalityProvider: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCreateFinalityProvider: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1999,7 +1999,7 @@ func (m *MsgCreateBTCValidator) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { +func (m *MsgCreateFinalityProviderResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -2022,10 +2022,10 @@ func (m *MsgCreateBTCValidatorResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgCreateFinalityProviderResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateBTCValidatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgCreateFinalityProviderResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2219,7 +2219,7 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -2247,8 +2247,8 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPkList = append(m.ValBtcPkList, v) - if err := m.ValBtcPkList[len(m.ValBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPkList = append(m.FpBtcPkList, v) + if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/finality/abci.go b/x/finality/abci.go index 1faf65fe7..facf59d75 100644 --- a/x/finality/abci.go +++ b/x/finality/abci.go @@ -18,7 +18,7 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { func EndBlocker(ctx context.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) - // if the BTC staking protocol is activated, i.e., there exists a height where a BTC validator + // if the BTC staking protocol is activated, i.e., there exists a height where a finality provider // has voting power, start indexing and tallying blocks if _, err := k.BTCStakingKeeper.GetBTCStakingActivatedHeight(ctx); err == nil { // index the current block diff --git a/x/finality/client/cli/query.go b/x/finality/client/cli/query.go index abf2faa59..7198e450f 100644 --- a/x/finality/client/cli/query.go +++ b/x/finality/client/cli/query.go @@ -41,7 +41,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { func CmdVotesAtHeight() *cobra.Command { cmd := &cobra.Command{ Use: "votes-at-height [height]", - Short: "retrieve all BTC val pks who voted at requested babylon height", + Short: "retrieve all finality provider pks who voted at requested babylon height", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -69,8 +69,8 @@ func CmdVotesAtHeight() *cobra.Command { func CmdListPublicRandomness() *cobra.Command { cmd := &cobra.Command{ - Use: "list-public-randomness [val_btc_pk_hex]", - Short: "list public randomness committed by a given BTC validator", + Use: "list-public-randomness [fp_btc_pk_hex]", + Short: "list public randomness committed by a given finality provider", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := client.GetClientContextFromCmd(cmd) @@ -83,8 +83,8 @@ func CmdListPublicRandomness() *cobra.Command { } res, err := queryClient.ListPublicRandomness(cmd.Context(), &types.QueryListPublicRandomnessRequest{ - ValBtcPkHex: args[0], - Pagination: pageReq, + FpBtcPkHex: args[0], + Pagination: pageReq, }) if err != nil { return err diff --git a/x/finality/client/cli/tx.go b/x/finality/client/cli/tx.go index 7419d0fa8..bf4b4178b 100644 --- a/x/finality/client/cli/tx.go +++ b/x/finality/client/cli/tx.go @@ -34,7 +34,7 @@ func GetTxCmd() *cobra.Command { func NewCommitPubRandListCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "commit-pubrand-list [val_btc_pk] [start_height] [pub_rand1] [pub_rand2] ... [sig]", + Use: "commit-pubrand-list [fp_btc_pk] [start_height] [pub_rand1] [pub_rand2] ... [sig]", Args: cobra.MinimumNArgs(4), Short: "Commit a list of public randomness", Long: strings.TrimSpace( @@ -46,8 +46,8 @@ func NewCommitPubRandListCmd() *cobra.Command { return err } - // get validator BTC PK - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + // get finality provider BTC PK + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { return err } @@ -77,7 +77,7 @@ func NewCommitPubRandListCmd() *cobra.Command { msg := types.MsgCommitPubRandList{ Signer: clientCtx.FromAddress.String(), - ValBtcPk: valBTCPK, + FpBtcPk: fpBTCPK, StartHeight: startHeight, PubRandList: pubRandList, Sig: sig, @@ -94,7 +94,7 @@ func NewCommitPubRandListCmd() *cobra.Command { func NewAddFinalitySigCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "add-finality-sig [val_btc_pk] [block_height] [block_app_hash] [finality_sig]", + Use: "add-finality-sig [fp_btc_pk] [block_height] [block_app_hash] [finality_sig]", Args: cobra.ExactArgs(4), Short: "Add a finality signature", Long: strings.TrimSpace( @@ -106,8 +106,8 @@ func NewAddFinalitySigCmd() *cobra.Command { return err } - // get validator BTC PK - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) + // get finality provider BTC PK + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(args[0]) if err != nil { return err } @@ -132,7 +132,7 @@ func NewAddFinalitySigCmd() *cobra.Command { msg := types.MsgAddFinalitySig{ Signer: clientCtx.FromAddress.String(), - ValBtcPk: valBTCPK, + FpBtcPk: fpBTCPK, BlockHeight: blockHeight, BlockAppHash: blockLch, FinalitySig: finalitySig, diff --git a/x/finality/keeper/evidence.go b/x/finality/keeper/evidence.go index 73785a55f..0d9f610ad 100644 --- a/x/finality/keeper/evidence.go +++ b/x/finality/keeper/evidence.go @@ -11,20 +11,20 @@ import ( ) func (k Keeper) SetEvidence(ctx context.Context, evidence *types.Evidence) { - store := k.evidenceStore(ctx, evidence.ValBtcPk) + store := k.evidenceStore(ctx, evidence.FpBtcPk) store.Set(sdk.Uint64ToBigEndian(evidence.BlockHeight), k.cdc.MustMarshal(evidence)) } -func (k Keeper) HasEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) bool { - store := k.evidenceStore(ctx, valBtcPK) - return store.Has(valBtcPK.MustMarshal()) +func (k Keeper) HasEvidence(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, height uint64) bool { + store := k.evidenceStore(ctx, fpBtcPK) + return store.Has(fpBtcPK.MustMarshal()) } -func (k Keeper) GetEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { +func (k Keeper) GetEvidence(ctx context.Context, fpBtcPK *bbn.BIP340PubKey, height uint64) (*types.Evidence, error) { if uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) < height { return nil, types.ErrHeightTooHigh } - store := k.evidenceStore(ctx, valBtcPK) + store := k.evidenceStore(ctx, fpBtcPK) evidenceBytes := store.Get(sdk.Uint64ToBigEndian(height)) if len(evidenceBytes) == 0 { return nil, types.ErrEvidenceNotFound @@ -37,10 +37,10 @@ func (k Keeper) GetEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey, hei // GetFirstSlashableEvidence gets the first evidence that is slashable, // i.e., it contains all fields. // NOTE: it's possible that the CanonicalFinalitySig field is empty for -// an evidence, which happens when the BTC validator signed a fork block +// an evidence, which happens when the finality provider signed a fork block // but hasn't signed the canonical block yet. -func (k Keeper) GetFirstSlashableEvidence(ctx context.Context, valBtcPK *bbn.BIP340PubKey) *types.Evidence { - store := k.evidenceStore(ctx, valBtcPK) +func (k Keeper) GetFirstSlashableEvidence(ctx context.Context, fpBtcPK *bbn.BIP340PubKey) *types.Evidence { + store := k.evidenceStore(ctx, fpBtcPK) iter := store.Iterator(nil, nil) defer iter.Close() for ; iter.Valid(); iter.Next() { @@ -56,10 +56,10 @@ func (k Keeper) GetFirstSlashableEvidence(ctx context.Context, valBtcPK *bbn.BIP // evidenceStore returns the KVStore of the evidences // prefix: EvidenceKey -// key: (BTC validator PK || height) +// key: (finality provider PK || height) // value: Evidence -func (k Keeper) evidenceStore(ctx context.Context, valBTCPK *bbn.BIP340PubKey) prefix.Store { +func (k Keeper) evidenceStore(ctx context.Context, fpBTCPK *bbn.BIP340PubKey) prefix.Store { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) eStore := prefix.NewStore(storeAdapter, types.EvidenceKey) - return prefix.NewStore(eStore, valBTCPK.MustMarshal()) + return prefix.NewStore(eStore, fpBTCPK.MustMarshal()) } diff --git a/x/finality/keeper/grpc_query.go b/x/finality/keeper/grpc_query.go index fe74f087a..f6cd86d56 100644 --- a/x/finality/keeper/grpc_query.go +++ b/x/finality/keeper/grpc_query.go @@ -18,19 +18,19 @@ import ( var _ types.QueryServer = Keeper{} // ListPublicRandomness returns a list of public randomness committed by a given -// BTC validator +// finality provider func (k Keeper) ListPublicRandomness(ctx context.Context, req *types.QueryListPublicRandomnessRequest) (*types.QueryListPublicRandomnessResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal finality provider BTC PK hex: %v", err) } sdkCtx := sdk.UnwrapSDKContext(ctx) - store := k.pubRandStore(sdkCtx, valBTCPK) + store := k.pubRandStore(sdkCtx, fpBTCPK) pubRandMap := map[uint64]*bbn.SchnorrPubRand{} pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { height := sdk.BigEndianToUint64(key) @@ -114,7 +114,7 @@ func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeight for pkHex := range sigSet { pk, err := bbn.NewBIP340PubKeyFromHex(pkHex) if err != nil { - // failing to unmarshal validator BTC PK in KVStore is a programming error + // failing to unmarshal finality provider BTC PK in KVStore is a programming error panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } @@ -124,20 +124,20 @@ func (k Keeper) VotesAtHeight(ctx context.Context, req *types.QueryVotesAtHeight return &types.QueryVotesAtHeightResponse{BtcPks: btcPks}, nil } -// Evidence returns the first evidence that allows to extract the BTC validator's SK -// associated with the given BTC validator's PK. +// Evidence returns the first evidence that allows to extract the finality provider's SK +// associated with the given finality provider's PK. func (k Keeper) Evidence(ctx context.Context, req *types.QueryEvidenceRequest) (*types.QueryEvidenceResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - valBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.ValBtcPkHex) + fpBTCPK, err := bbn.NewBIP340PubKeyFromHex(req.FpBtcPkHex) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal validator BTC PK hex: %v", err) + return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal finality provider BTC PK hex: %v", err) } sdkCtx := sdk.UnwrapSDKContext(ctx) - evidence := k.GetFirstSlashableEvidence(sdkCtx, valBTCPK) + evidence := k.GetFirstSlashableEvidence(sdkCtx, fpBTCPK) if evidence == nil { return nil, types.ErrNoSlashableEvidence } @@ -164,13 +164,13 @@ func (k Keeper) ListEvidences(ctx context.Context, req *types.QueryListEvidences // since there is another layer of KVStore (height -> evidence) under eStore // in which height is uint64 thus takes 8 bytes strippedKey := key[:bbn.BIP340PubKeyLen] - valBTCPK, err := bbn.NewBIP340PubKey(strippedKey) + fpBTCPK, err := bbn.NewBIP340PubKey(strippedKey) if err != nil { - panic(err) // failing to unmarshal valBTCPK in KVStore can only be a programming error + panic(err) // failing to unmarshal fpBTCPK in KVStore can only be a programming error } - evidence := k.GetFirstSlashableEvidence(sdkCtx, valBTCPK) + evidence := k.GetFirstSlashableEvidence(sdkCtx, fpBTCPK) - // hit if the BTC validator has a full evidence of equivocation + // hit if the finality provider has a full evidence of equivocation if evidence != nil && evidence.BlockHeight >= req.StartHeight { if accumulate { evidences = append(evidences, evidence) diff --git a/x/finality/keeper/grpc_query_test.go b/x/finality/keeper/grpc_query_test.go index 7f492b3ed..d9f6c1430 100644 --- a/x/finality/keeper/grpc_query_test.go +++ b/x/finality/keeper/grpc_query_test.go @@ -24,20 +24,20 @@ func FuzzListPublicRandomness(f *testing.F) { ctx = sdk.UnwrapSDKContext(ctx) // add a random list of EOTS public randomness - valBTCPK, err := datagen.GenRandomBIP340PubKey(r) + fpBTCPK, err := datagen.GenRandomBIP340PubKey(r) require.NoError(t, err) startHeight := datagen.RandomInt(r, 100) numPubRand := datagen.RandomInt(r, 1000) + 2 _, prList, err := datagen.GenRandomPubRandList(r, numPubRand) require.NoError(t, err) - keeper.SetPubRandList(ctx, valBTCPK, startHeight, prList) + keeper.SetPubRandList(ctx, fpBTCPK, startHeight, prList) // perform a query to pubrand list and assert consistency // NOTE: pagination is already tested in Cosmos SDK so we don't test it here again, // instead only ensure it takes effect limit := datagen.RandomInt(r, int(numPubRand)-1) + 1 req := &types.QueryListPublicRandomnessRequest{ - ValBtcPkHex: valBTCPK.MarshalHex(), + FpBtcPkHex: fpBTCPK.MarshalHex(), Pagination: &query.PageRequest{ Limit: limit, }, @@ -184,18 +184,18 @@ func FuzzVotesAtHeight(f *testing.F) { keeper, ctx := testkeeper.FinalityKeeper(t, nil, nil) ctx = sdk.UnwrapSDKContext(ctx) - // Add random number of voted validators to the store + // Add random number of voted finality providers to the store babylonHeight := datagen.RandomInt(r, 10) + 1 - numVotedVals := datagen.RandomInt(r, 10) + 1 - votedValMap := make(map[string]bool, numVotedVals) - for i := uint64(0); i < numVotedVals; i++ { - votedValPK, err := datagen.GenRandomBIP340PubKey(r) + numVotedFps := datagen.RandomInt(r, 10) + 1 + votedFpsMap := make(map[string]bool, numVotedFps) + for i := uint64(0); i < numVotedFps; i++ { + votedFpPK, err := datagen.GenRandomBIP340PubKey(r) require.NoError(t, err) votedSig, err := bbn.NewSchnorrEOTSSig(datagen.GenRandomByteArray(r, 32)) require.NoError(t, err) - keeper.SetSig(ctx, babylonHeight, votedValPK, votedSig) + keeper.SetSig(ctx, babylonHeight, votedFpPK, votedSig) - votedValMap[votedValPK.MarshalHex()] = true + votedFpsMap[votedFpPK.MarshalHex()] = true } resp, err := keeper.VotesAtHeight(ctx, &types.QueryVotesAtHeightRequest{ @@ -203,16 +203,16 @@ func FuzzVotesAtHeight(f *testing.F) { }) require.NoError(t, err) - // Check if all voted validators are returned - valFoundMap := make(map[string]bool) + // Check if all voted finality providers are returned + fpsFoundMap := make(map[string]bool) for _, pk := range resp.BtcPks { - if _, ok := votedValMap[pk.MarshalHex()]; !ok { - t.Fatalf("rpc returned a val that was not created") + if _, ok := votedFpsMap[pk.MarshalHex()]; !ok { + t.Fatalf("rpc returned a finality provider that was not created") } - valFoundMap[pk.MarshalHex()] = true + fpsFoundMap[pk.MarshalHex()] = true } - if len(valFoundMap) != len(votedValMap) { - t.Errorf("Some vals were missed. Got %d while %d were expected", len(valFoundMap), len(votedValMap)) + if len(fpsFoundMap) != len(votedFpsMap) { + t.Errorf("Some finality providers were missed. Got %d while %d were expected", len(fpsFoundMap), len(votedFpsMap)) } }) } @@ -252,7 +252,7 @@ func FuzzQueryEvidence(f *testing.F) { } // get first slashable evidence - evidenceResp, err := keeper.Evidence(ctx, &types.QueryEvidenceRequest{ValBtcPkHex: bip340PK.MarshalHex()}) + evidenceResp, err := keeper.Evidence(ctx, &types.QueryEvidenceRequest{FpBtcPkHex: bip340PK.MarshalHex()}) if randomFirstSlashableEvidence == nil { require.Error(t, err) require.Nil(t, evidenceResp) @@ -322,8 +322,8 @@ func FuzzListEvidences(f *testing.F) { require.LessOrEqual(t, len(resp.Evidences), int(limit)) // check if pagination takes effect require.EqualValues(t, resp.Pagination.Total, numEvidences) // ensure evidences before startHeight are not included for _, actualEvidence := range resp.Evidences { - require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].CanonicalAppHash, actualEvidence.CanonicalAppHash) - require.Equal(t, evidences[actualEvidence.ValBtcPk.MarshalHex()].ForkAppHash, actualEvidence.ForkAppHash) + require.Equal(t, evidences[actualEvidence.FpBtcPk.MarshalHex()].CanonicalAppHash, actualEvidence.CanonicalAppHash) + require.Equal(t, evidences[actualEvidence.FpBtcPk.MarshalHex()].ForkAppHash, actualEvidence.ForkAppHash) } }) } diff --git a/x/finality/keeper/msg_server.go b/x/finality/keeper/msg_server.go index 3f6495da8..991b244bc 100644 --- a/x/finality/keeper/msg_server.go +++ b/x/finality/keeper/msg_server.go @@ -47,15 +47,15 @@ func (ms msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdatePara func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinalitySig) (*types.MsgAddFinalitySigResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // ensure the BTC validator exists - btcVal, err := ms.BTCStakingKeeper.GetBTCValidator(ctx, req.ValBtcPk.MustMarshal()) + // ensure the finality provider exists + fp, err := ms.BTCStakingKeeper.GetFinalityProvider(ctx, req.FpBtcPk.MustMarshal()) if err != nil { return nil, err } - // ensure the BTC validator is not slashed at this time point - // NOTE: it's possible that the BTC validator equivocates for height h, and the signature is processed at + // ensure the finality provider is not slashed at this time point + // NOTE: it's possible that the finality provider equivocates for height h, and the signature is processed at // height h' > h. In this case: - // - Babylon should reject any new signature from this BTC validator, since it's known to be adversarial + // - Babylon should reject any new signature from this finality provider, since it's known to be adversarial // - Babylon should set its voting power since height h'+1 to be zero, due to the same reason // - Babylon should NOT set its voting power between [h, h'] to be zero, since // - Babylon BTC staking ensures safety upon 2f+1 votes, *even if* f of them are adversarial. This is @@ -66,43 +66,43 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal // modify voting power table in the history, some finality decisions might be contradicting to the // signature set and voting power table. // - To fix the above issue, Babylon has to allow finalise and unfinalise blocks. However, this means - // Babylon will lose safety under an adaptive adversary corrupting even 1 validator. It can simply - // corrupt a new validator and equivocate a historical block over and over again, making a previous block + // Babylon will lose safety under an adaptive adversary corrupting even 1 finality provider. It can simply + // corrupt a new finality provider and equivocate a historical block over and over again, making a previous block // unfinalisable forever - if btcVal.IsSlashed() { - return nil, bstypes.ErrBTCValAlreadySlashed + if fp.IsSlashed() { + return nil, bstypes.ErrFpAlreadySlashed } - // ensure the BTC validator has voting power at this height - if req.ValBtcPk == nil { - return nil, types.ErrInvalidFinalitySig.Wrap("empty validator BTC PK") + // ensure the finality providerhas voting power at this height + if req.FpBtcPk == nil { + return nil, types.ErrInvalidFinalitySig.Wrap("empty finality provider BTC PK") } - valPK := req.ValBtcPk - if ms.BTCStakingKeeper.GetVotingPower(ctx, valPK.MustMarshal(), req.BlockHeight) == 0 { - return nil, types.ErrInvalidFinalitySig.Wrapf("the BTC validator %v does not have voting power at height %d", valPK.MustMarshal(), req.BlockHeight) + fpPK := req.FpBtcPk + if ms.BTCStakingKeeper.GetVotingPower(ctx, fpPK.MustMarshal(), req.BlockHeight) == 0 { + return nil, types.ErrInvalidFinalitySig.Wrapf("the finality provider %v does not have voting power at height %d", fpPK.MustMarshal(), req.BlockHeight) } - // ensure the BTC validator has not casted the same vote yet + // ensure the finality provider has not cast the same vote yet if req.FinalitySig == nil { return nil, types.ErrInvalidFinalitySig.Wrap("empty finality signature") } - existingSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) + existingSig, err := ms.GetSig(ctx, req.BlockHeight, fpPK) if err == nil && existingSig.Equals(req.FinalitySig) { return nil, types.ErrDuplicatedFinalitySig } - // ensure the BTC validator has committed public randomness - pubRand, err := ms.GetPubRand(ctx, valPK, req.BlockHeight) + // ensure the finality provider has committed public randomness + pubRand, err := ms.GetPubRand(ctx, fpPK, req.BlockHeight) if err != nil { return nil, types.ErrPubRandNotFound } // verify EOTS signature w.r.t. public randomness - valBTCPK, err := valPK.ToBTCPK() + fpBTCPK, err := fpPK.ToBTCPK() if err != nil { return nil, err } - if err := eots.Verify(valBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar()); err != nil { + if err := eots.Verify(fpBTCPK, pubRand.ToFieldVal(), req.MsgToSign(), req.FinalitySig.ToModNScalar()); err != nil { return nil, types.ErrInvalidFinalitySig.Wrapf("the EOTS signature is invalid: %v", err) } @@ -112,11 +112,11 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal return nil, err } if !bytes.Equal(indexedBlock.AppHash, req.BlockAppHash) { - // the BTC validator votes for a fork! + // the finality provider votes for a fork! // construct evidence evidence := &types.Evidence{ - ValBtcPk: req.ValBtcPk, + FpBtcPk: req.FpBtcPk, BlockHeight: req.BlockHeight, PubRand: pubRand, CanonicalAppHash: indexedBlock.AppHash, @@ -125,14 +125,14 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal ForkFinalitySig: req.FinalitySig, } - // if this BTC validator has also signed canonical block, slash it - canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, valPK) + // if this finality provider has also signed canonical block, slash it + canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, fpPK) if err == nil { //set canonial sig evidence.CanonicalFinalitySig = canonicalSig - // slash this BTC validator, including setting its voting power to + // slash this finality provider, including setting its voting power to // zero, extracting its BTC SK, and emit an event - ms.slashBTCValidator(ctx, req.ValBtcPk, evidence) + ms.slashFinalityProvider(ctx, req.FpBtcPk, evidence) } // save evidence @@ -144,16 +144,16 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal } // this signature is good, add vote to DB - ms.SetSig(ctx, req.BlockHeight, valPK, req.FinalitySig) + ms.SetSig(ctx, req.BlockHeight, fpPK, req.FinalitySig) - // if this BTC validator has signed the canonical block before, + // if this finality provider has signed the canonical block before, // slash it via extracting its secret key, and emit an event - if ms.HasEvidence(ctx, req.ValBtcPk, req.BlockHeight) { - // the BTC validator has voted for a fork before! - // If this evidence is at the same height as this signature, slash this BTC validator + if ms.HasEvidence(ctx, req.FpBtcPk, req.BlockHeight) { + // the finality provider has voted for a fork before! + // If this evidence is at the same height as this signature, slash this finality provider // get evidence - evidence, err := ms.GetEvidence(ctx, req.ValBtcPk, req.BlockHeight) + evidence, err := ms.GetEvidence(ctx, req.FpBtcPk, req.BlockHeight) if err != nil { panic(fmt.Errorf("failed to get evidence despite HasEvidence returns true")) } @@ -162,9 +162,9 @@ func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinal evidence.CanonicalFinalitySig = req.FinalitySig ms.SetEvidence(ctx, evidence) - // slash this BTC validator, including setting its voting power to + // slash this finality provider, including setting its voting power to // zero, extracting its BTC SK, and emit an event - ms.slashBTCValidator(ctx, req.ValBtcPk, evidence) + ms.slashFinalityProvider(ctx, req.FpBtcPk, evidence) } return &types.MsgAddFinalitySigResponse{}, nil @@ -181,24 +181,24 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi return nil, types.ErrTooFewPubRand.Wrapf("required minimum: %d, actual: %d", minPubRand, givenPubRand) } - // ensure the BTC validator is registered - if req.ValBtcPk == nil { - return nil, types.ErrInvalidPubRand.Wrap("empty validator public key") + // ensure the finality provider is registered + if req.FpBtcPk == nil { + return nil, types.ErrInvalidPubRand.Wrap("empty finality provider public key") } - valBTCPKBytes := req.ValBtcPk.MustMarshal() - if !ms.BTCStakingKeeper.HasBTCValidator(ctx, valBTCPKBytes) { - return nil, bstypes.ErrBTCValNotFound.Wrapf("the validator with BTC PK %v is not registered", valBTCPKBytes) + fpBTCPKBytes := req.FpBtcPk.MustMarshal() + if !ms.BTCStakingKeeper.HasFinalityProvider(ctx, fpBTCPKBytes) { + return nil, bstypes.ErrFpNotFound.Wrapf("the finality provider with BTC PK %v is not registered", fpBTCPKBytes) } - // this BTC validator has not commit any public randomness, + // this finality provider has not commit any public randomness, // commit the given public randomness list and return - if ms.IsFirstPubRand(ctx, req.ValBtcPk) { - ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + if ms.IsFirstPubRand(ctx, req.FpBtcPk) { + ms.SetPubRandList(ctx, req.FpBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil } // ensure height and req.StartHeight do not overlap, i.e., height < req.StartHeight - height, _, err := ms.GetLastPubRand(ctx, req.ValBtcPk) + height, _, err := ms.GetLastPubRand(ctx, req.FpBtcPk) if err != nil { return nil, err } @@ -212,22 +212,22 @@ func (ms msgServer) CommitPubRandList(goCtx context.Context, req *types.MsgCommi } // all good, commit the given public randomness list - ms.SetPubRandList(ctx, req.ValBtcPk, req.StartHeight, req.PubRandList) + ms.SetPubRandList(ctx, req.FpBtcPk, req.StartHeight, req.PubRandList) return &types.MsgCommitPubRandListResponse{}, nil } -// slashBTCValidator slashes a BTC validator with the given evidence +// slashFinalityProvider slashes a finality provider with the given evidence // including setting its voting power to zero, extracting its BTC SK, // and emit an event -func (k Keeper) slashBTCValidator(ctx context.Context, valBtcPk *bbn.BIP340PubKey, evidence *types.Evidence) { - // slash this BTC validator, i.e., set its voting power to zero - if err := k.BTCStakingKeeper.SlashBTCValidator(ctx, valBtcPk.MustMarshal()); err != nil { - panic(fmt.Errorf("failed to slash BTC validator: %v", err)) +func (k Keeper) slashFinalityProvider(ctx context.Context, fpBtcPk *bbn.BIP340PubKey, evidence *types.Evidence) { + // slash this finality provider, i.e., set its voting power to zero + if err := k.BTCStakingKeeper.SlashFinalityProvider(ctx, fpBtcPk.MustMarshal()); err != nil { + panic(fmt.Errorf("failed to slash finality provider: %v", err)) } // emit slashing event - eventSlashing := types.NewEventSlashedBTCValidator(evidence) + eventSlashing := types.NewEventSlashedFinalityProvider(evidence) if err := sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(eventSlashing); err != nil { - panic(fmt.Errorf("failed to emit EventSlashedBTCValidator event: %w", err)) + panic(fmt.Errorf("failed to emit EventSlashedFinalityProvider event: %w", err)) } } diff --git a/x/finality/keeper/msg_server_test.go b/x/finality/keeper/msg_server_test.go index c174552b5..a2420df4a 100644 --- a/x/finality/keeper/msg_server_test.go +++ b/x/finality/keeper/msg_server_test.go @@ -39,22 +39,22 @@ func FuzzCommitPubRandList(f *testing.F) { fKeeper, ctx := keepertest.FinalityKeeper(t, bsKeeper, nil) ms := keeper.NewMsgServerImpl(*fKeeper) - // create a random BTC validator + // create a random finality provider btcSK, btcPK, err := datagen.GenRandomBTCKeyPair(r) require.NoError(t, err) - valBTCPK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) - valBTCPKBytes := valBTCPK.MustMarshal() + fpBTCPK := bbn.NewBIP340PubKeyFromBTCPK(btcPK) + fpBTCPKBytes := fpBTCPK.MustMarshal() - // Case 1: fail if the BTC validator is not registered - bsKeeper.EXPECT().HasBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(false).Times(1) + // Case 1: fail if the finality provider is not registered + bsKeeper.EXPECT().HasFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(false).Times(1) startHeight := datagen.RandomInt(r, 10) numPubRand := uint64(200) _, msg, err := datagen.GenRandomMsgCommitPubRandList(r, btcSK, startHeight, numPubRand) require.NoError(t, err) _, err = ms.CommitPubRandList(ctx, msg) require.Error(t, err) - // register the BTC validator - bsKeeper.EXPECT().HasBTCValidator(gomock.Any(), gomock.Eq(valBTCPKBytes)).Return(true).AnyTimes() + // register the finality provider + bsKeeper.EXPECT().HasFinalityProvider(gomock.Any(), gomock.Eq(fpBTCPKBytes)).Return(true).AnyTimes() // Case 2: commit a list of 2/3 votes, finalise it k.finalizeBlock(ctx, ib, voterBTCPKs) } else { @@ -58,24 +58,24 @@ func (k Keeper) TallyBlocks(ctx context.Context) { // thus, we need to break here break } - } else if valSet == nil && !ib.Finalized { - // does not have validators, non-finalised: not finalisable, + } else if fpSet == nil && !ib.Finalized { + // does not have finality providers, non-finalised: not finalisable, // increment the next height to finalise and continue k.setNextHeightToFinalize(ctx, ib.Height+1) continue - } else if valSet != nil && ib.Finalized { - // has validators and the block has finalised + } else if fpSet != nil && ib.Finalized { + // has finality providers and the block has finalised // this can only be a programming error panic(fmt.Errorf("block %d is finalized, but last finalized height in DB does not reach here", ib.Height)) - } else if valSet == nil && ib.Finalized { - // does not have validators, finalised: impossible to happen, panic - panic(fmt.Errorf("block %d is finalized, but does not have a validator set", ib.Height)) + } else if fpSet == nil && ib.Finalized { + // does not have finality providers, finalised: impossible to happen, panic + panic(fmt.Errorf("block %d is finalized, but does not have a finality provider set", ib.Height)) } } } // finalizeBlock sets a block to be finalised in KVStore and distributes rewards to -// BTC validators and delegations +// finality providers and delegations func (k Keeper) finalizeBlock(ctx context.Context, block *types.IndexedBlock, voterBTCPKs map[string]struct{}) { // set block to be finalised in KVStore block.Finalized = true @@ -88,19 +88,19 @@ func (k Keeper) finalizeBlock(ctx context.Context, block *types.IndexedBlock, vo // failing to get a reward distribution cache before distributing reward is a programming error panic(err) } - // filter out voted BTC validators - rdc.FilterVotedBTCVals(voterBTCPKs) - // reward voted BTC validators + // filter out voted finality providers + rdc.FilterVotedFinalityProviders(voterBTCPKs) + // reward voted finality providers k.IncentiveKeeper.RewardBTCStaking(ctx, block.Height, rdc) // remove reward distribution cache afterwards k.BTCStakingKeeper.RemoveRewardDistCache(ctx, block.Height) } -// tally checks whether a block with the given validator set and votes reaches a quorum or not -func tally(valSet map[string]uint64, voterBTCPKs map[string]struct{}) bool { +// tally checks whether a block with the given finality provider set and votes reaches a quorum or not +func tally(fpSet map[string]uint64, voterBTCPKs map[string]struct{}) bool { totalPower := uint64(0) votedPower := uint64(0) - for pkStr, power := range valSet { + for pkStr, power := range fpSet { totalPower += power if _, ok := voterBTCPKs[pkStr]; ok { votedPower += power diff --git a/x/finality/keeper/tallying_test.go b/x/finality/keeper/tallying_test.go index 2719d6d86..aa4af547a 100644 --- a/x/finality/keeper/tallying_test.go +++ b/x/finality/keeper/tallying_test.go @@ -32,7 +32,7 @@ func FuzzTallying_PanicCases(f *testing.F) { bsKeeper.EXPECT().GetBTCStakingActivatedHeight(gomock.Any()).Return(uint64(0), bstypes.ErrBTCStakingNotActivated).Times(1) require.Panics(t, func() { fKeeper.TallyBlocks(ctx) }) - // Case 2: expect to panic if finalised block with nil validator set + // Case 2: expect to panic if finalised block with nil finality provider fKeeper.SetBlock(ctx, &types.IndexedBlock{ Height: 1, AppHash: datagen.GenRandomByteArray(r, 32), @@ -146,11 +146,11 @@ func FuzzTallying_FinalizingSomeBlocks(f *testing.F) { } func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakingKeeper, fKeeper *keeper.Keeper, height uint64) error { - // 4 BTC vals - valSet := map[string]uint64{} + // 4 finality providers + fpSet := map[string]uint64{} // 3 votes for i := 0; i < 3; i++ { - votedValPK, err := datagen.GenRandomBIP340PubKey(r) + votedFpPK, err := datagen.GenRandomBIP340PubKey(r) if err != nil { return err } @@ -158,20 +158,20 @@ func giveQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakin if err != nil { return err } - fKeeper.SetSig(ctx, height, votedValPK, votedSig) - // add val - valSet[votedValPK.MarshalHex()] = 1 + fKeeper.SetSig(ctx, height, votedFpPK, votedSig) + // add finality provider + fpSet[votedFpPK.MarshalHex()] = 1 } - // the rest val that does not vote - valSet[hex.EncodeToString(datagen.GenRandomByteArray(r, 32))] = 1 - bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).Times(1) + // the rest of the finality providers do not vote + fpSet[hex.EncodeToString(datagen.GenRandomByteArray(r, 32))] = 1 + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(fpSet).Times(1) return nil } func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStakingKeeper, fKeeper *keeper.Keeper, height uint64) error { // 1 vote - votedValPK, err := datagen.GenRandomBIP340PubKey(r) + votedFpPK, err := datagen.GenRandomBIP340PubKey(r) if err != nil { return err } @@ -179,15 +179,15 @@ func giveNoQCToHeight(r *rand.Rand, ctx sdk.Context, bsKeeper *types.MockBTCStak if err != nil { return err } - fKeeper.SetSig(ctx, height, votedValPK, votedSig) - // 4 BTC vals - valSet := map[string]uint64{ - votedValPK.MarshalHex(): 1, + fKeeper.SetSig(ctx, height, votedFpPK, votedSig) + // 4 finality providers + fpSet := map[string]uint64{ + votedFpPK.MarshalHex(): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, hex.EncodeToString(datagen.GenRandomByteArray(r, 32)): 1, } - bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(valSet).MaxTimes(1) + bsKeeper.EXPECT().GetVotingPowerTable(gomock.Any(), gomock.Eq(height)).Return(fpSet).MaxTimes(1) return nil } diff --git a/x/finality/keeper/votes.go b/x/finality/keeper/votes.go index fb0eb1c90..44bd6352b 100644 --- a/x/finality/keeper/votes.go +++ b/x/finality/keeper/votes.go @@ -12,22 +12,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) SetSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { +func (k Keeper) SetSig(ctx context.Context, height uint64, fpBtcPK *bbn.BIP340PubKey, sig *bbn.SchnorrEOTSSig) { store := k.voteStore(ctx, height) - store.Set(valBtcPK.MustMarshal(), sig.MustMarshal()) + store.Set(fpBtcPK.MustMarshal(), sig.MustMarshal()) } -func (k Keeper) HasSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey) bool { +func (k Keeper) HasSig(ctx context.Context, height uint64, fpBtcPK *bbn.BIP340PubKey) bool { store := k.voteStore(ctx, height) - return store.Has(valBtcPK.MustMarshal()) + return store.Has(fpBtcPK.MustMarshal()) } -func (k Keeper) GetSig(ctx context.Context, height uint64, valBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { +func (k Keeper) GetSig(ctx context.Context, height uint64, fpBtcPK *bbn.BIP340PubKey) (*bbn.SchnorrEOTSSig, error) { if uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) < height { return nil, types.ErrHeightTooHigh } store := k.voteStore(ctx, height) - sigBytes := store.Get(valBtcPK.MustMarshal()) + sigBytes := store.Get(fpBtcPK.MustMarshal()) if len(sigBytes) == 0 { return nil, types.ErrVoteNotFound } @@ -51,9 +51,9 @@ func (k Keeper) GetSigSet(ctx context.Context, height uint64) map[string]*bbn.Sc sigs := map[string]*bbn.SchnorrEOTSSig{} for ; iter.Valid(); iter.Next() { - valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + fpBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) if err != nil { - // failing to unmarshal validator BTC PK in KVStore is a programming error + // failing to unmarshal finality provider's BTC PK in KVStore is a programming error panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } sig, err := bbn.NewSchnorrEOTSSig(iter.Value()) @@ -61,7 +61,7 @@ func (k Keeper) GetSigSet(ctx context.Context, height uint64) map[string]*bbn.Sc // failing to unmarshal EOTS sig in KVStore is a programming error panic(fmt.Errorf("failed to unmarshal EOTS signature: %w", err)) } - sigs[valBTCPK.MarshalHex()] = sig + sigs[fpBTCPK.MarshalHex()] = sig } return sigs } @@ -80,19 +80,19 @@ func (k Keeper) GetVoters(ctx context.Context, height uint64) map[string]struct{ voterBTCPKs := map[string]struct{}{} for ; iter.Valid(); iter.Next() { // accumulate voterBTCPKs - valBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) + fpBTCPK, err := bbn.NewBIP340PubKey(iter.Key()) if err != nil { - // failing to unmarshal validator BTC PK in KVStore is a programming error + // failing to unmarshal finality provider's BTC PK in KVStore is a programming error panic(fmt.Errorf("%w: %w", bbn.ErrUnmarshal, err)) } - voterBTCPKs[valBTCPK.MarshalHex()] = struct{}{} + voterBTCPKs[fpBTCPK.MarshalHex()] = struct{}{} } return voterBTCPKs } // voteStore returns the KVStore of the votes // prefix: VoteKey -// key: (block height || BTC validator PK) +// key: (block height || finality provider PK) // value: EOTS sig func (k Keeper) voteStore(ctx context.Context, height uint64) prefix.Store { storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) diff --git a/x/finality/types/errors.go b/x/finality/types/errors.go index 1b3e86b4f..1485f09b5 100644 --- a/x/finality/types/errors.go +++ b/x/finality/types/errors.go @@ -10,7 +10,7 @@ var ( ErrVoteNotFound = errorsmod.Register(ModuleName, 1101, "vote is not found") ErrHeightTooHigh = errorsmod.Register(ModuleName, 1102, "the chain has not reached the given height yet") ErrPubRandNotFound = errorsmod.Register(ModuleName, 1103, "public randomness is not found") - ErrNoPubRandYet = errorsmod.Register(ModuleName, 1104, "the BTC validator has not committed any public randomness yet") + ErrNoPubRandYet = errorsmod.Register(ModuleName, 1104, "the finality provider has not committed any public randomness yet") ErrTooFewPubRand = errorsmod.Register(ModuleName, 1105, "the request contains too few public randomness") ErrInvalidPubRand = errorsmod.Register(ModuleName, 1106, "the public randomness list is invalid") ErrEvidenceNotFound = errorsmod.Register(ModuleName, 1107, "evidence is not found") diff --git a/x/finality/types/events.go b/x/finality/types/events.go index 2b7ac88da..6d452f5d0 100644 --- a/x/finality/types/events.go +++ b/x/finality/types/events.go @@ -1,7 +1,7 @@ package types -func NewEventSlashedBTCValidator(evidence *Evidence) *EventSlashedBTCValidator { - return &EventSlashedBTCValidator{ +func NewEventSlashedFinalityProvider(evidence *Evidence) *EventSlashedFinalityProvider { + return &EventSlashedFinalityProvider{ Evidence: evidence, } } diff --git a/x/finality/types/events.pb.go b/x/finality/types/events.pb.go index 68c30c37b..2a19e5f20 100644 --- a/x/finality/types/events.pb.go +++ b/x/finality/types/events.pb.go @@ -22,25 +22,25 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// EventSlashedBTCValidator is the event emitted when a BTC validator is slashed +// EventSlashedFinalityProvider is the event emitted when a finality provider is slashed // due to signing two conflicting blocks -type EventSlashedBTCValidator struct { - // evidence is the evidence that the BTC validator double signs +type EventSlashedFinalityProvider struct { + // evidence is the evidence that the finality provider double signs Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` } -func (m *EventSlashedBTCValidator) Reset() { *m = EventSlashedBTCValidator{} } -func (m *EventSlashedBTCValidator) String() string { return proto.CompactTextString(m) } -func (*EventSlashedBTCValidator) ProtoMessage() {} -func (*EventSlashedBTCValidator) Descriptor() ([]byte, []int) { +func (m *EventSlashedFinalityProvider) Reset() { *m = EventSlashedFinalityProvider{} } +func (m *EventSlashedFinalityProvider) String() string { return proto.CompactTextString(m) } +func (*EventSlashedFinalityProvider) ProtoMessage() {} +func (*EventSlashedFinalityProvider) Descriptor() ([]byte, []int) { return fileDescriptor_c34c03aae5e3e6bf, []int{0} } -func (m *EventSlashedBTCValidator) XXX_Unmarshal(b []byte) error { +func (m *EventSlashedFinalityProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *EventSlashedBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *EventSlashedFinalityProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_EventSlashedBTCValidator.Marshal(b, m, deterministic) + return xxx_messageInfo_EventSlashedFinalityProvider.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -50,19 +50,19 @@ func (m *EventSlashedBTCValidator) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *EventSlashedBTCValidator) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventSlashedBTCValidator.Merge(m, src) +func (m *EventSlashedFinalityProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSlashedFinalityProvider.Merge(m, src) } -func (m *EventSlashedBTCValidator) XXX_Size() int { +func (m *EventSlashedFinalityProvider) XXX_Size() int { return m.Size() } -func (m *EventSlashedBTCValidator) XXX_DiscardUnknown() { - xxx_messageInfo_EventSlashedBTCValidator.DiscardUnknown(m) +func (m *EventSlashedFinalityProvider) XXX_DiscardUnknown() { + xxx_messageInfo_EventSlashedFinalityProvider.DiscardUnknown(m) } -var xxx_messageInfo_EventSlashedBTCValidator proto.InternalMessageInfo +var xxx_messageInfo_EventSlashedFinalityProvider proto.InternalMessageInfo -func (m *EventSlashedBTCValidator) GetEvidence() *Evidence { +func (m *EventSlashedFinalityProvider) GetEvidence() *Evidence { if m != nil { return m.Evidence } @@ -70,29 +70,29 @@ func (m *EventSlashedBTCValidator) GetEvidence() *Evidence { } func init() { - proto.RegisterType((*EventSlashedBTCValidator)(nil), "babylon.finality.v1.EventSlashedBTCValidator") + proto.RegisterType((*EventSlashedFinalityProvider)(nil), "babylon.finality.v1.EventSlashedFinalityProvider") } func init() { proto.RegisterFile("babylon/finality/v1/events.proto", fileDescriptor_c34c03aae5e3e6bf) } var fileDescriptor_c34c03aae5e3e6bf = []byte{ - // 197 bytes of a gzipped FileDescriptorProto + // 193 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0xcb, 0xcc, 0x4b, 0xcc, 0xc9, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xaa, 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x33, 0x94, 0x52, 0xc2, 0xa6, 0x0d, 0xae, 0x00, 0xac, 0x51, 0x29, - 0x94, 0x4b, 0xc2, 0x15, 0x64, 0x50, 0x70, 0x4e, 0x62, 0x71, 0x46, 0x6a, 0x8a, 0x53, 0x88, 0x73, - 0x58, 0x62, 0x4e, 0x66, 0x4a, 0x62, 0x49, 0x7e, 0x91, 0x90, 0x25, 0x17, 0x47, 0x6a, 0x59, 0x66, - 0x4a, 0x6a, 0x5e, 0x72, 0xaa, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xac, 0x1e, 0x16, 0x7b, - 0xf4, 0x5c, 0xa1, 0x8a, 0x82, 0xe0, 0xca, 0x9d, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, - 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, - 0x58, 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, - 0x6a, 0x58, 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0x70, 0x6e, 0x49, 0x65, 0x41, - 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0xa5, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, 0x4e, 0x21, - 0xed, 0x06, 0x01, 0x00, 0x00, + 0x92, 0x4b, 0xc6, 0x15, 0x64, 0x50, 0x70, 0x4e, 0x62, 0x71, 0x46, 0x6a, 0x8a, 0x1b, 0x54, 0x36, + 0xa0, 0x28, 0xbf, 0x2c, 0x33, 0x25, 0xb5, 0x48, 0xc8, 0x92, 0x8b, 0x23, 0x15, 0xc4, 0xca, 0x4b, + 0x4e, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x36, 0x92, 0xd5, 0xc3, 0x62, 0x97, 0x9e, 0x2b, 0x54, + 0x51, 0x10, 0x5c, 0xb9, 0x93, 0xd7, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, + 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, + 0x19, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0x0d, 0x4b, 0xce, + 0x48, 0xcc, 0xcc, 0x83, 0x71, 0xf4, 0x2b, 0x10, 0x4e, 0x2e, 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, + 0x03, 0xbb, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xa3, 0x4e, 0xb5, 0x88, 0x0a, 0x01, 0x00, + 0x00, } -func (m *EventSlashedBTCValidator) Marshal() (dAtA []byte, err error) { +func (m *EventSlashedFinalityProvider) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -102,12 +102,12 @@ func (m *EventSlashedBTCValidator) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *EventSlashedBTCValidator) MarshalTo(dAtA []byte) (int, error) { +func (m *EventSlashedFinalityProvider) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *EventSlashedBTCValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *EventSlashedFinalityProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -138,7 +138,7 @@ func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *EventSlashedBTCValidator) Size() (n int) { +func (m *EventSlashedFinalityProvider) Size() (n int) { if m == nil { return 0 } @@ -157,7 +157,7 @@ func sovEvents(x uint64) (n int) { func sozEvents(x uint64) (n int) { return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { +func (m *EventSlashedFinalityProvider) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -180,10 +180,10 @@ func (m *EventSlashedBTCValidator) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventSlashedBTCValidator: wiretype end group for non-group") + return fmt.Errorf("proto: EventSlashedFinalityProvider: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventSlashedBTCValidator: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventSlashedFinalityProvider: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/x/finality/types/expected_keepers.go b/x/finality/types/expected_keepers.go index 0bab833f8..77b02370f 100644 --- a/x/finality/types/expected_keepers.go +++ b/x/finality/types/expected_keepers.go @@ -6,10 +6,10 @@ import ( ) type BTCStakingKeeper interface { - GetBTCValidator(ctx context.Context, valBTCPK []byte) (*bstypes.BTCValidator, error) - HasBTCValidator(ctx context.Context, valBTCPK []byte) bool - SlashBTCValidator(ctx context.Context, valBTCPK []byte) error - GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 + GetFinalityProvider(ctx context.Context, fpBTCPK []byte) (*bstypes.FinalityProvider, error) + HasFinalityProvider(ctx context.Context, fpBTCPK []byte) bool + SlashFinalityProvider(ctx context.Context, fpBTCPK []byte) error + GetVotingPower(ctx context.Context, fpBTCPK []byte, height uint64) uint64 GetVotingPowerTable(ctx context.Context, height uint64) map[string]uint64 GetBTCStakingActivatedHeight(ctx context.Context) (uint64, error) RecordRewardDistCache(ctx context.Context) diff --git a/x/finality/types/finality.go b/x/finality/types/finality.go index 5ccb82231..5e1380d1d 100644 --- a/x/finality/types/finality.go +++ b/x/finality/types/finality.go @@ -39,8 +39,8 @@ func (e *Evidence) forkMsgToSign() []byte { } func (e *Evidence) ValidateBasic() error { - if e.ValBtcPk == nil { - return fmt.Errorf("empty ValBtcPk") + if e.FpBtcPk == nil { + return fmt.Errorf("empty FpBtcPk") } if e.PubRand == nil { return fmt.Errorf("empty PubRand") @@ -52,7 +52,7 @@ func (e *Evidence) ValidateBasic() error { return fmt.Errorf("malformed ForkAppHash") } if e.ForkFinalitySig == nil { - return fmt.Errorf("empty ValBtcPk") + return fmt.Errorf("empty ForkFinalitySig") } return nil } @@ -72,7 +72,7 @@ func (e *Evidence) ExtractBTCSK() (*btcec.PrivateKey, error) { if !e.IsSlashable() { return nil, fmt.Errorf("the evidence lacks some fields so does not allow extracting BTC SK") } - btcPK, err := e.ValBtcPk.ToBTCPK() + btcPK, err := e.FpBtcPk.ToBTCPK() if err != nil { return nil, err } diff --git a/x/finality/types/finality.pb.go b/x/finality/types/finality.pb.go index c510d0d8d..e03aeb453 100644 --- a/x/finality/types/finality.pb.go +++ b/x/finality/types/finality.pb.go @@ -31,7 +31,7 @@ type IndexedBlock struct { // app_hash is the AppHash of the block AppHash []byte `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` // finalized indicates whether the IndexedBlock is finalised by 2/3 - // BTC validators or not + // finality providers or not Finalized bool `protobuf:"varint,3,opt,name=finalized,proto3" json:"finalized,omitempty"` } @@ -89,14 +89,14 @@ func (m *IndexedBlock) GetFinalized() bool { return false } -// Evidence is the evidence that a BTC validator has signed finality +// Evidence is the evidence that a finality provider has signed finality // signatures with correct public randomness on two conflicting Babylon headers type Evidence struct { - // val_btc_pk is the BTC Pk of the validator that casts this vote - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // fp_btc_pk is the BTC Pk of the finality provider that casts this vote + FpBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=fp_btc_pk,json=fpBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk,omitempty"` // block_height is the height of the conflicting blocks BlockHeight uint64 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - // pub_rand is the public randomness the BTC validator has committed to + // pub_rand is the public randomness the finality provider has committed to PubRand *github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,3,opt,name=pub_rand,json=pubRand,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand,omitempty"` // canonical_app_hash is the AppHash of the canonical block CanonicalAppHash []byte `protobuf:"bytes,4,opt,name=canonical_app_hash,json=canonicalAppHash,proto3" json:"canonical_app_hash,omitempty"` @@ -105,7 +105,7 @@ type Evidence struct { // canonical_finality_sig is the finality signature to the canonical block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator + // `r` is the public randomness that is already committed by the finality provider CanonicalFinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,6,opt,name=canonical_finality_sig,json=canonicalFinalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"canonical_finality_sig,omitempty"` // fork_finality_sig is the finality signature to the fork block // where finality signature is an EOTS signature @@ -176,34 +176,34 @@ func init() { } var fileDescriptor_ca5b87e52e3e6d02 = []byte{ - // 425 bytes of a gzipped FileDescriptorProto + // 424 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x92, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0x87, 0x9b, 0x51, 0xda, 0xe0, 0x05, 0x01, 0x66, 0x9a, 0x0a, 0x42, 0x59, 0xe9, 0xa9, 0x07, - 0xd4, 0x6c, 0x6c, 0x42, 0x5c, 0x89, 0x34, 0xb4, 0xc1, 0x81, 0x2a, 0xd9, 0x89, 0x8b, 0x65, 0x3b, - 0x5e, 0x6c, 0x35, 0xd8, 0x56, 0xfe, 0x69, 0xe1, 0xc4, 0x47, 0xe0, 0x63, 0x71, 0xdc, 0x11, 0xed, - 0x30, 0xa1, 0xf6, 0x8b, 0xa0, 0x38, 0x69, 0x02, 0x27, 0x10, 0xdc, 0x5e, 0xbf, 0xfe, 0xe9, 0x7d, - 0x1e, 0xbd, 0x36, 0x98, 0x11, 0x4c, 0xaa, 0x44, 0x49, 0xef, 0x52, 0x48, 0x9c, 0x88, 0xbc, 0xf2, - 0xca, 0xa3, 0xae, 0x5e, 0xe8, 0x54, 0xe5, 0x0a, 0x3e, 0x6e, 0x33, 0x8b, 0xae, 0x5f, 0x1e, 0x3d, - 0xdd, 0x8b, 0x55, 0xac, 0xcc, 0xbd, 0x57, 0x57, 0x4d, 0x74, 0x86, 0x80, 0x73, 0x2e, 0x23, 0x76, - 0xc5, 0x22, 0x3f, 0x51, 0x74, 0x05, 0xf7, 0xc1, 0x88, 0x33, 0x11, 0xf3, 0x7c, 0x62, 0x4d, 0xad, - 0xf9, 0x30, 0x68, 0x4f, 0xf0, 0x09, 0xb0, 0xb1, 0xd6, 0x88, 0xe3, 0x8c, 0x4f, 0x76, 0xa6, 0xd6, - 0xdc, 0x09, 0xc6, 0x58, 0xeb, 0x33, 0x9c, 0x71, 0xf8, 0x0c, 0xdc, 0x6b, 0x38, 0x9f, 0x59, 0x34, - 0xb9, 0x33, 0xb5, 0xe6, 0x76, 0xd0, 0x37, 0x66, 0x5f, 0x86, 0xc0, 0x3e, 0x2d, 0x45, 0xc4, 0x24, - 0x65, 0xf0, 0x02, 0x80, 0x12, 0x27, 0x88, 0xe4, 0x14, 0xe9, 0x95, 0x21, 0x38, 0xfe, 0xab, 0x9b, - 0xdb, 0x83, 0x97, 0xb1, 0xc8, 0x79, 0x41, 0x16, 0x54, 0x7d, 0xf2, 0x5a, 0x77, 0xca, 0xb1, 0x90, - 0xdb, 0x83, 0x97, 0x57, 0x9a, 0x65, 0x0b, 0xff, 0x7c, 0x79, 0x7c, 0x72, 0xb8, 0x2c, 0xc8, 0x7b, - 0x56, 0x05, 0x76, 0x89, 0x13, 0x3f, 0xa7, 0xcb, 0x15, 0x7c, 0x0e, 0x1c, 0x52, 0xcb, 0xa3, 0xd6, - 0x7c, 0xc7, 0x98, 0xef, 0x9a, 0xde, 0x59, 0xa3, 0x1f, 0x02, 0x5b, 0x17, 0x04, 0xa5, 0x58, 0x36, - 0x8a, 0x8e, 0xff, 0xfa, 0xe6, 0xf6, 0xe0, 0xe4, 0xef, 0xb0, 0x21, 0xe5, 0x52, 0xa5, 0xe9, 0xb2, - 0x20, 0x01, 0x96, 0x51, 0x30, 0xd6, 0x4d, 0x01, 0x5f, 0x00, 0x48, 0xb1, 0x54, 0x52, 0x50, 0x9c, - 0xa0, 0x6e, 0x3b, 0x43, 0xb3, 0x9d, 0x87, 0xdd, 0xcd, 0x9b, 0x76, 0x4d, 0x33, 0x70, 0xff, 0x52, - 0xa5, 0xab, 0x3e, 0x78, 0xd7, 0x04, 0x77, 0xeb, 0xe6, 0x36, 0x23, 0xc1, 0x7e, 0x3f, 0x71, 0xfb, - 0x78, 0x28, 0x13, 0xf1, 0x64, 0xf4, 0x8f, 0xd2, 0xa7, 0x1f, 0x2e, 0xc2, 0x50, 0xc4, 0xc1, 0x5e, - 0x37, 0xf7, 0x6d, 0x3b, 0x36, 0x14, 0x31, 0x8c, 0xc0, 0x23, 0xe3, 0xf4, 0x1b, 0x6a, 0xfc, 0x9f, - 0xa8, 0x07, 0xf5, 0xc8, 0x5f, 0x28, 0xfe, 0xbb, 0x6f, 0x6b, 0xd7, 0xba, 0x5e, 0xbb, 0xd6, 0x8f, - 0xb5, 0x6b, 0x7d, 0xdd, 0xb8, 0x83, 0xeb, 0x8d, 0x3b, 0xf8, 0xbe, 0x71, 0x07, 0x1f, 0x0f, 0xff, - 0x04, 0xb8, 0xea, 0xbf, 0xb9, 0x61, 0x91, 0x91, 0xf9, 0xb6, 0xc7, 0x3f, 0x03, 0x00, 0x00, 0xff, - 0xff, 0x34, 0x61, 0xea, 0x06, 0x07, 0x03, 0x00, 0x00, + 0x18, 0x87, 0x9b, 0x6d, 0xb4, 0x9d, 0x17, 0x04, 0x98, 0x69, 0x2a, 0x08, 0x65, 0xa5, 0xa7, 0x1e, + 0x50, 0xb3, 0xb1, 0x09, 0x71, 0x25, 0xd2, 0xd0, 0x06, 0x07, 0x2a, 0x87, 0x13, 0x17, 0xcb, 0x76, + 0xdc, 0xd8, 0x6a, 0xb1, 0xad, 0xfc, 0xa9, 0x16, 0x3e, 0x05, 0x1f, 0x8b, 0xe3, 0x8e, 0x68, 0x87, + 0x0a, 0xb5, 0xdf, 0x03, 0xa1, 0x38, 0x69, 0x02, 0x27, 0x10, 0xbb, 0xbd, 0x7e, 0xfd, 0xd3, 0xfb, + 0x3c, 0x7a, 0x6d, 0x30, 0xa2, 0x84, 0x16, 0x0b, 0xad, 0xfc, 0x99, 0x54, 0x64, 0x21, 0xb3, 0xc2, + 0x5f, 0x9e, 0x36, 0xf5, 0xc4, 0x24, 0x3a, 0xd3, 0xf0, 0x71, 0x9d, 0x99, 0x34, 0xfd, 0xe5, 0xe9, + 0xd3, 0xc3, 0x58, 0xc7, 0xda, 0xde, 0xfb, 0x65, 0x55, 0x45, 0x47, 0x18, 0xb8, 0x57, 0x2a, 0xe2, + 0xd7, 0x3c, 0x0a, 0x16, 0x9a, 0xcd, 0xe1, 0x11, 0xe8, 0x0a, 0x2e, 0x63, 0x91, 0x0d, 0x9c, 0xa1, + 0x33, 0xde, 0x43, 0xf5, 0x09, 0x3e, 0x01, 0x7d, 0x62, 0x0c, 0x16, 0x24, 0x15, 0x83, 0x9d, 0xa1, + 0x33, 0x76, 0x51, 0x8f, 0x18, 0x73, 0x49, 0x52, 0x01, 0x9f, 0x81, 0xfd, 0x8a, 0xf3, 0x85, 0x47, + 0x83, 0xdd, 0xa1, 0x33, 0xee, 0xa3, 0xb6, 0x31, 0xfa, 0xb9, 0x0b, 0xfa, 0x17, 0x4b, 0x19, 0x71, + 0xc5, 0x38, 0x44, 0x60, 0x7f, 0x66, 0x30, 0xcd, 0x18, 0x36, 0x73, 0x0b, 0x70, 0x83, 0x57, 0xb7, + 0xab, 0xe3, 0x97, 0xb1, 0xcc, 0x44, 0x4e, 0x27, 0x4c, 0x7f, 0xf6, 0x6b, 0x75, 0x26, 0x88, 0x54, + 0xdb, 0x83, 0x9f, 0x15, 0x86, 0xa7, 0x93, 0xe0, 0x6a, 0x7a, 0x76, 0x7e, 0x32, 0xcd, 0xe9, 0x7b, + 0x5e, 0xa0, 0xde, 0xcc, 0x04, 0x19, 0x9b, 0xce, 0xe1, 0x73, 0xe0, 0xd2, 0x52, 0x1d, 0xd7, 0xde, + 0x3b, 0xd6, 0xfb, 0xc0, 0xf6, 0x2e, 0x2b, 0xf9, 0x10, 0xf4, 0x4d, 0x4e, 0x71, 0x42, 0x54, 0x25, + 0xe8, 0x06, 0xaf, 0x6f, 0x57, 0xc7, 0xe7, 0xff, 0x46, 0x0d, 0x99, 0x50, 0x3a, 0x49, 0xa6, 0x39, + 0x45, 0x44, 0x45, 0xa8, 0x67, 0xaa, 0x02, 0xbe, 0x00, 0x90, 0x11, 0xa5, 0x95, 0x64, 0x64, 0x81, + 0x9b, 0xdd, 0xec, 0xd9, 0xdd, 0x3c, 0x6c, 0x6e, 0xde, 0xd4, 0x4b, 0x1a, 0x81, 0xfb, 0x33, 0x9d, + 0xcc, 0xdb, 0xe0, 0x3d, 0x1b, 0x3c, 0x28, 0x9b, 0xdb, 0x8c, 0x02, 0x47, 0xed, 0xc4, 0xed, 0xd3, + 0xe1, 0x54, 0xc6, 0x83, 0xee, 0x7f, 0x4a, 0x5f, 0x7c, 0xf8, 0x18, 0x86, 0x32, 0x46, 0x87, 0xcd, + 0xdc, 0xb7, 0xf5, 0xd8, 0x50, 0xc6, 0x30, 0x02, 0x8f, 0xac, 0xd3, 0x1f, 0xa8, 0xde, 0x1d, 0x51, + 0x0f, 0xca, 0x91, 0xbf, 0x51, 0x82, 0x77, 0xdf, 0xd6, 0x9e, 0x73, 0xb3, 0xf6, 0x9c, 0x1f, 0x6b, + 0xcf, 0xf9, 0xba, 0xf1, 0x3a, 0x37, 0x1b, 0xaf, 0xf3, 0x7d, 0xe3, 0x75, 0x3e, 0x9d, 0xfc, 0x0d, + 0x70, 0xdd, 0x7e, 0x72, 0xcb, 0xa2, 0x5d, 0xfb, 0x69, 0xcf, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, + 0xac, 0x30, 0x4b, 0x66, 0x05, 0x03, 0x00, 0x00, } func (m *IndexedBlock) Marshal() (dAtA []byte, err error) { @@ -326,11 +326,11 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x10 } - if m.ValBtcPk != nil { + if m.FpBtcPk != nil { { - size := m.ValBtcPk.Size() + size := m.FpBtcPk.Size() i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintFinality(dAtA, i, uint64(size)) @@ -377,8 +377,8 @@ func (m *Evidence) Size() (n int) { } var l int _ = l - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() + if m.FpBtcPk != nil { + l = m.FpBtcPk.Size() n += 1 + l + sovFinality(uint64(l)) } if m.BlockHeight != 0 { @@ -567,7 +567,7 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -595,8 +595,8 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPk = &v + if err := m.FpBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/finality/types/mocked_keepers.go b/x/finality/types/mocked_keepers.go index 8c9149713..0a3dd80b1 100644 --- a/x/finality/types/mocked_keepers.go +++ b/x/finality/types/mocked_keepers.go @@ -50,19 +50,19 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetBTCStakingActivatedHeight(ctx int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCStakingActivatedHeight", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCStakingActivatedHeight), ctx) } -// GetBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) GetBTCValidator(ctx context.Context, valBTCPK []byte) (*types.BTCValidator, error) { +// GetFinalityProvider mocks base method. +func (m *MockBTCStakingKeeper) GetFinalityProvider(ctx context.Context, fpBTCPK []byte) (*types.FinalityProvider, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBTCValidator", ctx, valBTCPK) - ret0, _ := ret[0].(*types.BTCValidator) + ret := m.ctrl.Call(m, "GetFinalityProvider", ctx, fpBTCPK) + ret0, _ := ret[0].(*types.FinalityProvider) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetBTCValidator indicates an expected call of GetBTCValidator. -func (mr *MockBTCStakingKeeperMockRecorder) GetBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { +// GetFinalityProvider indicates an expected call of GetFinalityProvider. +func (mr *MockBTCStakingKeeperMockRecorder) GetFinalityProvider(ctx, fpBTCPK interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetBTCValidator), ctx, valBTCPK) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalityProvider", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetFinalityProvider), ctx, fpBTCPK) } // GetRewardDistCache mocks base method. @@ -81,17 +81,17 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetRewardDistCache(ctx, height inter } // GetVotingPower mocks base method. -func (m *MockBTCStakingKeeper) GetVotingPower(ctx context.Context, valBTCPK []byte, height uint64) uint64 { +func (m *MockBTCStakingKeeper) GetVotingPower(ctx context.Context, fpBTCPK []byte, height uint64) uint64 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVotingPower", ctx, valBTCPK, height) + ret := m.ctrl.Call(m, "GetVotingPower", ctx, fpBTCPK, height) ret0, _ := ret[0].(uint64) return ret0 } // GetVotingPower indicates an expected call of GetVotingPower. -func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, valBTCPK, height interface{}) *gomock.Call { +func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPower(ctx, fpBTCPK, height interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPower", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPower), ctx, valBTCPK, height) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPower", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPower), ctx, fpBTCPK, height) } // GetVotingPowerTable mocks base method. @@ -108,18 +108,18 @@ func (mr *MockBTCStakingKeeperMockRecorder) GetVotingPowerTable(ctx, height inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVotingPowerTable", reflect.TypeOf((*MockBTCStakingKeeper)(nil).GetVotingPowerTable), ctx, height) } -// HasBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) HasBTCValidator(ctx context.Context, valBTCPK []byte) bool { +// HasFinalityProvider mocks base method. +func (m *MockBTCStakingKeeper) HasFinalityProvider(ctx context.Context, fpBTCPK []byte) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasBTCValidator", ctx, valBTCPK) + ret := m.ctrl.Call(m, "HasFinalityProvider", ctx, fpBTCPK) ret0, _ := ret[0].(bool) return ret0 } -// HasBTCValidator indicates an expected call of HasBTCValidator. -func (mr *MockBTCStakingKeeperMockRecorder) HasBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { +// HasFinalityProvider indicates an expected call of HasFinalityProvider. +func (mr *MockBTCStakingKeeperMockRecorder) HasFinalityProvider(ctx, fpBTCPK interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).HasBTCValidator), ctx, valBTCPK) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasFinalityProvider", reflect.TypeOf((*MockBTCStakingKeeper)(nil).HasFinalityProvider), ctx, fpBTCPK) } // RecordRewardDistCache mocks base method. @@ -146,18 +146,18 @@ func (mr *MockBTCStakingKeeperMockRecorder) RemoveRewardDistCache(ctx, height in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRewardDistCache", reflect.TypeOf((*MockBTCStakingKeeper)(nil).RemoveRewardDistCache), ctx, height) } -// SlashBTCValidator mocks base method. -func (m *MockBTCStakingKeeper) SlashBTCValidator(ctx context.Context, valBTCPK []byte) error { +// SlashFinalityProvider mocks base method. +func (m *MockBTCStakingKeeper) SlashFinalityProvider(ctx context.Context, fpBTCPK []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SlashBTCValidator", ctx, valBTCPK) + ret := m.ctrl.Call(m, "SlashFinalityProvider", ctx, fpBTCPK) ret0, _ := ret[0].(error) return ret0 } -// SlashBTCValidator indicates an expected call of SlashBTCValidator. -func (mr *MockBTCStakingKeeperMockRecorder) SlashBTCValidator(ctx, valBTCPK interface{}) *gomock.Call { +// SlashFinalityProvider indicates an expected call of SlashFinalityProvider. +func (mr *MockBTCStakingKeeperMockRecorder) SlashFinalityProvider(ctx, fpBTCPK interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashBTCValidator", reflect.TypeOf((*MockBTCStakingKeeper)(nil).SlashBTCValidator), ctx, valBTCPK) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashFinalityProvider", reflect.TypeOf((*MockBTCStakingKeeper)(nil).SlashFinalityProvider), ctx, fpBTCPK) } // MockIncentiveKeeper is a mock of IncentiveKeeper interface. diff --git a/x/finality/types/msg.go b/x/finality/types/msg.go index f9ce2bfe9..af941426f 100644 --- a/x/finality/types/msg.go +++ b/x/finality/types/msg.go @@ -20,7 +20,7 @@ var ( func NewMsgAddFinalitySig(signer string, sk *btcec.PrivateKey, sr *eots.PrivateRand, blockHeight uint64, blockHash []byte) (*MsgAddFinalitySig, error) { msg := &MsgAddFinalitySig{ Signer: signer, - ValBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), + FpBtcPk: bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()), BlockHeight: blockHeight, BlockAppHash: blockHash, } @@ -40,7 +40,7 @@ func (m *MsgAddFinalitySig) MsgToSign() []byte { func (m *MsgAddFinalitySig) VerifyEOTSSig(pubRand *bbn.SchnorrPubRand) error { msgToSign := m.MsgToSign() - pk, err := m.ValBtcPk.ToBTCPK() + pk, err := m.FpBtcPk.ToBTCPK() if err != nil { return err } @@ -68,7 +68,7 @@ func (m *MsgCommitPubRandList) VerifySig() error { if err != nil { return err } - pk, err := m.ValBtcPk.ToBTCPK() + pk, err := m.FpBtcPk.ToBTCPK() if err != nil { return err } diff --git a/x/finality/types/query.pb.go b/x/finality/types/query.pb.go index 3aedb4057..a4b12e2ea 100644 --- a/x/finality/types/query.pb.go +++ b/x/finality/types/query.pb.go @@ -149,8 +149,8 @@ func (m *QueryParamsResponse) GetParams() Params { // QueryListPublicRandomnessRequest is the request type for the // Query/ListPublicRandomness RPC method. type QueryListPublicRandomnessRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the BTC validator - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK of the finality provider + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` // pagination defines an optional pagination for the request. Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` } @@ -188,9 +188,9 @@ func (m *QueryListPublicRandomnessRequest) XXX_DiscardUnknown() { var xxx_messageInfo_QueryListPublicRandomnessRequest proto.InternalMessageInfo -func (m *QueryListPublicRandomnessRequest) GetValBtcPkHex() string { +func (m *QueryListPublicRandomnessRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } @@ -206,7 +206,7 @@ func (m *QueryListPublicRandomnessRequest) GetPagination() *query.PageRequest { // Query/ListPublicRandomness RPC method. type QueryListPublicRandomnessResponse struct { // pub_rand_map is the map where the key is the height and the value - // is the public randomness at this height for the given BTC validator + // is the public randomness at this height for the given finality provider PubRandMap map[uint64]*github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,1,rep,name=pub_rand_map,json=pubRandMap,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand_map,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // pagination defines the pagination in the response. Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` @@ -461,7 +461,7 @@ func (m *QueryListBlocksResponse) GetPagination() *query.PageResponse { // QueryVotesAtHeightRequest is the request type for the // Query/VotesAtHeight RPC method. type QueryVotesAtHeightRequest struct { - // height defines at which height to query the BTC validators. + // height defines at which height to query the finality providers. Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` } @@ -508,7 +508,7 @@ func (m *QueryVotesAtHeightRequest) GetHeight() uint64 { // QueryVotesAtHeightResponse is the response type for the // Query/VotesAtHeight RPC method. type QueryVotesAtHeightResponse struct { - // btc_pk is the Bitcoin secp256k1 PK of BTC validators who have signed the block at given height. + // btc_pk is the Bitcoin secp256k1 PK of finality providers who have signed the block at given height. // the PK follows encoding in BIP-340 spec BtcPks []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,rep,name=btc_pks,json=btcPks,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pks,omitempty"` } @@ -549,9 +549,9 @@ var xxx_messageInfo_QueryVotesAtHeightResponse proto.InternalMessageInfo // QueryEvidenceRequest is the request type for the // Query/Evidence RPC method. type QueryEvidenceRequest struct { - // val_btc_pk_hex is the hex str of Bitcoin secp256k1 PK - // (in BIP340 format) of the BTC validator - ValBtcPkHex string `protobuf:"bytes,1,opt,name=val_btc_pk_hex,json=valBtcPkHex,proto3" json:"val_btc_pk_hex,omitempty"` + // fp_btc_pk_hex is the hex str of Bitcoin secp256k1 PK + // (in BIP340 format) of the finality provider + FpBtcPkHex string `protobuf:"bytes,1,opt,name=fp_btc_pk_hex,json=fpBtcPkHex,proto3" json:"fp_btc_pk_hex,omitempty"` } func (m *QueryEvidenceRequest) Reset() { *m = QueryEvidenceRequest{} } @@ -587,9 +587,9 @@ func (m *QueryEvidenceRequest) XXX_DiscardUnknown() { var xxx_messageInfo_QueryEvidenceRequest proto.InternalMessageInfo -func (m *QueryEvidenceRequest) GetValBtcPkHex() string { +func (m *QueryEvidenceRequest) GetFpBtcPkHex() string { if m != nil { - return m.ValBtcPkHex + return m.FpBtcPkHex } return "" } @@ -775,72 +775,72 @@ func init() { func init() { proto.RegisterFile("babylon/finality/v1/query.proto", fileDescriptor_32bddab77af6fdae) } var fileDescriptor_32bddab77af6fdae = []byte{ - // 1031 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x4f, 0x1b, 0x47, - 0x14, 0xf6, 0x98, 0xe0, 0xc0, 0x03, 0x52, 0x32, 0x71, 0x53, 0xea, 0x34, 0xc6, 0x2c, 0x2d, 0x50, - 0x88, 0x76, 0x83, 0x49, 0xd3, 0xb4, 0x51, 0x84, 0xb0, 0x0a, 0x0d, 0x4d, 0x20, 0xee, 0x22, 0x55, - 0x6a, 0x2e, 0xd6, 0xac, 0x3d, 0xb5, 0x57, 0xac, 0x77, 0x36, 0xde, 0xb1, 0x85, 0x15, 0x45, 0xaa, - 0x7a, 0xc8, 0xa9, 0x52, 0x2b, 0xf5, 0xd2, 0x4b, 0x0e, 0xcd, 0xa9, 0x7f, 0x4a, 0x8e, 0x48, 0xbd, - 0x54, 0x1c, 0x50, 0x05, 0xed, 0xff, 0x51, 0xed, 0xcc, 0xec, 0x1a, 0x93, 0x35, 0x76, 0x10, 0x37, - 0xef, 0xcc, 0xf7, 0xde, 0xfb, 0xde, 0xf7, 0x7e, 0x8c, 0x61, 0xda, 0x22, 0x56, 0xdb, 0x61, 0xae, - 0xf1, 0x83, 0xed, 0x12, 0xc7, 0xe6, 0x6d, 0xa3, 0xb5, 0x6c, 0x3c, 0x6b, 0xd2, 0x46, 0x5b, 0xf7, - 0x1a, 0x8c, 0x33, 0x7c, 0x4d, 0x01, 0xf4, 0x10, 0xa0, 0xb7, 0x96, 0x33, 0xe9, 0x2a, 0xab, 0x32, - 0x71, 0x6f, 0x04, 0xbf, 0x24, 0x34, 0xf3, 0x51, 0x95, 0xb1, 0xaa, 0x43, 0x0d, 0xe2, 0xd9, 0x06, - 0x71, 0x5d, 0xc6, 0x09, 0xb7, 0x99, 0xeb, 0xab, 0xdb, 0xc5, 0x32, 0xf3, 0xeb, 0xcc, 0x37, 0x2c, - 0xe2, 0x53, 0x19, 0xc1, 0x68, 0x2d, 0x5b, 0x94, 0x93, 0x65, 0xc3, 0x23, 0x55, 0xdb, 0x15, 0x60, - 0x85, 0xcd, 0xc5, 0xb1, 0xf2, 0x48, 0x83, 0xd4, 0x43, 0x6f, 0x5a, 0x1c, 0x22, 0xa2, 0x28, 0x30, - 0x5a, 0x1a, 0xf0, 0xb7, 0x41, 0x9c, 0xa2, 0x30, 0x34, 0xe9, 0xb3, 0x26, 0xf5, 0xb9, 0x56, 0x84, - 0x6b, 0x5d, 0xa7, 0xbe, 0xc7, 0x5c, 0x9f, 0xe2, 0x2f, 0x20, 0x25, 0x03, 0x4c, 0xa1, 0x1c, 0x5a, - 0x18, 0xcb, 0xdf, 0xd0, 0x63, 0x12, 0xd7, 0xa5, 0x51, 0xe1, 0xd2, 0x9b, 0xc3, 0xe9, 0x84, 0xa9, - 0x0c, 0xb4, 0x5f, 0x10, 0xe4, 0x84, 0xcb, 0xc7, 0xb6, 0xcf, 0x8b, 0x4d, 0xcb, 0xb1, 0xcb, 0x26, - 0x71, 0x2b, 0xac, 0xee, 0x52, 0x3f, 0x0c, 0x8b, 0x67, 0xe1, 0x4a, 0x8b, 0x38, 0x25, 0x8b, 0x97, - 0x4b, 0xde, 0x6e, 0xa9, 0x46, 0xf7, 0x44, 0x9c, 0x51, 0x73, 0xac, 0x45, 0x9c, 0x02, 0x2f, 0x17, - 0x77, 0x1f, 0xd2, 0x3d, 0xbc, 0x01, 0xd0, 0xd1, 0x62, 0x2a, 0x29, 0x88, 0xcc, 0xe9, 0x52, 0x38, - 0x3d, 0x10, 0x4e, 0x97, 0xa5, 0x51, 0xc2, 0xe9, 0x45, 0x52, 0xa5, 0x2a, 0x80, 0x79, 0xc2, 0x52, - 0xdb, 0x4f, 0xc2, 0xcc, 0x19, 0x8c, 0x54, 0xca, 0xaf, 0x11, 0x8c, 0x7b, 0x4d, 0xab, 0xd4, 0x20, - 0x6e, 0xa5, 0x54, 0x27, 0xde, 0x14, 0xca, 0x0d, 0x2d, 0x8c, 0xe5, 0x37, 0x62, 0x33, 0xef, 0xeb, - 0x4e, 0x2f, 0x36, 0xad, 0xe0, 0x74, 0x8b, 0x78, 0xeb, 0x2e, 0x6f, 0xb4, 0x0b, 0xf7, 0x0e, 0x0e, - 0xa7, 0xef, 0x54, 0x6d, 0x5e, 0x6b, 0x5a, 0x7a, 0x99, 0xd5, 0x0d, 0xe5, 0xb5, 0x5c, 0x23, 0xb6, - 0x1b, 0x7e, 0x18, 0xbc, 0xed, 0x51, 0x5f, 0xdf, 0x29, 0xd7, 0x5c, 0xd6, 0x68, 0x28, 0x0f, 0x26, - 0x78, 0x91, 0x2b, 0xfc, 0x75, 0x8c, 0x24, 0xf3, 0x7d, 0x25, 0x91, 0x94, 0x4e, 0x6a, 0x92, 0x79, - 0x00, 0xef, 0x9d, 0x62, 0x88, 0x27, 0x61, 0x68, 0x97, 0xb6, 0x45, 0x21, 0x2e, 0x99, 0xc1, 0x4f, - 0x9c, 0x86, 0xe1, 0x16, 0x71, 0x9a, 0x54, 0x04, 0x1a, 0x37, 0xe5, 0xc7, 0x97, 0xc9, 0x7b, 0x48, - 0x5b, 0x82, 0xab, 0x42, 0x82, 0x82, 0xc3, 0xca, 0xbb, 0x61, 0x51, 0xaf, 0x43, 0xaa, 0x46, 0xed, - 0x6a, 0x8d, 0x2b, 0x1f, 0xea, 0x4b, 0xdb, 0x52, 0x9d, 0xa7, 0xc0, 0x4a, 0xef, 0xcf, 0x61, 0xd8, - 0x0a, 0x0e, 0x54, 0x87, 0xcd, 0xc4, 0xea, 0xbc, 0xe9, 0x56, 0xe8, 0x1e, 0xad, 0x48, 0x4b, 0x89, - 0xd7, 0xfe, 0x40, 0x70, 0x3d, 0xd2, 0x5f, 0xdc, 0x44, 0x6d, 0xb5, 0x0a, 0x29, 0x9f, 0x13, 0xde, - 0x94, 0x6d, 0x7b, 0x25, 0x3f, 0xdf, 0xb3, 0x78, 0xb6, 0x72, 0xba, 0x23, 0xe0, 0xa6, 0x32, 0xbb, - 0xb0, 0x96, 0x7b, 0x85, 0xe0, 0x83, 0xb7, 0x38, 0x76, 0x66, 0x4b, 0x24, 0xe2, 0xab, 0x0e, 0x1b, - 0x20, 0x73, 0x65, 0x70, 0x61, 0xe5, 0xd7, 0x56, 0xe0, 0x43, 0x41, 0xef, 0x3b, 0xc6, 0xa9, 0xbf, - 0xc6, 0x1f, 0x8a, 0x42, 0xf5, 0xab, 0x63, 0x1d, 0x32, 0x71, 0x46, 0x2a, 0xad, 0x27, 0x70, 0x59, - 0x8e, 0xb3, 0xcc, 0x6b, 0xbc, 0x70, 0xf7, 0xe0, 0x70, 0x3a, 0x3f, 0x58, 0xc7, 0x17, 0x36, 0x8b, - 0x2b, 0x77, 0x6e, 0x17, 0x9b, 0xd6, 0x23, 0xda, 0x36, 0x53, 0x56, 0xb0, 0x00, 0x7c, 0xed, 0x3e, - 0xa4, 0x45, 0xb8, 0xf5, 0x96, 0x5d, 0xa1, 0x6e, 0x99, 0xbe, 0xcb, 0xee, 0xd0, 0x4c, 0x78, 0xff, - 0x94, 0x71, 0xa4, 0xfe, 0x08, 0x55, 0x67, 0xaa, 0xf3, 0x6e, 0xc6, 0xea, 0x1f, 0x19, 0x46, 0x70, - 0xed, 0x25, 0x52, 0xaa, 0x05, 0x45, 0x0d, 0xef, 0xa3, 0xde, 0x9b, 0x81, 0x71, 0x9f, 0x93, 0x06, - 0x2f, 0x75, 0x69, 0x37, 0x26, 0xce, 0xa4, 0x54, 0x17, 0xd6, 0x5d, 0xaf, 0x91, 0xaa, 0xc4, 0x29, - 0x22, 0x2a, 0xc5, 0xfb, 0x30, 0x1a, 0x72, 0x0e, 0x7b, 0xac, 0x4f, 0x8e, 0x1d, 0xfc, 0x85, 0xb5, - 0xd8, 0xe2, 0xaa, 0x9c, 0xfa, 0xee, 0x41, 0xc3, 0x57, 0x61, 0x62, 0xfb, 0xc9, 0x76, 0x69, 0x63, - 0x73, 0x7b, 0xed, 0xf1, 0xe6, 0xd3, 0xf5, 0xaf, 0x26, 0x13, 0x78, 0x02, 0x46, 0x3b, 0x9f, 0x08, - 0x5f, 0x86, 0xa1, 0xb5, 0xed, 0xef, 0x27, 0x93, 0xf9, 0xff, 0x46, 0x60, 0x58, 0x64, 0x89, 0x7f, - 0x44, 0x90, 0x92, 0x6f, 0x0d, 0xee, 0x3d, 0xd1, 0xdd, 0x0f, 0x5b, 0x66, 0xa1, 0x3f, 0x50, 0x92, - 0xd6, 0x66, 0x7f, 0xfa, 0xeb, 0xdf, 0xdf, 0x92, 0x37, 0xf1, 0x0d, 0xa3, 0xf7, 0x3b, 0x8b, 0x0f, - 0x10, 0xa4, 0xe3, 0xf6, 0x3d, 0xfe, 0xec, 0x5d, 0xdf, 0x07, 0x49, 0xef, 0xee, 0xf9, 0x9e, 0x15, - 0x6d, 0x47, 0x90, 0xdd, 0xc2, 0x8f, 0x62, 0xc9, 0x06, 0x33, 0xd1, 0x22, 0x8e, 0x5d, 0x21, 0x9c, - 0x35, 0x7c, 0xe3, 0x79, 0xf7, 0x9c, 0xbc, 0x30, 0x3c, 0xe1, 0x56, 0x3c, 0x71, 0xd2, 0x6f, 0xc9, - 0xb1, 0x7d, 0x8e, 0x5f, 0x22, 0x18, 0x16, 0x45, 0xc2, 0x73, 0xbd, 0x69, 0x9d, 0x5c, 0xf5, 0x99, - 0xf9, 0xbe, 0x38, 0xc5, 0xf7, 0x96, 0xe0, 0x3b, 0x87, 0x3f, 0x8e, 0xe7, 0x2b, 0xd6, 0x9a, 0xf1, - 0x5c, 0x8e, 0xcc, 0x0b, 0xfc, 0x33, 0x02, 0xe8, 0x6c, 0x4c, 0xbc, 0x74, 0xb6, 0x48, 0x5d, 0xbb, - 0x3f, 0x73, 0x6b, 0x30, 0xf0, 0x40, 0x45, 0x57, 0xeb, 0xf6, 0x15, 0x82, 0x89, 0xae, 0x65, 0x87, - 0xf5, 0xde, 0x41, 0xe2, 0x56, 0x69, 0xc6, 0x18, 0x18, 0xaf, 0x78, 0x2d, 0x09, 0x5e, 0x9f, 0xe0, - 0xd9, 0x58, 0x5e, 0xad, 0xc0, 0xa6, 0x23, 0xd7, 0x9f, 0x08, 0x46, 0xc2, 0x19, 0xc6, 0x9f, 0xf6, - 0x0e, 0x75, 0x6a, 0x83, 0x66, 0x16, 0x07, 0x81, 0x2a, 0x42, 0xeb, 0x82, 0xd0, 0x2a, 0x7e, 0x70, - 0xae, 0x86, 0x0b, 0xf7, 0x0a, 0xfe, 0x1d, 0xc1, 0x44, 0xd7, 0xb6, 0x3a, 0x4b, 0xca, 0xb8, 0xfd, - 0x7a, 0x96, 0x94, 0xb1, 0x6b, 0x50, 0x9b, 0x13, 0xcc, 0x73, 0x38, 0x1b, 0xcb, 0x3c, 0xda, 0x78, - 0x85, 0x6f, 0xde, 0x1c, 0x65, 0xd1, 0xfe, 0x51, 0x16, 0xfd, 0x73, 0x94, 0x45, 0xbf, 0x1e, 0x67, - 0x13, 0xfb, 0xc7, 0xd9, 0xc4, 0xdf, 0xc7, 0xd9, 0xc4, 0xd3, 0xdb, 0xfd, 0x5e, 0xaf, 0xbd, 0x8e, - 0x4b, 0xf1, 0x90, 0x59, 0x29, 0xf1, 0x5f, 0x7b, 0xe5, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x78, - 0x80, 0xbb, 0xc1, 0x49, 0x0c, 0x00, 0x00, + // 1028 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0x38, 0x8d, 0x9b, 0xbc, 0xc4, 0x90, 0x4e, 0x4d, 0x09, 0x2e, 0x75, 0x9c, 0x2d, 0x24, + 0x21, 0xa9, 0x76, 0x1b, 0xa7, 0x94, 0x16, 0x84, 0x4a, 0x2c, 0x12, 0x12, 0x68, 0x5d, 0xb3, 0x95, + 0x2a, 0xd1, 0x8b, 0x35, 0x6b, 0x4f, 0xec, 0x55, 0xec, 0x9d, 0xed, 0xee, 0xac, 0x15, 0xab, 0xaa, + 0x84, 0x38, 0xf4, 0x04, 0x12, 0x12, 0x17, 0x2e, 0x3d, 0xd0, 0x2b, 0xff, 0x48, 0x8f, 0x91, 0xb8, + 0xa0, 0x4a, 0x44, 0x28, 0xe1, 0xc6, 0x3f, 0x81, 0x76, 0x66, 0xd6, 0x8e, 0xc3, 0xfa, 0x47, 0xab, + 0xdc, 0xbc, 0x33, 0xef, 0xc7, 0xf7, 0xbe, 0xf7, 0xe6, 0x7b, 0x86, 0x79, 0x8b, 0x58, 0xed, 0x06, + 0x73, 0x8c, 0x5d, 0xdb, 0x21, 0x0d, 0x9b, 0xb7, 0x8d, 0xd6, 0x9a, 0xf1, 0x38, 0xa0, 0x5e, 0x5b, + 0x77, 0x3d, 0xc6, 0x19, 0xbe, 0xa8, 0x0c, 0xf4, 0xc8, 0x40, 0x6f, 0xad, 0x65, 0xd2, 0x35, 0x56, + 0x63, 0xe2, 0xde, 0x08, 0x7f, 0x49, 0xd3, 0xcc, 0xfb, 0x35, 0xc6, 0x6a, 0x0d, 0x6a, 0x10, 0xd7, + 0x36, 0x88, 0xe3, 0x30, 0x4e, 0xb8, 0xcd, 0x1c, 0x5f, 0xdd, 0xae, 0x54, 0x98, 0xdf, 0x64, 0xbe, + 0x61, 0x11, 0x9f, 0xca, 0x0c, 0x46, 0x6b, 0xcd, 0xa2, 0x9c, 0xac, 0x19, 0x2e, 0xa9, 0xd9, 0x8e, + 0x30, 0x56, 0xb6, 0xb9, 0x38, 0x54, 0x2e, 0xf1, 0x48, 0x33, 0x8a, 0xa6, 0xc5, 0x59, 0x74, 0x20, + 0x0a, 0x1b, 0x2d, 0x0d, 0xf8, 0xdb, 0x30, 0x4f, 0x49, 0x38, 0x9a, 0xf4, 0x71, 0x40, 0x7d, 0xae, + 0x95, 0xe0, 0x62, 0xcf, 0xa9, 0xef, 0x32, 0xc7, 0xa7, 0xf8, 0x36, 0x24, 0x65, 0x82, 0x39, 0x94, + 0x43, 0xcb, 0xd3, 0xf9, 0xcb, 0x7a, 0x4c, 0xe1, 0xba, 0x74, 0x2a, 0x9c, 0x7b, 0x79, 0x38, 0x3f, + 0x66, 0x2a, 0x07, 0xed, 0x27, 0x04, 0x39, 0x11, 0xf2, 0xae, 0xed, 0xf3, 0x52, 0x60, 0x35, 0xec, + 0x8a, 0x49, 0x9c, 0x2a, 0x6b, 0x3a, 0xd4, 0x8f, 0xd2, 0xe2, 0x05, 0x48, 0xed, 0xba, 0x65, 0x8b, + 0x57, 0xca, 0xee, 0x5e, 0xb9, 0x4e, 0xf7, 0x45, 0x9a, 0x29, 0x13, 0x76, 0xdd, 0x02, 0xaf, 0x94, + 0xf6, 0xb6, 0xe9, 0x3e, 0xde, 0x02, 0xe8, 0x32, 0x31, 0x97, 0x10, 0x30, 0x16, 0x75, 0x49, 0x9b, + 0x1e, 0xd2, 0xa6, 0xcb, 0xc6, 0x28, 0xda, 0xf4, 0x12, 0xa9, 0x51, 0x15, 0xde, 0x3c, 0xe1, 0xa9, + 0x1d, 0x24, 0x60, 0x61, 0x00, 0x1e, 0x55, 0xf0, 0x0b, 0x04, 0x33, 0x6e, 0x60, 0x95, 0x3d, 0xe2, + 0x54, 0xcb, 0x4d, 0xe2, 0xce, 0xa1, 0xdc, 0xf8, 0xf2, 0x74, 0x7e, 0x2b, 0xb6, 0xee, 0xa1, 0xe1, + 0xf4, 0x52, 0x60, 0x85, 0xa7, 0xf7, 0x88, 0xbb, 0xe9, 0x70, 0xaf, 0x5d, 0xb8, 0xf5, 0xea, 0x70, + 0xfe, 0x46, 0xcd, 0xe6, 0xf5, 0xc0, 0xd2, 0x2b, 0xac, 0x69, 0xa8, 0xa8, 0x95, 0x3a, 0xb1, 0x9d, + 0xe8, 0xc3, 0xe0, 0x6d, 0x97, 0xfa, 0xfa, 0x83, 0x4a, 0xdd, 0x61, 0x9e, 0xa7, 0x22, 0x98, 0xe0, + 0x76, 0x42, 0xe1, 0xaf, 0x62, 0x28, 0x59, 0x1a, 0x4a, 0x89, 0x84, 0x74, 0x92, 0x93, 0xcc, 0xe7, + 0xf0, 0xf6, 0x29, 0x84, 0x78, 0x16, 0xc6, 0xf7, 0x68, 0x5b, 0xf4, 0xe1, 0x9c, 0x19, 0xfe, 0xc4, + 0x69, 0x98, 0x68, 0x91, 0x46, 0x40, 0x45, 0xa2, 0x19, 0x53, 0x7e, 0x7c, 0x9a, 0xb8, 0x85, 0xb4, + 0x55, 0xb8, 0x20, 0x28, 0x28, 0x34, 0x58, 0x65, 0x2f, 0x6a, 0xe9, 0x25, 0x48, 0xd6, 0xa9, 0x5d, + 0xab, 0x73, 0x15, 0x43, 0x7d, 0x69, 0xf7, 0xd4, 0xdc, 0x29, 0x63, 0xc5, 0xf7, 0x27, 0x30, 0x61, + 0x85, 0x07, 0x6a, 0xbe, 0x16, 0x62, 0x79, 0xde, 0x71, 0xaa, 0x74, 0x9f, 0x56, 0xa5, 0xa7, 0xb4, + 0xd7, 0x7e, 0x43, 0x70, 0xa9, 0xc3, 0xbf, 0xb8, 0xe9, 0x0c, 0xd5, 0x1d, 0x48, 0xfa, 0x9c, 0xf0, + 0x40, 0x0e, 0xed, 0x5b, 0xf9, 0xa5, 0xbe, 0xcd, 0xb3, 0x55, 0xd0, 0x07, 0xc2, 0xdc, 0x54, 0x6e, + 0x67, 0x36, 0x72, 0xcf, 0x11, 0xbc, 0xfb, 0x3f, 0x8c, 0xdd, 0x97, 0x25, 0x0a, 0xf1, 0xd5, 0x84, + 0x8d, 0x50, 0xb9, 0x72, 0x38, 0xb3, 0xf6, 0x6b, 0xeb, 0xf0, 0x9e, 0x80, 0xf7, 0x90, 0x71, 0xea, + 0x6f, 0xf0, 0x6d, 0xd1, 0xa8, 0x61, 0x7d, 0x6c, 0x42, 0x26, 0xce, 0x49, 0x95, 0x75, 0x1f, 0xce, + 0xcb, 0xd7, 0x2c, 0xeb, 0x9a, 0x29, 0xdc, 0x7c, 0x75, 0x38, 0x9f, 0x1f, 0x6d, 0xe2, 0x0b, 0x3b, + 0xa5, 0xf5, 0x1b, 0xd7, 0x4b, 0x81, 0xf5, 0x0d, 0x6d, 0x9b, 0x49, 0x2b, 0x14, 0x00, 0x5f, 0xbb, + 0x0d, 0x69, 0x91, 0x6e, 0xb3, 0x65, 0x57, 0xa9, 0x53, 0xa1, 0xa3, 0x2b, 0x87, 0x66, 0xc2, 0x3b, + 0xa7, 0x5c, 0x3b, 0xdc, 0x4f, 0x52, 0x75, 0xa6, 0xe6, 0xee, 0x4a, 0x2c, 0xfb, 0x1d, 0xc7, 0x8e, + 0xb9, 0xf6, 0x0c, 0x29, 0xce, 0xc2, 0x96, 0x46, 0xf7, 0x27, 0xe4, 0x6c, 0xc6, 0xe7, 0xc4, 0xe3, + 0xe5, 0x1e, 0xe6, 0xa6, 0xc5, 0x99, 0x24, 0xea, 0xcc, 0x66, 0xeb, 0x05, 0x52, 0x7d, 0x38, 0x05, + 0x44, 0x95, 0xf8, 0x19, 0x4c, 0x45, 0x98, 0xa3, 0x09, 0x1b, 0x52, 0x63, 0xd7, 0xfe, 0xcc, 0x06, + 0x6c, 0xe5, 0x8e, 0x7c, 0xf3, 0xbd, 0xcf, 0x0c, 0x5f, 0x80, 0x54, 0xf1, 0x7e, 0xb1, 0xbc, 0xb5, + 0x53, 0xdc, 0xb8, 0xbb, 0xf3, 0x68, 0xf3, 0xcb, 0xd9, 0x31, 0x9c, 0x82, 0xa9, 0xee, 0x27, 0xc2, + 0xe7, 0x61, 0x7c, 0xa3, 0xf8, 0xdd, 0x6c, 0x22, 0xff, 0xef, 0x24, 0x4c, 0x88, 0x2a, 0xf1, 0xf7, + 0x08, 0x92, 0x72, 0xcf, 0xe0, 0xfe, 0xef, 0xb9, 0x77, 0xa9, 0x65, 0x96, 0x87, 0x1b, 0x4a, 0xd0, + 0xda, 0xd5, 0x1f, 0xfe, 0xf8, 0xe7, 0x97, 0xc4, 0x15, 0x7c, 0xd9, 0xe8, 0xbf, 0x63, 0xf1, 0x5f, + 0x08, 0xd2, 0x71, 0x6a, 0x8f, 0x3f, 0x7e, 0xdd, 0xed, 0x20, 0xe1, 0xdd, 0x7c, 0xb3, 0xa5, 0xa2, + 0x3d, 0x14, 0x60, 0x4b, 0xb8, 0x68, 0x0c, 0x5a, 0xf7, 0x65, 0xd7, 0x63, 0x61, 0x47, 0x3d, 0xdf, + 0x78, 0xd2, 0xf3, 0x52, 0x9e, 0x1a, 0xae, 0x88, 0x2c, 0x76, 0x9c, 0x0c, 0x5d, 0x6e, 0xd8, 0x3e, + 0xc7, 0xcf, 0x10, 0x4c, 0x88, 0x3e, 0xe1, 0xc5, 0xfe, 0xc8, 0x4e, 0x6a, 0x7d, 0x66, 0x69, 0xa8, + 0x9d, 0x82, 0x7c, 0x4d, 0x40, 0x5e, 0xc4, 0x1f, 0xc4, 0x42, 0x96, 0xba, 0x66, 0x3c, 0x91, 0xaf, + 0xe6, 0x29, 0xfe, 0x11, 0x01, 0x74, 0x25, 0x13, 0xaf, 0x0e, 0xe6, 0xa9, 0x47, 0xfc, 0x33, 0xd7, + 0x46, 0x33, 0x1e, 0xa9, 0xef, 0x4a, 0x6f, 0x9f, 0x23, 0x48, 0xf5, 0xa8, 0x1d, 0xd6, 0xfb, 0x27, + 0x89, 0xd3, 0xd2, 0x8c, 0x31, 0xb2, 0xbd, 0xc2, 0xb5, 0x2a, 0x70, 0x7d, 0x88, 0xaf, 0xc6, 0xe2, + 0x6a, 0x85, 0x3e, 0x5d, 0xba, 0x7e, 0x47, 0x30, 0x19, 0x3d, 0x63, 0xfc, 0x51, 0xff, 0x54, 0xa7, + 0x24, 0x34, 0xb3, 0x32, 0x8a, 0xa9, 0x02, 0xb4, 0x2d, 0x00, 0x15, 0xf0, 0x17, 0x6f, 0x3a, 0x73, + 0x91, 0xba, 0xe0, 0x5f, 0x11, 0xa4, 0x7a, 0x34, 0x6b, 0x10, 0x9b, 0x71, 0x2a, 0x3b, 0x88, 0xcd, + 0x58, 0x31, 0xd4, 0x16, 0x05, 0xf8, 0x1c, 0xce, 0xc6, 0x82, 0xef, 0xe8, 0x5e, 0xe1, 0xeb, 0x97, + 0x47, 0x59, 0x74, 0x70, 0x94, 0x45, 0x7f, 0x1f, 0x65, 0xd1, 0xcf, 0xc7, 0xd9, 0xb1, 0x83, 0xe3, + 0xec, 0xd8, 0x9f, 0xc7, 0xd9, 0xb1, 0x47, 0xd7, 0x87, 0x6d, 0xb0, 0xfd, 0x6e, 0x48, 0xb1, 0xcc, + 0xac, 0xa4, 0xf8, 0xb7, 0xbd, 0xfe, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x08, 0x51, 0x06, + 0x4b, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -857,13 +857,13 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Parameters queries the parameters of the module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // ListPublicRandomness is a range query for public randomness of a given BTC validator + // ListPublicRandomness is a range query for public randomness of a given finality provider ListPublicRandomness(ctx context.Context, in *QueryListPublicRandomnessRequest, opts ...grpc.CallOption) (*QueryListPublicRandomnessResponse, error) // Block queries a block at a given height Block(ctx context.Context, in *QueryBlockRequest, opts ...grpc.CallOption) (*QueryBlockResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(ctx context.Context, in *QueryListBlocksRequest, opts ...grpc.CallOption) (*QueryListBlocksResponse, error) - // VotesAtHeight queries BTC validators who have signed the block at given height. + // VotesAtHeight queries finality providers who have signed the block at given height. VotesAtHeight(ctx context.Context, in *QueryVotesAtHeightRequest, opts ...grpc.CallOption) (*QueryVotesAtHeightResponse, error) // Evidence queries the first evidence which can be used for extracting the BTC SK Evidence(ctx context.Context, in *QueryEvidenceRequest, opts ...grpc.CallOption) (*QueryEvidenceResponse, error) @@ -946,13 +946,13 @@ func (c *queryClient) ListEvidences(ctx context.Context, in *QueryListEvidencesR type QueryServer interface { // Parameters queries the parameters of the module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // ListPublicRandomness is a range query for public randomness of a given BTC validator + // ListPublicRandomness is a range query for public randomness of a given finality provider ListPublicRandomness(context.Context, *QueryListPublicRandomnessRequest) (*QueryListPublicRandomnessResponse, error) // Block queries a block at a given height Block(context.Context, *QueryBlockRequest) (*QueryBlockResponse, error) // ListBlocks is a range query for blocks at a given status ListBlocks(context.Context, *QueryListBlocksRequest) (*QueryListBlocksResponse, error) - // VotesAtHeight queries BTC validators who have signed the block at given height. + // VotesAtHeight queries finality providers who have signed the block at given height. VotesAtHeight(context.Context, *QueryVotesAtHeightRequest) (*QueryVotesAtHeightResponse, error) // Evidence queries the first evidence which can be used for extracting the BTC SK Evidence(context.Context, *QueryEvidenceRequest) (*QueryEvidenceResponse, error) @@ -1241,10 +1241,10 @@ func (m *QueryListPublicRandomnessRequest) MarshalToSizedBuffer(dAtA []byte) (in i-- dAtA[i] = 0x12 } - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } @@ -1547,10 +1547,10 @@ func (m *QueryEvidenceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.ValBtcPkHex) > 0 { - i -= len(m.ValBtcPkHex) - copy(dAtA[i:], m.ValBtcPkHex) - i = encodeVarintQuery(dAtA, i, uint64(len(m.ValBtcPkHex))) + if len(m.FpBtcPkHex) > 0 { + i -= len(m.FpBtcPkHex) + copy(dAtA[i:], m.FpBtcPkHex) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FpBtcPkHex))) i-- dAtA[i] = 0xa } @@ -1718,7 +1718,7 @@ func (m *QueryListPublicRandomnessRequest) Size() (n int) { } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -1848,7 +1848,7 @@ func (m *QueryEvidenceRequest) Size() (n int) { } var l int _ = l - l = len(m.ValBtcPkHex) + l = len(m.FpBtcPkHex) if l > 0 { n += 1 + l + sovQuery(uint64(l)) } @@ -2073,7 +2073,7 @@ func (m *QueryListPublicRandomnessRequest) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2101,7 +2101,7 @@ func (m *QueryListPublicRandomnessRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2927,7 +2927,7 @@ func (m *QueryEvidenceRequest) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPkHex", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkHex", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2955,7 +2955,7 @@ func (m *QueryEvidenceRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ValBtcPkHex = string(dAtA[iNdEx:postIndex]) + m.FpBtcPkHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/x/finality/types/query.pb.gw.go b/x/finality/types/query.pb.gw.go index 68056654e..1d001a7a0 100644 --- a/x/finality/types/query.pb.gw.go +++ b/x/finality/types/query.pb.gw.go @@ -52,7 +52,7 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } var ( - filter_Query_ListPublicRandomness_0 = &utilities.DoubleArray{Encoding: map[string]int{"val_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} + filter_Query_ListPublicRandomness_0 = &utilities.DoubleArray{Encoding: map[string]int{"fp_btc_pk_hex": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) func request_Query_ListPublicRandomness_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { @@ -66,15 +66,15 @@ func request_Query_ListPublicRandomness_0(ctx context.Context, marshaler runtime _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { @@ -100,15 +100,15 @@ func local_request_Query_ListPublicRandomness_0(ctx context.Context, marshaler r _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } if err := req.ParseForm(); err != nil { @@ -278,15 +278,15 @@ func request_Query_Evidence_0(ctx context.Context, marshaler runtime.Marshaler, _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } msg, err := client.Evidence(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) @@ -305,15 +305,15 @@ func local_request_Query_Evidence_0(ctx context.Context, marshaler runtime.Marsh _ = err ) - val, ok = pathParams["val_btc_pk_hex"] + val, ok = pathParams["fp_btc_pk_hex"] if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "val_btc_pk_hex") + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "fp_btc_pk_hex") } - protoReq.ValBtcPkHex, err = runtime.String(val) + protoReq.FpBtcPkHex, err = runtime.String(val) if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "val_btc_pk_hex", err) + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "fp_btc_pk_hex", err) } msg, err := server.Evidence(ctx, &protoReq) @@ -711,7 +711,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ListPublicRandomness_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "finality_providers", "fp_btc_pk_hex", "public_randomness_list"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Block_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "blocks", "height"}, "", runtime.AssumeColonVerbOpt(false))) @@ -719,7 +719,7 @@ var ( pattern_Query_VotesAtHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"babylon", "finality", "v1", "votes", "height"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_Evidence_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "btc_validators", "val_btc_pk_hex", "evidence"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Evidence_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"babylon", "finality", "v1", "finality_providers", "fp_btc_pk_hex", "evidence"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_ListEvidences_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "finality", "v1", "evidences"}, "", runtime.AssumeColonVerbOpt(false))) ) diff --git a/x/finality/types/tx.pb.go b/x/finality/types/tx.pb.go index 259739472..d717445b1 100644 --- a/x/finality/types/tx.pb.go +++ b/x/finality/types/tx.pb.go @@ -34,8 +34,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgAddFinalitySig defines a message for adding a vote type MsgAddFinalitySig struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // val_btc_pk is the BTC Pk of the validator that casts this vote - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // fp_btc_pk is the BTC Pk of the finality provider that casts this vote + FpBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=fp_btc_pk,json=fpBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk,omitempty"` // block_height is the height of the voted block BlockHeight uint64 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` // block_app_hash is the AppHash of the voted block @@ -43,7 +43,7 @@ type MsgAddFinalitySig struct { // finality_sig is the finality signature to this block // where finality signature is an EOTS signature, i.e., // the `s` in a Schnorr signature `(r, s)` - // `r` is the public randomness that is already committed by the validator + // `r` is the public randomness that is already committed by the finality provider FinalitySig *github_com_babylonchain_babylon_types.SchnorrEOTSSig `protobuf:"bytes,5,opt,name=finality_sig,json=finalitySig,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrEOTSSig" json:"finality_sig,omitempty"` } @@ -141,17 +141,17 @@ var xxx_messageInfo_MsgAddFinalitySigResponse proto.InternalMessageInfo // MsgCommitPubRandList defines a message for committing a list of public randomness for EOTS type MsgCommitPubRandList struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - // val_btc_pk is the BTC Pk of the validator that commits the public randomness - ValBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=val_btc_pk,json=valBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"val_btc_pk,omitempty"` + // fp_btc_pk is the BTC Pk of the finality provider that commits the public randomness + FpBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=fp_btc_pk,json=fpBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk,omitempty"` // start_height is the start block height of the list of public randomness StartHeight uint64 `protobuf:"varint,3,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` // pub_rand_list is the list of public randomness PubRandList []github_com_babylonchain_babylon_types.SchnorrPubRand `protobuf:"bytes,4,rep,name=pub_rand_list,json=pubRandList,proto3,customtype=github.com/babylonchain/babylon/types.SchnorrPubRand" json:"pub_rand_list,omitempty"` // sig is the signature on (start_height || pub_rand_list) signed by - // SK corresponding to val_btc_pk. This prevents others to commit public - // randomness on behalf of val_btc_pk - // TODO: another option is to restrict signer to correspond to val_btc_pk. This restricts - // the tx submitter to be the holder of val_btc_pk. Decide this later + // SK corresponding to fp_btc_pk. This prevents others to commit public + // randomness on behalf of fp_btc_pk + // TODO: another option is to restrict signer to correspond to fp_btc_pk. This restricts + // the tx submitter to be the holder of fp_btc_pk. Decide this later Sig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=sig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"sig,omitempty"` } @@ -349,47 +349,47 @@ func init() { proto.RegisterFile("babylon/finality/v1/tx.proto", fileDescriptor_ var fileDescriptor_2dd6da066b6baf1d = []byte{ // 644 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x4f, 0x4f, 0x13, 0x41, - 0x14, 0xef, 0xb6, 0x85, 0xc8, 0xb4, 0x62, 0x58, 0x89, 0x2c, 0x85, 0x6c, 0x6b, 0x43, 0x0c, 0x12, - 0xdd, 0xe5, 0x9f, 0x44, 0xb8, 0x51, 0xa3, 0x41, 0xb1, 0xb1, 0xd9, 0xe2, 0x45, 0x4d, 0x36, 0xb3, - 0x7f, 0x98, 0x9d, 0xd0, 0xdd, 0x19, 0x77, 0xa6, 0x0d, 0xbd, 0x19, 0x3f, 0x81, 0x07, 0x3f, 0x08, - 0x07, 0x3e, 0x04, 0x17, 0x13, 0xe2, 0xc9, 0x70, 0xa8, 0x06, 0x0e, 0x7c, 0x0d, 0xd3, 0xdd, 0x59, - 0x0a, 0xb4, 0xc4, 0xea, 0xc1, 0xdb, 0xbe, 0x79, 0xbf, 0xf7, 0x7e, 0xef, 0xfd, 0xde, 0x7b, 0x0b, - 0x66, 0x2d, 0x68, 0xb5, 0x1b, 0x24, 0xd0, 0x77, 0x71, 0x00, 0x1b, 0x98, 0xb7, 0xf5, 0xd6, 0x92, - 0xce, 0xf7, 0x35, 0x1a, 0x12, 0x4e, 0xe4, 0xbb, 0xc2, 0xab, 0x25, 0x5e, 0xad, 0xb5, 0x54, 0x98, - 0x44, 0x04, 0x91, 0xc8, 0xaf, 0x77, 0xbf, 0x62, 0x68, 0x61, 0xda, 0x26, 0xcc, 0x27, 0xcc, 0x8c, - 0x1d, 0xb1, 0x21, 0x5c, 0x53, 0xb1, 0xa5, 0xfb, 0x0c, 0x75, 0xb3, 0xfb, 0x0c, 0x09, 0x47, 0x69, - 0x10, 0x39, 0x85, 0x21, 0xf4, 0x45, 0x68, 0xf9, 0x30, 0x0d, 0x26, 0xaa, 0x0c, 0x6d, 0x3a, 0xce, - 0x0b, 0x01, 0xa9, 0x63, 0x24, 0xdf, 0x03, 0xa3, 0x0c, 0xa3, 0xc0, 0x0d, 0x15, 0xa9, 0x24, 0xcd, - 0x8f, 0x19, 0xc2, 0x92, 0x77, 0x00, 0x68, 0xc1, 0x86, 0x69, 0x71, 0xdb, 0xa4, 0x7b, 0x4a, 0xba, - 0x24, 0xcd, 0xe7, 0x2b, 0x6b, 0x27, 0x9d, 0xe2, 0x32, 0xc2, 0xdc, 0x6b, 0x5a, 0x9a, 0x4d, 0x7c, - 0x5d, 0x50, 0xda, 0x1e, 0xc4, 0x41, 0x62, 0xe8, 0xbc, 0x4d, 0x5d, 0xa6, 0x55, 0x5e, 0xd6, 0x56, - 0x56, 0x17, 0x6b, 0x4d, 0x6b, 0xdb, 0x6d, 0x1b, 0xb7, 0x5a, 0xb0, 0x51, 0xe1, 0x76, 0x6d, 0x4f, - 0xbe, 0x0f, 0xf2, 0x56, 0x83, 0xd8, 0x7b, 0xa6, 0xe7, 0x62, 0xe4, 0x71, 0x25, 0x53, 0x92, 0xe6, - 0xb3, 0x46, 0x2e, 0x7a, 0xdb, 0x8a, 0x9e, 0xe4, 0x39, 0x30, 0x1e, 0x43, 0x20, 0xa5, 0xa6, 0x07, - 0x99, 0xa7, 0x64, 0xbb, 0xe4, 0x46, 0x1c, 0xb8, 0x49, 0xe9, 0x16, 0x64, 0x9e, 0xfc, 0x1e, 0xe4, - 0x93, 0x46, 0x4d, 0x86, 0x91, 0x32, 0x12, 0x15, 0xf8, 0xf4, 0xa4, 0x53, 0x5c, 0x1d, 0xae, 0xc0, - 0xba, 0xed, 0x05, 0x24, 0x0c, 0x9f, 0xbf, 0xd9, 0xa9, 0xd7, 0x31, 0x32, 0x72, 0xbb, 0x3d, 0x4d, - 0x36, 0x72, 0x9f, 0xcf, 0x0f, 0x16, 0x84, 0x10, 0xe5, 0x19, 0x30, 0xdd, 0xa7, 0x9a, 0xe1, 0x32, - 0x4a, 0x02, 0xe6, 0x96, 0x7f, 0xa6, 0xc1, 0x64, 0x95, 0xa1, 0x67, 0xc4, 0xf7, 0x31, 0xaf, 0x35, - 0x2d, 0x03, 0x06, 0xce, 0x6b, 0xcc, 0xf8, 0xff, 0x97, 0x95, 0x71, 0x18, 0xf2, 0x6b, 0xb2, 0x46, - 0x6f, 0x42, 0xd6, 0x0f, 0xe0, 0x36, 0x6d, 0x5a, 0x66, 0x08, 0x03, 0xc7, 0x6c, 0x60, 0xc6, 0x95, - 0x6c, 0x29, 0xf3, 0x4f, 0x8a, 0x89, 0x2e, 0x8d, 0x1c, 0xbd, 0xd4, 0xee, 0x36, 0xc8, 0xf4, 0xa6, - 0xb0, 0x7e, 0xd2, 0x29, 0x3e, 0xf9, 0x9b, 0x7e, 0xea, 0x18, 0x05, 0x90, 0x37, 0x43, 0xd7, 0xe8, - 0x66, 0xb9, 0x2a, 0xbf, 0x0a, 0x66, 0x07, 0x09, 0x7c, 0x31, 0x81, 0xaf, 0x12, 0xb8, 0x53, 0x65, - 0xe8, 0x2d, 0x75, 0x20, 0x77, 0x6b, 0xd1, 0xbe, 0xcb, 0x6b, 0x60, 0x0c, 0x36, 0xb9, 0x47, 0x42, - 0xcc, 0xdb, 0xb1, 0xfe, 0x15, 0xe5, 0xfb, 0xe1, 0xe3, 0x49, 0x71, 0x49, 0x9b, 0x8e, 0x13, 0xba, - 0x8c, 0xd5, 0x79, 0x88, 0x03, 0x64, 0xf4, 0xa0, 0xf2, 0x3a, 0x18, 0x8d, 0x2f, 0x26, 0x1a, 0x4c, - 0x6e, 0x79, 0x46, 0x1b, 0x70, 0xb3, 0x5a, 0x4c, 0x52, 0xc9, 0x1e, 0x75, 0x8a, 0x29, 0x43, 0x04, - 0x6c, 0x8c, 0x77, 0x6b, 0xee, 0xa5, 0x2a, 0x4f, 0x83, 0xa9, 0x6b, 0x55, 0x25, 0x15, 0x2f, 0x7f, - 0x4b, 0x83, 0x4c, 0x95, 0x21, 0xd9, 0x03, 0xe3, 0xd7, 0x6e, 0xf1, 0xc1, 0x40, 0xbe, 0xbe, 0xed, - 0x2b, 0x68, 0xc3, 0xe1, 0x12, 0x46, 0xf9, 0x23, 0x98, 0xe8, 0xdf, 0xd0, 0x87, 0x37, 0x25, 0xe9, - 0x83, 0x16, 0x96, 0x86, 0x86, 0x5e, 0x50, 0x5a, 0x20, 0x7f, 0x65, 0x24, 0x73, 0x37, 0xa5, 0xb8, - 0x8c, 0x2a, 0x3c, 0x1a, 0x06, 0x95, 0x70, 0x14, 0x46, 0x3e, 0x9d, 0x1f, 0x2c, 0x48, 0x95, 0x57, - 0x47, 0xa7, 0xaa, 0x74, 0x7c, 0xaa, 0x4a, 0xbf, 0x4e, 0x55, 0xe9, 0xcb, 0x99, 0x9a, 0x3a, 0x3e, - 0x53, 0x53, 0x3f, 0xce, 0xd4, 0xd4, 0xbb, 0xc5, 0x3f, 0x2d, 0xe1, 0x7e, 0xef, 0x6f, 0x19, 0xed, - 0xa3, 0x35, 0x1a, 0xfd, 0x2a, 0x57, 0x7e, 0x07, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x72, 0x38, 0x11, - 0xcb, 0x05, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0x93, 0xb4, 0x9f, 0x3a, 0xc9, 0x57, 0x54, 0x53, 0x51, 0x37, 0xad, 0x9c, 0x10, 0x55, + 0xa8, 0x54, 0x60, 0xf7, 0x8f, 0x8a, 0x76, 0xd7, 0x20, 0x50, 0xa1, 0x44, 0x44, 0x0e, 0x6c, 0x00, + 0xc9, 0x1a, 0xff, 0x64, 0x3c, 0x6a, 0xec, 0x19, 0x3c, 0x93, 0xaa, 0xd9, 0x21, 0x9e, 0x80, 0x05, + 0x0f, 0x52, 0x21, 0x1e, 0xa2, 0x1b, 0xa4, 0x8a, 0x15, 0xaa, 0x44, 0x84, 0xda, 0x45, 0x5f, 0x03, + 0xc5, 0x1e, 0x37, 0x6d, 0x92, 0x8a, 0xc0, 0x82, 0x9d, 0xef, 0x9c, 0x33, 0xf7, 0xdc, 0x7b, 0xe6, + 0x5e, 0x83, 0x79, 0x0b, 0x5a, 0xed, 0x26, 0x09, 0xf4, 0x06, 0x0e, 0x60, 0x13, 0xf3, 0xb6, 0xbe, + 0xbf, 0xa2, 0xf3, 0x03, 0x8d, 0x86, 0x84, 0x13, 0xf9, 0xa6, 0x40, 0xb5, 0x04, 0xd5, 0xf6, 0x57, + 0x0a, 0xd3, 0x88, 0x20, 0x12, 0xe1, 0x7a, 0xf7, 0x2b, 0xa6, 0x16, 0x66, 0x6d, 0xc2, 0x7c, 0xc2, + 0xcc, 0x18, 0x88, 0x03, 0x01, 0xcd, 0xc4, 0x91, 0xee, 0x33, 0xd4, 0xcd, 0xee, 0x33, 0x24, 0x80, + 0xd2, 0x30, 0x71, 0x0a, 0x43, 0xe8, 0x8b, 0xab, 0xe5, 0xcf, 0x69, 0x30, 0x55, 0x65, 0x68, 0xdb, + 0x71, 0x9e, 0x08, 0x4a, 0x1d, 0x23, 0xf9, 0x16, 0x18, 0x67, 0x18, 0x05, 0x6e, 0xa8, 0x48, 0x25, + 0x69, 0x71, 0xc2, 0x10, 0x91, 0x6c, 0x80, 0x89, 0x06, 0x35, 0x2d, 0x6e, 0x9b, 0x74, 0x4f, 0x49, + 0x97, 0xa4, 0xc5, 0x7c, 0x65, 0xe3, 0xa4, 0x53, 0x5c, 0x45, 0x98, 0x7b, 0x2d, 0x4b, 0xb3, 0x89, + 0xaf, 0x0b, 0x45, 0xdb, 0x83, 0x38, 0x48, 0x02, 0x9d, 0xb7, 0xa9, 0xcb, 0xb4, 0xca, 0xd3, 0xda, + 0xda, 0xfa, 0x72, 0xad, 0x65, 0xed, 0xba, 0x6d, 0xe3, 0xbf, 0x06, 0xad, 0x70, 0xbb, 0xb6, 0x27, + 0xdf, 0x06, 0x79, 0xab, 0x49, 0xec, 0x3d, 0xd3, 0x73, 0x31, 0xf2, 0xb8, 0x92, 0x29, 0x49, 0x8b, + 0x59, 0x23, 0x17, 0x9d, 0xed, 0x44, 0x47, 0xf2, 0x02, 0x98, 0x8c, 0x29, 0x90, 0x52, 0xd3, 0x83, + 0xcc, 0x53, 0xb2, 0x5d, 0x6d, 0x23, 0xbe, 0xb8, 0x4d, 0xe9, 0x0e, 0x64, 0x9e, 0xfc, 0x06, 0xe4, + 0x93, 0x36, 0x4d, 0x86, 0x91, 0x32, 0x16, 0xd5, 0xf7, 0xf0, 0xa4, 0x53, 0x5c, 0x1f, 0xad, 0xbe, + 0xba, 0xed, 0x05, 0x24, 0x0c, 0x1f, 0xbf, 0x78, 0x59, 0xaf, 0x63, 0x64, 0xe4, 0x1a, 0x3d, 0x47, + 0xb6, 0x72, 0x1f, 0xce, 0x0f, 0x97, 0x84, 0x0d, 0xe5, 0x39, 0x30, 0x3b, 0xe0, 0x99, 0xe1, 0x32, + 0x4a, 0x02, 0xe6, 0x96, 0x7f, 0xa4, 0xc1, 0x74, 0x95, 0xa1, 0x47, 0xc4, 0xf7, 0x31, 0xaf, 0xb5, + 0x2c, 0x03, 0x06, 0xce, 0x73, 0xcc, 0xf8, 0xbf, 0x36, 0x95, 0x71, 0x18, 0xf2, 0x3e, 0x53, 0xa3, + 0x33, 0x61, 0xea, 0x5b, 0xf0, 0x3f, 0x6d, 0x59, 0x66, 0x08, 0x03, 0xc7, 0x6c, 0x62, 0xc6, 0x95, + 0x6c, 0x29, 0xf3, 0x57, 0x7e, 0x89, 0x1e, 0x8d, 0x1c, 0xbd, 0xd4, 0xec, 0x2e, 0xc8, 0xf4, 0xde, + 0x60, 0xf3, 0xa4, 0x53, 0x7c, 0xf0, 0x27, 0xed, 0xd4, 0x31, 0x0a, 0x20, 0x6f, 0x85, 0xae, 0xd1, + 0xcd, 0x72, 0xd5, 0x7c, 0x15, 0xcc, 0x0f, 0xb3, 0xf7, 0xc2, 0xff, 0x4f, 0x12, 0xb8, 0x51, 0x65, + 0xe8, 0x15, 0x75, 0x20, 0x77, 0x6b, 0xd1, 0xac, 0xcb, 0x1b, 0x60, 0x02, 0xb6, 0xb8, 0x47, 0x42, + 0xcc, 0xdb, 0xb1, 0xfb, 0x15, 0xe5, 0xdb, 0x97, 0xfb, 0xd3, 0x62, 0x8b, 0xb6, 0x1d, 0x27, 0x74, + 0x19, 0xab, 0xf3, 0x10, 0x07, 0xc8, 0xe8, 0x51, 0xe5, 0x4d, 0x30, 0x1e, 0x6f, 0x4b, 0xf4, 0x2e, + 0xb9, 0xd5, 0x39, 0x6d, 0xc8, 0xbe, 0x6a, 0xb1, 0x48, 0x25, 0x7b, 0xd4, 0x29, 0xa6, 0x0c, 0x71, + 0x61, 0x6b, 0xb2, 0x5b, 0x73, 0x2f, 0x55, 0x79, 0x16, 0xcc, 0xf4, 0x55, 0x95, 0x54, 0xbc, 0xfa, + 0x35, 0x0d, 0x32, 0x55, 0x86, 0x64, 0x0f, 0x4c, 0xf6, 0xed, 0xe1, 0x9d, 0xa1, 0x7a, 0x03, 0xb3, + 0x57, 0xd0, 0x46, 0xe3, 0x25, 0x8a, 0xf2, 0x3b, 0x30, 0x35, 0x38, 0x9f, 0x77, 0xaf, 0x4b, 0x32, + 0x40, 0x2d, 0xac, 0x8c, 0x4c, 0xbd, 0x90, 0xb4, 0x40, 0xfe, 0xca, 0x93, 0x2c, 0x5c, 0x97, 0xe2, + 0x32, 0xab, 0x70, 0x6f, 0x14, 0x56, 0xa2, 0x51, 0x18, 0x7b, 0x7f, 0x7e, 0xb8, 0x24, 0x55, 0x9e, + 0x1d, 0x9d, 0xaa, 0xd2, 0xf1, 0xa9, 0x2a, 0xfd, 0x3c, 0x55, 0xa5, 0x8f, 0x67, 0x6a, 0xea, 0xf8, + 0x4c, 0x4d, 0x7d, 0x3f, 0x53, 0x53, 0xaf, 0x97, 0x7f, 0x37, 0x84, 0x07, 0xbd, 0x3f, 0x65, 0x34, + 0x8f, 0xd6, 0x78, 0xf4, 0x9b, 0x5c, 0xfb, 0x15, 0x00, 0x00, 0xff, 0xff, 0x01, 0xd6, 0x89, 0xb4, + 0xc7, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -596,11 +596,11 @@ func (m *MsgAddFinalitySig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - if m.ValBtcPk != nil { + if m.FpBtcPk != nil { { - size := m.ValBtcPk.Size() + size := m.FpBtcPk.Size() i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -692,11 +692,11 @@ func (m *MsgCommitPubRandList) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - if m.ValBtcPk != nil { + if m.FpBtcPk != nil { { - size := m.ValBtcPk.Size() + size := m.FpBtcPk.Size() i -= size - if _, err := m.ValBtcPk.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.FpBtcPk.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintTx(dAtA, i, uint64(size)) @@ -821,8 +821,8 @@ func (m *MsgAddFinalitySig) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() + if m.FpBtcPk != nil { + l = m.FpBtcPk.Size() n += 1 + l + sovTx(uint64(l)) } if m.BlockHeight != 0 { @@ -858,8 +858,8 @@ func (m *MsgCommitPubRandList) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.ValBtcPk != nil { - l = m.ValBtcPk.Size() + if m.FpBtcPk != nil { + l = m.FpBtcPk.Size() n += 1 + l + sovTx(uint64(l)) } if m.StartHeight != 0 { @@ -980,7 +980,7 @@ func (m *MsgAddFinalitySig) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1008,8 +1008,8 @@ func (m *MsgAddFinalitySig) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPk = &v + if err := m.FpBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1235,7 +1235,7 @@ func (m *MsgCommitPubRandList) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValBtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPk", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1263,8 +1263,8 @@ func (m *MsgCommitPubRandList) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } var v github_com_babylonchain_babylon_types.BIP340PubKey - m.ValBtcPk = &v - if err := m.ValBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.FpBtcPk = &v + if err := m.FpBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go index f5d5afa93..d039820c2 100644 --- a/x/incentive/client/cli/tx.go +++ b/x/incentive/client/cli/tx.go @@ -30,7 +30,7 @@ func GetTxCmd() *cobra.Command { func NewWithdrawRewardCmd() *cobra.Command { cmd := &cobra.Command{ Use: "withdraw-reward [type]", - Short: "withdraw reward of the stakeholder behind the transaction submitter in a given type (one of {submitter, reporter, btc_validator, btc_delegation})", + Short: "withdraw reward of the stakeholder behind the transaction submitter in a given type (one of {submitter, reporter, finality_provider, btc_delegation})", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/incentive/keeper/btc_staking_gauge.go b/x/incentive/keeper/btc_staking_gauge.go index a62150cd0..b3067ab65 100644 --- a/x/incentive/keeper/btc_staking_gauge.go +++ b/x/incentive/keeper/btc_staking_gauge.go @@ -10,7 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// RewardBTCStaking distributes rewards to BTC validators/delegations at a given height according +// RewardBTCStaking distributes rewards to finality providers/delegations at a given height according // to the reward distribution cache // (adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L12-L64) func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, rdc *bstypes.RewardDistCache) { @@ -19,18 +19,18 @@ func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, rdc *bstype // failing to get a reward gauge at previous height is a programming error panic("failed to get a reward gauge at previous height") } - // reward each of the BTC validator and its BTC delegations in proportion - for _, btcVal := range rdc.BtcVals { - // get coins that will be allocated to the BTC validator and its BTC delegations - btcValPortion := rdc.GetBTCValPortion(btcVal) - coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) - // reward the BTC validator with commission - coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) - k.accumulateRewardGauge(ctx, types.BTCValidatorType, btcVal.GetAddress(), coinsForCommission) + // reward each of the finality provider and its BTC delegations in proportion + for _, fp := range rdc.FinalityProviders { + // get coins that will be allocated to the finality provider and its BTC delegations + fpPortion := rdc.GetFinalityProviderPortion(fp) + coinsForFpsAndDels := gauge.GetCoinsPortion(fpPortion) + // reward the finality provider with commission + coinsForCommission := types.GetCoinsPortion(coinsForFpsAndDels, *fp.Commission) + k.accumulateRewardGauge(ctx, types.FinalityProviderType, fp.GetAddress(), coinsForCommission) // reward the rest of coins to each BTC delegation proportional to its voting power portion - coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) - for _, btcDel := range btcVal.BtcDels { - btcDelPortion := btcVal.GetBTCDelPortion(btcDel) + coinsForBTCDels := coinsForFpsAndDels.Sub(coinsForCommission...) + for _, btcDel := range fp.BtcDels { + btcDelPortion := fp.GetBTCDelPortion(btcDel) coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) k.accumulateRewardGauge(ctx, types.BTCDelegationType, btcDel.GetAddress(), coinsForDel) } diff --git a/x/incentive/keeper/btc_staking_gauge_test.go b/x/incentive/keeper/btc_staking_gauge_test.go index 2cdadec4c..e467829c1 100644 --- a/x/incentive/keeper/btc_staking_gauge_test.go +++ b/x/incentive/keeper/btc_staking_gauge_test.go @@ -38,20 +38,20 @@ func FuzzRewardBTCStaking(f *testing.F) { // expected values distributedCoins := sdk.NewCoins() - btcValRewardMap := map[string]sdk.Coins{} // key: address, value: reward + fpRewardMap := map[string]sdk.Coins{} // key: address, value: reward btcDelRewardMap := map[string]sdk.Coins{} // key: address, value: reward - for _, btcVal := range rdc.BtcVals { - btcValPortion := rdc.GetBTCValPortion(btcVal) - coinsForBTCValAndDels := gauge.GetCoinsPortion(btcValPortion) - coinsForCommission := types.GetCoinsPortion(coinsForBTCValAndDels, *btcVal.Commission) + for _, fp := range rdc.FinalityProviders { + fpPortion := rdc.GetFinalityProviderPortion(fp) + coinsForFpsAndDels := gauge.GetCoinsPortion(fpPortion) + coinsForCommission := types.GetCoinsPortion(coinsForFpsAndDels, *fp.Commission) if coinsForCommission.IsAllPositive() { - btcValRewardMap[btcVal.GetAddress().String()] = coinsForCommission + fpRewardMap[fp.GetAddress().String()] = coinsForCommission distributedCoins.Add(coinsForCommission...) } - coinsForBTCDels := coinsForBTCValAndDels.Sub(coinsForCommission...) - for _, btcDel := range btcVal.BtcDels { - btcDelPortion := btcVal.GetBTCDelPortion(btcDel) + coinsForBTCDels := coinsForFpsAndDels.Sub(coinsForCommission...) + for _, btcDel := range fp.BtcDels { + btcDelPortion := fp.GetBTCDelPortion(btcDel) coinsForDel := types.GetCoinsPortion(coinsForBTCDels, btcDelPortion) if coinsForDel.IsAllPositive() { btcDelRewardMap[btcDel.GetAddress().String()] = coinsForDel @@ -60,14 +60,14 @@ func FuzzRewardBTCStaking(f *testing.F) { } } - // distribute rewards in the gauge to BTC validators/delegations + // distribute rewards in the gauge to finality providers/delegations keeper.RewardBTCStaking(ctx, height, rdc) // assert consistency between reward map and reward gauge - for addrStr, reward := range btcValRewardMap { + for addrStr, reward := range fpRewardMap { addr, err := sdk.AccAddressFromBech32(addrStr) require.NoError(t, err) - rg := keeper.GetRewardGauge(ctx, types.BTCValidatorType, addr) + rg := keeper.GetRewardGauge(ctx, types.FinalityProviderType, addr) require.NotNil(t, rg) require.Equal(t, reward, rg.Coins) } diff --git a/x/incentive/keeper/btc_timestamping_gauge_test.go b/x/incentive/keeper/btc_timestamping_gauge_test.go index d7173a3ac..3b42cae19 100644 --- a/x/incentive/keeper/btc_timestamping_gauge_test.go +++ b/x/incentive/keeper/btc_timestamping_gauge_test.go @@ -98,7 +98,7 @@ func FuzzRewardBTCTimestamping(f *testing.F) { } } - // distribute rewards in the gauge to BTC validators/delegations + // distribute rewards in the gauge to reporters/submitters keeper.RewardBTCTimestamping(ctx, epoch, rdi) // assert consistency between reward map and reward gauge diff --git a/x/incentive/keeper/reward_gauge.go b/x/incentive/keeper/reward_gauge.go index f9dd70f71..d06cbf3fd 100644 --- a/x/incentive/keeper/reward_gauge.go +++ b/x/incentive/keeper/reward_gauge.go @@ -66,7 +66,7 @@ func (k Keeper) GetRewardGauge(ctx context.Context, sType types.StakeholderType, } // rewardGaugeStore returns the KVStore of the reward gauge of a stakeholder -// of a given type {submitter, reporter, BTC validator, BTC delegation} +// of a given type {submitter, reporter, finality provider, BTC delegation} // prefix: RewardGaugeKey // key: (stakeholder type || stakeholder address) // value: reward gauge diff --git a/x/incentive/types/incentive.go b/x/incentive/types/incentive.go index 6f76f0acf..9b0184cb8 100644 --- a/x/incentive/types/incentive.go +++ b/x/incentive/types/incentive.go @@ -61,12 +61,12 @@ type StakeholderType byte const ( SubmitterType StakeholderType = iota ReporterType - BTCValidatorType + FinalityProviderType BTCDelegationType ) func GetAllStakeholderTypes() []StakeholderType { - return []StakeholderType{SubmitterType, ReporterType, BTCValidatorType, BTCDelegationType} + return []StakeholderType{SubmitterType, ReporterType, FinalityProviderType, BTCDelegationType} } func NewStakeHolderType(stBytes []byte) (StakeholderType, error) { @@ -77,8 +77,8 @@ func NewStakeHolderType(stBytes []byte) (StakeholderType, error) { return SubmitterType, nil } else if stBytes[0] == byte(ReporterType) { return ReporterType, nil - } else if stBytes[0] == byte(BTCValidatorType) { - return BTCValidatorType, nil + } else if stBytes[0] == byte(FinalityProviderType) { + return FinalityProviderType, nil } else if stBytes[0] == byte(BTCDelegationType) { return BTCDelegationType, nil } else { @@ -91,8 +91,8 @@ func NewStakeHolderTypeFromString(stStr string) (StakeholderType, error) { return SubmitterType, nil } else if stStr == "reporter" { return ReporterType, nil - } else if stStr == "btc_validator" { - return BTCValidatorType, nil + } else if stStr == "finality_provider" { + return FinalityProviderType, nil } else if stStr == "btc_delegation" { return BTCDelegationType, nil } else { @@ -109,8 +109,8 @@ func (st StakeholderType) String() string { return "submitter" } else if st == ReporterType { return "reporter" - } else if st == BTCValidatorType { - return "btc_validator" + } else if st == FinalityProviderType { + return "finality_provider" } else if st == BTCDelegationType { return "btc_delegation" } diff --git a/x/incentive/types/params.pb.go b/x/incentive/types/params.pb.go index fac26d98a..a0062b8d6 100644 --- a/x/incentive/types/params.pb.go +++ b/x/incentive/types/params.pb.go @@ -34,9 +34,9 @@ type Params struct { SubmitterPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=submitter_portion,json=submitterPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"submitter_portion"` // reporter_portion is the portion of rewards that goes to reporter ReporterPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=reporter_portion,json=reporterPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"reporter_portion"` - // btc_staking_portion is the portion of rewards that goes to BTC validators/delegations - // NOTE: the portion of each BTC validator/delegation is calculated by using its voting - // power and BTC validator's commission + // btc_staking_portion is the portion of rewards that goes to Finality Providers/delegations + // NOTE: the portion of each Finality Provider/delegation is calculated by using its voting + // power and finality provider's commission BtcStakingPortion cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=btc_staking_portion,json=btcStakingPortion,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"btc_staking_portion"` } diff --git a/x/incentive/types/tx.pb.go b/x/incentive/types/tx.pb.go index 5e8fc701b..dece25fd3 100644 --- a/x/incentive/types/tx.pb.go +++ b/x/incentive/types/tx.pb.go @@ -34,7 +34,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgWithdrawReward defines a message for withdrawing reward of a stakeholder. type MsgWithdrawReward struct { - // {submitter, reporter, btc_validator, btc_delegation} + // {submitter, reporter, finality_provider, btc_delegation} Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // address is the address of the stakeholder in bech32 string // signer of this msg has to be this address diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index a6c66e7dd..5018b27c9 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -10,7 +10,6 @@ import ( types1 "github.com/babylonchain/babylon/x/checkpointing/types" types "github.com/babylonchain/babylon/x/epoching/types" crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - _ "github.com/cometbft/cometbft/proto/tendermint/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" @@ -582,66 +581,65 @@ func init() { } var fileDescriptor_ab886e1868e5c5cd = []byte{ - // 934 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4f, 0x73, 0xdb, 0x44, - 0x14, 0xaf, 0x62, 0x27, 0x69, 0x9e, 0xe3, 0x36, 0x6c, 0x52, 0xaa, 0x06, 0x70, 0x3c, 0xee, 0x4c, - 0x71, 0x19, 0x90, 0xc7, 0x06, 0x0e, 0x70, 0xc3, 0x9e, 0x96, 0xa6, 0x30, 0x94, 0x91, 0xdd, 0xc2, - 0x30, 0x30, 0x1a, 0xfd, 0x59, 0x4b, 0x9a, 0xc8, 0x5a, 0x8f, 0xb4, 0x71, 0xe3, 0x7c, 0x8a, 0x7e, - 0x0b, 0xce, 0x7c, 0x00, 0xee, 0x1c, 0x7b, 0xe4, 0x06, 0x93, 0x7c, 0x05, 0x2e, 0xdc, 0x98, 0x7d, - 0xbb, 0x2b, 0x4b, 0xcd, 0x98, 0xc0, 0x45, 0xa3, 0xdd, 0xfd, 0xbd, 0xdf, 0xfb, 0xed, 0xfb, 0x27, - 0xc1, 0x87, 0x9e, 0xeb, 0x2d, 0x13, 0x96, 0xf6, 0xce, 0x59, 0x4a, 0x7d, 0x96, 0xfa, 0x31, 0xcd, - 0x42, 0xda, 0x5b, 0xf4, 0xab, 0x1b, 0xd6, 0x3c, 0x63, 0x9c, 0x11, 0x53, 0xa1, 0xad, 0xea, 0xe1, - 0xa2, 0x7f, 0x78, 0x10, 0xb2, 0x90, 0x21, 0xa8, 0x27, 0xde, 0x24, 0xfe, 0xf0, 0x28, 0x64, 0x2c, - 0x4c, 0x68, 0x0f, 0x57, 0xde, 0xe9, 0xb4, 0xc7, 0xe3, 0x19, 0xcd, 0xb9, 0x3b, 0x9b, 0x2b, 0xc0, - 0xbb, 0x9c, 0xa6, 0x01, 0xcd, 0x66, 0x71, 0xca, 0x7b, 0x7c, 0x39, 0xa7, 0xb9, 0x7c, 0xaa, 0xd3, - 0xf7, 0x4a, 0xa7, 0x7e, 0xb6, 0x9c, 0x73, 0x26, 0x98, 0xd8, 0x54, 0x1d, 0x17, 0xda, 0x3d, 0xee, - 0xfb, 0x11, 0xf5, 0x4f, 0xe6, 0x4c, 0x20, 0x17, 0xfd, 0xea, 0x86, 0x42, 0x3f, 0xd0, 0xe8, 0xd5, - 0x49, 0x9c, 0x86, 0x88, 0x4e, 0x72, 0xe7, 0x84, 0x2e, 0x15, 0xee, 0xe1, 0x5a, 0xdc, 0x15, 0xca, - 0x8e, 0x86, 0xd2, 0x39, 0xf3, 0x23, 0x85, 0xd2, 0xef, 0x0a, 0x63, 0x95, 0x44, 0x26, 0x71, 0x18, - 0x89, 0x27, 0x2d, 0x54, 0x96, 0x76, 0x24, 0xbe, 0xf3, 0xeb, 0x06, 0x34, 0x8f, 0xd3, 0x80, 0x9e, - 0xd1, 0xe0, 0x09, 0x75, 0x03, 0x9a, 0x91, 0x7b, 0x70, 0xd3, 0x8f, 0xdc, 0x38, 0x75, 0xe2, 0xc0, - 0x34, 0xda, 0x46, 0x77, 0xc7, 0xde, 0xc6, 0xf5, 0x71, 0x40, 0x08, 0xd4, 0x23, 0x37, 0x8f, 0xcc, - 0x8d, 0xb6, 0xd1, 0xdd, 0xb5, 0xf1, 0x9d, 0xbc, 0x0d, 0x5b, 0x11, 0x15, 0xb4, 0x66, 0xad, 0x6d, - 0x74, 0xeb, 0xb6, 0x5a, 0x91, 0x4f, 0xa0, 0x2e, 0xa2, 0x6f, 0xd6, 0xdb, 0x46, 0xb7, 0x31, 0x38, - 0xb4, 0x64, 0x6a, 0x2c, 0x9d, 0x1a, 0x6b, 0xa2, 0x53, 0x33, 0xac, 0xbf, 0xfa, 0xe3, 0xc8, 0xb0, - 0x11, 0x4d, 0x2c, 0xd8, 0x57, 0x17, 0x70, 0x22, 0x94, 0xe3, 0xa0, 0xc3, 0x4d, 0x74, 0xf8, 0x96, - 0x3a, 0x92, 0x42, 0x9f, 0x08, 0xef, 0x03, 0xb8, 0xf3, 0x26, 0x5e, 0x8a, 0xd9, 0x42, 0x31, 0xfb, - 0x55, 0x0b, 0xa9, 0xec, 0x3e, 0x34, 0xb5, 0x0d, 0x06, 0xcf, 0xdc, 0x46, 0xec, 0xae, 0xda, 0x7c, - 0x24, 0xf6, 0xc8, 0x03, 0xb8, 0xad, 0x41, 0xfc, 0x4c, 0x8a, 0xb8, 0x89, 0x22, 0xb4, 0xed, 0xe4, - 0x4c, 0x08, 0xe8, 0x3c, 0x85, 0xcd, 0xc7, 0x2c, 0x3b, 0xc9, 0xc9, 0x17, 0xb0, 0x2d, 0x15, 0xe4, - 0x66, 0xad, 0x5d, 0xeb, 0x36, 0x06, 0xef, 0x5b, 0xeb, 0xaa, 0xd7, 0xaa, 0x04, 0xdc, 0xd6, 0x76, - 0x9d, 0xbf, 0x0c, 0xd8, 0x19, 0x61, 0xa8, 0xd3, 0x29, 0xfb, 0xb7, 0x3c, 0x7c, 0x0d, 0xcd, 0xc4, - 0xe5, 0x34, 0xe7, 0xea, 0xd2, 0x98, 0x90, 0xff, 0xe1, 0x71, 0x57, 0x5a, 0xab, 0x84, 0x0f, 0x41, - 0xad, 0x9d, 0xa9, 0xb8, 0x09, 0xe6, 0xb1, 0x31, 0x38, 0x5a, 0x4f, 0x86, 0x17, 0xb6, 0x1b, 0xd2, - 0x48, 0xde, 0xfe, 0x73, 0xb8, 0x57, 0xf4, 0x1a, 0x0d, 0x94, 0xac, 0xdc, 0xf1, 0xd9, 0x69, 0xca, - 0xb1, 0x04, 0xea, 0xf6, 0xdd, 0x12, 0x40, 0x7a, 0xce, 0x47, 0xe2, 0xb8, 0xf3, 0x4b, 0x0d, 0xc8, - 0xe3, 0x38, 0x75, 0x93, 0xf8, 0x9c, 0x06, 0xff, 0xe9, 0xfe, 0xcf, 0xe1, 0x60, 0xaa, 0x0d, 0x1c, - 0x05, 0x4a, 0xa7, 0x4c, 0x85, 0xe1, 0xfe, 0x7a, 0xe5, 0x05, 0xbb, 0x4d, 0xa6, 0x57, 0x3d, 0x7e, - 0x06, 0x80, 0x05, 0x21, 0xc9, 0x6a, 0xaa, 0x70, 0x35, 0x59, 0xd1, 0x68, 0x8b, 0xbe, 0x85, 0x35, - 0x62, 0xef, 0xe0, 0x16, 0x9a, 0x7e, 0x03, 0xb7, 0x32, 0xf7, 0xa5, 0xb3, 0x6a, 0x59, 0x55, 0xf7, - 0xab, 0x94, 0x54, 0xda, 0x5b, 0x70, 0xd8, 0xee, 0xcb, 0x51, 0xb1, 0x67, 0x37, 0xb3, 0xf2, 0x92, - 0x3c, 0x07, 0xe2, 0x71, 0xdf, 0xc9, 0x4f, 0xbd, 0x59, 0x9c, 0xe7, 0x31, 0x4b, 0xc5, 0xc4, 0xc0, - 0x36, 0x28, 0x73, 0x56, 0xe7, 0xce, 0xa2, 0x6f, 0x8d, 0x0b, 0xfc, 0x57, 0x74, 0x69, 0xef, 0x79, - 0xdc, 0xaf, 0xec, 0x90, 0x2f, 0x61, 0x13, 0x27, 0x1a, 0xb6, 0x47, 0x63, 0xd0, 0x5f, 0x1f, 0xa9, - 0x6f, 0x05, 0xec, 0x6a, 0x56, 0x6c, 0x69, 0xdf, 0xf9, 0xdb, 0x80, 0x3d, 0x84, 0x60, 0x24, 0xc6, - 0xd4, 0x4d, 0x68, 0x40, 0x6c, 0x68, 0x2e, 0xdc, 0x24, 0x0e, 0x5c, 0xce, 0x32, 0x27, 0xa7, 0xdc, - 0x34, 0xb0, 0x11, 0x3e, 0x5a, 0x1f, 0x83, 0x17, 0x1a, 0xfe, 0x5d, 0xcc, 0xa3, 0x61, 0x92, 0x0b, - 0xd5, 0xbb, 0x05, 0xc7, 0x98, 0x72, 0xf2, 0x08, 0xf6, 0xd0, 0xa3, 0x53, 0xca, 0x8c, 0x4c, 0xf3, - 0x3b, 0xd6, 0x6a, 0x5c, 0x5b, 0x72, 0x5c, 0x4b, 0xd5, 0xcf, 0xe6, 0xb9, 0x7d, 0x6b, 0x5e, 0x88, - 0xc3, 0xfc, 0x3c, 0x85, 0xfd, 0x32, 0xcd, 0xc2, 0x4d, 0x50, 0x60, 0xed, 0x7a, 0xa6, 0xbd, 0x15, - 0xd3, 0x0b, 0x37, 0x19, 0x53, 0xde, 0xf9, 0x79, 0x03, 0xee, 0xae, 0x09, 0x0f, 0x19, 0x83, 0x29, - 0xfd, 0xf8, 0xe7, 0x7a, 0x20, 0xc5, 0x7a, 0xcc, 0x18, 0xd7, 0x3b, 0x3b, 0x40, 0xe3, 0xd1, 0xb9, - 0xec, 0x8f, 0x63, 0x35, 0x8b, 0xbe, 0x07, 0x52, 0x16, 0x9f, 0x63, 0xb4, 0x55, 0x14, 0x3e, 0xb8, - 0x26, 0x85, 0xa5, 0xfc, 0x94, 0xaf, 0xa2, 0x32, 0xf6, 0x13, 0xdc, 0xa9, 0x30, 0x8b, 0x62, 0xe1, - 0x9c, 0x06, 0x6a, 0x84, 0x3d, 0x5c, 0x5f, 0x69, 0x93, 0xcc, 0x4d, 0x73, 0xd7, 0xe7, 0x31, 0x93, - 0x75, 0xb1, 0x5f, 0xe2, 0xd6, 0x2c, 0x9d, 0x1f, 0xe1, 0xf6, 0x70, 0x32, 0xc2, 0xe8, 0x8c, 0x69, - 0x38, 0xa3, 0x29, 0x27, 0xc7, 0xd0, 0x10, 0x85, 0xad, 0x47, 0xa5, 0xac, 0x90, 0x6e, 0xd9, 0x4f, - 0xf9, 0x1b, 0xb5, 0xe8, 0x5b, 0xc3, 0xc9, 0x48, 0x47, 0x63, 0xca, 0x6c, 0xf0, 0xb8, 0xaf, 0x86, - 0xc7, 0xf0, 0xd9, 0x6f, 0x17, 0x2d, 0xe3, 0xf5, 0x45, 0xcb, 0xf8, 0xf3, 0xa2, 0x65, 0xbc, 0xba, - 0x6c, 0xdd, 0x78, 0x7d, 0xd9, 0xba, 0xf1, 0xfb, 0x65, 0xeb, 0xc6, 0x0f, 0x9f, 0x86, 0x31, 0x8f, - 0x4e, 0x3d, 0xcb, 0x67, 0xb3, 0x9e, 0x62, 0xc6, 0x29, 0xa1, 0x17, 0xbd, 0xb3, 0x37, 0xfe, 0x3f, - 0xf0, 0x2f, 0xc0, 0xdb, 0xc2, 0x8f, 0xd3, 0xc7, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x02, 0xb5, - 0x3e, 0x43, 0xa5, 0x08, 0x00, 0x00, + // 928 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xdf, 0x6e, 0x1b, 0xc5, + 0x17, 0xce, 0xc6, 0x4e, 0xd2, 0x1c, 0xc7, 0x6d, 0x7e, 0x93, 0xf4, 0xd7, 0x6d, 0x10, 0x8e, 0xe5, + 0x4a, 0xc5, 0x45, 0xb0, 0x96, 0x0d, 0x5c, 0xc0, 0x1d, 0xb6, 0x5a, 0x9a, 0x82, 0x28, 0x5a, 0xbb, + 0x05, 0x21, 0xd0, 0x6a, 0xff, 0x8c, 0x77, 0x57, 0x59, 0xef, 0x58, 0xbb, 0x13, 0x37, 0xce, 0x53, + 0xf4, 0x2d, 0xb8, 0xe6, 0x01, 0xb8, 0xe7, 0xb2, 0x97, 0xdc, 0x81, 0x92, 0x57, 0xe0, 0x86, 0x3b, + 0x34, 0x67, 0x66, 0xd6, 0xbb, 0x8d, 0x4c, 0xe0, 0x26, 0xda, 0x99, 0xf9, 0xce, 0x77, 0xbe, 0xf9, + 0xce, 0x99, 0xe3, 0xc0, 0x07, 0x9e, 0xeb, 0x2d, 0x13, 0x96, 0xf6, 0x2e, 0x58, 0x4a, 0x7d, 0x96, + 0xfa, 0x31, 0xcd, 0x42, 0xda, 0x5b, 0xf4, 0xab, 0x1b, 0xd6, 0x3c, 0x63, 0x9c, 0x11, 0x53, 0xa1, + 0xad, 0xea, 0xe1, 0xa2, 0x7f, 0x74, 0x18, 0xb2, 0x90, 0x21, 0xa8, 0x27, 0xbe, 0x24, 0xfe, 0xe8, + 0x38, 0x64, 0x2c, 0x4c, 0x68, 0x0f, 0x57, 0xde, 0xd9, 0xb4, 0xc7, 0xe3, 0x19, 0xcd, 0xb9, 0x3b, + 0x9b, 0x2b, 0xc0, 0xbb, 0x9c, 0xa6, 0x01, 0xcd, 0x66, 0x71, 0xca, 0x7b, 0x7e, 0xb6, 0x9c, 0x73, + 0x26, 0xb0, 0x6c, 0xaa, 0x8e, 0x0b, 0x75, 0x1e, 0xf7, 0xfd, 0x88, 0xfa, 0xa7, 0x73, 0x26, 0x90, + 0x8b, 0x7e, 0x75, 0x43, 0xa1, 0x1f, 0x6a, 0xf4, 0xea, 0x24, 0x4e, 0x43, 0x44, 0x27, 0xb9, 0x73, + 0x4a, 0x97, 0x0a, 0xf7, 0x68, 0x2d, 0xee, 0x1a, 0x65, 0x47, 0x43, 0xe9, 0x9c, 0xf9, 0x91, 0x42, + 0xe9, 0x6f, 0x85, 0xb1, 0x4a, 0x22, 0x93, 0x38, 0x8c, 0xc4, 0x5f, 0x5a, 0xa8, 0x2c, 0xed, 0x48, + 0x7c, 0xe7, 0x97, 0x4d, 0x68, 0x9e, 0xa4, 0x01, 0x3d, 0xa7, 0xc1, 0x53, 0xea, 0x06, 0x34, 0x23, + 0xf7, 0xe1, 0x96, 0x1f, 0xb9, 0x71, 0xea, 0xc4, 0x81, 0x69, 0xb4, 0x8d, 0xee, 0xae, 0xbd, 0x83, + 0xeb, 0x93, 0x80, 0x10, 0xa8, 0x47, 0x6e, 0x1e, 0x99, 0x9b, 0x6d, 0xa3, 0xbb, 0x67, 0xe3, 0x37, + 0xf9, 0x3f, 0x6c, 0x47, 0x54, 0xd0, 0x9a, 0xb5, 0xb6, 0xd1, 0xad, 0xdb, 0x6a, 0x45, 0x3e, 0x86, + 0xba, 0xf0, 0xd7, 0xac, 0xb7, 0x8d, 0x6e, 0x63, 0x70, 0x64, 0x49, 0xf3, 0x2d, 0x6d, 0xbe, 0x35, + 0xd1, 0xe6, 0x0f, 0xeb, 0xaf, 0x7f, 0x3f, 0x36, 0x6c, 0x44, 0x13, 0x0b, 0x0e, 0xd4, 0x05, 0x9c, + 0x08, 0xe5, 0x38, 0x98, 0x70, 0x0b, 0x13, 0xfe, 0x4f, 0x1d, 0x49, 0xa1, 0x4f, 0x45, 0xf6, 0x01, + 0xdc, 0x7d, 0x1b, 0x2f, 0xc5, 0x6c, 0xa3, 0x98, 0x83, 0x6a, 0x84, 0x54, 0xf6, 0x00, 0x9a, 0x3a, + 0x06, 0xcd, 0x33, 0x77, 0x10, 0xbb, 0xa7, 0x36, 0x1f, 0x8b, 0x3d, 0xf2, 0x10, 0xee, 0x68, 0x10, + 0x3f, 0x97, 0x22, 0x6e, 0xa1, 0x08, 0x1d, 0x3b, 0x39, 0x17, 0x02, 0x3a, 0xcf, 0x60, 0xeb, 0x09, + 0xcb, 0x4e, 0x73, 0xf2, 0x39, 0xec, 0x48, 0x05, 0xb9, 0x59, 0x6b, 0xd7, 0xba, 0x8d, 0xc1, 0x7b, + 0xd6, 0xba, 0xfe, 0xb4, 0x2a, 0x86, 0xdb, 0x3a, 0xae, 0xf3, 0xa7, 0x01, 0xbb, 0x23, 0xb4, 0x3a, + 0x9d, 0xb2, 0x7f, 0xaa, 0xc3, 0x57, 0xd0, 0x4c, 0x5c, 0x4e, 0x73, 0xae, 0x2e, 0x8d, 0x05, 0xf9, + 0x0f, 0x19, 0xf7, 0x64, 0xb4, 0x2a, 0xf8, 0x10, 0xd4, 0xda, 0x99, 0x8a, 0x9b, 0x60, 0x1d, 0x1b, + 0x83, 0xe3, 0xf5, 0x64, 0x78, 0x61, 0xbb, 0x21, 0x83, 0xe4, 0xed, 0x3f, 0x83, 0xfb, 0xc5, 0x6b, + 0xa2, 0x81, 0x92, 0x95, 0x3b, 0x3e, 0x3b, 0x4b, 0x39, 0xb6, 0x40, 0xdd, 0xbe, 0x57, 0x02, 0xc8, + 0xcc, 0xf9, 0x48, 0x1c, 0x77, 0x7e, 0xae, 0x01, 0x79, 0x12, 0xa7, 0x6e, 0x12, 0x5f, 0xd0, 0xe0, + 0x5f, 0xdd, 0xff, 0x05, 0x1c, 0x4e, 0x75, 0x80, 0xa3, 0x40, 0xe9, 0x94, 0x29, 0x1b, 0x1e, 0xac, + 0x57, 0x5e, 0xb0, 0xdb, 0x64, 0x7a, 0x3d, 0xe3, 0xa7, 0x00, 0xd8, 0x10, 0x92, 0xac, 0xa6, 0x1a, + 0x57, 0x93, 0x15, 0x0f, 0x6d, 0xd1, 0xb7, 0xb0, 0x47, 0xec, 0x5d, 0xdc, 0xc2, 0xd0, 0xaf, 0xe1, + 0x76, 0xe6, 0xbe, 0x72, 0x56, 0x4f, 0x56, 0xf5, 0xfd, 0xaa, 0x24, 0x95, 0xe7, 0x2d, 0x38, 0x6c, + 0xf7, 0xd5, 0xa8, 0xd8, 0xb3, 0x9b, 0x59, 0x79, 0x49, 0x5e, 0x00, 0xf1, 0xb8, 0xef, 0xe4, 0x67, + 0xde, 0x2c, 0xce, 0xf3, 0x98, 0xa5, 0x62, 0x62, 0xe0, 0x33, 0x28, 0x73, 0x56, 0xe7, 0xce, 0xa2, + 0x6f, 0x8d, 0x0b, 0xfc, 0x97, 0x74, 0x69, 0xef, 0x7b, 0xdc, 0xaf, 0xec, 0x90, 0x2f, 0x60, 0x0b, + 0x27, 0x1a, 0x3e, 0x8f, 0xc6, 0xa0, 0xbf, 0xde, 0xa9, 0x6f, 0x04, 0xec, 0x7a, 0x55, 0x6c, 0x19, + 0xdf, 0xf9, 0xcb, 0x80, 0x7d, 0x84, 0xa0, 0x13, 0x63, 0xea, 0x26, 0x34, 0x20, 0x36, 0x34, 0x17, + 0x6e, 0x12, 0x07, 0x2e, 0x67, 0x99, 0x93, 0x53, 0x6e, 0x1a, 0xf8, 0x10, 0x3e, 0x5c, 0xef, 0xc1, + 0x4b, 0x0d, 0xff, 0x36, 0xe6, 0xd1, 0x30, 0xc9, 0x85, 0xea, 0xbd, 0x82, 0x63, 0x4c, 0x39, 0x79, + 0x0c, 0xfb, 0x98, 0xd1, 0x29, 0x55, 0x46, 0x96, 0xf9, 0x1d, 0x6b, 0x35, 0xae, 0x2d, 0x39, 0xae, + 0xa5, 0xea, 0xe7, 0xf3, 0xdc, 0xbe, 0x3d, 0x2f, 0xc4, 0x61, 0x7d, 0x9e, 0xc1, 0x41, 0x99, 0x66, + 0xe1, 0x26, 0x28, 0xb0, 0x76, 0x33, 0xd3, 0xfe, 0x8a, 0xe9, 0xa5, 0x9b, 0x8c, 0x29, 0xef, 0xfc, + 0xb4, 0x09, 0xf7, 0xd6, 0xd8, 0x43, 0xc6, 0x60, 0xca, 0x3c, 0xfe, 0x85, 0x1e, 0x48, 0xb1, 0x1e, + 0x33, 0xc6, 0xcd, 0xc9, 0x0e, 0x31, 0x78, 0x74, 0x21, 0xdf, 0xc7, 0x89, 0x9a, 0x45, 0xdf, 0x01, + 0x29, 0x8b, 0xcf, 0xd1, 0x6d, 0xe5, 0xc2, 0xfb, 0x37, 0x94, 0xb0, 0x54, 0x9f, 0xf2, 0x55, 0x54, + 0xc5, 0x7e, 0x84, 0xbb, 0x15, 0x66, 0xd1, 0x2c, 0x9c, 0xd3, 0x40, 0x8d, 0xb0, 0x47, 0xeb, 0x3b, + 0x6d, 0x92, 0xb9, 0x69, 0xee, 0xfa, 0x3c, 0x66, 0xb2, 0x2f, 0x0e, 0x4a, 0xdc, 0x9a, 0xa5, 0xf3, + 0x03, 0xdc, 0x19, 0x4e, 0x46, 0xe8, 0xce, 0x98, 0x86, 0x33, 0x9a, 0x72, 0x72, 0x02, 0x0d, 0xd1, + 0xd8, 0x7a, 0x54, 0xca, 0x0e, 0xe9, 0x96, 0xf3, 0x94, 0x7f, 0xa3, 0x16, 0x7d, 0x6b, 0x38, 0x19, + 0x69, 0x37, 0xa6, 0xcc, 0x06, 0x8f, 0xfb, 0x6a, 0x78, 0x0c, 0x9f, 0xff, 0x7a, 0xd9, 0x32, 0xde, + 0x5c, 0xb6, 0x8c, 0x3f, 0x2e, 0x5b, 0xc6, 0xeb, 0xab, 0xd6, 0xc6, 0x9b, 0xab, 0xd6, 0xc6, 0x6f, + 0x57, 0xad, 0x8d, 0xef, 0x3f, 0x09, 0x63, 0x1e, 0x9d, 0x79, 0x96, 0xcf, 0x66, 0x3d, 0xc5, 0x8c, + 0x53, 0x42, 0x2f, 0x7a, 0xe7, 0x6f, 0xfd, 0x87, 0xc1, 0x97, 0x73, 0x9a, 0x7b, 0xdb, 0xf8, 0xe3, + 0xf4, 0xd1, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x76, 0x46, 0x5d, 0x86, 0x87, 0x08, 0x00, 0x00, } func (m *IndexedHeader) Marshal() (dAtA []byte, err error) { From ad315f598d8e1860077850111c36f037d3f2c6a8 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 15 Dec 2023 10:21:47 +1100 Subject: [PATCH 129/202] testing infra for modules and fuzz tests on Cosmos store (#149) --- testutil/datagen/tendermint.go | 13 +++ testutil/helper/README.md | 3 + .../helper}/helper.go | 51 +++++------- .../helper}/validator.go | 2 +- x/checkpointing/keeper/grpc_query_bls_test.go | 6 +- x/checkpointing/keeper/msg_server_test.go | 38 ++++----- x/checkpointing/keeper/val_bls_set_test.go | 6 +- x/checkpointing/testckpt/helper.go | 81 ------------------- x/epoching/keeper/apphash_chain_test.go | 6 +- x/epoching/keeper/epoch_msg_queue_test.go | 50 ++++++------ .../keeper/epoch_slashed_val_set_test.go | 6 +- x/epoching/keeper/epoch_val_set_test.go | 11 +-- x/epoching/keeper/epochs_test.go | 6 +- x/epoching/keeper/grpc_query_test.go | 20 ++--- x/epoching/keeper/keeper_test.go | 8 +- x/epoching/keeper/msg_server_test.go | 10 +-- x/epoching/testepoching/README.md | 3 - .../keeper/proof_cz_header_in_epoch_test.go | 66 +++++++++++++++ x/zoneconcierge/keeper/proof_epoch_sealed.go | 50 ++++++++---- .../keeper/proof_epoch_sealed_test.go | 73 ++++++++++++++++- x/zoneconcierge/keeper/query_kvstore.go | 13 ++- 21 files changed, 304 insertions(+), 218 deletions(-) create mode 100644 testutil/helper/README.md rename {x/epoching/testepoching => testutil/helper}/helper.go (93%) rename {x/epoching/testepoching => testutil/helper}/validator.go (96%) delete mode 100644 x/checkpointing/testckpt/helper.go delete mode 100644 x/epoching/testepoching/README.md create mode 100644 x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go diff --git a/testutil/datagen/tendermint.go b/testutil/datagen/tendermint.go index b3c2365fa..8ad0ee7f5 100644 --- a/testutil/datagen/tendermint.go +++ b/testutil/datagen/tendermint.go @@ -4,6 +4,7 @@ import ( "math/rand" "time" + "cosmossdk.io/core/header" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -31,10 +32,22 @@ func GenRandomIBCTMHeader(r *rand.Rand, chainID string, height uint64) *ibctmtyp } } +func GenRandomTMHeaderInfo(r *rand.Rand, chainID string, height uint64) *header.Info { + tmHeader := GenRandomIBCTMHeader(r, chainID, height) + return &header.Info{ + Height: tmHeader.Header.Height, + Hash: tmHeader.Header.DataHash, + Time: tmHeader.Header.Time, + ChainID: tmHeader.Header.ChainID, + AppHash: tmHeader.Header.AppHash, + } +} + func HeaderToHeaderInfo(header *ibctmtypes.Header) *zctypes.HeaderInfo { return &zctypes.HeaderInfo{ AppHash: header.Header.AppHash, ChainId: header.Header.ChainID, + Time: header.Header.Time, Height: uint64(header.Header.Height), } } diff --git a/testutil/helper/README.md b/testutil/helper/README.md new file mode 100644 index 000000000..8968fde79 --- /dev/null +++ b/testutil/helper/README.md @@ -0,0 +1,3 @@ +# helper + +The code under this directory provides infrastructure for testing the Babylon app. The code is adapted from https://github.com/cosmos/cosmos-sdk/tree/v0.45.5/x/staking/teststaking \ No newline at end of file diff --git a/x/epoching/testepoching/helper.go b/testutil/helper/helper.go similarity index 93% rename from x/epoching/testepoching/helper.go rename to testutil/helper/helper.go index df6f77915..6c923ec70 100644 --- a/x/epoching/testepoching/helper.go +++ b/testutil/helper/helper.go @@ -1,36 +1,31 @@ -package testepoching +package helper import ( "math/rand" "testing" "cosmossdk.io/core/header" + "cosmossdk.io/math" + "github.com/babylonchain/babylon/app" + appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/privval" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/babylonchain/babylon/x/epoching/keeper" + "github.com/babylonchain/babylon/x/epoching/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/ed25519" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - - "cosmossdk.io/math" - proto "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - - appparams "github.com/babylonchain/babylon/app/params" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/babylonchain/babylon/app" - "github.com/babylonchain/babylon/x/epoching/keeper" - "github.com/babylonchain/babylon/x/epoching/types" + proto "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" ) type GenesisValidators struct { @@ -42,12 +37,10 @@ type GenesisValidators struct { type Helper struct { t *testing.T - Ctx sdk.Context - App *app.BabylonApp - EpochingKeeper *keeper.Keeper - MsgSrvr types.MsgServer - QueryClient types.QueryClient - StakingKeeper *stakingkeeper.Keeper + Ctx sdk.Context + App *app.BabylonApp + MsgSrvr types.MsgServer + QueryClient types.QueryClient GenAccs []authtypes.GenesisAccount GenValidators *GenesisValidators @@ -79,10 +72,8 @@ func NewHelper(t *testing.T) *Helper { t, ctx, app, - &epochingKeeper, msgSrvr, queryClient, - app.StakingKeeper, nil, valSet, } @@ -120,19 +111,21 @@ func NewHelperWithValSet(t *testing.T) *Helper { t, ctx, app, - &epochingKeeper, msgSrvr, queryClient, - app.StakingKeeper, GenAccs, valSet, } } +func (h *Helper) NoError(err error) { + require.NoError(h.t, err) +} + // GenAndApplyEmptyBlock generates a new empty block and appends it to the current blockchain func (h *Helper) GenAndApplyEmptyBlock(r *rand.Rand) (sdk.Context, error) { newHeight := h.App.LastBlockHeight() + 1 - valSet, err := h.StakingKeeper.GetLastValidators(h.Ctx) + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) if err != nil { return sdk.Context{}, err } @@ -184,7 +177,7 @@ func (h *Helper) WrappedDelegate(delegator sdk.AccAddress, val sdk.ValAddress, a // WrappedDelegateWithPower calls handler to delegate stake for a validator func (h *Helper) WrappedDelegateWithPower(delegator sdk.AccAddress, val sdk.ValAddress, power int64) *sdk.Result { - coin := sdk.NewCoin(appparams.DefaultBondDenom, h.StakingKeeper.TokensFromConsensusPower(h.Ctx, power)) + coin := sdk.NewCoin(appparams.DefaultBondDenom, h.App.StakingKeeper.TokensFromConsensusPower(h.Ctx, power)) msg := stakingtypes.NewMsgDelegate(delegator.String(), val.String(), coin) wmsg := types.NewMsgWrappedDelegate(msg) return h.Handle(func(ctx sdk.Context) (proto.Message, error) { @@ -236,7 +229,7 @@ func (h *Helper) Handle(action func(sdk.Context) (proto.Message, error)) *sdk.Re // CheckValidator asserts that a validor exists and has a given status (if status!="") // and if has a right jailed flag. func (h *Helper) CheckValidator(addr sdk.ValAddress, status stakingtypes.BondStatus, jailed bool) stakingtypes.Validator { - v, err := h.StakingKeeper.GetValidator(h.Ctx, addr) + v, err := h.App.StakingKeeper.GetValidator(h.Ctx, addr) require.NoError(h.t, err) require.Equal(h.t, jailed, v.Jailed, "wrong Jalied status") if status >= 0 { @@ -247,7 +240,7 @@ func (h *Helper) CheckValidator(addr sdk.ValAddress, status stakingtypes.BondSta // CheckDelegator asserts that a delegator exists func (h *Helper) CheckDelegator(delegator sdk.AccAddress, val sdk.ValAddress, found bool) { - _, ok := h.StakingKeeper.GetDelegation(h.Ctx, delegator, val) + _, ok := h.App.StakingKeeper.GetDelegation(h.Ctx, delegator, val) require.Equal(h.t, ok, found) } diff --git a/x/epoching/testepoching/validator.go b/testutil/helper/validator.go similarity index 96% rename from x/epoching/testepoching/validator.go rename to testutil/helper/validator.go index 353bdcf1d..ab7ba4c65 100644 --- a/x/epoching/testepoching/validator.go +++ b/testutil/helper/validator.go @@ -1,4 +1,4 @@ -package testepoching +package helper import ( "github.com/cometbft/cometbft/crypto/merkle" diff --git a/x/checkpointing/keeper/grpc_query_bls_test.go b/x/checkpointing/keeper/grpc_query_bls_test.go index c76784d19..6ba88caeb 100644 --- a/x/checkpointing/keeper/grpc_query_bls_test.go +++ b/x/checkpointing/keeper/grpc_query_bls_test.go @@ -8,9 +8,9 @@ import ( "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" @@ -25,9 +25,9 @@ func FuzzQueryBLSKeySet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generated for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, ck) diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index fdbb996db..6fa11c649 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -16,9 +16,9 @@ import ( "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/privval" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/babylonchain/babylon/x/epoching/testepoching" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" ) @@ -32,9 +32,9 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generate for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -79,13 +79,13 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { // ensure all validators (not just validators in the val set) have correct bond status // - the 1st validator is bonded // - all the rest are unbonded since they have zero voting power - iterator, err := helper.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + iterator, err := helper.App.StakingKeeper.ValidatorsPowerStoreIterator(ctx) require.NoError(t, err) defer iterator.Close() count := 0 for ; iterator.Valid(); iterator.Next() { valAddr := sdk.ValAddress(iterator.Value()) - val, err := helper.StakingKeeper.GetValidator(ctx, valAddr) + val, err := helper.App.StakingKeeper.GetValidator(ctx, valAddr) require.NoError(t, err) count++ if count == 1 { @@ -107,9 +107,9 @@ func FuzzWrappedCreateValidator_InsufficientBalance(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generate for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -148,9 +148,9 @@ func FuzzWrappedCreateValidator(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generate for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -214,9 +214,9 @@ func FuzzAddBlsSig_NoError(f *testing.F) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generate for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -268,9 +268,9 @@ func FuzzAddBlsSig_NotInValSet(f *testing.F) { r := rand.New(rand.NewSource(seed)) var err error - helper := testepoching.NewHelperWithValSet(t) + helper := testhelper.NewHelperWithValSet(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -308,9 +308,9 @@ func FuzzAddBlsSig_CkptNotExist(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) + helper := testhelper.NewHelperWithValSet(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -345,9 +345,9 @@ func FuzzAddBlsSig_WrongAppHash(f *testing.F) { r := rand.New(rand.NewSource(seed)) var err error - helper := testepoching.NewHelperWithValSet(t) + helper := testhelper.NewHelperWithValSet(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) @@ -390,8 +390,8 @@ func FuzzAddBlsSig_InvalidSignature(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) - ek := helper.EpochingKeeper + helper := testhelper.NewHelperWithValSet(t) + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper msgServer := checkpointingkeeper.NewMsgServerImpl(ck) ctx := helper.Ctx diff --git a/x/checkpointing/keeper/val_bls_set_test.go b/x/checkpointing/keeper/val_bls_set_test.go index 7853b2b59..e90b470f1 100644 --- a/x/checkpointing/keeper/val_bls_set_test.go +++ b/x/checkpointing/keeper/val_bls_set_test.go @@ -8,9 +8,9 @@ import ( "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/stretchr/testify/require" ) @@ -20,9 +20,9 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) // a genesis validator is generated for setup - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) ctx := helper.Ctx - ek := helper.EpochingKeeper + ek := helper.App.EpochingKeeper ck := helper.App.CheckpointingKeeper queryHelper := baseapp.NewQueryServerTestHelper(helper.Ctx, helper.App.InterfaceRegistry()) types.RegisterQueryServer(queryHelper, ck) diff --git a/x/checkpointing/testckpt/helper.go b/x/checkpointing/testckpt/helper.go deleted file mode 100644 index c832bc361..000000000 --- a/x/checkpointing/testckpt/helper.go +++ /dev/null @@ -1,81 +0,0 @@ -package testckpt - -import ( - "context" - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/babylonchain/babylon/app" - appparams "github.com/babylonchain/babylon/app/params" - "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/x/checkpointing/keeper" - "github.com/babylonchain/babylon/x/checkpointing/types" - epochingkeeper "github.com/babylonchain/babylon/x/epoching/keeper" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" -) - -// Helper is a structure which wraps the entire app and exposes functionalities for testing the epoching module -type Helper struct { - t *testing.T - - Ctx context.Context - App *app.BabylonApp - CheckpointingKeeper *keeper.Keeper - MsgSrvr types.MsgServer - QueryClient types.QueryClient - StakingKeeper *stakingkeeper.Keeper - EpochingKeeper *epochingkeeper.Keeper - - GenAccs []authtypes.GenesisAccount -} - -// CreateValidator calls handler to create a new staking validator -func (h *Helper) CreateValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount sdkmath.Int) { - coin := sdk.NewCoin(appparams.DefaultBondDenom, stakeAmount) - h.createValidator(addr, pk, blsPK, pop, coin) -} - -// CreateValidatorWithValPower calls handler to create a new staking validator with zero commission -func (h *Helper) CreateValidatorWithValPower(addr sdk.ValAddress, pk cryptotypes.PubKey, - blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, valPower int64) sdkmath.Int { - amount := h.StakingKeeper.TokensFromConsensusPower(h.Ctx, valPower) - coin := sdk.NewCoin(appparams.DefaultBondDenom, amount) - h.createValidator(addr, pk, blsPK, pop, coin) - return amount -} - -// CreateValidatorMsg returns a message used to create validator in this service. -func (h *Helper) CreateValidatorMsg(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, stakeAmount sdkmath.Int) *types.MsgWrappedCreateValidator { - coin := sdk.NewCoin(appparams.DefaultBondDenom, stakeAmount) - msg, err := stakingtypes.NewMsgCreateValidator(addr.String(), pk, coin, stakingtypes.Description{}, ZeroCommission(), sdkmath.OneInt()) - require.NoError(h.t, err) - wmsg, err := types.NewMsgWrappedCreateValidator(msg, blsPK, pop) - require.NoError(h.t, err) - return wmsg -} - -func (h *Helper) createValidator(addr sdk.ValAddress, pk cryptotypes.PubKey, blsPK *bls12381.PublicKey, pop *types.ProofOfPossession, coin sdk.Coin) { - h.Handle(func(ctx context.Context) (proto.Message, error) { - return h.CreateValidatorMsg(addr, pk, blsPK, pop, coin.Amount), nil - }) -} - -// Handle executes an action function with the Helper's context, wraps the result into an SDK service result, and performs two assertions before returning it -func (h *Helper) Handle(action func(context.Context) (proto.Message, error)) *sdk.Result { - res, err := action(h.Ctx) - r, _ := sdk.WrapServiceResult(sdk.UnwrapSDKContext(h.Ctx), res, err) - require.NotNil(h.t, r) - require.NoError(h.t, err) - return r -} - -// ZeroCommission constructs a commission rates with all zeros. -func ZeroCommission() stakingtypes.CommissionRates { - return stakingtypes.NewCommissionRates(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()) -} diff --git a/x/epoching/keeper/apphash_chain_test.go b/x/epoching/keeper/apphash_chain_test.go index fb7a23dab..29520fb04 100644 --- a/x/epoching/keeper/apphash_chain_test.go +++ b/x/epoching/keeper/apphash_chain_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/keeper" - "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/stretchr/testify/require" ) @@ -17,8 +17,8 @@ func FuzzAppHashChain(f *testing.F) { r := rand.New(rand.NewSource(seed)) var err error - helper := testepoching.NewHelper(t) - ctx, k := helper.Ctx, helper.EpochingKeeper + helper := testhelper.NewHelper(t) + ctx, k := helper.Ctx, helper.App.EpochingKeeper // ensure that the epoch info is correct at the genesis epoch := k.GetEpoch(ctx) require.Equal(t, epoch.EpochNumber, uint64(1)) diff --git a/x/epoching/keeper/epoch_msg_queue_test.go b/x/epoching/keeper/epoch_msg_queue_test.go index 7a0098ab1..5ebae2412 100644 --- a/x/epoching/keeper/epoch_msg_queue_test.go +++ b/x/epoching/keeper/epoch_msg_queue_test.go @@ -4,14 +4,12 @@ import ( "math/rand" "testing" - "github.com/babylonchain/babylon/testutil/datagen" - appparams "github.com/babylonchain/babylon/app/params" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/babylonchain/babylon/x/epoching/testepoching" + "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" ) @@ -25,8 +23,8 @@ func FuzzEnqueueMsg(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelper(t) - ctx, keeper := helper.Ctx, helper.EpochingKeeper + helper := testhelper.NewHelper(t) + ctx, keeper := helper.Ctx, helper.App.EpochingKeeper // ensure that the epoch msg queue is correct at the genesis require.Empty(t, keeper.GetCurrentEpochMsgs(ctx)) require.Equal(t, uint64(0), keeper.GetCurrentQueueLength(ctx)) @@ -66,8 +64,8 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) - ctx, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs + helper := testhelper.NewHelperWithValSet(t) + ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs params := keeper.GetParams(ctx) // get genesis account's address, whose holder will be the delegator @@ -115,7 +113,7 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { // ensure the voting power has been added w.r.t. the newly delegated tokens valPower2, err := keeper.GetCurrentValidatorVotingPower(ctx, val) require.NoError(t, err) - addedPower := helper.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewDels)) + addedPower := helper.App.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewDels)) require.Equal(t, valPower+addedPower, valPower2) }) } @@ -127,8 +125,8 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) - ctx, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs + helper := testhelper.NewHelperWithValSet(t) + ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator require.NotNil(t, genAccs) @@ -139,9 +137,9 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) - valSet1 := helper.EpochingKeeper.GetCurrentValidatorSet(helper.Ctx) + valSet1 := helper.App.EpochingKeeper.GetCurrentValidatorSet(helper.Ctx) val := valSet1[0].Addr // validator to be undelegated - valPower, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val) + valPower, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val) require.NoError(t, err) // ensure the validator's lifecycle data is generated @@ -175,13 +173,13 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { require.Empty(t, keeper.GetCurrentEpochMsgs(ctx)) // ensure the voting power has been reduced w.r.t. the unbonding tokens - valPower2, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val) + valPower2, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val) require.NoError(t, err) - reducedPower := helper.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewUndels)) + reducedPower := helper.App.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewUndels)) require.Equal(t, valPower-reducedPower, valPower2) // ensure the genesis account has these unbonding tokens - unbondingDels, err := helper.StakingKeeper.GetAllUnbondingDelegations(ctx, genAddr) + unbondingDels, err := helper.App.StakingKeeper.GetAllUnbondingDelegations(ctx, genAddr) require.NoError(t, err) require.Equal(t, 1, len(unbondingDels)) // there is only 1 type of tokens @@ -199,8 +197,8 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) - ctx, keeper, genAccs := helper.Ctx, helper.EpochingKeeper, helper.GenAccs + helper := testhelper.NewHelperWithValSet(t) + ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator require.NotNil(t, genAccs) @@ -211,14 +209,14 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { epoch := keeper.GetEpoch(ctx) require.Equal(t, uint64(1), epoch.EpochNumber) - valSet1 := helper.EpochingKeeper.GetCurrentValidatorSet(ctx) + valSet1 := helper.App.EpochingKeeper.GetCurrentValidatorSet(ctx) // 2 validators, where the operator will redelegate some token from val1 to val2 val1 := valSet1[0].Addr - val1Power, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val1) + val1Power, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val1) require.NoError(t, err) val2 := valSet1[1].Addr - val2Power, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val2) + val2Power, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val2) require.NoError(t, err) require.Equal(t, val1Power, val2Power) @@ -257,18 +255,18 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { // Note that in Cosmos SDK, redelegation happens upon the next `EndBlock`, rather than waiting for 14 days. // This is because redelegation does not affect PoS security: upon redelegation requests, no token is leaving the system. // SImilarly, in Babylon, redelegation happens unconditionally upon `EndEpoch`, rather than depending on checkpoint status. - val1Power2, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val1) + val1Power2, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val1) require.NoError(t, err) - val2Power2, err := helper.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val2) + val2Power2, err := helper.App.EpochingKeeper.GetCurrentValidatorVotingPower(ctx, val2) require.NoError(t, err) - redelegatedPower := helper.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewRedels)) + redelegatedPower := helper.App.StakingKeeper.TokensToConsensusPower(ctx, coinWithOnePower.Amount.MulRaw(numNewRedels)) // ensure the voting power of val1 has reduced require.Equal(t, val1Power-redelegatedPower, val1Power2) // ensure the voting power of val2 has increased require.Equal(t, val2Power+redelegatedPower, val2Power2) // ensure the genesis account has these redelegating tokens - redelegations, err := helper.StakingKeeper.GetAllRedelegations(ctx, genAddr, val1, val2) + redelegations, err := helper.App.StakingKeeper.GetAllRedelegations(ctx, genAddr, val1, val2) require.NoError(t, err) require.Equal(t, 1, len(redelegations)) // there is only 1 type of tokens require.Equal(t, numNewRedels, int64(len(redelegations[0].Entries))) // there are numNewRedels entries diff --git a/x/epoching/keeper/epoch_slashed_val_set_test.go b/x/epoching/keeper/epoch_slashed_val_set_test.go index f46006257..46c32b713 100644 --- a/x/epoching/keeper/epoch_slashed_val_set_test.go +++ b/x/epoching/keeper/epoch_slashed_val_set_test.go @@ -7,8 +7,8 @@ import ( sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" - "github.com/babylonchain/babylon/x/epoching/testepoching" "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -20,8 +20,8 @@ func FuzzSlashedValSet(f *testing.F) { r := rand.New(rand.NewSource(seed)) var err error - helper := testepoching.NewHelperWithValSet(t) - ctx, keeper, stakingKeeper := helper.Ctx, helper.EpochingKeeper, helper.StakingKeeper + helper := testhelper.NewHelperWithValSet(t) + ctx, keeper, stakingKeeper := helper.Ctx, helper.App.EpochingKeeper, helper.App.StakingKeeper getValSet := keeper.GetValidatorSet(ctx, 1) // slash a random subset of validators diff --git a/x/epoching/keeper/epoch_val_set_test.go b/x/epoching/keeper/epoch_val_set_test.go index bbab84b0b..95e8438e2 100644 --- a/x/epoching/keeper/epoch_val_set_test.go +++ b/x/epoching/keeper/epoch_val_set_test.go @@ -1,11 +1,12 @@ package keeper_test import ( - "github.com/babylonchain/babylon/testutil/datagen" "math/rand" "testing" - "github.com/babylonchain/babylon/x/epoching/testepoching" + "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -15,9 +16,9 @@ func FuzzEpochValSet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) - ctx, keeper := helper.Ctx, helper.EpochingKeeper - valSet, err := helper.StakingKeeper.GetLastValidators(helper.Ctx) + helper := testhelper.NewHelperWithValSet(t) + ctx, keeper := helper.Ctx, helper.App.EpochingKeeper + valSet, err := helper.App.StakingKeeper.GetLastValidators(helper.Ctx) require.NoError(t, err) getValSet := keeper.GetValidatorSet(ctx, 0) require.Equal(t, len(valSet), len(getValSet)) diff --git a/x/epoching/keeper/epochs_test.go b/x/epoching/keeper/epochs_test.go index 04551b988..7fece1ac0 100644 --- a/x/epoching/keeper/epochs_test.go +++ b/x/epoching/keeper/epochs_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/babylonchain/babylon/testutil/datagen" - "github.com/babylonchain/babylon/x/epoching/testepoching" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/stretchr/testify/require" ) @@ -15,8 +15,8 @@ func FuzzEpochs(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelper(t) - ctx, keeper := helper.Ctx, helper.EpochingKeeper + helper := testhelper.NewHelper(t) + ctx, keeper := helper.Ctx, helper.App.EpochingKeeper // ensure that the epoch info is correct at the genesis epoch := keeper.GetEpoch(ctx) require.Equal(t, epoch.EpochNumber, uint64(1)) diff --git a/x/epoching/keeper/grpc_query_test.go b/x/epoching/keeper/grpc_query_test.go index 4b6649705..c6bda89b4 100644 --- a/x/epoching/keeper/grpc_query_test.go +++ b/x/epoching/keeper/grpc_query_test.go @@ -8,7 +8,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/babylonchain/babylon/testutil/datagen" - "github.com/babylonchain/babylon/x/epoching/testepoching" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/stretchr/testify/require" @@ -37,8 +37,8 @@ func FuzzParamsQuery(f *testing.F) { params.EpochInterval = uint64(r.Int()) } - helper := testepoching.NewHelper(t) - ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient + helper := testhelper.NewHelper(t) + ctx, keeper, queryClient := helper.Ctx, helper.App.EpochingKeeper, helper.QueryClient // if setParamsFlag == 0, set params setParamsFlag := r.Intn(2) if setParamsFlag == 0 { @@ -70,8 +70,8 @@ func FuzzCurrentEpoch(f *testing.F) { increment := datagen.RandomInt(r, 100) + 1 - helper := testepoching.NewHelper(t) - ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient + helper := testhelper.NewHelper(t) + ctx, keeper, queryClient := helper.Ctx, helper.App.EpochingKeeper, helper.QueryClient epochInterval := keeper.GetParams(ctx).EpochInterval // starting from epoch 1 @@ -104,8 +104,8 @@ func FuzzEpochsInfo(f *testing.F) { numEpochs := datagen.RandomInt(r, 10) + 2 limit := datagen.RandomInt(r, 10) + 1 - helper := testepoching.NewHelper(t) - ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient + helper := testhelper.NewHelper(t) + ctx, keeper, queryClient := helper.Ctx, helper.App.EpochingKeeper, helper.QueryClient // enque the 1st block of the numEpochs'th epoch epochInterval := keeper.GetParams(ctx).EpochInterval @@ -145,8 +145,8 @@ func FuzzEpochMsgsQuery(f *testing.F) { limit := uint64(r.Int()%100) + 1 txidsMap := map[string]bool{} - helper := testepoching.NewHelper(t) - ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient + helper := testhelper.NewHelper(t) + ctx, keeper, queryClient := helper.Ctx, helper.App.EpochingKeeper, helper.QueryClient // enque a random number of msgs with random txids for i := uint64(0); i < numMsgs; i++ { txid := datagen.GenRandomByteArray(r, 32) @@ -193,7 +193,7 @@ func FuzzEpochValSetQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testepoching.NewHelperWithValSet(t) + helper := testhelper.NewHelperWithValSet(t) ctx, queryClient := helper.Ctx, helper.QueryClient limit := uint64(r.Int() % 100) diff --git a/x/epoching/keeper/keeper_test.go b/x/epoching/keeper/keeper_test.go index 807bdf5f5..80ea94d3b 100644 --- a/x/epoching/keeper/keeper_test.go +++ b/x/epoching/keeper/keeper_test.go @@ -5,19 +5,19 @@ import ( "github.com/stretchr/testify/require" - "github.com/babylonchain/babylon/x/epoching/testepoching" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" ) func TestParams(t *testing.T) { - helper := testepoching.NewHelper(t) - keeper := helper.EpochingKeeper + helper := testhelper.NewHelper(t) + keeper := helper.App.EpochingKeeper ctx := helper.Ctx expParams := types.DefaultParams() // check that the empty keeper loads the default - resParams := helper.EpochingKeeper.GetParams(ctx) + resParams := helper.App.EpochingKeeper.GetParams(ctx) require.True(t, expParams.Equal(resParams)) // modify a params, save, and retrieve diff --git a/x/epoching/keeper/msg_server_test.go b/x/epoching/keeper/msg_server_test.go index 266de27ef..05ce50669 100644 --- a/x/epoching/keeper/msg_server_test.go +++ b/x/epoching/keeper/msg_server_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/babylonchain/babylon/x/epoching/testepoching" + testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" @@ -14,7 +14,7 @@ import ( // TODO (fuzz tests): replace the following tests with fuzz ones func TestMsgWrappedDelegate(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs ctx, err := helper.GenAndApplyEmptyBlock(r) @@ -44,7 +44,7 @@ func TestMsgWrappedDelegate(t *testing.T) { func TestMsgWrappedUndelegate(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs ctx, err := helper.GenAndApplyEmptyBlock(r) @@ -74,7 +74,7 @@ func TestMsgWrappedUndelegate(t *testing.T) { func TestMsgWrappedBeginRedelegate(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs ctx, err := helper.GenAndApplyEmptyBlock(r) @@ -105,7 +105,7 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { func TestMsgWrappedCancelUnbondingDelegation(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - helper := testepoching.NewHelper(t) + helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs ctx, err := helper.GenAndApplyEmptyBlock(r) diff --git a/x/epoching/testepoching/README.md b/x/epoching/testepoching/README.md deleted file mode 100644 index f5ff163cc..000000000 --- a/x/epoching/testepoching/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# testepoching - -The code under this directory provides infrastructure for testing the epoching module, such as mocking staking module messages, parsing Tenderming/Cosmos validators, etc.. The code is adapted from https://github.com/cosmos/cosmos-sdk/tree/v0.45.5/x/staking/teststaking \ No newline at end of file diff --git a/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go b/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go new file mode 100644 index 000000000..4d9f8aed1 --- /dev/null +++ b/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go @@ -0,0 +1,66 @@ +package keeper_test + +import ( + "math/rand" + "testing" + + "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" + zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" +) + +func FuzzProofCZHeaderInEpoch(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + h := testhelper.NewHelper(t) + ek := h.App.EpochingKeeper + zck := h.App.ZoneConciergeKeeper + var err error + + // chain is at height 1 thus epoch 1 + + // enter the 1st block of epoch 2 + epochInterval := ek.GetParams(h.Ctx).EpochInterval + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + + // handle a random header from a random consumer chain + chainID := datagen.GenRandomHexStr(r, 10) + height := datagen.RandomInt(r, 100) + 1 + ibctmHeader := datagen.GenRandomIBCTMHeader(r, chainID, height) + headerInfo := datagen.HeaderToHeaderInfo(ibctmHeader) + zck.HandleHeaderWithValidCommit(h.Ctx, datagen.GenRandomByteArray(r, 32), headerInfo, false) + + // ensure the header is successfully inserted + indexedHeader, err := zck.GetHeader(h.Ctx, chainID, height) + h.NoError(err) + + // enter the 1st block of the next epoch + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + // sealer header hash is the AppHash of the 1st header in CometBFT + //, i.e., 2nd header captured at BeginBlock() of 2nd header, of an epoch + sealerHeaderHash := h.Ctx.HeaderInfo().AppHash + // seal last epoch + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + + epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) + h.NoError(err) + epochWithHeader.SealerHeaderHash = sealerHeaderHash + + // generate inclusion proof + proof, err := zck.ProveCZHeaderInEpoch(h.Ctx, indexedHeader, epochWithHeader) + h.NoError(err) + + // verify the inclusion proof + err = zckeeper.VerifyCZHeaderInEpoch(indexedHeader, epochWithHeader, proof) + h.NoError(err) + }) +} diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed.go b/x/zoneconcierge/keeper/proof_epoch_sealed.go index 98aa7231d..7f4984003 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed.go @@ -29,6 +29,27 @@ func (k Keeper) ProveEpochInfo(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, return proof, nil } +func VerifyEpochInfo(epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { + // get the Merkle root, i.e., the AppHash of the sealer header + root := epoch.SealerHeaderHash + + // Ensure The epoch medatata is committed to the app_hash of the sealer header + // NOTE: the proof is generated when sealer header is generated. At that time + // sealer header hash is not given to epoch metadata. Thus we need to clear the + // sealer header hash when verifying the proof. + epochNoSealerHeader := *epoch + epochNoSealerHeader.SealerHeaderHash = []byte{} + epochBytes, err := epochNoSealerHeader.Marshal() + if err != nil { + return err + } + if err := VerifyStore(root, epochingtypes.StoreKey, getEpochInfoKey(epoch.EpochNumber), epochBytes, proof); err != nil { + return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for epoch metadata: %v", err) + } + + return nil +} + func getValSetKey(epochNumber uint64) []byte { valSetKey := checkpointingtypes.ValidatorBlsKeySetPrefix valSetKey = append(valSetKey, sdk.Uint64ToBigEndian(epochNumber)...) @@ -44,6 +65,18 @@ func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, err return proof, nil } +func VerifyValSet(epoch *epochingtypes.Epoch, valSet *checkpointingtypes.ValidatorWithBlsKeySet, proof *tmcrypto.ProofOps) error { + valSetBytes, err := valSet.Marshal() + if err != nil { + return err + } + if err := VerifyStore(epoch.SealerHeaderHash, checkpointingtypes.StoreKey, getValSetKey(epoch.EpochNumber), valSetBytes, proof); err != nil { + return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for validator set: %v", err) + } + + return nil +} + // ProveEpochSealed proves an epoch has been sealed, i.e., // - the epoch's validator set has a valid multisig over the sealer header // - the epoch's validator set is committed to the sealer header's app_hash @@ -126,7 +159,7 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R /* Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || app_hash) in the raw checkpoint */ - valSet := checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} + valSet := &checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} // filter validator set that contributes to the signature signerSet, signerSetPower, err := valSet.FindSubsetWithPowerSum(rawCkpt.Bitmap) if err != nil { @@ -146,26 +179,15 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R return fmt.Errorf("BLS signature does not match the public key") } - // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeaderHash - // Ensure The epoch medatata is committed to the app_hash of the sealer header - epochBytes, err := epoch.Marshal() - if err != nil { + if err := VerifyEpochInfo(epoch, proof.ProofEpochInfo); err != nil { return err } - if err := VerifyStore(root, epochingtypes.StoreKey, getEpochInfoKey(epoch.EpochNumber), epochBytes, proof.ProofEpochInfo); err != nil { - return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for epoch metadata: %v", err) - } // Ensure The validator set is committed to the app_hash of the sealer header - valSetBytes, err := valSet.Marshal() - if err != nil { + if err := VerifyValSet(epoch, valSet, proof.ProofEpochValSet); err != nil { return err } - if err := VerifyStore(root, checkpointingtypes.StoreKey, getValSetKey(epoch.EpochNumber), valSetBytes, proof.ProofEpochValSet); err != nil { - return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for validator set: %v", err) - } return nil } diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go index dad1356aa..a1d619d0b 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_epoch_sealed_test.go @@ -6,6 +6,7 @@ import ( "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" testkeeper "github.com/babylonchain/babylon/testutil/keeper" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" @@ -102,20 +103,84 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { }) } -// - TODO: epoch metadata has a valid inclusion proof func FuzzProofEpochSealed_Epoch(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { - // r := rand.New(rand.NewSource(seed)) + r := rand.New(rand.NewSource(seed)) + h := testhelper.NewHelper(t) + ek := h.App.EpochingKeeper + zck := h.App.ZoneConciergeKeeper + var err error + + // chain is at height 1 + + // enter the 1st block of a random epoch + epochInterval := ek.GetParams(h.Ctx).EpochInterval + newEpochs := datagen.RandomInt(r, 10) + 2 + for i := 0; i < int(newEpochs); i++ { + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + } + + // seal the last epoch at the 2nd block of the current epoch + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + + // prove the inclusion of last epoch + lastEpochNumber := ek.GetEpoch(h.Ctx).EpochNumber - 1 + h.NoError(err) + lastEpoch, err := ek.GetHistoricalEpoch(h.Ctx, lastEpochNumber) + h.NoError(err) + proof, err := zck.ProveEpochInfo(lastEpoch) + h.NoError(err) + + // verify inclusion proof + err = zckeeper.VerifyEpochInfo(lastEpoch, proof) + h.NoError(err) }) } -// - TODO: BLS val set has a valid inclusion proof func FuzzProofEpochSealed_ValSet(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { - // r := rand.New(rand.NewSource(seed)) + r := rand.New(rand.NewSource(seed)) + h := testhelper.NewHelperWithValSet(t) + ek := h.App.EpochingKeeper + ck := h.App.CheckpointingKeeper + zck := h.App.ZoneConciergeKeeper + var err error + + // chain is at height 1 + + // enter the 1st block of a random epoch + epochInterval := ek.GetParams(h.Ctx).EpochInterval + newEpochs := datagen.RandomInt(r, 10) + 2 + for i := 0; i < int(newEpochs); i++ { + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + } + + // seal the last epoch at the 2nd block of the current epoch + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + + // prove the inclusion of last epoch + lastEpochNumber := ek.GetEpoch(h.Ctx).EpochNumber - 1 + h.NoError(err) + lastEpoch, err := ek.GetHistoricalEpoch(h.Ctx, lastEpochNumber) + h.NoError(err) + lastEpochValSet := ck.GetValidatorBlsKeySet(h.Ctx, lastEpochNumber) + proof, err := zck.ProveValSet(lastEpoch) + h.NoError(err) + + // verify inclusion proof + err = zckeeper.VerifyValSet(lastEpoch, lastEpochValSet, proof) + h.NoError(err) }) } diff --git a/x/zoneconcierge/keeper/query_kvstore.go b/x/zoneconcierge/keeper/query_kvstore.go index ad01a3260..5a571686b 100644 --- a/x/zoneconcierge/keeper/query_kvstore.go +++ b/x/zoneconcierge/keeper/query_kvstore.go @@ -1,9 +1,10 @@ package keeper import ( - storetypes "cosmossdk.io/store/types" "fmt" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/store/rootmulti" "github.com/cometbft/cometbft/crypto/merkle" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" @@ -51,5 +52,13 @@ func VerifyStore(root []byte, moduleStoreKey string, key []byte, value []byte, p keypath = keypath.AppendKey(key, merkle.KeyEncodingURL) keypathStr := keypath.String() - return prt.VerifyAbsence(proof, root, keypathStr) // TODO: verify value rather than just existence + // NOTE: the proof can specify verification rules, either only verifying the + // top Merkle root w.r.t. all KV pairs, or verifying every layer of Merkle root + // TODO: investigate how the verification rules are chosen when generating the + // proof + if err := prt.VerifyValue(proof, root, keypathStr, value); err != nil { + return prt.VerifyAbsence(proof, root, keypathStr) + } + + return nil } From 2da69e000511f548551d93f7de37a5b6965117e5 Mon Sep 17 00:00:00 2001 From: Gaki <153402253+GakiBash@users.noreply.github.com> Date: Sat, 16 Dec 2023 04:15:45 +0100 Subject: [PATCH 130/202] README: updating discord link (#402) update permanent discord link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e8dc4656..7f12eea64 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Bringing Bitcoin security to Cosmos and beyond. [![Website](https://badgen.net/badge/icon/website?label=)](https://babylonchain.io) [![Whitepaper](https://badgen.net/badge/icon/whitepaper?label=)](https://arxiv.org/abs/2207.08392) [![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/babylon_chain) -[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/babylonchain) +[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.com/invite/babylonglobal) [![Medium](https://badgen.net/badge/icon/medium?icon=medium&label)](https://medium.com/babylonchain-io) ## Build and install From fc9f92ea03f03ee1454be2cee38b37dd90d5593a Mon Sep 17 00:00:00 2001 From: Kunal Date: Mon, 18 Dec 2023 11:45:39 +1100 Subject: [PATCH 131/202] Updated testnet link (#404) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f12eea64..0d624d332 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ For the most up-to-date documentation please visit [docs.babylonchain.io](https: ## Joining the testnet -Please follow the instructions on the [Joining the Testnet documentation page](https://docs.babylonchain.io/docs/testnet/overview). +Please follow the instructions on the [Joining the Testnet documentation page](https://docs.babylonchain.io/docs/user-guides/btc-timestamping-testnet/overview). ## Contributing From ce3dc60d493c6c388282dcb1e64ccd2408c1bd43 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 18 Dec 2023 22:00:34 +1100 Subject: [PATCH 132/202] btcstaking: handling evidence for selective slashing launched by finality provider (#147) --- client/docs/swagger-ui/swagger.yaml | 468 ++++--------- proto/babylon/btcstaking/v1/btcstaking.proto | 19 + proto/babylon/btcstaking/v1/events.proto | 25 +- proto/babylon/btcstaking/v1/tx.proto | 26 +- x/btcstaking/client/cli/tx.go | 39 ++ x/btcstaking/keeper/msg_server.go | 113 ++-- x/btcstaking/keeper/msg_server_test.go | 158 ++++- x/btcstaking/types/btc_delegation.go | 56 +- x/btcstaking/types/btc_slashing_tx.go | 72 +- x/btcstaking/types/btc_undelegation.go | 23 +- x/btcstaking/types/btcstaking.pb.go | 436 +++++++++++-- x/btcstaking/types/events.pb.go | 427 ++++-------- x/btcstaking/types/tx.pb.go | 651 ++++++++++++++++--- 13 files changed, 1606 insertions(+), 907 deletions(-) diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 8239c4496..63c925bc8 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -7407,62 +7407,35 @@ paths: title: proof is the proof that the chain info is finalized type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries the - header is included + proof_cz_header_in_epoch is the proof that the CZ header + is timestamped - in a certain Babylon block + within a certain epoch type: object properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a - transaction in the Merkle tree. - proof_header_in_epoch: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon header - is in a certain + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: >- + ProofOp defines an operation used for calculating + Merkle root - epoch + The data could be arbitrary format, providing + nessecary data + + for example neighbouring node hash proof_epoch_sealed: title: proof_epoch_sealed is the proof that the epoch is sealed type: object @@ -8163,62 +8136,35 @@ paths: title: proof is the proof that the chain info is finalized type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries - the header is included + proof_cz_header_in_epoch is the proof that the CZ + header is timestamped - in a certain Babylon block - type: object - properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of - a transaction in the Merkle tree. - proof_header_in_epoch: + within a certain epoch type: object properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon - header is in a certain + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: >- + ProofOp defines an operation used for + calculating Merkle root + + The data could be arbitrary format, providing + nessecary data - epoch + for example neighbouring node hash proof_epoch_sealed: title: >- proof_epoch_sealed is the proof that the epoch is @@ -14562,62 +14508,34 @@ definitions: title: proof is the proof that the chain info is finalized type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries the header is - included + proof_cz_header_in_epoch is the proof that the CZ header is + timestamped - in a certain Babylon block - type: object - properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a transaction - in the Merkle tree. - proof_header_in_epoch: + within a certain epoch type: object properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon header is in a - certain + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: >- + ProofOp defines an operation used for calculating Merkle + root - epoch + The data could be arbitrary format, providing nessecary data + + for example neighbouring node hash proof_epoch_sealed: title: proof_epoch_sealed is the proof that the epoch is sealed type: object @@ -15024,62 +14942,31 @@ definitions: babylon.zoneconcierge.v1.ProofFinalizedChainInfo: type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries the header is - included + proof_cz_header_in_epoch is the proof that the CZ header is + timestamped - in a certain Babylon block - type: object - properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a transaction in - the Merkle tree. - proof_header_in_epoch: + within a certain epoch type: object properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon header is in a - certain - - epoch + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: |- + ProofOp defines an operation used for calculating Merkle root + The data could be arbitrary format, providing nessecary data + for example neighbouring node hash proof_epoch_sealed: title: proof_epoch_sealed is the proof that the epoch is sealed type: object @@ -15935,62 +15822,34 @@ definitions: title: proof is the proof that the chain info is finalized type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries the header is - included + proof_cz_header_in_epoch is the proof that the CZ header is + timestamped - in a certain Babylon block + within a certain epoch type: object properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a transaction - in the Merkle tree. - proof_header_in_epoch: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon header is in a - certain + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: >- + ProofOp defines an operation used for calculating Merkle + root - epoch + The data could be arbitrary format, providing nessecary data + + for example neighbouring node hash proof_epoch_sealed: title: proof_epoch_sealed is the proof that the epoch is sealed type: object @@ -16448,62 +16307,35 @@ definitions: title: proof is the proof that the chain info is finalized type: object properties: - proof_tx_in_block: + proof_cz_header_in_epoch: title: >- - proof_tx_in_block is the proof that tx that carries the - header is included + proof_cz_header_in_epoch is the proof that the CZ header is + timestamped - in a certain Babylon block + within a certain epoch type: object properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a - transaction in the Merkle tree. - proof_header_in_epoch: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: + ops: type: array items: - type: string - format: byte - title: >- - proof_header_in_epoch is the proof that the Babylon header - is in a certain + type: object + properties: + type: + type: string + key: + type: string + format: byte + data: + type: string + format: byte + title: >- + ProofOp defines an operation used for calculating + Merkle root - epoch + The data could be arbitrary format, providing + nessecary data + + for example neighbouring node hash proof_epoch_sealed: title: proof_epoch_sealed is the proof that the epoch is sealed type: object @@ -16990,23 +16822,6 @@ definitions: IBC packet becomes timeout, measured in seconds description: QueryParamsResponse is the response type for the Query/Params RPC method. - tendermint.crypto.Proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte tendermint.crypto.ProofOp: type: object properties: @@ -17043,32 +16858,3 @@ definitions: The data could be arbitrary format, providing nessecary data for example neighbouring node hash title: ProofOps is Merkle proof defined by the list of ProofOps - tendermint.types.TxProof: - type: object - properties: - root_hash: - type: string - format: byte - data: - type: string - format: byte - proof: - type: object - properties: - total: - type: string - format: int64 - index: - type: string - format: int64 - leaf_hash: - type: string - format: byte - aunts: - type: array - items: - type: string - format: byte - description: >- - TxProof represents a Merkle proof of the presence of a transaction in the - Merkle tree. diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f53aeca25..f9c8992d6 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -188,3 +188,22 @@ message CovenantAdaptorSignatures { // adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key repeated bytes adaptor_sigs = 2; } + +// SelectiveSlashingEvidence is the evidence that the finality provider +// selectively slashed a BTC delegation +// NOTE: it's possible that a slashed finality provider exploits the +// SelectiveSlashingEvidence endpoint while it is actually slashed due to +// equivocation. But such behaviour does not affect the system's security +// or gives any benefit for the adversary +message SelectiveSlashingEvidence { + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + string staking_tx_hash = 1; + // fp_btc_pk is the BTC PK of the finality provider who + // launches the selective slashing offence + bytes fp_btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; + // recovered_fp_btc_sk is the finality provider's BTC SK recovered from + // the covenant adaptor/Schnorr signature pair. It is the consequence + // of selective slashing. + bytes recovered_fp_btc_sk = 3; + } \ No newline at end of file diff --git a/proto/babylon/btcstaking/v1/events.proto b/proto/babylon/btcstaking/v1/events.proto index f447f4904..4aa422bb6 100644 --- a/proto/babylon/btcstaking/v1/events.proto +++ b/proto/babylon/btcstaking/v1/events.proto @@ -18,24 +18,6 @@ message EventNewBTCDelegation { BTCDelegation btc_del = 1; } // such that the BTC delegation starts to have voting power in its timelock period message EventActivateBTCDelegation { BTCDelegation btc_del = 1; } -// EventUnbondingBTCDelegation is the event emitted when receiving an unbonding request -// for an existing BTC delegation -message EventUnbondingBTCDelegation { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - bytes btc_pk = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // fp_btc_pk_list is the list of BIP-340 PKs of the BTC finality providers that - // this BTC delegation delegates to - // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple finality providers - repeated bytes fp_btc_pk_list = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; - // staking_tx_hash is the hash of the staking tx. - // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation - string staking_tx_hash = 3; - // unbonding_tx_hash is the hash of the unbonding tx. - string unbonding_tx_hash = 4; -} - // EventUnbondingBTCDelegation is the event emitted when an unbonding BTC delegation // receives all signatures needed for becoming unbonded message EventUnbondedBTCDelegation { @@ -55,3 +37,10 @@ message EventUnbondedBTCDelegation { // from_state is the last state the BTC delegation was at BTCDelegationStatus from_state = 5; } + +// EventSelectiveSlashing is the event emitted when an adversarial BTC +// validator selectively slashes a BTC delegation +message EventSelectiveSlashing { + // evidence is the evidence of selective slashing + SelectiveSlashingEvidence evidence = 1; +} diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index b52c7ca36..c55b37c1a 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -23,10 +23,13 @@ service Msg { rpc CreateBTCDelegation(MsgCreateBTCDelegation) returns (MsgCreateBTCDelegationResponse); // AddCovenantSigs handles signatures from a covenant member rpc AddCovenantSigs(MsgAddCovenantSigs) returns (MsgAddCovenantSigsResponse); - // UpdateParams updates the btcstaking module parameters. - rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); // BTCUndelegate handles a signature on unbonding tx from its delegator rpc BTCUndelegate(MsgBTCUndelegate) returns (MsgBTCUndelegateResponse); + // SelectiveSlashingEvidence handles the evidence of selective slashing launched + // by a finality provider + rpc SelectiveSlashingEvidence(MsgSelectiveSlashingEvidence) returns (MsgSelectiveSlashingEvidenceResponse); + // UpdateParams updates the btcstaking module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } // MsgCreateFinalityProvider is the message for creating a finality provider @@ -126,7 +129,6 @@ message MsgAddCovenantSigs { // MsgAddCovenantSigsResponse is the response for MsgAddCovenantSigs message MsgAddCovenantSigsResponse {} - // MsgBTCUndelegate is the message for handling signature on unbonding tx // from its delegator. This signature effectively proves that the delegator // wants to unbond this BTC delegation @@ -144,6 +146,24 @@ message MsgBTCUndelegate { // MsgBTCUndelegateResponse is the response for MsgBTCUndelegate message MsgBTCUndelegateResponse {} +// MsgSelectiveSlashingEvidence is the message for handling evidence of selective slashing +// launched by a finality provider +message MsgSelectiveSlashingEvidence { + option (cosmos.msg.v1.signer) = "signer"; + + string signer = 1; + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + string staking_tx_hash = 2; + // recovered_fp_btc_sk is the BTC SK of the finality provider who + // launches the selective slashing offence. The SK is recovered by + // using a covenant adaptor signature and the corresponding Schnorr + // signature + bytes recovered_fp_btc_sk = 3; +} +// MsgSelectiveSlashingEvidenceResponse is the response for MsgSelectiveSlashingEvidence +message MsgSelectiveSlashingEvidenceResponse {} + // MsgUpdateParams defines a message for updating btcstaking module parameters. message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; diff --git a/x/btcstaking/client/cli/tx.go b/x/btcstaking/client/cli/tx.go index c99fd18fd..8dc5e51f9 100644 --- a/x/btcstaking/client/cli/tx.go +++ b/x/btcstaking/client/cli/tx.go @@ -43,6 +43,7 @@ func GetTxCmd() *cobra.Command { NewCreateBTCDelegationCmd(), NewAddCovenantSigsCmd(), NewBTCUndelegateCmd(), + NewSelectiveSlashingEvidenceCmd(), ) return cmd @@ -364,3 +365,41 @@ func NewBTCUndelegateCmd() *cobra.Command { return cmd } + +func NewSelectiveSlashingEvidenceCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "selective-slashing-evidence [staking_tx_hash] [recovered_fp_btc_sk]", + Args: cobra.ExactArgs(2), + Short: "Add the recovered BTC SK of a finality provider that launched selective slashing offence.", + Long: strings.TrimSpace( + `Add the recovered BTC SK of a finality provider that launched selective slashing offence. The SK is recovered from a pair of Schnorr/adaptor signatures`, // TODO: example + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get staking tx hash + stakingTxHash := args[0] + + // get delegator signature for unbonding tx + fpSKBytes, err := hex.DecodeString(args[1]) + if err != nil { + return err + } + + msg := types.MsgSelectiveSlashingEvidence{ + Signer: clientCtx.FromAddress.String(), + StakingTxHash: stakingTxHash, + RecoveredFpBtcSk: fpSKBytes, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 265c744cd..f08391396 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -7,17 +7,15 @@ import ( sdkmath "cosmossdk.io/math" errorsmod "cosmossdk.io/errors" + "github.com/babylonchain/babylon/btcstaking" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - "github.com/babylonchain/babylon/btcstaking" - asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" - bbn "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/x/btcstaking/types" ) type msgServer struct { @@ -358,7 +356,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return &types.MsgCreateBTCDelegationResponse{}, nil } -// AddCovenantSig adds a signature from covenant to a BTC delegation +// AddCovenantSig adds signatures from covenants to a BTC delegation // TODO: refactor this handler. Now it's too convoluted func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCovenantSigs) (*types.MsgAddCovenantSigsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -403,10 +401,9 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove // this fails, it is a programming error panic(err) } - err = verifySlashingTxAdaptorSigs( + err = btcDel.SlashingTx.EncVerifyAdaptorSignatures( stakingInfo.StakingOutput, slashingSpendInfo, - btcDel.SlashingTx, req.Pk, btcDel.FpBtcPkList, req.SlashingTxSigs, @@ -465,10 +462,9 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove // this fails, it is a programming error panic(err) } - err = verifySlashingTxAdaptorSigs( + err = btcDel.BtcUndelegation.SlashingTx.EncVerifyAdaptorSignatures( unbondingOutput, unbondingSlashingSpendInfo, - btcDel.BtcUndelegation.SlashingTx, req.Pk, btcDel.FpBtcPkList, req.SlashingUnbondingTxSigs, @@ -497,7 +493,9 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove return &types.MsgAddCovenantSigsResponse{}, nil } -// AddCovenantSig adds a signature from covenant to a BTC delegation +// BTCUndelegate adds a signature on the unbonding tx from the BTC delegator +// this effectively proves that the BTC delegator wants to unbond and Babylon +// will consider its BTC delegation unbonded func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // basic stateless checks @@ -571,38 +569,63 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele return &types.MsgBTCUndelegateResponse{}, nil } -// verifySlashingTxAdaptorSigs verifies a list of adaptor signatures, each -// encrypted by a restaked finality provider PK and signed by the given PK, w.r.t. the -// given funding output (in staking or unbonding tx), slashing spend info and -// slashing tx -func verifySlashingTxAdaptorSigs( - fundingOut *wire.TxOut, - slashingSpendInfo *btcstaking.SpendInfo, - slashingTx *types.BTCSlashingTx, - pk *bbn.BIP340PubKey, - fpPKs []bbn.BIP340PubKey, - sigs [][]byte, -) error { - for i, sig := range sigs { - adaptorSig, err := asig.NewAdaptorSignatureFromBytes(sig) - if err != nil { - return err - } - encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPKs[i].MustToBTCPK()) - if err != nil { - return err - } - err = slashingTx.EncVerifyAdaptorSignature( - fundingOut.PkScript, - fundingOut.Value, - slashingSpendInfo.GetPkScriptPath(), - pk.MustToBTCPK(), - encKey, - adaptorSig, - ) - if err != nil { - return types.ErrInvalidCovenantSig.Wrapf("err: %v", err) - } +// SelectiveSlashingEvidence handles the evidence that a finality provider has +// selectively slashed a BTC delegation +func (ms msgServer) SelectiveSlashingEvidence(goCtx context.Context, req *types.MsgSelectiveSlashingEvidence) (*types.MsgSelectiveSlashingEvidenceResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + bsParams := ms.GetParams(ctx) + + // ensure BTC delegation exists + btcDel, err := ms.GetBTCDelegation(ctx, req.StakingTxHash) + if err != nil { + return nil, err } - return nil + // ensure the BTC delegation is active, or its BTC undelegation receives an + // unbonding signature from the staker + btcTip := ms.btclcKeeper.GetTipInfo(ctx) + wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout + covQuorum := bsParams.CovenantQuorum + if btcDel.GetStatus(btcTip.Height, wValue, covQuorum) != types.BTCDelegationStatus_ACTIVE && !btcDel.IsUnbondedEarly() { + return nil, types.ErrBTCDelegationNotFound.Wrap("a BTC delegation that is not active or unbonding early cannot be slashed") + } + + // decode the finality provider's BTC SK/PK + fpSK, fpPK := btcec.PrivKeyFromBytes(req.RecoveredFpBtcSk) + fpBTCPK := bbn.NewBIP340PubKeyFromBTCPK(fpPK) + + // ensure the BTC delegation is staked to the given finality provider + fpIdx := btcDel.GetFpIdx(fpBTCPK) + if fpIdx == -1 { + return nil, types.ErrFpNotFound.Wrapf("BTC delegation is not staked to the finality provider") + } + + // ensure the finality provider exists and is not slashed + fp, err := ms.GetFinalityProvider(ctx, fpBTCPK.MustMarshal()) + if err != nil { + panic(types.ErrFpNotFound.Wrapf("failing to find the finality provider with BTC delegations")) + } + if fp.IsSlashed() { + return nil, types.ErrFpAlreadySlashed + } + + // at this point, the finality provider must have done selective slashing and must be + // adversarial + + // slash the finality provider now + if err := ms.SlashFinalityProvider(ctx, fpBTCPK.MustMarshal()); err != nil { + panic(err) // failed to slash the finality provider, must be programming error + } + + // emit selective slashing event + evidence := &types.SelectiveSlashingEvidence{ + StakingTxHash: req.StakingTxHash, + FpBtcPk: fpBTCPK, + RecoveredFpBtcSk: fpSK.Serialize(), + } + event := &types.EventSelectiveSlashing{Evidence: evidence} + if err := sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(event); err != nil { + panic(fmt.Errorf("failed to emit EventSelectiveSlashing event: %w", err)) + } + + return &types.MsgSelectiveSlashingEvidenceResponse{}, nil } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index f5fd7d825..c7dfb1053 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -7,15 +7,10 @@ import ( "testing" "time" + "cosmossdk.io/core/header" sdkmath "cosmossdk.io/math" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" - + asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonchain/babylon/testutil/datagen" keepertest "github.com/babylonchain/babylon/testutil/keeper" bbn "github.com/babylonchain/babylon/types" @@ -23,6 +18,12 @@ import ( btclctypes "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/babylonchain/babylon/x/btcstaking/keeper" "github.com/babylonchain/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" ) type Helper struct { @@ -38,6 +39,7 @@ type Helper struct { func NewHelper(t *testing.T, btclcKeeper *types.MockBTCLightClientKeeper, btccKeeper *types.MockBtcCheckpointKeeper) *Helper { k, ctx := keepertest.BTCStakingKeeper(t, btclcKeeper, btccKeeper) + ctx = ctx.WithHeaderInfo(header.Info{Height: 1}) msgSrvr := keeper.NewMsgServerImpl(*k) return &Helper{ @@ -295,7 +297,7 @@ func (h *Helper) CreateCovenantSigs( */ actualDelWithCovenantSigs, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) h.NoError(err) - require.Equal(h.t, len(actualDelWithCovenantSigs.CovenantSigs), int(bsParams.CovenantQuorum)) // TODO: fix + require.Equal(h.t, len(actualDelWithCovenantSigs.CovenantSigs), int(bsParams.CovenantQuorum)) require.True(h.t, actualDelWithCovenantSigs.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum)) require.NotNil(h.t, actualDelWithCovenantSigs.BtcUndelegation) @@ -497,6 +499,146 @@ func FuzzBTCUndelegate(f *testing.F) { }) } +func FuzzSelectiveSlashing(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new finality provider + fpSK, fpPK, _ := h.CreateFinalityProvider(r) + fpBtcPk := bbn.NewBIP340PubKeyFromBTCPK(fpPK) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + stakingTxHash, _, _, msgCreateBTCDel := h.CreateDelegation( + r, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + ) + + // add covenant signatures to this BTC delegation + // so that the BTC delegation becomes bonded + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) + // now BTC delegation has all covenant signatures + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.True(t, actualDel.HasCovenantQuorums(bsParams.CovenantQuorum)) + + // submit evidence of selective slashing + msg := &types.MsgSelectiveSlashingEvidence{ + Signer: datagen.GenRandomAccount().Address, + StakingTxHash: actualDel.MustGetStakingTxHash().String(), + RecoveredFpBtcSk: fpSK.Serialize(), + } + _, err = h.MsgServer.SelectiveSlashingEvidence(h.Ctx, msg) + h.NoError(err) + + // ensure the finality provider is slashed + slashedFp, err := h.BTCStakingKeeper.GetFinalityProvider(h.Ctx, fpBtcPk.MustMarshal()) + h.NoError(err) + require.True(t, slashedFp.IsSlashed()) + }) +} + +func FuzzSelectiveSlashing_StakingTx(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new finality provider + fpSK, fpPK, _ := h.CreateFinalityProvider(r) + fpBtcPk := bbn.NewBIP340PubKeyFromBTCPK(fpPK) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + stakingTxHash, _, _, msgCreateBTCDel := h.CreateDelegation( + r, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + ) + + // add covenant signatures to this BTC delegation + // so that the BTC delegation becomes bonded + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + h.CreateCovenantSigs(r, covenantSKs, msgCreateBTCDel, actualDel) + // now BTC delegation has all covenant signatures + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.True(t, actualDel.HasCovenantQuorums(bsParams.CovenantQuorum)) + + // finality provider pulls off selective slashing by decrypting covenant's adaptor signature + // on the slashing tx + // choose a random covenant adaptor signature + covIdx := datagen.RandomInt(r, int(bsParams.CovenantQuorum)) + covPK := bbn.NewBIP340PubKeyFromBTCPK(covenantSKs[covIdx].PubKey()) + fpIdx := datagen.RandomInt(r, len(actualDel.FpBtcPkList)) + covASig, err := actualDel.GetCovSlashingAdaptorSig(covPK, int(fpIdx), bsParams.CovenantQuorum) + h.NoError(err) + + // finality provider decrypts the covenant signature + decKey, err := asig.NewDecyptionKeyFromBTCSK(fpSK) + h.NoError(err) + decryptedCovenantSig := bbn.NewBIP340SignatureFromBTCSig(covASig.Decrypt(decKey)) + + // recover the fpSK by using adaptor signature and decrypted Schnorr signature + recoveredFPDecKey := covASig.Recover(decryptedCovenantSig.MustToBTCSig()) + recoveredFPSK := recoveredFPDecKey.ToBTCSK() + // ensure the recovered finality provider SK is same as the real one + require.Equal(t, fpSK.Serialize(), recoveredFPSK.Serialize()) + + // submit evidence of selective slashing + msg := &types.MsgSelectiveSlashingEvidence{ + Signer: datagen.GenRandomAccount().Address, + StakingTxHash: actualDel.MustGetStakingTxHash().String(), + RecoveredFpBtcSk: recoveredFPSK.Serialize(), + } + _, err = h.MsgServer.SelectiveSlashingEvidence(h.Ctx, msg) + h.NoError(err) + + // ensure the finality provider is slashed + slashedFp, err := h.BTCStakingKeeper.GetFinalityProvider(h.Ctx, fpBtcPk.MustMarshal()) + h.NoError(err) + require.True(t, slashedFp.IsSlashed()) + }) +} + func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) ctrl := gomock.NewController(t) diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index 91ca3d2ae..af330d991 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -42,19 +42,51 @@ func (d *BTCDelegation) GetStakingTime() uint16 { return uint16(diff) } -// GetStatus returns the status of the BTC Delegation based on a BTC height and a w value -// TODO: Given that we only accept delegations that can be activated immediately, -// we can only have expired delegations. If we accept optimistic submissions, -// we could also have delegations that are in the waiting, so we need an extra status. -// This is covered by expired for now as it is the default value. -// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has a covenant sig -// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have a covenant sig -// TODO: fix comment above -// Expired: Delegation timelock +// GetFpIdx returns the index of the finality provider in the list of finality providers +// that the BTC delegation is restaked to +func (d *BTCDelegation) GetFpIdx(fpBTCPK *bbn.BIP340PubKey) int { + for i := 0; i < len(d.FpBtcPkList); i++ { + if d.FpBtcPkList[i].Equals(fpBTCPK) { + return i + } + } + return -1 +} + +func (d *BTCDelegation) GetCovSlashingAdaptorSig( + covBTCPK *bbn.BIP340PubKey, + valIdx int, + quorum uint32, +) (*asig.AdaptorSignature, error) { + if !d.HasCovenantQuorums(quorum) { + return nil, ErrInvalidDelegationState.Wrap("BTC delegation does not have a covenant quorum yet") + } + for _, covASigs := range d.CovenantSigs { + if covASigs.CovPk.Equals(covBTCPK) { + if valIdx >= len(covASigs.AdaptorSigs) { + return nil, ErrFpNotFound.Wrap("validator index is out of scope") + } + sigBytes := covASigs.AdaptorSigs[valIdx] + return asig.NewAdaptorSignatureFromBytes(sigBytes) + } + } + + return nil, ErrInvalidCovenantPK.Wrap("covenant PK is not found") +} + +// IsUnbondedEarly returns whether the delegator has signed unbonding signature. +// Signing unbonding signature means the delegator wants to unbond early, and +// Babylon will consider this BTC delegation unbonded directly +func (d *BTCDelegation) IsUnbondedEarly() bool { + return d.BtcUndelegation.DelegatorUnbondingSig != nil +} + +// GetStatus returns the status of the BTC Delegation based on BTC height, w value, and covenant quorum +// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have covenant signatures +// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has quorum number of signatures over slashing tx, unbonding tx, and slashing unbonding tx from covenant committee +// Unbonded: the BTC height is larger than `endHeight-w` or the BTC delegation has received a signature on unbonding tx from the delegator func (d *BTCDelegation) GetStatus(btcHeight uint64, w uint64, covenantQuorum uint32) BTCDelegationStatus { - if d.BtcUndelegation.DelegatorUnbondingSig != nil { - // this means the delegator has signed unbonding signature, and Babylon will consider - // this BTC delegation unbonded directly + if d.IsUnbondedEarly() { return BTCDelegationStatus_UNBONDED } diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index c1a556c07..922603857 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/babylonchain/babylon/btcstaking" @@ -87,6 +88,15 @@ func (tx *BTCSlashingTx) ToMsgTx() (*wire.MsgTx, error) { return bbn.NewBTCTxFromBytes(*tx) } +func (tx *BTCSlashingTx) MustGetTxHash() *chainhash.Hash { + msgTx, err := tx.ToMsgTx() + if err != nil { + panic(err) + } + txHash := msgTx.TxHash() + return &txHash +} + func (tx *BTCSlashingTx) Validate( net *chaincfg.Params, slashingAddress string, @@ -111,9 +121,9 @@ func (tx *BTCSlashingTx) Validate( // Sign generates a signature on the slashing tx func (tx *BTCSlashingTx) Sign( - stakingMsgTx *wire.MsgTx, + fundingTx *wire.MsgTx, spendOutputIndex uint32, - scriptPath []byte, + slashingPkScriptPath []byte, sk *btcec.PrivateKey, ) (*bbn.BIP340Signature, error) { msgTx, err := tx.ToMsgTx() @@ -122,9 +132,9 @@ func (tx *BTCSlashingTx) Sign( } schnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( msgTx, - stakingMsgTx, + fundingTx, spendOutputIndex, - scriptPath, + slashingPkScriptPath, sk, ) if err != nil { @@ -135,19 +145,20 @@ func (tx *BTCSlashingTx) Sign( // VerifySignature verifies a signature on the slashing tx signed by staker, finality provider, or covenant func (tx *BTCSlashingTx) VerifySignature( - stakingPkScript []byte, - stakingAmount int64, + fundingPkScript []byte, + fundingAmount int64, slashingPkScriptPath []byte, pk *btcec.PublicKey, - sig *bbn.BIP340Signature) error { + sig *bbn.BIP340Signature, +) error { msgTx, err := tx.ToMsgTx() if err != nil { return err } return btcstaking.VerifyTransactionSigWithOutputData( msgTx, - stakingPkScript, - stakingAmount, + fundingPkScript, + fundingAmount, slashingPkScriptPath, pk, *sig, @@ -157,9 +168,9 @@ func (tx *BTCSlashingTx) VerifySignature( // EncSign generates an adaptor signature on the slashing tx with finality provider's // public key as encryption key func (tx *BTCSlashingTx) EncSign( - stakingMsgTx *wire.MsgTx, + fundingMsgTx *wire.MsgTx, spendOutputIndex uint32, - scriptPath []byte, + slashingPkScriptPath []byte, sk *btcec.PrivateKey, encKey *asig.EncryptionKey, ) (*asig.AdaptorSignature, error) { @@ -169,9 +180,9 @@ func (tx *BTCSlashingTx) EncSign( } adaptorSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( msgTx, - stakingMsgTx, + fundingMsgTx, spendOutputIndex, - scriptPath, + slashingPkScriptPath, sk, encKey, ) @@ -207,6 +218,41 @@ func (tx *BTCSlashingTx) EncVerifyAdaptorSignature( ) } +// EncVerifyAdaptorSignatures verifies a list of adaptor signatures, each +// encrypted by a restaked validator PK and signed by the given PK, w.r.t. the +// given funding output (in staking or unbonding tx), slashing spend info and +// slashing tx +func (tx *BTCSlashingTx) EncVerifyAdaptorSignatures( + fundingOut *wire.TxOut, + slashingSpendInfo *btcstaking.SpendInfo, + pk *bbn.BIP340PubKey, + valPKs []bbn.BIP340PubKey, + sigs [][]byte, +) error { + for i, sig := range sigs { + adaptorSig, err := asig.NewAdaptorSignatureFromBytes(sig) + if err != nil { + return err + } + encKey, err := asig.NewEncryptionKeyFromBTCPK(valPKs[i].MustToBTCPK()) + if err != nil { + return err + } + err = tx.EncVerifyAdaptorSignature( + fundingOut.PkScript, + fundingOut.Value, + slashingSpendInfo.GetPkScriptPath(), + pk.MustToBTCPK(), + encKey, + adaptorSig, + ) + if err != nil { + return ErrInvalidCovenantSig.Wrapf("err: %v", err) + } + } + return nil +} + func (tx *BTCSlashingTx) BuildSlashingTxWithWitness( fpSK *btcec.PrivateKey, fundingMsgTx *wire.MsgTx, diff --git a/x/btcstaking/types/btc_undelegation.go b/x/btcstaking/types/btc_undelegation.go index 0e1237498..cef87fe3b 100644 --- a/x/btcstaking/types/btc_undelegation.go +++ b/x/btcstaking/types/btc_undelegation.go @@ -6,7 +6,7 @@ import ( ) func (ud *BTCUndelegation) HasCovenantQuorumOnSlashing(quorum uint32) bool { - return len(ud.CovenantUnbondingSigList) >= int(quorum) + return len(ud.CovenantSlashingSigs) >= int(quorum) } func (ud *BTCUndelegation) HasCovenantQuorumOnUnbonding(quorum uint32) bool { @@ -42,6 +42,27 @@ func (ud *BTCUndelegation) HasCovenantQuorums(covenantQuorum uint32) bool { ud.HasCovenantQuorumOnSlashing(covenantQuorum) } +func (ud *BTCUndelegation) GetCovSlashingAdaptorSig( + covBTCPK *bbn.BIP340PubKey, + valIdx int, + quorum uint32, +) (*asig.AdaptorSignature, error) { + if !ud.HasCovenantQuorums(quorum) { + return nil, ErrInvalidDelegationState.Wrap("BTC undelegation does not have a covenant quorum yet") + } + for _, covASigs := range ud.CovenantSlashingSigs { + if covASigs.CovPk.Equals(covBTCPK) { + if valIdx >= len(covASigs.AdaptorSigs) { + return nil, ErrFpNotFound.Wrap("validator index is out of scope") + } + sigBytes := covASigs.AdaptorSigs[valIdx] + return asig.NewAdaptorSignatureFromBytes(sigBytes) + } + } + + return nil, ErrInvalidCovenantPK.Wrap("covenant PK is not found") +} + // AddCovenantSigs adds a Schnorr signature on the unbonding tx, and // a list of adaptor signatures on the unbonding slashing tx, each encrypted // by a finality provider's PK this BTC delegation restakes to, from the given diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index ead40de9b..cd74a4ec1 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -720,6 +720,72 @@ func (m *CovenantAdaptorSignatures) GetAdaptorSigs() [][]byte { return nil } +// SelectiveSlashingEvidence is the evidence that the finality provider +// selectively slashed a BTC delegation +// NOTE: it's possible that a slashed finality provider exploits the +// SelectiveSlashingEvidence endpoint while it is actually slashed due to +// equivocation. But such behaviour does not affect the system's security +// or gives any benefit for the adversary +type SelectiveSlashingEvidence struct { + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,1,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // fp_btc_pk is the BTC PK of the finality provider who + // launches the selective slashing offence + FpBtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,opt,name=fp_btc_pk,json=fpBtcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk,omitempty"` + // recovered_fp_btc_sk is the finality provider's BTC SK recovered from + // the covenant adaptor/Schnorr signature pair. It is the consequence + // of selective slashing. + RecoveredFpBtcSk []byte `protobuf:"bytes,3,opt,name=recovered_fp_btc_sk,json=recoveredFpBtcSk,proto3" json:"recovered_fp_btc_sk,omitempty"` +} + +func (m *SelectiveSlashingEvidence) Reset() { *m = SelectiveSlashingEvidence{} } +func (m *SelectiveSlashingEvidence) String() string { return proto.CompactTextString(m) } +func (*SelectiveSlashingEvidence) ProtoMessage() {} +func (*SelectiveSlashingEvidence) Descriptor() ([]byte, []int) { + return fileDescriptor_3851ae95ccfaf7db, []int{9} +} +func (m *SelectiveSlashingEvidence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SelectiveSlashingEvidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SelectiveSlashingEvidence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SelectiveSlashingEvidence) XXX_Merge(src proto.Message) { + xxx_messageInfo_SelectiveSlashingEvidence.Merge(m, src) +} +func (m *SelectiveSlashingEvidence) XXX_Size() int { + return m.Size() +} +func (m *SelectiveSlashingEvidence) XXX_DiscardUnknown() { + xxx_messageInfo_SelectiveSlashingEvidence.DiscardUnknown(m) +} + +var xxx_messageInfo_SelectiveSlashingEvidence proto.InternalMessageInfo + +func (m *SelectiveSlashingEvidence) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +func (m *SelectiveSlashingEvidence) GetRecoveredFpBtcSk() []byte { + if m != nil { + return m.RecoveredFpBtcSk + } + return nil +} + func init() { proto.RegisterEnum("babylon.btcstaking.v1.BTCDelegationStatus", BTCDelegationStatus_name, BTCDelegationStatus_value) proto.RegisterType((*FinalityProvider)(nil), "babylon.btcstaking.v1.FinalityProvider") @@ -731,6 +797,7 @@ func init() { proto.RegisterType((*BTCDelegatorDelegationIndex)(nil), "babylon.btcstaking.v1.BTCDelegatorDelegationIndex") proto.RegisterType((*SignatureInfo)(nil), "babylon.btcstaking.v1.SignatureInfo") proto.RegisterType((*CovenantAdaptorSignatures)(nil), "babylon.btcstaking.v1.CovenantAdaptorSignatures") + proto.RegisterType((*SelectiveSlashingEvidence)(nil), "babylon.btcstaking.v1.SelectiveSlashingEvidence") } func init() { @@ -738,78 +805,82 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1127 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0x1a, 0x47, - 0x1b, 0xf6, 0x02, 0xc6, 0xe1, 0x05, 0x12, 0x32, 0x9f, 0xe3, 0x6c, 0x62, 0x7d, 0x40, 0x69, 0x1a, - 0xa1, 0xaa, 0x59, 0x62, 0xe7, 0x47, 0x6d, 0x0f, 0x2a, 0x05, 0xe3, 0x34, 0x28, 0x09, 0xa1, 0x0b, - 0x6e, 0xd5, 0x56, 0x2a, 0x5a, 0x76, 0x87, 0x65, 0x05, 0xec, 0x6c, 0x99, 0x81, 0xc2, 0x1d, 0xf4, - 0xa4, 0x52, 0x6f, 0xa1, 0x52, 0x2f, 0xa1, 0x17, 0xd0, 0x83, 0x1e, 0xf4, 0x30, 0xea, 0x49, 0x2b, - 0x1f, 0x58, 0x95, 0x7d, 0x23, 0xd5, 0xcc, 0x0e, 0xbb, 0x8b, 0x6b, 0x27, 0x4d, 0xf0, 0x19, 0xf3, - 0xfe, 0x3c, 0xef, 0xcf, 0xf3, 0x30, 0xb3, 0x70, 0xbb, 0x6b, 0x74, 0xe7, 0x43, 0xe2, 0x56, 0xba, - 0xcc, 0xa4, 0xcc, 0x18, 0x38, 0xae, 0x5d, 0x99, 0xee, 0x44, 0x4e, 0x9a, 0x37, 0x26, 0x8c, 0xa0, - 0x6b, 0x32, 0x4e, 0x8b, 0x78, 0xa6, 0x3b, 0x37, 0x37, 0x6d, 0x62, 0x13, 0x11, 0x51, 0xe1, 0xbf, - 0xfc, 0xe0, 0x9b, 0x37, 0x4c, 0x42, 0x47, 0x84, 0x76, 0x7c, 0x87, 0x7f, 0x90, 0xae, 0x92, 0x7f, - 0xaa, 0x98, 0xe3, 0xb9, 0xc7, 0x48, 0x85, 0x62, 0xd3, 0xdb, 0x7d, 0xf0, 0x70, 0xb0, 0x53, 0x19, - 0xe0, 0xf9, 0x22, 0xe6, 0x96, 0x8c, 0x09, 0xfb, 0xe9, 0x62, 0x66, 0xec, 0x54, 0x96, 0x3a, 0xba, - 0x59, 0x38, 0xbb, 0x73, 0x8f, 0x78, 0x7e, 0x40, 0xe9, 0xcf, 0x38, 0xe4, 0x1e, 0x3b, 0xae, 0x31, - 0x74, 0xd8, 0xbc, 0x39, 0x26, 0x53, 0xc7, 0xc2, 0x63, 0xb4, 0x0f, 0x69, 0x0b, 0x53, 0x73, 0xec, - 0x78, 0xcc, 0x21, 0xae, 0xaa, 0x14, 0x95, 0x72, 0x7a, 0xf7, 0x5d, 0x4d, 0xf6, 0x18, 0x4e, 0x26, - 0x2a, 0x6a, 0xb5, 0x30, 0x54, 0x8f, 0xe6, 0xa1, 0xe7, 0x00, 0x26, 0x19, 0x8d, 0x1c, 0x4a, 0x39, - 0x4a, 0xac, 0xa8, 0x94, 0x53, 0xd5, 0x3b, 0x87, 0x47, 0x85, 0x6d, 0x1f, 0x88, 0x5a, 0x03, 0xcd, - 0x21, 0x95, 0x91, 0xc1, 0xfa, 0xda, 0x33, 0x6c, 0x1b, 0xe6, 0xbc, 0x86, 0xcd, 0x3f, 0x7e, 0xb9, - 0x03, 0xb2, 0x4e, 0x0d, 0x9b, 0x7a, 0x04, 0x00, 0x7d, 0x02, 0x20, 0xa7, 0xe9, 0x78, 0x03, 0x35, - 0x2e, 0x9a, 0x2a, 0x2c, 0x9a, 0xf2, 0x57, 0xa5, 0x05, 0xab, 0xd2, 0x9a, 0x93, 0xee, 0x53, 0x3c, - 0xd7, 0x53, 0x32, 0xa5, 0x39, 0x40, 0xcf, 0x21, 0xd9, 0x65, 0x26, 0xcf, 0x4d, 0x14, 0x95, 0x72, - 0xa6, 0xfa, 0xf0, 0xf0, 0xa8, 0xb0, 0x6b, 0x3b, 0xac, 0x3f, 0xe9, 0x6a, 0x26, 0x19, 0x55, 0x64, - 0xa4, 0xd9, 0x37, 0x1c, 0x77, 0x71, 0xa8, 0xb0, 0xb9, 0x87, 0xa9, 0x56, 0xad, 0x37, 0xef, 0xdd, - 0xbf, 0x2b, 0x21, 0xd7, 0xbb, 0xcc, 0x6c, 0x0e, 0xd0, 0xc7, 0x10, 0xf7, 0x88, 0xa7, 0xae, 0x8b, - 0x3e, 0xca, 0xda, 0x99, 0xd4, 0x6b, 0xcd, 0x31, 0x21, 0xbd, 0x17, 0xbd, 0x26, 0xa1, 0x14, 0x8b, - 0x29, 0x74, 0x9e, 0x84, 0xee, 0xc3, 0x16, 0x1d, 0x1a, 0xb4, 0x8f, 0xad, 0xce, 0x62, 0xa4, 0x3e, - 0x76, 0xec, 0x3e, 0x53, 0x93, 0x45, 0xa5, 0x9c, 0xd0, 0x37, 0xa5, 0xb7, 0xea, 0x3b, 0x9f, 0x08, - 0x1f, 0xfa, 0x00, 0x50, 0x90, 0xc5, 0xcc, 0x45, 0xc6, 0x86, 0xc8, 0xc8, 0x2d, 0x32, 0x98, 0xe9, - 0x47, 0x97, 0xbe, 0x8f, 0x81, 0x7a, 0x9a, 0xd9, 0x2f, 0x1c, 0xd6, 0x7f, 0x8e, 0x99, 0x11, 0xd9, - 0x85, 0x72, 0x11, 0xbb, 0xd8, 0x82, 0xa4, 0xec, 0x26, 0x26, 0xba, 0x91, 0x27, 0xf4, 0x0e, 0x64, - 0xa6, 0x84, 0x39, 0xae, 0xdd, 0xf1, 0xc8, 0x77, 0x78, 0x2c, 0x48, 0x4b, 0xe8, 0x69, 0xdf, 0xd6, - 0xe4, 0xa6, 0x57, 0xac, 0x22, 0xf1, 0xc6, 0xab, 0x58, 0x3f, 0x67, 0x15, 0x3f, 0x25, 0x21, 0x5b, - 0x6d, 0xef, 0xd5, 0xf0, 0x10, 0xdb, 0x06, 0xfb, 0xb7, 0x96, 0x94, 0x15, 0xb4, 0x14, 0xbb, 0x40, - 0x2d, 0xc5, 0xdf, 0x46, 0x4b, 0x5f, 0xc3, 0xe5, 0x9e, 0xd7, 0xf1, 0xbb, 0xe9, 0x0c, 0x1d, 0xca, - 0x17, 0x17, 0x5f, 0xa1, 0xa5, 0x74, 0xcf, 0xab, 0xf2, 0xa6, 0x9e, 0x39, 0x54, 0x10, 0x48, 0x99, - 0x31, 0x66, 0xcb, 0x1b, 0x4e, 0x0b, 0x9b, 0xa4, 0xe2, 0xff, 0x00, 0xd8, 0xb5, 0x96, 0xf5, 0x9b, - 0xc2, 0xae, 0x25, 0xdd, 0xdb, 0x90, 0x62, 0x84, 0x19, 0xc3, 0x0e, 0x35, 0x16, 0x5a, 0xbd, 0x24, - 0x0c, 0x2d, 0x43, 0xe4, 0xca, 0x01, 0x3b, 0x6c, 0xa6, 0x5e, 0xe2, 0xab, 0xd4, 0x53, 0xd2, 0xd2, - 0x9e, 0x09, 0x96, 0xa5, 0x9b, 0x4c, 0x98, 0x37, 0x61, 0x1d, 0xc7, 0x9a, 0xa9, 0xa9, 0xa2, 0x52, - 0xce, 0xea, 0x39, 0xe9, 0x79, 0x21, 0x1c, 0x75, 0x6b, 0x86, 0x76, 0x21, 0x2d, 0x98, 0x97, 0x68, - 0x20, 0x88, 0xb9, 0x7a, 0x78, 0x54, 0xe0, 0xdc, 0xb7, 0xa4, 0xa7, 0x3d, 0xd3, 0x81, 0x06, 0xbf, - 0xd1, 0x37, 0x90, 0xb5, 0x7c, 0x55, 0x90, 0x71, 0x87, 0x3a, 0xb6, 0x9a, 0x16, 0x59, 0x1f, 0x1d, - 0x1e, 0x15, 0x1e, 0xbc, 0xc9, 0xee, 0x5a, 0x8e, 0xed, 0x1a, 0x6c, 0x32, 0xc6, 0x7a, 0x26, 0xc0, - 0x6b, 0x39, 0x36, 0x3a, 0x80, 0xac, 0x49, 0xa6, 0xd8, 0x35, 0x5c, 0xc6, 0xe1, 0xa9, 0x9a, 0x29, - 0xc6, 0xcb, 0xe9, 0xdd, 0xbb, 0xe7, 0x50, 0xbc, 0x27, 0x63, 0x1f, 0x59, 0x86, 0xe7, 0x23, 0xf8, - 0xa8, 0x54, 0xcf, 0x2c, 0x60, 0x5a, 0x8e, 0x4d, 0xd1, 0x67, 0x90, 0xe3, 0x84, 0x4f, 0x5c, 0x2b, - 0x90, 0xb4, 0x9a, 0x15, 0xe2, 0xb9, 0x7d, 0x0e, 0x72, 0xb5, 0xbd, 0x77, 0x10, 0x89, 0xd6, 0xaf, - 0x74, 0x99, 0x19, 0x35, 0x94, 0x7e, 0x4d, 0xc0, 0x95, 0x53, 0x41, 0x9c, 0xfd, 0x89, 0xdb, 0x25, - 0xae, 0x25, 0x57, 0x2a, 0xee, 0x0a, 0x3d, 0x1d, 0xd8, 0xda, 0x33, 0xf4, 0x1e, 0x5c, 0x8e, 0x84, - 0x38, 0x23, 0x2c, 0xfe, 0x10, 0x59, 0x3d, 0x1b, 0x06, 0x39, 0x23, 0x7c, 0x9a, 0x9b, 0xf8, 0x7f, - 0xe1, 0xe6, 0x5b, 0xb8, 0x1e, 0x72, 0x13, 0x16, 0xe1, 0x2c, 0x25, 0x56, 0x65, 0xe9, 0x5a, 0x80, - 0x7c, 0xb0, 0x00, 0xe6, 0x74, 0x11, 0xd8, 0x8a, 0xc8, 0x61, 0xd1, 0x30, 0xaf, 0xb8, 0xbe, 0x6a, - 0xc5, 0xcd, 0x50, 0x17, 0x12, 0x97, 0x17, 0xec, 0xc1, 0x56, 0xa8, 0x8f, 0x48, 0x3d, 0xaa, 0x26, - 0xdf, 0x52, 0x28, 0x9b, 0x81, 0x50, 0xc2, 0x32, 0x14, 0x99, 0xb0, 0x1d, 0xd4, 0x59, 0x5a, 0xa5, - 0x7f, 0x63, 0x6c, 0x88, 0x62, 0xb7, 0xce, 0x29, 0x16, 0xa0, 0xd7, 0xdd, 0x1e, 0xd1, 0xd5, 0x05, - 0x50, 0x74, 0x73, 0xfc, 0xb2, 0x28, 0xfd, 0xa6, 0xc0, 0xff, 0x4e, 0x49, 0x88, 0x67, 0x5c, 0xa0, - 0x8c, 0x5e, 0x33, 0x46, 0xfc, 0x42, 0xc6, 0x68, 0xc1, 0xf5, 0xf0, 0xb1, 0x20, 0xe3, 0xf0, 0xd5, - 0xa0, 0xe8, 0x43, 0x48, 0x58, 0x78, 0x48, 0x55, 0xe5, 0x95, 0x85, 0x96, 0x9e, 0x1a, 0x5d, 0x64, - 0x94, 0x1a, 0xb0, 0x7d, 0x36, 0x68, 0xdd, 0xb5, 0xf0, 0x0c, 0x55, 0x60, 0x33, 0xbc, 0x08, 0x3b, - 0x7d, 0x83, 0xf6, 0xfd, 0x89, 0x78, 0xa1, 0x8c, 0x7e, 0x35, 0xb8, 0x12, 0x9f, 0x18, 0xb4, 0x2f, - 0x9a, 0xfc, 0x59, 0x81, 0xec, 0xd2, 0x40, 0xe8, 0x31, 0xc4, 0x56, 0x7e, 0xce, 0x63, 0xde, 0x00, - 0x3d, 0x85, 0x38, 0x17, 0x7c, 0x6c, 0x55, 0xc1, 0x73, 0x94, 0xd2, 0x0f, 0x0a, 0xdc, 0x38, 0x57, - 0xab, 0xfc, 0x15, 0x35, 0xc9, 0xf4, 0x02, 0xbe, 0x42, 0x4c, 0x32, 0x6d, 0x0e, 0xb8, 0xce, 0x0c, - 0xbf, 0x86, 0xff, 0x17, 0x8a, 0x89, 0xe5, 0xa5, 0x8d, 0xa0, 0x2e, 0x7d, 0x7f, 0x5f, 0x28, 0x34, - 0xdc, 0x7e, 0x8b, 0x19, 0x6c, 0x42, 0x51, 0x1a, 0x36, 0x9a, 0xfb, 0x8d, 0x5a, 0xbd, 0xf1, 0x69, - 0x6e, 0x0d, 0x01, 0x24, 0x1f, 0xed, 0xb5, 0xeb, 0x9f, 0xef, 0xe7, 0x14, 0x94, 0x81, 0x4b, 0x07, - 0x8d, 0xea, 0x8b, 0x46, 0x6d, 0xbf, 0x96, 0x8b, 0xa1, 0x0d, 0x88, 0x3f, 0x6a, 0x7c, 0x99, 0x8b, - 0x57, 0x9f, 0xfd, 0x7e, 0x9c, 0x57, 0x5e, 0x1e, 0xe7, 0x95, 0xbf, 0x8f, 0xf3, 0xca, 0x8f, 0x27, - 0xf9, 0xb5, 0x97, 0x27, 0xf9, 0xb5, 0xbf, 0x4e, 0xf2, 0x6b, 0x5f, 0xbd, 0xb6, 0xfd, 0x59, 0xf4, - 0x53, 0x5c, 0xcc, 0xd2, 0x4d, 0x8a, 0x4f, 0xf1, 0x7b, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x8a, - 0xa9, 0x66, 0xc0, 0x67, 0x0c, 0x00, 0x00, + // 1197 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0x1b, 0xc5, + 0x17, 0xcf, 0xda, 0x8e, 0x53, 0x1f, 0xdb, 0xad, 0x3b, 0x4d, 0xd3, 0x6d, 0xa3, 0x7f, 0x92, 0xbf, + 0x29, 0x95, 0x85, 0xe8, 0xba, 0x49, 0x3f, 0x04, 0x5c, 0x20, 0xd5, 0x71, 0x4a, 0xa3, 0xb6, 0xa9, + 0x59, 0x3b, 0x20, 0x40, 0x62, 0xb5, 0xde, 0x1d, 0xaf, 0x57, 0xb6, 0x77, 0x96, 0x9d, 0xb1, 0xb1, + 0xdf, 0x80, 0x1b, 0x24, 0x5e, 0x01, 0x89, 0x47, 0xe0, 0x01, 0xb8, 0x40, 0x88, 0xcb, 0x8a, 0x1b, + 0x50, 0x2e, 0x22, 0xd4, 0xbe, 0x08, 0x9a, 0xd9, 0xd9, 0x0f, 0x87, 0xa6, 0xa5, 0x75, 0xee, 0x3c, + 0x73, 0xce, 0xf9, 0x9d, 0x8f, 0xdf, 0x6f, 0x67, 0xc6, 0x70, 0xa3, 0x6b, 0x76, 0x67, 0x43, 0xe2, + 0xd5, 0xbb, 0xcc, 0xa2, 0xcc, 0x1c, 0xb8, 0x9e, 0x53, 0x9f, 0x6c, 0xa7, 0x56, 0x9a, 0x1f, 0x10, + 0x46, 0xd0, 0x65, 0xe9, 0xa7, 0xa5, 0x2c, 0x93, 0xed, 0x6b, 0xab, 0x0e, 0x71, 0x88, 0xf0, 0xa8, + 0xf3, 0x5f, 0xa1, 0xf3, 0xb5, 0xab, 0x16, 0xa1, 0x23, 0x42, 0x8d, 0xd0, 0x10, 0x2e, 0xa4, 0xa9, + 0x1a, 0xae, 0xea, 0x56, 0x30, 0xf3, 0x19, 0xa9, 0x53, 0x6c, 0xf9, 0x3b, 0x77, 0xef, 0x0d, 0xb6, + 0xeb, 0x03, 0x3c, 0x8b, 0x7c, 0xae, 0x4b, 0x9f, 0xa4, 0x9e, 0x2e, 0x66, 0xe6, 0x76, 0x7d, 0xae, + 0xa2, 0x6b, 0x9b, 0x2f, 0xaf, 0xdc, 0x27, 0x7e, 0xe8, 0x50, 0xfd, 0x33, 0x0b, 0x95, 0x07, 0xae, + 0x67, 0x0e, 0x5d, 0x36, 0x6b, 0x05, 0x64, 0xe2, 0xda, 0x38, 0x40, 0x7b, 0x50, 0xb4, 0x31, 0xb5, + 0x02, 0xd7, 0x67, 0x2e, 0xf1, 0x54, 0x65, 0x4b, 0xa9, 0x15, 0x77, 0xde, 0xd1, 0x64, 0x8d, 0x49, + 0x67, 0x22, 0xa3, 0xd6, 0x4c, 0x5c, 0xf5, 0x74, 0x1c, 0x7a, 0x02, 0x60, 0x91, 0xd1, 0xc8, 0xa5, + 0x94, 0xa3, 0x64, 0xb6, 0x94, 0x5a, 0xa1, 0x71, 0xf3, 0xe8, 0x78, 0x73, 0x3d, 0x04, 0xa2, 0xf6, + 0x40, 0x73, 0x49, 0x7d, 0x64, 0xb2, 0xbe, 0xf6, 0x18, 0x3b, 0xa6, 0x35, 0x6b, 0x62, 0xeb, 0x8f, + 0x9f, 0x6f, 0x82, 0xcc, 0xd3, 0xc4, 0x96, 0x9e, 0x02, 0x40, 0x1f, 0x03, 0xc8, 0x6e, 0x0c, 0x7f, + 0xa0, 0x66, 0x45, 0x51, 0x9b, 0x51, 0x51, 0xe1, 0xa8, 0xb4, 0x78, 0x54, 0x5a, 0x6b, 0xdc, 0x7d, + 0x84, 0x67, 0x7a, 0x41, 0x86, 0xb4, 0x06, 0xe8, 0x09, 0xe4, 0xbb, 0xcc, 0xe2, 0xb1, 0xb9, 0x2d, + 0xa5, 0x56, 0x6a, 0xdc, 0x3b, 0x3a, 0xde, 0xdc, 0x71, 0x5c, 0xd6, 0x1f, 0x77, 0x35, 0x8b, 0x8c, + 0xea, 0xd2, 0xd3, 0xea, 0x9b, 0xae, 0x17, 0x2d, 0xea, 0x6c, 0xe6, 0x63, 0xaa, 0x35, 0xf6, 0x5b, + 0xb7, 0xef, 0xdc, 0x92, 0x90, 0xcb, 0x5d, 0x66, 0xb5, 0x06, 0xe8, 0x23, 0xc8, 0xfa, 0xc4, 0x57, + 0x97, 0x45, 0x1d, 0x35, 0xed, 0xa5, 0xd4, 0x6b, 0xad, 0x80, 0x90, 0xde, 0xd3, 0x5e, 0x8b, 0x50, + 0x8a, 0x45, 0x17, 0x3a, 0x0f, 0x42, 0x77, 0x60, 0x8d, 0x0e, 0x4d, 0xda, 0xc7, 0xb6, 0x11, 0xb5, + 0xd4, 0xc7, 0xae, 0xd3, 0x67, 0x6a, 0x7e, 0x4b, 0xa9, 0xe5, 0xf4, 0x55, 0x69, 0x6d, 0x84, 0xc6, + 0x87, 0xc2, 0x86, 0xde, 0x07, 0x14, 0x47, 0x31, 0x2b, 0x8a, 0x58, 0x11, 0x11, 0x95, 0x28, 0x82, + 0x59, 0xa1, 0x77, 0xf5, 0xbb, 0x0c, 0xa8, 0x27, 0x99, 0xfd, 0xdc, 0x65, 0xfd, 0x27, 0x98, 0x99, + 0xa9, 0x59, 0x28, 0x67, 0x31, 0x8b, 0x35, 0xc8, 0xcb, 0x6a, 0x32, 0xa2, 0x1a, 0xb9, 0x42, 0xff, + 0x87, 0xd2, 0x84, 0x30, 0xd7, 0x73, 0x0c, 0x9f, 0x7c, 0x8b, 0x03, 0x41, 0x5a, 0x4e, 0x2f, 0x86, + 0x7b, 0x2d, 0xbe, 0xf5, 0x8a, 0x51, 0xe4, 0xde, 0x78, 0x14, 0xcb, 0xa7, 0x8c, 0xe2, 0xc7, 0x3c, + 0x94, 0x1b, 0x9d, 0xdd, 0x26, 0x1e, 0x62, 0xc7, 0x64, 0xff, 0xd6, 0x92, 0xb2, 0x80, 0x96, 0x32, + 0x67, 0xa8, 0xa5, 0xec, 0xdb, 0x68, 0xe9, 0x2b, 0x38, 0xdf, 0xf3, 0x8d, 0xb0, 0x1a, 0x63, 0xe8, + 0x52, 0x3e, 0xb8, 0xec, 0x02, 0x25, 0x15, 0x7b, 0x7e, 0x83, 0x17, 0xf5, 0xd8, 0xa5, 0x82, 0x40, + 0xca, 0xcc, 0x80, 0xcd, 0x4f, 0xb8, 0x28, 0xf6, 0x24, 0x15, 0xff, 0x03, 0xc0, 0x9e, 0x3d, 0xaf, + 0xdf, 0x02, 0xf6, 0x6c, 0x69, 0x5e, 0x87, 0x02, 0x23, 0xcc, 0x1c, 0x1a, 0xd4, 0x8c, 0xb4, 0x7a, + 0x4e, 0x6c, 0xb4, 0x4d, 0x11, 0x2b, 0x1b, 0x34, 0xd8, 0x54, 0x3d, 0xc7, 0x47, 0xa9, 0x17, 0xe4, + 0x4e, 0x67, 0x2a, 0x58, 0x96, 0x66, 0x32, 0x66, 0xfe, 0x98, 0x19, 0xae, 0x3d, 0x55, 0x0b, 0x5b, + 0x4a, 0xad, 0xac, 0x57, 0xa4, 0xe5, 0xa9, 0x30, 0xec, 0xdb, 0x53, 0xb4, 0x03, 0x45, 0xc1, 0xbc, + 0x44, 0x03, 0x41, 0xcc, 0xc5, 0xa3, 0xe3, 0x4d, 0xce, 0x7d, 0x5b, 0x5a, 0x3a, 0x53, 0x1d, 0x68, + 0xfc, 0x1b, 0x7d, 0x0d, 0x65, 0x3b, 0x54, 0x05, 0x09, 0x0c, 0xea, 0x3a, 0x6a, 0x51, 0x44, 0x7d, + 0x78, 0x74, 0xbc, 0x79, 0xf7, 0x4d, 0x66, 0xd7, 0x76, 0x1d, 0xcf, 0x64, 0xe3, 0x00, 0xeb, 0xa5, + 0x18, 0xaf, 0xed, 0x3a, 0xe8, 0x10, 0xca, 0x16, 0x99, 0x60, 0xcf, 0xf4, 0x18, 0x87, 0xa7, 0x6a, + 0x69, 0x2b, 0x5b, 0x2b, 0xee, 0xdc, 0x3a, 0x85, 0xe2, 0x5d, 0xe9, 0x7b, 0xdf, 0x36, 0xfd, 0x10, + 0x21, 0x44, 0xa5, 0x7a, 0x29, 0x82, 0x69, 0xbb, 0x0e, 0x45, 0x9f, 0x42, 0x85, 0x13, 0x3e, 0xf6, + 0xec, 0x58, 0xd2, 0x6a, 0x59, 0x88, 0xe7, 0xc6, 0x29, 0xc8, 0x8d, 0xce, 0xee, 0x61, 0xca, 0x5b, + 0xbf, 0xd0, 0x65, 0x56, 0x7a, 0xa3, 0xfa, 0x4b, 0x0e, 0x2e, 0x9c, 0x70, 0xe2, 0xec, 0x8f, 0xbd, + 0x2e, 0xf1, 0x6c, 0x39, 0x52, 0x71, 0x56, 0xe8, 0xc5, 0x78, 0xaf, 0x33, 0x45, 0xef, 0xc2, 0xf9, + 0x94, 0x8b, 0x3b, 0xc2, 0xe2, 0x83, 0x28, 0xeb, 0xe5, 0xc4, 0xc9, 0x1d, 0xe1, 0x93, 0xdc, 0x64, + 0xff, 0x0b, 0x37, 0xdf, 0xc0, 0x95, 0x84, 0x9b, 0x24, 0x09, 0x67, 0x29, 0xb7, 0x28, 0x4b, 0x97, + 0x63, 0xe4, 0xc3, 0x08, 0x98, 0xd3, 0x45, 0x60, 0x2d, 0x25, 0x87, 0xa8, 0x60, 0x9e, 0x71, 0x79, + 0xd1, 0x8c, 0xab, 0x89, 0x2e, 0x24, 0x2e, 0x4f, 0xd8, 0x83, 0xb5, 0x44, 0x1f, 0xa9, 0x7c, 0x54, + 0xcd, 0xbf, 0xa5, 0x50, 0x56, 0x63, 0xa1, 0x24, 0x69, 0x28, 0xb2, 0x60, 0x3d, 0xce, 0x33, 0x37, + 0xca, 0xf0, 0xc4, 0x58, 0x11, 0xc9, 0xae, 0x9f, 0x92, 0x2c, 0x46, 0xdf, 0xf7, 0x7a, 0x44, 0x57, + 0x23, 0xa0, 0xf4, 0xe4, 0xf8, 0x61, 0x51, 0xfd, 0x55, 0x81, 0x4b, 0x27, 0x24, 0xc4, 0x23, 0xce, + 0x50, 0x46, 0xaf, 0x69, 0x23, 0x7b, 0x26, 0x6d, 0xb4, 0xe1, 0x4a, 0x72, 0x59, 0x90, 0x20, 0xb9, + 0x35, 0x28, 0xfa, 0x00, 0x72, 0x36, 0x1e, 0x52, 0x55, 0x79, 0x65, 0xa2, 0xb9, 0xab, 0x46, 0x17, + 0x11, 0xd5, 0x03, 0x58, 0x7f, 0x39, 0xe8, 0xbe, 0x67, 0xe3, 0x29, 0xaa, 0xc3, 0x6a, 0x72, 0x10, + 0x1a, 0x7d, 0x93, 0xf6, 0xc3, 0x8e, 0x78, 0xa2, 0x92, 0x7e, 0x31, 0x3e, 0x12, 0x1f, 0x9a, 0xb4, + 0x2f, 0x8a, 0xfc, 0x49, 0x81, 0xf2, 0x5c, 0x43, 0xe8, 0x01, 0x64, 0x16, 0xbe, 0xce, 0x33, 0xfe, + 0x00, 0x3d, 0x82, 0x2c, 0x17, 0x7c, 0x66, 0x51, 0xc1, 0x73, 0x94, 0xea, 0xf7, 0x0a, 0x5c, 0x3d, + 0x55, 0xab, 0xfc, 0x16, 0xb5, 0xc8, 0xe4, 0x0c, 0x5e, 0x21, 0x16, 0x99, 0xb4, 0x06, 0x5c, 0x67, + 0x66, 0x98, 0x23, 0xfc, 0x84, 0x32, 0x62, 0x78, 0x45, 0x33, 0xce, 0x4b, 0xab, 0xbf, 0x29, 0x70, + 0xb5, 0x8d, 0x87, 0xd8, 0x62, 0xee, 0x04, 0x47, 0x5f, 0xc8, 0x1e, 0x7f, 0x1b, 0x79, 0x16, 0x46, + 0x37, 0xe0, 0xc2, 0x09, 0x16, 0x44, 0x61, 0x05, 0xbd, 0x3c, 0x47, 0x00, 0xd2, 0xa1, 0x10, 0x5f, + 0xb9, 0x0b, 0x3e, 0x00, 0x56, 0xe4, 0x6d, 0x8b, 0x6e, 0xc2, 0xa5, 0x00, 0x73, 0x4d, 0x06, 0xd8, + 0x36, 0x24, 0x3a, 0x0d, 0x9f, 0xb9, 0x25, 0xbd, 0x12, 0x9b, 0x1e, 0x70, 0xf7, 0xf6, 0xe0, 0xbd, + 0x3d, 0xf1, 0xa9, 0x25, 0x32, 0x6a, 0x33, 0x93, 0x8d, 0x29, 0x2a, 0xc2, 0x4a, 0x6b, 0xef, 0xa0, + 0xb9, 0x7f, 0xf0, 0x49, 0x65, 0x09, 0x01, 0xe4, 0xef, 0xef, 0x76, 0xf6, 0x3f, 0xdb, 0xab, 0x28, + 0xa8, 0x04, 0xe7, 0x0e, 0x0f, 0x1a, 0x4f, 0x0f, 0x9a, 0x7b, 0xcd, 0x4a, 0x06, 0xad, 0x40, 0xf6, + 0xfe, 0xc1, 0x17, 0x95, 0x6c, 0xe3, 0xf1, 0xef, 0xcf, 0x37, 0x94, 0x67, 0xcf, 0x37, 0x94, 0xbf, + 0x9f, 0x6f, 0x28, 0x3f, 0xbc, 0xd8, 0x58, 0x7a, 0xf6, 0x62, 0x63, 0xe9, 0xaf, 0x17, 0x1b, 0x4b, + 0x5f, 0xbe, 0xb6, 0x99, 0x69, 0xfa, 0x3f, 0x85, 0xe8, 0xac, 0x9b, 0x17, 0xff, 0x29, 0x6e, 0xff, + 0x13, 0x00, 0x00, 0xff, 0xff, 0xd6, 0xf0, 0x60, 0x55, 0x30, 0x0d, 0x00, 0x00, } func (m *FinalityProvider) Marshal() (dAtA []byte, err error) { @@ -1418,6 +1489,55 @@ func (m *CovenantAdaptorSignatures) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *SelectiveSlashingEvidence) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SelectiveSlashingEvidence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SelectiveSlashingEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RecoveredFpBtcSk) > 0 { + i -= len(m.RecoveredFpBtcSk) + copy(dAtA[i:], m.RecoveredFpBtcSk) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.RecoveredFpBtcSk))) + i-- + dAtA[i] = 0x1a + } + if m.FpBtcPk != nil { + { + size := m.FpBtcPk.Size() + i -= size + if _, err := m.FpBtcPk.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintBtcstaking(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBtcstaking(dAtA []byte, offset int, v uint64) int { offset -= sovBtcstaking(v) base := offset @@ -1678,6 +1798,27 @@ func (m *CovenantAdaptorSignatures) Size() (n int) { return n } +func (m *SelectiveSlashingEvidence) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + if m.FpBtcPk != nil { + l = m.FpBtcPk.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + l = len(m.RecoveredFpBtcSk) + if l > 0 { + n += 1 + l + sovBtcstaking(uint64(l)) + } + return n +} + func sovBtcstaking(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3370,6 +3511,157 @@ func (m *CovenantAdaptorSignatures) Unmarshal(dAtA []byte) error { } return nil } +func (m *SelectiveSlashingEvidence) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SelectiveSlashingEvidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SelectiveSlashingEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_types.BIP340PubKey + m.FpBtcPk = &v + if err := m.FpBtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RecoveredFpBtcSk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RecoveredFpBtcSk = append(m.RecoveredFpBtcSk[:0], dAtA[iNdEx:postIndex]...) + if m.RecoveredFpBtcSk == nil { + m.RecoveredFpBtcSk = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBtcstaking(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBtcstaking + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBtcstaking(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/btcstaking/types/events.pb.go b/x/btcstaking/types/events.pb.go index 25b1dd73e..0157aab8e 100644 --- a/x/btcstaking/types/events.pb.go +++ b/x/btcstaking/types/events.pb.go @@ -162,71 +162,6 @@ func (m *EventActivateBTCDelegation) GetBtcDel() *BTCDelegation { return nil } -// EventUnbondingBTCDelegation is the event emitted when receiving an unbonding request -// for an existing BTC delegation -type EventUnbondingBTCDelegation struct { - // btc_pk is the Bitcoin secp256k1 PK of this BTC delegation - // the PK follows encoding in BIP-340 spec - BtcPk *github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,1,opt,name=btc_pk,json=btcPk,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"btc_pk,omitempty"` - // fp_btc_pk_list is the list of BIP-340 PKs of the BTC finality providers that - // this BTC delegation delegates to - // If there is more than 1 PKs, then this means the delegation is restaked - // to multiple finality providers - FpBtcPkList []github_com_babylonchain_babylon_types.BIP340PubKey `protobuf:"bytes,2,rep,name=fp_btc_pk_list,json=fpBtcPkList,proto3,customtype=github.com/babylonchain/babylon/types.BIP340PubKey" json:"fp_btc_pk_list,omitempty"` - // staking_tx_hash is the hash of the staking tx. - // (fp_pks..., del_pk, staking_tx_hash) uniquely identifies a BTC delegation - StakingTxHash string `protobuf:"bytes,3,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` - // unbonding_tx_hash is the hash of the unbonding tx. - UnbondingTxHash string `protobuf:"bytes,4,opt,name=unbonding_tx_hash,json=unbondingTxHash,proto3" json:"unbonding_tx_hash,omitempty"` -} - -func (m *EventUnbondingBTCDelegation) Reset() { *m = EventUnbondingBTCDelegation{} } -func (m *EventUnbondingBTCDelegation) String() string { return proto.CompactTextString(m) } -func (*EventUnbondingBTCDelegation) ProtoMessage() {} -func (*EventUnbondingBTCDelegation) Descriptor() ([]byte, []int) { - return fileDescriptor_74118427820fff75, []int{3} -} -func (m *EventUnbondingBTCDelegation) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *EventUnbondingBTCDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_EventUnbondingBTCDelegation.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *EventUnbondingBTCDelegation) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventUnbondingBTCDelegation.Merge(m, src) -} -func (m *EventUnbondingBTCDelegation) XXX_Size() int { - return m.Size() -} -func (m *EventUnbondingBTCDelegation) XXX_DiscardUnknown() { - xxx_messageInfo_EventUnbondingBTCDelegation.DiscardUnknown(m) -} - -var xxx_messageInfo_EventUnbondingBTCDelegation proto.InternalMessageInfo - -func (m *EventUnbondingBTCDelegation) GetStakingTxHash() string { - if m != nil { - return m.StakingTxHash - } - return "" -} - -func (m *EventUnbondingBTCDelegation) GetUnbondingTxHash() string { - if m != nil { - return m.UnbondingTxHash - } - return "" -} - // EventUnbondingBTCDelegation is the event emitted when an unbonding BTC delegation // receives all signatures needed for becoming unbonded type EventUnbondedBTCDelegation struct { @@ -251,7 +186,7 @@ func (m *EventUnbondedBTCDelegation) Reset() { *m = EventUnbondedBTCDele func (m *EventUnbondedBTCDelegation) String() string { return proto.CompactTextString(m) } func (*EventUnbondedBTCDelegation) ProtoMessage() {} func (*EventUnbondedBTCDelegation) Descriptor() ([]byte, []int) { - return fileDescriptor_74118427820fff75, []int{4} + return fileDescriptor_74118427820fff75, []int{3} } func (m *EventUnbondedBTCDelegation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -301,12 +236,59 @@ func (m *EventUnbondedBTCDelegation) GetFromState() BTCDelegationStatus { return BTCDelegationStatus_PENDING } +// EventSelectiveSlashing is the event emitted when an adversarial BTC +// validator selectively slashes a BTC delegation +type EventSelectiveSlashing struct { + // evidence is the evidence of selective slashing + Evidence *SelectiveSlashingEvidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` +} + +func (m *EventSelectiveSlashing) Reset() { *m = EventSelectiveSlashing{} } +func (m *EventSelectiveSlashing) String() string { return proto.CompactTextString(m) } +func (*EventSelectiveSlashing) ProtoMessage() {} +func (*EventSelectiveSlashing) Descriptor() ([]byte, []int) { + return fileDescriptor_74118427820fff75, []int{4} +} +func (m *EventSelectiveSlashing) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventSelectiveSlashing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventSelectiveSlashing.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventSelectiveSlashing) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSelectiveSlashing.Merge(m, src) +} +func (m *EventSelectiveSlashing) XXX_Size() int { + return m.Size() +} +func (m *EventSelectiveSlashing) XXX_DiscardUnknown() { + xxx_messageInfo_EventSelectiveSlashing.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSelectiveSlashing proto.InternalMessageInfo + +func (m *EventSelectiveSlashing) GetEvidence() *SelectiveSlashingEvidence { + if m != nil { + return m.Evidence + } + return nil +} + func init() { proto.RegisterType((*EventNewFinalityProvider)(nil), "babylon.btcstaking.v1.EventNewFinalityProvider") proto.RegisterType((*EventNewBTCDelegation)(nil), "babylon.btcstaking.v1.EventNewBTCDelegation") proto.RegisterType((*EventActivateBTCDelegation)(nil), "babylon.btcstaking.v1.EventActivateBTCDelegation") - proto.RegisterType((*EventUnbondingBTCDelegation)(nil), "babylon.btcstaking.v1.EventUnbondingBTCDelegation") proto.RegisterType((*EventUnbondedBTCDelegation)(nil), "babylon.btcstaking.v1.EventUnbondedBTCDelegation") + proto.RegisterType((*EventSelectiveSlashing)(nil), "babylon.btcstaking.v1.EventSelectiveSlashing") } func init() { @@ -314,36 +296,37 @@ func init() { } var fileDescriptor_74118427820fff75 = []byte{ - // 450 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x93, 0xc1, 0x6a, 0xd4, 0x40, - 0x18, 0xc7, 0x37, 0xa9, 0xad, 0x74, 0xaa, 0x2d, 0x06, 0x0b, 0x61, 0x85, 0xb8, 0x04, 0xa9, 0x4b, - 0x0f, 0x89, 0xdd, 0x8a, 0x9e, 0x3c, 0x18, 0xab, 0x58, 0xac, 0xb2, 0xa4, 0xd5, 0x83, 0x3d, 0x84, - 0x99, 0xec, 0x24, 0x19, 0x36, 0x9d, 0x09, 0x99, 0x2f, 0x71, 0xf3, 0x16, 0xbe, 0x80, 0xef, 0xe3, - 0xb1, 0x47, 0xf1, 0x20, 0xb2, 0x7b, 0xf0, 0x35, 0x24, 0x93, 0x6c, 0xdd, 0xca, 0x8a, 0x82, 0x1e, - 0xbd, 0x4d, 0x92, 0xdf, 0xff, 0xf7, 0xf1, 0xfd, 0xc9, 0x20, 0x9b, 0x60, 0x52, 0xa5, 0x82, 0xbb, - 0x04, 0x42, 0x09, 0x78, 0xcc, 0x78, 0xec, 0x96, 0x7b, 0x2e, 0x2d, 0x29, 0x07, 0xe9, 0x64, 0xb9, - 0x00, 0x61, 0x6c, 0xb7, 0x8c, 0xf3, 0x83, 0x71, 0xca, 0xbd, 0xee, 0xcd, 0x58, 0xc4, 0x42, 0x11, - 0x6e, 0x7d, 0x6a, 0xe0, 0xee, 0xce, 0x72, 0xe1, 0x42, 0x54, 0x71, 0xf6, 0x31, 0x32, 0x9f, 0xd6, - 0x43, 0x5e, 0xd1, 0x77, 0xcf, 0x18, 0xc7, 0x29, 0x83, 0x6a, 0x98, 0x8b, 0x92, 0x8d, 0x68, 0x6e, - 0x3c, 0x44, 0x7a, 0x94, 0x99, 0x5a, 0x4f, 0xeb, 0x6f, 0x0c, 0xee, 0x3a, 0x4b, 0xa7, 0x3b, 0x3f, - 0x87, 0x7c, 0x3d, 0xca, 0xec, 0x37, 0x68, 0x7b, 0x2e, 0xf5, 0x4e, 0x9e, 0x1c, 0xd0, 0x94, 0xc6, - 0x18, 0x98, 0xe0, 0xc6, 0x23, 0x74, 0x95, 0x40, 0x18, 0x8c, 0x68, 0xda, 0x6a, 0xef, 0xfc, 0x42, - 0x7b, 0x29, 0xe6, 0xaf, 0x11, 0x08, 0x0f, 0x68, 0x6a, 0x9f, 0xa2, 0xae, 0xf2, 0x3e, 0x0e, 0x81, - 0x95, 0x18, 0xe8, 0x3f, 0x95, 0x7f, 0xd0, 0xd1, 0x2d, 0x65, 0x7f, 0xcd, 0x89, 0xe0, 0x23, 0xc6, - 0xe3, 0xcb, 0xfa, 0x97, 0xa8, 0x26, 0x83, 0x6c, 0xac, 0xec, 0xd7, 0xbc, 0x07, 0x9f, 0xbf, 0xdc, - 0x1e, 0xc4, 0x0c, 0x92, 0x82, 0x38, 0xa1, 0x38, 0x73, 0xdb, 0x59, 0x61, 0x82, 0x19, 0x9f, 0x3f, - 0xb8, 0x50, 0x65, 0x54, 0x3a, 0xde, 0xe1, 0x70, 0xff, 0xfe, 0xbd, 0x61, 0x41, 0x5e, 0xd0, 0xca, - 0x5f, 0x25, 0x10, 0x0e, 0xc7, 0xc6, 0x29, 0xda, 0x8c, 0xb2, 0xa0, 0x31, 0x06, 0x29, 0x93, 0x60, - 0xea, 0xbd, 0x95, 0xbf, 0xd0, 0x6e, 0x44, 0x99, 0x57, 0x8b, 0x8f, 0x98, 0x04, 0x63, 0x07, 0x6d, - 0xb5, 0xfb, 0x06, 0x30, 0x09, 0x12, 0x2c, 0x13, 0x73, 0xa5, 0xa7, 0xf5, 0xd7, 0xfd, 0xeb, 0xed, - 0xeb, 0x93, 0xc9, 0x73, 0x2c, 0x13, 0x63, 0x17, 0xdd, 0x28, 0xe6, 0xdb, 0x5e, 0x90, 0x57, 0x14, - 0xb9, 0x75, 0xf1, 0xa1, 0x61, 0xed, 0x6f, 0x7a, 0xdb, 0x7e, 0xd3, 0x0f, 0x1d, 0xfd, 0xaf, 0x67, - 0xa1, 0x1e, 0xe3, 0x10, 0xa1, 0x28, 0x17, 0x67, 0x81, 0x04, 0x0c, 0xd4, 0x5c, 0xed, 0x69, 0xfd, - 0xcd, 0xc1, 0xee, 0x9f, 0xfc, 0x80, 0xc7, 0x80, 0xa1, 0x90, 0xfe, 0x7a, 0x9d, 0xae, 0xcf, 0xd4, - 0x3b, 0xfa, 0x38, 0xb5, 0xb4, 0xf3, 0xa9, 0xa5, 0x7d, 0x9d, 0x5a, 0xda, 0xfb, 0x99, 0xd5, 0x39, - 0x9f, 0x59, 0x9d, 0x4f, 0x33, 0xab, 0xf3, 0xf6, 0xb7, 0x9b, 0x4f, 0x16, 0xef, 0xbb, 0xaa, 0x81, - 0xac, 0xa9, 0x8b, 0xbe, 0xff, 0x3d, 0x00, 0x00, 0xff, 0xff, 0x4a, 0x42, 0x90, 0x3e, 0x63, 0x04, - 0x00, 0x00, + // 469 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x41, 0x6f, 0xd3, 0x30, + 0x18, 0x86, 0x9b, 0x8e, 0x0d, 0xe6, 0xc1, 0x26, 0x22, 0x86, 0xa2, 0x1e, 0x42, 0x15, 0xa1, 0x51, + 0xed, 0x90, 0x6c, 0x1d, 0x82, 0x13, 0x07, 0xc2, 0x86, 0x98, 0x28, 0xa8, 0x4a, 0x07, 0x07, 0x76, + 0x88, 0xec, 0xf4, 0x4b, 0x62, 0x35, 0xb3, 0xa3, 0xfa, 0x6b, 0x68, 0xff, 0x05, 0x3f, 0x8b, 0xe3, + 0x8e, 0x88, 0x03, 0x42, 0xed, 0x81, 0xbf, 0x81, 0x92, 0x79, 0xa3, 0x83, 0x55, 0x20, 0xb1, 0x9b, + 0x93, 0x3c, 0xef, 0xf3, 0xc9, 0xaf, 0x63, 0xe2, 0x30, 0xca, 0x26, 0x99, 0x14, 0x1e, 0xc3, 0x48, + 0x21, 0x1d, 0x70, 0x91, 0x78, 0xc5, 0xae, 0x07, 0x05, 0x08, 0x54, 0x6e, 0x3e, 0x94, 0x28, 0xcd, + 0x4d, 0xcd, 0xb8, 0xbf, 0x18, 0xb7, 0xd8, 0x6d, 0xdc, 0x4b, 0x64, 0x22, 0x2b, 0xc2, 0x2b, 0x57, + 0x67, 0x70, 0x63, 0xeb, 0x6a, 0xe1, 0x5c, 0xb4, 0xe2, 0x9c, 0x1e, 0xb1, 0x0e, 0xca, 0x21, 0x6f, + 0xe1, 0xe3, 0x4b, 0x2e, 0x68, 0xc6, 0x71, 0xd2, 0x1d, 0xca, 0x82, 0xf7, 0x61, 0x68, 0x3e, 0x25, + 0xf5, 0x38, 0xb7, 0x8c, 0xa6, 0xd1, 0x5a, 0x6b, 0x3f, 0x72, 0xaf, 0x9c, 0xee, 0xfe, 0x1e, 0x0a, + 0xea, 0x71, 0xee, 0xbc, 0x27, 0x9b, 0xe7, 0x52, 0xff, 0xe8, 0xc5, 0x3e, 0x64, 0x90, 0x50, 0xe4, + 0x52, 0x98, 0xcf, 0xc8, 0x4d, 0x86, 0x51, 0xd8, 0x87, 0x4c, 0x6b, 0x1f, 0x2e, 0xd0, 0x5e, 0x8a, + 0x05, 0x2b, 0x0c, 0xa3, 0x7d, 0xc8, 0x9c, 0x63, 0xd2, 0xa8, 0xbc, 0xcf, 0x23, 0xe4, 0x05, 0x45, + 0xb8, 0x56, 0xf9, 0x8f, 0xba, 0xb6, 0xbf, 0x13, 0x4c, 0x8a, 0x3e, 0xf4, 0x2f, 0xdb, 0xdf, 0x90, + 0x12, 0x0c, 0xf3, 0x41, 0x25, 0xbf, 0xed, 0x3f, 0xf9, 0xfa, 0xed, 0x41, 0x3b, 0xe1, 0x98, 0x8e, + 0x98, 0x1b, 0xc9, 0x13, 0x4f, 0x8f, 0x8a, 0x52, 0xca, 0xc5, 0xf9, 0x83, 0x87, 0x93, 0x1c, 0x94, + 0xeb, 0x1f, 0x76, 0xf7, 0x1e, 0xef, 0x74, 0x47, 0xec, 0x35, 0x4c, 0x82, 0x65, 0x86, 0x51, 0x77, + 0x60, 0x1e, 0x93, 0xf5, 0x38, 0x0f, 0xcf, 0x8c, 0x61, 0xc6, 0x15, 0x5a, 0xf5, 0xe6, 0xd2, 0x7f, + 0x68, 0xd7, 0xe2, 0xdc, 0x2f, 0xc5, 0x1d, 0xae, 0xd0, 0xdc, 0x22, 0x1b, 0x7a, 0xbb, 0x21, 0x8e, + 0xc3, 0x94, 0xaa, 0xd4, 0x5a, 0x6a, 0x1a, 0xad, 0xd5, 0xe0, 0x8e, 0x7e, 0x7d, 0x34, 0x7e, 0x45, + 0x55, 0x6a, 0x6e, 0x93, 0xbb, 0xa3, 0x6a, 0xb3, 0xf3, 0xe4, 0x8d, 0x8a, 0xdc, 0xb8, 0xf8, 0xa0, + 0xd9, 0x43, 0x42, 0xe2, 0xa1, 0x3c, 0x09, 0x15, 0x52, 0x04, 0x6b, 0xb9, 0x69, 0xb4, 0xd6, 0xdb, + 0xdb, 0xff, 0x52, 0x70, 0x0f, 0x29, 0x8e, 0x54, 0xb0, 0x5a, 0xa6, 0xcb, 0x35, 0x38, 0x31, 0xb9, + 0x5f, 0x15, 0xdd, 0x83, 0x0c, 0xca, 0x93, 0x84, 0x5e, 0x46, 0x55, 0xca, 0x45, 0x62, 0x76, 0xc8, + 0x2d, 0x28, 0x7f, 0x23, 0x11, 0x81, 0x3e, 0xc3, 0x9d, 0x05, 0x23, 0xfe, 0xc8, 0x1e, 0xe8, 0x5c, + 0x70, 0x61, 0xf0, 0x3b, 0x9f, 0xa7, 0xb6, 0x71, 0x3a, 0xb5, 0x8d, 0xef, 0x53, 0xdb, 0xf8, 0x34, + 0xb3, 0x6b, 0xa7, 0x33, 0xbb, 0xf6, 0x65, 0x66, 0xd7, 0x3e, 0xfc, 0xb5, 0xe1, 0xf1, 0xfc, 0xbd, + 0xa9, 0xea, 0x66, 0x2b, 0xd5, 0x85, 0xd9, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x88, 0x9f, + 0xc0, 0xab, 0x03, 0x00, 0x00, } func (m *EventNewFinalityProvider) Marshal() (dAtA []byte, err error) { @@ -451,7 +434,7 @@ func (m *EventActivateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } -func (m *EventUnbondingBTCDelegation) Marshal() (dAtA []byte, err error) { +func (m *EventUnbondedBTCDelegation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -461,16 +444,21 @@ func (m *EventUnbondingBTCDelegation) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *EventUnbondingBTCDelegation) MarshalTo(dAtA []byte) (int, error) { +func (m *EventUnbondedBTCDelegation) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *EventUnbondingBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *EventUnbondedBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l + if m.FromState != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.FromState)) + i-- + dAtA[i] = 0x28 + } if len(m.UnbondingTxHash) > 0 { i -= len(m.UnbondingTxHash) copy(dAtA[i:], m.UnbondingTxHash) @@ -514,7 +502,7 @@ func (m *EventUnbondingBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } -func (m *EventUnbondedBTCDelegation) Marshal() (dAtA []byte, err error) { +func (m *EventSelectiveSlashing) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -524,56 +512,23 @@ func (m *EventUnbondedBTCDelegation) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *EventUnbondedBTCDelegation) MarshalTo(dAtA []byte) (int, error) { +func (m *EventSelectiveSlashing) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *EventUnbondedBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *EventSelectiveSlashing) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.FromState != 0 { - i = encodeVarintEvents(dAtA, i, uint64(m.FromState)) - i-- - dAtA[i] = 0x28 - } - if len(m.UnbondingTxHash) > 0 { - i -= len(m.UnbondingTxHash) - copy(dAtA[i:], m.UnbondingTxHash) - i = encodeVarintEvents(dAtA, i, uint64(len(m.UnbondingTxHash))) - i-- - dAtA[i] = 0x22 - } - if len(m.StakingTxHash) > 0 { - i -= len(m.StakingTxHash) - copy(dAtA[i:], m.StakingTxHash) - i = encodeVarintEvents(dAtA, i, uint64(len(m.StakingTxHash))) - i-- - dAtA[i] = 0x1a - } - if len(m.FpBtcPkList) > 0 { - for iNdEx := len(m.FpBtcPkList) - 1; iNdEx >= 0; iNdEx-- { - { - size := m.FpBtcPkList[iNdEx].Size() - i -= size - if _, err := m.FpBtcPkList[iNdEx].MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintEvents(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - if m.BtcPk != nil { + if m.Evidence != nil { { - size := m.BtcPk.Size() - i -= size - if _, err := m.BtcPk.MarshalTo(dAtA[i:]); err != nil { + size, err := m.Evidence.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { return 0, err } + i -= size i = encodeVarintEvents(dAtA, i, uint64(size)) } i-- @@ -632,7 +587,7 @@ func (m *EventActivateBTCDelegation) Size() (n int) { return n } -func (m *EventUnbondingBTCDelegation) Size() (n int) { +func (m *EventUnbondedBTCDelegation) Size() (n int) { if m == nil { return 0 } @@ -656,36 +611,22 @@ func (m *EventUnbondingBTCDelegation) Size() (n int) { if l > 0 { n += 1 + l + sovEvents(uint64(l)) } + if m.FromState != 0 { + n += 1 + sovEvents(uint64(m.FromState)) + } return n } -func (m *EventUnbondedBTCDelegation) Size() (n int) { +func (m *EventSelectiveSlashing) Size() (n int) { if m == nil { return 0 } var l int _ = l - if m.BtcPk != nil { - l = m.BtcPk.Size() - n += 1 + l + sovEvents(uint64(l)) - } - if len(m.FpBtcPkList) > 0 { - for _, e := range m.FpBtcPkList { - l = e.Size() - n += 1 + l + sovEvents(uint64(l)) - } - } - l = len(m.StakingTxHash) - if l > 0 { + if m.Evidence != nil { + l = m.Evidence.Size() n += 1 + l + sovEvents(uint64(l)) } - l = len(m.UnbondingTxHash) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - if m.FromState != 0 { - n += 1 + sovEvents(uint64(m.FromState)) - } return n } @@ -953,7 +894,7 @@ func (m *EventActivateBTCDelegation) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { +func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -976,10 +917,10 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventUnbondingBTCDelegation: wiretype end group for non-group") + return fmt.Errorf("proto: EventUnbondedBTCDelegation: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventUnbondingBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventUnbondedBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1116,6 +1057,25 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { } m.UnbondingTxHash = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FromState", wireType) + } + m.FromState = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FromState |= BTCDelegationStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) @@ -1137,7 +1097,7 @@ func (m *EventUnbondingBTCDelegation) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { +func (m *EventSelectiveSlashing) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1160,17 +1120,17 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventUnbondedBTCDelegation: wiretype end group for non-group") + return fmt.Errorf("proto: EventSelectiveSlashing: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventUnbondedBTCDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventSelectiveSlashing: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BtcPk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -1180,145 +1140,28 @@ func (m *EventUnbondedBTCDelegation) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.BtcPk = &v - if err := m.BtcPk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FpBtcPkList", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF + if m.Evidence == nil { + m.Evidence = &SelectiveSlashingEvidence{} } - var v github_com_babylonchain_babylon_types.BIP340PubKey - m.FpBtcPkList = append(m.FpBtcPkList, v) - if err := m.FpBtcPkList[len(m.FpBtcPkList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Evidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.StakingTxHash = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTxHash", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.UnbondingTxHash = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FromState", wireType) - } - m.FromState = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.FromState |= BTCDelegationStatus(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 88f459aa4..8f28ec3c5 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -541,6 +541,111 @@ func (m *MsgBTCUndelegateResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgBTCUndelegateResponse proto.InternalMessageInfo +// MsgSelectiveSlashingEvidence is the message for handling evidence of selective slashing +// launched by a finality provider +type MsgSelectiveSlashingEvidence struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // staking_tx_hash is the hash of the staking tx. + // It uniquely identifies a BTC delegation + StakingTxHash string `protobuf:"bytes,2,opt,name=staking_tx_hash,json=stakingTxHash,proto3" json:"staking_tx_hash,omitempty"` + // recovered_fp_btc_sk is the BTC SK of the finality provider who + // launches the selective slashing offence. The SK is recovered by + // using a covenant adaptor signature and the corresponding Schnorr + // signature + RecoveredFpBtcSk []byte `protobuf:"bytes,3,opt,name=recovered_fp_btc_sk,json=recoveredFpBtcSk,proto3" json:"recovered_fp_btc_sk,omitempty"` +} + +func (m *MsgSelectiveSlashingEvidence) Reset() { *m = MsgSelectiveSlashingEvidence{} } +func (m *MsgSelectiveSlashingEvidence) String() string { return proto.CompactTextString(m) } +func (*MsgSelectiveSlashingEvidence) ProtoMessage() {} +func (*MsgSelectiveSlashingEvidence) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{8} +} +func (m *MsgSelectiveSlashingEvidence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSelectiveSlashingEvidence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSelectiveSlashingEvidence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSelectiveSlashingEvidence) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSelectiveSlashingEvidence.Merge(m, src) +} +func (m *MsgSelectiveSlashingEvidence) XXX_Size() int { + return m.Size() +} +func (m *MsgSelectiveSlashingEvidence) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSelectiveSlashingEvidence.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSelectiveSlashingEvidence proto.InternalMessageInfo + +func (m *MsgSelectiveSlashingEvidence) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgSelectiveSlashingEvidence) GetStakingTxHash() string { + if m != nil { + return m.StakingTxHash + } + return "" +} + +func (m *MsgSelectiveSlashingEvidence) GetRecoveredFpBtcSk() []byte { + if m != nil { + return m.RecoveredFpBtcSk + } + return nil +} + +// MsgSelectiveSlashingEvidenceResponse is the response for MsgSelectiveSlashingEvidence +type MsgSelectiveSlashingEvidenceResponse struct { +} + +func (m *MsgSelectiveSlashingEvidenceResponse) Reset() { *m = MsgSelectiveSlashingEvidenceResponse{} } +func (m *MsgSelectiveSlashingEvidenceResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSelectiveSlashingEvidenceResponse) ProtoMessage() {} +func (*MsgSelectiveSlashingEvidenceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_4baddb53e97f38f2, []int{9} +} +func (m *MsgSelectiveSlashingEvidenceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSelectiveSlashingEvidenceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSelectiveSlashingEvidenceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSelectiveSlashingEvidenceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSelectiveSlashingEvidenceResponse.Merge(m, src) +} +func (m *MsgSelectiveSlashingEvidenceResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSelectiveSlashingEvidenceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSelectiveSlashingEvidenceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSelectiveSlashingEvidenceResponse proto.InternalMessageInfo + // MsgUpdateParams defines a message for updating btcstaking module parameters. type MsgUpdateParams struct { // authority is the address of the governance account. @@ -558,7 +663,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{8} + return fileDescriptor_4baddb53e97f38f2, []int{10} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -609,7 +714,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4baddb53e97f38f2, []int{9} + return fileDescriptor_4baddb53e97f38f2, []int{11} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -647,6 +752,8 @@ func init() { proto.RegisterType((*MsgAddCovenantSigsResponse)(nil), "babylon.btcstaking.v1.MsgAddCovenantSigsResponse") proto.RegisterType((*MsgBTCUndelegate)(nil), "babylon.btcstaking.v1.MsgBTCUndelegate") proto.RegisterType((*MsgBTCUndelegateResponse)(nil), "babylon.btcstaking.v1.MsgBTCUndelegateResponse") + proto.RegisterType((*MsgSelectiveSlashingEvidence)(nil), "babylon.btcstaking.v1.MsgSelectiveSlashingEvidence") + proto.RegisterType((*MsgSelectiveSlashingEvidenceResponse)(nil), "babylon.btcstaking.v1.MsgSelectiveSlashingEvidenceResponse") proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btcstaking.v1.MsgUpdateParams") proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btcstaking.v1.MsgUpdateParamsResponse") } @@ -654,79 +761,84 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/tx.proto", fileDescriptor_4baddb53e97f38f2) } var fileDescriptor_4baddb53e97f38f2 = []byte{ - // 1145 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1a, 0x47, - 0x14, 0xf7, 0x82, 0x4d, 0xea, 0x87, 0xc1, 0xe9, 0x26, 0xb1, 0xd7, 0xb4, 0x01, 0x82, 0xdb, 0x84, - 0x44, 0xf5, 0x12, 0x9c, 0xda, 0x6a, 0x1d, 0xa9, 0x52, 0xb0, 0x13, 0x25, 0xaa, 0x51, 0xd1, 0x82, - 0x7b, 0x68, 0x0f, 0x68, 0x59, 0x86, 0x65, 0x04, 0xec, 0xac, 0x76, 0x06, 0x04, 0xea, 0xa5, 0x8a, - 0xfa, 0x01, 0x7a, 0xea, 0xbd, 0xdf, 0x20, 0x87, 0x7c, 0x84, 0x1e, 0x72, 0x8c, 0xa2, 0x1e, 0x2a, - 0x57, 0xb2, 0x2a, 0xfb, 0x90, 0x4f, 0xd0, 0x7b, 0xb5, 0xbb, 0xb3, 0x7f, 0xa0, 0xac, 0x62, 0xc7, - 0xb9, 0xed, 0xcc, 0xfc, 0xde, 0xef, 0xbd, 0xf7, 0x7b, 0xef, 0xed, 0x0c, 0x64, 0x5b, 0x6a, 0x6b, - 0xd2, 0x27, 0x46, 0xa9, 0xc5, 0x34, 0xca, 0xd4, 0x1e, 0x36, 0xf4, 0xd2, 0xa8, 0x5c, 0x62, 0x63, - 0xd9, 0xb4, 0x08, 0x23, 0xe2, 0x0d, 0x7e, 0x2e, 0x07, 0xe7, 0xf2, 0xa8, 0x9c, 0xb9, 0xae, 0x13, - 0x9d, 0x38, 0x88, 0x92, 0xfd, 0xe5, 0x82, 0x33, 0x1b, 0x1a, 0xa1, 0x03, 0x42, 0x9b, 0xee, 0x81, - 0xbb, 0xe0, 0x47, 0xeb, 0xee, 0xaa, 0x34, 0xa0, 0x0e, 0xff, 0x80, 0xea, 0xfc, 0xa0, 0xc0, 0x0f, - 0x34, 0x6b, 0x62, 0x32, 0x52, 0xa2, 0x48, 0x33, 0xb7, 0x77, 0x76, 0x7b, 0xe5, 0x52, 0x0f, 0x4d, - 0x3c, 0xe3, 0xc2, 0xfc, 0x20, 0x4d, 0xd5, 0x52, 0x07, 0x1e, 0xe6, 0x8b, 0x10, 0x46, 0xeb, 0x22, - 0xad, 0x67, 0x12, 0x6c, 0x30, 0x1b, 0x36, 0xb5, 0xc1, 0xd1, 0x9f, 0x71, 0xaf, 0x01, 0x5b, 0x0b, - 0x31, 0xb5, 0xec, 0xad, 0x39, 0x2a, 0x17, 0xe1, 0x97, 0x98, 0x2e, 0xa0, 0xf0, 0x7b, 0x1c, 0x36, - 0xaa, 0x54, 0xdf, 0xb7, 0x90, 0xca, 0xd0, 0x13, 0x6c, 0xa8, 0x7d, 0xcc, 0x26, 0x35, 0x8b, 0x8c, - 0x70, 0x1b, 0x59, 0xe2, 0x1a, 0x24, 0x28, 0xd6, 0x0d, 0x64, 0x49, 0x42, 0x5e, 0x28, 0x2e, 0x2b, - 0x7c, 0x25, 0x3e, 0x86, 0x64, 0x1b, 0x51, 0xcd, 0xc2, 0x26, 0xc3, 0xc4, 0x90, 0x62, 0x79, 0xa1, - 0x98, 0xdc, 0xde, 0x94, 0xb9, 0x5e, 0x81, 0xca, 0x4e, 0x48, 0xf2, 0x41, 0x00, 0x55, 0xc2, 0x76, - 0x62, 0x15, 0x40, 0x23, 0x83, 0x01, 0xa6, 0xd4, 0x66, 0x89, 0xdb, 0x2e, 0x2a, 0x5b, 0xc7, 0x27, - 0xb9, 0x4f, 0x5c, 0x22, 0xda, 0xee, 0xc9, 0x98, 0x94, 0x06, 0x2a, 0xeb, 0xca, 0x87, 0x48, 0x57, - 0xb5, 0xc9, 0x01, 0xd2, 0xde, 0xbc, 0xdc, 0x02, 0xee, 0xe7, 0x00, 0x69, 0x4a, 0x88, 0x40, 0xfc, - 0x06, 0x80, 0xa7, 0xdb, 0x34, 0x7b, 0xd2, 0xa2, 0x13, 0x54, 0xce, 0x0b, 0xca, 0xad, 0x8e, 0xec, - 0x57, 0x47, 0xae, 0x0d, 0x5b, 0xdf, 0xa2, 0x89, 0xb2, 0xcc, 0x4d, 0x6a, 0x3d, 0xb1, 0x0a, 0x89, - 0x16, 0xd3, 0x6c, 0xdb, 0xa5, 0xbc, 0x50, 0x5c, 0xa9, 0xec, 0x1e, 0x9f, 0xe4, 0xb6, 0x75, 0xcc, - 0xba, 0xc3, 0x96, 0xac, 0x91, 0x41, 0x89, 0x23, 0xb5, 0xae, 0x8a, 0x0d, 0x6f, 0x51, 0x62, 0x13, - 0x13, 0x51, 0xb9, 0xf2, 0xac, 0xf6, 0xe0, 0xcb, 0xfb, 0x9c, 0x72, 0xa9, 0xc5, 0xb4, 0x5a, 0x4f, - 0xdc, 0x83, 0xb8, 0x49, 0x4c, 0x29, 0xe1, 0xc4, 0x51, 0x94, 0xe7, 0xb6, 0xa1, 0x5c, 0xb3, 0x08, - 0xe9, 0x7c, 0xd7, 0xa9, 0x11, 0x4a, 0x91, 0x93, 0x85, 0x62, 0x1b, 0xed, 0x25, 0x9f, 0xbf, 0x7d, - 0x71, 0x8f, 0xab, 0x5d, 0xd8, 0x84, 0x5b, 0x91, 0x25, 0x52, 0x10, 0x35, 0x89, 0x41, 0x51, 0xe1, - 0xef, 0x2b, 0xb0, 0xe6, 0xa3, 0x2a, 0x8d, 0xfd, 0x03, 0xd4, 0x47, 0xba, 0xea, 0xc8, 0x1c, 0x55, - 0xc5, 0x69, 0xbd, 0x62, 0x17, 0xd6, 0x8b, 0x27, 0x18, 0x7f, 0x8f, 0x04, 0x43, 0x5a, 0x2f, 0x7e, - 0x08, 0xad, 0x7f, 0x84, 0x74, 0xc7, 0x6c, 0xba, 0x8c, 0xcd, 0x3e, 0xa6, 0x4c, 0x5a, 0xca, 0xc7, - 0x2f, 0x41, 0x9b, 0xec, 0x98, 0x15, 0x9b, 0xf8, 0x10, 0x53, 0x26, 0xde, 0x82, 0x15, 0x9e, 0x50, - 0x93, 0xe1, 0x01, 0x72, 0x2a, 0x9a, 0x52, 0x92, 0x7c, 0xaf, 0x81, 0x07, 0x48, 0xdc, 0x84, 0x94, - 0x07, 0x19, 0xa9, 0xfd, 0x21, 0x92, 0xae, 0xe4, 0x85, 0x62, 0x5c, 0xf1, 0xec, 0xbe, 0xb7, 0xf7, - 0xc4, 0xa7, 0x00, 0x3e, 0xcf, 0x58, 0xfa, 0xc8, 0x91, 0xed, 0x6e, 0x58, 0xb6, 0xd0, 0x90, 0x8f, - 0xca, 0x72, 0xc3, 0x52, 0x0d, 0xaa, 0x6a, 0x76, 0x09, 0x9f, 0x19, 0x1d, 0xa2, 0x2c, 0x7b, 0x0e, - 0xc7, 0xe2, 0x36, 0x24, 0x69, 0x5f, 0xa5, 0x5d, 0x4e, 0xb5, 0xec, 0x48, 0xf8, 0xf1, 0xf1, 0x49, - 0x2e, 0x55, 0x69, 0xec, 0xd7, 0xf9, 0x49, 0x63, 0xac, 0x00, 0xf5, 0xbf, 0x45, 0x02, 0x6b, 0x6d, - 0xb7, 0x27, 0x88, 0xd5, 0xf4, 0xad, 0x29, 0xd6, 0x25, 0x70, 0xcc, 0xbf, 0x3e, 0x3e, 0xc9, 0xed, - 0x5c, 0x44, 0xaa, 0x3a, 0xd6, 0x0d, 0x95, 0x0d, 0x2d, 0xa4, 0x5c, 0xf7, 0x89, 0x3d, 0xdf, 0x75, - 0xac, 0xdb, 0xb2, 0x0d, 0x8d, 0x16, 0x31, 0xda, 0x3c, 0xca, 0xa4, 0xed, 0x46, 0x49, 0xfa, 0x7b, - 0x8d, 0xb1, 0xf8, 0x39, 0xa4, 0x43, 0x10, 0x5b, 0xdb, 0x15, 0x47, 0xdb, 0x54, 0x00, 0xb2, 0xd5, - 0xbd, 0x03, 0xab, 0x01, 0xcc, 0xd5, 0x37, 0xe5, 0xe8, 0x1b, 0x58, 0xbb, 0x0a, 0x3f, 0x86, 0x1b, - 0x01, 0x30, 0xac, 0x50, 0x3a, 0x4a, 0xa1, 0x6b, 0x3e, 0x3e, 0xd8, 0x14, 0x9f, 0x0b, 0x90, 0x0f, - 0xb4, 0x9a, 0xc3, 0x68, 0xab, 0xb6, 0x7a, 0x59, 0xd5, 0x6e, 0xfa, 0x2e, 0x8e, 0x66, 0x63, 0xa8, - 0x63, 0x7d, 0xfa, 0x17, 0x90, 0x87, 0xec, 0xfc, 0xe1, 0xf6, 0xe7, 0xff, 0xdf, 0x18, 0x88, 0x55, - 0xaa, 0x3f, 0x6a, 0xb7, 0xf7, 0xc9, 0x08, 0x19, 0xaa, 0xc1, 0xea, 0x58, 0xa7, 0x91, 0xb3, 0xff, - 0x04, 0x62, 0x7c, 0xe6, 0xdf, 0x7f, 0x48, 0x62, 0x66, 0x4f, 0xbc, 0x0d, 0xab, 0x41, 0x4f, 0x37, - 0xbb, 0x2a, 0xed, 0xba, 0xff, 0x71, 0x25, 0xe5, 0x77, 0xeb, 0x53, 0x95, 0x76, 0xc5, 0x22, 0x5c, - 0x0d, 0xd5, 0xc3, 0x16, 0x90, 0x4a, 0x8b, 0xf6, 0x88, 0x2a, 0xe9, 0xa0, 0x47, 0x9d, 0x88, 0x35, - 0xb8, 0x1a, 0x6e, 0x1b, 0x47, 0xeb, 0xa5, 0xcb, 0x6a, 0x9d, 0x0e, 0x75, 0x9d, 0xdd, 0x9b, 0x0f, - 0x21, 0xe3, 0x87, 0x33, 0xeb, 0x8d, 0x4a, 0x09, 0x27, 0xb0, 0x75, 0x0f, 0x71, 0x34, 0x65, 0x4b, - 0xa7, 0x2b, 0xf3, 0x29, 0x64, 0xfe, 0x2f, 0xbb, 0x5f, 0x95, 0x3f, 0x04, 0xb8, 0x5a, 0xa5, 0x7a, - 0xa5, 0xb1, 0x7f, 0x64, 0xf0, 0x72, 0xa3, 0xc8, 0x9a, 0xcc, 0xd1, 0x32, 0x36, 0x4f, 0xcb, 0x79, - 0x0a, 0xc5, 0x3f, 0xb0, 0x42, 0xd3, 0x49, 0x66, 0x40, 0x9a, 0xcd, 0xc2, 0x4f, 0xf1, 0x37, 0x01, - 0x56, 0xab, 0x54, 0x3f, 0x32, 0xdb, 0x2a, 0x43, 0x35, 0xe7, 0x41, 0x23, 0xee, 0xc2, 0xb2, 0x3a, - 0x64, 0x5d, 0x62, 0x61, 0x36, 0x71, 0x93, 0xac, 0x48, 0x6f, 0x5e, 0x6e, 0x5d, 0xe7, 0x77, 0xcb, - 0xa3, 0x76, 0xdb, 0x42, 0x94, 0xd6, 0x99, 0x85, 0x0d, 0x5d, 0x09, 0xa0, 0xe2, 0x43, 0x48, 0xb8, - 0x4f, 0x22, 0x7e, 0x1b, 0xdd, 0x8c, 0xba, 0x54, 0x1c, 0x50, 0x65, 0xf1, 0xd5, 0x49, 0x6e, 0x41, - 0xe1, 0x26, 0x7b, 0x69, 0x3b, 0xe2, 0x80, 0xac, 0xb0, 0x01, 0xeb, 0x33, 0x71, 0x79, 0x31, 0x6f, - 0xff, 0xb9, 0x08, 0xf1, 0x2a, 0xd5, 0xc5, 0x5f, 0x04, 0x58, 0x8b, 0x78, 0xfa, 0xdc, 0x8f, 0x70, - 0x1d, 0x79, 0x13, 0x67, 0xbe, 0xba, 0xa8, 0x85, 0x17, 0x8e, 0xf8, 0x13, 0x5c, 0x9b, 0x77, 0x6f, - 0x6f, 0xbd, 0x8b, 0x70, 0x0a, 0x9e, 0xd9, 0xb9, 0x10, 0xdc, 0x77, 0x4e, 0x60, 0x75, 0xf6, 0xa7, - 0x71, 0x37, 0x9a, 0x69, 0x06, 0x9a, 0x29, 0x9f, 0x1b, 0xea, 0x3b, 0xec, 0xc0, 0xca, 0x54, 0xb3, - 0xdc, 0x8e, 0xa6, 0x08, 0xe3, 0x32, 0xf2, 0xf9, 0x70, 0xbe, 0x1f, 0x0c, 0xa9, 0xe9, 0xb9, 0xbb, - 0x13, 0x4d, 0x30, 0x05, 0xcc, 0x94, 0xce, 0x09, 0xf4, 0x5c, 0x65, 0x96, 0x7e, 0x7e, 0xfb, 0xe2, - 0x9e, 0x50, 0x39, 0x7c, 0x75, 0x9a, 0x15, 0x5e, 0x9f, 0x66, 0x85, 0x7f, 0x4e, 0xb3, 0xc2, 0xaf, - 0x67, 0xd9, 0x85, 0xd7, 0x67, 0xd9, 0x85, 0xbf, 0xce, 0xb2, 0x0b, 0x3f, 0xbc, 0xf3, 0xf7, 0x3a, - 0x0e, 0xbf, 0xd0, 0x9d, 0x09, 0x6d, 0x25, 0x9c, 0x17, 0xfa, 0x83, 0xff, 0x02, 0x00, 0x00, 0xff, - 0xff, 0x77, 0xf3, 0xc7, 0x94, 0xe1, 0x0c, 0x00, 0x00, + // 1224 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x8f, 0xd3, 0xc6, + 0x17, 0x5f, 0x27, 0x9b, 0xf0, 0xdd, 0x97, 0x4d, 0x76, 0xbf, 0x06, 0x16, 0xaf, 0x0b, 0x49, 0x08, + 0x14, 0x02, 0xea, 0x3a, 0x64, 0x29, 0xa8, 0x05, 0xa9, 0x12, 0xd9, 0x05, 0x81, 0x4a, 0xd4, 0xc8, + 0xc9, 0xf6, 0xd0, 0x1e, 0x22, 0xc7, 0x99, 0x38, 0xa3, 0x24, 0x1e, 0xcb, 0x33, 0x89, 0x12, 0xf5, + 0x52, 0xa1, 0x5e, 0x2b, 0xf5, 0xd4, 0x43, 0x6f, 0xfd, 0x0f, 0x38, 0xf0, 0x27, 0xf4, 0xc0, 0x11, + 0x71, 0xaa, 0xb6, 0xd2, 0xaa, 0x82, 0x4a, 0xfc, 0x05, 0xbd, 0x57, 0xb6, 0xc7, 0x3f, 0x92, 0xc6, + 0x85, 0x65, 0xb9, 0xc5, 0x33, 0x9f, 0xf7, 0x79, 0xef, 0x7d, 0xde, 0x7b, 0x33, 0x13, 0xc8, 0x77, + 0xb4, 0xce, 0x6c, 0x48, 0xcc, 0x4a, 0x87, 0xe9, 0x94, 0x69, 0x03, 0x6c, 0x1a, 0x95, 0x49, 0xb5, + 0xc2, 0xa6, 0x8a, 0x65, 0x13, 0x46, 0xc4, 0xb3, 0x7c, 0x5f, 0x09, 0xf7, 0x95, 0x49, 0x55, 0x3e, + 0x63, 0x10, 0x83, 0xb8, 0x88, 0x8a, 0xf3, 0xcb, 0x03, 0xcb, 0xdb, 0x3a, 0xa1, 0x23, 0x42, 0xdb, + 0xde, 0x86, 0xf7, 0xc1, 0xb7, 0xce, 0x79, 0x5f, 0x95, 0x11, 0x75, 0xf9, 0x47, 0xd4, 0xe0, 0x1b, + 0x25, 0xbe, 0xa1, 0xdb, 0x33, 0x8b, 0x91, 0x0a, 0x45, 0xba, 0xb5, 0x7b, 0xeb, 0xf6, 0xa0, 0x5a, + 0x19, 0xa0, 0x99, 0x6f, 0x5c, 0x5a, 0x1e, 0xa4, 0xa5, 0xd9, 0xda, 0xc8, 0xc7, 0x7c, 0x12, 0xc1, + 0xe8, 0x7d, 0xa4, 0x0f, 0x2c, 0x82, 0x4d, 0xe6, 0xc0, 0xe6, 0x16, 0x38, 0xfa, 0x32, 0xf7, 0x1a, + 0xb2, 0x75, 0x10, 0xd3, 0xaa, 0xfe, 0x37, 0x47, 0x15, 0x62, 0xfc, 0x12, 0xcb, 0x03, 0x94, 0x7e, + 0x4d, 0xc2, 0x76, 0x9d, 0x1a, 0x7b, 0x36, 0xd2, 0x18, 0x7a, 0x80, 0x4d, 0x6d, 0x88, 0xd9, 0xac, + 0x61, 0x93, 0x09, 0xee, 0x22, 0x5b, 0xdc, 0x82, 0x34, 0xc5, 0x86, 0x89, 0x6c, 0x49, 0x28, 0x0a, + 0xe5, 0x35, 0x95, 0x7f, 0x89, 0xf7, 0x21, 0xd3, 0x45, 0x54, 0xb7, 0xb1, 0xc5, 0x30, 0x31, 0xa5, + 0x44, 0x51, 0x28, 0x67, 0x76, 0x2f, 0x29, 0x5c, 0xaf, 0x50, 0x65, 0x37, 0x24, 0x65, 0x3f, 0x84, + 0xaa, 0x51, 0x3b, 0xb1, 0x0e, 0xa0, 0x93, 0xd1, 0x08, 0x53, 0xea, 0xb0, 0x24, 0x1d, 0x17, 0xb5, + 0x9d, 0xc3, 0xa3, 0xc2, 0x47, 0x1e, 0x11, 0xed, 0x0e, 0x14, 0x4c, 0x2a, 0x23, 0x8d, 0xf5, 0x95, + 0xc7, 0xc8, 0xd0, 0xf4, 0xd9, 0x3e, 0xd2, 0x5f, 0x3e, 0xdb, 0x01, 0xee, 0x67, 0x1f, 0xe9, 0x6a, + 0x84, 0x40, 0xfc, 0x02, 0x80, 0xa7, 0xdb, 0xb6, 0x06, 0xd2, 0xaa, 0x1b, 0x54, 0xc1, 0x0f, 0xca, + 0xab, 0x8e, 0x12, 0x54, 0x47, 0x69, 0x8c, 0x3b, 0x5f, 0xa2, 0x99, 0xba, 0xc6, 0x4d, 0x1a, 0x03, + 0xb1, 0x0e, 0xe9, 0x0e, 0xd3, 0x1d, 0xdb, 0x54, 0x51, 0x28, 0xaf, 0xd7, 0x6e, 0x1f, 0x1e, 0x15, + 0x76, 0x0d, 0xcc, 0xfa, 0xe3, 0x8e, 0xa2, 0x93, 0x51, 0x85, 0x23, 0xf5, 0xbe, 0x86, 0x4d, 0xff, + 0xa3, 0xc2, 0x66, 0x16, 0xa2, 0x4a, 0xed, 0x51, 0xe3, 0xe6, 0xa7, 0x37, 0x38, 0x65, 0xaa, 0xc3, + 0xf4, 0xc6, 0x40, 0xbc, 0x03, 0x49, 0x8b, 0x58, 0x52, 0xda, 0x8d, 0xa3, 0xac, 0x2c, 0x6d, 0x43, + 0xa5, 0x61, 0x13, 0xd2, 0xfb, 0xaa, 0xd7, 0x20, 0x94, 0x22, 0x37, 0x0b, 0xd5, 0x31, 0xba, 0x93, + 0x79, 0xf2, 0xe6, 0xe9, 0x75, 0xae, 0x76, 0xe9, 0x12, 0x5c, 0x8c, 0x2d, 0x91, 0x8a, 0xa8, 0x45, + 0x4c, 0x8a, 0x4a, 0x7f, 0x9c, 0x82, 0xad, 0x00, 0x55, 0x6b, 0xed, 0xed, 0xa3, 0x21, 0x32, 0x34, + 0x57, 0xe6, 0xb8, 0x2a, 0xce, 0xeb, 0x95, 0x38, 0xb6, 0x5e, 0x3c, 0xc1, 0xe4, 0x7b, 0x24, 0x18, + 0xd1, 0x7a, 0xf5, 0x43, 0x68, 0xfd, 0x2d, 0xe4, 0x7a, 0x56, 0xdb, 0x63, 0x6c, 0x0f, 0x31, 0x65, + 0x52, 0xaa, 0x98, 0x3c, 0x01, 0x6d, 0xa6, 0x67, 0xd5, 0x1c, 0xe2, 0xc7, 0x98, 0x32, 0xf1, 0x22, + 0xac, 0xf3, 0x84, 0xda, 0x0c, 0x8f, 0x90, 0x5b, 0xd1, 0xac, 0x9a, 0xe1, 0x6b, 0x2d, 0x3c, 0x42, + 0xe2, 0x25, 0xc8, 0xfa, 0x90, 0x89, 0x36, 0x1c, 0x23, 0xe9, 0x54, 0x51, 0x28, 0x27, 0x55, 0xdf, + 0xee, 0x6b, 0x67, 0x4d, 0x7c, 0x08, 0x10, 0xf0, 0x4c, 0xa5, 0xff, 0xb9, 0xb2, 0x5d, 0x8b, 0xca, + 0x16, 0x19, 0xf2, 0x49, 0x55, 0x69, 0xd9, 0x9a, 0x49, 0x35, 0xdd, 0x29, 0xe1, 0x23, 0xb3, 0x47, + 0xd4, 0x35, 0xdf, 0xe1, 0x54, 0xdc, 0x85, 0x0c, 0x1d, 0x6a, 0xb4, 0xcf, 0xa9, 0xd6, 0x5c, 0x09, + 0xff, 0x7f, 0x78, 0x54, 0xc8, 0xd6, 0x5a, 0x7b, 0x4d, 0xbe, 0xd3, 0x9a, 0xaa, 0x40, 0x83, 0xdf, + 0x22, 0x81, 0xad, 0xae, 0xd7, 0x13, 0xc4, 0x6e, 0x07, 0xd6, 0x14, 0x1b, 0x12, 0xb8, 0xe6, 0x9f, + 0x1f, 0x1e, 0x15, 0x6e, 0x1d, 0x47, 0xaa, 0x26, 0x36, 0x4c, 0x8d, 0x8d, 0x6d, 0xa4, 0x9e, 0x09, + 0x88, 0x7d, 0xdf, 0x4d, 0x6c, 0x38, 0xb2, 0x8d, 0xcd, 0x0e, 0x31, 0xbb, 0x3c, 0xca, 0x8c, 0xe3, + 0x46, 0xcd, 0x04, 0x6b, 0xad, 0xa9, 0xf8, 0x31, 0xe4, 0x22, 0x10, 0x47, 0xdb, 0x75, 0x57, 0xdb, + 0x6c, 0x08, 0x72, 0xd4, 0xbd, 0x0a, 0x1b, 0x21, 0xcc, 0xd3, 0x37, 0xeb, 0xea, 0x1b, 0x5a, 0x7b, + 0x0a, 0xdf, 0x87, 0xb3, 0x21, 0x30, 0xaa, 0x50, 0x2e, 0x4e, 0xa1, 0xd3, 0x01, 0x3e, 0x5c, 0x14, + 0x9f, 0x08, 0x50, 0x0c, 0xb5, 0x5a, 0xc2, 0xe8, 0xa8, 0xb6, 0x71, 0x52, 0xd5, 0x2e, 0x04, 0x2e, + 0x0e, 0x16, 0x63, 0x68, 0x62, 0x63, 0xfe, 0x08, 0x28, 0x42, 0x7e, 0xf9, 0x70, 0x07, 0xf3, 0xff, + 0x77, 0x02, 0xc4, 0x3a, 0x35, 0xee, 0x75, 0xbb, 0x7b, 0x64, 0x82, 0x4c, 0xcd, 0x64, 0x4d, 0x6c, + 0xd0, 0xd8, 0xd9, 0x7f, 0x00, 0x09, 0x3e, 0xf3, 0xef, 0x3f, 0x24, 0x09, 0x6b, 0x20, 0x5e, 0x81, + 0x8d, 0xb0, 0xa7, 0xdb, 0x7d, 0x8d, 0xf6, 0xbd, 0x73, 0x5c, 0xcd, 0x06, 0xdd, 0xfa, 0x50, 0xa3, + 0x7d, 0xb1, 0x0c, 0x9b, 0x91, 0x7a, 0x38, 0x02, 0x52, 0x69, 0xd5, 0x19, 0x51, 0x35, 0x17, 0xf6, + 0xa8, 0x1b, 0xb1, 0x0e, 0x9b, 0xd1, 0xb6, 0x71, 0xb5, 0x4e, 0x9d, 0x54, 0xeb, 0x5c, 0xa4, 0xeb, + 0x9c, 0xde, 0xbc, 0x0b, 0x72, 0x10, 0xce, 0xa2, 0x37, 0x2a, 0xa5, 0xdd, 0xc0, 0xce, 0xf9, 0x88, + 0x83, 0x39, 0x5b, 0x3a, 0x5f, 0x99, 0xf3, 0x20, 0xff, 0x5b, 0xf6, 0xa0, 0x2a, 0xbf, 0x09, 0xb0, + 0x59, 0xa7, 0x46, 0xad, 0xb5, 0x77, 0x60, 0xf2, 0x72, 0xa3, 0xd8, 0x9a, 0x2c, 0xd1, 0x32, 0xb1, + 0x4c, 0xcb, 0x65, 0x0a, 0x25, 0x3f, 0xb0, 0x42, 0xf3, 0x49, 0xca, 0x20, 0x2d, 0x66, 0x11, 0xa4, + 0xf8, 0x8b, 0x00, 0xe7, 0xeb, 0xd4, 0x68, 0xa2, 0x21, 0xd2, 0x19, 0x9e, 0x20, 0xbf, 0x87, 0xef, + 0x3b, 0xf7, 0x93, 0xa9, 0x9f, 0x3c, 0xdd, 0x1d, 0x38, 0x6d, 0x23, 0x9d, 0x4c, 0x90, 0x8d, 0xba, + 0x6d, 0x7e, 0xca, 0xd3, 0x81, 0x97, 0xb1, 0xba, 0x19, 0x6c, 0x3d, 0x70, 0x4e, 0xec, 0xe6, 0x60, + 0x3e, 0xf0, 0x2b, 0x70, 0xf9, 0xbf, 0x62, 0x0b, 0x92, 0xf8, 0x59, 0x80, 0x8d, 0x3a, 0x35, 0x0e, + 0xac, 0xae, 0xc6, 0x50, 0xc3, 0x7d, 0x95, 0x89, 0xb7, 0x61, 0x4d, 0x1b, 0xb3, 0x3e, 0xb1, 0x31, + 0x9b, 0x79, 0xa1, 0xd7, 0xa4, 0x97, 0xcf, 0x76, 0xce, 0xf0, 0x0b, 0xf2, 0x5e, 0xb7, 0x6b, 0x23, + 0x4a, 0x9b, 0xcc, 0xc6, 0xa6, 0xa1, 0x86, 0x50, 0xf1, 0x2e, 0xa4, 0xbd, 0x77, 0x1d, 0xbf, 0x52, + 0x2f, 0xc4, 0xdd, 0x8c, 0x2e, 0xa8, 0xb6, 0xfa, 0xfc, 0xa8, 0xb0, 0xa2, 0x72, 0x93, 0x3b, 0x39, + 0x27, 0xfa, 0x90, 0xac, 0xb4, 0x0d, 0xe7, 0x16, 0xe2, 0xf2, 0x63, 0xde, 0xfd, 0x2b, 0x05, 0xc9, + 0x3a, 0x35, 0xc4, 0x1f, 0x04, 0xd8, 0x8a, 0x79, 0xbf, 0xdd, 0x88, 0x71, 0x1d, 0xfb, 0x9c, 0x90, + 0x3f, 0x3b, 0xae, 0x85, 0x1f, 0x8e, 0xf8, 0x1d, 0x9c, 0x5e, 0xf6, 0xf8, 0xd8, 0x79, 0x1b, 0xe1, + 0x1c, 0x5c, 0xbe, 0x75, 0x2c, 0x78, 0xe0, 0x9c, 0xc0, 0xc6, 0xe2, 0xc9, 0x77, 0x2d, 0x9e, 0x69, + 0x01, 0x2a, 0x57, 0xdf, 0x19, 0x1a, 0x38, 0xc4, 0x90, 0x9d, 0x1f, 0xea, 0xab, 0xf1, 0x1c, 0x73, + 0x40, 0xb9, 0xf2, 0x8e, 0xc0, 0xc0, 0xd5, 0x8f, 0x02, 0x6c, 0xc7, 0x4f, 0xd7, 0xcd, 0x78, 0xba, + 0x58, 0x23, 0xf9, 0xee, 0x7b, 0x18, 0x05, 0xf1, 0xf4, 0x60, 0x7d, 0x6e, 0x4e, 0xae, 0xc4, 0x93, + 0x45, 0x71, 0xb2, 0xf2, 0x6e, 0x38, 0xdf, 0x8f, 0x9c, 0xfa, 0xfe, 0xcd, 0xd3, 0xeb, 0x42, 0xed, + 0xf1, 0xf3, 0x57, 0x79, 0xe1, 0xc5, 0xab, 0xbc, 0xf0, 0xe7, 0xab, 0xbc, 0xf0, 0xd3, 0xeb, 0xfc, + 0xca, 0x8b, 0xd7, 0xf9, 0x95, 0xdf, 0x5f, 0xe7, 0x57, 0xbe, 0x79, 0xeb, 0x9d, 0x35, 0x8d, 0xfe, + 0xed, 0x71, 0x8f, 0xbd, 0x4e, 0xda, 0xfd, 0xdb, 0x73, 0xf3, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xb2, 0xee, 0xb9, 0x19, 0x36, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -747,10 +859,13 @@ type MsgClient interface { CreateBTCDelegation(ctx context.Context, in *MsgCreateBTCDelegation, opts ...grpc.CallOption) (*MsgCreateBTCDelegationResponse, error) // AddCovenantSigs handles signatures from a covenant member AddCovenantSigs(ctx context.Context, in *MsgAddCovenantSigs, opts ...grpc.CallOption) (*MsgAddCovenantSigsResponse, error) - // UpdateParams updates the btcstaking module parameters. - UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) // BTCUndelegate handles a signature on unbonding tx from its delegator BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) + // SelectiveSlashingEvidence handles the evidence of selective slashing launched + // by a finality provider + SelectiveSlashingEvidence(ctx context.Context, in *MsgSelectiveSlashingEvidence, opts ...grpc.CallOption) (*MsgSelectiveSlashingEvidenceResponse, error) + // UpdateParams updates the btcstaking module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -788,18 +903,27 @@ func (c *msgClient) AddCovenantSigs(ctx context.Context, in *MsgAddCovenantSigs, return out, nil } -func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { - out := new(MsgUpdateParamsResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) +func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) { + out := new(MsgBTCUndelegateResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/BTCUndelegate", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *msgClient) BTCUndelegate(ctx context.Context, in *MsgBTCUndelegate, opts ...grpc.CallOption) (*MsgBTCUndelegateResponse, error) { - out := new(MsgBTCUndelegateResponse) - err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/BTCUndelegate", in, out, opts...) +func (c *msgClient) SelectiveSlashingEvidence(ctx context.Context, in *MsgSelectiveSlashingEvidence, opts ...grpc.CallOption) (*MsgSelectiveSlashingEvidenceResponse, error) { + out := new(MsgSelectiveSlashingEvidenceResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/SelectiveSlashingEvidence", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btcstaking.v1.Msg/UpdateParams", in, out, opts...) if err != nil { return nil, err } @@ -814,10 +938,13 @@ type MsgServer interface { CreateBTCDelegation(context.Context, *MsgCreateBTCDelegation) (*MsgCreateBTCDelegationResponse, error) // AddCovenantSigs handles signatures from a covenant member AddCovenantSigs(context.Context, *MsgAddCovenantSigs) (*MsgAddCovenantSigsResponse, error) - // UpdateParams updates the btcstaking module parameters. - UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) // BTCUndelegate handles a signature on unbonding tx from its delegator BTCUndelegate(context.Context, *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) + // SelectiveSlashingEvidence handles the evidence of selective slashing launched + // by a finality provider + SelectiveSlashingEvidence(context.Context, *MsgSelectiveSlashingEvidence) (*MsgSelectiveSlashingEvidenceResponse, error) + // UpdateParams updates the btcstaking module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -833,12 +960,15 @@ func (*UnimplementedMsgServer) CreateBTCDelegation(ctx context.Context, req *Msg func (*UnimplementedMsgServer) AddCovenantSigs(ctx context.Context, req *MsgAddCovenantSigs) (*MsgAddCovenantSigsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddCovenantSigs not implemented") } -func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") -} func (*UnimplementedMsgServer) BTCUndelegate(ctx context.Context, req *MsgBTCUndelegate) (*MsgBTCUndelegateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BTCUndelegate not implemented") } +func (*UnimplementedMsgServer) SelectiveSlashingEvidence(ctx context.Context, req *MsgSelectiveSlashingEvidence) (*MsgSelectiveSlashingEvidenceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SelectiveSlashingEvidence not implemented") +} +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -898,38 +1028,56 @@ func _Msg_AddCovenantSigs_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } -func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgUpdateParams) +func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgBTCUndelegate) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).UpdateParams(ctx, in) + return srv.(MsgServer).BTCUndelegate(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/UpdateParams", + FullMethod: "/babylon.btcstaking.v1.Msg/BTCUndelegate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + return srv.(MsgServer).BTCUndelegate(ctx, req.(*MsgBTCUndelegate)) } return interceptor(ctx, in, info, handler) } -func _Msg_BTCUndelegate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgBTCUndelegate) +func _Msg_SelectiveSlashingEvidence_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSelectiveSlashingEvidence) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).BTCUndelegate(ctx, in) + return srv.(MsgServer).SelectiveSlashingEvidence(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/babylon.btcstaking.v1.Msg/BTCUndelegate", + FullMethod: "/babylon.btcstaking.v1.Msg/SelectiveSlashingEvidence", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).BTCUndelegate(ctx, req.(*MsgBTCUndelegate)) + return srv.(MsgServer).SelectiveSlashingEvidence(ctx, req.(*MsgSelectiveSlashingEvidence)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btcstaking.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) } return interceptor(ctx, in, info, handler) } @@ -950,14 +1098,18 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AddCovenantSigs", Handler: _Msg_AddCovenantSigs_Handler, }, - { - MethodName: "UpdateParams", - Handler: _Msg_UpdateParams_Handler, - }, { MethodName: "BTCUndelegate", Handler: _Msg_BTCUndelegate_Handler, }, + { + MethodName: "SelectiveSlashingEvidence", + Handler: _Msg_SelectiveSlashingEvidence_Handler, + }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btcstaking/v1/tx.proto", @@ -1440,6 +1592,73 @@ func (m *MsgBTCUndelegateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *MsgSelectiveSlashingEvidence) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSelectiveSlashingEvidence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSelectiveSlashingEvidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RecoveredFpBtcSk) > 0 { + i -= len(m.RecoveredFpBtcSk) + copy(dAtA[i:], m.RecoveredFpBtcSk) + i = encodeVarintTx(dAtA, i, uint64(len(m.RecoveredFpBtcSk))) + i-- + dAtA[i] = 0x1a + } + if len(m.StakingTxHash) > 0 { + i -= len(m.StakingTxHash) + copy(dAtA[i:], m.StakingTxHash) + i = encodeVarintTx(dAtA, i, uint64(len(m.StakingTxHash))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSelectiveSlashingEvidenceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSelectiveSlashingEvidenceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSelectiveSlashingEvidenceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1708,6 +1927,36 @@ func (m *MsgBTCUndelegateResponse) Size() (n int) { return n } +func (m *MsgSelectiveSlashingEvidence) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.StakingTxHash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.RecoveredFpBtcSk) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSelectiveSlashingEvidenceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgUpdateParams) Size() (n int) { if m == nil { return 0 @@ -3106,6 +3355,204 @@ func (m *MsgBTCUndelegateResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSelectiveSlashingEvidence) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSelectiveSlashingEvidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSelectiveSlashingEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingTxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StakingTxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RecoveredFpBtcSk", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RecoveredFpBtcSk = append(m.RecoveredFpBtcSk[:0], dAtA[iNdEx:postIndex]...) + if m.RecoveredFpBtcSk == nil { + m.RecoveredFpBtcSk = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSelectiveSlashingEvidenceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSelectiveSlashingEvidenceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSelectiveSlashingEvidenceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From 569d537614129666206831f28d63a342228b14e9 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 19 Dec 2023 18:19:10 +1100 Subject: [PATCH 133/202] btcstaking: check consistency between staking tx output and unbonding tx input (#151) --- x/btcstaking/keeper/msg_server.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index f08391396..b7d579b23 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -262,6 +262,15 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre return nil, types.ErrInvalidUnbondingTx.Wrapf("cannot be converted to wire.MsgTx: %v", err) } + // Check that unbonding tx input is pointing to staking tx + if !unbondingMsgTx.TxIn[0].PreviousOutPoint.Hash.IsEqual(&stakingTxHash) { + return nil, types.ErrInvalidUnbondingTx.Wrapf("slashing transaction must spend staking output") + } + // Check that staking tx output index matches unbonding tx output index + if unbondingMsgTx.TxIn[0].PreviousOutPoint.Index != stakingOutputIdx { + return nil, types.ErrInvalidUnbondingTx.Wrapf("slashing transaction input must spend staking output") + } + // Check unbonding time (staking time from unbonding tx) is larger than finalization time // Unbonding time must be strictly larger that babylon finalization time. if uint64(req.UnbondingTime) <= wValue { From 60c1c1f886747f4b09ad9b69f29de93547bf36b2 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 20 Dec 2023 09:57:05 +1100 Subject: [PATCH 134/202] zoneconcierge: verification functionality and fuzz tests for BTC timestamp (#150) --- testutil/datagen/raw_checkpoint.go | 9 + x/btclightclient/keeper/keeper.go | 8 +- x/epoching/keeper/epochs.go | 44 +-- x/epoching/types/keys.go | 23 +- .../keeper/ibc_packet_btc_timestamp.go | 9 +- x/zoneconcierge/keeper/proof_btc_timestamp.go | 128 +++++++ ...ed_test.go => proof_btc_timestamp_test.go} | 118 +++++- .../keeper/proof_cz_header_in_epoch.go | 68 ---- .../keeper/proof_cz_header_in_epoch_test.go | 66 ---- x/zoneconcierge/keeper/proof_epoch_sealed.go | 193 ---------- .../keeper/proof_epoch_submitted.go | 94 ----- .../keeper/proof_epoch_submitted_test.go | 70 ---- .../keeper/proof_finalized_chain_info.go | 52 --- x/zoneconcierge/keeper/query_kvstore.go | 23 -- x/zoneconcierge/types/btc_timestamp.go | 336 ++++++++++++++++++ x/zoneconcierge/types/btc_timestamp_test.go | 165 +++++++++ x/zoneconcierge/types/zoneconcierge.go | 27 ++ 17 files changed, 813 insertions(+), 620 deletions(-) create mode 100644 x/zoneconcierge/keeper/proof_btc_timestamp.go rename x/zoneconcierge/keeper/{proof_epoch_sealed_test.go => proof_btc_timestamp_test.go} (59%) delete mode 100644 x/zoneconcierge/keeper/proof_cz_header_in_epoch.go delete mode 100644 x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go delete mode 100644 x/zoneconcierge/keeper/proof_epoch_sealed.go delete mode 100644 x/zoneconcierge/keeper/proof_epoch_submitted.go delete mode 100644 x/zoneconcierge/keeper/proof_epoch_submitted_test.go delete mode 100644 x/zoneconcierge/keeper/proof_finalized_chain_info.go create mode 100644 x/zoneconcierge/types/btc_timestamp.go create mode 100644 x/zoneconcierge/types/btc_timestamp_test.go diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index b59a0c2cb..a56690d69 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -10,6 +10,15 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/types" ) +func GenFullBitmap() bitmap.Bitmap { + bmBytes := make([]byte, txformat.BitMapLength) + bm := bitmap.Bitmap(bmBytes) + for i := 0; i < bm.Len(); i++ { + bitmap.Set(bm, i, true) + } + return bm +} + // GenRandomBitmap generates a random bitmap for the validator set // It returns a random bitmap and the number of validators in the subset func GenRandomBitmap(r *rand.Rand) (bitmap.Bitmap, int) { diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index 9168b962d..44ef1dee5 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -2,11 +2,13 @@ package keeper import ( "context" - corestoretypes "cosmossdk.io/core/store" "fmt" + corestoretypes "cosmossdk.io/core/store" + "cosmossdk.io/log" bbn "github.com/babylonchain/babylon/types" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/babylonchain/babylon/x/btclightclient/types" @@ -210,3 +212,7 @@ func (k Keeper) GetMainChainReverse(ctx context.Context) []*types.BTCHeaderInfo k.headersState(ctx).IterateReverseHeaders(accHeaderFn) return headers } + +func (k Keeper) GetBTCNet() *chaincfg.Params { + return k.btcConfig.NetParams() +} diff --git a/x/epoching/keeper/epochs.go b/x/epoching/keeper/epochs.go index 4574a30bb..5ebbf15b2 100644 --- a/x/epoching/keeper/epochs.go +++ b/x/epoching/keeper/epochs.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" @@ -11,30 +12,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// setEpochNumber sets epoch number -func (k Keeper) setEpochNumber(ctx context.Context, epochNumber uint64) { - store := k.storeService.OpenKVStore(ctx) - - epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) - err := store.Set(types.EpochNumberKey, epochNumberBytes) - if err != nil { - panic(err) - } -} - -func (k Keeper) getEpochNumber(ctx context.Context) uint64 { - store := k.storeService.OpenKVStore(ctx) - bz, err := store.Get(types.EpochNumberKey) - if err != nil { - panic(err) - } - if bz == nil { - panic(types.ErrUnknownEpochNumber) - } - epochNumber := sdk.BigEndianToUint64(bz) - return epochNumber -} - func (k Keeper) setEpochInfo(ctx context.Context, epochNumber uint64, epoch *types.Epoch) { store := k.epochInfoStore(ctx) epochNumberBytes := sdk.Uint64ToBigEndian(epochNumber) @@ -63,18 +40,18 @@ func (k Keeper) InitEpoch(ctx context.Context) { epochInterval := k.GetParams(ctx).EpochInterval epoch := types.NewEpoch(0, epochInterval, 0, &header.Time) k.setEpochInfo(ctx, 0, &epoch) - - k.setEpochNumber(ctx, 0) } // GetEpoch fetches the current epoch func (k Keeper) GetEpoch(ctx context.Context) *types.Epoch { - epochNumber := k.getEpochNumber(ctx) - epoch, err := k.getEpochInfo(ctx, epochNumber) - if err != nil { - panic(err) - } - return epoch + store := k.epochInfoStore(ctx) + iter := store.ReverseIterator(nil, nil) + defer iter.Close() + epochBytes := iter.Value() + var epoch types.Epoch + k.cdc.MustUnmarshal(epochBytes, &epoch) + + return &epoch } func (k Keeper) GetHistoricalEpoch(ctx context.Context, epochNumber uint64) (*types.Epoch, error) { @@ -111,7 +88,7 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch // get the sealer header epoch := k.GetEpoch(ctx) if !epoch.IsSecondBlock(ctx) { - panic("RecordSealerHeaderForPrevEpoch can only be invoked at the second header of a non-zero epoch") + panic(fmt.Errorf("RecordSealerHeaderForPrevEpoch can only be invoked at the second header of a non-zero epoch. current epoch: %v, current height: %d", epoch, sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)) } header := sdk.UnwrapSDKContext(ctx).HeaderInfo() @@ -134,7 +111,6 @@ func (k Keeper) IncEpoch(ctx context.Context) types.Epoch { sdkCtx := sdk.UnwrapSDKContext(ctx) epochNumber := k.GetEpoch(ctx).EpochNumber incrementedEpochNumber := epochNumber + 1 - k.setEpochNumber(ctx, incrementedEpochNumber) epochInterval := k.GetParams(ctx).EpochInterval newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(sdkCtx.HeaderInfo().Height), nil) diff --git a/x/epoching/types/keys.go b/x/epoching/types/keys.go index 417a3b433..5ec668b83 100644 --- a/x/epoching/types/keys.go +++ b/x/epoching/types/keys.go @@ -18,18 +18,17 @@ const ( ) var ( - EpochNumberKey = []byte{0x11} // key prefix for the epoch number - EpochInfoKey = []byte{0x12} // key prefix for the epoch info - QueueLengthKey = []byte{0x13} // key prefix for the queue length - MsgQueueKey = []byte{0x14} // key prefix for the message queue of an epoch - ValidatorSetKey = []byte{0x15} // key prefix for the validator set in a single epoch - VotingPowerKey = []byte{0x16} // key prefix for the total voting power of a validator set in a single epoch - SlashedVotingPowerKey = []byte{0x17} // key prefix for the total slashed voting power in a single epoch - SlashedValidatorSetKey = []byte{0x18} // key prefix for slashed validator set - ValidatorLifecycleKey = []byte{0x19} // key prefix for validator life cycle - DelegationLifecycleKey = []byte{0x20} // key prefix for delegation life cycle - AppHashKey = []byte{0x21} // key prefix for the app hash - ParamsKey = []byte{0x22} // key prefix for the parameters + EpochInfoKey = []byte{0x11} // key prefix for the epoch info + QueueLengthKey = []byte{0x12} // key prefix for the queue length + MsgQueueKey = []byte{0x13} // key prefix for the message queue of an epoch + ValidatorSetKey = []byte{0x14} // key prefix for the validator set in a single epoch + VotingPowerKey = []byte{0x15} // key prefix for the total voting power of a validator set in a single epoch + SlashedVotingPowerKey = []byte{0x16} // key prefix for the total slashed voting power in a single epoch + SlashedValidatorSetKey = []byte{0x17} // key prefix for slashed validator set + ValidatorLifecycleKey = []byte{0x18} // key prefix for validator life cycle + DelegationLifecycleKey = []byte{0x19} // key prefix for delegation life cycle + AppHashKey = []byte{0x20} // key prefix for the app hash + ParamsKey = []byte{0x21} // key prefix for the parameters ) func KeyPrefix(p string) []byte { diff --git a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go index 494810b49..89380453b 100644 --- a/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go +++ b/x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go @@ -95,7 +95,12 @@ func (k Keeper) getFinalizedInfo( // createBTCTimestamp creates a BTC timestamp from finalizedInfo for a given IBC channel // where the counterparty is a Cosmos zone -func (k Keeper) createBTCTimestamp(ctx context.Context, chainID string, channel channeltypes.IdentifiedChannel, finalizedInfo *finalizedInfo) (*types.BTCTimestamp, error) { +func (k Keeper) createBTCTimestamp( + ctx context.Context, + chainID string, + channel channeltypes.IdentifiedChannel, + finalizedInfo *finalizedInfo, +) (*types.BTCTimestamp, error) { // if the Babylon contract in this channel has not been initialised, get headers from // the tip to (w+1+len(finalizedInfo.BTCHeaders))-deep header var btcHeaders []*btclctypes.BTCHeaderInfo @@ -236,5 +241,3 @@ func (k Keeper) BroadcastBTCTimestamps( } } } - -// TODO: test case with at BTC headers and checkpoints diff --git a/x/zoneconcierge/keeper/proof_btc_timestamp.go b/x/zoneconcierge/keeper/proof_btc_timestamp.go new file mode 100644 index 000000000..54cd30de2 --- /dev/null +++ b/x/zoneconcierge/keeper/proof_btc_timestamp.go @@ -0,0 +1,128 @@ +package keeper + +import ( + "context" + "fmt" + + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + "github.com/babylonchain/babylon/x/zoneconcierge/types" + tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" +) + +func (k Keeper) ProveCZHeaderInEpoch(_ context.Context, header *types.IndexedHeader, epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { + czHeaderKey := types.GetCZHeaderKey(header.ChainId, header.Height) + _, _, proof, err := k.QueryStore(types.StoreKey, czHeaderKey, int64(epoch.GetSealerBlockHeight())) + if err != nil { + return nil, err + } + + return proof, nil +} + +func (k Keeper) ProveEpochInfo(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { + epochInfoKey := types.GetEpochInfoKey(epoch.EpochNumber) + _, _, proof, err := k.QueryStore(epochingtypes.StoreKey, epochInfoKey, int64(epoch.GetSealerBlockHeight())) + if err != nil { + return nil, err + } + + return proof, nil +} + +func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { + valSetKey := types.GetValSetKey(epoch.EpochNumber) + _, _, proof, err := k.QueryStore(checkpointingtypes.StoreKey, valSetKey, int64(epoch.GetSealerBlockHeight())) + if err != nil { + return nil, err + } + return proof, nil +} + +// ProveEpochSealed proves an epoch has been sealed, i.e., +// - the epoch's validator set has a valid multisig over the sealer header +// - the epoch's validator set is committed to the sealer header's app_hash +// - the epoch's metadata is committed to the sealer header's app_hash +func (k Keeper) ProveEpochSealed(ctx context.Context, epochNumber uint64) (*types.ProofEpochSealed, error) { + var ( + proof = &types.ProofEpochSealed{} + err error + ) + + // get the validator set of the sealed epoch + proof.ValidatorSet, err = k.checkpointingKeeper.GetBLSPubKeySet(ctx, epochNumber) + if err != nil { + return nil, err + } + + // get sealer header and the query height + epoch, err := k.epochingKeeper.GetHistoricalEpoch(ctx, epochNumber) + if err != nil { + return nil, err + } + + // proof of inclusion for epoch metadata in sealer header + proof.ProofEpochInfo, err = k.ProveEpochInfo(epoch) + if err != nil { + return nil, err + } + + // proof of inclusion for validator set in sealer header + proof.ProofEpochValSet, err = k.ProveValSet(epoch) + if err != nil { + return nil, err + } + + return proof, nil +} + +// ProveEpochSubmitted generates proof that the epoch's checkpoint is submitted to BTC +// i.e., the two `TransactionInfo`s for the checkpoint +func (k Keeper) ProveEpochSubmitted(ctx context.Context, sk *btcctypes.SubmissionKey) ([]*btcctypes.TransactionInfo, error) { + bestSubmissionData := k.btccKeeper.GetSubmissionData(ctx, *sk) + if bestSubmissionData == nil { + return nil, fmt.Errorf("the best submission key for epoch %d has no submission data", bestSubmissionData.Epoch) + } + return bestSubmissionData.TxsInfo, nil +} + +// proveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo +// It includes proofTxInBlock, proofHeaderInEpoch, proofEpochSealed and proofEpochSubmitted +// The proofs can be verified by a verifier with access to a BTC and Babylon light client +// CONTRACT: this is only a private helper function for simplifying the implementation of RPC calls +func (k Keeper) proveFinalizedChainInfo( + ctx context.Context, + chainInfo *types.ChainInfo, + epochInfo *epochingtypes.Epoch, + bestSubmissionKey *btcctypes.SubmissionKey, +) (*types.ProofFinalizedChainInfo, error) { + var ( + err error + proof = &types.ProofFinalizedChainInfo{} + ) + + // Proof that the CZ header is timestamped in epoch + proof.ProofCzHeaderInEpoch, err = k.ProveCZHeaderInEpoch(ctx, chainInfo.LatestHeader, epochInfo) + if err != nil { + return nil, err + } + + // proof that the epoch is sealed + proof.ProofEpochSealed, err = k.ProveEpochSealed(ctx, epochInfo.EpochNumber) + if err != nil { + return nil, err + } + + // proof that the epoch's checkpoint is submitted to BTC + // i.e., the two `TransactionInfo`s for the checkpoint + proof.ProofEpochSubmitted, err = k.ProveEpochSubmitted(ctx, bestSubmissionKey) + if err != nil { + // The only error in ProveEpochSubmitted is the nil bestSubmission. + // Since the epoch w.r.t. the bestSubmissionKey is finalised, this + // can only be a programming error, so we should panic here. + panic(err) + } + + return proof, nil +} diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go b/x/zoneconcierge/keeper/proof_btc_timestamp_test.go similarity index 59% rename from x/zoneconcierge/keeper/proof_epoch_sealed_test.go rename to x/zoneconcierge/keeper/proof_btc_timestamp_test.go index a1d619d0b..bd200dc91 100644 --- a/x/zoneconcierge/keeper/proof_epoch_sealed_test.go +++ b/x/zoneconcierge/keeper/proof_btc_timestamp_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "encoding/hex" "math/rand" "testing" @@ -8,14 +9,68 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" testkeeper "github.com/babylonchain/babylon/testutil/keeper" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" "github.com/boljen/go-bitmap" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) +func FuzzProofCZHeaderInEpoch(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + h := testhelper.NewHelper(t) + ek := h.App.EpochingKeeper + zck := h.App.ZoneConciergeKeeper + var err error + + // chain is at height 1 thus epoch 1 + + // enter the 1st block of epoch 2 + epochInterval := ek.GetParams(h.Ctx).EpochInterval + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + + // handle a random header from a random consumer chain + chainID := datagen.GenRandomHexStr(r, 10) + height := datagen.RandomInt(r, 100) + 1 + ibctmHeader := datagen.GenRandomIBCTMHeader(r, chainID, height) + headerInfo := datagen.HeaderToHeaderInfo(ibctmHeader) + zck.HandleHeaderWithValidCommit(h.Ctx, datagen.GenRandomByteArray(r, 32), headerInfo, false) + + // ensure the header is successfully inserted + indexedHeader, err := zck.GetHeader(h.Ctx, chainID, height) + h.NoError(err) + + // enter the 1st block of the next epoch + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + // seal last epoch + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + + epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) + h.NoError(err) + + // generate inclusion proof + proof, err := zck.ProveCZHeaderInEpoch(h.Ctx, indexedHeader, epochWithHeader) + h.NoError(err) + + // verify the inclusion proof + err = zctypes.VerifyCZHeaderInEpoch(indexedHeader, epochWithHeader, proof) + h.NoError(err) + }) +} + func signBLSWithBitmap(blsSKs []bls12381.PrivateKey, bm bitmap.Bitmap, msg []byte) (bls12381.Signature, error) { sigs := []bls12381.Signature{} for i := 0; i < len(blsSKs); i++ { @@ -89,7 +144,7 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { proof, err := zcKeeper.ProveEpochSealed(ctx, epoch.EpochNumber) require.NoError(t, err) // verify - err = zckeeper.VerifyEpochSealed(epoch, rawCkpt, proof) + err = zctypes.VerifyEpochSealed(epoch, rawCkpt, proof) if subsetPower <= valSet.GetTotalPower()*1/3 { // BLS sig does not reach a quorum require.LessOrEqual(t, numSubSet, numVals*1/3) @@ -138,7 +193,7 @@ func FuzzProofEpochSealed_Epoch(f *testing.F) { h.NoError(err) // verify inclusion proof - err = zckeeper.VerifyEpochInfo(lastEpoch, proof) + err = zctypes.VerifyEpochInfo(lastEpoch, proof) h.NoError(err) }) } @@ -180,7 +235,62 @@ func FuzzProofEpochSealed_ValSet(f *testing.F) { h.NoError(err) // verify inclusion proof - err = zckeeper.VerifyValSet(lastEpoch, lastEpochValSet, proof) + err = zctypes.VerifyValSet(lastEpoch, lastEpochValSet, proof) h.NoError(err) }) } + +func FuzzProofEpochSubmitted(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + + // generate random epoch, random rawBtcCkpt and random rawCkpt + epoch := datagen.GenRandomEpoch(r) + rawBtcCkpt := datagen.GetRandomRawBtcCheckpoint(r) + rawBtcCkpt.Epoch = epoch.EpochNumber + rawCkpt, err := checkpointingtypes.FromBTCCkptToRawCkpt(rawBtcCkpt) + require.NoError(t, err) + + // encode ckpt to BTC txs in BTC blocks + testRawCkptData := datagen.EncodeRawCkptToTestData(rawBtcCkpt) + idxs := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} + offsets := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} + btcBlocks := []*datagen.BlockCreationResult{ + datagen.CreateBlock(r, 1, uint32(idxs[0]+offsets[0]), uint32(idxs[0]), testRawCkptData.FirstPart), + datagen.CreateBlock(r, 2, uint32(idxs[1]+offsets[1]), uint32(idxs[1]), testRawCkptData.SecondPart), + } + // create MsgInsertBtcSpvProof for the rawCkpt + msgInsertBtcSpvProof := datagen.GenerateMessageWithRandomSubmitter([]*datagen.BlockCreationResult{btcBlocks[0], btcBlocks[1]}) + + // get headers for verification + btcHeaders := []*wire.BlockHeader{ + btcBlocks[0].HeaderBytes.ToBlockHeader(), + btcBlocks[1].HeaderBytes.ToBlockHeader(), + } + + // get 2 tx info for the ckpt parts + txsInfo := []*btcctypes.TransactionInfo{ + { + Key: &btcctypes.TransactionKey{Index: uint32(idxs[0]), Hash: btcBlocks[0].HeaderBytes.Hash()}, + Transaction: msgInsertBtcSpvProof.Proofs[0].BtcTransaction, + Proof: msgInsertBtcSpvProof.Proofs[0].MerkleNodes, + }, + { + Key: &btcctypes.TransactionKey{Index: uint32(idxs[1]), Hash: btcBlocks[1].HeaderBytes.Hash()}, + Transaction: msgInsertBtcSpvProof.Proofs[1].BtcTransaction, + Proof: msgInsertBtcSpvProof.Proofs[1].MerkleNodes, + }, + } + + // net param, babylonTag + powLimit := chaincfg.SimNetParams.PowLimit + babylonTag := btcctypes.DefaultCheckpointTag + tagAsBytes, _ := hex.DecodeString(babylonTag) + + // verify + err = zctypes.VerifyEpochSubmitted(rawCkpt, txsInfo, btcHeaders, powLimit, tagAsBytes) + require.NoError(t, err) + }) +} diff --git a/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go b/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go deleted file mode 100644 index 1216d3ae9..000000000 --- a/x/zoneconcierge/keeper/proof_cz_header_in_epoch.go +++ /dev/null @@ -1,68 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - errorsmod "cosmossdk.io/errors" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/babylonchain/babylon/x/zoneconcierge/types" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// TODO: fuzz tests for verifying proofs generated from KVStore - -func getCZHeaderKey(chainID string, height uint64) []byte { - key := types.CanonicalChainKey - key = append(key, []byte(chainID)...) - key = append(key, sdk.Uint64ToBigEndian(height)...) - return key -} - -func (k Keeper) ProveCZHeaderInEpoch(_ context.Context, header *types.IndexedHeader, epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { - czHeaderKey := getCZHeaderKey(header.ChainId, header.Height) - _, _, proof, err := k.QueryStore(types.StoreKey, czHeaderKey, int64(epoch.GetSealerBlockHeight())) - if err != nil { - return nil, err - } - - return proof, nil -} - -func VerifyCZHeaderInEpoch(header *types.IndexedHeader, epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { - // nil check - if header == nil { - return fmt.Errorf("header is nil") - } else if epoch == nil { - return fmt.Errorf("epoch is nil") - } else if proof == nil { - return fmt.Errorf("proof is nil") - } - - // sanity check - if err := header.ValidateBasic(); err != nil { - return err - } else if err := epoch.ValidateBasic(); err != nil { - return err - } - - // ensure epoch number is same in epoch and CZ header - if epoch.EpochNumber != header.BabylonEpoch { - return fmt.Errorf("epoch.EpochNumber (%d) is not equal to header.BabylonEpoch (%d)", epoch.EpochNumber, header.BabylonEpoch) - } - - // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeaderHash - - // Ensure The header is committed to the AppHash of the sealer header - headerBytes, err := header.Marshal() - if err != nil { - return err - } - if err := VerifyStore(root, types.StoreKey, getCZHeaderKey(header.ChainId, header.Height), headerBytes, proof); err != nil { - return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for CZ header: %v", err) - } - - return nil -} diff --git a/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go b/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go deleted file mode 100644 index 4d9f8aed1..000000000 --- a/x/zoneconcierge/keeper/proof_cz_header_in_epoch_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package keeper_test - -import ( - "math/rand" - "testing" - - "github.com/babylonchain/babylon/testutil/datagen" - testhelper "github.com/babylonchain/babylon/testutil/helper" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" -) - -func FuzzProofCZHeaderInEpoch(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - h := testhelper.NewHelper(t) - ek := h.App.EpochingKeeper - zck := h.App.ZoneConciergeKeeper - var err error - - // chain is at height 1 thus epoch 1 - - // enter the 1st block of epoch 2 - epochInterval := ek.GetParams(h.Ctx).EpochInterval - for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) - h.NoError(err) - } - - // handle a random header from a random consumer chain - chainID := datagen.GenRandomHexStr(r, 10) - height := datagen.RandomInt(r, 100) + 1 - ibctmHeader := datagen.GenRandomIBCTMHeader(r, chainID, height) - headerInfo := datagen.HeaderToHeaderInfo(ibctmHeader) - zck.HandleHeaderWithValidCommit(h.Ctx, datagen.GenRandomByteArray(r, 32), headerInfo, false) - - // ensure the header is successfully inserted - indexedHeader, err := zck.GetHeader(h.Ctx, chainID, height) - h.NoError(err) - - // enter the 1st block of the next epoch - for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) - h.NoError(err) - } - // sealer header hash is the AppHash of the 1st header in CometBFT - //, i.e., 2nd header captured at BeginBlock() of 2nd header, of an epoch - sealerHeaderHash := h.Ctx.HeaderInfo().AppHash - // seal last epoch - h.Ctx, err = h.GenAndApplyEmptyBlock(r) - h.NoError(err) - - epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) - h.NoError(err) - epochWithHeader.SealerHeaderHash = sealerHeaderHash - - // generate inclusion proof - proof, err := zck.ProveCZHeaderInEpoch(h.Ctx, indexedHeader, epochWithHeader) - h.NoError(err) - - // verify the inclusion proof - err = zckeeper.VerifyCZHeaderInEpoch(indexedHeader, epochWithHeader, proof) - h.NoError(err) - }) -} diff --git a/x/zoneconcierge/keeper/proof_epoch_sealed.go b/x/zoneconcierge/keeper/proof_epoch_sealed.go deleted file mode 100644 index 7f4984003..000000000 --- a/x/zoneconcierge/keeper/proof_epoch_sealed.go +++ /dev/null @@ -1,193 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - errorsmod "cosmossdk.io/errors" - "github.com/babylonchain/babylon/crypto/bls12381" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/babylonchain/babylon/x/zoneconcierge/types" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func getEpochInfoKey(epochNumber uint64) []byte { - epochInfoKey := epochingtypes.EpochInfoKey - epochInfoKey = append(epochInfoKey, sdk.Uint64ToBigEndian(epochNumber)...) - return epochInfoKey -} - -func (k Keeper) ProveEpochInfo(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { - epochInfoKey := getEpochInfoKey(epoch.EpochNumber) - _, _, proof, err := k.QueryStore(epochingtypes.StoreKey, epochInfoKey, int64(epoch.GetSealerBlockHeight())) - if err != nil { - return nil, err - } - - return proof, nil -} - -func VerifyEpochInfo(epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { - // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeaderHash - - // Ensure The epoch medatata is committed to the app_hash of the sealer header - // NOTE: the proof is generated when sealer header is generated. At that time - // sealer header hash is not given to epoch metadata. Thus we need to clear the - // sealer header hash when verifying the proof. - epochNoSealerHeader := *epoch - epochNoSealerHeader.SealerHeaderHash = []byte{} - epochBytes, err := epochNoSealerHeader.Marshal() - if err != nil { - return err - } - if err := VerifyStore(root, epochingtypes.StoreKey, getEpochInfoKey(epoch.EpochNumber), epochBytes, proof); err != nil { - return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for epoch metadata: %v", err) - } - - return nil -} - -func getValSetKey(epochNumber uint64) []byte { - valSetKey := checkpointingtypes.ValidatorBlsKeySetPrefix - valSetKey = append(valSetKey, sdk.Uint64ToBigEndian(epochNumber)...) - return valSetKey -} - -func (k Keeper) ProveValSet(epoch *epochingtypes.Epoch) (*tmcrypto.ProofOps, error) { - valSetKey := getValSetKey(epoch.EpochNumber) - _, _, proof, err := k.QueryStore(checkpointingtypes.StoreKey, valSetKey, int64(epoch.GetSealerBlockHeight())) - if err != nil { - return nil, err - } - return proof, nil -} - -func VerifyValSet(epoch *epochingtypes.Epoch, valSet *checkpointingtypes.ValidatorWithBlsKeySet, proof *tmcrypto.ProofOps) error { - valSetBytes, err := valSet.Marshal() - if err != nil { - return err - } - if err := VerifyStore(epoch.SealerHeaderHash, checkpointingtypes.StoreKey, getValSetKey(epoch.EpochNumber), valSetBytes, proof); err != nil { - return errorsmod.Wrapf(types.ErrInvalidMerkleProof, "invalid inclusion proof for validator set: %v", err) - } - - return nil -} - -// ProveEpochSealed proves an epoch has been sealed, i.e., -// - the epoch's validator set has a valid multisig over the sealer header -// - the epoch's validator set is committed to the sealer header's app_hash -// - the epoch's metadata is committed to the sealer header's app_hash -func (k Keeper) ProveEpochSealed(ctx context.Context, epochNumber uint64) (*types.ProofEpochSealed, error) { - var ( - proof = &types.ProofEpochSealed{} - err error - ) - - // get the validator set of the sealed epoch - proof.ValidatorSet, err = k.checkpointingKeeper.GetBLSPubKeySet(ctx, epochNumber) - if err != nil { - return nil, err - } - - // get sealer header and the query height - epoch, err := k.epochingKeeper.GetHistoricalEpoch(ctx, epochNumber) - if err != nil { - return nil, err - } - - // proof of inclusion for epoch metadata in sealer header - proof.ProofEpochInfo, err = k.ProveEpochInfo(epoch) - if err != nil { - return nil, err - } - - // proof of inclusion for validator set in sealer header - proof.ProofEpochValSet, err = k.ProveValSet(epoch) - if err != nil { - return nil, err - } - - return proof, nil -} - -// VerifyEpochSealed verifies that the given `epoch` is sealed by the `rawCkpt` by using the given `proof` -// The verification rules include: -// - basic sanity checks -// - The raw checkpoint's app_hash is same as in the header of the sealer epoch -// - More than 1/3 (in voting power) validators in the validator set of this epoch have signed app_hash of the sealer epoch -// - The epoch medatata is committed to the app_hash of the sealer epoch -// - The validator set is committed to the app_hash of the sealer epoch -func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.RawCheckpoint, proof *types.ProofEpochSealed) error { - // nil check - if epoch == nil { - return fmt.Errorf("epoch is nil") - } else if rawCkpt == nil { - return fmt.Errorf("rawCkpt is nil") - } else if proof == nil { - return fmt.Errorf("proof is nil") - } - - // sanity check - if err := epoch.ValidateBasic(); err != nil { - return err - } else if err := rawCkpt.ValidateBasic(); err != nil { - return err - } else if err = proof.ValidateBasic(); err != nil { - return err - } - - // ensure epoch number is same in epoch and rawCkpt - if epoch.EpochNumber != rawCkpt.EpochNum { - return fmt.Errorf("epoch.EpochNumber (%d) is not equal to rawCkpt.EpochNum (%d)", epoch.EpochNumber, rawCkpt.EpochNum) - } - - // ensure the raw checkpoint's app_hash is same as in the header of the sealer header - // NOTE: since this proof is assembled by a Babylon node who has verified the checkpoint, - // the two appHash values should always be the same, otherwise this Babylon node is malicious. - // This is different from the checkpoint verification rules in checkpointing, - // where a checkpoint with valid BLS multisig but different appHash signals a dishonest majority equivocation. - appHashInCkpt := rawCkpt.AppHash - appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeaderHash) - if !appHashInCkpt.Equal(appHashInSealerHeader) { - return fmt.Errorf("AppHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", appHashInCkpt.String(), appHashInSealerHeader.String()) - } - - /* - Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || app_hash) in the raw checkpoint - */ - valSet := &checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} - // filter validator set that contributes to the signature - signerSet, signerSetPower, err := valSet.FindSubsetWithPowerSum(rawCkpt.Bitmap) - if err != nil { - return err - } - // ensure the signerSet has > 1/3 voting power - if signerSetPower <= valSet.GetTotalPower()*1/3 { - return fmt.Errorf("the BLS signature involves insufficient voting power") - } - // verify BLS multisig - signedMsgBytes := rawCkpt.SignedMsg() - ok, err := bls12381.VerifyMultiSig(*rawCkpt.BlsMultiSig, signerSet.GetBLSKeySet(), signedMsgBytes) - if err != nil { - return err - } - if !ok { - return fmt.Errorf("BLS signature does not match the public key") - } - - // Ensure The epoch medatata is committed to the app_hash of the sealer header - if err := VerifyEpochInfo(epoch, proof.ProofEpochInfo); err != nil { - return err - } - - // Ensure The validator set is committed to the app_hash of the sealer header - if err := VerifyValSet(epoch, valSet, proof.ProofEpochValSet); err != nil { - return err - } - - return nil -} diff --git a/x/zoneconcierge/keeper/proof_epoch_submitted.go b/x/zoneconcierge/keeper/proof_epoch_submitted.go deleted file mode 100644 index 9c421a9e9..000000000 --- a/x/zoneconcierge/keeper/proof_epoch_submitted.go +++ /dev/null @@ -1,94 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - "math/big" - - txformat "github.com/babylonchain/babylon/btctxformatter" - "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/btcsuite/btcd/wire" -) - -// ProveEpochSubmitted generates proof that the epoch's checkpoint is submitted to BTC -// i.e., the two `TransactionInfo`s for the checkpoint -func (k Keeper) ProveEpochSubmitted(ctx context.Context, sk *btcctypes.SubmissionKey) ([]*btcctypes.TransactionInfo, error) { - bestSubmissionData := k.btccKeeper.GetSubmissionData(ctx, *sk) - if bestSubmissionData == nil { - return nil, fmt.Errorf("the best submission key for epoch %d has no submission data", bestSubmissionData.Epoch) - } - return bestSubmissionData.TxsInfo, nil -} - -// VerifyEpochSubmitted verifies whether an epoch's checkpoint has been included in BTC or not -// verifications include: -// - basic sanity checks -// - Merkle proofs in txsInfo are valid -// - the raw ckpt decoded from txsInfo is same as the expected rawCkpt -func VerifyEpochSubmitted(rawCkpt *checkpointingtypes.RawCheckpoint, txsInfo []*btcctypes.TransactionInfo, btcHeaders []*wire.BlockHeader, powLimit *big.Int, babylonTag txformat.BabylonTag) error { - // basic sanity check - if rawCkpt == nil { - return fmt.Errorf("rawCkpt is nil") - } else if len(txsInfo) != txformat.NumberOfParts { - return fmt.Errorf("txsInfo contains %d parts rather than %d", len(txsInfo), txformat.NumberOfParts) - } else if len(btcHeaders) != txformat.NumberOfParts { - return fmt.Errorf("btcHeaders contains %d parts rather than %d", len(btcHeaders), txformat.NumberOfParts) - } - - // sanity check of each tx info - for _, txInfo := range txsInfo { - if err := txInfo.ValidateBasic(); err != nil { - return err - } - } - - // verify Merkle proofs for each tx info - parsedProofs := []*btcctypes.ParsedProof{} - for i, txInfo := range txsInfo { - btcHeaderBytes := types.NewBTCHeaderBytesFromBlockHeader(btcHeaders[i]) - parsedProof, err := btcctypes.ParseProof( - txInfo.Transaction, - txInfo.Key.Index, - txInfo.Proof, - &btcHeaderBytes, - powLimit, - ) - if err != nil { - return err - } - parsedProofs = append(parsedProofs, parsedProof) - } - - // decode parsedProof to checkpoint data - checkpointData := [][]byte{} - for i, proof := range parsedProofs { - data, err := txformat.GetCheckpointData( - babylonTag, - txformat.CurrentVersion, - uint8(i), - proof.OpReturnData, - ) - - if err != nil { - return err - } - checkpointData = append(checkpointData, data) - } - rawCkptData, err := txformat.ConnectParts(txformat.CurrentVersion, checkpointData[0], checkpointData[1]) - if err != nil { - return err - } - decodedRawCkpt, err := checkpointingtypes.FromBTCCkptBytesToRawCkpt(rawCkptData) - if err != nil { - return err - } - - // check if decodedRawCkpt is same as the expected rawCkpt - if !decodedRawCkpt.Equal(rawCkpt) { - return fmt.Errorf("the decoded rawCkpt (%v) is different from the expected rawCkpt (%v)", decodedRawCkpt, rawCkpt) - } - - return nil -} diff --git a/x/zoneconcierge/keeper/proof_epoch_submitted_test.go b/x/zoneconcierge/keeper/proof_epoch_submitted_test.go deleted file mode 100644 index 89766045b..000000000 --- a/x/zoneconcierge/keeper/proof_epoch_submitted_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package keeper_test - -import ( - "encoding/hex" - "math/rand" - "testing" - - "github.com/babylonchain/babylon/testutil/datagen" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" - "github.com/stretchr/testify/require" -) - -func FuzzProofEpochSubmitted(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 10) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - - // generate random epoch, random rawBtcCkpt and random rawCkpt - epoch := datagen.GenRandomEpoch(r) - rawBtcCkpt := datagen.GetRandomRawBtcCheckpoint(r) - rawBtcCkpt.Epoch = epoch.EpochNumber - rawCkpt, err := checkpointingtypes.FromBTCCkptToRawCkpt(rawBtcCkpt) - require.NoError(t, err) - - // encode ckpt to BTC txs in BTC blocks - testRawCkptData := datagen.EncodeRawCkptToTestData(rawBtcCkpt) - idxs := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} - offsets := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} - btcBlocks := []*datagen.BlockCreationResult{ - datagen.CreateBlock(r, 1, uint32(idxs[0]+offsets[0]), uint32(idxs[0]), testRawCkptData.FirstPart), - datagen.CreateBlock(r, 2, uint32(idxs[1]+offsets[1]), uint32(idxs[1]), testRawCkptData.SecondPart), - } - // create MsgInsertBtcSpvProof for the rawCkpt - msgInsertBtcSpvProof := datagen.GenerateMessageWithRandomSubmitter([]*datagen.BlockCreationResult{btcBlocks[0], btcBlocks[1]}) - - // get headers for verification - btcHeaders := []*wire.BlockHeader{ - btcBlocks[0].HeaderBytes.ToBlockHeader(), - btcBlocks[1].HeaderBytes.ToBlockHeader(), - } - - // get 2 tx info for the ckpt parts - txsInfo := []*btcctypes.TransactionInfo{ - { - Key: &btcctypes.TransactionKey{Index: uint32(idxs[0]), Hash: btcBlocks[0].HeaderBytes.Hash()}, - Transaction: msgInsertBtcSpvProof.Proofs[0].BtcTransaction, - Proof: msgInsertBtcSpvProof.Proofs[0].MerkleNodes, - }, - { - Key: &btcctypes.TransactionKey{Index: uint32(idxs[1]), Hash: btcBlocks[1].HeaderBytes.Hash()}, - Transaction: msgInsertBtcSpvProof.Proofs[1].BtcTransaction, - Proof: msgInsertBtcSpvProof.Proofs[1].MerkleNodes, - }, - } - - // net param, babylonTag - powLimit := chaincfg.SimNetParams.PowLimit - babylonTag := btcctypes.DefaultCheckpointTag - tagAsBytes, _ := hex.DecodeString(babylonTag) - - // verify - err = zckeeper.VerifyEpochSubmitted(rawCkpt, txsInfo, btcHeaders, powLimit, tagAsBytes) - require.NoError(t, err) - }) -} diff --git a/x/zoneconcierge/keeper/proof_finalized_chain_info.go b/x/zoneconcierge/keeper/proof_finalized_chain_info.go deleted file mode 100644 index 6c2617c99..000000000 --- a/x/zoneconcierge/keeper/proof_finalized_chain_info.go +++ /dev/null @@ -1,52 +0,0 @@ -package keeper - -import ( - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/babylonchain/babylon/x/zoneconcierge/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// proveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo -// It includes proofTxInBlock, proofHeaderInEpoch, proofEpochSealed and proofEpochSubmitted -// The proofs can be verified by a verifier with access to a BTC and Babylon light client -// CONTRACT: this is only a private helper function for simplifying the implementation of RPC calls -func (k Keeper) proveFinalizedChainInfo( - ctx sdk.Context, - chainInfo *types.ChainInfo, - epochInfo *epochingtypes.Epoch, - bestSubmissionKey *btcctypes.SubmissionKey, -) (*types.ProofFinalizedChainInfo, error) { - var ( - err error - proof = &types.ProofFinalizedChainInfo{} - ) - - // Proof that the CZ header is timestamped in epoch - proof.ProofCzHeaderInEpoch, err = k.ProveCZHeaderInEpoch(ctx, chainInfo.LatestHeader, epochInfo) - if err != nil { - return nil, err - } - - // proof that the epoch is sealed - proof.ProofEpochSealed, err = k.ProveEpochSealed(ctx, epochInfo.EpochNumber) - if err != nil { - return nil, err - } - - // proof that the epoch's checkpoint is submitted to BTC - // i.e., the two `TransactionInfo`s for the checkpoint - proof.ProofEpochSubmitted, err = k.ProveEpochSubmitted(ctx, bestSubmissionKey) - if err != nil { - // The only error in ProveEpochSubmitted is the nil bestSubmission. - // Since the epoch w.r.t. the bestSubmissionKey is finalised, this - // can only be a programming error, so we should panic here. - panic(err) - } - - return proof, nil -} - -// TODO: implement a standalone verifier VerifyFinalizedChainInfo that -// verifies whether a chainInfo is finalised or not, with access to -// Bitcoin and Babylon light clients diff --git a/x/zoneconcierge/keeper/query_kvstore.go b/x/zoneconcierge/keeper/query_kvstore.go index 5a571686b..ad86b2998 100644 --- a/x/zoneconcierge/keeper/query_kvstore.go +++ b/x/zoneconcierge/keeper/query_kvstore.go @@ -5,8 +5,6 @@ import ( storetypes "cosmossdk.io/store/types" - "cosmossdk.io/store/rootmulti" - "github.com/cometbft/cometbft/crypto/merkle" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" ) @@ -41,24 +39,3 @@ func (k Keeper) QueryStore(moduleStoreKey string, key []byte, queryHeight int64) return resp.Key, resp.Value, resp.ProofOps, nil } - -// VerifyStore verifies whether a KV pair is committed to the Merkle root, with the assistance of a Merkle proof -// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.46.6/store/rootmulti/proof_test.go) -func VerifyStore(root []byte, moduleStoreKey string, key []byte, value []byte, proof *tmcrypto.ProofOps) error { - prt := rootmulti.DefaultProofRuntime() - - keypath := merkle.KeyPath{} - keypath = keypath.AppendKey([]byte(moduleStoreKey), merkle.KeyEncodingURL) - keypath = keypath.AppendKey(key, merkle.KeyEncodingURL) - keypathStr := keypath.String() - - // NOTE: the proof can specify verification rules, either only verifying the - // top Merkle root w.r.t. all KV pairs, or verifying every layer of Merkle root - // TODO: investigate how the verification rules are chosen when generating the - // proof - if err := prt.VerifyValue(proof, root, keypathStr, value); err != nil { - return prt.VerifyAbsence(proof, root, keypathStr) - } - - return nil -} diff --git a/x/zoneconcierge/types/btc_timestamp.go b/x/zoneconcierge/types/btc_timestamp.go new file mode 100644 index 000000000..8cfe3a14b --- /dev/null +++ b/x/zoneconcierge/types/btc_timestamp.go @@ -0,0 +1,336 @@ +package types + +import ( + "context" + "fmt" + "math/big" + + errorsmod "cosmossdk.io/errors" + txformat "github.com/babylonchain/babylon/btctxformatter" + "github.com/babylonchain/babylon/crypto/bls12381" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclckeeper "github.com/babylonchain/babylon/x/btclightclient/keeper" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + "github.com/btcsuite/btcd/wire" + tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func GetCZHeaderKey(chainID string, height uint64) []byte { + key := CanonicalChainKey + key = append(key, []byte(chainID)...) + key = append(key, sdk.Uint64ToBigEndian(height)...) + return key +} + +func GetEpochInfoKey(epochNumber uint64) []byte { + epochInfoKey := epochingtypes.EpochInfoKey + epochInfoKey = append(epochInfoKey, sdk.Uint64ToBigEndian(epochNumber)...) + return epochInfoKey +} + +func GetValSetKey(epochNumber uint64) []byte { + valSetKey := checkpointingtypes.ValidatorBlsKeySetPrefix + valSetKey = append(valSetKey, sdk.Uint64ToBigEndian(epochNumber)...) + return valSetKey +} + +func VerifyEpochInfo(epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { + // get the Merkle root, i.e., the AppHash of the sealer header + root := epoch.SealerHeaderHash + + // Ensure The epoch medatata is committed to the app_hash of the sealer header + // NOTE: the proof is generated when sealer header is generated. At that time + // sealer header hash is not given to epoch metadata. Thus we need to clear the + // sealer header hash when verifying the proof. + epoch.SealerHeaderHash = []byte{} + epochBytes, err := epoch.Marshal() + if err != nil { + return err + } + epoch.SealerHeaderHash = root + if err := VerifyStore(root, epochingtypes.StoreKey, GetEpochInfoKey(epoch.EpochNumber), epochBytes, proof); err != nil { + return errorsmod.Wrapf(ErrInvalidMerkleProof, "invalid inclusion proof for epoch metadata: %v", err) + } + + return nil +} + +func VerifyValSet(epoch *epochingtypes.Epoch, valSet *checkpointingtypes.ValidatorWithBlsKeySet, proof *tmcrypto.ProofOps) error { + valSetBytes, err := valSet.Marshal() + if err != nil { + return err + } + if err := VerifyStore(epoch.SealerHeaderHash, checkpointingtypes.StoreKey, GetValSetKey(epoch.EpochNumber), valSetBytes, proof); err != nil { + return errorsmod.Wrapf(ErrInvalidMerkleProof, "invalid inclusion proof for validator set: %v", err) + } + + return nil +} + +// VerifyEpochSealed verifies that the given `epoch` is sealed by the `rawCkpt` by using the given `proof` +// The verification rules include: +// - basic sanity checks +// - The raw checkpoint's app_hash is same as in the header of the sealer epoch +// - More than 1/3 (in voting power) validators in the validator set of this epoch have signed app_hash of the sealer epoch +// - The epoch medatata is committed to the app_hash of the sealer epoch +// - The validator set is committed to the app_hash of the sealer epoch +func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.RawCheckpoint, proof *ProofEpochSealed) error { + // nil check + if epoch == nil { + return fmt.Errorf("epoch is nil") + } else if rawCkpt == nil { + return fmt.Errorf("rawCkpt is nil") + } else if proof == nil { + return fmt.Errorf("proof is nil") + } + + // sanity check + if err := epoch.ValidateBasic(); err != nil { + return err + } else if err := rawCkpt.ValidateBasic(); err != nil { + return err + } else if err = proof.ValidateBasic(); err != nil { + return err + } + + // ensure epoch number is same in epoch and rawCkpt + if epoch.EpochNumber != rawCkpt.EpochNum { + return fmt.Errorf("epoch.EpochNumber (%d) is not equal to rawCkpt.EpochNum (%d)", epoch.EpochNumber, rawCkpt.EpochNum) + } + + // ensure the raw checkpoint's app_hash is same as in the header of the sealer header + // NOTE: since this proof is assembled by a Babylon node who has verified the checkpoint, + // the two appHash values should always be the same, otherwise this Babylon node is malicious. + // This is different from the checkpoint verification rules in checkpointing, + // where a checkpoint with valid BLS multisig but different appHash signals a dishonest majority equivocation. + appHashInCkpt := rawCkpt.AppHash + appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeaderHash) + if !appHashInCkpt.Equal(appHashInSealerHeader) { + return fmt.Errorf("AppHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", appHashInCkpt.String(), appHashInSealerHeader.String()) + } + + /* + Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || app_hash) in the raw checkpoint + */ + valSet := &checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} + // filter validator set that contributes to the signature + signerSet, signerSetPower, err := valSet.FindSubsetWithPowerSum(rawCkpt.Bitmap) + if err != nil { + return err + } + // ensure the signerSet has > 1/3 voting power + if signerSetPower <= valSet.GetTotalPower()*1/3 { + return fmt.Errorf("the BLS signature involves insufficient voting power") + } + // verify BLS multisig + signedMsgBytes := rawCkpt.SignedMsg() + ok, err := bls12381.VerifyMultiSig(*rawCkpt.BlsMultiSig, signerSet.GetBLSKeySet(), signedMsgBytes) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("BLS signature does not match the public key") + } + + // Ensure The epoch medatata is committed to the app_hash of the sealer header + if err := VerifyEpochInfo(epoch, proof.ProofEpochInfo); err != nil { + return err + } + + // Ensure The validator set is committed to the app_hash of the sealer header + if err := VerifyValSet(epoch, valSet, proof.ProofEpochValSet); err != nil { + return err + } + + return nil +} + +func VerifyCZHeaderInEpoch(header *IndexedHeader, epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { + // nil check + if header == nil { + return fmt.Errorf("header is nil") + } else if epoch == nil { + return fmt.Errorf("epoch is nil") + } else if proof == nil { + return fmt.Errorf("proof is nil") + } + + // sanity check + if err := header.ValidateBasic(); err != nil { + return err + } else if err := epoch.ValidateBasic(); err != nil { + return err + } + + // ensure epoch number is same in epoch and CZ header + if epoch.EpochNumber != header.BabylonEpoch { + return fmt.Errorf("epoch.EpochNumber (%d) is not equal to header.BabylonEpoch (%d)", epoch.EpochNumber, header.BabylonEpoch) + } + + // get the Merkle root, i.e., the AppHash of the sealer header + root := epoch.SealerHeaderHash + + // Ensure The header is committed to the AppHash of the sealer header + headerBytes, err := header.Marshal() + if err != nil { + return err + } + + if err := VerifyStore(root, StoreKey, GetCZHeaderKey(header.ChainId, header.Height), headerBytes, proof); err != nil { + return errorsmod.Wrapf(ErrInvalidMerkleProof, "invalid inclusion proof for CZ header: %v", err) + } + + return nil +} + +// VerifyEpochSubmitted verifies whether an epoch's checkpoint has been included in BTC or not +// verifications include: +// - basic sanity checks +// - Merkle proofs in txsInfo are valid +// - the raw ckpt decoded from txsInfo is same as the expected rawCkpt +func VerifyEpochSubmitted(rawCkpt *checkpointingtypes.RawCheckpoint, txsInfo []*btcctypes.TransactionInfo, btcHeaders []*wire.BlockHeader, powLimit *big.Int, babylonTag txformat.BabylonTag) error { + // basic sanity check + if rawCkpt == nil { + return fmt.Errorf("rawCkpt is nil") + } else if len(txsInfo) != txformat.NumberOfParts { + return fmt.Errorf("txsInfo contains %d parts rather than %d", len(txsInfo), txformat.NumberOfParts) + } else if len(btcHeaders) != txformat.NumberOfParts { + return fmt.Errorf("btcHeaders contains %d parts rather than %d", len(btcHeaders), txformat.NumberOfParts) + } + + // sanity check of each tx info + for _, txInfo := range txsInfo { + if err := txInfo.ValidateBasic(); err != nil { + return err + } + } + + // verify Merkle proofs for each tx info + parsedProofs := []*btcctypes.ParsedProof{} + for i, txInfo := range txsInfo { + btcHeaderBytes := bbn.NewBTCHeaderBytesFromBlockHeader(btcHeaders[i]) + parsedProof, err := btcctypes.ParseProof( + txInfo.Transaction, + txInfo.Key.Index, + txInfo.Proof, + &btcHeaderBytes, + powLimit, + ) + if err != nil { + return err + } + parsedProofs = append(parsedProofs, parsedProof) + } + + // decode parsedProof to checkpoint data + checkpointData := [][]byte{} + for i, proof := range parsedProofs { + data, err := txformat.GetCheckpointData( + babylonTag, + txformat.CurrentVersion, + uint8(i), + proof.OpReturnData, + ) + + if err != nil { + return err + } + checkpointData = append(checkpointData, data) + } + rawCkptData, err := txformat.ConnectParts(txformat.CurrentVersion, checkpointData[0], checkpointData[1]) + if err != nil { + return err + } + decodedRawCkpt, err := checkpointingtypes.FromBTCCkptBytesToRawCkpt(rawCkptData) + if err != nil { + return err + } + + // check if decodedRawCkpt is same as the expected rawCkpt + if !decodedRawCkpt.Equal(rawCkpt) { + return fmt.Errorf("the decoded rawCkpt (%v) is different from the expected rawCkpt (%v)", decodedRawCkpt, rawCkpt) + } + + return nil +} + +func (ts *BTCTimestamp) Verify( + ctx context.Context, + btclcKeeper *btclckeeper.Keeper, + wValue uint64, + ckptTag txformat.BabylonTag, +) error { + // BTC net + btcNet := btclcKeeper.GetBTCNet() + + // verify and insert all BTC headers + headersBytes := []bbn.BTCHeaderBytes{} + for _, headerInfo := range ts.BtcHeaders { + headerBytes := bbn.NewBTCHeaderBytesFromBlockHeader(headerInfo.Header.ToBlockHeader()) + headersBytes = append(headersBytes, headerBytes) + } + if err := btclcKeeper.InsertHeaders(ctx, headersBytes); err != nil { + return err + } + + // get BTC headers that include the checkpoint, and ensure at least 1 of them is w-deep + btcHeadersWithCkpt := []*wire.BlockHeader{} + wDeep := false + for _, key := range ts.BtcSubmissionKey.Key { + header := btclcKeeper.GetHeaderByHash(ctx, key.Hash) + if header == nil { + return fmt.Errorf("header corresponding to the inclusion proof is not on BTC light client") + } + btcHeadersWithCkpt = append(btcHeadersWithCkpt, header.Header.ToBlockHeader()) + + depth, err := btclcKeeper.MainChainDepth(ctx, header.Hash) + if err != nil { + return err + } + if depth >= wValue { + wDeep = true + } + } + if !wDeep { + return fmt.Errorf("checkpoint is not w-deep") + } + + // perform stateless checks that do not rely on BTC light client + return ts.VerifyStateless(btcHeadersWithCkpt, btcNet.PowLimit, ckptTag) +} + +func (ts *BTCTimestamp) VerifyStateless( + btcHeadersWithCkpt []*wire.BlockHeader, + powLimit *big.Int, + ckptTag txformat.BabylonTag, +) error { + // ensure raw checkpoint corresponds to the epoch + if ts.EpochInfo.EpochNumber != ts.RawCheckpoint.EpochNum { + return fmt.Errorf("epoch number in epoch metadata and raw checkpoint is not same") + } + + if len(ts.BtcSubmissionKey.Key) != txformat.NumberOfParts { + return fmt.Errorf("incorrect number of txs for a checkpoint") + } + + // verify the checkpoint txs are committed to the two headers + err := VerifyEpochSubmitted(ts.RawCheckpoint, ts.Proof.ProofEpochSubmitted, btcHeadersWithCkpt, powLimit, ckptTag) + if err != nil { + return err + } + + // verify the epoch is sealed + if err := VerifyEpochSealed(ts.EpochInfo, ts.RawCheckpoint, ts.Proof.ProofEpochSealed); err != nil { + return err + } + + // verify CZ header is committed to the epoch + if err := VerifyCZHeaderInEpoch(ts.Header, ts.EpochInfo, ts.Proof.ProofCzHeaderInEpoch); err != nil { + return err + } + + return nil +} diff --git a/x/zoneconcierge/types/btc_timestamp_test.go b/x/zoneconcierge/types/btc_timestamp_test.go new file mode 100644 index 000000000..a3208407f --- /dev/null +++ b/x/zoneconcierge/types/btc_timestamp_test.go @@ -0,0 +1,165 @@ +package types_test + +import ( + "encoding/hex" + "math/rand" + "testing" + + txformat "github.com/babylonchain/babylon/btctxformatter" + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/babylonchain/babylon/x/zoneconcierge/types" + "github.com/boljen/go-bitmap" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +func signBLSWithBitmap(blsSKs []bls12381.PrivateKey, bm bitmap.Bitmap, msg []byte) (bls12381.Signature, error) { + sigs := []bls12381.Signature{} + for i := 0; i < len(blsSKs); i++ { + if bitmap.Get(bm, i) { + sig := bls12381.Sign(blsSKs[i], msg) + sigs = append(sigs, sig) + } + } + return bls12381.AggrSigList(sigs) +} + +func FuzzBTCTimestamp(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 10) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + h := testhelper.NewHelperWithValSet(t) + ek := &h.App.EpochingKeeper + zck := h.App.ZoneConciergeKeeper + var err error + + // empty BTC timestamp + btcTs := &types.BTCTimestamp{} + btcTs.Proof = &types.ProofFinalizedChainInfo{} + + // chain is at height 1 thus epoch 1 + + /* + generate CZ header and its inclusion proof to an epoch + */ + // enter block 11, 1st block of epoch 2 + epochInterval := ek.GetParams(h.Ctx).EpochInterval + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + + // handle a random header from a random consumer chain + chainID := datagen.GenRandomHexStr(r, 10) + height := datagen.RandomInt(r, 100) + 1 + ibctmHeader := datagen.GenRandomIBCTMHeader(r, chainID, height) + headerInfo := datagen.HeaderToHeaderInfo(ibctmHeader) + zck.HandleHeaderWithValidCommit(h.Ctx, datagen.GenRandomByteArray(r, 32), headerInfo, false) + + // ensure the header is successfully inserted + indexedHeader, err := zck.GetHeader(h.Ctx, chainID, height) + h.NoError(err) + + // enter block 21, 1st block of epoch 3 + for j := 0; j < int(epochInterval); j++ { + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + } + // seal last epoch + h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.NoError(err) + + epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) + h.NoError(err) + + // generate inclusion proof + proof, err := zck.ProveCZHeaderInEpoch(h.Ctx, indexedHeader, epochWithHeader) + h.NoError(err) + + btcTs.EpochInfo = epochWithHeader + btcTs.Header = indexedHeader + btcTs.Proof.ProofCzHeaderInEpoch = proof + + /* + seal the epoch and generate ProofEpochSealed + */ + // construct the rawCkpt + // Note that the BlsMultiSig will be generated and assigned later + bm := datagen.GenFullBitmap() + appHash := checkpointingtypes.AppHash(epochWithHeader.SealerHeaderHash) + rawCkpt := &checkpointingtypes.RawCheckpoint{ + EpochNum: epochWithHeader.EpochNumber, + AppHash: &appHash, + Bitmap: bm, + BlsMultiSig: nil, + } + // let the subset generate a BLS multisig over sealer header's app_hash + multiSig, err := signBLSWithBitmap(h.GenValidators.BlsPrivKeys, bm, rawCkpt.SignedMsg()) + require.NoError(t, err) + // assign multiSig to rawCkpt + rawCkpt.BlsMultiSig = &multiSig + + // prove + btcTs.Proof.ProofEpochSealed, err = zck.ProveEpochSealed(h.Ctx, epochWithHeader.EpochNumber) + require.NoError(t, err) + + btcTs.RawCheckpoint = rawCkpt + + /* + forge two BTC headers including the checkpoint + */ + // encode ckpt to BTC txs in BTC blocks + submitterAddr := datagen.GenRandomByteArray(r, txformat.AddressLength) + rawBTCCkpt, err := checkpointingtypes.FromRawCkptToBTCCkpt(rawCkpt, submitterAddr) + h.NoError(err) + testRawCkptData := datagen.EncodeRawCkptToTestData(rawBTCCkpt) + idxs := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} + offsets := []uint64{datagen.RandomInt(r, 5) + 1, datagen.RandomInt(r, 5) + 1} + btcBlocks := []*datagen.BlockCreationResult{ + datagen.CreateBlock(r, 1, uint32(idxs[0]+offsets[0]), uint32(idxs[0]), testRawCkptData.FirstPart), + datagen.CreateBlock(r, 2, uint32(idxs[1]+offsets[1]), uint32(idxs[1]), testRawCkptData.SecondPart), + } + // create MsgInsertBtcSpvProof for the rawCkpt + msgInsertBtcSpvProof := datagen.GenerateMessageWithRandomSubmitter([]*datagen.BlockCreationResult{btcBlocks[0], btcBlocks[1]}) + + // assign BTC submission key and ProofEpochSubmitted + btcTs.BtcSubmissionKey = &btcctypes.SubmissionKey{ + Key: []*btcctypes.TransactionKey{ + &btcctypes.TransactionKey{Index: uint32(idxs[0]), Hash: btcBlocks[0].HeaderBytes.Hash()}, + &btcctypes.TransactionKey{Index: uint32(idxs[1]), Hash: btcBlocks[1].HeaderBytes.Hash()}, + }, + } + btcTs.Proof.ProofEpochSubmitted = []*btcctypes.TransactionInfo{ + { + Key: btcTs.BtcSubmissionKey.Key[0], + Transaction: msgInsertBtcSpvProof.Proofs[0].BtcTransaction, + Proof: msgInsertBtcSpvProof.Proofs[0].MerkleNodes, + }, + { + Key: btcTs.BtcSubmissionKey.Key[1], + Transaction: msgInsertBtcSpvProof.Proofs[1].BtcTransaction, + Proof: msgInsertBtcSpvProof.Proofs[1].MerkleNodes, + }, + } + + // get headers for verification + btcHeaders := []*wire.BlockHeader{ + btcBlocks[0].HeaderBytes.ToBlockHeader(), + btcBlocks[1].HeaderBytes.ToBlockHeader(), + } + + // net param, babylonTag + powLimit := chaincfg.SimNetParams.PowLimit + babylonTag := btcctypes.DefaultCheckpointTag + tagAsBytes, _ := hex.DecodeString(babylonTag) + + err = btcTs.VerifyStateless(btcHeaders, powLimit, tagAsBytes) + h.NoError(err) + }) +} diff --git a/x/zoneconcierge/types/zoneconcierge.go b/x/zoneconcierge/types/zoneconcierge.go index 02e237d68..270e75c6b 100644 --- a/x/zoneconcierge/types/zoneconcierge.go +++ b/x/zoneconcierge/types/zoneconcierge.go @@ -3,8 +3,35 @@ package types import ( "bytes" "fmt" + + "cosmossdk.io/store/rootmulti" + "github.com/cometbft/cometbft/crypto/merkle" + tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" ) +// VerifyStore verifies whether a KV pair is committed to the Merkle root, with the assistance of a Merkle proof +// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.46.6/store/rootmulti/proof_test.go) +func VerifyStore(root []byte, moduleStoreKey string, key []byte, value []byte, proof *tmcrypto.ProofOps) error { + prt := rootmulti.DefaultProofRuntime() + + keypath := merkle.KeyPath{} + keypath = keypath.AppendKey([]byte(moduleStoreKey), merkle.KeyEncodingURL) + keypath = keypath.AppendKey(key, merkle.KeyEncodingURL) + keypathStr := keypath.String() + + // NOTE: the proof can specify verification rules, either only verifying the + // top Merkle root w.r.t. all KV pairs, or verifying every layer of Merkle root + // TODO: investigate how the verification rules are chosen when generating the + // proof + if err1 := prt.VerifyValue(proof, root, keypathStr, value); err1 != nil { + if err2 := prt.VerifyAbsence(proof, root, keypathStr); err2 != nil { + return fmt.Errorf("the Merkle proof does not pass any verification: err of VerifyValue: %w; err of VerifyAbsence: %w", err1, err2) + } + } + + return nil +} + func (p *ProofEpochSealed) ValidateBasic() error { if p.ValidatorSet == nil { return ErrInvalidProofEpochSealed.Wrap("ValidatorSet is nil") From e94ab17dc2fd65576d868336f553e66f6dfaea5d Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 20 Dec 2023 08:46:10 +0100 Subject: [PATCH 135/202] Min unbonding time param (#152) * Add min unbonding time param * Move minunbonding time function to types --- cmd/babylond/cmd/flags.go | 8 +- cmd/babylond/cmd/genesis.go | 8 +- cmd/babylond/cmd/testnet.go | 11 +-- proto/babylon/btcstaking/v1/params.proto | 5 +- x/btcstaking/keeper/msg_server.go | 15 ++-- x/btcstaking/keeper/msg_server_test.go | 1 + x/btcstaking/types/btcstaking.go | 15 ++++ x/btcstaking/types/params.go | 17 ++++ x/btcstaking/types/params.pb.go | 98 ++++++++++++++++-------- 9 files changed, 132 insertions(+), 46 deletions(-) diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 6b239b41a..74ae9fb47 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -1,10 +1,11 @@ package cmd import ( - "cosmossdk.io/math" "strings" "time" + "cosmossdk.io/math" + babylonApp "github.com/babylonchain/babylon/app" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" @@ -31,6 +32,7 @@ const ( flagCovenantPks = "covenant-pks" flagCovenantQuorum = "covenant-quorum" flagMaxActiveFinalityProviders = "max-active-finality-providers" + flagMinUnbondingTime = "min-unbonding-time" flagSlashingAddress = "slashing-address" flagMinSlashingFee = "min-slashing-fee-sat" flagSlashingRate = "slashing-rate" @@ -60,6 +62,7 @@ type GenesisCLIArgs struct { MinSlashingTransactionFeeSat int64 SlashingRate math.LegacyDec MaxActiveFinalityProviders uint32 + MinUnbondingTime uint16 MinPubRand uint64 MinCommissionRate math.LegacyDec } @@ -86,6 +89,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") cmd.Flags().String(flagSlashingRate, "0.1", "Bitcoin staking slashing rate") cmd.Flags().Uint32(flagMaxActiveFinalityProviders, 100, "Bitcoin staking maximum active finality providers") + cmd.Flags().Uint16(flagMinUnbondingTime, 0, "Min timelock on unbonding transaction in btc blocks") // finality args cmd.Flags().Uint64(flagMinPubRand, 100, "Bitcoin staking minimum public randomness commit") // inflation args @@ -116,6 +120,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) slashingRate, _ := cmd.Flags().GetString(flagSlashingRate) maxActiveFinalityProviders, _ := cmd.Flags().GetUint32(flagMaxActiveFinalityProviders) + minUnbondingTime, _ := cmd.Flags().GetUint16(flagMinUnbondingTime) minPubRand, _ := cmd.Flags().GetUint64(flagMinPubRand) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) @@ -147,6 +152,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { MinCommissionRate: math.LegacyMustNewDecFromStr(minCommissionRate), SlashingRate: math.LegacyMustNewDecFromStr(slashingRate), MaxActiveFinalityProviders: maxActiveFinalityProviders, + MinUnbondingTime: minUnbondingTime, MinPubRand: minPubRand, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 11eb0bfa8..d489fe45c 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -1,11 +1,12 @@ package cmd import ( - sdkmath "cosmossdk.io/math" "encoding/json" "fmt" "time" + sdkmath "cosmossdk.io/math" + btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types" finalitytypes "github.com/babylonchain/babylon/x/finality/types" @@ -72,7 +73,7 @@ Example: genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, - genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, + genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) } else if network == "mainnet" { @@ -232,7 +233,7 @@ type GenesisParams struct { func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, - minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, + minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, minUnbondingTime uint16, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { @@ -322,6 +323,7 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 genParams.BtcstakingParams.MinCommissionRate = minCommissionRate genParams.BtcstakingParams.SlashingRate = slashingRate genParams.BtcstakingParams.MaxActiveFinalityProviders = maxActiveFinalityProviders + genParams.BtcstakingParams.MinUnbondingTime = uint32(minUnbondingTime) if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index f7b99bbb0..1a9397961 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -2,17 +2,18 @@ package cmd import ( "bufio" - "cosmossdk.io/math" "encoding/json" "errors" "fmt" - "github.com/cosmos/cosmos-sdk/runtime" - authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "net" "os" "path/filepath" "time" + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/runtime" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + appparams "github.com/babylonchain/babylon/app/params" bbn "github.com/babylonchain/babylon/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -100,8 +101,8 @@ Example: genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, - genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinPubRand, - genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, + genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, + genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index fb611fea7..03992b3dd 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -14,7 +14,7 @@ message Params { // each PK follows encoding in BIP-340 spec on Bitcoin repeated bytes covenant_pks = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ]; // covenant_quorum is the minimum number of signatures needed for the covenant - // multisignature + // multisignature uint32 covenant_quorum = 2; // slashing address is the address that the slashed BTC goes to // the address is in string on Bitcoin @@ -37,4 +37,7 @@ message Params { ]; // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol uint32 max_active_finality_providers = 7; + + // min_unbonding_time is the minimum time for unbonding transaction timelock in BTC blocks + uint32 min_unbonding_time = 8; } diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index b7d579b23..07bcfa398 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - sdkmath "cosmossdk.io/math" - errorsmod "cosmossdk.io/errors" + + sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" bbn "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btcstaking/types" @@ -270,11 +270,14 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre if unbondingMsgTx.TxIn[0].PreviousOutPoint.Index != stakingOutputIdx { return nil, types.ErrInvalidUnbondingTx.Wrapf("slashing transaction input must spend staking output") } + minUnbondingTime := types.MinimumUnbondingTime(params, btccParams) - // Check unbonding time (staking time from unbonding tx) is larger than finalization time - // Unbonding time must be strictly larger that babylon finalization time. - if uint64(req.UnbondingTime) <= wValue { - return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than finalization time %d", req.UnbondingTime, wValue) + // Check unbonding time (staking time from unbonding tx) is larger than min unbonding time + // which is larger value from: + // - MinUnbondingTime + // - CheckpointFinalizationTimeout + if uint64(req.UnbondingTime) <= minUnbondingTime { + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than %d", req.UnbondingTime, minUnbondingTime) } // building unbonding info diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index c7dfb1053..96c9bead6 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -75,6 +75,7 @@ func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec. MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveFinalityProviders: 100, + MinUnbondingTime: 0, }) h.NoError(err) return covenantSKs, covenantPKs diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index ff5c0d786..880755870 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -2,10 +2,13 @@ package types import ( "fmt" + "sort" + "cosmossdk.io/math" asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" ) func (fp *FinalityProvider) IsSlashed() bool { @@ -113,3 +116,15 @@ func GetOrderedCovenantSignatures(fpIdx int, covSigsList []*CovenantAdaptorSigna return orderedCovSigs, nil } + +// MinimumUnbondingTime returns the minimum unbonding time. It is the bigger value from: +// - MinUnbondingTime +// - CheckpointFinalizationTimeout +func MinimumUnbondingTime( + stakingParams Params, + checkpointingParams btcctypes.Params) uint64 { + return math.Max[uint64]( + uint64(stakingParams.MinUnbondingTime), + checkpointingParams.CheckpointFinalizationTimeout, + ) +} diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index bc36c4888..8e923fca7 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "math" sdkmath "cosmossdk.io/math" "github.com/babylonchain/babylon/btcstaking" @@ -60,6 +61,9 @@ func DefaultParams() Params { // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. SlashingRate: sdkmath.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 MaxActiveFinalityProviders: defaultMaxActiveFinalityProviders, + // The default minimum unbonding time is 0, which effectively defaults to checkpoint + // finalization timeout. + MinUnbondingTime: 0, } } @@ -107,6 +111,14 @@ func validateCovenantPks(covenantPks []bbn.BIP340PubKey) error { return nil } +func validateMinUnbondingTime(minUnbondingTimeBlocks uint32) error { + if minUnbondingTimeBlocks > math.MaxUint16 { + return fmt.Errorf("minimum unbonding time blocks cannot be greater than %d", math.MaxUint16) + } + + return nil +} + // Validate validates the set of params func (p Params) Validate() error { if p.CovenantQuorum == 0 { @@ -133,6 +145,11 @@ func (p Params) Validate() error { if err := validateMaxActiveFinalityProviders(p.MaxActiveFinalityProviders); err != nil { return err } + + if err := validateMinUnbondingTime(p.MinUnbondingTime); err != nil { + return err + } + return nil } diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 567da0ca5..642dda4e6 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -48,6 +48,8 @@ type Params struct { SlashingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slashing_rate"` // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol MaxActiveFinalityProviders uint32 `protobuf:"varint,7,opt,name=max_active_finality_providers,json=maxActiveFinalityProviders,proto3" json:"max_active_finality_providers,omitempty"` + // min_unbonding_time is the minimum time for unbonding transaction timelock in BTC blocks + MinUnbondingTime uint32 `protobuf:"varint,8,opt,name=min_unbonding_time,json=minUnbondingTime,proto3" json:"min_unbonding_time,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -110,6 +112,13 @@ func (m *Params) GetMaxActiveFinalityProviders() uint32 { return 0 } +func (m *Params) GetMinUnbondingTime() uint32 { + if m != nil { + return m.MinUnbondingTime + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "babylon.btcstaking.v1.Params") } @@ -119,36 +128,38 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 456 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xb1, 0x8e, 0xd3, 0x3e, - 0x1c, 0xc7, 0x93, 0x7f, 0xef, 0x5f, 0x84, 0xe9, 0x71, 0x10, 0x40, 0x84, 0x22, 0xd2, 0xea, 0x18, - 0x28, 0x03, 0x09, 0xe5, 0x4e, 0x0c, 0x6c, 0x2d, 0xe8, 0x24, 0xc4, 0x0d, 0x21, 0x45, 0x48, 0xb0, - 0x58, 0xbf, 0xb8, 0xbe, 0xd4, 0x6a, 0x6d, 0x97, 0xd8, 0x8d, 0x92, 0xb7, 0x60, 0x64, 0xe4, 0x21, - 0x78, 0x88, 0x1b, 0x4f, 0x4c, 0xe8, 0x86, 0x0a, 0xb5, 0x2f, 0x82, 0xe2, 0x24, 0x85, 0x0d, 0xb6, - 0xf8, 0xfb, 0xfb, 0xe4, 0x63, 0xcb, 0x5f, 0xa3, 0xc3, 0x18, 0xe2, 0x62, 0x21, 0x45, 0x10, 0x6b, - 0xa2, 0x34, 0xcc, 0x99, 0x48, 0x82, 0x6c, 0x18, 0x2c, 0x21, 0x05, 0xae, 0xfc, 0x65, 0x2a, 0xb5, - 0x74, 0xee, 0xd4, 0x8c, 0xff, 0x9b, 0xf1, 0xb3, 0x61, 0xf7, 0x76, 0x22, 0x13, 0x69, 0x88, 0xa0, - 0xfc, 0xaa, 0xe0, 0xee, 0x3d, 0x22, 0x15, 0x97, 0x0a, 0x57, 0x83, 0x6a, 0x51, 0x8d, 0x0e, 0xb7, - 0x2d, 0xd4, 0x0e, 0x8d, 0xd8, 0xf9, 0x80, 0x3a, 0x44, 0x66, 0x54, 0x80, 0xd0, 0x78, 0x39, 0x57, - 0xae, 0xdd, 0x6f, 0x0d, 0x3a, 0xe3, 0xe7, 0x97, 0xeb, 0xde, 0xb3, 0x84, 0xe9, 0xd9, 0x2a, 0xf6, - 0x89, 0xe4, 0x41, 0xbd, 0x2f, 0x99, 0x01, 0x13, 0xcd, 0x22, 0xd0, 0xc5, 0x92, 0x2a, 0x7f, 0xfc, - 0x3a, 0x3c, 0x3a, 0x7e, 0x1a, 0xae, 0xe2, 0x37, 0xb4, 0x88, 0xae, 0x35, 0xae, 0x70, 0xae, 0x9c, - 0x47, 0xe8, 0x60, 0xa7, 0xfe, 0xb4, 0x92, 0xe9, 0x8a, 0xbb, 0xff, 0xf5, 0xed, 0xc1, 0x7e, 0x74, - 0xbd, 0x89, 0xdf, 0x9a, 0xd4, 0x79, 0x8c, 0x6e, 0xa8, 0x05, 0xa8, 0x19, 0x13, 0x09, 0x86, 0xe9, - 0x34, 0xa5, 0x4a, 0xb9, 0xad, 0xbe, 0x3d, 0xb8, 0x1a, 0x1d, 0x34, 0xf9, 0xa8, 0x8a, 0x9d, 0x63, - 0x74, 0x97, 0x33, 0x81, 0x77, 0xb8, 0xce, 0xf1, 0x19, 0xa5, 0x58, 0x81, 0x76, 0xf7, 0xfa, 0xf6, - 0xa0, 0x15, 0xdd, 0xe2, 0x4c, 0x4c, 0xea, 0xe9, 0xbb, 0xfc, 0x84, 0xd2, 0x09, 0x68, 0x67, 0x82, - 0xca, 0x18, 0x13, 0xc9, 0x39, 0x53, 0x8a, 0x49, 0x81, 0x53, 0xd0, 0xd4, 0xfd, 0xbf, 0xdc, 0x63, - 0xfc, 0xf0, 0x7c, 0xdd, 0xb3, 0x2e, 0xd7, 0xbd, 0xfb, 0xd5, 0x15, 0xa9, 0xe9, 0xdc, 0x67, 0x32, - 0xe0, 0xa0, 0x67, 0xfe, 0x29, 0x4d, 0x80, 0x14, 0xaf, 0x28, 0x89, 0x6e, 0x72, 0x26, 0x5e, 0xee, - 0x7e, 0x8f, 0x40, 0x53, 0xe7, 0x3d, 0xda, 0xdf, 0x1d, 0xc3, 0xe8, 0xda, 0x46, 0x37, 0xfc, 0x07, - 0xdd, 0xf7, 0x6f, 0x4f, 0x50, 0x5d, 0x48, 0x29, 0xef, 0x34, 0x1e, 0xe3, 0x1d, 0xa1, 0x07, 0x1c, - 0x72, 0x0c, 0x44, 0xb3, 0x8c, 0xe2, 0x33, 0x26, 0x60, 0xc1, 0x74, 0x51, 0xd6, 0x98, 0xb1, 0x29, - 0x4d, 0x95, 0x7b, 0xc5, 0x5c, 0x62, 0x97, 0x43, 0x3e, 0x32, 0xcc, 0x49, 0x8d, 0x84, 0x0d, 0xf1, - 0x62, 0xef, 0xcb, 0xd7, 0x9e, 0x35, 0x3e, 0x3d, 0xdf, 0x78, 0xf6, 0xc5, 0xc6, 0xb3, 0x7f, 0x6e, - 0x3c, 0xfb, 0xf3, 0xd6, 0xb3, 0x2e, 0xb6, 0x9e, 0xf5, 0x63, 0xeb, 0x59, 0x1f, 0xff, 0x5a, 0x6d, - 0xfe, 0xe7, 0x2b, 0x34, 0x3d, 0xc7, 0x6d, 0xf3, 0x74, 0x8e, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, - 0x86, 0x1c, 0x80, 0x7e, 0xa8, 0x02, 0x00, 0x00, + // 483 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x6f, 0xd3, 0x3c, + 0x18, 0xc7, 0x93, 0xb7, 0x7d, 0x0b, 0x98, 0x8e, 0x8d, 0x00, 0x22, 0x14, 0x91, 0x56, 0xe3, 0x40, + 0x91, 0x20, 0xa1, 0x6c, 0xe2, 0xc0, 0xad, 0x05, 0x4d, 0x42, 0xec, 0x10, 0xd2, 0x81, 0x04, 0x17, + 0xcb, 0x71, 0xbc, 0xd4, 0x6a, 0x6d, 0x97, 0xd8, 0x89, 0x92, 0x6f, 0x01, 0x37, 0x8e, 0x7c, 0x08, + 0x3e, 0xc4, 0x8e, 0x13, 0x27, 0xb4, 0x43, 0x85, 0xda, 0x2f, 0x82, 0xe2, 0x24, 0x85, 0x1b, 0xdc, + 0xe2, 0xff, 0xf3, 0xf3, 0xcf, 0xf1, 0xe3, 0x07, 0xec, 0x87, 0x28, 0x2c, 0x16, 0x82, 0x7b, 0xa1, + 0xc2, 0x52, 0xa1, 0x39, 0xe5, 0xb1, 0x97, 0x8d, 0xbc, 0x25, 0x4a, 0x10, 0x93, 0xee, 0x32, 0x11, + 0x4a, 0x58, 0xb7, 0x6a, 0xc6, 0xfd, 0xcd, 0xb8, 0xd9, 0xa8, 0x77, 0x33, 0x16, 0xb1, 0xd0, 0x84, + 0x57, 0x7e, 0x55, 0x70, 0xef, 0x0e, 0x16, 0x92, 0x09, 0x09, 0xab, 0x42, 0xb5, 0xa8, 0x4a, 0xfb, + 0x9f, 0xdb, 0xa0, 0xe3, 0x6b, 0xb1, 0xf5, 0x1e, 0x74, 0xb1, 0xc8, 0x08, 0x47, 0x5c, 0xc1, 0xe5, + 0x5c, 0xda, 0xe6, 0xa0, 0x35, 0xec, 0x4e, 0x9e, 0x5d, 0xac, 0xfa, 0x4f, 0x63, 0xaa, 0x66, 0x69, + 0xe8, 0x62, 0xc1, 0xbc, 0xfa, 0x5c, 0x3c, 0x43, 0x94, 0x37, 0x0b, 0x4f, 0x15, 0x4b, 0x22, 0xdd, + 0xc9, 0x2b, 0xff, 0xe0, 0xf0, 0x89, 0x9f, 0x86, 0xaf, 0x49, 0x11, 0x5c, 0x6d, 0x5c, 0xfe, 0x5c, + 0x5a, 0x0f, 0xc0, 0xee, 0x56, 0xfd, 0x31, 0x15, 0x49, 0xca, 0xec, 0xff, 0x06, 0xe6, 0x70, 0x27, + 0xb8, 0xd6, 0xc4, 0x6f, 0x74, 0x6a, 0x3d, 0x04, 0x7b, 0x72, 0x81, 0xe4, 0x8c, 0xf2, 0x18, 0xa2, + 0x28, 0x4a, 0x88, 0x94, 0x76, 0x6b, 0x60, 0x0e, 0xaf, 0x04, 0xbb, 0x4d, 0x3e, 0xae, 0x62, 0xeb, + 0x10, 0xdc, 0x66, 0x94, 0xc3, 0x2d, 0xae, 0x72, 0x78, 0x4a, 0x08, 0x94, 0x48, 0xd9, 0xed, 0x81, + 0x39, 0x6c, 0x05, 0x37, 0x18, 0xe5, 0xd3, 0xba, 0x7a, 0x92, 0x1f, 0x11, 0x32, 0x45, 0xca, 0x9a, + 0x82, 0x32, 0x86, 0x58, 0x30, 0x46, 0xa5, 0xa4, 0x82, 0xc3, 0x04, 0x29, 0x62, 0xff, 0x5f, 0x9e, + 0x31, 0xb9, 0x7f, 0xb6, 0xea, 0x1b, 0x17, 0xab, 0xfe, 0xdd, 0xaa, 0x45, 0x32, 0x9a, 0xbb, 0x54, + 0x78, 0x0c, 0xa9, 0x99, 0x7b, 0x4c, 0x62, 0x84, 0x8b, 0x97, 0x04, 0x07, 0xd7, 0x19, 0xe5, 0x2f, + 0xb6, 0xdb, 0x03, 0xa4, 0x88, 0xf5, 0x0e, 0xec, 0x6c, 0x7f, 0x43, 0xeb, 0x3a, 0x5a, 0x37, 0xfa, + 0x07, 0xdd, 0xf7, 0x6f, 0x8f, 0x41, 0xfd, 0x20, 0xa5, 0xbc, 0xdb, 0x78, 0xb4, 0x77, 0x0c, 0xee, + 0x31, 0x94, 0x43, 0x84, 0x15, 0xcd, 0x08, 0x3c, 0xa5, 0x1c, 0x2d, 0xa8, 0x2a, 0xca, 0x67, 0xcc, + 0x68, 0x44, 0x12, 0x69, 0x5f, 0xd2, 0x4d, 0xec, 0x31, 0x94, 0x8f, 0x35, 0x73, 0x54, 0x23, 0x7e, + 0x43, 0x58, 0x8f, 0x80, 0x55, 0xde, 0x37, 0xe5, 0xa1, 0xe0, 0x91, 0x6e, 0x13, 0x65, 0xc4, 0xbe, + 0xac, 0xf7, 0xed, 0x31, 0xca, 0xdf, 0x36, 0x85, 0x13, 0xca, 0xc8, 0xf3, 0xf6, 0x97, 0xaf, 0x7d, + 0x63, 0x72, 0x7c, 0xb6, 0x76, 0xcc, 0xf3, 0xb5, 0x63, 0xfe, 0x5c, 0x3b, 0xe6, 0xa7, 0x8d, 0x63, + 0x9c, 0x6f, 0x1c, 0xe3, 0xc7, 0xc6, 0x31, 0x3e, 0xfc, 0x75, 0x10, 0xf2, 0x3f, 0x67, 0x56, 0x4f, + 0x45, 0xd8, 0xd1, 0x83, 0x76, 0xf0, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x9a, 0xce, 0xa4, 0xfd, 0xd6, + 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -171,6 +182,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MinUnbondingTime != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinUnbondingTime)) + i-- + dAtA[i] = 0x40 + } if m.MaxActiveFinalityProviders != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveFinalityProviders)) i-- @@ -270,6 +286,9 @@ func (m *Params) Size() (n int) { if m.MaxActiveFinalityProviders != 0 { n += 1 + sovParams(uint64(m.MaxActiveFinalityProviders)) } + if m.MinUnbondingTime != 0 { + n += 1 + sovParams(uint64(m.MinUnbondingTime)) + } return n } @@ -500,6 +519,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinUnbondingTime", wireType) + } + m.MinUnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinUnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) From 94130788700e3d7cc17cafdfa35499639aa15b91 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 21 Dec 2023 18:53:15 +1100 Subject: [PATCH 136/202] btcstaking: add covenant signatures to BTCDelegation rpc endpoint (#154) * init * add adaptor sigs to unde linfo --- proto/babylon/btcstaking/v1/btcstaking.proto | 5 +- proto/babylon/btcstaking/v1/query.proto | 8 +- x/btcstaking/keeper/grpc_query.go | 2 + x/btcstaking/types/btcstaking.pb.go | 212 +++++++++++------ x/btcstaking/types/query.pb.go | 236 ++++++++++++------- 5 files changed, 303 insertions(+), 160 deletions(-) diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f9c8992d6..f2331d600 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -139,12 +139,15 @@ message BTCUndelegationInfo { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. bytes unbonding_tx = 1; - // unbonding_time describes how long the funds will be locked in the unbonding output uint32 unbonding_time = 2; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members repeated SignatureInfo covenant_unbonding_sig_list = 3; + // covenant_slashing_sigs is a list of adaptor signatures on the + // unbonding slashing tx by each covenant member + // It will be a part of the witness for the staking tx output. + repeated CovenantAdaptorSignatures covenant_slashing_sigs = 4; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index 9656c0e17..e2adcfdda 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -236,9 +236,13 @@ message QueryBTCDelegationResponse { uint64 total_sat = 5; // staking_tx_hex is the hex string of staking tx string staking_tx_hex = 6; + // covenant_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member + // It will be a part of the witness for the staking tx output. + repeated CovenantAdaptorSignatures covenant_sigs = 7; // whether this delegation is active - bool active = 7; + bool active = 8; // undelegation_info is the undelegation info of this delegation. It is nil // if it is not undelegating - BTCUndelegationInfo undelegation_info = 8; + BTCUndelegationInfo undelegation_info = 9; } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 6791483b2..f89bdc81f 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -279,6 +279,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, UnbondingTime: btcDel.BtcUndelegation.UnbondingTime, CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, + CovenantSlashingSigs: btcDel.BtcUndelegation.CovenantSlashingSigs, } return &types.QueryBTCDelegationResponse{ @@ -288,6 +289,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation EndHeight: btcDel.EndHeight, TotalSat: btcDel.TotalSat, StakingTxHex: hex.EncodeToString(btcDel.StakingTx), + CovenantSigs: btcDel.CovenantSigs, Active: isActive, UndelegationInfo: undelegationInfo, }, nil diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index cd74a4ec1..8f264d9ad 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -486,6 +486,10 @@ type BTCUndelegationInfo struct { // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,3,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` + // covenant_slashing_sigs is a list of adaptor signatures on the + // unbonding slashing tx by each covenant member + // It will be a part of the witness for the staking tx output. + CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,4,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -542,6 +546,13 @@ func (m *BTCUndelegationInfo) GetCovenantUnbondingSigList() []*SignatureInfo { return nil } +func (m *BTCUndelegationInfo) GetCovenantSlashingSigs() []*CovenantAdaptorSignatures { + if m != nil { + return m.CovenantSlashingSigs + } + return nil +} + // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. type BTCDelegatorDelegations struct { Dels []*BTCDelegation `protobuf:"bytes,1,rep,name=dels,proto3" json:"dels,omitempty"` @@ -805,82 +816,83 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1197 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0x1b, 0xc5, - 0x17, 0xcf, 0xda, 0x8e, 0x53, 0x1f, 0xdb, 0xad, 0x3b, 0x4d, 0xd3, 0x6d, 0xa3, 0x7f, 0x92, 0xbf, - 0x29, 0x95, 0x85, 0xe8, 0xba, 0x49, 0x3f, 0x04, 0x5c, 0x20, 0xd5, 0x71, 0x4a, 0xa3, 0xb6, 0xa9, - 0x59, 0x3b, 0x20, 0x40, 0x62, 0xb5, 0xde, 0x1d, 0xaf, 0x57, 0xb6, 0x77, 0x96, 0x9d, 0xb1, 0xb1, - 0xdf, 0x80, 0x1b, 0x24, 0x5e, 0x01, 0x89, 0x47, 0xe0, 0x01, 0xb8, 0x40, 0x88, 0xcb, 0x8a, 0x1b, - 0x50, 0x2e, 0x22, 0xd4, 0xbe, 0x08, 0x9a, 0xd9, 0xd9, 0x0f, 0x87, 0xa6, 0xa5, 0x75, 0xee, 0x3c, - 0x73, 0xce, 0xf9, 0x9d, 0x8f, 0xdf, 0x6f, 0x67, 0xc6, 0x70, 0xa3, 0x6b, 0x76, 0x67, 0x43, 0xe2, - 0xd5, 0xbb, 0xcc, 0xa2, 0xcc, 0x1c, 0xb8, 0x9e, 0x53, 0x9f, 0x6c, 0xa7, 0x56, 0x9a, 0x1f, 0x10, - 0x46, 0xd0, 0x65, 0xe9, 0xa7, 0xa5, 0x2c, 0x93, 0xed, 0x6b, 0xab, 0x0e, 0x71, 0x88, 0xf0, 0xa8, - 0xf3, 0x5f, 0xa1, 0xf3, 0xb5, 0xab, 0x16, 0xa1, 0x23, 0x42, 0x8d, 0xd0, 0x10, 0x2e, 0xa4, 0xa9, - 0x1a, 0xae, 0xea, 0x56, 0x30, 0xf3, 0x19, 0xa9, 0x53, 0x6c, 0xf9, 0x3b, 0x77, 0xef, 0x0d, 0xb6, - 0xeb, 0x03, 0x3c, 0x8b, 0x7c, 0xae, 0x4b, 0x9f, 0xa4, 0x9e, 0x2e, 0x66, 0xe6, 0x76, 0x7d, 0xae, + // 1205 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x5d, 0x6f, 0x1b, 0x45, + 0x17, 0xce, 0xda, 0x8e, 0x53, 0x1f, 0xdb, 0xad, 0x3b, 0x4d, 0xd3, 0x6d, 0xa3, 0x37, 0xc9, 0x6b, + 0x4a, 0x65, 0x21, 0xba, 0x6e, 0xd2, 0x0f, 0x01, 0x17, 0x48, 0x75, 0x9c, 0xd2, 0xa8, 0x6d, 0x6a, + 0xd6, 0x09, 0x08, 0x90, 0x58, 0xad, 0x77, 0xc7, 0xeb, 0x95, 0xed, 0x9d, 0x65, 0x67, 0x6c, 0xec, + 0x7f, 0xc0, 0x0d, 0x12, 0x7f, 0x01, 0x89, 0x4b, 0x2e, 0xf9, 0x01, 0x5c, 0x21, 0x2e, 0x2b, 0x6e, + 0x40, 0xb9, 0x88, 0x50, 0xfb, 0x47, 0xd0, 0xcc, 0xce, 0x7e, 0x38, 0x34, 0x2d, 0xad, 0x7d, 0xe7, + 0x99, 0x73, 0xce, 0x73, 0x3e, 0x9e, 0x67, 0x67, 0xc6, 0x70, 0xa3, 0x63, 0x76, 0xa6, 0x03, 0xe2, + 0xd5, 0x3b, 0xcc, 0xa2, 0xcc, 0xec, 0xbb, 0x9e, 0x53, 0x1f, 0x6f, 0xa7, 0x56, 0x9a, 0x1f, 0x10, + 0x46, 0xd0, 0x65, 0xe9, 0xa7, 0xa5, 0x2c, 0xe3, 0xed, 0x6b, 0xab, 0x0e, 0x71, 0x88, 0xf0, 0xa8, + 0xf3, 0x5f, 0xa1, 0xf3, 0xb5, 0xab, 0x16, 0xa1, 0x43, 0x42, 0x8d, 0xd0, 0x10, 0x2e, 0xa4, 0xa9, + 0x1a, 0xae, 0xea, 0x56, 0x30, 0xf5, 0x19, 0xa9, 0x53, 0x6c, 0xf9, 0x3b, 0x77, 0xef, 0xf5, 0xb7, + 0xeb, 0x7d, 0x3c, 0x8d, 0x7c, 0xae, 0x4b, 0x9f, 0xa4, 0x9e, 0x0e, 0x66, 0xe6, 0x76, 0x7d, 0xa6, 0xa2, 0x6b, 0x9b, 0x2f, 0xaf, 0xdc, 0x27, 0x7e, 0xe8, 0x50, 0xfd, 0x33, 0x0b, 0x95, 0x07, 0xae, - 0x67, 0x0e, 0x5d, 0x36, 0x6b, 0x05, 0x64, 0xe2, 0xda, 0x38, 0x40, 0x7b, 0x50, 0xb4, 0x31, 0xb5, + 0x67, 0x0e, 0x5c, 0x36, 0x6d, 0x05, 0x64, 0xec, 0xda, 0x38, 0x40, 0x7b, 0x50, 0xb4, 0x31, 0xb5, 0x02, 0xd7, 0x67, 0x2e, 0xf1, 0x54, 0x65, 0x4b, 0xa9, 0x15, 0x77, 0xde, 0xd1, 0x64, 0x8d, 0x49, - 0x67, 0x22, 0xa3, 0xd6, 0x4c, 0x5c, 0xf5, 0x74, 0x1c, 0x7a, 0x02, 0x60, 0x91, 0xd1, 0xc8, 0xa5, - 0x94, 0xa3, 0x64, 0xb6, 0x94, 0x5a, 0xa1, 0x71, 0xf3, 0xe8, 0x78, 0x73, 0x3d, 0x04, 0xa2, 0xf6, - 0x40, 0x73, 0x49, 0x7d, 0x64, 0xb2, 0xbe, 0xf6, 0x18, 0x3b, 0xa6, 0x35, 0x6b, 0x62, 0xeb, 0x8f, - 0x9f, 0x6f, 0x82, 0xcc, 0xd3, 0xc4, 0x96, 0x9e, 0x02, 0x40, 0x1f, 0x03, 0xc8, 0x6e, 0x0c, 0x7f, - 0xa0, 0x66, 0x45, 0x51, 0x9b, 0x51, 0x51, 0xe1, 0xa8, 0xb4, 0x78, 0x54, 0x5a, 0x6b, 0xdc, 0x7d, - 0x84, 0x67, 0x7a, 0x41, 0x86, 0xb4, 0x06, 0xe8, 0x09, 0xe4, 0xbb, 0xcc, 0xe2, 0xb1, 0xb9, 0x2d, - 0xa5, 0x56, 0x6a, 0xdc, 0x3b, 0x3a, 0xde, 0xdc, 0x71, 0x5c, 0xd6, 0x1f, 0x77, 0x35, 0x8b, 0x8c, - 0xea, 0xd2, 0xd3, 0xea, 0x9b, 0xae, 0x17, 0x2d, 0xea, 0x6c, 0xe6, 0x63, 0xaa, 0x35, 0xf6, 0x5b, - 0xb7, 0xef, 0xdc, 0x92, 0x90, 0xcb, 0x5d, 0x66, 0xb5, 0x06, 0xe8, 0x23, 0xc8, 0xfa, 0xc4, 0x57, - 0x97, 0x45, 0x1d, 0x35, 0xed, 0xa5, 0xd4, 0x6b, 0xad, 0x80, 0x90, 0xde, 0xd3, 0x5e, 0x8b, 0x50, - 0x8a, 0x45, 0x17, 0x3a, 0x0f, 0x42, 0x77, 0x60, 0x8d, 0x0e, 0x4d, 0xda, 0xc7, 0xb6, 0x11, 0xb5, - 0xd4, 0xc7, 0xae, 0xd3, 0x67, 0x6a, 0x7e, 0x4b, 0xa9, 0xe5, 0xf4, 0x55, 0x69, 0x6d, 0x84, 0xc6, + 0x67, 0x22, 0xa3, 0xd6, 0x4c, 0x5c, 0xf5, 0x74, 0x1c, 0x7a, 0x02, 0x60, 0x91, 0xe1, 0xd0, 0xa5, + 0x94, 0xa3, 0x64, 0xb6, 0x94, 0x5a, 0xa1, 0x71, 0xf3, 0xf8, 0x64, 0x73, 0x3d, 0x04, 0xa2, 0x76, + 0x5f, 0x73, 0x49, 0x7d, 0x68, 0xb2, 0x9e, 0xf6, 0x18, 0x3b, 0xa6, 0x35, 0x6d, 0x62, 0xeb, 0x8f, + 0x5f, 0x6e, 0x82, 0xcc, 0xd3, 0xc4, 0x96, 0x9e, 0x02, 0x40, 0x1f, 0x03, 0xc8, 0x6e, 0x0c, 0xbf, + 0xaf, 0x66, 0x45, 0x51, 0x9b, 0x51, 0x51, 0xe1, 0xa8, 0xb4, 0x78, 0x54, 0x5a, 0x6b, 0xd4, 0x79, + 0x84, 0xa7, 0x7a, 0x41, 0x86, 0xb4, 0xfa, 0xe8, 0x09, 0xe4, 0x3b, 0xcc, 0xe2, 0xb1, 0xb9, 0x2d, + 0xa5, 0x56, 0x6a, 0xdc, 0x3b, 0x3e, 0xd9, 0xdc, 0x71, 0x5c, 0xd6, 0x1b, 0x75, 0x34, 0x8b, 0x0c, + 0xeb, 0xd2, 0xd3, 0xea, 0x99, 0xae, 0x17, 0x2d, 0xea, 0x6c, 0xea, 0x63, 0xaa, 0x35, 0xf6, 0x5b, + 0xb7, 0xef, 0xdc, 0x92, 0x90, 0xcb, 0x1d, 0x66, 0xb5, 0xfa, 0xe8, 0x23, 0xc8, 0xfa, 0xc4, 0x57, + 0x97, 0x45, 0x1d, 0x35, 0xed, 0xa5, 0xd4, 0x6b, 0xad, 0x80, 0x90, 0xee, 0xd3, 0x6e, 0x8b, 0x50, + 0x8a, 0x45, 0x17, 0x3a, 0x0f, 0x42, 0x77, 0x60, 0x8d, 0x0e, 0x4c, 0xda, 0xc3, 0xb6, 0x11, 0xb5, + 0xd4, 0xc3, 0xae, 0xd3, 0x63, 0x6a, 0x7e, 0x4b, 0xa9, 0xe5, 0xf4, 0x55, 0x69, 0x6d, 0x84, 0xc6, 0x87, 0xc2, 0x86, 0xde, 0x07, 0x14, 0x47, 0x31, 0x2b, 0x8a, 0x58, 0x11, 0x11, 0x95, 0x28, 0x82, - 0x59, 0xa1, 0x77, 0xf5, 0xbb, 0x0c, 0xa8, 0x27, 0x99, 0xfd, 0xdc, 0x65, 0xfd, 0x27, 0x98, 0x99, - 0xa9, 0x59, 0x28, 0x67, 0x31, 0x8b, 0x35, 0xc8, 0xcb, 0x6a, 0x32, 0xa2, 0x1a, 0xb9, 0x42, 0xff, - 0x87, 0xd2, 0x84, 0x30, 0xd7, 0x73, 0x0c, 0x9f, 0x7c, 0x8b, 0x03, 0x41, 0x5a, 0x4e, 0x2f, 0x86, - 0x7b, 0x2d, 0xbe, 0xf5, 0x8a, 0x51, 0xe4, 0xde, 0x78, 0x14, 0xcb, 0xa7, 0x8c, 0xe2, 0xc7, 0x3c, - 0x94, 0x1b, 0x9d, 0xdd, 0x26, 0x1e, 0x62, 0xc7, 0x64, 0xff, 0xd6, 0x92, 0xb2, 0x80, 0x96, 0x32, - 0x67, 0xa8, 0xa5, 0xec, 0xdb, 0x68, 0xe9, 0x2b, 0x38, 0xdf, 0xf3, 0x8d, 0xb0, 0x1a, 0x63, 0xe8, - 0x52, 0x3e, 0xb8, 0xec, 0x02, 0x25, 0x15, 0x7b, 0x7e, 0x83, 0x17, 0xf5, 0xd8, 0xa5, 0x82, 0x40, - 0xca, 0xcc, 0x80, 0xcd, 0x4f, 0xb8, 0x28, 0xf6, 0x24, 0x15, 0xff, 0x03, 0xc0, 0x9e, 0x3d, 0xaf, - 0xdf, 0x02, 0xf6, 0x6c, 0x69, 0x5e, 0x87, 0x02, 0x23, 0xcc, 0x1c, 0x1a, 0xd4, 0x8c, 0xb4, 0x7a, - 0x4e, 0x6c, 0xb4, 0x4d, 0x11, 0x2b, 0x1b, 0x34, 0xd8, 0x54, 0x3d, 0xc7, 0x47, 0xa9, 0x17, 0xe4, - 0x4e, 0x67, 0x2a, 0x58, 0x96, 0x66, 0x32, 0x66, 0xfe, 0x98, 0x19, 0xae, 0x3d, 0x55, 0x0b, 0x5b, - 0x4a, 0xad, 0xac, 0x57, 0xa4, 0xe5, 0xa9, 0x30, 0xec, 0xdb, 0x53, 0xb4, 0x03, 0x45, 0xc1, 0xbc, - 0x44, 0x03, 0x41, 0xcc, 0xc5, 0xa3, 0xe3, 0x4d, 0xce, 0x7d, 0x5b, 0x5a, 0x3a, 0x53, 0x1d, 0x68, - 0xfc, 0x1b, 0x7d, 0x0d, 0x65, 0x3b, 0x54, 0x05, 0x09, 0x0c, 0xea, 0x3a, 0x6a, 0x51, 0x44, 0x7d, - 0x78, 0x74, 0xbc, 0x79, 0xf7, 0x4d, 0x66, 0xd7, 0x76, 0x1d, 0xcf, 0x64, 0xe3, 0x00, 0xeb, 0xa5, - 0x18, 0xaf, 0xed, 0x3a, 0xe8, 0x10, 0xca, 0x16, 0x99, 0x60, 0xcf, 0xf4, 0x18, 0x87, 0xa7, 0x6a, - 0x69, 0x2b, 0x5b, 0x2b, 0xee, 0xdc, 0x3a, 0x85, 0xe2, 0x5d, 0xe9, 0x7b, 0xdf, 0x36, 0xfd, 0x10, - 0x21, 0x44, 0xa5, 0x7a, 0x29, 0x82, 0x69, 0xbb, 0x0e, 0x45, 0x9f, 0x42, 0x85, 0x13, 0x3e, 0xf6, - 0xec, 0x58, 0xd2, 0x6a, 0x59, 0x88, 0xe7, 0xc6, 0x29, 0xc8, 0x8d, 0xce, 0xee, 0x61, 0xca, 0x5b, - 0xbf, 0xd0, 0x65, 0x56, 0x7a, 0xa3, 0xfa, 0x4b, 0x0e, 0x2e, 0x9c, 0x70, 0xe2, 0xec, 0x8f, 0xbd, - 0x2e, 0xf1, 0x6c, 0x39, 0x52, 0x71, 0x56, 0xe8, 0xc5, 0x78, 0xaf, 0x33, 0x45, 0xef, 0xc2, 0xf9, - 0x94, 0x8b, 0x3b, 0xc2, 0xe2, 0x83, 0x28, 0xeb, 0xe5, 0xc4, 0xc9, 0x1d, 0xe1, 0x93, 0xdc, 0x64, - 0xff, 0x0b, 0x37, 0xdf, 0xc0, 0x95, 0x84, 0x9b, 0x24, 0x09, 0x67, 0x29, 0xb7, 0x28, 0x4b, 0x97, - 0x63, 0xe4, 0xc3, 0x08, 0x98, 0xd3, 0x45, 0x60, 0x2d, 0x25, 0x87, 0xa8, 0x60, 0x9e, 0x71, 0x79, - 0xd1, 0x8c, 0xab, 0x89, 0x2e, 0x24, 0x2e, 0x4f, 0xd8, 0x83, 0xb5, 0x44, 0x1f, 0xa9, 0x7c, 0x54, - 0xcd, 0xbf, 0xa5, 0x50, 0x56, 0x63, 0xa1, 0x24, 0x69, 0x28, 0xb2, 0x60, 0x3d, 0xce, 0x33, 0x37, - 0xca, 0xf0, 0xc4, 0x58, 0x11, 0xc9, 0xae, 0x9f, 0x92, 0x2c, 0x46, 0xdf, 0xf7, 0x7a, 0x44, 0x57, - 0x23, 0xa0, 0xf4, 0xe4, 0xf8, 0x61, 0x51, 0xfd, 0x55, 0x81, 0x4b, 0x27, 0x24, 0xc4, 0x23, 0xce, - 0x50, 0x46, 0xaf, 0x69, 0x23, 0x7b, 0x26, 0x6d, 0xb4, 0xe1, 0x4a, 0x72, 0x59, 0x90, 0x20, 0xb9, - 0x35, 0x28, 0xfa, 0x00, 0x72, 0x36, 0x1e, 0x52, 0x55, 0x79, 0x65, 0xa2, 0xb9, 0xab, 0x46, 0x17, - 0x11, 0xd5, 0x03, 0x58, 0x7f, 0x39, 0xe8, 0xbe, 0x67, 0xe3, 0x29, 0xaa, 0xc3, 0x6a, 0x72, 0x10, - 0x1a, 0x7d, 0x93, 0xf6, 0xc3, 0x8e, 0x78, 0xa2, 0x92, 0x7e, 0x31, 0x3e, 0x12, 0x1f, 0x9a, 0xb4, - 0x2f, 0x8a, 0xfc, 0x49, 0x81, 0xf2, 0x5c, 0x43, 0xe8, 0x01, 0x64, 0x16, 0xbe, 0xce, 0x33, 0xfe, - 0x00, 0x3d, 0x82, 0x2c, 0x17, 0x7c, 0x66, 0x51, 0xc1, 0x73, 0x94, 0xea, 0xf7, 0x0a, 0x5c, 0x3d, - 0x55, 0xab, 0xfc, 0x16, 0xb5, 0xc8, 0xe4, 0x0c, 0x5e, 0x21, 0x16, 0x99, 0xb4, 0x06, 0x5c, 0x67, - 0x66, 0x98, 0x23, 0xfc, 0x84, 0x32, 0x62, 0x78, 0x45, 0x33, 0xce, 0x4b, 0xab, 0xbf, 0x29, 0x70, - 0xb5, 0x8d, 0x87, 0xd8, 0x62, 0xee, 0x04, 0x47, 0x5f, 0xc8, 0x1e, 0x7f, 0x1b, 0x79, 0x16, 0x46, - 0x37, 0xe0, 0xc2, 0x09, 0x16, 0x44, 0x61, 0x05, 0xbd, 0x3c, 0x47, 0x00, 0xd2, 0xa1, 0x10, 0x5f, - 0xb9, 0x0b, 0x3e, 0x00, 0x56, 0xe4, 0x6d, 0x8b, 0x6e, 0xc2, 0xa5, 0x00, 0x73, 0x4d, 0x06, 0xd8, - 0x36, 0x24, 0x3a, 0x0d, 0x9f, 0xb9, 0x25, 0xbd, 0x12, 0x9b, 0x1e, 0x70, 0xf7, 0xf6, 0xe0, 0xbd, - 0x3d, 0xf1, 0xa9, 0x25, 0x32, 0x6a, 0x33, 0x93, 0x8d, 0x29, 0x2a, 0xc2, 0x4a, 0x6b, 0xef, 0xa0, - 0xb9, 0x7f, 0xf0, 0x49, 0x65, 0x09, 0x01, 0xe4, 0xef, 0xef, 0x76, 0xf6, 0x3f, 0xdb, 0xab, 0x28, - 0xa8, 0x04, 0xe7, 0x0e, 0x0f, 0x1a, 0x4f, 0x0f, 0x9a, 0x7b, 0xcd, 0x4a, 0x06, 0xad, 0x40, 0xf6, - 0xfe, 0xc1, 0x17, 0x95, 0x6c, 0xe3, 0xf1, 0xef, 0xcf, 0x37, 0x94, 0x67, 0xcf, 0x37, 0x94, 0xbf, - 0x9f, 0x6f, 0x28, 0x3f, 0xbc, 0xd8, 0x58, 0x7a, 0xf6, 0x62, 0x63, 0xe9, 0xaf, 0x17, 0x1b, 0x4b, - 0x5f, 0xbe, 0xb6, 0x99, 0x69, 0xfa, 0x3f, 0x85, 0xe8, 0xac, 0x9b, 0x17, 0xff, 0x29, 0x6e, 0xff, - 0x13, 0x00, 0x00, 0xff, 0xff, 0xd6, 0xf0, 0x60, 0x55, 0x30, 0x0d, 0x00, 0x00, + 0x59, 0xa1, 0x77, 0xf5, 0xbb, 0x0c, 0xa8, 0xa7, 0x99, 0xfd, 0xdc, 0x65, 0xbd, 0x27, 0x98, 0x99, + 0xa9, 0x59, 0x28, 0x8b, 0x98, 0xc5, 0x1a, 0xe4, 0x65, 0x35, 0x19, 0x51, 0x8d, 0x5c, 0xa1, 0xff, + 0x43, 0x69, 0x4c, 0x98, 0xeb, 0x39, 0x86, 0x4f, 0xbe, 0xc5, 0x81, 0x20, 0x2d, 0xa7, 0x17, 0xc3, + 0xbd, 0x16, 0xdf, 0x7a, 0xc5, 0x28, 0x72, 0x6f, 0x3c, 0x8a, 0xe5, 0x33, 0x46, 0xf1, 0x63, 0x1e, + 0xca, 0x8d, 0xc3, 0xdd, 0x26, 0x1e, 0x60, 0xc7, 0x64, 0xff, 0xd6, 0x92, 0x32, 0x87, 0x96, 0x32, + 0x0b, 0xd4, 0x52, 0xf6, 0x6d, 0xb4, 0xf4, 0x15, 0x9c, 0xef, 0xfa, 0x46, 0x58, 0x8d, 0x31, 0x70, + 0x29, 0x1f, 0x5c, 0x76, 0x8e, 0x92, 0x8a, 0x5d, 0xbf, 0xc1, 0x8b, 0x7a, 0xec, 0x52, 0x41, 0x20, + 0x65, 0x66, 0xc0, 0x66, 0x27, 0x5c, 0x14, 0x7b, 0x92, 0x8a, 0xff, 0x01, 0x60, 0xcf, 0x9e, 0xd5, + 0x6f, 0x01, 0x7b, 0xb6, 0x34, 0xaf, 0x43, 0x81, 0x11, 0x66, 0x0e, 0x0c, 0x6a, 0x46, 0x5a, 0x3d, + 0x27, 0x36, 0xda, 0xa6, 0x88, 0x95, 0x0d, 0x1a, 0x6c, 0xa2, 0x9e, 0xe3, 0xa3, 0xd4, 0x0b, 0x72, + 0xe7, 0x70, 0x22, 0x58, 0x96, 0x66, 0x32, 0x62, 0xfe, 0x88, 0x19, 0xae, 0x3d, 0x51, 0x0b, 0x5b, + 0x4a, 0xad, 0xac, 0x57, 0xa4, 0xe5, 0xa9, 0x30, 0xec, 0xdb, 0x13, 0xb4, 0x03, 0x45, 0xc1, 0xbc, + 0x44, 0x03, 0x41, 0xcc, 0xc5, 0xe3, 0x93, 0x4d, 0xce, 0x7d, 0x5b, 0x5a, 0x0e, 0x27, 0x3a, 0xd0, + 0xf8, 0x37, 0xfa, 0x1a, 0xca, 0x76, 0xa8, 0x0a, 0x12, 0x18, 0xd4, 0x75, 0xd4, 0xa2, 0x88, 0xfa, + 0xf0, 0xf8, 0x64, 0xf3, 0xee, 0x9b, 0xcc, 0xae, 0xed, 0x3a, 0x9e, 0xc9, 0x46, 0x01, 0xd6, 0x4b, + 0x31, 0x5e, 0xdb, 0x75, 0xd0, 0x11, 0x94, 0x2d, 0x32, 0xc6, 0x9e, 0xe9, 0x31, 0x0e, 0x4f, 0xd5, + 0xd2, 0x56, 0xb6, 0x56, 0xdc, 0xb9, 0x75, 0x06, 0xc5, 0xbb, 0xd2, 0xf7, 0xbe, 0x6d, 0xfa, 0x21, + 0x42, 0x88, 0x4a, 0xf5, 0x52, 0x04, 0xd3, 0x76, 0x1d, 0x8a, 0x3e, 0x85, 0x0a, 0x27, 0x7c, 0xe4, + 0xd9, 0xb1, 0xa4, 0xd5, 0xb2, 0x10, 0xcf, 0x8d, 0x33, 0x90, 0x1b, 0x87, 0xbb, 0x47, 0x29, 0x6f, + 0xfd, 0x42, 0x87, 0x59, 0xe9, 0x8d, 0xea, 0xaf, 0x39, 0xb8, 0x70, 0xca, 0x89, 0xb3, 0x3f, 0xf2, + 0x3a, 0xc4, 0xb3, 0xe5, 0x48, 0xc5, 0x59, 0xa1, 0x17, 0xe3, 0xbd, 0xc3, 0x09, 0x7a, 0x17, 0xce, + 0xa7, 0x5c, 0xdc, 0x21, 0x16, 0x1f, 0x44, 0x59, 0x2f, 0x27, 0x4e, 0xee, 0x10, 0x9f, 0xe6, 0x26, + 0xfb, 0x5f, 0xb8, 0xf9, 0x06, 0xae, 0x24, 0xdc, 0x24, 0x49, 0x38, 0x4b, 0xb9, 0x79, 0x59, 0xba, + 0x1c, 0x23, 0x1f, 0x45, 0xc0, 0x9c, 0x2e, 0x02, 0x6b, 0x29, 0x39, 0x44, 0x05, 0xf3, 0x8c, 0xcb, + 0xf3, 0x66, 0x5c, 0x4d, 0x74, 0x21, 0x71, 0x79, 0xc2, 0x2e, 0xac, 0x25, 0xfa, 0x48, 0xe5, 0xa3, + 0x6a, 0xfe, 0x2d, 0x85, 0xb2, 0x1a, 0x0b, 0x25, 0x49, 0x43, 0x91, 0x05, 0xeb, 0x71, 0x9e, 0x99, + 0x51, 0x86, 0x27, 0xc6, 0x8a, 0x48, 0x76, 0xfd, 0x8c, 0x64, 0x31, 0xfa, 0xbe, 0xd7, 0x25, 0xba, + 0x1a, 0x01, 0xa5, 0x27, 0xc7, 0x0f, 0x8b, 0xea, 0xcf, 0x19, 0xb8, 0x74, 0x4a, 0x42, 0x3c, 0x62, + 0x81, 0x32, 0x7a, 0x4d, 0x1b, 0xd9, 0x45, 0xb4, 0xf1, 0x0a, 0x4e, 0x72, 0x8b, 0xe4, 0xa4, 0xda, + 0x86, 0x2b, 0xc9, 0xa5, 0x44, 0x82, 0xe4, 0x76, 0xa2, 0xe8, 0x03, 0xc8, 0xd9, 0x78, 0x40, 0x55, + 0xe5, 0x95, 0x0d, 0xcd, 0x5c, 0x69, 0xba, 0x88, 0xa8, 0x1e, 0xc0, 0xfa, 0xcb, 0x41, 0xf7, 0x3d, + 0x1b, 0x4f, 0x50, 0x1d, 0x56, 0x93, 0x03, 0xd7, 0xe8, 0x99, 0xb4, 0x17, 0x4e, 0x8e, 0x27, 0x2a, + 0xe9, 0x17, 0xe3, 0xa3, 0xf7, 0xa1, 0x49, 0x7b, 0x82, 0xd3, 0x9f, 0x14, 0x28, 0xcf, 0x0c, 0x0e, + 0x3d, 0x80, 0xcc, 0xdc, 0xcf, 0x86, 0x8c, 0xdf, 0x47, 0x8f, 0x20, 0xcb, 0x3f, 0xac, 0xcc, 0xbc, + 0x1f, 0x16, 0x47, 0xa9, 0x7e, 0xaf, 0xc0, 0xd5, 0x33, 0xe7, 0xcf, 0x6f, 0x6b, 0x8b, 0x8c, 0x17, + 0xf0, 0xda, 0xb1, 0xc8, 0xb8, 0xd5, 0xe7, 0x7a, 0x36, 0xc3, 0x1c, 0xa1, 0x2c, 0x32, 0x62, 0x78, + 0x45, 0x33, 0xce, 0x4b, 0xab, 0xbf, 0x29, 0x70, 0xb5, 0x8d, 0x07, 0xd8, 0x62, 0xee, 0x18, 0x47, + 0xac, 0xef, 0xf1, 0x37, 0x98, 0x67, 0x61, 0x74, 0x03, 0x2e, 0x9c, 0x62, 0x41, 0x14, 0x56, 0xd0, + 0xcb, 0x33, 0x04, 0x20, 0x1d, 0x0a, 0xf1, 0xd5, 0x3e, 0xe7, 0x43, 0x63, 0x45, 0xde, 0xea, 0xe8, + 0x26, 0x5c, 0x0a, 0x30, 0xd7, 0x63, 0x80, 0x6d, 0x43, 0xa2, 0xd3, 0xf0, 0x39, 0x5d, 0xd2, 0x2b, + 0xb1, 0xe9, 0x01, 0x77, 0x6f, 0xf7, 0xdf, 0xdb, 0x13, 0x9f, 0x74, 0x22, 0xa3, 0x36, 0x33, 0xd9, + 0x88, 0xa2, 0x22, 0xac, 0xb4, 0xf6, 0x0e, 0x9a, 0xfb, 0x07, 0x9f, 0x54, 0x96, 0x10, 0x40, 0xfe, + 0xfe, 0xee, 0xe1, 0xfe, 0x67, 0x7b, 0x15, 0x05, 0x95, 0xe0, 0xdc, 0xd1, 0x41, 0xe3, 0xe9, 0x41, + 0x73, 0xaf, 0x59, 0xc9, 0xa0, 0x15, 0xc8, 0xde, 0x3f, 0xf8, 0xa2, 0x92, 0x6d, 0x3c, 0xfe, 0xfd, + 0xf9, 0x86, 0xf2, 0xec, 0xf9, 0x86, 0xf2, 0xf7, 0xf3, 0x0d, 0xe5, 0x87, 0x17, 0x1b, 0x4b, 0xcf, + 0x5e, 0x6c, 0x2c, 0xfd, 0xf5, 0x62, 0x63, 0xe9, 0xcb, 0xd7, 0x36, 0x33, 0x49, 0xff, 0x77, 0x11, + 0x9d, 0x75, 0xf2, 0xe2, 0xbf, 0xcb, 0xed, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x4e, 0xa7, + 0xb4, 0x98, 0x0d, 0x00, 0x00, } func (m *FinalityProvider) Marshal() (dAtA []byte, err error) { @@ -1300,6 +1312,20 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.CovenantSlashingSigs) > 0 { + for iNdEx := len(m.CovenantSlashingSigs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantSlashingSigs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBtcstaking(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } if len(m.CovenantUnbondingSigList) > 0 { for iNdEx := len(m.CovenantUnbondingSigList) - 1; iNdEx >= 0; iNdEx-- { { @@ -1729,6 +1755,12 @@ func (m *BTCUndelegationInfo) Size() (n int) { n += 1 + l + sovBtcstaking(uint64(l)) } } + if len(m.CovenantSlashingSigs) > 0 { + for _, e := range m.CovenantSlashingSigs { + l = e.Size() + n += 1 + l + sovBtcstaking(uint64(l)) + } + } return n } @@ -3087,6 +3119,40 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSigs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBtcstaking + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBtcstaking + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CovenantSlashingSigs = append(m.CovenantSlashingSigs, &CovenantAdaptorSignatures{}) + if err := m.CovenantSlashingSigs[len(m.CovenantSlashingSigs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBtcstaking(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 05db57f83..3b5d59719 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -1032,11 +1032,15 @@ type QueryBTCDelegationResponse struct { TotalSat uint64 `protobuf:"varint,5,opt,name=total_sat,json=totalSat,proto3" json:"total_sat,omitempty"` // staking_tx_hex is the hex string of staking tx StakingTxHex string `protobuf:"bytes,6,opt,name=staking_tx_hex,json=stakingTxHex,proto3" json:"staking_tx_hex,omitempty"` + // covenant_sigs is a list of adaptor signatures on the slashing tx + // by each covenant member + // It will be a part of the witness for the staking tx output. + CovenantSigs []*CovenantAdaptorSignatures `protobuf:"bytes,7,rep,name=covenant_sigs,json=covenantSigs,proto3" json:"covenant_sigs,omitempty"` // whether this delegation is active - Active bool `protobuf:"varint,7,opt,name=active,proto3" json:"active,omitempty"` + Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` // undelegation_info is the undelegation info of this delegation. It is nil // if it is not undelegating - UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,8,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` + UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,9,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` } func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationResponse{} } @@ -1100,6 +1104,13 @@ func (m *QueryBTCDelegationResponse) GetStakingTxHex() string { return "" } +func (m *QueryBTCDelegationResponse) GetCovenantSigs() []*CovenantAdaptorSignatures { + if m != nil { + return m.CovenantSigs + } + return nil +} + func (m *QueryBTCDelegationResponse) GetActive() bool { if m != nil { return m.Active @@ -1140,86 +1151,89 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1262 bytes of a gzipped FileDescriptorProto + // 1305 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0x45, - 0x1c, 0xce, 0xe4, 0x61, 0x92, 0x5f, 0xd2, 0x3c, 0xa6, 0x05, 0x5c, 0x27, 0x76, 0x92, 0x55, 0xc9, - 0xab, 0xb0, 0x5b, 0x3b, 0xa1, 0x07, 0x40, 0x4d, 0xe3, 0x94, 0x26, 0x7d, 0x44, 0x98, 0x4d, 0xa0, - 0x12, 0x48, 0x58, 0x63, 0x67, 0xbc, 0x5e, 0xe2, 0xec, 0xba, 0xde, 0xb1, 0x71, 0x54, 0xe5, 0xc2, - 0x81, 0x1b, 0x12, 0x12, 0xfc, 0x0f, 0x20, 0x71, 0x44, 0x5c, 0x38, 0x20, 0x71, 0xeb, 0xb1, 0x82, - 0x03, 0x88, 0x43, 0x41, 0x09, 0xe2, 0x80, 0x84, 0xb8, 0x71, 0x46, 0x9e, 0x1d, 0xe3, 0xb5, 0x77, - 0xd7, 0xd9, 0x4d, 0xd2, 0x5b, 0xbd, 0xf3, 0x7b, 0x7d, 0xdf, 0x7c, 0xfb, 0xed, 0xaf, 0x81, 0xd9, - 0x1c, 0xc9, 0x1d, 0x94, 0x4c, 0x43, 0xc9, 0xb1, 0xbc, 0xc5, 0xc8, 0x9e, 0x6e, 0x68, 0x4a, 0x2d, - 0xa9, 0x3c, 0xac, 0xd2, 0xca, 0x81, 0x5c, 0xae, 0x98, 0xcc, 0xc4, 0xcf, 0x8b, 0x10, 0xb9, 0x15, - 0x22, 0xd7, 0x92, 0xb1, 0x4b, 0x9a, 0xa9, 0x99, 0x3c, 0x42, 0x69, 0xfc, 0xcb, 0x0e, 0x8e, 0x4d, - 0x69, 0xa6, 0xa9, 0x95, 0xa8, 0x42, 0xca, 0xba, 0x42, 0x0c, 0xc3, 0x64, 0x84, 0xe9, 0xa6, 0x61, - 0x89, 0xd3, 0xa5, 0xbc, 0x69, 0xed, 0x9b, 0x96, 0x92, 0x23, 0x16, 0xb5, 0x7b, 0x28, 0xb5, 0x64, - 0x8e, 0x32, 0x92, 0x54, 0xca, 0x44, 0xd3, 0x0d, 0x1e, 0x2c, 0x62, 0x25, 0xef, 0xc9, 0xca, 0xa4, - 0x42, 0xf6, 0x9b, 0xf5, 0xe6, 0xbc, 0x63, 0x1c, 0x83, 0xf2, 0x38, 0xe9, 0x12, 0xe0, 0xb7, 0x1b, - 0xdd, 0x32, 0x3c, 0x59, 0xa5, 0x0f, 0xab, 0xd4, 0x62, 0x92, 0x0a, 0x17, 0xdb, 0x9e, 0x5a, 0x65, - 0xd3, 0xb0, 0x28, 0x7e, 0x1d, 0x22, 0x76, 0x93, 0x28, 0x9a, 0x41, 0x0b, 0xc3, 0xa9, 0xb8, 0xec, - 0x49, 0x80, 0x6c, 0xa7, 0xa5, 0xfb, 0x1f, 0x3f, 0x9d, 0xee, 0x51, 0x45, 0x8a, 0xa4, 0x41, 0x9c, - 0xd7, 0xbc, 0xad, 0x1b, 0xa4, 0xa4, 0xb3, 0x83, 0x4c, 0xc5, 0xac, 0xe9, 0xbb, 0xb4, 0xd2, 0x6c, - 0x8a, 0x6f, 0x03, 0xb4, 0xa0, 0x8a, 0x0e, 0x73, 0xb2, 0xcd, 0x8b, 0xdc, 0xe0, 0x45, 0xb6, 0xb9, - 0x17, 0xbc, 0xc8, 0x19, 0xa2, 0x51, 0x91, 0xab, 0x3a, 0x32, 0xa5, 0x1f, 0x10, 0x24, 0xfc, 0x3a, - 0x09, 0x20, 0xef, 0x02, 0x2e, 0x88, 0xc3, 0x6c, 0xb9, 0x79, 0x1a, 0x45, 0x33, 0x7d, 0x0b, 0xc3, - 0xa9, 0x79, 0x1f, 0x50, 0x9d, 0xd5, 0xd4, 0x89, 0x42, 0x67, 0x7d, 0xbc, 0xd1, 0x06, 0xa1, 0x97, - 0x43, 0x98, 0x3f, 0x11, 0x82, 0x3d, 0x54, 0x1b, 0x86, 0x35, 0x98, 0xf2, 0x84, 0xd0, 0xe4, 0x6a, - 0x16, 0x2e, 0x14, 0xca, 0xd9, 0x1c, 0xcb, 0x67, 0xcb, 0x7b, 0xd9, 0x22, 0xad, 0x73, 0xba, 0x86, - 0x54, 0x28, 0x94, 0xd3, 0x2c, 0x9f, 0xd9, 0xdb, 0xa4, 0x75, 0xa9, 0xea, 0xc3, 0xf7, 0xff, 0x24, - 0xec, 0xc0, 0x84, 0x8b, 0x04, 0x41, 0x7b, 0x60, 0x0e, 0xc6, 0x3b, 0x39, 0x90, 0xbe, 0x42, 0x10, - 0xe3, 0x7d, 0xd3, 0x3b, 0xeb, 0xb7, 0x68, 0x89, 0x6a, 0xb6, 0xcc, 0x9b, 0x83, 0xa7, 0x21, 0x62, - 0x31, 0xc2, 0xaa, 0xb6, 0x84, 0x46, 0x53, 0x4b, 0x3e, 0x9d, 0xda, 0xb2, 0xb7, 0x79, 0x86, 0x2a, - 0x32, 0x3b, 0x84, 0xd2, 0x7b, 0x6a, 0xa1, 0x7c, 0x8b, 0x60, 0xd2, 0x73, 0x54, 0x41, 0xd0, 0x16, - 0x8c, 0x35, 0x18, 0xde, 0x6d, 0x1d, 0x09, 0x89, 0x5c, 0x09, 0x32, 0xb4, 0x3a, 0x9a, 0x63, 0x79, - 0x47, 0xd9, 0xf3, 0x13, 0x47, 0x01, 0x16, 0x3d, 0x6f, 0x36, 0x63, 0x7e, 0x44, 0x2b, 0x6b, 0x6c, - 0x93, 0xea, 0x5a, 0x91, 0x05, 0x57, 0x0a, 0x7e, 0x01, 0x22, 0x45, 0x9e, 0xc3, 0x87, 0xea, 0x57, - 0xc5, 0x2f, 0xe9, 0x2d, 0x58, 0x0a, 0xd2, 0x47, 0xb0, 0x35, 0x0b, 0x23, 0x35, 0x93, 0xe9, 0x86, - 0x96, 0x2d, 0x37, 0xce, 0x79, 0x9f, 0x7e, 0x75, 0xd8, 0x7e, 0xc6, 0x53, 0xa4, 0x2d, 0x58, 0xf0, - 0x2c, 0xb8, 0x5e, 0xad, 0x54, 0xa8, 0xc1, 0x78, 0x50, 0x08, 0x85, 0xfb, 0xf1, 0xd0, 0x5e, 0x4e, - 0x8c, 0xd7, 0x02, 0x89, 0x9c, 0x20, 0x5d, 0x63, 0xf7, 0xba, 0xc7, 0xfe, 0x14, 0xc1, 0x55, 0xde, - 0x68, 0x2d, 0xcf, 0xf4, 0x1a, 0x75, 0xd9, 0x4a, 0x27, 0xe5, 0x7e, 0xad, 0xce, 0x4b, 0xb7, 0x3f, - 0x23, 0x78, 0x39, 0xd8, 0x3c, 0x02, 0xfb, 0x07, 0x5d, 0xec, 0x4e, 0x09, 0xf8, 0xaa, 0x3f, 0xd0, - 0x59, 0x71, 0x8b, 0x32, 0xf2, 0x4c, 0x6d, 0x2f, 0x2e, 0x5e, 0x48, 0x0e, 0x8c, 0x30, 0xba, 0xdb, - 0x46, 0xac, 0x74, 0x5d, 0xb8, 0xa2, 0xeb, 0xb8, 0xfb, 0x1d, 0x4b, 0x5f, 0x20, 0x98, 0xf7, 0x54, - 0x8a, 0x87, 0x41, 0x05, 0x78, 0x5f, 0xce, 0xeb, 0x1e, 0x7f, 0x43, 0x3e, 0xef, 0x83, 0x97, 0x19, - 0x7d, 0x08, 0x97, 0x1d, 0x66, 0x64, 0x56, 0x3c, 0x6c, 0x49, 0x3e, 0xd1, 0x96, 0xcc, 0xb6, 0xd2, - 0x2f, 0xb6, 0x0c, 0xaa, 0xed, 0xe0, 0xfc, 0xee, 0xf3, 0x2e, 0x5c, 0x76, 0x1b, 0x6c, 0x93, 0xe9, - 0x57, 0xe0, 0xa2, 0x18, 0x32, 0xcb, 0xea, 0xd9, 0x22, 0xb1, 0x8a, 0x0e, 0xbe, 0xc7, 0xc5, 0xd1, - 0x4e, 0x7d, 0x93, 0x58, 0xc5, 0xc6, 0xdb, 0xfe, 0x7d, 0x9f, 0xd7, 0x87, 0xc5, 0x61, 0xd6, 0x11, - 0xfb, 0xd2, 0x78, 0x81, 0x91, 0xf4, 0xf5, 0x5f, 0x9f, 0x4e, 0xa7, 0x34, 0x9d, 0x15, 0xab, 0x39, - 0x39, 0x6f, 0xee, 0x2b, 0x82, 0x9a, 0x7c, 0x91, 0xe8, 0x46, 0xf3, 0x87, 0xc2, 0x0e, 0xca, 0xd4, - 0x92, 0xd3, 0x77, 0x32, 0xcb, 0x2b, 0xd7, 0x32, 0xd5, 0xdc, 0x3d, 0x7a, 0xa0, 0x0e, 0xe4, 0x1a, - 0xd7, 0x8c, 0xdf, 0x87, 0xd1, 0x96, 0x0c, 0x4a, 0xba, 0xd5, 0xf0, 0xc6, 0xbe, 0x33, 0x94, 0x1d, - 0x16, 0xfa, 0xb9, 0xaf, 0x73, 0x8d, 0x8d, 0x58, 0x8c, 0x54, 0x58, 0x56, 0xa8, 0xb5, 0xcf, 0xf6, - 0x1c, 0xfe, 0xcc, 0x96, 0x34, 0x8e, 0x03, 0x50, 0x63, 0xb7, 0x19, 0xd0, 0xcf, 0x03, 0x86, 0xa8, - 0x21, 0x14, 0x8f, 0x27, 0x61, 0x88, 0x99, 0x8c, 0x94, 0xb2, 0x16, 0x61, 0xd1, 0x01, 0x7e, 0x3a, - 0xc8, 0x1f, 0x6c, 0x13, 0x86, 0xaf, 0xc0, 0xa8, 0x93, 0x58, 0x5a, 0x8f, 0x46, 0x38, 0xa7, 0x23, - 0x2d, 0x4e, 0x6d, 0xd7, 0x27, 0xdc, 0x3f, 0xa2, 0xcf, 0xcd, 0xa0, 0x85, 0x41, 0x55, 0xfc, 0xc2, - 0x0f, 0x60, 0xa2, 0x6a, 0xb4, 0xc4, 0x95, 0xd5, 0x8d, 0x82, 0x19, 0x1d, 0xe4, 0x1a, 0xe8, 0xf2, - 0xb1, 0x7e, 0xc7, 0x91, 0x72, 0xc7, 0x28, 0x98, 0xea, 0x78, 0xb5, 0xe3, 0x49, 0xea, 0x9f, 0x31, - 0x18, 0xe0, 0x17, 0x88, 0x3f, 0x41, 0x10, 0xb1, 0x77, 0x44, 0xbc, 0xe8, 0x53, 0xd2, 0xbd, 0x94, - 0xc6, 0x96, 0x82, 0x84, 0xda, 0x6a, 0x90, 0x5e, 0xfa, 0xf8, 0xa7, 0x3f, 0x3e, 0xef, 0x9d, 0xc6, - 0x71, 0xa5, 0xdb, 0xae, 0x8c, 0xbf, 0x41, 0x30, 0xe1, 0xb2, 0x4f, 0xbc, 0xd2, 0xad, 0x91, 0xdf, - 0xfa, 0x1a, 0x7b, 0x35, 0x64, 0x96, 0x98, 0x34, 0xc9, 0x27, 0xbd, 0x8a, 0x17, 0x7d, 0x26, 0x75, - 0x1b, 0x37, 0xfe, 0x11, 0xc1, 0x78, 0x67, 0x41, 0xbc, 0x1c, 0xa6, 0x7d, 0x73, 0xe6, 0x95, 0x70, - 0x49, 0x62, 0xe4, 0x6d, 0x3e, 0xf2, 0x16, 0xbe, 0x17, 0x78, 0x64, 0xe5, 0x51, 0x9b, 0xa7, 0x1e, - 0xba, 0x43, 0xf0, 0x97, 0x08, 0x46, 0xdb, 0xf7, 0x30, 0x9c, 0xec, 0x36, 0x9d, 0xe7, 0x7a, 0x19, - 0x4b, 0x85, 0x49, 0x11, 0x70, 0x64, 0x0e, 0x67, 0x01, 0xcf, 0x29, 0xbe, 0xff, 0x67, 0x72, 0x9a, - 0x2d, 0xfe, 0x13, 0xc1, 0xf4, 0x09, 0x5f, 0x5e, 0x9c, 0xee, 0x36, 0x47, 0xb0, 0x35, 0x22, 0xb6, - 0x7e, 0xa6, 0x1a, 0x02, 0xdc, 0x6b, 0x1c, 0xdc, 0x0a, 0x4e, 0x85, 0xb8, 0x2b, 0xdb, 0x74, 0x0e, - 0xf1, 0xbf, 0x08, 0xe2, 0x5d, 0x77, 0x3f, 0x7c, 0x33, 0x8c, 0x7e, 0xbc, 0xd6, 0xd3, 0xd8, 0xda, - 0x19, 0x2a, 0x08, 0x88, 0x19, 0x0e, 0xf1, 0x2e, 0xde, 0x3c, 0xbd, 0x1c, 0xf9, 0xea, 0xd7, 0x02, - 0xfe, 0x17, 0x82, 0xa9, 0x6e, 0x4b, 0x25, 0x5e, 0x0d, 0x33, 0xb5, 0xc7, 0x76, 0x1b, 0xbb, 0x79, - 0xfa, 0x02, 0x02, 0xf5, 0x06, 0x47, 0xbd, 0x86, 0x57, 0xcf, 0x88, 0x1a, 0x7f, 0x8d, 0x60, 0xac, - 0x63, 0xa1, 0xc2, 0xa9, 0x13, 0xa5, 0xe7, 0x5a, 0xce, 0x62, 0xcb, 0xa1, 0x72, 0x04, 0x0a, 0x85, - 0xa3, 0x58, 0xc4, 0xf3, 0x3e, 0x28, 0x48, 0x33, 0x4f, 0x7c, 0x09, 0xf1, 0xdf, 0x08, 0x26, 0xbb, - 0xac, 0x4b, 0xf8, 0x46, 0x18, 0x62, 0x3d, 0x0c, 0x64, 0xf5, 0xd4, 0xf9, 0x02, 0xd1, 0x16, 0x47, - 0xb4, 0x81, 0xdf, 0x3c, 0xfd, 0xbd, 0x38, 0xcd, 0xe6, 0x3b, 0x04, 0x17, 0xda, 0x7c, 0x0b, 0x5f, - 0x0b, 0x6c, 0x71, 0x4d, 0x4c, 0xc9, 0x10, 0x19, 0x02, 0xc5, 0x2d, 0x8e, 0xe2, 0x06, 0x7e, 0x23, - 0x98, 0x27, 0x2a, 0x8f, 0x3c, 0x36, 0xb9, 0xc3, 0xf4, 0xfd, 0xc7, 0x47, 0x09, 0xf4, 0xe4, 0x28, - 0x81, 0x7e, 0x3f, 0x4a, 0xa0, 0xcf, 0x8e, 0x13, 0x3d, 0x4f, 0x8e, 0x13, 0x3d, 0xbf, 0x1c, 0x27, - 0x7a, 0xde, 0x3b, 0x71, 0x85, 0xaa, 0x3b, 0x1b, 0xf2, 0x7d, 0x2a, 0x17, 0xe1, 0x7f, 0xb1, 0x5a, - 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xe1, 0xf1, 0x97, 0x99, 0x13, 0x00, 0x00, + 0x1c, 0xce, 0xe4, 0x61, 0x92, 0xc9, 0x7b, 0x5a, 0xc0, 0x75, 0x62, 0x27, 0x59, 0x95, 0xbc, 0x0a, + 0xbb, 0xb1, 0x13, 0x7a, 0x00, 0xd4, 0xd4, 0x4e, 0x69, 0xd2, 0x47, 0x84, 0xd9, 0xa4, 0x54, 0x02, + 0x09, 0x6b, 0xec, 0x8c, 0xd7, 0x4b, 0x9c, 0x9d, 0xed, 0xee, 0xd8, 0x38, 0xaa, 0x72, 0xe1, 0xc0, + 0x0d, 0x09, 0x09, 0xf8, 0x1b, 0x40, 0xe2, 0x88, 0xb8, 0x70, 0xe3, 0xd6, 0x63, 0x05, 0x07, 0x10, + 0x87, 0x82, 0x12, 0xc4, 0x01, 0x09, 0x71, 0xe3, 0x8c, 0x3c, 0x3b, 0x8b, 0xd7, 0xf6, 0xae, 0xb3, + 0x4e, 0xc2, 0xad, 0xde, 0xf9, 0xbd, 0xbe, 0x6f, 0xbe, 0xfd, 0xf6, 0xd7, 0xc0, 0xb9, 0x3c, 0xce, + 0x1f, 0x96, 0xa9, 0xa1, 0xe4, 0x59, 0xc1, 0x66, 0x78, 0x5f, 0x37, 0x34, 0xa5, 0x9a, 0x54, 0x1e, + 0x55, 0x88, 0x75, 0x28, 0x9b, 0x16, 0x65, 0x14, 0x3d, 0x2f, 0x42, 0xe4, 0x46, 0x88, 0x5c, 0x4d, + 0xc6, 0x2e, 0x6b, 0x54, 0xa3, 0x3c, 0x42, 0xa9, 0xff, 0xcb, 0x09, 0x8e, 0x4d, 0x6b, 0x94, 0x6a, + 0x65, 0xa2, 0x60, 0x53, 0x57, 0xb0, 0x61, 0x50, 0x86, 0x99, 0x4e, 0x0d, 0x5b, 0x9c, 0x2e, 0x17, + 0xa8, 0x7d, 0x40, 0x6d, 0x25, 0x8f, 0x6d, 0xe2, 0xf4, 0x50, 0xaa, 0xc9, 0x3c, 0x61, 0x38, 0xa9, + 0x98, 0x58, 0xd3, 0x0d, 0x1e, 0x2c, 0x62, 0x25, 0xff, 0xc9, 0x4c, 0x6c, 0xe1, 0x03, 0xb7, 0xde, + 0xbc, 0x7f, 0x8c, 0x67, 0x50, 0x1e, 0x27, 0x5d, 0x86, 0xe8, 0xed, 0x7a, 0xb7, 0x2c, 0x4f, 0x56, + 0xc9, 0xa3, 0x0a, 0xb1, 0x99, 0xa4, 0xc2, 0x4b, 0x4d, 0x4f, 0x6d, 0x93, 0x1a, 0x36, 0x41, 0xaf, + 0xc3, 0x88, 0xd3, 0x24, 0x0a, 0x66, 0xc1, 0xe2, 0x70, 0x2a, 0x2e, 0xfb, 0x12, 0x20, 0x3b, 0x69, + 0x99, 0xfe, 0x27, 0xcf, 0x66, 0x7a, 0x54, 0x91, 0x22, 0x69, 0x30, 0xce, 0x6b, 0xde, 0xd6, 0x0d, + 0x5c, 0xd6, 0xd9, 0x61, 0xd6, 0xa2, 0x55, 0x7d, 0x8f, 0x58, 0x6e, 0x53, 0x74, 0x1b, 0xc2, 0x06, + 0x54, 0xd1, 0x61, 0x5e, 0x76, 0x78, 0x91, 0xeb, 0xbc, 0xc8, 0x0e, 0xf7, 0x82, 0x17, 0x39, 0x8b, + 0x35, 0x22, 0x72, 0x55, 0x4f, 0xa6, 0xf4, 0x3d, 0x80, 0x89, 0xa0, 0x4e, 0x02, 0xc8, 0x3b, 0x10, + 0x15, 0xc5, 0x61, 0xce, 0x74, 0x4f, 0xa3, 0x60, 0xb6, 0x6f, 0x71, 0x38, 0xb5, 0x10, 0x00, 0xaa, + 0xb5, 0x9a, 0x3a, 0x59, 0x6c, 0xad, 0x8f, 0x36, 0x9b, 0x20, 0xf4, 0x72, 0x08, 0x0b, 0xa7, 0x42, + 0x70, 0x86, 0x6a, 0xc2, 0x90, 0x86, 0xd3, 0xbe, 0x10, 0x5c, 0xae, 0xe6, 0xe0, 0x68, 0xd1, 0xcc, + 0xe5, 0x59, 0x21, 0x67, 0xee, 0xe7, 0x4a, 0xa4, 0xc6, 0xe9, 0x1a, 0x52, 0x61, 0xd1, 0xcc, 0xb0, + 0x42, 0x76, 0x7f, 0x8b, 0xd4, 0xa4, 0x4a, 0x00, 0xdf, 0xff, 0x91, 0xb0, 0x0b, 0x27, 0xdb, 0x48, + 0x10, 0xb4, 0x87, 0xe6, 0x60, 0xa2, 0x95, 0x03, 0xe9, 0x2b, 0x00, 0x63, 0xbc, 0x6f, 0x66, 0x77, + 0xe3, 0x16, 0x29, 0x13, 0xcd, 0x91, 0xb9, 0x3b, 0x78, 0x06, 0x46, 0x6c, 0x86, 0x59, 0xc5, 0x91, + 0xd0, 0x58, 0x6a, 0x39, 0xa0, 0x53, 0x53, 0xf6, 0x0e, 0xcf, 0x50, 0x45, 0x66, 0x8b, 0x50, 0x7a, + 0xcf, 0x2c, 0x94, 0x6f, 0x01, 0x9c, 0xf2, 0x1d, 0x55, 0x10, 0xb4, 0x0d, 0xc7, 0xeb, 0x0c, 0xef, + 0x35, 0x8e, 0x84, 0x44, 0xae, 0x86, 0x19, 0x5a, 0x1d, 0xcb, 0xb3, 0x82, 0xa7, 0xec, 0xc5, 0x89, + 0xa3, 0x08, 0x97, 0x7c, 0x6f, 0x36, 0x4b, 0x3f, 0x24, 0x56, 0x9a, 0x6d, 0x11, 0x5d, 0x2b, 0xb1, + 0xf0, 0x4a, 0x41, 0x2f, 0xc0, 0x48, 0x89, 0xe7, 0xf0, 0xa1, 0xfa, 0x55, 0xf1, 0x4b, 0x7a, 0x0b, + 0x2e, 0x87, 0xe9, 0x23, 0xd8, 0x9a, 0x83, 0x23, 0x55, 0xca, 0x74, 0x43, 0xcb, 0x99, 0xf5, 0x73, + 0xde, 0xa7, 0x5f, 0x1d, 0x76, 0x9e, 0xf1, 0x14, 0x69, 0x1b, 0x2e, 0xfa, 0x16, 0xdc, 0xa8, 0x58, + 0x16, 0x31, 0x18, 0x0f, 0xea, 0x42, 0xe1, 0x41, 0x3c, 0x34, 0x97, 0x13, 0xe3, 0x35, 0x40, 0x02, + 0x2f, 0xc8, 0xb6, 0xb1, 0x7b, 0xdb, 0xc7, 0xfe, 0x04, 0xc0, 0x6b, 0xbc, 0x51, 0xba, 0xc0, 0xf4, + 0x2a, 0x69, 0xb3, 0x95, 0x56, 0xca, 0x83, 0x5a, 0x5d, 0x94, 0x6e, 0x7f, 0x02, 0xf0, 0xe5, 0x70, + 0xf3, 0x08, 0xec, 0xef, 0x77, 0xb0, 0x3b, 0x25, 0xe4, 0xab, 0xfe, 0x50, 0x67, 0xa5, 0x6d, 0xc2, + 0xf0, 0xff, 0x6a, 0x7b, 0x71, 0xf1, 0x42, 0x72, 0x60, 0x98, 0x91, 0xbd, 0x26, 0x62, 0xa5, 0xeb, + 0xc2, 0x15, 0xdb, 0x8e, 0x3b, 0xdf, 0xb1, 0xf4, 0x39, 0x80, 0x0b, 0xbe, 0x4a, 0xf1, 0x31, 0xa8, + 0x10, 0xef, 0xcb, 0x45, 0xdd, 0xe3, 0xaf, 0x20, 0xe0, 0x7d, 0xf0, 0x33, 0xa3, 0x0f, 0xe0, 0x15, + 0x8f, 0x19, 0x51, 0xcb, 0xc7, 0x96, 0xe4, 0x53, 0x6d, 0x89, 0x36, 0x95, 0x7e, 0xb1, 0x61, 0x50, + 0x4d, 0x07, 0x17, 0x77, 0x9f, 0x77, 0xe1, 0x95, 0x76, 0x83, 0x75, 0x99, 0x7e, 0x05, 0x5e, 0x12, + 0x43, 0xe6, 0x58, 0x2d, 0x57, 0xc2, 0x76, 0xc9, 0xc3, 0xf7, 0x84, 0x38, 0xda, 0xad, 0x6d, 0x61, + 0xbb, 0x54, 0x7f, 0xdb, 0xbf, 0xe8, 0xf7, 0xfb, 0xb0, 0x78, 0xcc, 0x3a, 0xe2, 0x5c, 0x1a, 0x2f, + 0x30, 0x92, 0xb9, 0xfe, 0xcb, 0xb3, 0x99, 0x94, 0xa6, 0xb3, 0x52, 0x25, 0x2f, 0x17, 0xe8, 0x81, + 0x22, 0xa8, 0x29, 0x94, 0xb0, 0x6e, 0xb8, 0x3f, 0x14, 0x76, 0x68, 0x12, 0x5b, 0xce, 0xdc, 0xc9, + 0xae, 0xae, 0xad, 0x64, 0x2b, 0xf9, 0x7b, 0xe4, 0x50, 0x1d, 0xc8, 0xd7, 0xaf, 0x19, 0xbd, 0x07, + 0xc7, 0x1a, 0x32, 0x28, 0xeb, 0x76, 0xdd, 0x1b, 0xfb, 0xce, 0x51, 0x76, 0x58, 0xe8, 0xe7, 0xbe, + 0xce, 0x35, 0x36, 0x62, 0x33, 0x6c, 0xb1, 0x9c, 0x50, 0x6b, 0x9f, 0xe3, 0x39, 0xfc, 0x99, 0x23, + 0x69, 0x14, 0x87, 0x90, 0x18, 0x7b, 0x6e, 0x40, 0x3f, 0x0f, 0x18, 0x22, 0x86, 0x50, 0x3c, 0x9a, + 0x82, 0x43, 0x8c, 0x32, 0x5c, 0xce, 0xd9, 0x98, 0x45, 0x07, 0xf8, 0xe9, 0x20, 0x7f, 0xb0, 0x83, + 0x19, 0xba, 0x0a, 0xc7, 0xbc, 0xc4, 0x92, 0x5a, 0x34, 0xc2, 0x39, 0x1d, 0x69, 0x70, 0x4a, 0x6a, + 0xe8, 0x01, 0x1c, 0x2d, 0xd0, 0x2a, 0x31, 0xb0, 0xc1, 0x72, 0xb6, 0xae, 0xd9, 0xd1, 0xe7, 0xb8, + 0x88, 0x56, 0x02, 0x44, 0xb4, 0x21, 0x62, 0xd3, 0x7b, 0xd8, 0x64, 0xd4, 0xda, 0xd1, 0x35, 0x03, + 0xb3, 0x8a, 0x45, 0x6c, 0x75, 0xc4, 0x2d, 0xb3, 0xa3, 0x6b, 0x76, 0xfd, 0x1d, 0xc4, 0xdc, 0x96, + 0xa2, 0x83, 0xb3, 0x60, 0x71, 0x50, 0x15, 0xbf, 0xd0, 0x43, 0x38, 0x59, 0x31, 0x1a, 0x9a, 0xcd, + 0xe9, 0x46, 0x91, 0x46, 0x87, 0xb8, 0xb4, 0x3a, 0xec, 0x00, 0x0f, 0x3c, 0x29, 0x77, 0x8c, 0x22, + 0x55, 0x27, 0x2a, 0x2d, 0x4f, 0x52, 0x7f, 0x8f, 0xc3, 0x01, 0xae, 0x0b, 0xf4, 0x31, 0x80, 0x11, + 0x67, 0xf5, 0x44, 0x4b, 0x01, 0x25, 0xdb, 0x77, 0xdd, 0xd8, 0x72, 0x98, 0x50, 0x47, 0x64, 0xd2, + 0x4b, 0x1f, 0xfd, 0xf8, 0xfb, 0x67, 0xbd, 0x33, 0x28, 0xae, 0x74, 0x5a, 0xc1, 0xd1, 0x37, 0x00, + 0x4e, 0xb6, 0xb9, 0x32, 0x5a, 0xeb, 0xd4, 0x28, 0x68, 0x2b, 0x8e, 0xbd, 0xda, 0x65, 0x96, 0x98, + 0x34, 0xc9, 0x27, 0xbd, 0x86, 0x96, 0x02, 0x26, 0x6d, 0xff, 0x1e, 0xa0, 0x1f, 0x00, 0x9c, 0x68, + 0x2d, 0x88, 0x56, 0xbb, 0x69, 0xef, 0xce, 0xbc, 0xd6, 0x5d, 0x92, 0x18, 0x79, 0x87, 0x8f, 0xbc, + 0x8d, 0xee, 0x85, 0x1e, 0x59, 0x79, 0xdc, 0x64, 0xd5, 0x47, 0xed, 0x21, 0xe8, 0x4b, 0x00, 0xc7, + 0x9a, 0xd7, 0x3b, 0x94, 0xec, 0x34, 0x9d, 0xef, 0xd6, 0x1a, 0x4b, 0x75, 0x93, 0x22, 0xe0, 0xc8, + 0x1c, 0xce, 0x22, 0x9a, 0x57, 0x02, 0xff, 0x2b, 0xe6, 0xf5, 0x70, 0xf4, 0x07, 0x80, 0x33, 0xa7, + 0x7c, 0xd0, 0x51, 0xa6, 0xd3, 0x1c, 0xe1, 0xb6, 0x93, 0xd8, 0xc6, 0xb9, 0x6a, 0x08, 0x70, 0xaf, + 0x71, 0x70, 0x6b, 0x28, 0xd5, 0xc5, 0x5d, 0x39, 0x5e, 0x76, 0x84, 0xfe, 0x01, 0x30, 0xde, 0x71, + 0xa5, 0x44, 0x37, 0xbb, 0xd1, 0x8f, 0xdf, 0xd6, 0x1b, 0x4b, 0x9f, 0xa3, 0x82, 0x80, 0x98, 0xe5, + 0x10, 0xef, 0xa2, 0xad, 0xb3, 0xcb, 0x91, 0x6f, 0x94, 0x0d, 0xe0, 0x7f, 0x02, 0x38, 0xdd, 0x69, + 0x57, 0x45, 0xeb, 0xdd, 0x4c, 0xed, 0xb3, 0x34, 0xc7, 0x6e, 0x9e, 0xbd, 0x80, 0x40, 0xbd, 0xc9, + 0x51, 0xa7, 0xd1, 0xfa, 0x39, 0x51, 0xa3, 0xaf, 0x01, 0x1c, 0x6f, 0xd9, 0xd3, 0x50, 0xea, 0x54, + 0xe9, 0xb5, 0xed, 0x7c, 0xb1, 0xd5, 0xae, 0x72, 0x04, 0x0a, 0x85, 0xa3, 0x58, 0x42, 0x0b, 0x01, + 0x28, 0xb0, 0x9b, 0x27, 0x3e, 0xb0, 0xe8, 0x2f, 0x00, 0xa7, 0x3a, 0x6c, 0x61, 0xe8, 0x46, 0x37, + 0xc4, 0xfa, 0x18, 0xc8, 0xfa, 0x99, 0xf3, 0x05, 0xa2, 0x6d, 0x8e, 0x68, 0x13, 0xbd, 0x79, 0xf6, + 0x7b, 0xf1, 0x9a, 0xcd, 0x77, 0x00, 0x8e, 0x36, 0xf9, 0x16, 0x5a, 0x09, 0x6d, 0x71, 0x2e, 0xa6, + 0x64, 0x17, 0x19, 0x02, 0xc5, 0x2d, 0x8e, 0xe2, 0x06, 0x7a, 0x23, 0x9c, 0x27, 0x2a, 0x8f, 0x7d, + 0x16, 0xc4, 0xa3, 0xcc, 0xfd, 0x27, 0xc7, 0x09, 0xf0, 0xf4, 0x38, 0x01, 0x7e, 0x3b, 0x4e, 0x80, + 0x4f, 0x4f, 0x12, 0x3d, 0x4f, 0x4f, 0x12, 0x3d, 0x3f, 0x9f, 0x24, 0x7a, 0xde, 0x3d, 0x75, 0x33, + 0xab, 0x79, 0x1b, 0xf2, 0x35, 0x2d, 0x1f, 0xe1, 0x7f, 0x08, 0x5b, 0xfd, 0x37, 0x00, 0x00, 0xff, + 0xff, 0x51, 0xd9, 0xf0, 0x54, 0xf0, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2359,7 +2373,7 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x42 + dAtA[i] = 0x4a } if m.Active { i-- @@ -2369,7 +2383,21 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err dAtA[i] = 0 } i-- - dAtA[i] = 0x38 + dAtA[i] = 0x40 + } + if len(m.CovenantSigs) > 0 { + for iNdEx := len(m.CovenantSigs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CovenantSigs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } } if len(m.StakingTxHex) > 0 { i -= len(m.StakingTxHex) @@ -2736,6 +2764,12 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } + if len(m.CovenantSigs) > 0 { + for _, e := range m.CovenantSigs { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } if m.Active { n += 2 } @@ -4677,6 +4711,40 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { m.StakingTxHex = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CovenantSigs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CovenantSigs = append(m.CovenantSigs, &CovenantAdaptorSignatures{}) + if err := m.CovenantSigs[len(m.CovenantSigs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Active", wireType) } @@ -4696,7 +4764,7 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { } } m.Active = bool(v != 0) - case 8: + case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UndelegationInfo", wireType) } From 79decc6a68666a51b9e75e9c4c8dcff00fdcebd7 Mon Sep 17 00:00:00 2001 From: Cirrus Gai Date: Sun, 24 Dec 2023 23:34:04 +0800 Subject: [PATCH 137/202] chore: Migrate BLS signature to vote extension (#143) --- app/app.go | 140 ++--- app/app_test.go | 7 +- app/encoding.go | 5 +- app/test_helpers.go | 101 ++-- app/utils.go | 34 +- btctxformatter/formatter.go | 8 +- btctxformatter/formatter_test.go | 6 +- cmd/babylond/cmd/add_gen_bls_test.go | 17 +- cmd/babylond/cmd/flags.go | 12 +- cmd/babylond/cmd/genbls_test.go | 2 +- cmd/babylond/cmd/genesis.go | 21 +- cmd/babylond/cmd/root.go | 52 +- cmd/babylond/cmd/testnet.go | 11 +- cmd/babylond/cmd/testnet_test.go | 5 +- cmd/babylond/cmd/validate_genesis_test.go | 10 +- go.mod | 2 +- go.sum | 4 +- privval/file.go | 8 +- proto/babylon/checkpointing/v1/bls_key.proto | 20 +- .../babylon/checkpointing/v1/checkpoint.proto | 23 +- proto/babylon/checkpointing/v1/tx.proto | 23 - proto/babylon/epoching/v1/epoching.proto | 11 +- proto/buf.lock | 4 +- proto/buf.yaml | 2 +- test/e2e/initialization/config.go | 31 +- test/e2e/initialization/node.go | 36 +- testutil/datagen/btc_transaction.go | 13 +- testutil/datagen/epoching.go | 3 +- testutil/datagen/genesiskey.go | 102 +++- testutil/datagen/raw_checkpoint.go | 14 +- testutil/datagen/vote_ext.go | 37 ++ testutil/helper/gen_blocks.go | 440 ++++++++++++++++ testutil/helper/helper.go | 175 ++----- testutil/helper/validator.go | 15 - testutil/keeper/checkpointing.go | 9 +- .../mocks/checkpointing_expected_keepers.go | 31 ++ x/checkpointing/abci.go | 39 +- x/checkpointing/client/cli/tx.go | 49 +- x/checkpointing/keeper/bls_signer.go | 58 +-- x/checkpointing/keeper/grpc_query_bls_test.go | 9 +- .../keeper/grpc_query_checkpoint_test.go | 11 +- x/checkpointing/keeper/keeper.go | 150 +++--- x/checkpointing/keeper/keeper_test.go | 32 +- x/checkpointing/keeper/msg_server.go | 15 - x/checkpointing/keeper/msg_server_test.go | 226 +------- x/checkpointing/keeper/val_bls_set_test.go | 7 +- x/checkpointing/proposal.go | 324 ++++++++++++ x/checkpointing/types/bls_key.pb.go | 484 ++++++++++++++++-- x/checkpointing/types/checkpoint.pb.go | 469 ++++++++++++++--- x/checkpointing/types/codec.go | 1 - x/checkpointing/types/errors.go | 29 +- x/checkpointing/types/expected_keepers.go | 7 +- x/checkpointing/types/msgs.go | 30 +- x/checkpointing/types/tx.pb.go | 439 +--------------- x/checkpointing/types/types.go | 82 ++- x/checkpointing/types/types_test.go | 24 +- x/checkpointing/types/utils.go | 16 +- x/checkpointing/vote_ext.go | 154 ++++++ x/checkpointing/vote_ext_test.go | 131 +++++ x/epoching/abci.go | 13 +- x/epoching/keeper/apphash_chain.go | 3 +- x/epoching/keeper/apphash_chain_test.go | 7 +- x/epoching/keeper/epoch_msg_queue_test.go | 28 +- .../keeper/epoch_slashed_val_set_test.go | 20 +- x/epoching/keeper/epoch_val_set_test.go | 20 +- x/epoching/keeper/epochs.go | 36 +- x/epoching/keeper/epochs_test.go | 9 +- x/epoching/keeper/grpc_query_test.go | 19 +- x/epoching/keeper/msg_server_test.go | 13 +- x/epoching/keeper/staking_functions.go | 5 + x/epoching/types/epoching.go | 13 +- x/epoching/types/epoching.pb.go | 201 +++++--- x/epoching/types/expected_keepers.go | 5 +- x/monitor/keeper/grpc_query_test.go | 9 +- .../keeper/proof_btc_timestamp_test.go | 48 +- x/zoneconcierge/types/btc_timestamp.go | 53 +- x/zoneconcierge/types/btc_timestamp_test.go | 29 +- 77 files changed, 2964 insertions(+), 1787 deletions(-) create mode 100644 testutil/datagen/vote_ext.go create mode 100644 testutil/helper/gen_blocks.go create mode 100644 x/checkpointing/proposal.go create mode 100644 x/checkpointing/vote_ext.go create mode 100644 x/checkpointing/vote_ext_test.go diff --git a/app/app.go b/app/app.go index ea5318c9f..608ffc745 100644 --- a/app/app.go +++ b/app/app.go @@ -24,19 +24,20 @@ import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/babylonchain/babylon/client/docs" - bbn "github.com/babylonchain/babylon/types" - owasm "github.com/babylonchain/babylon/wasmbinding" nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" "github.com/cosmos/cosmos-sdk/x/consensus" consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + "github.com/babylonchain/babylon/client/docs" + bbn "github.com/babylonchain/babylon/types" + owasm "github.com/babylonchain/babylon/wasmbinding" + "cosmossdk.io/log" abci "github.com/cometbft/cometbft/abci/types" tmos "github.com/cometbft/cometbft/libs/os" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" "github.com/spf13/cast" @@ -110,6 +111,9 @@ import ( appparams "github.com/babylonchain/babylon/app/params" storetypes "cosmossdk.io/store/types" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/babylonchain/babylon/x/btccheckpoint" btccheckpointkeeper "github.com/babylonchain/babylon/x/btccheckpoint/keeper" btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" @@ -134,8 +138,6 @@ import ( "github.com/babylonchain/babylon/x/monitor" monitorkeeper "github.com/babylonchain/babylon/x/monitor/keeper" monitortypes "github.com/babylonchain/babylon/x/monitor/types" - govclient "github.com/cosmos/cosmos-sdk/x/gov/client" - govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" @@ -148,11 +150,12 @@ import ( ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + // IBC-related "github.com/babylonchain/babylon/x/zoneconcierge" zckeeper "github.com/babylonchain/babylon/x/zoneconcierge/keeper" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" - ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" ) const ( @@ -174,6 +177,8 @@ const ( // https://medium.com/cosmwasm/cosmwasm-for-ctos-iv-native-integrations-713140bf75fc // suggests 50M as reasonable limits. Me may want to adjust it later. DefaultGasLimit int64 = 50000000 + + DefaultVoteExtensionsEnableHeight = 1 ) var ( @@ -316,12 +321,6 @@ func NewBabylonApp( std.RegisterLegacyAminoCodec(legacyAmino) std.RegisterInterfaces(interfaceRegistry) - bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) - bApp.SetCommitMultiStoreTracer(traceStore) - bApp.SetVersion(version.Version) - bApp.SetInterfaceRegistry(interfaceRegistry) - bApp.SetTxEncoder(txConfig.TxEncoder()) - keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, @@ -347,6 +346,64 @@ func NewBabylonApp( // tokenomics-related modules incentivetypes.StoreKey, ) + accountKeeper := authkeeper.NewAccountKeeper( + appCodec, + runtime.NewKVStoreService(keys[authtypes.StoreKey]), + authtypes.ProtoBaseAccount, + maccPerms, + authcodec.NewBech32Codec(appparams.Bech32PrefixAccAddr), + appparams.Bech32PrefixAccAddr, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + bankKeeper := bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + accountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + + stakingKeeper := stakingkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), + accountKeeper, + bankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), + authcodec.NewBech32Codec(appparams.Bech32PrefixConsAddr), + ) + + // NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module + epochingKeeper := epochingkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[epochingtypes.StoreKey]), + bankKeeper, + stakingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + checkpointingKeeper := checkpointingkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[checkpointingtypes.StoreKey]), + privSigner.WrappedPV, + epochingKeeper, + ) + + voteExtOp := func(bApp *baseapp.BaseApp) { + voteExtHandler := checkpointing.NewVoteExtensionHandler(logger, &checkpointingKeeper) + voteExtHandler.SetHandlers(bApp) + proposalHandler := checkpointing.NewProposalHandler(logger, &checkpointingKeeper, bApp.Mempool(), bApp) + proposalHandler.SetHandlers(bApp) + } + baseAppOptions = append(baseAppOptions, voteExtOp) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) tkeys := storetypes.NewTransientStoreKeys( paramstypes.TStoreKey, btccheckpointtypes.TStoreKey) @@ -403,34 +460,11 @@ func NewBabylonApp( app.CapabilityKeeper.Seal() // add keepers - app.AccountKeeper = authkeeper.NewAccountKeeper( - appCodec, - runtime.NewKVStoreService(keys[authtypes.StoreKey]), - authtypes.ProtoBaseAccount, - maccPerms, - authcodec.NewBech32Codec(appparams.Bech32PrefixAccAddr), - appparams.Bech32PrefixAccAddr, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) + app.AccountKeeper = accountKeeper - app.BankKeeper = bankkeeper.NewBaseKeeper( - appCodec, - runtime.NewKVStoreService(keys[banktypes.StoreKey]), - app.AccountKeeper, - BlockedAddresses(), - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - logger, - ) + app.BankKeeper = bankKeeper - app.StakingKeeper = stakingkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), - app.AccountKeeper, - app.BankKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr), - authcodec.NewBech32Codec(appparams.Bech32PrefixConsAddr), - ) + app.StakingKeeper = stakingKeeper app.CircuitKeeper = circuitkeeper.NewKeeper( appCodec, @@ -460,16 +494,6 @@ func NewBabylonApp( authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - // set up epoching keeper - // NOTE: the epoching module has to be set before the chekpointing module, as the checkpointing module will have access to the epoching module - epochingKeeper := epochingkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[epochingtypes.StoreKey]), - app.BankKeeper, - app.StakingKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - // set up incentive keeper app.IncentiveKeeper = incentivekeeper.NewKeeper( appCodec, @@ -578,13 +602,7 @@ func NewBabylonApp( runtime.NewKVStoreService(keys[btclightclienttypes.StoreKey]), btcConfig, ) - checkpointingKeeper := checkpointingkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[checkpointingtypes.StoreKey]), - privSigner.WrappedPV, - epochingKeeper, - privSigner.ClientCtx, - ) + btcCheckpointKeeper := btccheckpointkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[btccheckpointtypes.StoreKey]), @@ -991,7 +1009,6 @@ func NewBabylonApp( // initialize BaseApp app.SetInitChainer(app.InitChainer) - app.SetPreBlocker(app.PreBlocker) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(anteHandler) @@ -1037,7 +1054,7 @@ func NewBabylonApp( tmos.Exit(err.Error()) } - ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{}) + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) // Initialize pinned codes in wasmvm as they are not persisted there if err := app.WasmKeeper.InitializePinnedCodes(ctx); err != nil { @@ -1087,7 +1104,12 @@ func (app *BabylonApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) panic("FAULTY") } - return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) + res, err := app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) + if err != nil { + panic(err) + } + + return res, nil } // LoadHeight loads a particular height diff --git a/app/app_test.go b/app/app_test.go index f98b7acb4..4e2ac57d0 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -2,9 +2,10 @@ package app import ( "fmt" - abci "github.com/cometbft/cometbft/abci/types" "testing" + abci "github.com/cometbft/cometbft/abci/types" + "cosmossdk.io/log" dbm "github.com/cosmos/cosmos-db" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,7 +15,7 @@ import ( func TestBabylonBlockedAddrs(t *testing.T) { db := dbm.NewMemDB() - signer, _ := SetupPrivSigner() + signer, _ := SetupTestPrivSigner() logger := log.NewTestLogger(t) app := NewBabylonAppWithCustomOptions(t, false, signer, SetupOptions{ @@ -71,7 +72,7 @@ func TestGetMaccPerms(t *testing.T) { func TestUpgradeStateOnGenesis(t *testing.T) { db := dbm.NewMemDB() - privSigner, err := SetupPrivSigner() + privSigner, err := SetupTestPrivSigner() require.NoError(t, err) logger := log.NewTestLogger(t) diff --git a/app/encoding.go b/app/encoding.go index 5c9fb7110..8aa2a472f 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -3,12 +3,13 @@ package app import ( "cosmossdk.io/log" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - appparams "github.com/babylonchain/babylon/app/params" dbm "github.com/cosmos/cosmos-db" + + appparams "github.com/babylonchain/babylon/app/params" ) func NewTmpBabylonApp() *BabylonApp { - signer, _ := SetupPrivSigner() + signer, _ := SetupTestPrivSigner() return NewBabylonApp( log.NewNopLogger(), dbm.NewMemDB(), diff --git a/app/test_helpers.go b/app/test_helpers.go index a9e1bc895..451472f14 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -2,24 +2,25 @@ package app import ( "encoding/json" - "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/privval" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/client/flags" - cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/server" - "github.com/docker/docker/pkg/ioutils" "math/rand" "os" "testing" "time" + "github.com/cosmos/cosmos-sdk/client/flags" + cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/server" + "github.com/docker/docker/pkg/ioutils" + "cosmossdk.io/math" tmjson "github.com/cometbft/cometbft/libs/json" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/privval" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "cosmossdk.io/log" - appparams "github.com/babylonchain/babylon/app/params" - bbn "github.com/babylonchain/babylon/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/ed25519" dbm "github.com/cosmos/cosmos-db" @@ -27,7 +28,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/server/types" simsutils "github.com/cosmos/cosmos-sdk/testutil/sims" @@ -37,6 +37,9 @@ import ( minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" + + appparams "github.com/babylonchain/babylon/app/params" + bbn "github.com/babylonchain/babylon/types" ) // SetupOptions defines arguments that are passed into `Simapp` constructor. @@ -48,7 +51,7 @@ type SetupOptions struct { AppOpts types.AppOptions } -func setup(t *testing.T, withGenesis bool, invCheckPeriod uint) (*BabylonApp, GenesisState) { +func setup(t *testing.T, ps *PrivSigner, withGenesis bool, invCheckPeriod uint) (*BabylonApp, GenesisState) { db := dbm.NewMemDB() nodeHome := t.TempDir() @@ -56,10 +59,6 @@ func setup(t *testing.T, withGenesis bool, invCheckPeriod uint) (*BabylonApp, Ge appOptions[flags.FlagHome] = nodeHome // ensure unique folder appOptions[server.FlagInvCheckPeriod] = invCheckPeriod appOptions["btc-config.network"] = string(bbn.BtcSimnet) - privSigner, err := SetupPrivSigner() - if err != nil { - panic(err) - } app := NewBabylonApp( log.NewNopLogger(), db, @@ -67,7 +66,7 @@ func setup(t *testing.T, withGenesis bool, invCheckPeriod uint) (*BabylonApp, Ge true, map[int64]bool{}, invCheckPeriod, - privSigner, + ps, EmptyAppOptions{}, EmptyWasmOpts, ) @@ -123,11 +122,15 @@ func NewBabylonAppWithCustomOptions(t *testing.T, isCheckTx bool, privSigner *Pr require.NoError(t, err) // Initialize the chain + consensusParams := simsutils.DefaultConsensusParams + initialHeight := app.LastBlockHeight() + 1 + consensusParams.Abci = &cmtproto.ABCIParams{VoteExtensionsEnableHeight: initialHeight} _, err = app.InitChain( &abci.RequestInitChain{ Validators: []abci.ValidatorUpdate{}, - ConsensusParams: simsutils.DefaultConsensusParams, + ConsensusParams: consensusParams, AppStateBytes: stateBytes, + InitialHeight: initialHeight, }, ) require.NoError(t, err) @@ -221,39 +224,28 @@ func genesisStateWithValSet(t *testing.T, func Setup(t *testing.T, isCheckTx bool) *BabylonApp { t.Helper() - // create validator set with single validator - valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), bls12381.GenPrivKey()) + ps, err := SetupTestPrivSigner() require.NoError(t, err) - valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) - require.NoError(t, err) - genesisKey, err := checkpointingtypes.NewGenesisKey( - sdk.ValAddress(valKeys.ValPubkey.Address()), - &valKeys.BlsPubkey, - valKeys.PoP, - &cosmosed.PubKey{Key: valPubkey.Bytes()}, - ) - require.NoError(t, err) - genesisValSet := []*checkpointingtypes.GenesisKey{genesisKey} - + valPubKey := ps.WrappedPV.Key.PubKey // generate genesis account - acc := authtypes.NewBaseAccount(valPubkey.Address().Bytes(), valPubkey, 0, 0) + acc := authtypes.NewBaseAccount(valPubKey.Address().Bytes(), &cosmosed.PubKey{Key: valPubKey.Bytes()}, 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), } + ps.WrappedPV.Key.DelegatorAddress = acc.GetAddress().String() + // create validator set with single validator + genesisKey, err := GenesisKeyFromPrivSigner(ps) + require.NoError(t, err) + genesisValSet := []*checkpointingtypes.GenesisKey{genesisKey} - app := SetupWithGenesisValSet(t, genesisValSet, []authtypes.GenesisAccount{acc}, balance) + app := SetupWithGenesisValSet(t, genesisValSet, ps, []authtypes.GenesisAccount{acc}, balance) return app } -// SetupPrivSigner sets up a PrivSigner for testing -func SetupPrivSigner() (*PrivSigner, error) { - kr, err := client.NewKeyringFromBackend(client.Context{}, keyring.BackendMemory) - if err != nil { - return nil, err - } - encodingCfg := appparams.DefaultEncodingConfig() +// SetupTestPrivSigner sets up a PrivSigner for testing +func SetupTestPrivSigner() (*PrivSigner, error) { // Create a temporary node directory nodeDir, err := ioutils.TempDir("", "tmp-signer") if err != nil { @@ -262,7 +254,7 @@ func SetupPrivSigner() (*PrivSigner, error) { defer func() { _ = os.RemoveAll(nodeDir) }() - privSigner, _ := InitPrivSigner(client.Context{}, nodeDir, kr, "", encodingCfg) + privSigner, _ := InitPrivSigner(nodeDir) return privSigner, nil } @@ -270,9 +262,10 @@ func SetupPrivSigner() (*PrivSigner, error) { // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit (10^6) in the default token of the babylon app from first genesis // account. A Nop logger is set in BabylonApp. -func SetupWithGenesisValSet(t *testing.T, valSet []*checkpointingtypes.GenesisKey, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { +// Note that the privSigner should be the 0th item of valSet +func SetupWithGenesisValSet(t *testing.T, valSet []*checkpointingtypes.GenesisKey, privSigner *PrivSigner, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *BabylonApp { t.Helper() - app, genesisState := setup(t, true, 5) + app, genesisState := setup(t, privSigner, true, 5) genesisState = genesisStateWithValSet(t, app, genesisState, valSet, genAccs, balances...) stateBytes, err := json.MarshalIndent(genesisState, "", " ") @@ -281,18 +274,21 @@ func SetupWithGenesisValSet(t *testing.T, valSet []*checkpointingtypes.GenesisKe // init chain will set the validator set and initialize the genesis accounts consensusParams := simsutils.DefaultConsensusParams consensusParams.Block.MaxGas = 100 * simsutils.DefaultGenTxGas + // it is required that the VoteExtensionsEnableHeight > 0 to enable vote extension + initialHeight := app.LastBlockHeight() + 1 + consensusParams.Abci = &cmtproto.ABCIParams{VoteExtensionsEnableHeight: initialHeight} _, err = app.InitChain(&abci.RequestInitChain{ ChainId: app.ChainID(), Time: time.Now().UTC(), Validators: []abci.ValidatorUpdate{}, ConsensusParams: consensusParams, - InitialHeight: app.LastBlockHeight() + 1, + InitialHeight: initialHeight, AppStateBytes: stateBytes, }) require.NoError(t, err) _, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{ - Height: app.LastBlockHeight() + 1, + Height: initialHeight, Hash: app.LastCommitID().Hash, }) require.NoError(t, err) @@ -300,6 +296,23 @@ func SetupWithGenesisValSet(t *testing.T, valSet []*checkpointingtypes.GenesisKe return app } +func GenesisKeyFromPrivSigner(ps *PrivSigner) (*checkpointingtypes.GenesisKey, error) { + valKeys, err := privval.NewValidatorKeys(ps.WrappedPV.GetValPrivKey(), ps.WrappedPV.GetBlsPrivKey()) + if err != nil { + return nil, err + } + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) + if err != nil { + return nil, err + } + return checkpointingtypes.NewGenesisKey( + ps.WrappedPV.GetAddress(), + &valKeys.BlsPubkey, + valKeys.PoP, + &cosmosed.PubKey{Key: valPubkey.Bytes()}, + ) +} + // createRandomAccounts is a strategy used by addTestAddrs() in order to generated addresses in random order. func createRandomAccounts(accNum int) []sdk.AccAddress { testAddrs := make([]sdk.AccAddress, accNum) diff --git a/app/utils.go b/app/utils.go index de2d49c81..d483eaf0b 100644 --- a/app/utils.go +++ b/app/utils.go @@ -7,15 +7,10 @@ import ( "path/filepath" "text/template" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" - "github.com/cosmos/cosmos-sdk/x/auth/types" - appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/privval" ) @@ -40,11 +35,9 @@ broadcast-mode = "{{ .BroadcastMode }}" type PrivSigner struct { WrappedPV *privval.WrappedFilePV - ClientCtx client.Context } -func InitPrivSigner(clientCtx client.Context, nodeDir string, kr keyring.Keyring, feePayer string, encodingCfg *appparams.EncodingConfig) (*PrivSigner, error) { - // setup private validator +func InitPrivSigner(nodeDir string) (*PrivSigner, error) { nodeCfg := cmtconfig.DefaultConfig() pvKeyFile := filepath.Join(nodeDir, nodeCfg.PrivValidatorKeyFile()) err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0777) @@ -58,33 +51,8 @@ func InitPrivSigner(clientCtx client.Context, nodeDir string, kr keyring.Keyring } wrappedPV := privval.LoadOrGenWrappedFilePV(pvKeyFile, pvStateFile) - clientCtx = clientCtx. - WithInterfaceRegistry(encodingCfg.InterfaceRegistry). - WithCodec(encodingCfg.Codec). - WithLegacyAmino(encodingCfg.Amino). - WithTxConfig(encodingCfg.TxConfig). - WithAccountRetriever(types.AccountRetriever{}). - WithSkipConfirmation(true). - WithKeyring(kr). - WithNodeURI(nodeCfg.RPC.ListenAddress) - - if feePayer != "" { - feePayerRecord, err := kr.Key(feePayer) - if err != nil { - return nil, err - } - feePayerAddress, err := feePayerRecord.GetAddress() - if err != nil { - return nil, err - } - clientCtx = clientCtx. - WithFromAddress(feePayerAddress). - WithFromName(feePayer) - } - return &PrivSigner{ WrappedPV: wrappedPV, - ClientCtx: clientCtx, }, nil } diff --git a/btctxformatter/formatter.go b/btctxformatter/formatter.go index 6629b7ed4..83f19f3b4 100644 --- a/btctxformatter/formatter.go +++ b/btctxformatter/formatter.go @@ -25,7 +25,7 @@ type BabylonData struct { type RawBtcCheckpoint struct { Epoch uint64 - AppHash []byte + BlockHash []byte BitMap []byte SubmitterAddress []byte BlsSig []byte @@ -154,7 +154,7 @@ func EncodeCheckpointData( return nil, nil, errors.New("invalid format version") } - if len(rawBTCCheckpoint.AppHash) != AppHashLength { + if len(rawBTCCheckpoint.BlockHash) != AppHashLength { return nil, nil, errors.New("appHash should have 32 bytes") } @@ -174,7 +174,7 @@ func EncodeCheckpointData( tag, version, rawBTCCheckpoint.Epoch, - rawBTCCheckpoint.AppHash, + rawBTCCheckpoint.BlockHash, rawBTCCheckpoint.BitMap, rawBTCCheckpoint.SubmitterAddress, ) @@ -325,7 +325,7 @@ func DecodeRawCheckpoint(version FormatVersion, btcCkptBytes []byte) (*RawBtcChe rawCheckpoint := &RawBtcCheckpoint{ Epoch: binary.BigEndian.Uint64(epochBytes), - AppHash: appHashBytes, + BlockHash: appHashBytes, BitMap: bitmapBytes, SubmitterAddress: addressBytes, BlsSig: blsSigBytes, diff --git a/btctxformatter/formatter_test.go b/btctxformatter/formatter_test.go index d90127451..39a375337 100644 --- a/btctxformatter/formatter_test.go +++ b/btctxformatter/formatter_test.go @@ -28,7 +28,7 @@ func FuzzEncodingDecoding(f *testing.F) { rawBTCCkpt := &RawBtcCheckpoint{ Epoch: epoch, - AppHash: appHash, + BlockHash: appHash, BitMap: bitMap, SubmitterAddress: blsSig, BlsSig: address, @@ -78,8 +78,8 @@ func FuzzEncodingDecoding(f *testing.F) { t.Errorf("Epoch should match. Expected: %v. Got: %v", epoch, ckpt.Epoch) } - if !bytes.Equal(appHash, ckpt.AppHash) { - t.Errorf("AppHash should match. Expected: %v. Got: %v", appHash, ckpt.AppHash) + if !bytes.Equal(appHash, ckpt.BlockHash) { + t.Errorf("BlockHash should match. Expected: %v. Got: %v", appHash, ckpt.BlockHash) } if !bytes.Equal(bitMap, ckpt.BitMap) { diff --git a/cmd/babylond/cmd/add_gen_bls_test.go b/cmd/babylond/cmd/add_gen_bls_test.go index 459a2935d..f261c2c09 100644 --- a/cmd/babylond/cmd/add_gen_bls_test.go +++ b/cmd/babylond/cmd/add_gen_bls_test.go @@ -16,12 +16,6 @@ import ( "cosmossdk.io/core/appconfig" "cosmossdk.io/depinject" "cosmossdk.io/log" - "github.com/babylonchain/babylon/app" - "github.com/babylonchain/babylon/cmd/babylond/cmd" - "github.com/babylonchain/babylon/privval" - "github.com/babylonchain/babylon/testutil/cli" - "github.com/babylonchain/babylon/testutil/datagen" - "github.com/babylonchain/babylon/x/checkpointing/types" cmtconfig "github.com/cometbft/cometbft/config" tmjson "github.com/cometbft/cometbft/libs/json" "github.com/cometbft/cometbft/libs/tempfile" @@ -34,6 +28,13 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/spf13/viper" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/cmd/babylond/cmd" + "github.com/babylonchain/babylon/privval" + "github.com/babylonchain/babylon/testutil/cli" + "github.com/babylonchain/babylon/testutil/datagen" + "github.com/babylonchain/babylon/x/checkpointing/types" ) func newConfig() depinject.Config { @@ -73,7 +74,7 @@ func Test_AddGenBlsCmdWithoutGentx(t *testing.T) { require.NoError(t, err) db := dbm.NewMemDB() - signer, err := app.SetupPrivSigner() + signer, err := app.SetupTestPrivSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: logger, @@ -115,7 +116,7 @@ func Test_AddGenBlsCmdWithoutGentx(t *testing.T) { // error is expected if adding duplicate func Test_AddGenBlsCmdWithGentx(t *testing.T) { db := dbm.NewMemDB() - signer, err := app.SetupPrivSigner() + signer, err := app.SetupTestPrivSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: log.NewNopLogger(), diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 74ae9fb47..243d36c22 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -6,12 +6,13 @@ import ( "cosmossdk.io/math" - babylonApp "github.com/babylonchain/babylon/app" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" tmrand "github.com/cometbft/cometbft/libs/rand" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" + + babylonApp "github.com/babylonchain/babylon/app" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) const ( @@ -29,6 +30,7 @@ const ( flagBlocksPerYear = "blocks-per-year" flagGenesisTime = "genesis-time" flagBlockGasLimit = "block-gas-limit" + flagVoteExtensionEnableHeight = "vote-extension-enable-height" flagCovenantPks = "covenant-pks" flagCovenantQuorum = "covenant-quorum" flagMaxActiveFinalityProviders = "max-active-finality-providers" @@ -56,6 +58,7 @@ type GenesisCLIArgs struct { BlocksPerYear uint64 GenesisTime time.Time BlockGasLimit int64 + VoteExtensionEnableHeight int64 CovenantPKs []string CovenantQuorum uint32 SlashingAddress string @@ -102,6 +105,7 @@ func addGenesisFlags(cmd *cobra.Command) { cmd.Flags().Int64(flagGenesisTime, time.Now().Unix(), "Genesis time") // blocks args cmd.Flags().Int64(flagBlockGasLimit, babylonApp.DefaultGasLimit, "Block gas limit") + cmd.Flags().Int64(flagVoteExtensionEnableHeight, babylonApp.DefaultVoteExtensionsEnableHeight, "Vote extension enable height") } func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { @@ -129,6 +133,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { goalBonded, _ := cmd.Flags().GetFloat64(flagGoalBonded) blocksPerYear, _ := cmd.Flags().GetUint64(flagBlocksPerYear) blockGasLimit, _ := cmd.Flags().GetInt64(flagBlockGasLimit) + voteExtensionEnableHeight, _ := cmd.Flags().GetInt64(flagVoteExtensionEnableHeight) if chainID == "" { chainID = "chain-" + tmrand.NewRand().Str(6) @@ -161,5 +166,6 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { GoalBonded: goalBonded, BlocksPerYear: blocksPerYear, BlockGasLimit: blockGasLimit, + VoteExtensionEnableHeight: voteExtensionEnableHeight, } } diff --git a/cmd/babylond/cmd/genbls_test.go b/cmd/babylond/cmd/genbls_test.go index 93037f760..b79486fb1 100644 --- a/cmd/babylond/cmd/genbls_test.go +++ b/cmd/babylond/cmd/genbls_test.go @@ -34,7 +34,7 @@ func Test_GenBlsCmd(t *testing.T) { cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - signer, err := app.SetupPrivSigner() + signer, err := app.SetupTestPrivSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: logger, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index d489fe45c..4d0750de0 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -10,12 +10,6 @@ import ( btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types" finalitytypes "github.com/babylonchain/babylon/x/finality/types" - appparams "github.com/babylonchain/babylon/app/params" - bbn "github.com/babylonchain/babylon/types" - btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" comettypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -34,6 +28,13 @@ import ( minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/spf13/cobra" + + appparams "github.com/babylonchain/babylon/app/params" + bbn "github.com/babylonchain/babylon/types" + btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" ) func PrepareGenesisCmd(defaultNodeHome string, mbm module.BasicManager) *cobra.Command { @@ -75,7 +76,7 @@ Example: genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, - genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) + genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit, genesisCliArgs.VoteExtensionEnableHeight) } else if network == "mainnet" { // TODO: mainnet genesis params panic("Mainnet params not implemented.") @@ -122,10 +123,12 @@ func PrepareGenesis( if genesis.Consensus == nil { genesis.Consensus = genutiltypes.NewConsensusGenesis(comettypes.DefaultConsensusParams().ToProto(), nil) + } // Set gas limit genesis.Consensus.Params.Block.MaxGas = genesisParams.BlockGasLimit + genesis.Consensus.Params.ABCI.VoteExtensionsEnableHeight = genesisParams.VoteExtensionsEnableHeight // Set the confirmation and finalization parameters btccheckpointGenState := btccheckpointtypes.DefaultGenesis() @@ -228,6 +231,7 @@ type GenesisParams struct { FinalityParams finalitytypes.Params BtclightclientBaseBtcHeader btclightclienttypes.BTCHeaderInfo BlockGasLimit int64 + VoteExtensionsEnableHeight int64 } func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, @@ -236,7 +240,7 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, minUnbondingTime uint16, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, - blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64) GenesisParams { + blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64, voteExtensionEnableHeight int64) GenesisParams { genParams := GenesisParams{} @@ -344,5 +348,6 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 } genParams.BlockGasLimit = blockGasLimit + genParams.VoteExtensionsEnableHeight = voteExtensionEnableHeight return genParams } diff --git a/cmd/babylond/cmd/root.go b/cmd/babylond/cmd/root.go index a87067300..2ba71b172 100644 --- a/cmd/babylond/cmd/root.go +++ b/cmd/babylond/cmd/root.go @@ -1,8 +1,11 @@ package cmd import ( - confixcmd "cosmossdk.io/tools/confix/cmd" "errors" + "io" + "os" + + confixcmd "cosmossdk.io/tools/confix/cmd" "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" cmtcfg "github.com/cometbft/cometbft/config" @@ -13,8 +16,6 @@ import ( authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - "io" - "os" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" @@ -37,7 +38,6 @@ import ( "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/app/params" - bbntypes "github.com/babylonchain/babylon/types" ) // NewRootCmd creates a new root command for babylond. It is called once in the @@ -249,26 +249,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts serverty } homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) - encCfg := app.GetEncodingConfig() - clientCtx, err := config.ReadFromClientConfig( - client.Context{}. - WithHomeDir(homeDir). - WithViper(""). - WithKeyringDir(homeDir). - WithInput(os.Stdin). - // Warning: It is important that ReadFromClientConfig receives context - // with already initialized codec. It creates keyring inside, and from cosmos - // 0.46.0 keyring requires codec. Without codec, operations performed by - // keyring ends with nil pointer exception (`panic`) - WithCodec(encCfg.Codec), - ) - if err != nil { - panic(err) - } - // parse the key name that will be used for signing BLS-sig txs from app.toml - keyName := bbntypes.ParseKeyNameFromConfig(appOpts) - - privSigner, err := app.InitPrivSigner(clientCtx, homeDir, clientCtx.Keyring, keyName, encCfg) + privSigner, err := app.InitPrivSigner(homeDir) if err != nil { panic(err) } @@ -307,28 +288,7 @@ func appExport( return servertypes.ExportedApp{}, errors.New("application home not set") } - encCfg := app.GetEncodingConfig() - clientCtx, err := config.ReadFromClientConfig( - client.Context{}. - WithHomeDir(homePath). - WithViper(""). - WithKeyringDir(homePath). - WithInput(os.Stdin). - // Warning: It is important that ReadFromClientConfig receives context - // with already initialized codec. It creates keyring inside, and from cosmos - // 0.46.0 keyring requires codec. Without codec, operations performed by - // keyring ends with nil pointer exception (`panic`) - WithCodec(encCfg.Codec), - ) - if err != nil { - panic(err) - } - kr, err := client.NewKeyringFromBackend(clientCtx, keyring.BackendMemory) - if err != nil { - panic(err) - } - - privSigner, err := app.InitPrivSigner(clientCtx, homePath, kr, "", encCfg) + privSigner, err := app.InitPrivSigner(homePath) if err != nil { panic(err) } diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 1a9397961..10d18b210 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -14,17 +14,20 @@ import ( "github.com/cosmos/cosmos-sdk/runtime" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + appparams "github.com/babylonchain/babylon/app/params" bbn "github.com/babylonchain/babylon/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/babylonchain/babylon/app" "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/babylonchain/babylon/app" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/babylonchain/babylon/privval" "github.com/babylonchain/babylon/testutil/datagen" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" cmtconfig "github.com/cometbft/cometbft/config" tmos "github.com/cometbft/cometbft/libs/os" @@ -104,7 +107,7 @@ Example: genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, - genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit) + genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit, genesisCliArgs.VoteExtensionEnableHeight) return InitTestnet( clientCtx, cmd, config, mbm, genBalIterator, outputDir, genesisCliArgs.ChainID, minGasPrices, diff --git a/cmd/babylond/cmd/testnet_test.go b/cmd/babylond/cmd/testnet_test.go index b0e352eaf..f529bdf7e 100644 --- a/cmd/babylond/cmd/testnet_test.go +++ b/cmd/babylond/cmd/testnet_test.go @@ -3,9 +3,10 @@ package cmd import ( "context" "fmt" - dbm "github.com/cosmos/cosmos-db" "testing" + dbm "github.com/cosmos/cosmos-db" + "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -25,7 +26,7 @@ func Test_TestnetCmd(t *testing.T) { cfg, err := genutiltest.CreateDefaultCometConfig(home) require.NoError(t, err) - signer, err := app.SetupPrivSigner() + signer, err := app.SetupTestPrivSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: logger, diff --git a/cmd/babylond/cmd/validate_genesis_test.go b/cmd/babylond/cmd/validate_genesis_test.go index 75236771c..1d39caa13 100644 --- a/cmd/babylond/cmd/validate_genesis_test.go +++ b/cmd/babylond/cmd/validate_genesis_test.go @@ -6,22 +6,24 @@ import ( "fmt" "testing" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" dbm "github.com/cosmos/cosmos-db" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/spf13/viper" "github.com/stretchr/testify/require" "cosmossdk.io/log" - "github.com/babylonchain/babylon/app" - "github.com/babylonchain/babylon/cmd/babylond/cmd" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/cmd/babylond/cmd" ) func TestCheckCorrespondence(t *testing.T) { @@ -87,7 +89,7 @@ func generateTestGenesisState(t *testing.T, home string, n int) (*app.BabylonApp logger := log.NewNopLogger() cfg, _ := genutiltest.CreateDefaultCometConfig(home) - signer, err := app.SetupPrivSigner() + signer, err := app.SetupTestPrivSigner() require.NoError(t, err) bbn := app.NewBabylonAppWithCustomOptions(t, false, signer, app.SetupOptions{ Logger: logger, diff --git a/go.mod b/go.mod index 843c075d4..b840cdc10 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ module github.com/babylonchain/babylon require ( github.com/CosmWasm/wasmd v0.50.0-rc.2 github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 - github.com/cometbft/cometbft v0.38.0 + github.com/cometbft/cometbft v0.38.2 github.com/cometbft/cometbft-db v0.8.0 // indirect github.com/cosmos/cosmos-sdk v0.50.1 github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index bab2f3afa..1e8c2750f 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/cometbft/cometbft v0.38.0 h1:ogKnpiPX7gxCvqTEF4ly25/wAxUqf181t30P3vqdpdc= -github.com/cometbft/cometbft v0.38.0/go.mod h1:5Jz0Z8YsHSf0ZaAqGvi/ifioSdVFPtEGrm8Y9T/993k= +github.com/cometbft/cometbft v0.38.2 h1:io0JCh5EPxINKN5ZMI5hCdpW3QVZRy+o8qWe3mlJa/8= +github.com/cometbft/cometbft v0.38.2/go.mod h1:PIi48BpzwlHqtV3mzwPyQgOyOnU94BNBimLS2ebBHOg= github.com/cometbft/cometbft-db v0.8.0 h1:vUMDaH3ApkX8m0KZvOFFy9b5DZHBAjsnEuo9AKVZpjo= github.com/cometbft/cometbft-db v0.8.0/go.mod h1:6ASCP4pfhmrCBpfk01/9E1SI29nD3HfVHrY4PG8x5c0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= diff --git a/privval/file.go b/privval/file.go index 7a0eeb981..ab09d269e 100644 --- a/privval/file.go +++ b/privval/file.go @@ -75,7 +75,7 @@ func (pvKey WrappedFilePVKey) Save() { } } -//------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------- // WrappedFilePV wraps FilePV with WrappedFilePVKey. type WrappedFilePV struct { @@ -150,7 +150,7 @@ func loadWrappedFilePV(keyFilePath, stateFilePath string, loadState bool) *Wrapp } // adding path is not needed - //pvState.filePath = stateFilePath + // pvState.filePath = stateFilePath return &WrappedFilePV{ Key: pvKey, @@ -255,6 +255,10 @@ func (pv *WrappedFilePV) GetBlsPubkey() (bls12381.PublicKey, error) { return blsPrivKey.PubKey(), nil } +func (pv *WrappedFilePV) GetValidatorPubkey() (tmcrypto.PubKey, error) { + return pv.GetPubKey() +} + // Save persists the FilePV to disk. func (pv *WrappedFilePV) Save() { pv.Key.Save() diff --git a/proto/babylon/checkpointing/v1/bls_key.proto b/proto/babylon/checkpointing/v1/bls_key.proto index 204af94e8..0c6f223cf 100644 --- a/proto/babylon/checkpointing/v1/bls_key.proto +++ b/proto/babylon/checkpointing/v1/bls_key.proto @@ -8,12 +8,12 @@ option go_package = "github.com/babylonchain/babylon/x/checkpointing/types"; // BlsKey wraps BLS public key with PoP message BlsKey { // pubkey is the BLS public key of a validator - bytes pubkey = 2 + bytes pubkey = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/crypto/bls12381.PublicKey" ]; // pop is the proof-of-possession of the BLS key - ProofOfPossession pop = 3; + ProofOfPossession pop = 2; } // ProofOfPossession defines proof for the ownership of Ed25519 and BLS private @@ -21,10 +21,10 @@ message BlsKey { message ProofOfPossession { // ed25519_sig is used for verification, ed25519_sig = sign(key = Ed25519_sk, // data = BLS_pk) - bytes ed25519_sig = 2; + bytes ed25519_sig = 1; // bls_sig is the result of PoP, bls_sig = sign(key = BLS_sk, data = // ed25519_sig) - bytes bls_sig = 3 + bytes bls_sig = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/crypto/bls12381.Signature" ]; } @@ -39,3 +39,15 @@ message ValidatorWithBlsKey { bytes bls_pub_key = 2; uint64 voting_power = 3; } + +// VoteExtension defines the structure used to create a BLS vote extension. +message VoteExtension { + string signer = 1; + string validator_address = 2; + bytes block_hash = 3; + uint64 epoch_num = 4; + uint64 height =5; + bytes bls_sig = 6 + [ (gogoproto.customtype) = + "github.com/babylonchain/babylon/crypto/bls12381.Signature" ]; +} diff --git a/proto/babylon/checkpointing/v1/checkpoint.proto b/proto/babylon/checkpointing/v1/checkpoint.proto index 200487498..fcaf5a800 100644 --- a/proto/babylon/checkpointing/v1/checkpoint.proto +++ b/proto/babylon/checkpointing/v1/checkpoint.proto @@ -3,6 +3,7 @@ package babylon.checkpointing.v1; import "google/protobuf/timestamp.proto"; import "gogoproto/gogo.proto"; +import "tendermint/abci/types.proto"; option go_package = "github.com/babylonchain/babylon/x/checkpointing/types"; @@ -12,9 +13,9 @@ message RawCheckpoint { // epoch_num defines the epoch number the raw checkpoint is for uint64 epoch_num = 1; - // app_hash defines the 'AppHash' that individual BLS sigs are - // signed on - bytes app_hash = 2 [ (gogoproto.customtype) = "AppHash" ]; + // block_hash defines the 'BlockID.Hash', which is the hash of + // the block that individual BLS sigs are signed on + bytes block_hash = 2 [ (gogoproto.customtype) = "BlockHash" ]; // bitmap defines the bitmap that indicates the signers of the BLS multi sig bytes bitmap = 3; // bls_multi_sig defines the multi sig that is aggregated from individual BLS @@ -43,6 +44,12 @@ message RawCheckpointWithMeta { repeated CheckpointStateUpdate lifecycle = 5; } +// InjectedCheckpoint wraps the checkpoint and the extended votes +message InjectedCheckpoint { + RawCheckpointWithMeta ckpt = 1; + tendermint.abci.ExtendedCommitInfo extended_commit_info = 2; +} + // CheckpointStatus is the status of a checkpoint. enum CheckpointStatus { option (gogoproto.goproto_enum_prefix) = false; @@ -80,14 +87,18 @@ message BlsSig { // epoch_num defines the epoch number that the BLS sig is signed on uint64 epoch_num = 1; - // app_hash defines the 'AppHash' that the BLS sig is signed on - bytes app_hash = 2 [ (gogoproto.customtype) = "AppHash" ]; + // block_hash defines the 'BlockID.Hash', which is the hash of + // the block that individual BLS sigs are signed on + bytes block_hash = 2 [ (gogoproto.customtype) = "BlockHash" ]; bytes bls_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/crypto/bls12381.Signature" ]; // can't find cosmos_proto.scalar when compiling due to cosmos v0.45.4 does // not support scalar string signer_address = 4 [(cosmos_proto.scalar) = - // "cosmos.AddressString"]; the signer_address defines the address of the + // "cosmos.AddressString"] + // the signer_address defines the address of the // signer string signer_address = 4; + // validator_address defines the validator's consensus address + string validator_address = 5; } diff --git a/proto/babylon/checkpointing/v1/tx.proto b/proto/babylon/checkpointing/v1/tx.proto index fc8f96fff..bfc00666e 100644 --- a/proto/babylon/checkpointing/v1/tx.proto +++ b/proto/babylon/checkpointing/v1/tx.proto @@ -2,10 +2,8 @@ syntax = "proto3"; package babylon.checkpointing.v1; import "gogoproto/gogo.proto"; -import "babylon/checkpointing/v1/checkpoint.proto"; import "babylon/checkpointing/v1/bls_key.proto"; import "cosmos/staking/v1beta1/tx.proto"; -import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; option go_package = "github.com/babylonchain/babylon/x/checkpointing/types"; @@ -14,32 +12,11 @@ option go_package = "github.com/babylonchain/babylon/x/checkpointing/types"; service Msg { option (cosmos.msg.v1.service) = true; - // AddBlsSig defines a method for accumulating BLS signatures - rpc AddBlsSig(MsgAddBlsSig) returns (MsgAddBlsSigResponse); - // WrappedCreateValidator defines a method for registering a new validator rpc WrappedCreateValidator(MsgWrappedCreateValidator) returns (MsgWrappedCreateValidatorResponse); } -// MsgAddBlsSig defines a message to add a bls signature from a -// validator -message MsgAddBlsSig { - option (gogoproto.equal) = false; - option (gogoproto.goproto_getters) = false; - option (cosmos.msg.v1.signer) = "signer"; - - // signer corresponds to the submitter of the transaction - // This might be a different entity compared to the one that created the BLS signature - // (i.e. the validator owner of the BLS key which is specified by the `bls_sig.signer_address`) - string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - - BlsSig bls_sig = 2; -} - -// MsgAddBlsSigResponse defines the MsgAddBlsSig response type. -message MsgAddBlsSigResponse {} - // MsgWrappedCreateValidator defines a wrapped message to create a validator message MsgWrappedCreateValidator { option (gogoproto.equal) = false; diff --git a/proto/babylon/epoching/v1/epoching.proto b/proto/babylon/epoching/v1/epoching.proto index fc8686f3e..276633ae8 100644 --- a/proto/babylon/epoching/v1/epoching.proto +++ b/proto/babylon/epoching/v1/epoching.proto @@ -22,11 +22,14 @@ message Epoch { // app_hash_root is the Merkle root of all AppHashs in this epoch // It will be used for proving a block is in an epoch bytes app_hash_root = 5; - // sealer_header_hash is the app_hash of the sealer header, i.e., 2nd header + // sealer is the last block of the sealed epoch + // sealer_app_hash points to the sealer but stored in the 1st header // of the next epoch - // This validator set has generated a BLS multisig on `app_hash` of - // the sealer header - bytes sealer_header_hash = 6; + bytes sealer_app_hash = 6; + // sealer_block_hash is the hash of the sealer + // the validator set has generated a BLS multisig on the hash, + // i.e., hash of the last block in the epoch + bytes sealer_block_hash = 7; } // QueuedMessage is a message that can change the validator set and is delayed diff --git a/proto/buf.lock b/proto/buf.lock index 7a2810e68..bdb2a63ae 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -9,8 +9,8 @@ deps: - remote: buf.build owner: cosmos repository: cosmos-sdk - commit: 954f7b05f38440fc8250134b15adec47 - digest: shake256:2ab4404fd04a7d1d52df0e2d0f2d477a3d83ffd88d876957bf3fedfd702c8e52833d65b3ce1d89a3c5adf2aab512616b0e4f51d8463f07eda9a8a3317ee3ac54 + commit: 5a6ab7bc14314acaa912d5e53aef1c2f + digest: shake256:02c00c73493720055f9b57553a35b5550023a3c1914123b247956288a78fb913aff70e66552777ae14d759467e119079d484af081264a5dd607a94d9fbc8116b - remote: buf.build owner: cosmos repository: gogo-proto diff --git a/proto/buf.yaml b/proto/buf.yaml index dc5409427..2db8cfc68 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -1,7 +1,7 @@ version: v1 name: buf.build/babylon/babylond deps: - - buf.build/cosmos/cosmos-sdk:v0.47.0 + - buf.build/cosmos/cosmos-sdk:v0.50.0 - buf.build/cosmos/cosmos-proto:1935555c206d4afb9e94615dfd0fad31 - buf.build/cosmos/gogo-proto:a14993478f40695898ed8a86931094b6656e8a5d - buf.build/googleapis/googleapis:8d7204855ec14631a499bd7393ce1970 diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index c94ef9662..f15391114 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -1,20 +1,15 @@ package initialization import ( - "cosmossdk.io/math" "encoding/json" "fmt" "path/filepath" "time" - "github.com/babylonchain/babylon/privval" - bbn "github.com/babylonchain/babylon/types" - btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" - blctypes "github.com/babylonchain/babylon/x/btclightclient/types" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - tmjson "github.com/cometbft/cometbft/libs/json" + "cosmossdk.io/math" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - ed25519 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -26,6 +21,12 @@ import ( staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/gogoproto/proto" + "github.com/babylonchain/babylon/privval" + bbn "github.com/babylonchain/babylon/types" + btccheckpointtypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + blctypes "github.com/babylonchain/babylon/x/btclightclient/types" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + "github.com/babylonchain/babylon/test/e2e/util" ) @@ -257,16 +258,16 @@ func initGenesis(chain *internalChain, votingPeriod, expeditedVotingPeriod time. genDoc.AppState = bz - genesisJson, err := tmjson.MarshalIndent(genDoc, "", " ") - if err != nil { - return err - } - // write the updated genesis file to each validator for _, val := range chain.nodes { - if err := util.WriteFile(filepath.Join(val.configDir(), "config", "genesis.json"), genesisJson); err != nil { - return err + path := filepath.Join(val.configDir(), "config", "genesis.json") + + // We need to use genutil.ExportGenesisFile to marshal and write the genesis file + // to use correct json encoding. + if err = genutil.ExportGenesisFile(genDoc, path); err != nil { + return fmt.Errorf("failed to export app genesis state: %w", err) } + } return nil } diff --git a/test/e2e/initialization/node.go b/test/e2e/initialization/node.go index 2acae3ba0..c08be4dd3 100644 --- a/test/e2e/initialization/node.go +++ b/test/e2e/initialization/node.go @@ -1,10 +1,15 @@ package initialization import ( - "cosmossdk.io/log" - "cosmossdk.io/math" "encoding/json" "fmt" + "os" + "path" + "path/filepath" + "strings" + + "cosmossdk.io/log" + "cosmossdk.io/math" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" cmtos "github.com/cometbft/cometbft/libs/os" dbm "github.com/cosmos/cosmos-db" @@ -12,14 +17,7 @@ import ( simsutils "github.com/cosmos/cosmos-sdk/testutil/sims" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - "os" - "path" - "path/filepath" - "strings" - "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/privval" - bbn "github.com/babylonchain/babylon/types" cmtconfig "github.com/cometbft/cometbft/config" cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/p2p" @@ -39,6 +37,10 @@ import ( "github.com/cosmos/go-bip39" "github.com/spf13/viper" + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/privval" + bbn "github.com/babylonchain/babylon/types" + "github.com/babylonchain/babylon/test/e2e/util" babylonApp "github.com/babylonchain/babylon/app" @@ -63,18 +65,18 @@ func newNode(chain *internalChain, nodeConfig *NodeConfig) (*internalNode, error moniker: fmt.Sprintf("%s-node-%s", chain.chainMeta.Id, nodeConfig.Name), isValidator: nodeConfig.IsValidator, } - // generate genesis files - if err := node.init(); err != nil { + // creating keys comes before init + if err := node.createKey(ValidatorWalletName); err != nil { return nil, err } - // create keys - if err := node.createKey(ValidatorWalletName); err != nil { + if err := node.createConsensusKey(); err != nil { return nil, err } - if err := node.createNodeKey(); err != nil { + // generate genesis files + if err := node.init(); err != nil { return nil, err } - if err := node.createConsensusKey(); err != nil { + if err := node.createNodeKey(); err != nil { return nil, err } node.createAppConfig(nodeConfig) @@ -180,7 +182,6 @@ func (n *internalNode) createConsensusKey() error { return err } - // privval.LoadOrGenWrappedFilePV() privKey := cmted25519.GenPrivKeyFromSecret([]byte(n.mnemonic)) blsPrivKey := bls12381.GenPrivKeyFromSecret([]byte(n.mnemonic)) filePV := privval.NewWrappedFilePV(privKey, blsPrivKey, pvKeyFile, pvStateFile) @@ -305,7 +306,7 @@ func (n *internalNode) init() error { appOptions[flags.FlagHome] = n.configDir() appOptions["btc-config.network"] = string(bbn.BtcSimnet) - privSigner, _ := babylonApp.SetupPrivSigner() + privSigner := &babylonApp.PrivSigner{WrappedPV: &privval.WrappedFilePV{Key: n.consensusKey}} // Create a temp app to get the default genesis state tempApp := babylonApp.NewBabylonApp( log.NewNopLogger(), @@ -334,6 +335,7 @@ func (n *internalNode) init() error { Params: cmttypes.DefaultConsensusParams(), } appGenesis.Consensus.Params.Block.MaxGas = babylonApp.DefaultGasLimit + appGenesis.Consensus.Params.ABCI.VoteExtensionsEnableHeight = babylonApp.DefaultVoteExtensionsEnableHeight if err = genutil.ExportGenesisFile(appGenesis, config.GenesisFile()); err != nil { return fmt.Errorf("failed to export app genesis state: %w", err) diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 5b04b2f02..88731c82e 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -9,9 +9,6 @@ import ( "runtime" "time" - txformat "github.com/babylonchain/babylon/btctxformatter" - bbn "github.com/babylonchain/babylon/types" - btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -19,6 +16,10 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" + + txformat "github.com/babylonchain/babylon/btctxformatter" + bbn "github.com/babylonchain/babylon/types" + btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" ) var ( @@ -33,7 +34,7 @@ var ( type testCheckpointData struct { epoch uint64 - appHash []byte + blockHash []byte bitmap []byte blsSig []byte submitterAddress []byte @@ -458,7 +459,7 @@ func GenerateMessageWithRandomSubmitter(blockResults []*BlockCreationResult) *bt func getRandomCheckpointDataForEpoch(r *rand.Rand, e uint64) testCheckpointData { return testCheckpointData{ epoch: e, - appHash: GenRandomByteArray(r, txformat.AppHashLength), + blockHash: GenRandomByteArray(r, txformat.AppHashLength), bitmap: GenRandomByteArray(r, txformat.BitMapLength), blsSig: GenRandomByteArray(r, txformat.BlsSigLength), submitterAddress: GenRandomByteArray(r, txformat.AddressLength), @@ -502,7 +503,7 @@ func RandomRawCheckpointDataForEpoch(r *rand.Rand, e uint64) (*TestRawCheckpoint checkpointData := getRandomCheckpointDataForEpoch(r, e) rawBTCCkpt := &txformat.RawBtcCheckpoint{ Epoch: checkpointData.epoch, - AppHash: checkpointData.appHash, + BlockHash: checkpointData.blockHash, BitMap: checkpointData.bitmap, SubmitterAddress: checkpointData.submitterAddress, BlsSig: checkpointData.blsSig, diff --git a/testutil/datagen/epoching.go b/testutil/datagen/epoching.go index 6c1e67196..6cec642f1 100644 --- a/testutil/datagen/epoching.go +++ b/testutil/datagen/epoching.go @@ -38,6 +38,7 @@ func GenRandomEpoch(r *rand.Rand) *epochingtypes.Epoch { &lastBlockHeader.Time, ) sealerHeader := GenRandomTMHeader(r, "test-chain", firstBlockHeight+epochInterval+1) // 2nd block in the next epoch - epoch.SealerHeaderHash = sealerHeader.AppHash + epoch.SealerBlockHash = GenRandomBlockHash(r) + epoch.SealerAppHash = sealerHeader.AppHash return &epoch } diff --git a/testutil/datagen/genesiskey.go b/testutil/datagen/genesiskey.go index 631b24d39..45daf5c37 100644 --- a/testutil/datagen/genesiskey.go +++ b/testutil/datagen/genesiskey.go @@ -1,18 +1,106 @@ package datagen import ( - "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/privval" - "github.com/babylonchain/babylon/x/checkpointing/types" - ed255192 "github.com/cometbft/cometbft/crypto/ed25519" + "github.com/cometbft/cometbft/crypto/ed25519" + cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/app" + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/privval" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" ) -func GenerateGenesisKey() *types.GenesisKey { +type GenesisValidators struct { + Keys []*GenesisKeyWithBLS +} + +type GenesisKeyWithBLS struct { + checkpointingtypes.GenesisKey + bls12381.PrivateKey +} + +func (gvs *GenesisValidators) GetGenesisKeys() []*checkpointingtypes.GenesisKey { + gensisKeys := make([]*checkpointingtypes.GenesisKey, 0, len(gvs.Keys)) + for _, k := range gvs.Keys { + gensisKeys = append(gensisKeys, &k.GenesisKey) + } + + return gensisKeys +} + +func (gvs *GenesisValidators) GetBLSPrivKeys() []bls12381.PrivateKey { + blsPrivKeys := make([]bls12381.PrivateKey, 0, len(gvs.Keys)) + for _, k := range gvs.Keys { + blsPrivKeys = append(blsPrivKeys, k.PrivateKey) + } + + return blsPrivKeys +} + +// GenesisValidatorSet generates a set with `numVals` genesis validators +func GenesisValidatorSet(numVals int) (*GenesisValidators, error) { + genesisVals := make([]*GenesisKeyWithBLS, 0, numVals) + for i := 0; i < numVals; i++ { + blsPrivKey := bls12381.GenPrivKey() + // create validator set with single validator + valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), blsPrivKey) + if err != nil { + return nil, err + } + valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) + if err != nil { + return nil, err + } + genesisKey, err := checkpointingtypes.NewGenesisKey( + sdk.ValAddress(valKeys.ValPubkey.Address()), + &valKeys.BlsPubkey, + valKeys.PoP, + &cosmosed.PubKey{Key: valPubkey.Bytes()}, + ) + if err != nil { + return nil, err + } + genesisVals = append(genesisVals, &GenesisKeyWithBLS{ + GenesisKey: *genesisKey, + PrivateKey: blsPrivKey, + }) + } + + return &GenesisValidators{Keys: genesisVals}, nil +} + +// GenesisValidatorSetWithPrivSigner generates a set with `numVals` genesis validators +// along with the privSigner, which will be in the 0th position of the return validator set +func GenesisValidatorSetWithPrivSigner(numVals int) (*GenesisValidators, *app.PrivSigner, error) { + ps, err := app.SetupTestPrivSigner() + if err != nil { + return nil, nil, err + } + signerGenesisKey, err := app.GenesisKeyFromPrivSigner(ps) + if err != nil { + return nil, nil, err + } + signerVal := &GenesisKeyWithBLS{ + GenesisKey: *signerGenesisKey, + PrivateKey: ps.WrappedPV.Key.BlsPrivKey, + } + genesisVals, err := GenesisValidatorSet(numVals) + if err != nil { + return nil, nil, err + } + genesisVals.Keys[0] = signerVal + + return genesisVals, ps, nil +} + +func GenerateGenesisKey() *checkpointingtypes.GenesisKey { accPrivKey := secp256k1.GenPrivKey() - tmValPrivKey := ed255192.GenPrivKey() + tmValPrivKey := cmted25519.GenPrivKey() blsPrivKey := bls12381.GenPrivKey() tmValPubKey := tmValPrivKey.PubKey() valPubKey, err := codec.FromCmtPubKeyInterface(tmValPubKey) @@ -27,7 +115,7 @@ func GenerateGenesisKey() *types.GenesisKey { panic(err) } - gk, err := types.NewGenesisKey(address, &blsPubKey, pop, valPubKey) + gk, err := checkpointingtypes.NewGenesisKey(address, &blsPubKey, pop, valPubKey) if err != nil { panic(err) } diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index a56690d69..f852b3bbd 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -37,7 +37,7 @@ func GetRandomRawBtcCheckpoint(r *rand.Rand) *txformat.RawBtcCheckpoint { rawCkpt := GenRandomRawCheckpoint(r) return &txformat.RawBtcCheckpoint{ Epoch: rawCkpt.EpochNum, - AppHash: *rawCkpt.AppHash, + BlockHash: *rawCkpt.BlockHash, BitMap: rawCkpt.Bitmap, SubmitterAddress: GenRandomByteArray(r, txformat.AddressLength), BlsSig: rawCkpt.BlsMultiSig.Bytes(), @@ -54,11 +54,11 @@ func GenRandomRawCheckpointWithMeta(r *rand.Rand) *types.RawCheckpointWithMeta { } func GenRandomRawCheckpoint(r *rand.Rand) *types.RawCheckpoint { - randomHashBytes := GenRandomAppHash(r) + randomHashBytes := GenRandomBlockHash(r) randomBLSSig := GenRandomBlsMultiSig(r) return &types.RawCheckpoint{ EpochNum: GenRandomEpochNum(r), - AppHash: &randomHashBytes, + BlockHash: &randomHashBytes, Bitmap: bitmap.New(types.BitmapBits), BlsMultiSig: &randomBLSSig, } @@ -118,8 +118,8 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe // ensure sufficient signers signerNum := n/3 + 1 epochNum := GenRandomEpochNum(r) - appHash := GenRandomAppHash(r) - msgBytes := types.GetSignBytes(epochNum, appHash) + blockHash := GenRandomBlockHash(r) + msgBytes := types.GetSignBytes(epochNum, blockHash) sigs := GenerateBLSSigs(privKeys[:signerNum], msgBytes) multiSig, _ := bls12381.AggrSigList(sigs) bm := bitmap.New(types.BitmapBits) @@ -128,7 +128,7 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe } btcCheckpoint := &types.RawCheckpoint{ EpochNum: epochNum, - AppHash: &appHash, + BlockHash: &blockHash, Bitmap: bm, BlsMultiSig: &multiSig, } @@ -136,7 +136,7 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe return btcCheckpoint } -func GenRandomAppHash(r *rand.Rand) types.AppHash { +func GenRandomBlockHash(r *rand.Rand) types.BlockHash { return GenRandomByteArray(r, types.HashSize) } diff --git a/testutil/datagen/vote_ext.go b/testutil/datagen/vote_ext.go new file mode 100644 index 000000000..6b880a133 --- /dev/null +++ b/testutil/datagen/vote_ext.go @@ -0,0 +1,37 @@ +package datagen + +import ( + "math/rand" + + abci "github.com/cometbft/cometbft/abci/types" + + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" +) + +func GenRandomVoteExtension( + epochNum, height uint64, + blockHash checkpointingtypes.BlockHash, + valSet *GenesisValidators, + r *rand.Rand, +) ([]abci.ExtendedVoteInfo, error) { + genesisKeys := valSet.GetGenesisKeys() + extendedVotes := make([]abci.ExtendedVoteInfo, 0, len(valSet.Keys)) + for i := 0; i < len(valSet.Keys); i++ { + sig := GenRandomBlsMultiSig(r) + ve := checkpointingtypes.VoteExtension{ + Signer: genesisKeys[i].ValidatorAddress, + BlockHash: blockHash, + EpochNum: epochNum, + Height: height, + BlsSig: &sig, + } + veBytes, err := ve.Marshal() + if err != nil { + return nil, err + } + veInfo := abci.ExtendedVoteInfo{VoteExtension: veBytes} + extendedVotes = append(extendedVotes, veInfo) + } + + return extendedVotes, nil +} diff --git a/testutil/helper/gen_blocks.go b/testutil/helper/gen_blocks.go new file mode 100644 index 000000000..7e738f5dc --- /dev/null +++ b/testutil/helper/gen_blocks.go @@ -0,0 +1,440 @@ +package helper + +import ( + "fmt" + "math/rand" + + "cosmossdk.io/core/header" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/merkle" + cmttypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/babylonchain/babylon/testutil/datagen" +) + +func (h *Helper) genAndApplyEmptyBlock() error { + prevHeight := h.App.LastBlockHeight() + newHeight := prevHeight + 1 + + // finalize block + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return err + } + valhash := CalculateValHash(valSet) + newHeader := cmttypes.Header{ + Height: newHeight, + ValidatorsHash: valhash, + NextValidatorsHash: valhash, + } + + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + Hash: newHeader.Hash(), + }) + if err != nil { + return err + } + + newHeader.AppHash = resp.AppHash + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + _, err = h.App.Commit() + if err != nil { + return err + } + + if newHeight == 1 { + // do it again + // TODO: Figure out why when ctx height is 1, ApplyEmptyBlockWithVoteExtension + // will still give ctx height 1 once, then start to increment + return h.genAndApplyEmptyBlock() + } + + return nil +} + +func (h *Helper) ApplyEmptyBlockWithVoteExtension(r *rand.Rand) (sdk.Context, error) { + emptyCtx := sdk.Context{} + if h.App.LastBlockHeight() == 0 { + if err := h.genAndApplyEmptyBlock(); err != nil { + return emptyCtx, err + } + } + valSetWithKeys := h.GenValidators + prevHeight := h.App.LastBlockHeight() + epoch := h.App.EpochingKeeper.GetEpoch(h.Ctx) + newHeight := prevHeight + 1 + + // 1. get previous vote extensions + prevEpoch := epoch.EpochNumber + blockHash := datagen.GenRandomBlockHash(r) + extendedVotes, err := h.getExtendedVotesFromValSet(prevEpoch, uint64(prevHeight), blockHash, valSetWithKeys) + if err != nil { + return emptyCtx, err + } + + // 2. create new header + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return emptyCtx, err + } + valhash := CalculateValHash(valSet) + newHeader := cmttypes.Header{ + Height: newHeight, + ValidatorsHash: valhash, + NextValidatorsHash: valhash, + LastBlockID: cmttypes.BlockID{ + Hash: datagen.GenRandomByteArray(r, 32), + }, + } + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + // 3. prepare proposal with previous BLS sigs + blockTxs := [][]byte{} + ppRes, err := h.App.PrepareProposal(&abci.RequestPrepareProposal{ + LocalLastCommit: abci.ExtendedCommitInfo{Votes: extendedVotes}, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + if len(ppRes.Txs) > 0 { + blockTxs = ppRes.Txs + } + _, err = h.App.ProcessProposal(&abci.RequestProcessProposal{ + Txs: ppRes.Txs, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + // 4. finalize block + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Txs: blockTxs, + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + Hash: newHeader.Hash(), + }) + if err != nil { + return emptyCtx, err + } + + newHeader.AppHash = resp.AppHash + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + _, err = h.App.Commit() + if err != nil { + return emptyCtx, err + } + + return h.Ctx, nil +} + +func (h *Helper) ApplyEmptyBlockWithValSet(r *rand.Rand, valSetWithKeys *datagen.GenesisValidators) (sdk.Context, error) { + emptyCtx := sdk.Context{} + if h.App.LastBlockHeight() == 0 { + if err := h.genAndApplyEmptyBlock(); err != nil { + return emptyCtx, err + } + } + prevHeight := h.App.LastBlockHeight() + epoch := h.App.EpochingKeeper.GetEpoch(h.Ctx) + newHeight := prevHeight + 1 + + // 1. get previous vote extensions + prevEpoch := epoch.EpochNumber + blockHash := datagen.GenRandomBlockHash(r) + extendedVotes, err := h.getExtendedVotesFromValSet(prevEpoch, uint64(prevHeight), blockHash, valSetWithKeys) + if err != nil { + return emptyCtx, err + } + + // 2. create new header + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return emptyCtx, err + } + valhash := CalculateValHash(valSet) + newHeader := cmttypes.Header{ + Height: newHeight, + ValidatorsHash: valhash, + NextValidatorsHash: valhash, + LastBlockID: cmttypes.BlockID{ + Hash: datagen.GenRandomByteArray(r, 32), + }, + } + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + // 3. prepare proposal with previous BLS sigs + blockTxs := [][]byte{} + ppRes, err := h.App.PrepareProposal(&abci.RequestPrepareProposal{ + LocalLastCommit: abci.ExtendedCommitInfo{Votes: extendedVotes}, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + if len(ppRes.Txs) > 0 { + blockTxs = ppRes.Txs + } + _, err = h.App.ProcessProposal(&abci.RequestProcessProposal{ + Txs: ppRes.Txs, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + // 4. finalize block + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Txs: blockTxs, + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + Hash: newHeader.Hash(), + }) + if err != nil { + return emptyCtx, err + } + + newHeader.AppHash = resp.AppHash + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + _, err = h.App.Commit() + if err != nil { + return emptyCtx, err + } + + return h.Ctx, nil +} + +func (h *Helper) ApplyEmptyBlockWithInvalidBLSSig(r *rand.Rand) (sdk.Context, error) { + emptyCtx := sdk.Context{} + if h.App.LastBlockHeight() == 0 { + if err := h.genAndApplyEmptyBlock(); err != nil { + return emptyCtx, err + } + } + valSetWithKeys := h.GenValidators + prevHeight := h.App.LastBlockHeight() + epoch := h.App.EpochingKeeper.GetEpoch(h.Ctx) + newHeight := prevHeight + 1 + + // 1. get vote extensions with invalid BLS signature + prevEpoch := epoch.EpochNumber + blockHash := datagen.GenRandomBlockHash(r) + extendedVotes, err := datagen.GenRandomVoteExtension(prevEpoch, uint64(prevHeight), blockHash, valSetWithKeys, r) + if err != nil { + return emptyCtx, err + } + + res, err := h.App.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{ + Hash: blockHash, + Height: prevHeight, + VoteExtension: extendedVotes[0].VoteExtension, + }) + if err != nil || !res.IsAccepted() { + return emptyCtx, fmt.Errorf("invalid vote extension") + } + + // 2. create new header + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return emptyCtx, err + } + valhash := CalculateValHash(valSet) + newHeader := cmttypes.Header{ + Height: newHeight, + ValidatorsHash: valhash, + NextValidatorsHash: valhash, + LastBlockID: cmttypes.BlockID{ + Hash: datagen.GenRandomByteArray(r, 32), + }, + } + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + // 3. prepare proposal with previous BLS sigs + blockTxs := [][]byte{} + ppRes, err := h.App.PrepareProposal(&abci.RequestPrepareProposal{ + LocalLastCommit: abci.ExtendedCommitInfo{Votes: extendedVotes}, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + if len(ppRes.Txs) > 0 { + blockTxs = ppRes.Txs + } + _, err = h.App.ProcessProposal(&abci.RequestProcessProposal{ + Txs: ppRes.Txs, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + // 4. finalize block + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Txs: blockTxs, + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + Hash: newHeader.Hash(), + }) + if err != nil { + return emptyCtx, err + } + + newHeader.AppHash = resp.AppHash + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + _, err = h.App.Commit() + if err != nil { + return emptyCtx, err + } + + return h.Ctx, nil +} + +func (h *Helper) ApplyEmptyBlockWithValidBLSSig(r *rand.Rand) (sdk.Context, error) { + emptyCtx := sdk.Context{} + if h.App.LastBlockHeight() == 0 { + if err := h.genAndApplyEmptyBlock(); err != nil { + return emptyCtx, err + } + } + prevHeight := h.App.LastBlockHeight() + newHeight := prevHeight + 1 + + // 1. get vote extensions with invalid BLS signature + blockHash := datagen.GenRandomBlockHash(r) + evRes, err := h.App.ExtendVote(h.Ctx, &abci.RequestExtendVote{ + Hash: blockHash, + Height: prevHeight, + }) + if err != nil { + return emptyCtx, err + } + + valAddr := h.GenValidators.GetGenesisKeys()[0].ValPubkey.Address().Bytes() + res, err := h.App.VerifyVoteExtension(&abci.RequestVerifyVoteExtension{ + Hash: blockHash, + Height: prevHeight, + VoteExtension: evRes.VoteExtension, + ValidatorAddress: valAddr, + }) + if err != nil || !res.IsAccepted() { + return emptyCtx, fmt.Errorf("invalid vote extension") + } + + // 2. create new header + valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) + if err != nil { + return emptyCtx, err + } + valhash := CalculateValHash(valSet) + newHeader := cmttypes.Header{ + Height: newHeight, + ValidatorsHash: valhash, + NextValidatorsHash: valhash, + LastBlockID: cmttypes.BlockID{ + Hash: datagen.GenRandomByteArray(r, 32), + }, + } + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + // 3. prepare proposal with previous BLS sigs + extendedVotes := make([]abci.ExtendedVoteInfo, 0) + extendedVotes = append(extendedVotes, abci.ExtendedVoteInfo{ + VoteExtension: evRes.VoteExtension, + }) + blockTxs := [][]byte{} + ppRes, err := h.App.PrepareProposal(&abci.RequestPrepareProposal{ + LocalLastCommit: abci.ExtendedCommitInfo{Votes: extendedVotes}, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + if len(ppRes.Txs) > 0 { + blockTxs = ppRes.Txs + } + _, err = h.App.ProcessProposal(&abci.RequestProcessProposal{ + Txs: ppRes.Txs, + Height: newHeight, + }) + if err != nil { + return emptyCtx, err + } + + // 4. finalize block + resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Txs: blockTxs, + Height: newHeader.Height, + NextValidatorsHash: newHeader.NextValidatorsHash, + Hash: newHeader.Hash(), + }) + if err != nil { + return emptyCtx, err + } + + newHeader.AppHash = resp.AppHash + h.Ctx = h.Ctx.WithHeaderInfo(header.Info{ + Height: newHeader.Height, + AppHash: resp.AppHash, + Hash: newHeader.Hash(), + }).WithBlockHeader(*newHeader.ToProto()) + + _, err = h.App.Commit() + if err != nil { + return emptyCtx, err + } + + return h.Ctx, nil +} + +// CalculateValHash calculate validator hash and new header +// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/test_helpers.go#L156-L163) +func CalculateValHash(valSet []stakingtypes.Validator) []byte { + bzs := make([][]byte, len(valSet)) + for i, val := range valSet { + consAddr, _ := val.GetConsAddr() + bzs[i] = consAddr + } + return merkle.HashFromByteSlices(bzs) +} diff --git a/testutil/helper/helper.go b/testutil/helper/helper.go index 6c923ec70..e9dc02ce5 100644 --- a/testutil/helper/helper.go +++ b/testutil/helper/helper.go @@ -1,37 +1,31 @@ package helper import ( - "math/rand" "testing" "cosmossdk.io/core/header" - "cosmossdk.io/math" - "github.com/babylonchain/babylon/app" - appparams "github.com/babylonchain/babylon/app/params" - "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/privval" - checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/babylonchain/babylon/x/epoching/keeper" - "github.com/babylonchain/babylon/x/epoching/types" abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/crypto/ed25519" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" cosmosed "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + + "github.com/babylonchain/babylon/crypto/bls12381" + "github.com/babylonchain/babylon/testutil/datagen" + checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" + + "cosmossdk.io/math" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - proto "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" -) -type GenesisValidators struct { - GenesisKeys []*checkpointingtypes.GenesisKey - BlsPrivKeys []bls12381.PrivateKey -} + "github.com/babylonchain/babylon/app" + appparams "github.com/babylonchain/babylon/app/params" + "github.com/babylonchain/babylon/x/epoching/keeper" + "github.com/babylonchain/babylon/x/epoching/types" +) // Helper is a structure which wraps the entire app and exposes functionalities for testing the epoching module type Helper struct { @@ -43,51 +37,25 @@ type Helper struct { QueryClient types.QueryClient GenAccs []authtypes.GenesisAccount - GenValidators *GenesisValidators + GenValidators *datagen.GenesisValidators } // NewHelper creates the helper for testing the epoching module func NewHelper(t *testing.T) *Helper { - valSet, err := GenesisValidatorSet(1) + valSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(1) require.NoError(t, err) - // generate genesis account - acc := authtypes.NewBaseAccount(valSet.GenesisKeys[0].ValPubkey.Address().Bytes(), valSet.GenesisKeys[0].ValPubkey, 0, 0) - balance := banktypes.Balance{ - Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(100000000000000))), - } - app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, []authtypes.GenesisAccount{acc}, balance) - ctx := app.BaseApp.NewContext(false).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 - - epochingKeeper := app.EpochingKeeper - - querier := keeper.Querier{Keeper: epochingKeeper} - queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) - types.RegisterQueryServer(queryHelper, querier) - queryClient := types.NewQueryClient(queryHelper) - msgSrvr := keeper.NewMsgServerImpl(epochingKeeper) - - return &Helper{ - t, - ctx, - app, - msgSrvr, - queryClient, - nil, - valSet, - } + return NewHelperWithValSet(t, valSet, privSigner) } // NewHelperWithValSet is same as NewHelper, except that it creates a set of validators -func NewHelperWithValSet(t *testing.T) *Helper { - // generate the validator set with 10 validators - valSet, err := GenesisValidatorSet(10) - require.NoError(t, err) - +// the privSigner is the 0th validator in valSet +func NewHelperWithValSet(t *testing.T, valSet *datagen.GenesisValidators, privSigner *app.PrivSigner) *Helper { // generate the genesis account - senderPrivKey := secp256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + signerPubKey := privSigner.WrappedPV.Key.PubKey + acc := authtypes.NewBaseAccount(signerPubKey.Address().Bytes(), &cosmosed.PubKey{Key: signerPubKey.Bytes()}, 0, 0) + privSigner.WrappedPV.Key.DelegatorAddress = acc.Address + valSet.Keys[0].ValidatorAddress = privSigner.WrappedPV.GetAddress().String() // ensure the genesis account has a sufficient amount of tokens balance := banktypes.Balance{ Address: acc.GetAddress().String(), @@ -96,8 +64,8 @@ func NewHelperWithValSet(t *testing.T) *Helper { GenAccs := []authtypes.GenesisAccount{acc} // setup the app and ctx - app := app.SetupWithGenesisValSet(t, valSet.GenesisKeys, GenAccs, balance) - ctx := app.BaseApp.NewContext(false).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 + app := app.SetupWithGenesisValSet(t, valSet.GetGenesisKeys(), privSigner, GenAccs, balance) + ctx := app.BaseApp.NewContext(false).WithBlockHeight(1).WithHeaderInfo(header.Info{Height: 1}) // NOTE: height is 1 // get necessary subsets of the app/keeper epochingKeeper := app.EpochingKeeper @@ -122,47 +90,29 @@ func (h *Helper) NoError(err error) { require.NoError(h.t, err) } -// GenAndApplyEmptyBlock generates a new empty block and appends it to the current blockchain -func (h *Helper) GenAndApplyEmptyBlock(r *rand.Rand) (sdk.Context, error) { - newHeight := h.App.LastBlockHeight() + 1 - valSet, err := h.App.StakingKeeper.GetLastValidators(h.Ctx) - if err != nil { - return sdk.Context{}, err - } - valhash := CalculateValHash(valSet) - newHeader := tmproto.Header{ - Height: newHeight, - ValidatorsHash: valhash, - NextValidatorsHash: valhash, - } - - resp, err := h.App.FinalizeBlock(&abci.RequestFinalizeBlock{ - Height: newHeader.Height, - NextValidatorsHash: newHeader.NextValidatorsHash, - }) - if err != nil { - return sdk.Context{}, err - } - - newHeader.AppHash = resp.AppHash - ctxDuringHeight := h.Ctx.WithHeaderInfo(header.Info{ - Height: newHeader.Height, - AppHash: resp.AppHash, - }).WithBlockHeader(newHeader) - - _, err = h.App.Commit() - if err != nil { - return sdk.Context{}, err - } - - if newHeight == 1 { - // do it again - // TODO: Figure out why when ctx height is 1, GenAndApplyEmptyBlock - // will still give ctx height 1 once, then start to increment - return h.GenAndApplyEmptyBlock(r) +func (h *Helper) getExtendedVotesFromValSet(epochNum, height uint64, blockHash checkpointingtypes.BlockHash, valSet *datagen.GenesisValidators) ([]abci.ExtendedVoteInfo, error) { + blsPrivKeys := valSet.GetBLSPrivKeys() + genesisKeys := valSet.GetGenesisKeys() + signBytes := checkpointingtypes.GetSignBytes(epochNum, blockHash) + extendedVotes := make([]abci.ExtendedVoteInfo, 0, len(valSet.Keys)) + for i, sk := range blsPrivKeys { + sig := bls12381.Sign(sk, signBytes) + ve := checkpointingtypes.VoteExtension{ + Signer: genesisKeys[i].ValidatorAddress, + BlockHash: blockHash, + EpochNum: epochNum, + Height: height, + BlsSig: &sig, + } + veBytes, err := ve.Marshal() + if err != nil { + return nil, err + } + veInfo := abci.ExtendedVoteInfo{VoteExtension: veBytes} + extendedVotes = append(extendedVotes, veInfo) } - return ctxDuringHeight, nil + return extendedVotes, nil } // WrappedDelegate calls handler to delegate stake for a validator @@ -243,36 +193,3 @@ func (h *Helper) CheckDelegator(delegator sdk.AccAddress, val sdk.ValAddress, fo _, ok := h.App.StakingKeeper.GetDelegation(h.Ctx, delegator, val) require.Equal(h.t, ok, found) } - -// GenesisValidatorSet generates a set with `numVals` genesis validators -func GenesisValidatorSet(numVals int) (*GenesisValidators, error) { - genesisKeys := make([]*checkpointingtypes.GenesisKey, 0, numVals) - blsPrivKeys := make([]bls12381.PrivateKey, 0, numVals) - for i := 0; i < numVals; i++ { - blsPrivKey := bls12381.GenPrivKey() - // create validator set with single validator - valKeys, err := privval.NewValidatorKeys(ed25519.GenPrivKey(), blsPrivKey) - if err != nil { - return nil, err - } - valPubkey, err := cryptocodec.FromCmtPubKeyInterface(valKeys.ValPubkey) - if err != nil { - return nil, err - } - genesisKey, err := checkpointingtypes.NewGenesisKey( - sdk.ValAddress(valKeys.ValPubkey.Address()), - &valKeys.BlsPubkey, - valKeys.PoP, - &cosmosed.PubKey{Key: valPubkey.Bytes()}, - ) - if err != nil { - return nil, err - } - genesisKeys = append(genesisKeys, genesisKey) - blsPrivKeys = append(blsPrivKeys, blsPrivKey) - } - return &GenesisValidators{ - GenesisKeys: genesisKeys, - BlsPrivKeys: blsPrivKeys, - }, nil -} diff --git a/testutil/helper/validator.go b/testutil/helper/validator.go index ab7ba4c65..a9806402b 100644 --- a/testutil/helper/validator.go +++ b/testutil/helper/validator.go @@ -1,17 +1,2 @@ package helper -import ( - "github.com/cometbft/cometbft/crypto/merkle" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -// CalculateValHash calculate validator hash and new header -// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/simapp/test_helpers.go#L156-L163) -func CalculateValHash(valSet []stakingtypes.Validator) []byte { - bzs := make([][]byte, len(valSet)) - for i, val := range valSet { - consAddr, _ := val.GetConsAddr() - bzs[i] = consAddr - } - return merkle.HashFromByteSlices(bzs) -} diff --git a/testutil/keeper/checkpointing.go b/testutil/keeper/checkpointing.go index 194a941e1..426d27a74 100644 --- a/testutil/keeper/checkpointing.go +++ b/testutil/keeper/checkpointing.go @@ -11,17 +11,17 @@ import ( "cosmossdk.io/log" "cosmossdk.io/store" storetypes "cosmossdk.io/store/types" - "github.com/babylonchain/babylon/x/checkpointing/keeper" - "github.com/babylonchain/babylon/x/checkpointing/types" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/x/checkpointing/keeper" + "github.com/babylonchain/babylon/x/checkpointing/types" ) -func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.BlsSigner, cliCtx client.Context) (*keeper.Keeper, sdk.Context, *codec.ProtoCodec) { +func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.BlsSigner) (*keeper.Keeper, sdk.Context, *codec.ProtoCodec) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() @@ -38,7 +38,6 @@ func CheckpointingKeeper(t testing.TB, ek types.EpochingKeeper, signer keeper.Bl runtime.NewKVStoreService(storeKey), signer, ek, - cliCtx, ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) diff --git a/testutil/mocks/checkpointing_expected_keepers.go b/testutil/mocks/checkpointing_expected_keepers.go index 45aeab84f..409a1071b 100644 --- a/testutil/mocks/checkpointing_expected_keepers.go +++ b/testutil/mocks/checkpointing_expected_keepers.go @@ -10,6 +10,7 @@ import ( types "github.com/babylonchain/babylon/x/checkpointing/types" types0 "github.com/babylonchain/babylon/x/epoching/types" + crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" types1 "github.com/cosmos/cosmos-sdk/types" types2 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" @@ -64,6 +65,21 @@ func (mr *MockEpochingKeeperMockRecorder) EnqueueMsg(ctx, msg interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnqueueMsg", reflect.TypeOf((*MockEpochingKeeper)(nil).EnqueueMsg), ctx, msg) } +// GetAppHash mocks base method. +func (m *MockEpochingKeeper) GetAppHash(ctx context.Context, height uint64) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAppHash", ctx, height) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAppHash indicates an expected call of GetAppHash. +func (mr *MockEpochingKeeperMockRecorder) GetAppHash(ctx, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAppHash", reflect.TypeOf((*MockEpochingKeeper)(nil).GetAppHash), ctx, height) +} + // GetEpoch mocks base method. func (m *MockEpochingKeeper) GetEpoch(ctx context.Context) *types0.Epoch { m.ctrl.T.Helper() @@ -78,6 +94,21 @@ func (mr *MockEpochingKeeperMockRecorder) GetEpoch(ctx interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEpoch", reflect.TypeOf((*MockEpochingKeeper)(nil).GetEpoch), ctx) } +// GetPubKeyByConsAddr mocks base method. +func (m *MockEpochingKeeper) GetPubKeyByConsAddr(ctx context.Context, consAddr types1.ConsAddress) (crypto.PublicKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPubKeyByConsAddr", ctx, consAddr) + ret0, _ := ret[0].(crypto.PublicKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPubKeyByConsAddr indicates an expected call of GetPubKeyByConsAddr. +func (mr *MockEpochingKeeperMockRecorder) GetPubKeyByConsAddr(ctx, consAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPubKeyByConsAddr", reflect.TypeOf((*MockEpochingKeeper)(nil).GetPubKeyByConsAddr), ctx, consAddr) +} + // GetTotalVotingPower mocks base method. func (m *MockEpochingKeeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { m.ctrl.T.Helper() diff --git a/x/checkpointing/abci.go b/x/checkpointing/abci.go index ec4c6793f..15f5d4db9 100644 --- a/x/checkpointing/abci.go +++ b/x/checkpointing/abci.go @@ -10,19 +10,13 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" ) // BeginBlocker is called at the beginning of every block. -// Upon each BeginBlock, if reaching the second block after the epoch begins, then -// - extract the AppHash from the block -// - create a raw checkpoint with the status of ACCUMULATING -// - start a BLS signer which creates a BLS sig transaction and distributes it to the network +// Upon each BeginBlock, if reaching the first block after the epoch begins +// then we store the current validator set with BLS keys func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - sdkCtx := sdk.UnwrapSDKContext(ctx) - - // if this block is the second block of an epoch epoch := k.GetEpoch(ctx) if epoch.IsFirstBlock(ctx) { err := k.InitValidatorBLSSet(ctx) @@ -30,34 +24,5 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { panic(fmt.Errorf("failed to store validator BLS set: %w", err)) } } - if epoch.IsSecondBlock(ctx) { - // note that this epochNum is obtained after the BeginBlocker of the epoching module is executed - // meaning that the epochNum has been incremented upon a new epoch - - appHash := sdkCtx.HeaderInfo().AppHash - ckpt, err := k.BuildRawCheckpoint(ctx, epoch.EpochNumber-1, appHash) - if err != nil { - panic("failed to generate a raw checkpoint") - } - - // emit BeginEpoch event - err = sdkCtx.EventManager().EmitTypedEvent( - &types.EventCheckpointAccumulating{ - Checkpoint: ckpt, - }, - ) - if err != nil { - panic(err) - } - curValSet := k.GetValidatorSet(ctx, epoch.EpochNumber-1) - - go func() { - err := k.SendBlsSig(ctx, epoch.EpochNumber-1, appHash, curValSet) - if err != nil { - // failing to send a BLS-sig causes a panicking situation - panic(err) - } - }() - } return nil } diff --git a/x/checkpointing/client/cli/tx.go b/x/checkpointing/client/cli/tx.go index bfcdd7eff..371caa21a 100644 --- a/x/checkpointing/client/cli/tx.go +++ b/x/checkpointing/client/cli/tx.go @@ -4,17 +4,15 @@ import ( "fmt" "os" "path/filepath" - "strconv" "strings" "cosmossdk.io/core/address" - appparams "github.com/babylonchain/babylon/app/params" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" - "github.com/babylonchain/babylon/crypto/bls12381" + appparams "github.com/babylonchain/babylon/app/params" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" cosmoscli "github.com/cosmos/cosmos-sdk/x/staking/client/cli" "github.com/spf13/cobra" @@ -33,54 +31,11 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } - cmd.AddCommand(CmdTxAddBlsSig()) cmd.AddCommand(CmdWrappedCreateValidator(authcodec.NewBech32Codec(appparams.Bech32PrefixValAddr))) return cmd } -func CmdTxAddBlsSig() *cobra.Command { - cmd := &cobra.Command{ - Use: "submit [epoch_number] [app_hash] [bls_sig] [signer address]", - Short: "submit a BLS signature", - Args: cobra.ExactArgs(4), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - epoch_num, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - - appHash, err := types.NewAppHashFromHex(args[1]) - if err != nil { - return err - } - - blsSig, err := bls12381.NewBLSSigFromHex(args[2]) - if err != nil { - return err - } - - addr, err := sdk.ValAddressFromBech32(args[3]) - if err != nil { - return err - } - - msg := types.NewMsgAddBlsSig(clientCtx.GetFromAddress(), epoch_num, appHash, blsSig, addr) - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - - return cmd -} - func CmdWrappedCreateValidator(valAddrCodec address.Codec) *cobra.Command { cmd := cosmoscli.NewCreateValidatorCmd(valAddrCodec) cmd.Long = strings.TrimSpace(`create-validator will create a new validator initialized diff --git a/x/checkpointing/keeper/bls_signer.go b/x/checkpointing/keeper/bls_signer.go index 71fcf2f67..54e3ba974 100644 --- a/x/checkpointing/keeper/bls_signer.go +++ b/x/checkpointing/keeper/bls_signer.go @@ -1,17 +1,10 @@ package keeper import ( - "context" - "fmt" - "time" - - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - + "github.com/cometbft/cometbft/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/babylonchain/babylon/client/tx" "github.com/babylonchain/babylon/crypto/bls12381" - "github.com/babylonchain/babylon/types/retry" "github.com/babylonchain/babylon/x/checkpointing/types" ) @@ -19,47 +12,24 @@ type BlsSigner interface { GetAddress() sdk.ValAddress SignMsgWithBls(msg []byte) (bls12381.Signature, error) GetBlsPubkey() (bls12381.PublicKey, error) + GetValidatorPubkey() (crypto.PubKey, error) } -// SendBlsSig prepares a BLS signature message and sends it to Comet -func (k Keeper) SendBlsSig(ctx context.Context, epochNum uint64, appHash types.AppHash, valSet epochingtypes.ValidatorSet) error { - sdkCtx := sdk.UnwrapSDKContext(ctx) - // get self address - addr := k.blsSigner.GetAddress() - - // check if itself is the validator - _, _, err := valSet.FindValidatorWithIndex(addr) - if err != nil { - // only send the BLS sig when the node itself is a validator, not being a validator is not an error - return nil - } - +// SignBLS signs a BLS signature over the given information +func (k Keeper) SignBLS(epochNum uint64, blockHash types.BlockHash) (bls12381.Signature, error) { // get BLS signature by signing - signBytes := types.GetSignBytes(epochNum, appHash) - blsSig, err := k.blsSigner.SignMsgWithBls(signBytes) - if err != nil { - return err - } + signBytes := types.GetSignBytes(epochNum, blockHash) + return k.blsSigner.SignMsgWithBls(signBytes) +} - // create MsgAddBlsSig message - msg := types.NewMsgAddBlsSig(k.clientCtx.GetFromAddress(), epochNum, appHash, blsSig, addr) +func (k Keeper) GetBLSSignerAddress() sdk.ValAddress { + return k.blsSigner.GetAddress() +} - // keep sending the message to Comet until success or timeout - // TODO should read the parameters from config file - var res *sdk.TxResponse - err = retry.Do(1*time.Second, 1*time.Minute, func() error { - res, err = tx.SendMsgToComet(ctx, k.clientCtx, msg) - if err != nil { - return err - } - return nil - }) +func (k Keeper) GetValidatorAddress() sdk.ValAddress { + pk, err := k.blsSigner.GetValidatorPubkey() if err != nil { - sdkCtx.Logger().Error(fmt.Sprintf("Failed to send the BLS sig tx for epoch %v: %v", epochNum, err)) - return err + panic(err) } - - sdkCtx.Logger().Info(fmt.Sprintf("Successfully sent BLS-sig tx for epoch %d, tx hash: %s, gas used: %d, gas wanted: %d", epochNum, res.TxHash, res.GasUsed, res.GasWanted)) - - return nil + return sdk.ValAddress(pk.Address()) } diff --git a/x/checkpointing/keeper/grpc_query_bls_test.go b/x/checkpointing/keeper/grpc_query_bls_test.go index 6ba88caeb..8ec938bd8 100644 --- a/x/checkpointing/keeper/grpc_query_bls_test.go +++ b/x/checkpointing/keeper/grpc_query_bls_test.go @@ -6,14 +6,15 @@ import ( "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/types/query" - "github.com/stretchr/testify/require" ) // FuzzQueryBLSKeySet does the following checks @@ -67,7 +68,7 @@ func FuzzQueryBLSKeySet(f *testing.F) { // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = ek.GetEpoch(ctx) diff --git a/x/checkpointing/keeper/grpc_query_checkpoint_test.go b/x/checkpointing/keeper/grpc_query_checkpoint_test.go index 10e21715a..ef01034a4 100644 --- a/x/checkpointing/keeper/grpc_query_checkpoint_test.go +++ b/x/checkpointing/keeper/grpc_query_checkpoint_test.go @@ -15,7 +15,6 @@ import ( "github.com/babylonchain/babylon/x/checkpointing/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/stretchr/testify/require" "github.com/babylonchain/babylon/testutil/datagen" @@ -26,7 +25,7 @@ func FuzzQueryEpoch(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil) // test querying a raw checkpoint with epoch number mockCkptWithMeta := datagen.GenRandomRawCheckpointWithMeta(r) @@ -53,7 +52,7 @@ func FuzzQueryRawCheckpoints(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil) // add a random number of checkpoints checkpoints := datagen.GenRandomSequenceRawCheckpointsWithMeta(r) @@ -93,7 +92,7 @@ func FuzzQueryStatusCount(f *testing.F) { defer ctrl.Finish() ek := mocks.NewMockEpochingKeeper(ctrl) ek.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: tipEpoch + 1}) - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil) expectedCounts := make(map[string]uint64) epochCount := uint64(r.Int63n(int64(tipEpoch))) for e, ckpt := range checkpoints { @@ -130,7 +129,7 @@ func FuzzQueryLastCheckpointWithStatus(f *testing.F) { defer ctrl.Finish() ek := mocks.NewMockEpochingKeeper(ctrl) ek.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: tipEpoch}).AnyTimes() - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil) checkpoints := datagen.GenSequenceRawCheckpointsWithMeta(r, tipEpoch) finalizedEpoch := datagen.RandomInt(r, int(tipEpoch)) for e := uint64(0); e < tipEpoch; e++ { @@ -173,7 +172,7 @@ func FuzzQueryRawCheckpointList(f *testing.F) { defer ctrl.Finish() ek := mocks.NewMockEpochingKeeper(ctrl) ek.EXPECT().GetEpoch(gomock.Any()).Return(&epochingtypes.Epoch{EpochNumber: tipEpoch}).AnyTimes() - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil) checkpoints := datagen.GenSequenceRawCheckpointsWithMeta(r, tipEpoch) finalizedEpoch := datagen.RandomInt(r, int(tipEpoch)) diff --git a/x/checkpointing/keeper/keeper.go b/x/checkpointing/keeper/keeper.go index 299e9df30..d5262b4e5 100644 --- a/x/checkpointing/keeper/keeper.go +++ b/x/checkpointing/keeper/keeper.go @@ -2,17 +2,19 @@ package keeper import ( "context" - corestoretypes "cosmossdk.io/core/store" "errors" "fmt" + corestoretypes "cosmossdk.io/core/store" + txformat "github.com/babylonchain/babylon/btctxformatter" "cosmossdk.io/log" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/x/checkpointing/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" @@ -25,7 +27,6 @@ type ( blsSigner BlsSigner epochingKeeper types.EpochingKeeper hooks types.CheckpointingHooks - clientCtx client.Context } ) @@ -34,7 +35,6 @@ func NewKeeper( storeService corestoretypes.KVStoreService, signer BlsSigner, ek types.EpochingKeeper, - clientCtx client.Context, ) Keeper { return Keeper{ cdc: cdc, @@ -42,7 +42,6 @@ func NewKeeper( blsSigner: signer, epochingKeeper: ek, hooks: nil, - clientCtx: clientCtx, } } @@ -61,43 +60,42 @@ func (k *Keeper) SetHooks(sh types.CheckpointingHooks) *Keeper { return k } -// addBlsSig adds a BLS signature to the raw checkpoint and updates the status -// if sufficient signatures are accumulated for the epoch. -func (k Keeper) addBlsSig(ctx context.Context, sig *types.BlsSig) error { - // assuming stateless checks have done in Antehandler - sdkCtx := sdk.UnwrapSDKContext(ctx) - - // get raw checkpoint - ckptWithMeta, err := k.GetRawCheckpoint(ctx, sig.GetEpochNum()) - if err != nil { - return err +func (k Keeper) SealCheckpoint(ctx context.Context, ckptWithMeta *types.RawCheckpointWithMeta) error { + if ckptWithMeta.Status != types.Sealed { + return fmt.Errorf("the checkpoint is not Sealed") } - // the checkpoint is not accumulating - if ckptWithMeta.Status != types.Accumulating { - return nil + sdkCtx := sdk.UnwrapSDKContext(ctx) + // record state update of Sealed + ckptWithMeta.RecordStateUpdate(ctx, types.Sealed) + // log in console + k.Logger(sdkCtx).Info(fmt.Sprintf("Checkpointing: checkpoint for epoch %v is Sealed", ckptWithMeta.Ckpt.EpochNum)) + // emit event + if err := sdkCtx.EventManager().EmitTypedEvent( + &types.EventCheckpointSealed{Checkpoint: ckptWithMeta}, + ); err != nil { + panic(fmt.Errorf("failed to emit checkpoint sealed event for epoch %v", ckptWithMeta.Ckpt.EpochNum)) } - if !sig.AppHash.Equal(*ckptWithMeta.Ckpt.AppHash) { - // processed BlsSig message is for invalid last commit hash - return types.ErrInvalidAppHash - } + // if reaching this line, it means ckptWithMeta is updated, + // and we need to write the updated ckptWithMeta back to KVStore + return k.AddRawCheckpoint(ctx, ckptWithMeta) +} +func (k Keeper) VerifyBLSSig(ctx context.Context, sig *types.BlsSig) error { // get signer's address signerAddr, err := sdk.ValAddressFromBech32(sig.SignerAddress) if err != nil { return err } - // get validators for the epoch - vals := k.GetValidatorSet(ctx, sig.GetEpochNum()) signerBlsKey, err := k.GetBlsPubKey(ctx, signerAddr) if err != nil { return err } // verify BLS sig - signBytes := types.GetSignBytes(sig.GetEpochNum(), *sig.AppHash) + signBytes := types.GetSignBytes(sig.GetEpochNum(), *sig.BlockHash) ok, err := bls12381.Verify(*sig.BlsSig, signerBlsKey, signBytes) if err != nil { return err @@ -106,33 +104,18 @@ func (k Keeper) addBlsSig(ctx context.Context, sig *types.BlsSig) error { return types.ErrInvalidBlsSignature } - // accumulate BLS signatures - err = ckptWithMeta.Accumulate(vals, signerAddr, signerBlsKey, *sig.BlsSig, k.GetTotalVotingPower(ctx, sig.GetEpochNum())) - if err != nil { - return err - } + return nil +} - if ckptWithMeta.Status == types.Sealed { - // emit event - err = sdkCtx.EventManager().EmitTypedEvent( - &types.EventCheckpointSealed{Checkpoint: ckptWithMeta}, - ) - if err != nil { - k.Logger(sdkCtx).Error("failed to emit checkpoint sealed event for epoch %v", ckptWithMeta.Ckpt.EpochNum) - } - // record state update of Sealed - ckptWithMeta.RecordStateUpdate(ctx, types.Sealed) - // log in console - k.Logger(sdkCtx).Info(fmt.Sprintf("Checkpointing: checkpoint for epoch %v is Sealed", ckptWithMeta.Ckpt.EpochNum)) - } +func (k Keeper) GetVotingPowerByAddress(ctx context.Context, epochNum uint64, valAddr sdk.ValAddress) (int64, error) { + vals := k.GetValidatorSet(ctx, epochNum) - // if reaching this line, it means ckptWithMeta is updated, - // and we need to write the updated ckptWithMeta back to KVStore - if err = k.UpdateCheckpoint(ctx, ckptWithMeta); err != nil { - return err + v, _, err := vals.FindValidatorWithIndex(valAddr) + if err != nil { + return 0, err } - return nil + return v.Power, nil } func (k Keeper) GetRawCheckpoint(ctx context.Context, epochNum uint64) (*types.RawCheckpointWithMeta, error) { @@ -152,8 +135,8 @@ func (k Keeper) AddRawCheckpoint(ctx context.Context, ckptWithMeta *types.RawChe return k.CheckpointsState(ctx).CreateRawCkptWithMeta(ckptWithMeta) } -func (k Keeper) BuildRawCheckpoint(ctx context.Context, epochNum uint64, appHash types.AppHash) (*types.RawCheckpointWithMeta, error) { - ckptWithMeta := types.NewCheckpointWithMeta(types.NewCheckpoint(epochNum, appHash), types.Accumulating) +func (k Keeper) BuildRawCheckpoint(ctx context.Context, epochNum uint64, blockHash types.BlockHash) (*types.RawCheckpointWithMeta, error) { + ckptWithMeta := types.NewCheckpointWithMeta(types.NewCheckpoint(epochNum, blockHash), types.Accumulating) ckptWithMeta.RecordStateUpdate(ctx, types.Accumulating) // record the state update of Accumulating err := k.AddRawCheckpoint(ctx, ckptWithMeta) if err != nil { @@ -164,6 +147,38 @@ func (k Keeper) BuildRawCheckpoint(ctx context.Context, epochNum uint64, appHash return ckptWithMeta, nil } +func (k Keeper) VerifyRawCheckpoint(ctx context.Context, ckpt *types.RawCheckpoint) error { + // check whether sufficient voting power is accumulated + // and verify if the multi signature is valid + totalPower := k.GetTotalVotingPower(ctx, ckpt.EpochNum) + signerSet, err := k.GetValidatorSet(ctx, ckpt.EpochNum).FindSubset(ckpt.Bitmap) + if err != nil { + return fmt.Errorf("failed to get the signer set via bitmap of epoch %d: %w", ckpt.EpochNum, err) + } + var sum int64 + signersPubKeys := make([]bls12381.PublicKey, len(signerSet)) + for i, v := range signerSet { + signersPubKeys[i], err = k.GetBlsPubKey(ctx, v.Addr) + if err != nil { + return err + } + sum += v.Power + } + if sum*3 <= totalPower*2 { + return types.ErrInvalidRawCheckpoint.Wrap("insufficient voting power") + } + msgBytes := types.GetSignBytes(ckpt.GetEpochNum(), *ckpt.BlockHash) + ok, err := bls12381.VerifyMultiSig(*ckpt.BlsMultiSig, signersPubKeys, msgBytes) + if err != nil { + return err + } + if !ok { + return types.ErrInvalidRawCheckpoint.Wrap("invalid BLS multi-sig") + } + + return nil +} + // VerifyCheckpoint verifies checkpoint from BTC. It verifies // the raw checkpoint and decides whether it is an invalid checkpoint or a // conflicting checkpoint. A conflicting checkpoint indicates the existence @@ -210,33 +225,10 @@ func (k Keeper) verifyCkptBytes(ctx context.Context, rawCheckpoint *txformat.Raw return ckptWithMeta, nil } - // next verify if the multi signature is valid - // check whether sufficient voting power is accumulated - totalPower := k.GetTotalVotingPower(ctx, ckpt.EpochNum) - signerSet, err := k.GetValidatorSet(ctx, ckpt.EpochNum).FindSubset(ckpt.Bitmap) - if err != nil { - return nil, fmt.Errorf("failed to get the signer set via bitmap of epoch %d: %w", ckpt.EpochNum, err) - } - var sum int64 - signersPubKeys := make([]bls12381.PublicKey, len(signerSet)) - for i, v := range signerSet { - signersPubKeys[i], err = k.GetBlsPubKey(ctx, v.Addr) - if err != nil { - return nil, err - } - sum += v.Power - } - if sum <= totalPower*1/3 { - return nil, types.ErrInvalidRawCheckpoint.Wrap("insufficient voting power") - } - msgBytes := types.GetSignBytes(ckpt.GetEpochNum(), *ckpt.AppHash) - ok, err := bls12381.VerifyMultiSig(*ckpt.BlsMultiSig, signersPubKeys, msgBytes) - if err != nil { + // verify raw checkpoint + if err := k.VerifyRawCheckpoint(ctx, ckpt); err != nil { return nil, err } - if !ok { - return nil, types.ErrInvalidRawCheckpoint.Wrap("invalid BLS multi-sig") - } // record verified checkpoint err = k.AfterRawCheckpointBlsSigVerified(ctx, ckpt) @@ -244,10 +236,10 @@ func (k Keeper) verifyCkptBytes(ctx context.Context, rawCheckpoint *txformat.Raw return nil, fmt.Errorf("failed to record verified checkpoint of epoch %d for monitoring: %w", ckpt.EpochNum, err) } - // now the checkpoint's multi-sig is valid, if the AppHash is the + // now the checkpoint's multi-sig is valid, if the BlockHash is the // same with that of the local checkpoint, it means it is valid except that // it is signed by a different signer set - if ckptWithMeta.Ckpt.AppHash.Equal(*ckpt.AppHash) { + if ckptWithMeta.Ckpt.BlockHash.Equal(*ckpt.BlockHash) { return ckptWithMeta, nil } @@ -399,3 +391,7 @@ func (k Keeper) GetValidatorSet(ctx context.Context, epochNumber uint64) epochin func (k Keeper) GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 { return k.epochingKeeper.GetTotalVotingPower(ctx, epochNumber) } + +func (k Keeper) GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) { + return k.epochingKeeper.GetPubKeyByConsAddr(ctx, consAddr) +} diff --git a/x/checkpointing/keeper/keeper_test.go b/x/checkpointing/keeper/keeper_test.go index c613fbf44..4b7e89fd5 100644 --- a/x/checkpointing/keeper/keeper_test.go +++ b/x/checkpointing/keeper/keeper_test.go @@ -7,13 +7,11 @@ import ( "github.com/boljen/go-bitmap" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/babylonchain/babylon/btctxformatter" - "github.com/babylonchain/babylon/crypto/bls12381" - - "github.com/cosmos/cosmos-sdk/client" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/btctxformatter" + "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/testutil/datagen" testkeeper "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/testutil/mocks" @@ -28,7 +26,7 @@ func FuzzKeeperAddRawCheckpoint(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 1) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil) // test nil raw checkpoint err := ckptKeeper.AddRawCheckpoint(ctx, nil) @@ -53,7 +51,7 @@ func FuzzKeeperAddRawCheckpoint(f *testing.F) { _, err = ckptKeeper.BuildRawCheckpoint( ctx, mockCkptWithMeta.Ckpt.EpochNum, - datagen.GenRandomAppHash(r), + datagen.GenRandomBlockHash(r), ) require.Errorf(t, err, "raw checkpoint with the same epoch already exists") }) @@ -69,7 +67,7 @@ func FuzzKeeperSetCheckpointStatus(f *testing.F) { ctrl := gomock.NewController(t) defer ctrl.Finish() ek := mocks.NewMockEpochingKeeper(ctrl) - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil) /* new accumulating checkpoint*/ mockCkptWithMeta := datagen.GenRandomRawCheckpointWithMeta(r) @@ -167,8 +165,8 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { defer ctrl.Finish() ek := mocks.NewMockEpochingKeeper(ctrl) ek.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any()).Return(valSet).AnyTimes() - ek.EXPECT().GetTotalVotingPower(gomock.Any(), gomock.Any()).Return(int64(20)).AnyTimes() - ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil, client.Context{}) + ek.EXPECT().GetTotalVotingPower(gomock.Any(), gomock.Any()).Return(int64(10)).AnyTimes() + ckptKeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, ek, nil) for i, val := range valSet { err := ckptKeeper.CreateRegistration(ctx, pubkeys[i], val.Addr) require.NoError(t, err) @@ -181,7 +179,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { localCkptWithMeta.Status = types.Sealed localCkptWithMeta.PowerSum = 10 localCkptWithMeta.Ckpt.Bitmap = bm - msgBytes := types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, *localCkptWithMeta.Ckpt.AppHash) + msgBytes := types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, *localCkptWithMeta.Ckpt.BlockHash) sig := bls12381.Sign(blsPrivKey1, msgBytes) localCkptWithMeta.Ckpt.BlsMultiSig = &sig _ = ckptKeeper.AddRawCheckpoint( @@ -193,7 +191,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { rawBtcCheckpoint := makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - localCkptWithMeta.Ckpt.AppHash.MustMarshal(), + localCkptWithMeta.Ckpt.BlockHash.MustMarshal(), localCkptWithMeta.Ckpt.Bitmap, localCkptWithMeta.Ckpt.BlsMultiSig.Bytes(), t, @@ -206,7 +204,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { rawBtcCheckpoint = makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - localCkptWithMeta.Ckpt.AppHash.MustMarshal(), + localCkptWithMeta.Ckpt.BlockHash.MustMarshal(), localCkptWithMeta.Ckpt.Bitmap, datagen.GenRandomByteArray(r, btctxformatter.BlsSigLength), t, @@ -214,13 +212,13 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { err = ckptKeeper.VerifyCheckpoint(ctx, *rawBtcCheckpoint) require.ErrorIs(t, err, types.ErrInvalidRawCheckpoint) - // 3. check a conflicting checkpoint; signed on a random AppHash - conflictAppHash := datagen.GenRandomByteArray(r, btctxformatter.AppHashLength) - msgBytes = types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, conflictAppHash) + // 3. check a conflicting checkpoint; signed on a random BlockHash + conflictBlockHash := datagen.GenRandomByteArray(r, btctxformatter.AppHashLength) + msgBytes = types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, conflictBlockHash) rawBtcCheckpoint = makeBtcCkptBytes( r, localCkptWithMeta.Ckpt.EpochNum, - conflictAppHash, + conflictBlockHash, localCkptWithMeta.Ckpt.Bitmap, bls12381.Sign(blsPrivKey1, msgBytes), t, @@ -238,7 +236,7 @@ func makeBtcCkptBytes(r *rand.Rand, epoch uint64, appHash []byte, bitmap []byte, rawBTCCkpt := &btctxformatter.RawBtcCheckpoint{ Epoch: epoch, - AppHash: appHash, + BlockHash: appHash, BitMap: bitmap, SubmitterAddress: address, BlsSig: blsSig, diff --git a/x/checkpointing/keeper/msg_server.go b/x/checkpointing/keeper/msg_server.go index 20d0870f4..04a088763 100644 --- a/x/checkpointing/keeper/msg_server.go +++ b/x/checkpointing/keeper/msg_server.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -23,20 +22,6 @@ func NewMsgServerImpl(keeper Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} -// AddBlsSig adds BLS sig messages and changes a raw checkpoint status to SEALED if sufficient voting power is accumulated -func (m msgServer) AddBlsSig(goCtx context.Context, msg *types.MsgAddBlsSig) (*types.MsgAddBlsSigResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - ctx.Logger().Info(fmt.Sprintf("received BLS sig for epoch %d from %s", msg.BlsSig.EpochNum, msg.Signer)) - - err := m.k.addBlsSig(ctx, msg.BlsSig) - if err != nil { - return nil, err - } - - return &types.MsgAddBlsSigResponse{}, nil -} - // WrappedCreateValidator registers validator's BLS public key // and forwards corresponding MsgCreateValidator message to // the epoching module diff --git a/x/checkpointing/keeper/msg_server_test.go b/x/checkpointing/keeper/msg_server_test.go index 6fa11c649..e8b32e471 100644 --- a/x/checkpointing/keeper/msg_server_test.go +++ b/x/checkpointing/keeper/msg_server_test.go @@ -62,7 +62,7 @@ func FuzzWrappedCreateValidator_InsufficientTokens(f *testing.F) { // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = ek.GetEpoch(ctx) @@ -178,7 +178,7 @@ func FuzzWrappedCreateValidator(f *testing.F) { // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = ek.GetEpoch(ctx) @@ -202,228 +202,6 @@ func FuzzWrappedCreateValidator(f *testing.F) { }) } -// FuzzAddBlsSig_NoError tests adding BLS signatures via MsgAddBlsSig -// it covers the following scenarios that would not cause errors: -// 1. a BLS signature is successfully accumulated and the checkpoint remains ACCUMULATING -// 2. a BLS signature is successfully accumulated and the checkpoint is changed to SEALED -// 3. a BLS signature is rejected if the checkpoint is not ACCUMULATING -func FuzzAddBlsSig_NoError(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 4) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - - // a genesis validator is generate for setup - helper := testhelper.NewHelper(t) - ctx := helper.Ctx - ek := helper.App.EpochingKeeper - ck := helper.App.CheckpointingKeeper - msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - - // epoch 1 right now - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built - for i := uint64(0); i < 2; i++ { - _, err := helper.GenAndApplyEmptyBlock(r) - require.NoError(t, err) - } - endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - ckpt, err := ck.GetRawCheckpoint(ctx, endingEpoch) - require.NoError(t, err) - - // add BLS signatures - n := len(helper.GenValidators.BlsPrivKeys) - totalPower := uint64(ck.GetTotalVotingPower(ctx, endingEpoch)) - for i := 0; i < n; i++ { - blsPrivKey := helper.GenValidators.BlsPrivKeys[i] - appHash := ckpt.Ckpt.AppHash.MustMarshal() - addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) - require.NoError(t, err) - signBytes := types.GetSignBytes(endingEpoch, appHash) - blsSig := bls12381.Sign(blsPrivKey, signBytes) - - // create MsgAddBlsSig message - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) - _, err = msgServer.AddBlsSig(ctx, msg) - require.NoError(t, err) - afterCkpt, err := ck.GetRawCheckpoint(ctx, endingEpoch) - require.NoError(t, err) - if afterCkpt.PowerSum <= totalPower/3 { - require.True(t, afterCkpt.Status == types.Accumulating) - } else { - require.True(t, afterCkpt.Status == types.Sealed) - } - } - }) -} - -// FuzzAddBlsSig_Error tests adding BLS signatures via MsgAddBlsSig -// in a scenario where the signer is not in the checkpoint's validator set -func FuzzAddBlsSig_NotInValSet(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 4) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - var err error - - helper := testhelper.NewHelperWithValSet(t) - ctx := helper.Ctx - ek := helper.App.EpochingKeeper - ck := helper.App.CheckpointingKeeper - msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - - // epoch 1 right now - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built - for i := uint64(0); i < 2; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) - require.NoError(t, err) - } - endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err = ck.GetRawCheckpoint(ctx, endingEpoch) - require.NoError(t, err) - - // build BLS sig from a random validator (not in the validator set) - appHash := ctx.HeaderInfo().AppHash - blsPrivKey := bls12381.GenPrivKey() - valAddr := datagen.GenRandomValidatorAddress() - signBytes := types.GetSignBytes(endingEpoch, appHash) - blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(valAddr), endingEpoch, appHash, blsSig, valAddr) - - _, err = msgServer.AddBlsSig(ctx, msg) - require.Error(t, err, types.ErrCkptDoesNotExist) - }) -} - -// FuzzAddBlsSig_CkptNotExist tests adding BLS signatures via MsgAddBlsSig -// in a scenario where the corresponding checkpoint does not exist -func FuzzAddBlsSig_CkptNotExist(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 4) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - - helper := testhelper.NewHelperWithValSet(t) - ctx := helper.Ctx - ek := helper.App.EpochingKeeper - ck := helper.App.CheckpointingKeeper - msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - - // epoch 1 right now - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // build BLS signature from a random validator of the validator set - n := len(helper.GenValidators.BlsPrivKeys) - i := r.Intn(n) - appHash := ctx.HeaderInfo().AppHash - blsPrivKey := helper.GenValidators.BlsPrivKeys[i] - addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) - require.NoError(t, err) - signBytes := types.GetSignBytes(epoch.EpochNumber-1, appHash) - blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), epoch.EpochNumber-1, appHash, blsSig, addr) - - // add the BLS signature - _, err = msgServer.AddBlsSig(ctx, msg) - require.Error(t, err, types.ErrCkptDoesNotExist) - }) -} - -// FuzzAddBlsSig_WrongAppHash tests adding BLS signatures via MsgAddBlsSig -// in a scenario where the signature is signed over wrong app_hash -// 4. a BLS signature is rejected if the signature is invalid -func FuzzAddBlsSig_WrongAppHash(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 4) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - var err error - - helper := testhelper.NewHelperWithValSet(t) - ctx := helper.Ctx - ek := helper.App.EpochingKeeper - ck := helper.App.CheckpointingKeeper - msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - - // epoch 1 right now - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - - // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built - for i := uint64(0); i < 2; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) - require.NoError(t, err) - } - endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err = ck.GetRawCheckpoint(ctx, endingEpoch) - require.NoError(t, err) - - // build BLS sig from a random validator - n := len(helper.GenValidators.BlsPrivKeys) - i := r.Intn(n) - // inject random last commit hash - appHash := datagen.GenRandomAppHash(r) - blsPrivKey := helper.GenValidators.BlsPrivKeys[i] - addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) - require.NoError(t, err) - signBytes := types.GetSignBytes(endingEpoch, appHash) - blsSig := bls12381.Sign(blsPrivKey, signBytes) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) - - // add the BLS signature - _, err = msgServer.AddBlsSig(ctx, msg) - require.Error(t, err, types.ErrInvalidAppHash) - }) -} - -// FuzzAddBlsSig_InvalidSignature tests adding BLS signatures via MsgAddBlsSig -// in a scenario where the signature is invalid -func FuzzAddBlsSig_InvalidSignature(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 4) - - f.Fuzz(func(t *testing.T, seed int64) { - r := rand.New(rand.NewSource(seed)) - - helper := testhelper.NewHelperWithValSet(t) - ek := helper.App.EpochingKeeper - ck := helper.App.CheckpointingKeeper - msgServer := checkpointingkeeper.NewMsgServerImpl(ck) - ctx := helper.Ctx - - // BeginBlock of block 1, and thus entering epoch 1 - epoch := ek.GetEpoch(ctx) - require.Equal(t, uint64(1), epoch.EpochNumber) - // apply 2 blocks to ensure that a raw checkpoint for the previous epoch is built - for i := uint64(0); i < 2; i++ { - _, err := helper.GenAndApplyEmptyBlock(r) - require.NoError(t, err) - } - endingEpoch := ek.GetEpoch(ctx).EpochNumber - 1 - _, err := ck.GetRawCheckpoint(ctx, endingEpoch) - require.NoError(t, err) - - // build BLS sig from a random validator - n := len(helper.GenValidators.BlsPrivKeys) - i := r.Intn(n) - // inject random last commit hash - appHash := ctx.HeaderInfo().AppHash - addr, err := sdk.ValAddressFromBech32(helper.GenValidators.GenesisKeys[i].ValidatorAddress) - require.NoError(t, err) - blsSig := datagen.GenRandomBlsMultiSig(r) - msg := types.NewMsgAddBlsSig(sdk.AccAddress(addr), endingEpoch, appHash, blsSig, addr) - - // add the BLS signature message - _, err = msgServer.AddBlsSig(ctx, msg) - require.Error(t, err, types.ErrInvalidBlsSignature) - }) -} - func buildMsgWrappedCreateValidator(addr sdk.AccAddress) (*types.MsgWrappedCreateValidator, error) { bondTokens := sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction) return buildMsgWrappedCreateValidatorWithAmount(addr, bondTokens) diff --git a/x/checkpointing/keeper/val_bls_set_test.go b/x/checkpointing/keeper/val_bls_set_test.go index e90b470f1..1c4e84292 100644 --- a/x/checkpointing/keeper/val_bls_set_test.go +++ b/x/checkpointing/keeper/val_bls_set_test.go @@ -6,13 +6,14 @@ import ( "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" checkpointingkeeper "github.com/babylonchain/babylon/x/checkpointing/keeper" "github.com/babylonchain/babylon/x/checkpointing/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/stretchr/testify/require" ) func FuzzGetValidatorBlsKeySet(f *testing.F) { @@ -57,7 +58,7 @@ func FuzzGetValidatorBlsKeySet(f *testing.F) { // go to block 11, and thus entering epoch 2 for i := uint64(0); i < ek.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = ek.GetEpoch(ctx) diff --git a/x/checkpointing/proposal.go b/x/checkpointing/proposal.go new file mode 100644 index 000000000..e9453f145 --- /dev/null +++ b/x/checkpointing/proposal.go @@ -0,0 +1,324 @@ +package checkpointing + +import ( + "fmt" + "slices" + + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/babylonchain/babylon/x/checkpointing/keeper" + ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" +) + +const defaultInjectedTxIndex = 0 + +type ProposalHandler struct { + logger log.Logger + ckptKeeper *keeper.Keeper + valStore baseapp.ValidatorStore + txVerifier baseapp.ProposalTxVerifier + defaultHandler *baseapp.DefaultProposalHandler +} + +func NewProposalHandler(logger log.Logger, ckptKeeper *keeper.Keeper, mp mempool.Mempool, txVerifier baseapp.ProposalTxVerifier) *ProposalHandler { + return &ProposalHandler{ + logger: logger, + ckptKeeper: ckptKeeper, + valStore: ckptKeeper, + txVerifier: txVerifier, + defaultHandler: baseapp.NewDefaultProposalHandler(mp, txVerifier), + } +} + +func (h *ProposalHandler) SetHandlers(bApp *baseapp.BaseApp) { + bApp.SetPrepareProposal(h.PrepareProposal()) + bApp.SetProcessProposal(h.ProcessProposal()) + bApp.SetPreBlocker(h.PreBlocker()) +} + +// PrepareProposal examines the vote extensions from the previous block, accumulates +// them into a checkpoint, and injects the checkpoint into the current proposal +// as a special tx +// Warning: the returned error of the handler will cause panic of the proposer, +// therefore we only return error when something really wrong happened +func (h *ProposalHandler) PrepareProposal() sdk.PrepareProposalHandler { + return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { + // call default handler first to do basic validation + res, err := h.defaultHandler.PrepareProposalHandler()(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed in default PrepareProposal handler: %w", err) + } + + k := h.ckptKeeper + proposalTxs := res.Txs + proposalRes := &abci.ResponsePrepareProposal{Txs: proposalTxs} + + epoch := k.GetEpoch(ctx) + // BLS signatures are sent in the last block of the previous epoch, + // so they should be aggregated in the first block of the new epoch + // and no BLS signatures are send in epoch 0 + if !epoch.IsVoteExtensionProposal(ctx) { + return proposalRes, nil + } + + if len(req.LocalLastCommit.Votes) == 0 { + return proposalRes, fmt.Errorf("no extended votes received from the last block") + } + + // 1. verify the validity of vote extensions (2/3 majority is achieved) + err = baseapp.ValidateVoteExtensions(ctx, h.valStore, req.Height, ctx.ChainID(), req.LocalLastCommit) + if err != nil { + return proposalRes, fmt.Errorf("invalid vote extensions: %w", err) + } + + // 2. build a checkpoint for the previous epoch + // Note: the epoch has not increased yet, so + // we can use the current epoch + ckpt, err := h.buildCheckpointFromVoteExtensions(ctx, epoch.EpochNumber, req.LocalLastCommit.Votes) + if err != nil { + return proposalRes, fmt.Errorf("failed to build checkpoint from vote extensions: %w", err) + } + + // 3. inject a "fake" tx into the proposal s.t. validators can decode, verify the checkpoint + injectedCkpt := &ckpttypes.InjectedCheckpoint{ + Ckpt: ckpt, + ExtendedCommitInfo: &req.LocalLastCommit, + } + injectedVoteExtTx, err := injectedCkpt.Marshal() + if err != nil { + return nil, fmt.Errorf("failed to encode vote extensions into a special tx: %w", err) + } + proposalTxs = slices.Insert(proposalTxs, defaultInjectedTxIndex, [][]byte{injectedVoteExtTx}...) + + return &abci.ResponsePrepareProposal{ + Txs: proposalTxs, + }, nil + } +} + +func (h *ProposalHandler) buildCheckpointFromVoteExtensions(ctx sdk.Context, epoch uint64, extendedVotes []abci.ExtendedVoteInfo) (*ckpttypes.RawCheckpointWithMeta, error) { + prevBlockID, err := h.findLastBlockHash(extendedVotes) + if err != nil { + return nil, err + } + ckpt := ckpttypes.NewCheckpointWithMeta(ckpttypes.NewCheckpoint(epoch, prevBlockID), ckpttypes.Accumulating) + validBLSSigs := h.getValidBlsSigs(ctx, extendedVotes) + vals := h.ckptKeeper.GetValidatorSet(ctx, epoch) + totalPower := h.ckptKeeper.GetTotalVotingPower(ctx, epoch) + // TODO: maybe we don't need to verify BLS sigs anymore as they are already + // verified by VerifyVoteExtension + for _, sig := range validBLSSigs { + signerAddress, err := sdk.ValAddressFromBech32(sig.SignerAddress) + if err != nil { + h.logger.Error( + "skip invalid BLS sig", + "invalid signer address", sig.SignerAddress, + "err", err, + ) + continue + } + signerBlsKey, err := h.ckptKeeper.GetBlsPubKey(ctx, signerAddress) + if err != nil { + h.logger.Error( + "skip invalid BLS sig", + "can't find BLS public key", err, + ) + continue + } + err = ckpt.Accumulate(vals, signerAddress, signerBlsKey, *sig.BlsSig, totalPower) + if err != nil { + h.logger.Error( + "skip invalid BLS sig", + "accumulation failed", err, + ) + continue + } + // sufficient voting power is accumulated + if ckpt.Status == ckpttypes.Sealed { + break + } + } + if ckpt.Status != ckpttypes.Sealed { + return nil, fmt.Errorf("insufficient voting power to build the checkpoint") + } + + return ckpt, nil +} + +func (h *ProposalHandler) getValidBlsSigs(ctx sdk.Context, extendedVotes []abci.ExtendedVoteInfo) []ckpttypes.BlsSig { + k := h.ckptKeeper + validBLSSigs := make([]ckpttypes.BlsSig, 0, len(extendedVotes)) + for _, voteInfo := range extendedVotes { + veBytes := voteInfo.VoteExtension + if len(veBytes) == 0 { + h.logger.Error("received empty vote extension", "validator", voteInfo.Validator.String()) + continue + } + var ve ckpttypes.VoteExtension + if err := ve.Unmarshal(veBytes); err != nil { + h.logger.Error("failed to unmarshal vote extension", "err", err) + continue + } + sig := ve.ToBLSSig() + + if err := k.VerifyBLSSig(ctx, sig); err != nil { + h.logger.Error("invalid BLS signature", "err", err) + continue + } + + validBLSSigs = append(validBLSSigs, *sig) + } + + return validBLSSigs +} + +// findLastBlockHash finds the last block hash from the first vote extension +// this is a workaround that the last block hash can't be obtained from the context +// this is also safe as the BLS sig is already verified by VerifyVoteExtension +func (h *ProposalHandler) findLastBlockHash(extendedVotes []abci.ExtendedVoteInfo) ([]byte, error) { + var ve ckpttypes.VoteExtension + if err := ve.Unmarshal(extendedVotes[0].VoteExtension); err != nil { + return nil, err + } + return ve.ToBLSSig().BlockHash.MustMarshal(), nil +} + +// ProcessProposal examines the checkpoint in the injected tx of the proposal +// Warning: the returned error of the handler will cause panic of the node, +// therefore we only return error when something really wrong happened +func (h *ProposalHandler) ProcessProposal() sdk.ProcessProposalHandler { + return func(ctx sdk.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { + resAccept := &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + resReject := &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + + k := h.ckptKeeper + + epoch := k.GetEpoch(ctx) + // BLS signatures are sent in the last block of the previous epoch, + // so they should be aggregated in the first block of the new epoch + // and no BLS signatures are send in epoch 0 + if !epoch.IsVoteExtensionProposal(ctx) { + res, err := h.defaultHandler.ProcessProposalHandler()(ctx, req) + if err != nil { + return resReject, fmt.Errorf("failed in default ProcessProposal handler: %w", err) + } + return res, nil + } + + // 1. extract the special tx containing the checkpoint + injectedTx, err := getInjectedTx(req.Txs) + if err != nil { + h.logger.Error("cannot get injected tx", "err", err) + return resReject, nil + } + var injectedCkpt ckpttypes.InjectedCheckpoint + if err := injectedCkpt.Unmarshal(injectedTx); err != nil { + h.logger.Error("failed to decode injected vote extension tx", "err", err) + // should not return error here as error will cause panic + return resReject, nil + } + + // 2. verify the validity of the vote extension (2/3 majority is achieved) + err = baseapp.ValidateVoteExtensions(ctx, h.valStore, req.Height, ctx.ChainID(), *injectedCkpt.ExtendedCommitInfo) + if err != nil { + // the returned err will lead to panic as something very wrong happened during consensus + return resReject, err + } + + // 3. rebuild the checkpoint from vote extensions and compare it with + // the injected checkpoint + // Note: this is needed because LastBlockID is not available here so that + // we can't verify whether the injected checkpoint is signing the correct + // LastBlockID + ckpt, err := h.buildCheckpointFromVoteExtensions(ctx, epoch.EpochNumber, injectedCkpt.ExtendedCommitInfo.Votes) + // TODO it is possible that although the checkpoints do not match but the injected + // checkpoint is still valid. This indicates the existence of a fork (>1/3 malicious voting power) + // and we should probably send an alarm and stall the blockchain + if !ckpt.Equal(injectedCkpt.Ckpt) { + // should not return error here as error will cause panic + h.logger.Error("invalid checkpoint in vote extension tx", "err", err) + return resReject, nil + } + + // 4. verify the rest of the txs (copied from the default process proposal handler) + // https://github.com/cosmos/cosmos-sdk/blob/9814f684b9dd7e384064ca86876688c05e685e54/baseapp/abci_utils.go#L260 + var ( + totalTxGas uint64 + maxBlockGas int64 + ) + if b := ctx.ConsensusParams().Block; b != nil { + maxBlockGas = b.MaxGas + } + for _, txBytes := range req.Txs[1:] { + tx, err := h.txVerifier.ProcessProposalVerifyTx(txBytes) + if err != nil { + return resReject, nil + } + + if maxBlockGas > 0 { + gasTx, ok := tx.(baseapp.GasTx) + if ok { + totalTxGas += gasTx.GetGas() + } + + if totalTxGas > uint64(maxBlockGas) { + return resReject, nil + } + } + } + + return resAccept, nil + } +} + +// PreBlocker extracts the checkpoint from the injected tx and stores it in +// the application +// no more validation is needed as it is already done in ProcessProposal +func (h *ProposalHandler) PreBlocker() sdk.PreBlocker { + return func(ctx sdk.Context, req *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + k := h.ckptKeeper + res := &sdk.ResponsePreBlock{} + + epoch := k.GetEpoch(ctx) + // BLS signatures are sent in the last block of the previous epoch, + // so they should be aggregated in the first block of the new epoch + // and no BLS signatures are send in epoch 0 + if !epoch.IsVoteExtensionProposal(ctx) { + return res, nil + } + + // 1. extract the special tx containing BLS sigs + injectedTx, err := getInjectedTx(req.Txs) + if err != nil { + return res, err + } + var ckpt ckpttypes.InjectedCheckpoint + if err := ckpt.Unmarshal(injectedTx); err != nil { + return res, fmt.Errorf("failed to decode injected vote extension tx: %w", err) + } + + // 2. update checkpoint + if err := k.SealCheckpoint(ctx, ckpt.Ckpt); err != nil { + return res, fmt.Errorf("failed to update checkpoint: %w", err) + } + + return res, nil + } +} + +func getInjectedTx(txs [][]byte) ([]byte, error) { + if len(txs) == 0 { + return nil, fmt.Errorf("the proposal does not contain the checkpoint") + } + + if len(txs[defaultInjectedTxIndex]) == 0 { + return nil, fmt.Errorf("err in PreBlocker: the injected vote extensions tx is empty") + } + + return txs[defaultInjectedTxIndex], nil +} diff --git a/x/checkpointing/types/bls_key.pb.go b/x/checkpointing/types/bls_key.pb.go index d42efd40d..ac29f17d6 100644 --- a/x/checkpointing/types/bls_key.pb.go +++ b/x/checkpointing/types/bls_key.pb.go @@ -27,9 +27,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // BlsKey wraps BLS public key with PoP type BlsKey struct { // pubkey is the BLS public key of a validator - Pubkey *github_com_babylonchain_babylon_crypto_bls12381.PublicKey `protobuf:"bytes,2,opt,name=pubkey,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.PublicKey" json:"pubkey,omitempty"` + Pubkey *github_com_babylonchain_babylon_crypto_bls12381.PublicKey `protobuf:"bytes,1,opt,name=pubkey,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.PublicKey" json:"pubkey,omitempty"` // pop is the proof-of-possession of the BLS key - Pop *ProofOfPossession `protobuf:"bytes,3,opt,name=pop,proto3" json:"pop,omitempty"` + Pop *ProofOfPossession `protobuf:"bytes,2,opt,name=pop,proto3" json:"pop,omitempty"` } func (m *BlsKey) Reset() { *m = BlsKey{} } @@ -77,10 +77,10 @@ func (m *BlsKey) GetPop() *ProofOfPossession { type ProofOfPossession struct { // ed25519_sig is used for verification, ed25519_sig = sign(key = Ed25519_sk, // data = BLS_pk) - Ed25519Sig []byte `protobuf:"bytes,2,opt,name=ed25519_sig,json=ed25519Sig,proto3" json:"ed25519_sig,omitempty"` + Ed25519Sig []byte `protobuf:"bytes,1,opt,name=ed25519_sig,json=ed25519Sig,proto3" json:"ed25519_sig,omitempty"` // bls_sig is the result of PoP, bls_sig = sign(key = BLS_sk, data = // ed25519_sig) - BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` + BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,2,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` } func (m *ProofOfPossession) Reset() { *m = ProofOfPossession{} } @@ -230,11 +230,90 @@ func (m *ValidatorWithBlsKey) GetVotingPower() uint64 { return 0 } +// VoteExtension defines the structure used to create a BLS vote extension. +type VoteExtension struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + BlockHash []byte `protobuf:"bytes,3,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + EpochNum uint64 `protobuf:"varint,4,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` + Height uint64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"` + BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,6,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` +} + +func (m *VoteExtension) Reset() { *m = VoteExtension{} } +func (m *VoteExtension) String() string { return proto.CompactTextString(m) } +func (*VoteExtension) ProtoMessage() {} +func (*VoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_3a8c0d37ce63f038, []int{4} +} +func (m *VoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_VoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *VoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_VoteExtension.Merge(m, src) +} +func (m *VoteExtension) XXX_Size() int { + return m.Size() +} +func (m *VoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_VoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_VoteExtension proto.InternalMessageInfo + +func (m *VoteExtension) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *VoteExtension) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + +func (m *VoteExtension) GetBlockHash() []byte { + if m != nil { + return m.BlockHash + } + return nil +} + +func (m *VoteExtension) GetEpochNum() uint64 { + if m != nil { + return m.EpochNum + } + return 0 +} + +func (m *VoteExtension) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + func init() { proto.RegisterType((*BlsKey)(nil), "babylon.checkpointing.v1.BlsKey") proto.RegisterType((*ProofOfPossession)(nil), "babylon.checkpointing.v1.ProofOfPossession") proto.RegisterType((*ValidatorWithBlsKeySet)(nil), "babylon.checkpointing.v1.ValidatorWithBlsKeySet") proto.RegisterType((*ValidatorWithBlsKey)(nil), "babylon.checkpointing.v1.ValidatorWithBlsKey") + proto.RegisterType((*VoteExtension)(nil), "babylon.checkpointing.v1.VoteExtension") } func init() { @@ -242,35 +321,40 @@ func init() { } var fileDescriptor_3a8c0d37ce63f038 = []byte{ - // 433 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0x41, 0x6b, 0xd4, 0x40, - 0x18, 0xdd, 0x71, 0x25, 0xa5, 0x93, 0x1e, 0x6c, 0x14, 0x09, 0x1e, 0xd2, 0x75, 0x0f, 0xb2, 0x50, - 0x4c, 0xc8, 0x96, 0x05, 0x7b, 0xe8, 0xc1, 0x3d, 0x78, 0xe9, 0xa1, 0x21, 0xc1, 0x0a, 0x5e, 0xe2, - 0x4c, 0x76, 0x3a, 0x3b, 0xec, 0x98, 0x6f, 0xc8, 0x4c, 0xa2, 0xf9, 0x01, 0xde, 0x3c, 0xf8, 0x0b, - 0xfc, 0x3d, 0x1e, 0x7b, 0x14, 0x0f, 0x22, 0xbb, 0x7f, 0x44, 0x66, 0x13, 0x17, 0xd4, 0x16, 0xc1, - 0x5b, 0x78, 0xef, 0xe5, 0xf1, 0xde, 0x9b, 0x0f, 0x3f, 0xa1, 0x84, 0xb6, 0x12, 0xca, 0xa8, 0x58, - 0xb2, 0x62, 0xa5, 0x40, 0x94, 0x46, 0x94, 0x3c, 0x6a, 0xe2, 0x88, 0x4a, 0x9d, 0xaf, 0x58, 0x1b, - 0xaa, 0x0a, 0x0c, 0x78, 0x7e, 0xaf, 0x0b, 0x7f, 0xd3, 0x85, 0x4d, 0xfc, 0xe8, 0x01, 0x07, 0x0e, - 0x5b, 0x51, 0x64, 0xbf, 0x3a, 0xfd, 0xf8, 0x33, 0xc2, 0xce, 0x5c, 0xea, 0x73, 0xd6, 0x7a, 0x2f, - 0xb1, 0xa3, 0x6a, 0xba, 0x62, 0xad, 0x7f, 0x67, 0x84, 0x26, 0x07, 0xf3, 0xb3, 0x6f, 0xdf, 0x8f, - 0x4e, 0xb9, 0x30, 0xcb, 0x9a, 0x86, 0x05, 0xbc, 0x8d, 0x7a, 0xe7, 0x62, 0x49, 0x44, 0x19, 0xed, - 0xe2, 0x54, 0xad, 0x32, 0x60, 0x43, 0xc4, 0xd3, 0x93, 0x67, 0x71, 0x98, 0xd4, 0x54, 0x8a, 0xe2, - 0x9c, 0xb5, 0x69, 0x6f, 0xe6, 0x9d, 0xe1, 0xa1, 0x02, 0xe5, 0x0f, 0x47, 0x68, 0xe2, 0x4e, 0x8f, - 0xc3, 0xdb, 0xf2, 0x85, 0x49, 0x05, 0x70, 0x75, 0x71, 0x95, 0x80, 0xd6, 0x4c, 0x6b, 0x01, 0x65, - 0x6a, 0xff, 0x1b, 0x7f, 0x44, 0xf8, 0xf0, 0x2f, 0xca, 0x3b, 0xc2, 0x2e, 0x5b, 0x4c, 0x67, 0xb3, - 0xf8, 0x34, 0xd7, 0x82, 0x77, 0x81, 0x53, 0xdc, 0x43, 0x99, 0xe0, 0xde, 0x25, 0xde, 0xb3, 0xc3, - 0x58, 0x72, 0xf8, 0xff, 0x6d, 0x32, 0xc1, 0x4b, 0x62, 0xea, 0x8a, 0xa5, 0x0e, 0x95, 0x3a, 0x13, - 0x7c, 0xfc, 0x06, 0x3f, 0xbc, 0x24, 0x52, 0x2c, 0x88, 0x81, 0xea, 0x95, 0x30, 0xcb, 0x6e, 0xbb, - 0x8c, 0x19, 0xef, 0x05, 0xde, 0x6b, 0x88, 0xcc, 0x35, 0x33, 0x3e, 0x1a, 0x0d, 0x27, 0xee, 0xf4, - 0xe9, 0xed, 0x5d, 0x6f, 0xb0, 0x48, 0x9d, 0x86, 0xc8, 0x8c, 0x99, 0xf1, 0x07, 0x84, 0xef, 0xdf, - 0xc0, 0x7b, 0xc7, 0xf8, 0xb0, 0xf9, 0x05, 0xe7, 0x64, 0xb1, 0xa8, 0x98, 0xd6, 0x3e, 0x1a, 0xa1, - 0xc9, 0x7e, 0x7a, 0x6f, 0x47, 0x3c, 0xef, 0x70, 0x2f, 0xc0, 0xae, 0xad, 0xaf, 0x6a, 0x9a, 0xef, - 0x1e, 0x34, 0xdd, 0xa7, 0x52, 0x27, 0x35, 0xb5, 0x66, 0x8f, 0xf1, 0x41, 0x03, 0x36, 0x4d, 0xae, - 0xe0, 0x1d, 0xab, 0xb6, 0x1b, 0xdd, 0x4d, 0xdd, 0x0e, 0x4b, 0x2c, 0x34, 0xbf, 0xf8, 0xb2, 0x0e, - 0xd0, 0xf5, 0x3a, 0x40, 0x3f, 0xd6, 0x01, 0xfa, 0xb4, 0x09, 0x06, 0xd7, 0x9b, 0x60, 0xf0, 0x75, - 0x13, 0x0c, 0x5e, 0xcf, 0xfe, 0x35, 0xe3, 0xfb, 0x3f, 0xae, 0xd4, 0xb4, 0x8a, 0x69, 0xea, 0x6c, - 0x2f, 0xee, 0xe4, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x90, 0xb1, 0x5b, 0xcb, 0x02, 0x00, - 0x00, + // 524 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x4f, 0x8b, 0xd3, 0x40, + 0x18, 0xc6, 0x77, 0xda, 0x35, 0x6b, 0xa7, 0x2b, 0xb8, 0x51, 0x96, 0xa0, 0x98, 0xad, 0x3d, 0x48, + 0x61, 0x31, 0xa1, 0x5d, 0x0a, 0xee, 0x61, 0x0f, 0x16, 0x14, 0x61, 0xc1, 0x2d, 0x29, 0x56, 0xf0, + 0x12, 0x33, 0xe9, 0x6c, 0x32, 0x74, 0x9a, 0x77, 0xc8, 0x4c, 0xe2, 0xe6, 0xe6, 0xc5, 0x9b, 0x07, + 0x3f, 0x81, 0x9f, 0xc7, 0xe3, 0x1e, 0xc5, 0x83, 0x48, 0xfb, 0x45, 0x64, 0x92, 0xb8, 0xf8, 0xa7, + 0x45, 0x10, 0x6f, 0x99, 0xe7, 0x79, 0xf3, 0xcc, 0xef, 0x7d, 0x67, 0x06, 0x3f, 0x20, 0x01, 0x29, + 0x38, 0x24, 0x6e, 0x18, 0xd3, 0x70, 0x2e, 0x80, 0x25, 0x8a, 0x25, 0x91, 0x9b, 0xf7, 0x5d, 0xc2, + 0xa5, 0x3f, 0xa7, 0x85, 0x23, 0x52, 0x50, 0x60, 0x5a, 0x75, 0x9d, 0xf3, 0x4b, 0x9d, 0x93, 0xf7, + 0xef, 0xdc, 0x8e, 0x20, 0x82, 0xb2, 0xc8, 0xd5, 0x5f, 0x55, 0x7d, 0xf7, 0x23, 0xc2, 0xc6, 0x88, + 0xcb, 0x53, 0x5a, 0x98, 0x2f, 0xb0, 0x21, 0x32, 0x32, 0xa7, 0x85, 0x85, 0x3a, 0xa8, 0xb7, 0x3b, + 0x3a, 0xf9, 0xf2, 0xf5, 0xe0, 0x38, 0x62, 0x2a, 0xce, 0x88, 0x13, 0xc2, 0xc2, 0xad, 0x93, 0xc3, + 0x38, 0x60, 0x89, 0x7b, 0x85, 0x93, 0x16, 0x42, 0x81, 0x86, 0xe8, 0x0f, 0x8e, 0x1e, 0xf5, 0x9d, + 0x71, 0x46, 0x38, 0x0b, 0x4f, 0x69, 0xe1, 0xd5, 0x61, 0xe6, 0x09, 0x6e, 0x0a, 0x10, 0x56, 0xa3, + 0x83, 0x7a, 0xed, 0xc1, 0xa1, 0xb3, 0x89, 0xcf, 0x19, 0xa7, 0x00, 0xe7, 0x67, 0xe7, 0x63, 0x90, + 0x92, 0x4a, 0xc9, 0x20, 0xf1, 0xf4, 0x7f, 0xdd, 0xf7, 0x08, 0xef, 0xfd, 0x61, 0x99, 0x07, 0xb8, + 0x4d, 0x67, 0x83, 0xe1, 0xb0, 0x7f, 0xec, 0x4b, 0x16, 0x55, 0xc0, 0x1e, 0xae, 0xa5, 0x09, 0x8b, + 0xcc, 0x29, 0xde, 0xd1, 0x83, 0xd1, 0x66, 0xe3, 0xdf, 0xbb, 0x99, 0xb0, 0x28, 0x09, 0x54, 0x96, + 0x52, 0xcf, 0x20, 0x5c, 0x4e, 0x58, 0xd4, 0x7d, 0x8d, 0xf7, 0xa7, 0x01, 0x67, 0xb3, 0x40, 0x41, + 0xfa, 0x92, 0xa9, 0xb8, 0x9a, 0xdd, 0x84, 0x2a, 0xf3, 0x29, 0xde, 0xc9, 0x03, 0xee, 0x4b, 0xaa, + 0x2c, 0xd4, 0x69, 0xf6, 0xda, 0x83, 0x87, 0x9b, 0x7b, 0x5d, 0x13, 0xe1, 0x19, 0x79, 0xc0, 0x27, + 0x54, 0x75, 0xdf, 0x21, 0x7c, 0x6b, 0x8d, 0x6f, 0x1e, 0xe2, 0xbd, 0xfc, 0x87, 0xec, 0x07, 0xb3, + 0x59, 0x4a, 0xa5, 0x2c, 0x1b, 0x6f, 0x79, 0x37, 0xaf, 0x8c, 0xc7, 0x95, 0x6e, 0xda, 0xb8, 0xad, + 0xdb, 0x17, 0x19, 0xd1, 0x77, 0xa3, 0x1a, 0x81, 0xd7, 0x22, 0x5c, 0x8e, 0x33, 0xa2, 0xc3, 0xee, + 0xe3, 0xdd, 0x1c, 0x34, 0x8d, 0x2f, 0xe0, 0x0d, 0x4d, 0xad, 0x66, 0x07, 0xf5, 0xb6, 0xbd, 0x76, + 0xa5, 0x8d, 0xb5, 0xd4, 0x7d, 0xdb, 0xc0, 0x37, 0xa6, 0xa0, 0xe8, 0x93, 0x0b, 0x45, 0x93, 0x72, + 0xe8, 0xfb, 0xd8, 0x90, 0x2c, 0x4a, 0x68, 0x5a, 0x6f, 0x5b, 0xaf, 0xd6, 0x93, 0x35, 0x36, 0x90, + 0xdd, 0xc3, 0x98, 0x70, 0x08, 0xe7, 0x7e, 0x1c, 0xc8, 0xb8, 0xdc, 0xb7, 0x04, 0x83, 0x70, 0xfe, + 0x2c, 0x90, 0xb1, 0x79, 0x17, 0xb7, 0xa8, 0x80, 0x30, 0xf6, 0x93, 0x6c, 0x61, 0x6d, 0x97, 0x54, + 0xd7, 0x4b, 0xe1, 0x79, 0xb6, 0xd0, 0x00, 0x31, 0x65, 0x51, 0xac, 0xac, 0x6b, 0xa5, 0x53, 0xaf, + 0x7e, 0x3e, 0x6c, 0xe3, 0x3f, 0x1e, 0xf6, 0xe8, 0xec, 0xd3, 0xd2, 0x46, 0x97, 0x4b, 0x1b, 0x7d, + 0x5b, 0xda, 0xe8, 0xc3, 0xca, 0xde, 0xba, 0x5c, 0xd9, 0x5b, 0x9f, 0x57, 0xf6, 0xd6, 0xab, 0xe1, + 0xdf, 0xc2, 0x2f, 0x7e, 0x7b, 0xa8, 0xaa, 0x10, 0x54, 0x12, 0xa3, 0x7c, 0x74, 0x47, 0xdf, 0x03, + 0x00, 0x00, 0xff, 0xff, 0xd2, 0x99, 0x6b, 0x42, 0xce, 0x03, 0x00, 0x00, } func (m *BlsKey) Marshal() (dAtA []byte, err error) { @@ -303,7 +387,7 @@ func (m *BlsKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBlsKey(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x12 } if m.Pubkey != nil { { @@ -315,7 +399,7 @@ func (m *BlsKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBlsKey(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -350,14 +434,14 @@ func (m *ProofOfPossession) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBlsKey(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x12 } if len(m.Ed25519Sig) > 0 { i -= len(m.Ed25519Sig) copy(dAtA[i:], m.Ed25519Sig) i = encodeVarintBlsKey(dAtA, i, uint64(len(m.Ed25519Sig))) i-- - dAtA[i] = 0x12 + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -441,6 +525,72 @@ func (m *ValidatorWithBlsKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *VoteExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VoteExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BlsSig != nil { + { + size := m.BlsSig.Size() + i -= size + if _, err := m.BlsSig.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBlsKey(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.Height != 0 { + i = encodeVarintBlsKey(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x28 + } + if m.EpochNum != 0 { + i = encodeVarintBlsKey(dAtA, i, uint64(m.EpochNum)) + i-- + dAtA[i] = 0x20 + } + if len(m.BlockHash) > 0 { + i -= len(m.BlockHash) + copy(dAtA[i:], m.BlockHash) + i = encodeVarintBlsKey(dAtA, i, uint64(len(m.BlockHash))) + i-- + dAtA[i] = 0x1a + } + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintBlsKey(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintBlsKey(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBlsKey(dAtA []byte, offset int, v uint64) int { offset -= sovBlsKey(v) base := offset @@ -521,6 +671,37 @@ func (m *ValidatorWithBlsKey) Size() (n int) { return n } +func (m *VoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovBlsKey(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovBlsKey(uint64(l)) + } + l = len(m.BlockHash) + if l > 0 { + n += 1 + l + sovBlsKey(uint64(l)) + } + if m.EpochNum != 0 { + n += 1 + sovBlsKey(uint64(m.EpochNum)) + } + if m.Height != 0 { + n += 1 + sovBlsKey(uint64(m.Height)) + } + if m.BlsSig != nil { + l = m.BlsSig.Size() + n += 1 + l + sovBlsKey(uint64(l)) + } + return n +} + func sovBlsKey(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -556,7 +737,7 @@ func (m *BlsKey) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: BlsKey: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: + case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pubkey", wireType) } @@ -591,7 +772,7 @@ func (m *BlsKey) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Pop", wireType) } @@ -677,7 +858,7 @@ func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: ProofOfPossession: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: + case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Ed25519Sig", wireType) } @@ -711,7 +892,7 @@ func (m *ProofOfPossession) Unmarshal(dAtA []byte) error { m.Ed25519Sig = []byte{} } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BlsSig", wireType) } @@ -986,6 +1167,227 @@ func (m *ValidatorWithBlsKey) Unmarshal(dAtA []byte) error { } return nil } +func (m *VoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBlsKey + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBlsKey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBlsKey + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBlsKey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBlsKey + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBlsKey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BlockHash = append(m.BlockHash[:0], dAtA[iNdEx:postIndex]...) + if m.BlockHash == nil { + m.BlockHash = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochNum", wireType) + } + m.EpochNum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochNum |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BlsSig", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBlsKey + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBlsKey + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBlsKey + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_babylonchain_babylon_crypto_bls12381.Signature + m.BlsSig = &v + if err := m.BlsSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBlsKey(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBlsKey + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBlsKey(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/checkpointing/types/checkpoint.pb.go b/x/checkpointing/types/checkpoint.pb.go index ee8a7af89..99e7572eb 100644 --- a/x/checkpointing/types/checkpoint.pb.go +++ b/x/checkpointing/types/checkpoint.pb.go @@ -7,6 +7,7 @@ import ( bytes "bytes" fmt "fmt" github_com_babylonchain_babylon_crypto_bls12381 "github.com/babylonchain/babylon/crypto/bls12381" + types "github.com/cometbft/cometbft/abci/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" @@ -73,9 +74,9 @@ func (CheckpointStatus) EnumDescriptor() ([]byte, []int) { type RawCheckpoint struct { // epoch_num defines the epoch number the raw checkpoint is for EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` - // app_hash defines the 'AppHash' that individual BLS sigs are - // signed on - AppHash *AppHash `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3,customtype=AppHash" json:"app_hash,omitempty"` + // block_hash defines the 'BlockID.Hash', which is the hash of + // the block that individual BLS sigs are signed on + BlockHash *BlockHash `protobuf:"bytes,2,opt,name=block_hash,json=blockHash,proto3,customtype=BlockHash" json:"block_hash,omitempty"` // bitmap defines the bitmap that indicates the signers of the BLS multi sig Bitmap []byte `protobuf:"bytes,3,opt,name=bitmap,proto3" json:"bitmap,omitempty"` // bls_multi_sig defines the multi sig that is aggregated from individual BLS @@ -206,6 +207,59 @@ func (m *RawCheckpointWithMeta) GetLifecycle() []*CheckpointStateUpdate { return nil } +// InjectedCheckpoint wraps the checkpoint and the extended votes +type InjectedCheckpoint struct { + Ckpt *RawCheckpointWithMeta `protobuf:"bytes,1,opt,name=ckpt,proto3" json:"ckpt,omitempty"` + ExtendedCommitInfo *types.ExtendedCommitInfo `protobuf:"bytes,2,opt,name=extended_commit_info,json=extendedCommitInfo,proto3" json:"extended_commit_info,omitempty"` +} + +func (m *InjectedCheckpoint) Reset() { *m = InjectedCheckpoint{} } +func (m *InjectedCheckpoint) String() string { return proto.CompactTextString(m) } +func (*InjectedCheckpoint) ProtoMessage() {} +func (*InjectedCheckpoint) Descriptor() ([]byte, []int) { + return fileDescriptor_73996df9c6aabde4, []int{2} +} +func (m *InjectedCheckpoint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InjectedCheckpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InjectedCheckpoint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InjectedCheckpoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_InjectedCheckpoint.Merge(m, src) +} +func (m *InjectedCheckpoint) XXX_Size() int { + return m.Size() +} +func (m *InjectedCheckpoint) XXX_DiscardUnknown() { + xxx_messageInfo_InjectedCheckpoint.DiscardUnknown(m) +} + +var xxx_messageInfo_InjectedCheckpoint proto.InternalMessageInfo + +func (m *InjectedCheckpoint) GetCkpt() *RawCheckpointWithMeta { + if m != nil { + return m.Ckpt + } + return nil +} + +func (m *InjectedCheckpoint) GetExtendedCommitInfo() *types.ExtendedCommitInfo { + if m != nil { + return m.ExtendedCommitInfo + } + return nil +} + // CheckpointStateUpdate defines a state transition on the checkpoint. type CheckpointStateUpdate struct { // state defines the event of a state transition towards this state @@ -222,7 +276,7 @@ func (m *CheckpointStateUpdate) Reset() { *m = CheckpointStateUpdate{} } func (m *CheckpointStateUpdate) String() string { return proto.CompactTextString(m) } func (*CheckpointStateUpdate) ProtoMessage() {} func (*CheckpointStateUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_73996df9c6aabde4, []int{2} + return fileDescriptor_73996df9c6aabde4, []int{3} } func (m *CheckpointStateUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -276,21 +330,25 @@ func (m *CheckpointStateUpdate) GetBlockTime() *time.Time { type BlsSig struct { // epoch_num defines the epoch number that the BLS sig is signed on EpochNum uint64 `protobuf:"varint,1,opt,name=epoch_num,json=epochNum,proto3" json:"epoch_num,omitempty"` - // app_hash defines the 'AppHash' that the BLS sig is signed on - AppHash *AppHash `protobuf:"bytes,2,opt,name=app_hash,json=appHash,proto3,customtype=AppHash" json:"app_hash,omitempty"` - BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` + // block_hash defines the 'BlockID.Hash', which is the hash of + // the block that individual BLS sigs are signed on + BlockHash *BlockHash `protobuf:"bytes,2,opt,name=block_hash,json=blockHash,proto3,customtype=BlockHash" json:"block_hash,omitempty"` + BlsSig *github_com_babylonchain_babylon_crypto_bls12381.Signature `protobuf:"bytes,3,opt,name=bls_sig,json=blsSig,proto3,customtype=github.com/babylonchain/babylon/crypto/bls12381.Signature" json:"bls_sig,omitempty"` // can't find cosmos_proto.scalar when compiling due to cosmos v0.45.4 does // not support scalar string signer_address = 4 [(cosmos_proto.scalar) = - // "cosmos.AddressString"]; the signer_address defines the address of the + // "cosmos.AddressString"] + // the signer_address defines the address of the // signer SignerAddress string `protobuf:"bytes,4,opt,name=signer_address,json=signerAddress,proto3" json:"signer_address,omitempty"` + // validator_address defines the validator's consensus address + ValidatorAddress string `protobuf:"bytes,5,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` } func (m *BlsSig) Reset() { *m = BlsSig{} } func (m *BlsSig) String() string { return proto.CompactTextString(m) } func (*BlsSig) ProtoMessage() {} func (*BlsSig) Descriptor() ([]byte, []int) { - return fileDescriptor_73996df9c6aabde4, []int{3} + return fileDescriptor_73996df9c6aabde4, []int{4} } func (m *BlsSig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -333,10 +391,18 @@ func (m *BlsSig) GetSignerAddress() string { return "" } +func (m *BlsSig) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + func init() { proto.RegisterEnum("babylon.checkpointing.v1.CheckpointStatus", CheckpointStatus_name, CheckpointStatus_value) proto.RegisterType((*RawCheckpoint)(nil), "babylon.checkpointing.v1.RawCheckpoint") proto.RegisterType((*RawCheckpointWithMeta)(nil), "babylon.checkpointing.v1.RawCheckpointWithMeta") + proto.RegisterType((*InjectedCheckpoint)(nil), "babylon.checkpointing.v1.InjectedCheckpoint") proto.RegisterType((*CheckpointStateUpdate)(nil), "babylon.checkpointing.v1.CheckpointStateUpdate") proto.RegisterType((*BlsSig)(nil), "babylon.checkpointing.v1.BlsSig") } @@ -346,55 +412,61 @@ func init() { } var fileDescriptor_73996df9c6aabde4 = []byte{ - // 755 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x5d, 0x6b, 0xe3, 0x46, - 0x14, 0xb5, 0x62, 0xad, 0x13, 0x8f, 0xe3, 0xc5, 0x0c, 0x4d, 0x31, 0x2e, 0xd8, 0x6e, 0xa0, 0xad, - 0xbb, 0x0f, 0x12, 0xf6, 0x52, 0xe8, 0x07, 0xa5, 0x95, 0x3f, 0xd2, 0x35, 0x1b, 0x7b, 0x83, 0x64, - 0xb7, 0xb0, 0x50, 0xc4, 0x48, 0x9e, 0x8c, 0x06, 0x4b, 0x1a, 0xa1, 0x19, 0x6d, 0xea, 0xfe, 0x82, - 0x92, 0xa7, 0xfc, 0x81, 0x40, 0xa1, 0x3f, 0xa5, 0x2f, 0x7d, 0x0c, 0x85, 0x42, 0xc9, 0x43, 0x5a, - 0x92, 0x97, 0xb6, 0xbf, 0xa2, 0x68, 0xe4, 0x7c, 0x38, 0x69, 0x68, 0xbb, 0xe4, 0xed, 0xea, 0x70, - 0xce, 0x65, 0xce, 0xb9, 0xf7, 0x0a, 0xbc, 0xef, 0x20, 0x67, 0xe1, 0xb3, 0x50, 0x77, 0x3d, 0xec, - 0xce, 0x23, 0x46, 0x43, 0x41, 0x43, 0xa2, 0xbf, 0x6a, 0xdf, 0x00, 0xb4, 0x28, 0x66, 0x82, 0xc1, - 0xea, 0x92, 0xaa, 0xad, 0x50, 0xb5, 0x57, 0xed, 0x5a, 0x83, 0x30, 0x46, 0x7c, 0xac, 0x4b, 0x9e, - 0x93, 0xec, 0xeb, 0x82, 0x06, 0x98, 0x0b, 0x14, 0x44, 0x99, 0xb4, 0xf6, 0x06, 0x61, 0x84, 0xc9, - 0x52, 0x4f, 0xab, 0x0c, 0xdd, 0xfe, 0x45, 0x01, 0x65, 0x13, 0x1d, 0xf4, 0xae, 0xda, 0xc1, 0xb7, - 0x40, 0x11, 0x47, 0xcc, 0xf5, 0xec, 0x30, 0x09, 0xaa, 0x4a, 0x53, 0x69, 0xa9, 0xe6, 0x86, 0x04, - 0xc6, 0x49, 0x00, 0xdf, 0x05, 0x1b, 0x28, 0x8a, 0x6c, 0x0f, 0x71, 0xaf, 0xba, 0xd6, 0x54, 0x5a, - 0x9b, 0xdd, 0xd2, 0xe9, 0x59, 0x63, 0xdd, 0x88, 0xa2, 0x67, 0x88, 0x7b, 0xe6, 0x3a, 0xca, 0x0a, - 0xf8, 0x26, 0x28, 0x38, 0x54, 0x04, 0x28, 0xaa, 0xe6, 0x53, 0x96, 0xb9, 0xfc, 0x82, 0x08, 0x94, - 0x1d, 0x9f, 0xdb, 0x41, 0xe2, 0x0b, 0x6a, 0x73, 0x4a, 0xaa, 0xaa, 0x6c, 0xf2, 0xe9, 0xe9, 0x59, - 0xe3, 0x23, 0x42, 0x85, 0x97, 0x38, 0x9a, 0xcb, 0x02, 0x7d, 0xe9, 0xd2, 0xf5, 0x10, 0x0d, 0xf5, - 0xab, 0x74, 0xe2, 0x45, 0x24, 0x98, 0xee, 0xf8, 0xbc, 0xdd, 0x79, 0xfa, 0x61, 0x5b, 0xb3, 0x28, - 0x09, 0x91, 0x48, 0x62, 0x6c, 0x96, 0x1c, 0x9f, 0x8f, 0xd2, 0x96, 0x16, 0x25, 0x1f, 0xab, 0x7f, - 0x7c, 0xdf, 0x50, 0xb6, 0xff, 0x5c, 0x03, 0x5b, 0x2b, 0xbe, 0xbe, 0xa2, 0xc2, 0x1b, 0x61, 0x81, - 0xe0, 0x27, 0x40, 0x75, 0xe7, 0x91, 0x90, 0xd6, 0x4a, 0x9d, 0xf7, 0xb4, 0xfb, 0x12, 0xd5, 0x56, - 0xe4, 0xa6, 0x14, 0xc1, 0x2e, 0x28, 0x70, 0x81, 0x44, 0xc2, 0xa5, 0xfb, 0xc7, 0x9d, 0x27, 0xf7, - 0xcb, 0xaf, 0xb5, 0x96, 0x54, 0x98, 0x4b, 0x25, 0xfc, 0x1a, 0xa4, 0xef, 0xb5, 0x11, 0x21, 0xb1, - 0x1d, 0xcd, 0xb3, 0x80, 0x5e, 0x2f, 0x81, 0xbd, 0xc4, 0xf1, 0xa9, 0xfb, 0x1c, 0x2f, 0xcc, 0xa2, - 0xe3, 0x73, 0x83, 0x90, 0x78, 0x6f, 0x9e, 0xce, 0x2f, 0x62, 0x07, 0x38, 0xb6, 0x79, 0x12, 0xc8, - 0x78, 0x55, 0x73, 0x43, 0x02, 0x56, 0x12, 0xc0, 0x11, 0x28, 0xfa, 0x74, 0x1f, 0xbb, 0x0b, 0xd7, - 0xc7, 0xd5, 0x47, 0xcd, 0x7c, 0xab, 0xd4, 0xd1, 0xff, 0xab, 0x05, 0x3c, 0x8d, 0x66, 0x48, 0x60, - 0xf3, 0xba, 0xc3, 0x32, 0xeb, 0x1f, 0x15, 0xb0, 0xf5, 0x8f, 0x54, 0xf8, 0x39, 0x78, 0x94, 0x9a, - 0xc6, 0x32, 0xec, 0xff, 0x97, 0x56, 0x26, 0x84, 0x6f, 0x83, 0x4d, 0xc7, 0x67, 0xee, 0xdc, 0xf6, - 0x30, 0x25, 0x9e, 0x90, 0xb1, 0xab, 0xe9, 0xc0, 0x99, 0x3b, 0x7f, 0x26, 0x21, 0xf8, 0x19, 0x00, - 0x19, 0x25, 0xdd, 0x78, 0x19, 0x67, 0xa9, 0x53, 0xd3, 0xb2, 0x73, 0xd0, 0x2e, 0xcf, 0x41, 0x9b, - 0x5c, 0x9e, 0x43, 0x57, 0x3d, 0xfa, 0xad, 0xa1, 0xa4, 0x89, 0x31, 0x77, 0x9e, 0xa2, 0x4b, 0x17, - 0x3f, 0x2b, 0xa0, 0xd0, 0xf5, 0xb9, 0x45, 0xc9, 0xc3, 0x9c, 0xc0, 0x97, 0x60, 0x3d, 0x1d, 0x73, - 0xba, 0xe4, 0xf9, 0x87, 0x58, 0xf2, 0x82, 0x93, 0x3d, 0xee, 0x1d, 0xf0, 0x98, 0x53, 0x12, 0xe2, - 0xd8, 0x46, 0xb3, 0x59, 0x8c, 0x39, 0x97, 0x43, 0x2e, 0x9a, 0xe5, 0x0c, 0x35, 0x32, 0x50, 0x9a, - 0xca, 0x3d, 0xf9, 0x4b, 0x01, 0x95, 0xdb, 0xd1, 0x42, 0x0d, 0x54, 0x7b, 0xcf, 0xf7, 0x26, 0xb6, - 0x35, 0x31, 0x26, 0x53, 0xcb, 0x36, 0x7a, 0xbd, 0xe9, 0x68, 0xba, 0x6b, 0x4c, 0x86, 0xe3, 0x2f, - 0x2a, 0xb9, 0x5a, 0xe5, 0xf0, 0xb8, 0xb9, 0x69, 0xb8, 0x6e, 0x12, 0x24, 0x3e, 0x4a, 0xc7, 0x03, - 0xb7, 0x01, 0xbc, 0xc9, 0xb7, 0x06, 0xc6, 0xee, 0xa0, 0x5f, 0x51, 0x6a, 0xe0, 0xf0, 0xb8, 0x59, - 0xb0, 0x30, 0xf2, 0xf1, 0x0c, 0xb6, 0xc0, 0xd6, 0x0a, 0x67, 0xda, 0x1d, 0x0d, 0x27, 0x93, 0x41, - 0xbf, 0xb2, 0x56, 0x2b, 0x1f, 0x1e, 0x37, 0x8b, 0x56, 0xe2, 0x04, 0x54, 0x88, 0xbb, 0xcc, 0xde, - 0x8b, 0xf1, 0xce, 0xd0, 0x1c, 0x0d, 0xfa, 0x95, 0x7c, 0xc6, 0xec, 0xb1, 0x70, 0x9f, 0xc6, 0xc1, - 0x5d, 0xe6, 0xce, 0x70, 0x6c, 0xec, 0x0e, 0x5f, 0x0e, 0xfa, 0x15, 0x35, 0x63, 0xee, 0xd0, 0x10, - 0xf9, 0xf4, 0x5b, 0x3c, 0xab, 0xa9, 0xdf, 0xfd, 0x50, 0xcf, 0x75, 0x5f, 0xfc, 0x74, 0x5e, 0x57, - 0x4e, 0xce, 0xeb, 0xca, 0xef, 0xe7, 0x75, 0xe5, 0xe8, 0xa2, 0x9e, 0x3b, 0xb9, 0xa8, 0xe7, 0x7e, - 0xbd, 0xa8, 0xe7, 0x5e, 0x7e, 0xf0, 0x6f, 0xb1, 0x7f, 0x73, 0xeb, 0xdf, 0x2b, 0x16, 0x11, 0xe6, - 0x4e, 0x41, 0x6e, 0xcf, 0xd3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9c, 0x61, 0x57, 0xf2, 0xa1, - 0x05, 0x00, 0x00, + // 853 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0xe3, 0x44, + 0x1c, 0x8d, 0x5b, 0x37, 0x6c, 0x26, 0xcd, 0x2a, 0x8c, 0xb6, 0x28, 0xca, 0x4a, 0x49, 0x28, 0x42, + 0x94, 0x05, 0xd9, 0x6a, 0x56, 0x48, 0xfc, 0x11, 0x82, 0xc4, 0x4d, 0x21, 0xda, 0xa6, 0x5b, 0xd9, + 0x09, 0x48, 0x2b, 0x21, 0x6b, 0x6c, 0x4f, 0xec, 0x21, 0xb6, 0xc7, 0xf2, 0x8c, 0xbb, 0x1b, 0xee, + 0x48, 0xa8, 0xa7, 0xfd, 0x02, 0x95, 0x90, 0xf8, 0x02, 0x7c, 0x07, 0x2e, 0x1c, 0xf7, 0x88, 0x16, + 0x69, 0x41, 0xed, 0x05, 0xf8, 0x14, 0x68, 0xc6, 0x4e, 0xd3, 0x6c, 0x59, 0xb1, 0xa0, 0xde, 0x26, + 0xcf, 0xef, 0x4d, 0x66, 0xde, 0xef, 0x3d, 0x0d, 0x78, 0xdb, 0x41, 0xce, 0x3c, 0xa4, 0xb1, 0xee, + 0x06, 0xd8, 0x9d, 0x25, 0x94, 0xc4, 0x9c, 0xc4, 0xbe, 0x7e, 0xbc, 0x7b, 0x09, 0xd0, 0x92, 0x94, + 0x72, 0x0a, 0x1b, 0x05, 0x55, 0x5b, 0xa1, 0x6a, 0xc7, 0xbb, 0xcd, 0xb6, 0x4f, 0xa9, 0x1f, 0x62, + 0x5d, 0xf2, 0x9c, 0x6c, 0xaa, 0x73, 0x12, 0x61, 0xc6, 0x51, 0x94, 0xe4, 0xd2, 0xe6, 0x2d, 0x9f, + 0xfa, 0x54, 0x2e, 0x75, 0xb1, 0x2a, 0xd0, 0xdb, 0x1c, 0xc7, 0x1e, 0x4e, 0x23, 0x12, 0x73, 0x1d, + 0x39, 0x2e, 0xd1, 0xf9, 0x3c, 0xc1, 0x2c, 0xff, 0xb8, 0xfd, 0xab, 0x02, 0x6a, 0x26, 0x7a, 0x68, + 0x5c, 0xfc, 0x17, 0xbc, 0x0d, 0x2a, 0x38, 0xa1, 0x6e, 0x60, 0xc7, 0x59, 0xd4, 0x50, 0x3a, 0xca, + 0x8e, 0x6a, 0xde, 0x90, 0xc0, 0x61, 0x16, 0xc1, 0x77, 0x01, 0x70, 0x42, 0xea, 0xce, 0xec, 0x00, + 0xb1, 0xa0, 0xb1, 0xd6, 0x51, 0x76, 0x36, 0xfb, 0xb5, 0xa7, 0xcf, 0xda, 0x95, 0xbe, 0x40, 0x3f, + 0x47, 0x2c, 0x30, 0x2b, 0xce, 0x62, 0x09, 0x5f, 0x03, 0x65, 0x87, 0xf0, 0x08, 0x25, 0x8d, 0x75, + 0xc1, 0x34, 0x8b, 0x5f, 0x10, 0x81, 0x9a, 0x13, 0x32, 0x3b, 0xca, 0x42, 0x4e, 0x6c, 0x46, 0xfc, + 0x86, 0x2a, 0x37, 0xfa, 0xf8, 0xe9, 0xb3, 0xf6, 0x07, 0x3e, 0xe1, 0x41, 0xe6, 0x68, 0x2e, 0x8d, + 0xf4, 0xc2, 0x08, 0x37, 0x40, 0x24, 0xd6, 0x2f, 0x0c, 0x4c, 0xe7, 0x09, 0xa7, 0xba, 0x13, 0xb2, + 0xdd, 0xee, 0xdd, 0xf7, 0x77, 0x35, 0x8b, 0xf8, 0x31, 0xe2, 0x59, 0x8a, 0xcd, 0xaa, 0x13, 0xb2, + 0x91, 0xd8, 0xd2, 0x22, 0xfe, 0x87, 0xea, 0x1f, 0xdf, 0xb7, 0x95, 0xed, 0x3f, 0xd7, 0xc0, 0xd6, + 0xca, 0xed, 0xbe, 0x24, 0x3c, 0x18, 0x61, 0x8e, 0xe0, 0x47, 0x40, 0x75, 0x67, 0x09, 0x97, 0x17, + 0xac, 0x76, 0xdf, 0xd2, 0x5e, 0x64, 0xba, 0xb6, 0x22, 0x37, 0xa5, 0x08, 0xf6, 0x41, 0x99, 0x71, + 0xc4, 0x33, 0x26, 0x1d, 0xb8, 0xd9, 0xbd, 0xf3, 0x62, 0xf9, 0x52, 0x6b, 0x49, 0x85, 0x59, 0x28, + 0xe1, 0x57, 0x40, 0x9c, 0xd7, 0x46, 0xbe, 0x9f, 0xda, 0xc9, 0x2c, 0x37, 0xe8, 0xff, 0x39, 0x70, + 0x94, 0x39, 0x21, 0x71, 0xef, 0xe1, 0xb9, 0xb0, 0x9e, 0xf5, 0x7c, 0x3f, 0x3d, 0x9a, 0x89, 0x29, + 0x26, 0xf4, 0x21, 0x4e, 0x6d, 0x96, 0x45, 0xd2, 0x5e, 0xd5, 0xbc, 0x21, 0x01, 0x2b, 0x8b, 0xe0, + 0x08, 0x54, 0x42, 0x32, 0xc5, 0xee, 0xdc, 0x0d, 0x71, 0x63, 0xa3, 0xb3, 0xbe, 0x53, 0xed, 0xea, + 0x2f, 0x7b, 0x05, 0x3c, 0x49, 0x3c, 0xc4, 0xb1, 0xb9, 0xdc, 0xa1, 0xf0, 0xfa, 0x47, 0x05, 0xc0, + 0x61, 0xfc, 0x35, 0x76, 0x39, 0xf6, 0x2e, 0xc5, 0xc9, 0x58, 0x31, 0x5a, 0x7f, 0x49, 0xa3, 0x17, + 0x73, 0x2a, 0x0c, 0x9f, 0x80, 0x5b, 0xf8, 0x91, 0x8c, 0xb1, 0x67, 0xbb, 0x34, 0x8a, 0x08, 0xb7, + 0x49, 0x3c, 0xa5, 0xd2, 0xfe, 0x6a, 0xf7, 0x0d, 0x6d, 0x99, 0x70, 0x4d, 0x24, 0x5c, 0x1b, 0x14, + 0x64, 0x43, 0x72, 0x87, 0xf1, 0x94, 0x9a, 0x10, 0x5f, 0xc1, 0xb6, 0x7f, 0x52, 0xc0, 0xd6, 0x3f, + 0xde, 0x0e, 0x7e, 0x0a, 0x36, 0xc4, 0x9c, 0xb0, 0x3c, 0xf6, 0x7f, 0x1b, 0x70, 0x2e, 0x84, 0xaf, + 0x83, 0xcd, 0xa2, 0x29, 0x98, 0xf8, 0x01, 0x97, 0x47, 0x55, 0x45, 0x46, 0x45, 0x39, 0x24, 0x04, + 0x3f, 0x59, 0x94, 0x49, 0xf4, 0x58, 0x26, 0xa0, 0xda, 0x6d, 0x6a, 0x79, 0xc9, 0xb5, 0x45, 0xc9, + 0xb5, 0xf1, 0xa2, 0xe4, 0x7d, 0xf5, 0xf1, 0x6f, 0x6d, 0xa5, 0xe8, 0x97, 0x40, 0x0b, 0xe3, 0xbf, + 0x5d, 0x03, 0xe5, 0x7e, 0xc8, 0x2c, 0xe2, 0x5f, 0x67, 0x77, 0xbf, 0x00, 0xaf, 0x88, 0x7c, 0x8a, + 0x76, 0xae, 0x5f, 0x47, 0x3b, 0xcb, 0x4e, 0x7e, 0xc4, 0x37, 0xc1, 0x4d, 0x46, 0xfc, 0x18, 0xa7, + 0x36, 0xf2, 0xbc, 0x14, 0x33, 0x26, 0xd3, 0x59, 0x31, 0x6b, 0x39, 0xda, 0xcb, 0x41, 0xf8, 0x0e, + 0x78, 0xf5, 0x18, 0x85, 0xc4, 0x43, 0x9c, 0x2e, 0x99, 0x1b, 0x92, 0x59, 0xbf, 0xf8, 0x50, 0x90, + 0xa5, 0x0f, 0xa5, 0x3b, 0x7f, 0x29, 0xa0, 0xfe, 0xfc, 0x34, 0xa0, 0x06, 0x1a, 0xc6, 0xbd, 0xa3, + 0xb1, 0x6d, 0x8d, 0x7b, 0xe3, 0x89, 0x65, 0xf7, 0x0c, 0x63, 0x32, 0x9a, 0x1c, 0xf4, 0xc6, 0xc3, + 0xc3, 0xcf, 0xea, 0xa5, 0x66, 0xfd, 0xe4, 0xb4, 0xb3, 0xd9, 0x73, 0xdd, 0x2c, 0xca, 0x42, 0x24, + 0x26, 0x0a, 0xb7, 0x01, 0xbc, 0xcc, 0xb7, 0x06, 0xbd, 0x83, 0xc1, 0x5e, 0x5d, 0x69, 0x82, 0x93, + 0xd3, 0x4e, 0xd9, 0xc2, 0x28, 0xc4, 0x1e, 0xdc, 0x01, 0x5b, 0x2b, 0x9c, 0x49, 0x7f, 0x34, 0x1c, + 0x8f, 0x07, 0x7b, 0xf5, 0xb5, 0x66, 0xed, 0xe4, 0xb4, 0x53, 0xb1, 0x32, 0x27, 0x22, 0x9c, 0x5f, + 0x65, 0x1a, 0xf7, 0x0f, 0xf7, 0x87, 0xe6, 0x68, 0xb0, 0x57, 0x5f, 0xcf, 0x99, 0x06, 0x8d, 0xa7, + 0x24, 0x8d, 0xae, 0x32, 0xf7, 0x87, 0x87, 0xbd, 0x83, 0xe1, 0x83, 0xc1, 0x5e, 0x5d, 0xcd, 0x99, + 0xfb, 0x24, 0x46, 0x21, 0xf9, 0x06, 0x7b, 0x4d, 0xf5, 0xbb, 0x1f, 0x5a, 0xa5, 0xfe, 0xfd, 0x9f, + 0xcf, 0x5a, 0xca, 0x93, 0xb3, 0x96, 0xf2, 0xfb, 0x59, 0x4b, 0x79, 0x7c, 0xde, 0x2a, 0x3d, 0x39, + 0x6f, 0x95, 0x7e, 0x39, 0x6f, 0x95, 0x1e, 0xbc, 0xf7, 0x6f, 0x33, 0x7a, 0xf4, 0xdc, 0x23, 0x24, + 0x9f, 0x03, 0xa7, 0x2c, 0x03, 0x77, 0xf7, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc9, 0x97, 0x52, + 0x24, 0xaa, 0x06, 0x00, 0x00, } func (this *RawCheckpoint) Equal(that interface{}) bool { @@ -419,11 +491,11 @@ func (this *RawCheckpoint) Equal(that interface{}) bool { if this.EpochNum != that1.EpochNum { return false } - if that1.AppHash == nil { - if this.AppHash != nil { + if that1.BlockHash == nil { + if this.BlockHash != nil { return false } - } else if !this.AppHash.Equal(*that1.AppHash) { + } else if !this.BlockHash.Equal(*that1.BlockHash) { return false } if !bytes.Equal(this.Bitmap, that1.Bitmap) { @@ -556,11 +628,11 @@ func (m *RawCheckpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - if m.AppHash != nil { + if m.BlockHash != nil { { - size := m.AppHash.Size() + size := m.BlockHash.Size() i -= size - if _, err := m.AppHash.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.BlockHash.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintCheckpoint(dAtA, i, uint64(size)) @@ -647,6 +719,53 @@ func (m *RawCheckpointWithMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *InjectedCheckpoint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InjectedCheckpoint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InjectedCheckpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ExtendedCommitInfo != nil { + { + size, err := m.ExtendedCommitInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCheckpoint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Ckpt != nil { + { + size, err := m.Ckpt.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCheckpoint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *CheckpointStateUpdate) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -668,12 +787,12 @@ func (m *CheckpointStateUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l if m.BlockTime != nil { - n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) - if err2 != nil { - return 0, err2 + n4, err4 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.BlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.BlockTime):]) + if err4 != nil { + return 0, err4 } - i -= n2 - i = encodeVarintCheckpoint(dAtA, i, uint64(n2)) + i -= n4 + i = encodeVarintCheckpoint(dAtA, i, uint64(n4)) i-- dAtA[i] = 0x1a } @@ -710,6 +829,13 @@ func (m *BlsSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintCheckpoint(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x2a + } if len(m.SignerAddress) > 0 { i -= len(m.SignerAddress) copy(dAtA[i:], m.SignerAddress) @@ -729,11 +855,11 @@ func (m *BlsSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x1a } - if m.AppHash != nil { + if m.BlockHash != nil { { - size := m.AppHash.Size() + size := m.BlockHash.Size() i -= size - if _, err := m.AppHash.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.BlockHash.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintCheckpoint(dAtA, i, uint64(size)) @@ -769,8 +895,8 @@ func (m *RawCheckpoint) Size() (n int) { if m.EpochNum != 0 { n += 1 + sovCheckpoint(uint64(m.EpochNum)) } - if m.AppHash != nil { - l = m.AppHash.Size() + if m.BlockHash != nil { + l = m.BlockHash.Size() n += 1 + l + sovCheckpoint(uint64(l)) } l = len(m.Bitmap) @@ -813,6 +939,23 @@ func (m *RawCheckpointWithMeta) Size() (n int) { return n } +func (m *InjectedCheckpoint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Ckpt != nil { + l = m.Ckpt.Size() + n += 1 + l + sovCheckpoint(uint64(l)) + } + if m.ExtendedCommitInfo != nil { + l = m.ExtendedCommitInfo.Size() + n += 1 + l + sovCheckpoint(uint64(l)) + } + return n +} + func (m *CheckpointStateUpdate) Size() (n int) { if m == nil { return 0 @@ -841,8 +984,8 @@ func (m *BlsSig) Size() (n int) { if m.EpochNum != 0 { n += 1 + sovCheckpoint(uint64(m.EpochNum)) } - if m.AppHash != nil { - l = m.AppHash.Size() + if m.BlockHash != nil { + l = m.BlockHash.Size() n += 1 + l + sovCheckpoint(uint64(l)) } if m.BlsSig != nil { @@ -853,6 +996,10 @@ func (m *BlsSig) Size() (n int) { if l > 0 { n += 1 + l + sovCheckpoint(uint64(l)) } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovCheckpoint(uint64(l)) + } return n } @@ -912,7 +1059,7 @@ func (m *RawCheckpoint) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -939,9 +1086,9 @@ func (m *RawCheckpoint) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v AppHash - m.AppHash = &v - if err := m.AppHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v BlockHash + m.BlockHash = &v + if err := m.BlockHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1228,6 +1375,128 @@ func (m *RawCheckpointWithMeta) Unmarshal(dAtA []byte) error { } return nil } +func (m *InjectedCheckpoint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCheckpoint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InjectedCheckpoint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InjectedCheckpoint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ckpt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCheckpoint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCheckpoint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCheckpoint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Ckpt == nil { + m.Ckpt = &RawCheckpointWithMeta{} + } + if err := m.Ckpt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExtendedCommitInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCheckpoint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCheckpoint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCheckpoint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ExtendedCommitInfo == nil { + m.ExtendedCommitInfo = &types.ExtendedCommitInfo{} + } + if err := m.ExtendedCommitInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCheckpoint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCheckpoint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *CheckpointStateUpdate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1402,7 +1671,7 @@ func (m *BlsSig) Unmarshal(dAtA []byte) error { } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BlockHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1429,9 +1698,9 @@ func (m *BlsSig) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v AppHash - m.AppHash = &v - if err := m.AppHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v BlockHash + m.BlockHash = &v + if err := m.BlockHash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1502,6 +1771,38 @@ func (m *BlsSig) Unmarshal(dAtA []byte) error { } m.SignerAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCheckpoint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCheckpoint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCheckpoint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCheckpoint(dAtA[iNdEx:]) diff --git a/x/checkpointing/types/codec.go b/x/checkpointing/types/codec.go index cd1a2a495..5faec792e 100644 --- a/x/checkpointing/types/codec.go +++ b/x/checkpointing/types/codec.go @@ -14,7 +14,6 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { // Register messages registry.RegisterImplementations((*sdk.Msg)(nil), - &MsgAddBlsSig{}, &MsgWrappedCreateValidator{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/checkpointing/types/errors.go b/x/checkpointing/types/errors.go index 810ccb8d8..f212924c6 100644 --- a/x/checkpointing/types/errors.go +++ b/x/checkpointing/types/errors.go @@ -4,18 +4,19 @@ import errorsmod "cosmossdk.io/errors" // x/checkpointing module sentinel errors var ( - ErrCkptDoesNotExist = errorsmod.Register(ModuleName, 1201, "raw checkpoint does not exist") - ErrCkptAlreadyExist = errorsmod.Register(ModuleName, 1202, "raw checkpoint already exists") - ErrCkptHashNotEqual = errorsmod.Register(ModuleName, 1203, "hash does not equal to raw checkpoint") - ErrCkptNotAccumulating = errorsmod.Register(ModuleName, 1204, "raw checkpoint is no longer accumulating BLS sigs") - ErrCkptAlreadyVoted = errorsmod.Register(ModuleName, 1205, "raw checkpoint already accumulated the validator") - ErrInvalidRawCheckpoint = errorsmod.Register(ModuleName, 1206, "raw checkpoint is invalid") - ErrInvalidCkptStatus = errorsmod.Register(ModuleName, 1207, "raw checkpoint's status is invalid") - ErrInvalidPoP = errorsmod.Register(ModuleName, 1208, "proof-of-possession is invalid") - ErrBlsKeyDoesNotExist = errorsmod.Register(ModuleName, 1209, "BLS public key does not exist") - ErrBlsKeyAlreadyExist = errorsmod.Register(ModuleName, 1210, "BLS public key already exists") - ErrBlsPrivKeyDoesNotExist = errorsmod.Register(ModuleName, 1211, "BLS private key does not exist") - ErrInvalidBlsSignature = errorsmod.Register(ModuleName, 1212, "BLS signature is invalid") - ErrConflictingCheckpoint = errorsmod.Register(ModuleName, 1213, "Conflicting checkpoint is found") - ErrInvalidAppHash = errorsmod.Register(ModuleName, 1214, "Provided app hash is Invalid") + ErrCkptDoesNotExist = errorsmod.Register(ModuleName, 1201, "raw checkpoint does not exist") + ErrCkptAlreadyExist = errorsmod.Register(ModuleName, 1202, "raw checkpoint already exists") + ErrCkptHashNotEqual = errorsmod.Register(ModuleName, 1203, "hash does not equal to raw checkpoint") + ErrCkptNotAccumulating = errorsmod.Register(ModuleName, 1204, "raw checkpoint is no longer accumulating BLS sigs") + ErrCkptAlreadyVoted = errorsmod.Register(ModuleName, 1205, "raw checkpoint already accumulated the validator") + ErrInvalidRawCheckpoint = errorsmod.Register(ModuleName, 1206, "raw checkpoint is invalid") + ErrInvalidCkptStatus = errorsmod.Register(ModuleName, 1207, "raw checkpoint's status is invalid") + ErrInvalidPoP = errorsmod.Register(ModuleName, 1208, "proof-of-possession is invalid") + ErrBlsKeyDoesNotExist = errorsmod.Register(ModuleName, 1209, "BLS public key does not exist") + ErrBlsKeyAlreadyExist = errorsmod.Register(ModuleName, 1210, "BLS public key already exists") + ErrBlsPrivKeyDoesNotExist = errorsmod.Register(ModuleName, 1211, "BLS private key does not exist") + ErrInvalidBlsSignature = errorsmod.Register(ModuleName, 1212, "BLS signature is invalid") + ErrConflictingCheckpoint = errorsmod.Register(ModuleName, 1213, "Conflicting checkpoint is found") + ErrInvalidAppHash = errorsmod.Register(ModuleName, 1214, "Provided app hash is Invalid") + ErrInsufficientVotingPower = errorsmod.Register(ModuleName, 1215, "Accumulated voting power is not greater than 2/3 of total power") ) diff --git a/x/checkpointing/types/expected_keepers.go b/x/checkpointing/types/expected_keepers.go index 609c2f880..329cdb67f 100644 --- a/x/checkpointing/types/expected_keepers.go +++ b/x/checkpointing/types/expected_keepers.go @@ -2,18 +2,23 @@ package types import ( "context" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" + + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" ) // EpochingKeeper defines the expected interface needed to retrieve epoch info type EpochingKeeper interface { GetEpoch(ctx context.Context) *epochingtypes.Epoch EnqueueMsg(ctx context.Context, msg epochingtypes.QueuedMessage) + GetAppHash(ctx context.Context, height uint64) ([]byte, error) GetValidatorSet(ctx context.Context, epochNumer uint64) epochingtypes.ValidatorSet GetTotalVotingPower(ctx context.Context, epochNumber uint64) int64 CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.MsgCreateValidator) error + GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) } // Event Hooks diff --git a/x/checkpointing/types/msgs.go b/x/checkpointing/types/msgs.go index 4a4f99e9a..4af049c62 100644 --- a/x/checkpointing/types/msgs.go +++ b/x/checkpointing/types/msgs.go @@ -3,32 +3,20 @@ package types import ( "errors" - "github.com/babylonchain/babylon/crypto/bls12381" codectypes "github.com/cosmos/cosmos-sdk/codec/types" ed255192 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/babylonchain/babylon/crypto/bls12381" ) var ( // Ensure that MsgInsertHeader implements all functions of the Msg interface - _ sdk.Msg = (*MsgAddBlsSig)(nil) _ sdk.Msg = (*MsgWrappedCreateValidator)(nil) ) -func NewMsgAddBlsSig(signer sdk.AccAddress, epochNum uint64, appHash AppHash, sig bls12381.Signature, addr sdk.ValAddress) *MsgAddBlsSig { - return &MsgAddBlsSig{ - Signer: signer.String(), - BlsSig: &BlsSig{ - EpochNum: epochNum, - AppHash: &appHash, - BlsSig: &sig, - SignerAddress: addr.String(), - }, - } -} - func NewMsgWrappedCreateValidator(msgCreateVal *stakingtypes.MsgCreateValidator, blsPK *bls12381.PublicKey, pop *ProofOfPossession) (*MsgWrappedCreateValidator, error) { return &MsgWrappedCreateValidator{ Key: &BlsKey{ @@ -39,20 +27,6 @@ func NewMsgWrappedCreateValidator(msgCreateVal *stakingtypes.MsgCreateValidator, }, nil } -// ValidateBasic validates stateless message elements -func (m *MsgAddBlsSig) ValidateBasic() error { - err := m.BlsSig.BlsSig.ValidateBasic() - if err != nil { - return err - } - err = m.BlsSig.AppHash.ValidateBasic() - if err != nil { - return err - } - - return nil -} - func (m *MsgWrappedCreateValidator) VerifyPoP(valPubkey cryptotypes.PubKey) bool { return m.Key.Pop.IsValid(*m.Key.Pubkey, valPubkey) } diff --git a/x/checkpointing/types/tx.pb.go b/x/checkpointing/types/tx.pb.go index d6d2350fa..9a309d96a 100644 --- a/x/checkpointing/types/tx.pb.go +++ b/x/checkpointing/types/tx.pb.go @@ -6,7 +6,6 @@ package types import ( context "context" fmt "fmt" - _ "github.com/cosmos/cosmos-proto" _ "github.com/cosmos/cosmos-sdk/types/msgservice" types "github.com/cosmos/cosmos-sdk/x/staking/types" _ "github.com/cosmos/gogoproto/gogoproto" @@ -31,86 +30,6 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// MsgAddBlsSig defines a message to add a bls signature from a -// validator -type MsgAddBlsSig struct { - // signer corresponds to the submitter of the transaction - // This might be a different entity compared to the one that created the BLS signature - // (i.e. the validator owner of the BLS key which is specified by the `bls_sig.signer_address`) - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` - BlsSig *BlsSig `protobuf:"bytes,2,opt,name=bls_sig,json=blsSig,proto3" json:"bls_sig,omitempty"` -} - -func (m *MsgAddBlsSig) Reset() { *m = MsgAddBlsSig{} } -func (m *MsgAddBlsSig) String() string { return proto.CompactTextString(m) } -func (*MsgAddBlsSig) ProtoMessage() {} -func (*MsgAddBlsSig) Descriptor() ([]byte, []int) { - return fileDescriptor_6b16c54750152c21, []int{0} -} -func (m *MsgAddBlsSig) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddBlsSig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddBlsSig.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddBlsSig) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddBlsSig.Merge(m, src) -} -func (m *MsgAddBlsSig) XXX_Size() int { - return m.Size() -} -func (m *MsgAddBlsSig) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddBlsSig.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddBlsSig proto.InternalMessageInfo - -// MsgAddBlsSigResponse defines the MsgAddBlsSig response type. -type MsgAddBlsSigResponse struct { -} - -func (m *MsgAddBlsSigResponse) Reset() { *m = MsgAddBlsSigResponse{} } -func (m *MsgAddBlsSigResponse) String() string { return proto.CompactTextString(m) } -func (*MsgAddBlsSigResponse) ProtoMessage() {} -func (*MsgAddBlsSigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6b16c54750152c21, []int{1} -} -func (m *MsgAddBlsSigResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgAddBlsSigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgAddBlsSigResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgAddBlsSigResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgAddBlsSigResponse.Merge(m, src) -} -func (m *MsgAddBlsSigResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgAddBlsSigResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgAddBlsSigResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgAddBlsSigResponse proto.InternalMessageInfo - // MsgWrappedCreateValidator defines a wrapped message to create a validator type MsgWrappedCreateValidator struct { Key *BlsKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` @@ -121,7 +40,7 @@ func (m *MsgWrappedCreateValidator) Reset() { *m = MsgWrappedCreateValid func (m *MsgWrappedCreateValidator) String() string { return proto.CompactTextString(m) } func (*MsgWrappedCreateValidator) ProtoMessage() {} func (*MsgWrappedCreateValidator) Descriptor() ([]byte, []int) { - return fileDescriptor_6b16c54750152c21, []int{2} + return fileDescriptor_6b16c54750152c21, []int{0} } func (m *MsgWrappedCreateValidator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -159,7 +78,7 @@ func (m *MsgWrappedCreateValidatorResponse) Reset() { *m = MsgWrappedCre func (m *MsgWrappedCreateValidatorResponse) String() string { return proto.CompactTextString(m) } func (*MsgWrappedCreateValidatorResponse) ProtoMessage() {} func (*MsgWrappedCreateValidatorResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6b16c54750152c21, []int{3} + return fileDescriptor_6b16c54750152c21, []int{1} } func (m *MsgWrappedCreateValidatorResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -189,8 +108,6 @@ func (m *MsgWrappedCreateValidatorResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgWrappedCreateValidatorResponse proto.InternalMessageInfo func init() { - proto.RegisterType((*MsgAddBlsSig)(nil), "babylon.checkpointing.v1.MsgAddBlsSig") - proto.RegisterType((*MsgAddBlsSigResponse)(nil), "babylon.checkpointing.v1.MsgAddBlsSigResponse") proto.RegisterType((*MsgWrappedCreateValidator)(nil), "babylon.checkpointing.v1.MsgWrappedCreateValidator") proto.RegisterType((*MsgWrappedCreateValidatorResponse)(nil), "babylon.checkpointing.v1.MsgWrappedCreateValidatorResponse") } @@ -198,37 +115,30 @@ func init() { func init() { proto.RegisterFile("babylon/checkpointing/v1/tx.proto", fileDescriptor_6b16c54750152c21) } var fileDescriptor_6b16c54750152c21 = []byte{ - // 476 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x93, 0x4f, 0x6b, 0xd4, 0x40, - 0x18, 0xc6, 0x33, 0x16, 0x57, 0x3a, 0x7a, 0x1a, 0x97, 0xba, 0xcd, 0x21, 0xfb, 0x47, 0x28, 0x75, - 0xc1, 0x89, 0xdd, 0xe2, 0xc1, 0x7a, 0xea, 0x7a, 0x94, 0x45, 0x48, 0x41, 0x41, 0x84, 0x65, 0x92, - 0x0c, 0xb3, 0xc3, 0x26, 0x99, 0x90, 0x77, 0x5c, 0x9a, 0x9b, 0x78, 0x12, 0x4f, 0xfd, 0x08, 0xfd, - 0x08, 0x3d, 0xf8, 0x21, 0x3c, 0x2e, 0x9e, 0x3c, 0xca, 0xee, 0xa1, 0x7e, 0x0a, 0x91, 0x4d, 0x26, - 0xec, 0x5a, 0x1b, 0x15, 0x4f, 0xc9, 0xcc, 0xfc, 0xde, 0xe7, 0x7d, 0x9e, 0x37, 0x19, 0xdc, 0xf5, - 0x99, 0x9f, 0x47, 0x2a, 0x71, 0x83, 0x09, 0x0f, 0xa6, 0xa9, 0x92, 0x89, 0x96, 0x89, 0x70, 0x67, - 0x07, 0xae, 0x3e, 0xa5, 0x69, 0xa6, 0xb4, 0x22, 0x2d, 0x83, 0xd0, 0x5f, 0x10, 0x3a, 0x3b, 0xb0, - 0x9b, 0x42, 0x09, 0x55, 0x40, 0xee, 0xea, 0xad, 0xe4, 0xed, 0x07, 0xb5, 0x92, 0xeb, 0x0d, 0x83, - 0xee, 0xd5, 0xa2, 0x7e, 0x04, 0xe3, 0x29, 0xcf, 0x0d, 0xd7, 0x0e, 0x14, 0xc4, 0x0a, 0x5c, 0xd0, - 0x6c, 0x5a, 0x02, 0x3e, 0xd7, 0x6c, 0xed, 0xd1, 0xde, 0x2d, 0x81, 0x71, 0x69, 0xa6, 0x5c, 0x98, - 0xa3, 0x7b, 0xa6, 0x36, 0x86, 0x42, 0x38, 0x06, 0x51, 0x1e, 0xf4, 0xce, 0x10, 0xbe, 0x33, 0x02, - 0x71, 0x1c, 0x86, 0xc3, 0x08, 0x4e, 0xa4, 0x20, 0x8f, 0x70, 0x03, 0xa4, 0x48, 0x78, 0xd6, 0x42, - 0x1d, 0xb4, 0xbf, 0x3d, 0x6c, 0x7d, 0xf9, 0xf4, 0xb0, 0x69, 0xb4, 0x8e, 0xc3, 0x30, 0xe3, 0x00, - 0x27, 0x3a, 0x93, 0x89, 0xf0, 0x0c, 0x47, 0x9e, 0xe0, 0x5b, 0x2b, 0xa3, 0x20, 0x45, 0xeb, 0x46, - 0x07, 0xed, 0xdf, 0x1e, 0x74, 0x68, 0xdd, 0xb0, 0x68, 0xd9, 0xc4, 0x6b, 0xf8, 0xc5, 0xf3, 0xe8, - 0xee, 0x87, 0xf3, 0xb6, 0xf5, 0xfd, 0xbc, 0x6d, 0xbd, 0xbf, 0xbc, 0xe8, 0x1b, 0xbd, 0xde, 0x0e, - 0x6e, 0x6e, 0x3a, 0xf2, 0x38, 0xa4, 0x2a, 0x01, 0xde, 0x9b, 0x23, 0xbc, 0x3b, 0x02, 0xf1, 0x2a, - 0x63, 0x69, 0xca, 0xc3, 0x67, 0x19, 0x67, 0x9a, 0xbf, 0x64, 0x91, 0x0c, 0x99, 0x56, 0x19, 0x19, - 0xe0, 0xad, 0x29, 0xcf, 0x0b, 0xd3, 0x7f, 0x73, 0xf0, 0x9c, 0xe7, 0xde, 0x0a, 0x26, 0x6f, 0x70, - 0x33, 0x06, 0x31, 0x0e, 0x0a, 0xa9, 0xf1, 0xac, 0xd2, 0x32, 0x31, 0xfa, 0xd4, 0xc4, 0x36, 0x03, - 0xa7, 0x66, 0xe0, 0x74, 0x04, 0xe2, 0x4a, 0x77, 0x8f, 0xc4, 0xbf, 0xed, 0x1d, 0x75, 0x37, 0xc3, - 0x5d, 0xdb, 0xa8, 0x77, 0x1f, 0x77, 0x6b, 0x13, 0x55, 0xb9, 0x07, 0x3f, 0x10, 0xde, 0x1a, 0x81, - 0x20, 0x01, 0xde, 0x5e, 0x7f, 0xa6, 0xbd, 0xfa, 0x84, 0x9b, 0xc3, 0xb3, 0xe9, 0xbf, 0x71, 0x55, - 0x33, 0xf2, 0x11, 0xe1, 0x9d, 0x9a, 0x09, 0x1f, 0xfe, 0x51, 0xea, 0xfa, 0x22, 0xfb, 0xe9, 0x7f, - 0x14, 0x55, 0x66, 0xec, 0x9b, 0xef, 0x2e, 0x2f, 0xfa, 0x68, 0xf8, 0xe2, 0xf3, 0xc2, 0x41, 0xf3, - 0x85, 0x83, 0xbe, 0x2d, 0x1c, 0x74, 0xb6, 0x74, 0xac, 0xf9, 0xd2, 0xb1, 0xbe, 0x2e, 0x1d, 0xeb, - 0xf5, 0x63, 0x21, 0xf5, 0xe4, 0xad, 0x4f, 0x03, 0x15, 0xbb, 0xa6, 0x4f, 0x30, 0x61, 0x32, 0xa9, - 0x16, 0xee, 0xe9, 0x95, 0x4b, 0xa5, 0xf3, 0x94, 0x83, 0xdf, 0x28, 0xfe, 0xfd, 0xc3, 0x9f, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xb7, 0x5a, 0x7e, 0xa6, 0xf8, 0x03, 0x00, 0x00, + // 357 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0x3d, 0x4b, 0xfb, 0x40, + 0x1c, 0xc7, 0x73, 0xff, 0xf2, 0x77, 0x38, 0xb7, 0x50, 0xb4, 0x66, 0x48, 0x1f, 0x04, 0x91, 0x0e, + 0x77, 0xb4, 0xc5, 0x45, 0xb7, 0x3a, 0x4a, 0x11, 0x3a, 0x28, 0x88, 0x50, 0x2e, 0xe9, 0x71, 0x0d, + 0x79, 0xb8, 0x90, 0xdf, 0x59, 0x9a, 0x4d, 0x9c, 0xc4, 0xc9, 0xd5, 0xad, 0x2f, 0xa1, 0x2f, 0xc3, + 0xb1, 0xa3, 0xa3, 0xb4, 0x43, 0x7d, 0x19, 0xd2, 0x24, 0x45, 0xac, 0xde, 0xe2, 0x76, 0x0f, 0x9f, + 0xdf, 0xf7, 0x81, 0x3b, 0x5c, 0x77, 0x98, 0x93, 0x06, 0x32, 0xa2, 0xee, 0x88, 0xbb, 0x7e, 0x2c, + 0xbd, 0x48, 0x79, 0x91, 0xa0, 0xe3, 0x16, 0x55, 0x13, 0x12, 0x27, 0x52, 0x49, 0xb3, 0x52, 0x20, + 0xe4, 0x1b, 0x42, 0xc6, 0x2d, 0xab, 0x2c, 0xa4, 0x90, 0x19, 0x44, 0xd7, 0xab, 0x9c, 0xb7, 0x8e, + 0xb4, 0x92, 0x4e, 0x00, 0x03, 0x9f, 0xa7, 0x05, 0x57, 0x75, 0x25, 0x84, 0x12, 0x28, 0x28, 0xe6, + 0xe7, 0x80, 0xc3, 0x15, 0xfb, 0x32, 0xb6, 0xf6, 0x0b, 0x20, 0x84, 0x6c, 0x3a, 0x04, 0x91, 0x5f, + 0x34, 0xe6, 0x08, 0x1f, 0xf4, 0x40, 0x5c, 0x27, 0x2c, 0x8e, 0xf9, 0xf0, 0x3c, 0xe1, 0x4c, 0xf1, + 0x2b, 0x16, 0x78, 0x43, 0xa6, 0x64, 0x62, 0xb6, 0x71, 0xc9, 0xe7, 0x69, 0x05, 0xd5, 0xd0, 0xf1, + 0x6e, 0xbb, 0x46, 0x74, 0xe9, 0x49, 0x37, 0x80, 0x0b, 0x9e, 0xf6, 0xd7, 0xb0, 0x79, 0x8b, 0xcb, + 0x21, 0x88, 0x81, 0x9b, 0x49, 0x0d, 0xc6, 0x1b, 0xad, 0xca, 0xbf, 0x4c, 0xa4, 0x49, 0xf2, 0x24, + 0xa4, 0x88, 0x4a, 0x8a, 0xa8, 0xa4, 0x07, 0x62, 0xcb, 0xbd, 0x6f, 0x86, 0x3f, 0xce, 0x4e, 0xeb, + 0x8f, 0xd3, 0xaa, 0xf1, 0x31, 0xad, 0x1a, 0x0f, 0xab, 0x59, 0xf3, 0x57, 0xa3, 0xc6, 0x21, 0xae, + 0x6b, 0x1b, 0xf5, 0x39, 0xc4, 0x32, 0x02, 0xde, 0x7e, 0x41, 0xb8, 0xd4, 0x03, 0x61, 0x3e, 0x21, + 0xbc, 0xa7, 0x29, 0xdf, 0xd1, 0xf7, 0xd5, 0xea, 0x5b, 0x67, 0x7f, 0x18, 0xda, 0x84, 0xb2, 0xfe, + 0xdf, 0xaf, 0x66, 0x4d, 0xd4, 0xbd, 0x7c, 0x5d, 0xd8, 0x68, 0xbe, 0xb0, 0xd1, 0xfb, 0xc2, 0x46, + 0xcf, 0x4b, 0xdb, 0x98, 0x2f, 0x6d, 0xe3, 0x6d, 0x69, 0x1b, 0x37, 0x27, 0xc2, 0x53, 0xa3, 0x3b, + 0x87, 0xb8, 0x32, 0xa4, 0x85, 0x8f, 0x3b, 0x62, 0x5e, 0xb4, 0xd9, 0xd0, 0xc9, 0xd6, 0x4f, 0x51, + 0x69, 0xcc, 0xc1, 0xd9, 0xc9, 0xde, 0xba, 0xf3, 0x19, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x29, 0x9a, + 0x31, 0xa2, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -243,8 +153,6 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { - // AddBlsSig defines a method for accumulating BLS signatures - AddBlsSig(ctx context.Context, in *MsgAddBlsSig, opts ...grpc.CallOption) (*MsgAddBlsSigResponse, error) // WrappedCreateValidator defines a method for registering a new validator WrappedCreateValidator(ctx context.Context, in *MsgWrappedCreateValidator, opts ...grpc.CallOption) (*MsgWrappedCreateValidatorResponse, error) } @@ -257,15 +165,6 @@ func NewMsgClient(cc grpc1.ClientConn) MsgClient { return &msgClient{cc} } -func (c *msgClient) AddBlsSig(ctx context.Context, in *MsgAddBlsSig, opts ...grpc.CallOption) (*MsgAddBlsSigResponse, error) { - out := new(MsgAddBlsSigResponse) - err := c.cc.Invoke(ctx, "/babylon.checkpointing.v1.Msg/AddBlsSig", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *msgClient) WrappedCreateValidator(ctx context.Context, in *MsgWrappedCreateValidator, opts ...grpc.CallOption) (*MsgWrappedCreateValidatorResponse, error) { out := new(MsgWrappedCreateValidatorResponse) err := c.cc.Invoke(ctx, "/babylon.checkpointing.v1.Msg/WrappedCreateValidator", in, out, opts...) @@ -277,8 +176,6 @@ func (c *msgClient) WrappedCreateValidator(ctx context.Context, in *MsgWrappedCr // MsgServer is the server API for Msg service. type MsgServer interface { - // AddBlsSig defines a method for accumulating BLS signatures - AddBlsSig(context.Context, *MsgAddBlsSig) (*MsgAddBlsSigResponse, error) // WrappedCreateValidator defines a method for registering a new validator WrappedCreateValidator(context.Context, *MsgWrappedCreateValidator) (*MsgWrappedCreateValidatorResponse, error) } @@ -287,9 +184,6 @@ type MsgServer interface { type UnimplementedMsgServer struct { } -func (*UnimplementedMsgServer) AddBlsSig(ctx context.Context, req *MsgAddBlsSig) (*MsgAddBlsSigResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddBlsSig not implemented") -} func (*UnimplementedMsgServer) WrappedCreateValidator(ctx context.Context, req *MsgWrappedCreateValidator) (*MsgWrappedCreateValidatorResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WrappedCreateValidator not implemented") } @@ -298,24 +192,6 @@ func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) } -func _Msg_AddBlsSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgAddBlsSig) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).AddBlsSig(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/babylon.checkpointing.v1.Msg/AddBlsSig", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).AddBlsSig(ctx, req.(*MsgAddBlsSig)) - } - return interceptor(ctx, in, info, handler) -} - func _Msg_WrappedCreateValidator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgWrappedCreateValidator) if err := dec(in); err != nil { @@ -338,10 +214,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.checkpointing.v1.Msg", HandlerType: (*MsgServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "AddBlsSig", - Handler: _Msg_AddBlsSig_Handler, - }, { MethodName: "WrappedCreateValidator", Handler: _Msg_WrappedCreateValidator_Handler, @@ -351,71 +223,6 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Metadata: "babylon/checkpointing/v1/tx.proto", } -func (m *MsgAddBlsSig) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgAddBlsSig) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddBlsSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.BlsSig != nil { - { - size, err := m.BlsSig.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgAddBlsSigResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgAddBlsSigResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgAddBlsSigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - func (m *MsgWrappedCreateValidator) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -497,32 +304,6 @@ func encodeVarintTx(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MsgAddBlsSig) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.BlsSig != nil { - l = m.BlsSig.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgAddBlsSigResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - func (m *MsgWrappedCreateValidator) Size() (n int) { if m == nil { return 0 @@ -555,174 +336,6 @@ func sovTx(x uint64) (n int) { func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MsgAddBlsSig) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgAddBlsSig: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddBlsSig: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Signer = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BlsSig", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.BlsSig == nil { - m.BlsSig = &BlsSig{} - } - if err := m.BlsSig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgAddBlsSigResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgAddBlsSigResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgAddBlsSigResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *MsgWrappedCreateValidator) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/checkpointing/types/types.go b/x/checkpointing/types/types.go index eb02ec64d..d3742a423 100644 --- a/x/checkpointing/types/types.go +++ b/x/checkpointing/types/types.go @@ -6,11 +6,12 @@ import ( "encoding/hex" "errors" - "github.com/babylonchain/babylon/crypto/bls12381" - epochingtypes "github.com/babylonchain/babylon/x/epoching/types" "github.com/boljen/go-bitmap" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/crypto/bls12381" + epochingtypes "github.com/babylonchain/babylon/x/epoching/types" ) const ( @@ -19,16 +20,16 @@ const ( BitmapBits = 104 // 104 bits for 104 validators at top ) -type AppHash []byte +type BlockHash []byte type BlsSigHash []byte type RawCkptHash []byte -func NewCheckpoint(epochNum uint64, appHash AppHash) *RawCheckpoint { +func NewCheckpoint(epochNum uint64, blockHash BlockHash) *RawCheckpoint { return &RawCheckpoint{ EpochNum: epochNum, - AppHash: &appHash, + BlockHash: &blockHash, Bitmap: bitmap.New(BitmapBits), // 13 bytes, holding 100 validators BlsMultiSig: nil, } @@ -98,7 +99,7 @@ func (cm *RawCheckpointWithMeta) Accumulate( // accumulate voting power and update status when the threshold is reached cm.PowerSum += uint64(val.Power) - if int64(cm.PowerSum) > totalPower/3 { + if int64(cm.PowerSum)*3 > totalPower*2 { cm.Status = Sealed } @@ -122,68 +123,52 @@ func (cm *RawCheckpointWithMeta) RecordStateUpdate(ctx context.Context, status C cm.Lifecycle = append(cm.Lifecycle, stateUpdate) } -func NewAppHashFromHex(s string) (AppHash, error) { - bz, err := hex.DecodeString(s) - if err != nil { - return nil, err - } - - var appHash AppHash - - err = appHash.Unmarshal(bz) - if err != nil { - return nil, err - } - - return appHash, nil -} - -func (appHash *AppHash) Unmarshal(bz []byte) error { +func (bh *BlockHash) Unmarshal(bz []byte) error { if len(bz) != HashSize { return errors.New("invalid appHash length") } - *appHash = bz + *bh = bz return nil } -func (appHash *AppHash) Size() (n int) { - if appHash == nil { +func (bh *BlockHash) Size() (n int) { + if bh == nil { return 0 } - return len(*appHash) + return len(*bh) } -func (appHash *AppHash) Equal(l AppHash) bool { - return appHash.String() == l.String() +func (bh *BlockHash) Equal(l BlockHash) bool { + return bh.String() == l.String() } -func (appHash *AppHash) String() string { - return hex.EncodeToString(*appHash) +func (bh *BlockHash) String() string { + return hex.EncodeToString(*bh) } -func (appHash *AppHash) MustMarshal() []byte { - bz, err := appHash.Marshal() +func (bh *BlockHash) MustMarshal() []byte { + bz, err := bh.Marshal() if err != nil { panic(err) } return bz } -func (appHash *AppHash) Marshal() ([]byte, error) { - return *appHash, nil +func (bh *BlockHash) Marshal() ([]byte, error) { + return *bh, nil } -func (appHash AppHash) MarshalTo(data []byte) (int, error) { - copy(data, appHash) +func (bh BlockHash) MarshalTo(data []byte) (int, error) { + copy(data, bh) return len(data), nil } -func (appHash *AppHash) ValidateBasic() error { - if appHash == nil { - return errors.New("invalid appHash") +func (bh *BlockHash) ValidateBasic() error { + if bh == nil { + return errors.New("invalid block hash") } - if len(*appHash) != HashSize { - return errors.New("invalid appHash") + if len(*bh) != HashSize { + return errors.New("invalid block hash") } return nil } @@ -193,7 +178,7 @@ func (ckpt RawCheckpoint) ValidateBasic() error { if ckpt.Bitmap == nil { return ErrInvalidRawCheckpoint.Wrapf("bitmap cannot be empty") } - err := ckpt.AppHash.ValidateBasic() + err := ckpt.BlockHash.ValidateBasic() if err != nil { return ErrInvalidRawCheckpoint.Wrapf(err.Error()) } @@ -214,3 +199,14 @@ func BytesToCkptWithMeta(cdc codec.BinaryCodec, bz []byte) (*RawCheckpointWithMe err := cdc.Unmarshal(bz, ckptWithMeta) return ckptWithMeta, err } + +func (ve *VoteExtension) ToBLSSig() *BlsSig { + blockHash := BlockHash(ve.BlockHash) + return &BlsSig{ + EpochNum: ve.EpochNum, + BlockHash: &blockHash, + BlsSig: ve.BlsSig, + SignerAddress: ve.Signer, + ValidatorAddress: ve.ValidatorAddress, + } +} diff --git a/x/checkpointing/types/types_test.go b/x/checkpointing/types/types_test.go index 0d90ba99e..3875024c9 100644 --- a/x/checkpointing/types/types_test.go +++ b/x/checkpointing/types/types_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/client" "github.com/stretchr/testify/require" "github.com/babylonchain/babylon/testutil/datagen" @@ -19,11 +18,11 @@ func TestRawCheckpointWithMeta_Accumulate1(t *testing.T) { epochNum := uint64(2) n := 1 totalPower := int64(10) - ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - appHash := datagen.GenRandomAppHash(r) - msg := types.GetSignBytes(epochNum, appHash) + ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil) + blockHash := datagen.GenRandomBlockHash(r) + msg := types.GetSignBytes(epochNum, blockHash) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) - ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, appHash) + ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, blockHash) require.NoError(t, err) valSet := datagen.GenRandomValSet(n) err = ckpt.Accumulate(valSet, valSet[0].Addr, blsPubkeys[0], blsSigs[0], totalPower) @@ -42,24 +41,23 @@ func TestRawCheckpointWithMeta_Accumulate4(t *testing.T) { epochNum := uint64(2) n := 4 totalPower := int64(10) * int64(n) - ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil, client.Context{}) - appHash := datagen.GenRandomAppHash(r) - msg := types.GetSignBytes(epochNum, appHash) + ckptkeeper, ctx, _ := testkeeper.CheckpointingKeeper(t, nil, nil) + blockHash := datagen.GenRandomBlockHash(r) + msg := types.GetSignBytes(epochNum, blockHash) blsPubkeys, blsSigs := datagen.GenRandomPubkeysAndSigs(n, msg) - ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, appHash) + ckpt, err := ckptkeeper.BuildRawCheckpoint(ctx, epochNum, blockHash) require.NoError(t, err) valSet := datagen.GenRandomValSet(n) for i := 0; i < n; i++ { err = ckpt.Accumulate(valSet, valSet[i].Addr, blsPubkeys[i], blsSigs[i], totalPower) - if i == 0 { + if i <= 1 { require.NoError(t, err) require.Equal(t, types.Accumulating, ckpt.Status) } - if i == 1 { - require.NoError(t, err) + if i == 2 { require.Equal(t, types.Sealed, ckpt.Status) } - if i >= 2 { + if i == 3 { require.ErrorIs(t, err, types.ErrCkptNotAccumulating) require.Equal(t, types.Sealed, ckpt.Status) } diff --git a/x/checkpointing/types/utils.go b/x/checkpointing/types/utils.go index a7594b96b..faef788dd 100644 --- a/x/checkpointing/types/utils.go +++ b/x/checkpointing/types/utils.go @@ -14,7 +14,7 @@ import ( func (m BlsSig) Hash() BlsSigHash { fields := [][]byte{ sdk.Uint64ToBigEndian(m.EpochNum), - m.AppHash.MustMarshal(), + m.BlockHash.MustMarshal(), m.BlsSig.MustMarshal(), []byte(m.SignerAddress), } @@ -24,7 +24,7 @@ func (m BlsSig) Hash() BlsSigHash { func (m RawCheckpoint) Hash() RawCkptHash { fields := [][]byte{ sdk.Uint64ToBigEndian(m.EpochNum), - m.AppHash.MustMarshal(), + m.BlockHash.MustMarshal(), m.BlsMultiSig.MustMarshal(), m.Bitmap, } @@ -38,7 +38,7 @@ func (m RawCheckpoint) HashStr() string { // SignedMsg is the message corresponding to the BLS sig in this raw checkpoint // Its value should be (epoch_number || app_hash) func (m RawCheckpoint) SignedMsg() []byte { - return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.AppHash...) + return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.BlockHash...) } func hash(fields [][]byte) []byte { @@ -78,8 +78,8 @@ func FromBTCCkptBytesToRawCkpt(btcCkptBytes []byte) (*RawCheckpoint, error) { } func FromBTCCkptToRawCkpt(btcCkpt *btctxformatter.RawBtcCheckpoint) (*RawCheckpoint, error) { - var appHash AppHash - err := appHash.Unmarshal(btcCkpt.AppHash) + var blockHash BlockHash + err := blockHash.Unmarshal(btcCkpt.BlockHash) if err != nil { return nil, err } @@ -90,7 +90,7 @@ func FromBTCCkptToRawCkpt(btcCkpt *btctxformatter.RawBtcCheckpoint) (*RawCheckpo } rawCheckpoint := &RawCheckpoint{ EpochNum: btcCkpt.Epoch, - AppHash: &appHash, + BlockHash: &blockHash, Bitmap: btcCkpt.BitMap, BlsMultiSig: &blsSig, } @@ -99,7 +99,7 @@ func FromBTCCkptToRawCkpt(btcCkpt *btctxformatter.RawBtcCheckpoint) (*RawCheckpo } func FromRawCkptToBTCCkpt(rawCkpt *RawCheckpoint, address []byte) (*btctxformatter.RawBtcCheckpoint, error) { - appHashBytes, err := rawCkpt.AppHash.Marshal() + appHashBytes, err := rawCkpt.BlockHash.Marshal() if err != nil { return nil, err } @@ -110,7 +110,7 @@ func FromRawCkptToBTCCkpt(rawCkpt *RawCheckpoint, address []byte) (*btctxformatt btcCkpt := &btctxformatter.RawBtcCheckpoint{ Epoch: rawCkpt.EpochNum, - AppHash: appHashBytes, + BlockHash: appHashBytes, BitMap: rawCkpt.Bitmap, SubmitterAddress: address, BlsSig: blsSigBytes, diff --git a/x/checkpointing/vote_ext.go b/x/checkpointing/vote_ext.go new file mode 100644 index 000000000..749714124 --- /dev/null +++ b/x/checkpointing/vote_ext.go @@ -0,0 +1,154 @@ +package checkpointing + +import ( + "fmt" + + "cosmossdk.io/log" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/x/checkpointing/keeper" + ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" +) + +// VoteExtensionHandler defines a BLS-based vote extension handlers for Babylon. +type VoteExtensionHandler struct { + logger log.Logger + ckptKeeper *keeper.Keeper + valStore baseapp.ValidatorStore +} + +func NewVoteExtensionHandler(logger log.Logger, ckptKeeper *keeper.Keeper) *VoteExtensionHandler { + return &VoteExtensionHandler{logger: logger, ckptKeeper: ckptKeeper, valStore: ckptKeeper} +} + +func (h *VoteExtensionHandler) SetHandlers(bApp *baseapp.BaseApp) { + bApp.SetExtendVoteHandler(h.ExtendVote()) + bApp.SetVerifyVoteExtensionHandler(h.VerifyVoteExtension()) +} + +// ExtendVote sends a BLS signature as a vote extension +// the signature is signed over the hash of the last +// block of the current epoch +func (h *VoteExtensionHandler) ExtendVote() sdk.ExtendVoteHandler { + return func(ctx sdk.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { + k := h.ckptKeeper + // the returned response MUST not be nil + emptyRes := &abci.ResponseExtendVote{VoteExtension: []byte{}} + + epoch := k.GetEpoch(ctx) + // BLS vote extension is only applied at the last block of the current epoch + if !epoch.IsLastBlockByHeight(req.Height) { + return emptyRes, nil + } + + // 1. check if itself is the validator as the BLS sig is only signed + // when the node itself is a validator + signer := k.GetBLSSignerAddress() + curValSet := k.GetValidatorSet(ctx, epoch.EpochNumber) + _, _, err := curValSet.FindValidatorWithIndex(signer) + if err != nil { + // not being a validator is not an error + h.logger.Info("the BLS signer is not in the validator set", "signer_address", signer.String()) + return emptyRes, nil + } + + // 2. sign BLS signature + blsSig, err := k.SignBLS(epoch.EpochNumber, req.Hash) + if err != nil { + // the returned error will lead to panic + return emptyRes, fmt.Errorf("failed to sign BLS signature at epoch %v, height %v", + epoch.EpochNumber, req.Height) + } + + // 3. build vote extension + ve := &ckpttypes.VoteExtension{ + Signer: signer.String(), + ValidatorAddress: k.GetValidatorAddress().String(), + BlockHash: req.Hash, + EpochNum: epoch.EpochNumber, + Height: uint64(req.Height), + BlsSig: &blsSig, + } + bz, err := ve.Marshal() + if err != nil { + return emptyRes, fmt.Errorf("failed to encode vote extension: %w", err) + } + + h.logger.Info("successfully sent BLS signature in vote extension", + "epoch", epoch.EpochNumber, "height", req.Height) + + return &abci.ResponseExtendVote{VoteExtension: bz}, nil + } +} + +// VerifyVoteExtension verifies the BLS sig within the vote extension +func (h *VoteExtensionHandler) VerifyVoteExtension() sdk.VerifyVoteExtensionHandler { + return func(ctx sdk.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) { + k := h.ckptKeeper + resAccept := &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT} + resReject := &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_REJECT} + + epoch := k.GetEpoch(ctx) + // BLS vote extension is only applied at the last block of the current epoch + if !epoch.IsLastBlockByHeight(req.Height) { + return resAccept, nil + } + + if len(req.VoteExtension) == 0 { + h.logger.Error("received empty vote extension", "height", req.Height) + return resReject, nil + } + + var ve ckpttypes.VoteExtension + if err := ve.Unmarshal(req.VoteExtension); err != nil { + h.logger.Error("failed to unmarshal vote extension", "err", err, "height", req.Height) + return resReject, nil + } + + // 1. verify epoch number + if epoch.EpochNumber != ve.EpochNum { + h.logger.Error("invalid epoch number in the vote extension", + "want", epoch.EpochNumber, "got", ve.EpochNum) + return resReject, nil + } + + // 2. ensure the validator address in the BLS sig matches the signer of the vote extension + // this prevents validators use valid BLS from another validator + blsSig := ve.ToBLSSig() + extensionSigner := sdk.ValAddress(req.ValidatorAddress).String() + if extensionSigner != blsSig.ValidatorAddress { + h.logger.Error("the vote extension signer does not match the BLS signature signer", + "extension signer", extensionSigner, "BLS signer", blsSig.SignerAddress) + return resReject, nil + } + + // 3. verify signing hash + if !blsSig.BlockHash.Equal(req.Hash) { + // processed BlsSig message is for invalid last commit hash + h.logger.Error("in valid block ID in BLS sig", "want", req.Hash, "got", blsSig.BlockHash) + return resReject, nil + } + + // 4. verify the validity of the BLS signature + if err := k.VerifyBLSSig(ctx, blsSig); err != nil { + // Note: reject this vote extension as BLS is invalid + // this will also reject the corresponding PreCommit vote + h.logger.Error("invalid BLS sig in vote extension", + "err", err, + "height", req.Height, + "epoch", epoch.EpochNumber, + ) + return resReject, nil + } + + h.logger.Info("successfully verified vote extension", + "signer_address", ve.Signer, + "height", req.Height, + "epoch", epoch.EpochNumber, + ) + + return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil + } +} diff --git a/x/checkpointing/vote_ext_test.go b/x/checkpointing/vote_ext_test.go new file mode 100644 index 000000000..ed0717b09 --- /dev/null +++ b/x/checkpointing/vote_ext_test.go @@ -0,0 +1,131 @@ +package checkpointing_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/testutil/datagen" + testhelper "github.com/babylonchain/babylon/testutil/helper" + "github.com/babylonchain/babylon/x/checkpointing/types" +) + +// FuzzAddBLSSigVoteExtension_MultipleVals tests adding BLS signatures via VoteExtension +// with multiple validators +func FuzzAddBLSSigVoteExtension_MultipleVals(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 4) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) + ek := helper.App.EpochingKeeper + ck := helper.App.CheckpointingKeeper + + epoch := ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + + // go to block 11, ensure the checkpoint is finalized + interval := ek.GetParams(helper.Ctx).EpochInterval + for i := uint64(0); i < interval; i++ { + _, err := helper.ApplyEmptyBlockWithVoteExtension(r) + require.NoError(t, err) + } + + epoch = ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(2), epoch.EpochNumber) + + ckpt, err := ck.GetRawCheckpoint(helper.Ctx, epoch.EpochNumber-1) + require.NoError(t, err) + require.Equal(t, types.Sealed, ckpt.Status) + }) +} + +// FuzzAddBLSSigVoteExtension_InsufficientVotingPower tests adding BLS signatures +// with insufficient voting power +func FuzzAddBLSSigVoteExtension_InsufficientVotingPower(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 4) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) + ek := helper.App.EpochingKeeper + + epoch := ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + + // the number of validators is less than 2/3 if the total set + numOfValidators := datagen.RandomInt(r, 5) + 1 + genesisValSet.Keys = genesisValSet.Keys[:numOfValidators] + interval := ek.GetParams(helper.Ctx).EpochInterval + for i := uint64(0); i < interval-1; i++ { + _, err := helper.ApplyEmptyBlockWithValSet(r, genesisValSet) + if i < interval-2 { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } + }) +} + +// FuzzAddBLSSigVoteExtension_ValidBLSSig tests adding BLS signatures +// with valid BLS signature +func FuzzAddBLSSigVoteExtension_ValidBLSSig(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 4) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + helper := testhelper.NewHelper(t) + ek := helper.App.EpochingKeeper + ck := helper.App.CheckpointingKeeper + + epoch := ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + + // go to block 11, ensure the checkpoint is finalized + interval := ek.GetParams(helper.Ctx).EpochInterval + for i := uint64(0); i < interval; i++ { + _, err := helper.ApplyEmptyBlockWithValidBLSSig(r) + require.NoError(t, err) + } + + epoch = ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(2), epoch.EpochNumber) + + ckpt, err := ck.GetRawCheckpoint(helper.Ctx, epoch.EpochNumber-1) + require.NoError(t, err) + require.Equal(t, types.Sealed, ckpt.Status) + }) +} + +// FuzzAddBLSSigVoteExtension_InvalidBLSSig tests adding BLS signatures +// with invalid BLS signature +func FuzzAddBLSSigVoteExtension_InvalidBLSSig(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 4) + + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + helper := testhelper.NewHelper(t) + ek := helper.App.EpochingKeeper + + epoch := ek.GetEpoch(helper.Ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + + interval := ek.GetParams(helper.Ctx).EpochInterval + for i := uint64(0); i < interval-1; i++ { + _, err := helper.ApplyEmptyBlockWithInvalidBLSSig(r) + if i < interval-2 { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } + }) +} diff --git a/x/epoching/abci.go b/x/epoching/abci.go index 4e468d65a..34d9f561f 100644 --- a/x/epoching/abci.go +++ b/x/epoching/abci.go @@ -15,7 +15,7 @@ import ( // BeginBlocker is called at the beginning of every block. // Upon each BeginBlock, -// - record the current AppHash +// - record the current BlockHash // - if reaching the epoch beginning, then // - increment epoch number // - trigger AfterEpochBegins hook @@ -29,7 +29,7 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) sdkCtx := sdk.UnwrapSDKContext(ctx) - // record the current AppHash + // record the current BlockHash k.RecordAppHash(ctx) // if this block is the first block of the next epoch @@ -38,6 +38,9 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { if epoch.IsFirstBlockOfNextEpoch(ctx) { // increase epoch number incEpoch := k.IncEpoch(ctx) + // record the BlockHash referencing + // the last block of the previous epoch + k.RecordSealerAppHashForPrevEpoch(ctx) // init the msg queue of this new epoch k.InitMsgQueue(ctx) // init the slashed voting power of this new epoch @@ -57,8 +60,10 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { } } - if epoch.IsSecondBlock(ctx) { - k.RecordSealerHeaderForPrevEpoch(ctx) + if epoch.IsLastBlock(ctx) { + // record the block hash of the last block + // of the sealing epoch + k.RecordSealerBlockHashForEpoch(ctx) } return nil } diff --git a/x/epoching/keeper/apphash_chain.go b/x/epoching/keeper/apphash_chain.go index c083d8cc8..9a4ddc4b4 100644 --- a/x/epoching/keeper/apphash_chain.go +++ b/x/epoching/keeper/apphash_chain.go @@ -9,10 +9,11 @@ import ( errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" - "github.com/babylonchain/babylon/x/epoching/types" "github.com/cometbft/cometbft/crypto/merkle" tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/x/epoching/types" ) func (k Keeper) setAppHash(ctx context.Context, height uint64, appHash []byte) { diff --git a/x/epoching/keeper/apphash_chain_test.go b/x/epoching/keeper/apphash_chain_test.go index 29520fb04..3b4148dd9 100644 --- a/x/epoching/keeper/apphash_chain_test.go +++ b/x/epoching/keeper/apphash_chain_test.go @@ -4,10 +4,11 @@ import ( "math/rand" "testing" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/keeper" - "github.com/stretchr/testify/require" ) func FuzzAppHashChain(f *testing.F) { @@ -28,9 +29,9 @@ func FuzzAppHashChain(f *testing.F) { epochInterval := k.GetParams(ctx).EpochInterval // reach the end of the 1st epoch - expectedHeight := epochInterval - 1 + expectedHeight := epochInterval - 2 for i := uint64(0); i < expectedHeight; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } // ensure epoch number is 1 diff --git a/x/epoching/keeper/epoch_msg_queue_test.go b/x/epoching/keeper/epoch_msg_queue_test.go index 5ebae2412..22703df73 100644 --- a/x/epoching/keeper/epoch_msg_queue_test.go +++ b/x/epoching/keeper/epoch_msg_queue_test.go @@ -4,13 +4,16 @@ import ( "math/rand" "testing" - appparams "github.com/babylonchain/babylon/app/params" "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" - sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + + appparams "github.com/babylonchain/babylon/app/params" ) var ( @@ -64,7 +67,10 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs params := keeper.GetParams(ctx) @@ -101,7 +107,7 @@ func FuzzHandleQueuedMsg_MsgWrappedDelegate(f *testing.F) { // go to BeginBlock of block 11, and thus entering epoch 2 for i := uint64(0); i < params.EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) @@ -125,7 +131,10 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator @@ -163,7 +172,7 @@ func FuzzHandleQueuedMsg_MsgWrappedUndelegate(f *testing.F) { // enter epoch 2 for i := uint64(0); i < keeper.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) @@ -197,7 +206,10 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, keeper, genAccs := helper.Ctx, helper.App.EpochingKeeper, helper.GenAccs // get genesis account's address, whose holder will be the delegator @@ -242,7 +254,7 @@ func FuzzHandleQueuedMsg_MsgWrappedBeginRedelegate(f *testing.F) { // enter epoch 2 for i := uint64(0); i < keeper.GetParams(ctx).EpochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } epoch = keeper.GetEpoch(ctx) diff --git a/x/epoching/keeper/epoch_slashed_val_set_test.go b/x/epoching/keeper/epoch_slashed_val_set_test.go index 46c32b713..90ae8ad4b 100644 --- a/x/epoching/keeper/epoch_slashed_val_set_test.go +++ b/x/epoching/keeper/epoch_slashed_val_set_test.go @@ -6,12 +6,14 @@ import ( "testing" sdkmath "cosmossdk.io/math" + "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" - "github.com/babylonchain/babylon/x/epoching/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + + "github.com/babylonchain/babylon/x/epoching/types" ) func FuzzSlashedValSet(f *testing.F) { @@ -20,10 +22,17 @@ func FuzzSlashedValSet(f *testing.F) { r := rand.New(rand.NewSource(seed)) var err error - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, keeper, stakingKeeper := helper.Ctx, helper.App.EpochingKeeper, helper.App.StakingKeeper getValSet := keeper.GetValidatorSet(ctx, 1) + // at epoch 1 right now + epoch := keeper.GetEpoch(ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + // slash a random subset of validators numSlashed := r.Intn(len(getValSet)) excpectedSlashedVals := []sdk.ValAddress{} @@ -50,11 +59,12 @@ func FuzzSlashedValSet(f *testing.F) { // go to epoch 2 epochInterval := keeper.GetParams(ctx).EpochInterval for i := uint64(0); i < epochInterval; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } - epochNumber := keeper.GetEpoch(ctx).EpochNumber - require.Equal(t, uint64(2), epochNumber) + epoch = keeper.GetEpoch(ctx) + require.Equal(t, uint64(2), epoch.EpochNumber) + // no validator is slashed in epoch 1 require.Empty(t, keeper.GetSlashedValidators(ctx, 2)) }) diff --git a/x/epoching/keeper/epoch_val_set_test.go b/x/epoching/keeper/epoch_val_set_test.go index 95e8438e2..8bc1e3b89 100644 --- a/x/epoching/keeper/epoch_val_set_test.go +++ b/x/epoching/keeper/epoch_val_set_test.go @@ -16,7 +16,10 @@ func FuzzEpochValSet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, keeper := helper.Ctx, helper.App.EpochingKeeper valSet, err := helper.App.StakingKeeper.GetLastValidators(helper.Ctx) require.NoError(t, err) @@ -28,15 +31,22 @@ func FuzzEpochValSet(f *testing.F) { require.Equal(t, sdk.ValAddress(consAddr), getValSet[i].GetValAddress()) } + // at epoch 1 right now + epoch := keeper.GetEpoch(ctx) + require.Equal(t, uint64(1), epoch.EpochNumber) + params := keeper.GetParams(ctx) + // generate a random number of new blocks - numIncBlocks := r.Uint64()%1000 + 1 - for i := uint64(0); i < numIncBlocks; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + for i := uint64(0); i < params.EpochInterval; i++ { + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } + epoch = keeper.GetEpoch(ctx) + require.Equal(t, uint64(2), epoch.EpochNumber) + // check whether the validator set remains the same or not - getValSet2 := keeper.GetValidatorSet(ctx, keeper.GetEpoch(ctx).EpochNumber) + getValSet2 := keeper.GetValidatorSet(ctx, epoch.EpochNumber) require.Equal(t, len(valSet), len(getValSet2)) for i := range getValSet2 { consAddr, err := valSet[i].GetConsAddr() diff --git a/x/epoching/keeper/epochs.go b/x/epoching/keeper/epochs.go index 5ebbf15b2..71719bbf3 100644 --- a/x/epoching/keeper/epochs.go +++ b/x/epoching/keeper/epochs.go @@ -6,10 +6,11 @@ import ( errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" - "github.com/babylonchain/babylon/x/epoching/types" "github.com/cometbft/cometbft/crypto/merkle" "github.com/cosmos/cosmos-sdk/runtime" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/babylonchain/babylon/x/epoching/types" ) func (k Keeper) setEpochInfo(ctx context.Context, epochNumber uint64, epoch *types.Epoch) { @@ -81,14 +82,13 @@ func (k Keeper) RecordLastHeaderAndAppHashRoot(ctx context.Context) error { return nil } -// RecordSealerHeaderForPrevEpoch records the sealer header for the previous epoch, -// where the sealer header of an epoch is the 2nd header of the next epoch -// This validator set of the epoch has generated a BLS multisig on `app_hash` of the sealer header -func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch { - // get the sealer header +// RecordSealerAppHashForPrevEpoch records the BlockHash referencing +// the last block of the previous epoch +func (k Keeper) RecordSealerAppHashForPrevEpoch(ctx context.Context) *types.Epoch { epoch := k.GetEpoch(ctx) - if !epoch.IsSecondBlock(ctx) { - panic(fmt.Errorf("RecordSealerHeaderForPrevEpoch can only be invoked at the second header of a non-zero epoch. current epoch: %v, current height: %d", epoch, sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)) + if !epoch.IsFirstBlock(ctx) { + panic(fmt.Errorf("RecordSealerAppHashForPrevEpoch can only be invoked at the first header of a non-zero epoch. "+ + "current epoch: %v, current height: %d", epoch, sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)) } header := sdk.UnwrapSDKContext(ctx).HeaderInfo() @@ -99,12 +99,30 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx context.Context) *types.Epoch } // record the sealer header for the sealed epoch - sealedEpoch.SealerHeaderHash = header.AppHash + sealedEpoch.SealerAppHash = header.AppHash k.setEpochInfo(ctx, sealedEpoch.EpochNumber, sealedEpoch) return sealedEpoch } +// RecordSealerBlockHashForEpoch records the block hash of +// the last block of the current epoch +func (k Keeper) RecordSealerBlockHashForEpoch(ctx context.Context) *types.Epoch { + // get the sealer header + epoch := k.GetEpoch(ctx) + if !epoch.IsLastBlock(ctx) { + panic(fmt.Errorf("RecordSealerBlockHashForEpoch can only be invoked at the last header of a non-zero epoch. "+ + "current epoch: %v, current height: %d", epoch, sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)) + } + header := sdk.UnwrapSDKContext(ctx).HeaderInfo() + + // record the sealer block hash for the sealing epoch + epoch.SealerBlockHash = header.Hash + k.setEpochInfo(ctx, epoch.EpochNumber, epoch) + + return epoch +} + // IncEpoch adds epoch number by 1 // CONTRACT: can only be invoked at the first block of an epoch func (k Keeper) IncEpoch(ctx context.Context) types.Epoch { diff --git a/x/epoching/keeper/epochs_test.go b/x/epoching/keeper/epochs_test.go index 7fece1ac0..a4a3dff99 100644 --- a/x/epoching/keeper/epochs_test.go +++ b/x/epoching/keeper/epochs_test.go @@ -4,9 +4,10 @@ import ( "math/rand" "testing" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" - "github.com/stretchr/testify/require" ) func FuzzEpochs(f *testing.F) { @@ -28,10 +29,10 @@ func FuzzEpochs(f *testing.F) { // increment a random number of new blocks numIncBlocks := r.Uint64()%1000 + 1 var err error - for i := uint64(0); i < numIncBlocks; i++ { - // TODO: Figure out why when ctx height is 1, GenAndApplyEmptyBlock + for i := uint64(0); i < numIncBlocks-1; i++ { + // TODO: Figure out why when ctx height is 1, ApplyEmptyBlockWithVoteExtension // will still give ctx height 1 once, then start to increment - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } diff --git a/x/epoching/keeper/grpc_query_test.go b/x/epoching/keeper/grpc_query_test.go index c6bda89b4..8e528da99 100644 --- a/x/epoching/keeper/grpc_query_test.go +++ b/x/epoching/keeper/grpc_query_test.go @@ -7,11 +7,12 @@ import ( "cosmossdk.io/core/header" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" "github.com/babylonchain/babylon/x/epoching/types" - "github.com/cosmos/cosmos-sdk/types/query" - "github.com/stretchr/testify/require" ) // FuzzParamsQuery fuzzes queryClient.Params @@ -111,7 +112,7 @@ func FuzzEpochsInfo(f *testing.F) { epochInterval := keeper.GetParams(ctx).EpochInterval for i := uint64(0); i < (numEpochs - 2); i++ { // exclude the existing epoch 0 and 1 for j := uint64(0); j < epochInterval; j++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } } @@ -193,7 +194,10 @@ func FuzzEpochValSetQuery(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - helper := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + helper := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ctx, queryClient := helper.Ctx, helper.QueryClient limit := uint64(r.Int() % 100) @@ -207,10 +211,11 @@ func FuzzEpochValSetQuery(f *testing.F) { resp, err := queryClient.EpochValSet(ctx, req) require.NoError(t, err) + params := helper.App.EpochingKeeper.GetParams(ctx) + // generate a random number of new blocks - numIncBlocks := r.Uint64()%1000 + 1 - for i := uint64(0); i < numIncBlocks; i++ { - ctx, err = helper.GenAndApplyEmptyBlock(r) + for i := uint64(0); i < params.EpochInterval; i++ { + ctx, err = helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) } diff --git a/x/epoching/keeper/msg_server_test.go b/x/epoching/keeper/msg_server_test.go index 05ce50669..cd1857f9f 100644 --- a/x/epoching/keeper/msg_server_test.go +++ b/x/epoching/keeper/msg_server_test.go @@ -5,10 +5,11 @@ import ( "testing" "time" - testhelper "github.com/babylonchain/babylon/testutil/helper" - "github.com/babylonchain/babylon/x/epoching/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" + + testhelper "github.com/babylonchain/babylon/testutil/helper" + "github.com/babylonchain/babylon/x/epoching/types" ) // TODO (fuzz tests): replace the following tests with fuzz ones @@ -17,7 +18,7 @@ func TestMsgWrappedDelegate(t *testing.T) { helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx, err := helper.GenAndApplyEmptyBlock(r) + ctx, err := helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) testCases := []struct { @@ -47,7 +48,7 @@ func TestMsgWrappedUndelegate(t *testing.T) { helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx, err := helper.GenAndApplyEmptyBlock(r) + ctx, err := helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) testCases := []struct { @@ -77,7 +78,7 @@ func TestMsgWrappedBeginRedelegate(t *testing.T) { helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx, err := helper.GenAndApplyEmptyBlock(r) + ctx, err := helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) testCases := []struct { @@ -108,7 +109,7 @@ func TestMsgWrappedCancelUnbondingDelegation(t *testing.T) { helper := testhelper.NewHelper(t) msgSrvr := helper.MsgSrvr // enter 1st epoch, in which BBN starts handling validator-related msgs - ctx, err := helper.GenAndApplyEmptyBlock(r) + ctx, err := helper.ApplyEmptyBlockWithVoteExtension(r) require.NoError(t, err) testCases := []struct { diff --git a/x/epoching/keeper/staking_functions.go b/x/epoching/keeper/staking_functions.go index 52e94be6a..577e7c88e 100644 --- a/x/epoching/keeper/staking_functions.go +++ b/x/epoching/keeper/staking_functions.go @@ -4,6 +4,7 @@ import ( "context" errorsmod "cosmossdk.io/errors" + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -110,3 +111,7 @@ func (k Keeper) CheckMsgCreateValidator(ctx context.Context, msg *stakingtypes.M return nil } + +func (k Keeper) GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) { + return k.stk.GetPubKeyByConsAddr(ctx, consAddr) +} diff --git a/x/epoching/types/epoching.go b/x/epoching/types/epoching.go index 2d2bfd6a9..295fe7671 100644 --- a/x/epoching/types/epoching.go +++ b/x/epoching/types/epoching.go @@ -35,7 +35,7 @@ func (e Epoch) GetLastBlockHeight() uint64 { } func (e Epoch) GetSealerBlockHeight() uint64 { - return e.GetLastBlockHeight() + 2 + return e.GetLastBlockHeight() + 1 } func (e Epoch) GetSecondBlockHeight() uint64 { @@ -49,6 +49,10 @@ func (e Epoch) IsLastBlock(ctx context.Context) bool { return e.GetLastBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } +func (e Epoch) IsLastBlockByHeight(height int64) bool { + return e.GetLastBlockHeight() == uint64(height) +} + func (e Epoch) IsFirstBlock(ctx context.Context) bool { return e.FirstBlockHeight == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } @@ -60,6 +64,13 @@ func (e Epoch) IsSecondBlock(ctx context.Context) bool { return e.GetSecondBlockHeight() == uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) } +func (e Epoch) IsVoteExtensionProposal(ctx context.Context) bool { + if e.EpochNumber == 0 { + return false + } + return e.IsFirstBlockOfNextEpoch(ctx) +} + // IsFirstBlockOfNextEpoch checks whether the current block is the first block of // the next epoch // CONTRACT: IsFirstBlockOfNextEpoch can only be called by the epoching module diff --git a/x/epoching/types/epoching.pb.go b/x/epoching/types/epoching.pb.go index a0e7dddb0..40aae32d5 100644 --- a/x/epoching/types/epoching.pb.go +++ b/x/epoching/types/epoching.pb.go @@ -83,11 +83,14 @@ type Epoch struct { // app_hash_root is the Merkle root of all AppHashs in this epoch // It will be used for proving a block is in an epoch AppHashRoot []byte `protobuf:"bytes,5,opt,name=app_hash_root,json=appHashRoot,proto3" json:"app_hash_root,omitempty"` - // sealer_header_hash is the app_hash of the sealer header, i.e., 2nd header + // sealer is the last block of the sealed epoch + // sealer_app_hash points to the sealer but stored in the 1st header // of the next epoch - // This validator set has generated a BLS multisig on `app_hash` of - // the sealer header - SealerHeaderHash []byte `protobuf:"bytes,6,opt,name=sealer_header_hash,json=sealerHeaderHash,proto3" json:"sealer_header_hash,omitempty"` + SealerAppHash []byte `protobuf:"bytes,6,opt,name=sealer_app_hash,json=sealerAppHash,proto3" json:"sealer_app_hash,omitempty"` + // sealer_block_hash is the hash of the sealer + // the validator set has generated a BLS multisig on the hash, + // i.e., hash of the last block in the epoch + SealerBlockHash []byte `protobuf:"bytes,7,opt,name=sealer_block_hash,json=sealerBlockHash,proto3" json:"sealer_block_hash,omitempty"` } func (m *Epoch) Reset() { *m = Epoch{} } @@ -158,9 +161,16 @@ func (m *Epoch) GetAppHashRoot() []byte { return nil } -func (m *Epoch) GetSealerHeaderHash() []byte { +func (m *Epoch) GetSealerAppHash() []byte { if m != nil { - return m.SealerHeaderHash + return m.SealerAppHash + } + return nil +} + +func (m *Epoch) GetSealerBlockHash() []byte { + if m != nil { + return m.SealerBlockHash } return nil } @@ -703,65 +713,65 @@ func init() { } var fileDescriptor_2f2f209d5311f84c = []byte{ - // 914 bytes of a gzipped FileDescriptorProto + // 927 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x96, 0xdf, 0x6e, 0xdb, 0x36, - 0x14, 0xc6, 0x2d, 0xff, 0x4b, 0x4c, 0xdb, 0x99, 0xc7, 0xa4, 0x83, 0x1b, 0x0c, 0x4e, 0xa6, 0x62, - 0x40, 0x10, 0x0c, 0xd2, 0xe2, 0x65, 0xbb, 0xdc, 0x50, 0xc7, 0xc6, 0x94, 0xa1, 0x71, 0x31, 0xae, - 0xc9, 0xc5, 0x2e, 0x26, 0x50, 0x12, 0x23, 0x09, 0x95, 0x48, 0x41, 0xa4, 0xdc, 0xe4, 0x62, 0xef, - 0xd0, 0xe7, 0xe8, 0x93, 0xec, 0xb2, 0x97, 0xbb, 0xdb, 0x90, 0x3c, 0x48, 0x07, 0x52, 0xb2, 0x6c, - 0x2f, 0x46, 0xb2, 0xac, 0x57, 0x26, 0xcf, 0x39, 0xdf, 0x47, 0x9e, 0x9f, 0x8f, 0x09, 0x03, 0xdd, - 0xc1, 0xce, 0x75, 0xc4, 0xa8, 0x49, 0x12, 0xe6, 0x06, 0x21, 0xf5, 0xcd, 0xd9, 0x51, 0xb9, 0x36, - 0x92, 0x94, 0x09, 0x06, 0xb7, 0x8b, 0x1a, 0xa3, 0x8c, 0xcf, 0x8e, 0x76, 0xf7, 0x7c, 0xc6, 0xfc, - 0x88, 0x98, 0xaa, 0xc4, 0xc9, 0x2e, 0x4d, 0x11, 0xc6, 0x84, 0x0b, 0x1c, 0x27, 0xb9, 0x6a, 0x77, - 0xc7, 0x67, 0x3e, 0x53, 0x4b, 0x53, 0xae, 0x8a, 0xe8, 0x9e, 0xcb, 0x78, 0xcc, 0xb8, 0xc9, 0x05, - 0x7e, 0x9d, 0x9f, 0xe6, 0x10, 0x81, 0x8f, 0x4c, 0x71, 0x55, 0x14, 0x0c, 0x8a, 0x02, 0x07, 0x73, - 0x52, 0x66, 0x5d, 0x16, 0xd2, 0x3c, 0xaf, 0xbf, 0xab, 0x82, 0xc6, 0x44, 0xde, 0x03, 0x7e, 0x01, - 0x3a, 0xea, 0x42, 0x36, 0xcd, 0x62, 0x87, 0xa4, 0x7d, 0x6d, 0x5f, 0x3b, 0xa8, 0xa3, 0xb6, 0x8a, - 0x4d, 0x55, 0x08, 0x1e, 0x83, 0xcf, 0xdc, 0x2c, 0x4d, 0x09, 0x15, 0x76, 0x5e, 0x1a, 0x52, 0x41, - 0xd2, 0x19, 0x8e, 0xfa, 0x55, 0x55, 0xbc, 0x53, 0x64, 0x95, 0xe1, 0x69, 0x91, 0x83, 0x5f, 0x01, - 0x78, 0x19, 0xa6, 0x5c, 0xd8, 0x4e, 0xc4, 0xdc, 0xd7, 0x76, 0x40, 0x42, 0x3f, 0x10, 0xfd, 0x9a, - 0x52, 0xf4, 0x54, 0x66, 0x24, 0x13, 0x96, 0x8a, 0x43, 0x0b, 0x7c, 0x12, 0xe1, 0xb2, 0x58, 0x52, - 0xe8, 0xd7, 0xf7, 0xb5, 0x83, 0xf6, 0x70, 0xd7, 0xc8, 0x11, 0x19, 0x73, 0x44, 0xc6, 0xab, 0x39, - 0xa2, 0x51, 0xfd, 0xed, 0x5f, 0x7b, 0x1a, 0xea, 0x4a, 0xa1, 0xf2, 0x92, 0x19, 0xa8, 0x83, 0x2e, - 0x4e, 0x12, 0x3b, 0xc0, 0x3c, 0xb0, 0x53, 0xc6, 0x44, 0xbf, 0xb1, 0xaf, 0x1d, 0x74, 0x50, 0x1b, - 0x27, 0x89, 0x85, 0x79, 0x80, 0x18, 0x13, 0xf2, 0x6e, 0x9c, 0xe0, 0x88, 0xa4, 0x76, 0x40, 0xb0, - 0x27, 0x3f, 0x30, 0x0f, 0xfa, 0x4d, 0x55, 0xd8, 0xcb, 0x33, 0x96, 0x4a, 0x48, 0x85, 0xfe, 0xa1, - 0x0e, 0xba, 0x3f, 0x67, 0x24, 0x23, 0xde, 0x19, 0xe1, 0x1c, 0xfb, 0x04, 0x6e, 0x83, 0x86, 0xb8, - 0xb2, 0x43, 0x4f, 0xd1, 0xea, 0xa0, 0xba, 0xb8, 0x3a, 0xf5, 0xe0, 0x13, 0xd0, 0x8c, 0xb9, 0x2f, - 0xa3, 0x55, 0x15, 0x6d, 0xc4, 0xdc, 0x3f, 0xf5, 0x24, 0xe0, 0x35, 0x04, 0xda, 0xce, 0x52, 0xf3, - 0x3f, 0x00, 0xf0, 0x3f, 0xfa, 0x6e, 0x39, 0x65, 0xcf, 0xbf, 0x81, 0x1d, 0x79, 0xb4, 0x9b, 0x12, - 0x2c, 0x88, 0x3d, 0xc3, 0x51, 0xe8, 0x61, 0xc1, 0x52, 0xd5, 0x7a, 0x7b, 0x78, 0x68, 0xe4, 0xd3, - 0x60, 0x14, 0xe3, 0x62, 0x14, 0x03, 0x61, 0x9c, 0x71, 0xff, 0x44, 0x49, 0x2e, 0xe6, 0x0a, 0xab, - 0x82, 0x60, 0x7c, 0x27, 0x0a, 0x2d, 0xd0, 0x91, 0xfe, 0x1e, 0x89, 0x88, 0x8f, 0x05, 0x51, 0xa4, - 0xda, 0xc3, 0x67, 0xf7, 0xf8, 0x8e, 0x8b, 0x52, 0xab, 0x82, 0xda, 0xf1, 0x62, 0x0b, 0xa7, 0x60, - 0x4b, 0x3a, 0x65, 0xb4, 0xf4, 0xda, 0x50, 0x5e, 0x5f, 0xde, 0xe3, 0x75, 0x5e, 0x16, 0x5b, 0x15, - 0xd4, 0x8d, 0x97, 0x03, 0xf3, 0xce, 0x1d, 0xe2, 0x87, 0xd4, 0x4e, 0x49, 0xe9, 0xba, 0xf9, 0x60, - 0xe7, 0x23, 0x29, 0x41, 0x64, 0xc9, 0x5a, 0x76, 0xfe, 0xaf, 0x28, 0xfc, 0x1d, 0xec, 0x29, 0xb2, - 0x98, 0xba, 0x24, 0xb2, 0x33, 0xea, 0x30, 0xea, 0x85, 0xb4, 0x44, 0x11, 0x32, 0xda, 0x6f, 0xa9, - 0xa3, 0x8e, 0xef, 0x83, 0xac, 0xd4, 0xe7, 0x73, 0xf1, 0xb8, 0xd4, 0x5a, 0x15, 0xf4, 0x79, 0x7c, - 0x4f, 0x7e, 0xd4, 0x00, 0xb5, 0x98, 0xfb, 0x3a, 0x05, 0x9f, 0xae, 0x0c, 0xe0, 0x8b, 0x90, 0x8b, - 0xff, 0xf2, 0xcb, 0xfd, 0x0e, 0xd4, 0x63, 0xee, 0xf3, 0x7e, 0x75, 0xbf, 0x76, 0xd0, 0x1e, 0xea, - 0xc6, 0x9a, 0x27, 0xc8, 0x58, 0x31, 0x46, 0xaa, 0x5e, 0x7f, 0xa7, 0x81, 0xad, 0x0b, 0x1c, 0xfd, - 0x22, 0xb0, 0x20, 0xe7, 0x89, 0x27, 0x41, 0x1c, 0x83, 0x06, 0x97, 0x5b, 0x75, 0xcc, 0xd6, 0x70, - 0xb0, 0xd6, 0x6b, 0xc4, 0xa8, 0xa7, 0x44, 0x28, 0x2f, 0xbe, 0x33, 0xfc, 0xd5, 0x87, 0x86, 0xbf, - 0xf6, 0xe8, 0xe1, 0xd7, 0x19, 0x80, 0xe5, 0xa4, 0xbe, 0x08, 0x2f, 0x89, 0x7b, 0xed, 0x46, 0x04, - 0x3e, 0x05, 0x9b, 0x33, 0x1c, 0xd9, 0xd8, 0xf3, 0x72, 0x32, 0x2d, 0xb4, 0x31, 0xc3, 0xd1, 0x73, - 0xcf, 0x4b, 0xe1, 0xf7, 0x79, 0x2a, 0x0a, 0x2f, 0x49, 0x41, 0xe6, 0xd9, 0xda, 0x6e, 0x56, 0x09, - 0x28, 0xbd, 0xf4, 0xd7, 0x3f, 0x68, 0xe0, 0xc9, 0xe2, 0x3b, 0xfa, 0x78, 0x48, 0xcb, 0x57, 0xad, - 0xae, 0x5e, 0xf5, 0x08, 0x34, 0x71, 0xcc, 0x32, 0x2a, 0x0a, 0x30, 0x4f, 0xe7, 0x53, 0x26, 0x1f, - 0xf6, 0x72, 0xc4, 0x4e, 0x58, 0x48, 0x51, 0x51, 0x78, 0x07, 0x79, 0xfd, 0x21, 0xe4, 0x8d, 0xc7, - 0x23, 0x7f, 0x03, 0xb6, 0x17, 0x00, 0x56, 0x98, 0x7b, 0x64, 0x95, 0xb9, 0x47, 0xf2, 0x46, 0x26, - 0x79, 0x6a, 0x89, 0xf9, 0xe1, 0x5a, 0x38, 0x6b, 0xb9, 0x2a, 0x1b, 0x85, 0xfe, 0x5b, 0xd0, 0x5a, - 0xbc, 0x4a, 0x10, 0xd4, 0xcb, 0xa3, 0x3a, 0x48, 0xad, 0xe1, 0x0e, 0x68, 0x24, 0xec, 0x0d, 0xc9, - 0x41, 0xd6, 0x50, 0xbe, 0x39, 0x9c, 0x82, 0x56, 0x49, 0x1d, 0xb6, 0xc1, 0xc6, 0x09, 0x9a, 0x3c, - 0x7f, 0x35, 0x19, 0xf7, 0x2a, 0x10, 0x80, 0xe6, 0xe8, 0xe5, 0x74, 0x3c, 0x19, 0xf7, 0x34, 0xd8, - 0x05, 0xad, 0xf3, 0xa9, 0xdc, 0x9d, 0x4e, 0x7f, 0xec, 0x55, 0x61, 0x07, 0x6c, 0xe6, 0xdb, 0xc9, - 0xb8, 0x57, 0x93, 0x2a, 0x34, 0x39, 0x7b, 0x79, 0x31, 0x19, 0xf7, 0xea, 0xa3, 0x9f, 0xfe, 0xb8, - 0x19, 0x68, 0xef, 0x6f, 0x06, 0xda, 0xdf, 0x37, 0x03, 0xed, 0xed, 0xed, 0xa0, 0xf2, 0xfe, 0x76, - 0x50, 0xf9, 0xf3, 0x76, 0x50, 0xf9, 0xf5, 0x6b, 0x3f, 0x14, 0x41, 0xe6, 0x18, 0x2e, 0x8b, 0xcd, - 0xa2, 0x3f, 0x37, 0xc0, 0x21, 0x9d, 0x6f, 0xcc, 0xab, 0xc5, 0x7f, 0x04, 0x71, 0x9d, 0x10, 0xee, - 0x34, 0x15, 0xf0, 0x6f, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x32, 0x7b, 0xad, 0x44, 0x08, - 0x00, 0x00, + 0x14, 0xc6, 0x2d, 0xff, 0x49, 0xe2, 0x63, 0x3b, 0x75, 0x99, 0x74, 0x70, 0x83, 0xc1, 0xc9, 0x5c, + 0x6c, 0x08, 0x82, 0x41, 0x5e, 0xbc, 0x6c, 0x97, 0x1b, 0xe2, 0xd8, 0x98, 0x33, 0x34, 0x2e, 0xc6, + 0x35, 0xb9, 0xd8, 0xc5, 0x04, 0x4a, 0x62, 0x64, 0xa1, 0x12, 0x29, 0x88, 0x94, 0x9b, 0x5c, 0xec, + 0x1d, 0xfa, 0x1c, 0x7b, 0x92, 0x5d, 0xe6, 0x72, 0x77, 0x1b, 0x92, 0x07, 0xe9, 0x40, 0x4a, 0x96, + 0xed, 0xc5, 0x48, 0xd6, 0xf5, 0x8e, 0x3c, 0xe7, 0x3b, 0x1f, 0x79, 0x7e, 0x3a, 0x26, 0x0c, 0x1d, + 0x9b, 0xd8, 0xd7, 0x01, 0x67, 0x5d, 0x1a, 0x71, 0x67, 0xe2, 0x33, 0xaf, 0x3b, 0x3d, 0xcc, 0xd7, + 0x66, 0x14, 0x73, 0xc9, 0xd1, 0x56, 0xa6, 0x31, 0xf3, 0xf8, 0xf4, 0x70, 0x67, 0xd7, 0xe3, 0xdc, + 0x0b, 0x68, 0x57, 0x4b, 0xec, 0xe4, 0xb2, 0x2b, 0xfd, 0x90, 0x0a, 0x49, 0xc2, 0x28, 0xad, 0xda, + 0xd9, 0xf6, 0xb8, 0xc7, 0xf5, 0xb2, 0xab, 0x56, 0x59, 0x74, 0xd7, 0xe1, 0x22, 0xe4, 0xa2, 0x2b, + 0x24, 0x79, 0x93, 0x9e, 0x66, 0x53, 0x49, 0x0e, 0xbb, 0xf2, 0x2a, 0x13, 0xb4, 0x33, 0x81, 0x4d, + 0x04, 0xcd, 0xb3, 0x0e, 0xf7, 0x59, 0x9a, 0xef, 0xdc, 0x14, 0xa1, 0x32, 0x54, 0xf7, 0x40, 0x9f, + 0x41, 0x5d, 0x5f, 0xc8, 0x62, 0x49, 0x68, 0xd3, 0xb8, 0x65, 0xec, 0x19, 0xfb, 0x65, 0x5c, 0xd3, + 0xb1, 0xb1, 0x0e, 0xa1, 0x23, 0xf8, 0xc4, 0x49, 0xe2, 0x98, 0x32, 0x69, 0xa5, 0x52, 0x9f, 0x49, + 0x1a, 0x4f, 0x49, 0xd0, 0x2a, 0x6a, 0xf1, 0x76, 0x96, 0xd5, 0x86, 0xa7, 0x59, 0x0e, 0x7d, 0x09, + 0xe8, 0xd2, 0x8f, 0x85, 0xb4, 0xec, 0x80, 0x3b, 0x6f, 0xac, 0x09, 0xf5, 0xbd, 0x89, 0x6c, 0x95, + 0x74, 0x45, 0x53, 0x67, 0xfa, 0x2a, 0x31, 0xd2, 0x71, 0x34, 0x82, 0x27, 0x01, 0xc9, 0xc5, 0x8a, + 0x42, 0xab, 0xbc, 0x67, 0xec, 0xd7, 0x7a, 0x3b, 0x66, 0x8a, 0xc8, 0x9c, 0x21, 0x32, 0x5f, 0xcf, + 0x10, 0xf5, 0xcb, 0xef, 0xfe, 0xda, 0x35, 0x70, 0x43, 0x15, 0x6a, 0x2f, 0x95, 0x41, 0x1d, 0x68, + 0x90, 0x28, 0xb2, 0x26, 0x44, 0x4c, 0xac, 0x98, 0x73, 0xd9, 0xaa, 0xec, 0x19, 0xfb, 0x75, 0x5c, + 0x23, 0x51, 0x34, 0x22, 0x62, 0x82, 0x39, 0x97, 0xe8, 0x0b, 0x78, 0x22, 0x28, 0x09, 0x68, 0x6c, + 0xcd, 0xa4, 0xad, 0x35, 0xad, 0x6a, 0xa4, 0xe1, 0xe3, 0x54, 0x8b, 0x0e, 0xe0, 0x69, 0xa6, 0xcb, + 0x9a, 0x50, 0xca, 0x75, 0xad, 0xcc, 0x0c, 0xd2, 0x1e, 0x88, 0x98, 0x74, 0xde, 0x97, 0xa1, 0xf1, + 0x53, 0x42, 0x13, 0xea, 0x9e, 0x51, 0x21, 0x88, 0x47, 0xd1, 0x16, 0x54, 0xe4, 0x95, 0xe5, 0xbb, + 0x9a, 0x69, 0x1d, 0x97, 0xe5, 0xd5, 0xa9, 0x8b, 0x9e, 0xc1, 0x5a, 0x28, 0x3c, 0x15, 0x2d, 0xea, + 0x68, 0x25, 0x14, 0xde, 0xa9, 0xab, 0x3e, 0xc3, 0x0a, 0x4e, 0x35, 0x7b, 0x01, 0xd1, 0xf7, 0x00, + 0xff, 0x83, 0x4e, 0xd5, 0xce, 0xc9, 0xfc, 0x0a, 0xdb, 0xea, 0x68, 0x27, 0xa6, 0x44, 0x52, 0x6b, + 0x4a, 0x02, 0xdf, 0x25, 0x92, 0xc7, 0x1a, 0x50, 0xad, 0x77, 0x60, 0xa6, 0x33, 0x63, 0x66, 0x43, + 0x65, 0x66, 0x63, 0x63, 0x9e, 0x09, 0xef, 0x44, 0x97, 0x5c, 0xcc, 0x2a, 0x46, 0x05, 0x8c, 0xc2, + 0x7b, 0x51, 0x34, 0x82, 0xba, 0xf2, 0x77, 0x69, 0x40, 0x3d, 0x22, 0xa9, 0x46, 0x5a, 0xeb, 0xbd, + 0x78, 0xc0, 0x77, 0x90, 0x49, 0x47, 0x05, 0x5c, 0x0b, 0xe7, 0x5b, 0x34, 0x86, 0x4d, 0xe5, 0x94, + 0xb0, 0xdc, 0x6b, 0x5d, 0x7b, 0x7d, 0xfe, 0x80, 0xd7, 0x79, 0x2e, 0x1e, 0x15, 0x70, 0x23, 0x5c, + 0x0c, 0xcc, 0x3a, 0xb7, 0xa9, 0xe7, 0x33, 0x2b, 0xa6, 0xb9, 0xeb, 0xc6, 0xa3, 0x9d, 0xf7, 0x55, + 0x09, 0xa6, 0x0b, 0xd6, 0xaa, 0xf3, 0x7f, 0x45, 0xd1, 0x6f, 0xb0, 0xab, 0xc9, 0x12, 0xe6, 0xd0, + 0xc0, 0x4a, 0x98, 0xcd, 0x99, 0xeb, 0xb3, 0x1c, 0x85, 0xcf, 0x59, 0xab, 0xaa, 0x8f, 0x3a, 0x7a, + 0x08, 0xb2, 0xae, 0x3e, 0x9f, 0x15, 0x0f, 0xf2, 0xda, 0x51, 0x01, 0x7f, 0x1a, 0x3e, 0x90, 0xef, + 0x57, 0xa0, 0x14, 0x0a, 0xaf, 0xc3, 0xe0, 0xe9, 0xd2, 0x00, 0xbe, 0xf4, 0x85, 0xfc, 0x2f, 0xbf, + 0xef, 0x6f, 0xa1, 0x1c, 0x0a, 0x4f, 0xb4, 0x8a, 0x7b, 0xa5, 0xfd, 0x5a, 0xaf, 0x63, 0xae, 0x78, + 0xa8, 0xcc, 0x25, 0x63, 0xac, 0xf5, 0x9d, 0xdf, 0x0d, 0xd8, 0xbc, 0x20, 0xc1, 0xcf, 0x92, 0x48, + 0x7a, 0x1e, 0xb9, 0x0a, 0xc4, 0x11, 0x54, 0x84, 0xda, 0xea, 0x63, 0x36, 0x7b, 0xed, 0x95, 0x5e, + 0x7d, 0xce, 0x5c, 0x5d, 0x84, 0x53, 0xf1, 0xbd, 0xe1, 0x2f, 0x3e, 0x36, 0xfc, 0xa5, 0x0f, 0x1e, + 0xfe, 0x0e, 0x07, 0x94, 0x4f, 0xea, 0x4b, 0xff, 0x92, 0x3a, 0xd7, 0x4e, 0x40, 0xd1, 0x73, 0xd8, + 0x98, 0x92, 0xc0, 0x22, 0xae, 0x9b, 0x92, 0xa9, 0xe2, 0xf5, 0x29, 0x09, 0x8e, 0x5d, 0x37, 0x46, + 0xdf, 0xa5, 0xa9, 0xc0, 0xbf, 0xa4, 0x19, 0x99, 0x17, 0x2b, 0xbb, 0x59, 0x26, 0xa0, 0xeb, 0x95, + 0x7f, 0xe7, 0xbd, 0x01, 0xcf, 0xe6, 0xdf, 0xe8, 0xe3, 0x21, 0x2d, 0x5e, 0xb5, 0xb8, 0x7c, 0xd5, + 0x43, 0x58, 0x23, 0x21, 0x4f, 0x98, 0xcc, 0xc0, 0x3c, 0x9f, 0x4d, 0x99, 0x7a, 0xfe, 0xf3, 0x11, + 0x3b, 0xe1, 0x3e, 0xc3, 0x99, 0xf0, 0x1e, 0xf2, 0xf2, 0x63, 0xc8, 0x2b, 0x1f, 0x8e, 0xfc, 0x2d, + 0x6c, 0xcd, 0x01, 0x2c, 0x31, 0x77, 0xe9, 0x32, 0x73, 0x97, 0xa6, 0x8d, 0x0c, 0xd3, 0xd4, 0x02, + 0xf3, 0x83, 0x95, 0x70, 0x56, 0x72, 0xd5, 0x36, 0x1a, 0xfd, 0x37, 0x50, 0x9d, 0xbf, 0x4a, 0x08, + 0xca, 0xf9, 0x51, 0x75, 0xac, 0xd7, 0x68, 0x1b, 0x2a, 0x11, 0x7f, 0x4b, 0x53, 0x90, 0x25, 0x9c, + 0x6e, 0x0e, 0xc6, 0x50, 0xcd, 0xa9, 0xa3, 0x1a, 0xac, 0x9f, 0xe0, 0xe1, 0xf1, 0xeb, 0xe1, 0xa0, + 0x59, 0x40, 0x00, 0x6b, 0xfd, 0x57, 0xe3, 0xc1, 0x70, 0xd0, 0x34, 0x50, 0x03, 0xaa, 0xe7, 0x63, + 0xb5, 0x3b, 0x1d, 0xff, 0xd0, 0x2c, 0xa2, 0x3a, 0x6c, 0xa4, 0xdb, 0xe1, 0xa0, 0x59, 0x52, 0x55, + 0x78, 0x78, 0xf6, 0xea, 0x62, 0x38, 0x68, 0x96, 0xfb, 0x3f, 0xfe, 0x71, 0xdb, 0x36, 0x6e, 0x6e, + 0xdb, 0xc6, 0xdf, 0xb7, 0x6d, 0xe3, 0xdd, 0x5d, 0xbb, 0x70, 0x73, 0xd7, 0x2e, 0xfc, 0x79, 0xd7, + 0x2e, 0xfc, 0xf2, 0x95, 0xe7, 0xcb, 0x49, 0x62, 0x9b, 0x0e, 0x0f, 0xbb, 0x59, 0x7f, 0xce, 0x84, + 0xf8, 0x6c, 0xb6, 0xe9, 0x5e, 0xcd, 0xff, 0x49, 0xc8, 0xeb, 0x88, 0x0a, 0x7b, 0x4d, 0x03, 0xff, + 0xfa, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2e, 0x41, 0x06, 0xf6, 0x6a, 0x08, 0x00, 0x00, } func (m *Epoch) Marshal() (dAtA []byte, err error) { @@ -784,10 +794,17 @@ func (m *Epoch) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.SealerHeaderHash) > 0 { - i -= len(m.SealerHeaderHash) - copy(dAtA[i:], m.SealerHeaderHash) - i = encodeVarintEpoching(dAtA, i, uint64(len(m.SealerHeaderHash))) + if len(m.SealerBlockHash) > 0 { + i -= len(m.SealerBlockHash) + copy(dAtA[i:], m.SealerBlockHash) + i = encodeVarintEpoching(dAtA, i, uint64(len(m.SealerBlockHash))) + i-- + dAtA[i] = 0x3a + } + if len(m.SealerAppHash) > 0 { + i -= len(m.SealerAppHash) + copy(dAtA[i:], m.SealerAppHash) + i = encodeVarintEpoching(dAtA, i, uint64(len(m.SealerAppHash))) i-- dAtA[i] = 0x32 } @@ -1296,7 +1313,11 @@ func (m *Epoch) Size() (n int) { if l > 0 { n += 1 + l + sovEpoching(uint64(l)) } - l = len(m.SealerHeaderHash) + l = len(m.SealerAppHash) + if l > 0 { + n += 1 + l + sovEpoching(uint64(l)) + } + l = len(m.SealerBlockHash) if l > 0 { n += 1 + l + sovEpoching(uint64(l)) } @@ -1672,7 +1693,41 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SealerHeaderHash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SealerAppHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEpoching + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthEpoching + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthEpoching + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SealerAppHash = append(m.SealerAppHash[:0], dAtA[iNdEx:postIndex]...) + if m.SealerAppHash == nil { + m.SealerAppHash = []byte{} + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SealerBlockHash", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1699,9 +1754,9 @@ func (m *Epoch) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SealerHeaderHash = append(m.SealerHeaderHash[:0], dAtA[iNdEx:postIndex]...) - if m.SealerHeaderHash == nil { - m.SealerHeaderHash = []byte{} + m.SealerBlockHash = append(m.SealerBlockHash[:0], dAtA[iNdEx:postIndex]...) + if m.SealerBlockHash == nil { + m.SealerBlockHash = []byte{} } iNdEx = postIndex default: diff --git a/x/epoching/types/expected_keepers.go b/x/epoching/types/expected_keepers.go index 367aa21b4..3cbc29698 100644 --- a/x/epoching/types/expected_keepers.go +++ b/x/epoching/types/expected_keepers.go @@ -2,9 +2,11 @@ package types import ( "context" - storetypes "cosmossdk.io/store/types" "time" + storetypes "cosmossdk.io/store/types" + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -44,6 +46,7 @@ type StakingKeeper interface { ValidatorQueueIterator(ctx context.Context, endTime time.Time, endHeight int64) (storetypes.Iterator, error) UnbondAllMatureValidators(ctx context.Context) error GetValidatorByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (stakingtypes.Validator, error) + GetPubKeyByConsAddr(ctx context.Context, consAddr sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) } // Event Hooks diff --git a/x/monitor/keeper/grpc_query_test.go b/x/monitor/keeper/grpc_query_test.go index 1c15f7897..6f33b8fe7 100644 --- a/x/monitor/keeper/grpc_query_test.go +++ b/x/monitor/keeper/grpc_query_test.go @@ -4,6 +4,10 @@ import ( "math/rand" "testing" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/babylonchain/babylon/app" "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/testutil/datagen" @@ -11,9 +15,6 @@ import ( ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types" types2 "github.com/babylonchain/babylon/x/epoching/types" "github.com/babylonchain/babylon/x/monitor/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func FuzzQueryEndedEpochBtcHeight(f *testing.F) { @@ -124,7 +125,7 @@ func FuzzQueryReportedCheckpointBtcHeight(f *testing.F) { // Verify checkpoint btcCkpt := btctxformatter.RawBtcCheckpoint{ Epoch: mockCkptWithMeta.Ckpt.EpochNum, - AppHash: *mockCkptWithMeta.Ckpt.AppHash, + BlockHash: *mockCkptWithMeta.Ckpt.BlockHash, BitMap: mockCkptWithMeta.Ckpt.Bitmap, SubmitterAddress: datagen.GenRandomByteArray(r, btctxformatter.AddressLength), BlsSig: *mockCkptWithMeta.Ckpt.BlsMultiSig, diff --git a/x/zoneconcierge/keeper/proof_btc_timestamp_test.go b/x/zoneconcierge/keeper/proof_btc_timestamp_test.go index bd200dc91..d6297aa19 100644 --- a/x/zoneconcierge/keeper/proof_btc_timestamp_test.go +++ b/x/zoneconcierge/keeper/proof_btc_timestamp_test.go @@ -5,6 +5,13 @@ import ( "math/rand" "testing" + "github.com/boljen/go-bitmap" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/testutil/datagen" testhelper "github.com/babylonchain/babylon/testutil/helper" @@ -12,11 +19,6 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" zctypes "github.com/babylonchain/babylon/x/zoneconcierge/types" - "github.com/boljen/go-bitmap" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" ) func FuzzProofCZHeaderInEpoch(f *testing.F) { @@ -33,8 +35,8 @@ func FuzzProofCZHeaderInEpoch(f *testing.F) { // enter the 1st block of epoch 2 epochInterval := ek.GetParams(h.Ctx).EpochInterval - for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + for j := 0; j < int(epochInterval)-1; j++ { + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } @@ -51,12 +53,9 @@ func FuzzProofCZHeaderInEpoch(f *testing.F) { // enter the 1st block of the next epoch for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } - // seal last epoch - h.Ctx, err = h.GenAndApplyEmptyBlock(r) - h.NoError(err) epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) h.NoError(err) @@ -116,10 +115,10 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { // construct the rawCkpt // Note that the BlsMultiSig will be generated and assigned later - appHash := checkpointingtypes.AppHash(epoch.SealerHeaderHash) + blockHash := checkpointingtypes.BlockHash(epoch.SealerBlockHash) rawCkpt := &checkpointingtypes.RawCheckpoint{ EpochNum: epoch.EpochNumber, - AppHash: &appHash, + BlockHash: &blockHash, Bitmap: bm, BlsMultiSig: nil, } @@ -146,12 +145,12 @@ func FuzzProofEpochSealed_BLSSig(f *testing.F) { // verify err = zctypes.VerifyEpochSealed(epoch, rawCkpt, proof) - if subsetPower <= valSet.GetTotalPower()*1/3 { // BLS sig does not reach a quorum - require.LessOrEqual(t, numSubSet, numVals*1/3) + if subsetPower*3 <= valSet.GetTotalPower()*2 { // BLS sig does not reach a quorum + require.LessOrEqual(t, numSubSet, numVals*2/3) require.Error(t, err) require.NotErrorIs(t, err, zctypes.ErrInvalidMerkleProof) } else { // BLS sig has a valid quorum - require.Greater(t, numSubSet, numVals*1/3) + require.Greater(t, numSubSet, numVals*2/3) require.Error(t, err) require.ErrorIs(t, err, zctypes.ErrInvalidMerkleProof) } @@ -175,15 +174,11 @@ func FuzzProofEpochSealed_Epoch(f *testing.F) { newEpochs := datagen.RandomInt(r, 10) + 2 for i := 0; i < int(newEpochs); i++ { for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } } - // seal the last epoch at the 2nd block of the current epoch - h.Ctx, err = h.GenAndApplyEmptyBlock(r) - h.NoError(err) - // prove the inclusion of last epoch lastEpochNumber := ek.GetEpoch(h.Ctx).EpochNumber - 1 h.NoError(err) @@ -203,26 +198,27 @@ func FuzzProofEpochSealed_ValSet(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - h := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + h := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ek := h.App.EpochingKeeper ck := h.App.CheckpointingKeeper zck := h.App.ZoneConciergeKeeper - var err error // chain is at height 1 - // enter the 1st block of a random epoch epochInterval := ek.GetParams(h.Ctx).EpochInterval newEpochs := datagen.RandomInt(r, 10) + 2 for i := 0; i < int(newEpochs); i++ { for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + _, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } } // seal the last epoch at the 2nd block of the current epoch - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) // prove the inclusion of last epoch diff --git a/x/zoneconcierge/types/btc_timestamp.go b/x/zoneconcierge/types/btc_timestamp.go index 8cfe3a14b..be72d1f50 100644 --- a/x/zoneconcierge/types/btc_timestamp.go +++ b/x/zoneconcierge/types/btc_timestamp.go @@ -6,6 +6,10 @@ import ( "math/big" errorsmod "cosmossdk.io/errors" + "github.com/btcsuite/btcd/wire" + tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + txformat "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/crypto/bls12381" bbn "github.com/babylonchain/babylon/types" @@ -13,9 +17,6 @@ import ( btclckeeper "github.com/babylonchain/babylon/x/btclightclient/keeper" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" epochingtypes "github.com/babylonchain/babylon/x/epoching/types" - "github.com/btcsuite/btcd/wire" - tmcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" ) func GetCZHeaderKey(chainID string, height uint64) []byte { @@ -38,19 +39,19 @@ func GetValSetKey(epochNumber uint64) []byte { } func VerifyEpochInfo(epoch *epochingtypes.Epoch, proof *tmcrypto.ProofOps) error { - // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeaderHash + // get the Merkle root, i.e., the BlockHash of the sealer header + root := epoch.SealerAppHash // Ensure The epoch medatata is committed to the app_hash of the sealer header // NOTE: the proof is generated when sealer header is generated. At that time // sealer header hash is not given to epoch metadata. Thus we need to clear the // sealer header hash when verifying the proof. - epoch.SealerHeaderHash = []byte{} + epoch.SealerAppHash = []byte{} epochBytes, err := epoch.Marshal() if err != nil { return err } - epoch.SealerHeaderHash = root + epoch.SealerAppHash = root if err := VerifyStore(root, epochingtypes.StoreKey, GetEpochInfoKey(epoch.EpochNumber), epochBytes, proof); err != nil { return errorsmod.Wrapf(ErrInvalidMerkleProof, "invalid inclusion proof for epoch metadata: %v", err) } @@ -63,7 +64,7 @@ func VerifyValSet(epoch *epochingtypes.Epoch, valSet *checkpointingtypes.Validat if err != nil { return err } - if err := VerifyStore(epoch.SealerHeaderHash, checkpointingtypes.StoreKey, GetValSetKey(epoch.EpochNumber), valSetBytes, proof); err != nil { + if err := VerifyStore(epoch.SealerAppHash, checkpointingtypes.StoreKey, GetValSetKey(epoch.EpochNumber), valSetBytes, proof); err != nil { return errorsmod.Wrapf(ErrInvalidMerkleProof, "invalid inclusion proof for validator set: %v", err) } @@ -73,10 +74,10 @@ func VerifyValSet(epoch *epochingtypes.Epoch, valSet *checkpointingtypes.Validat // VerifyEpochSealed verifies that the given `epoch` is sealed by the `rawCkpt` by using the given `proof` // The verification rules include: // - basic sanity checks -// - The raw checkpoint's app_hash is same as in the header of the sealer epoch -// - More than 1/3 (in voting power) validators in the validator set of this epoch have signed app_hash of the sealer epoch -// - The epoch medatata is committed to the app_hash of the sealer epoch -// - The validator set is committed to the app_hash of the sealer epoch +// - The raw checkpoint's BlockHash is same as the sealer_block_hash of the sealed epoch +// - More than 2/3 (in voting power) validators in the validator set of this epoch have signed sealer_block_hash of the sealed epoch +// - The epoch medatata is committed to the sealer_app_hash of the sealed epoch +// - The validator set is committed to the sealer_app_hash of the sealed epoch func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.RawCheckpoint, proof *ProofEpochSealed) error { // nil check if epoch == nil { @@ -101,19 +102,19 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R return fmt.Errorf("epoch.EpochNumber (%d) is not equal to rawCkpt.EpochNum (%d)", epoch.EpochNumber, rawCkpt.EpochNum) } - // ensure the raw checkpoint's app_hash is same as in the header of the sealer header + // ensure the raw checkpoint's block_hash is same as the sealer_block_hash of the sealed epoch // NOTE: since this proof is assembled by a Babylon node who has verified the checkpoint, - // the two appHash values should always be the same, otherwise this Babylon node is malicious. + // the two blockhash values should always be the same, otherwise this Babylon node is malicious. // This is different from the checkpoint verification rules in checkpointing, - // where a checkpoint with valid BLS multisig but different appHash signals a dishonest majority equivocation. - appHashInCkpt := rawCkpt.AppHash - appHashInSealerHeader := checkpointingtypes.AppHash(epoch.SealerHeaderHash) - if !appHashInCkpt.Equal(appHashInSealerHeader) { - return fmt.Errorf("AppHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", appHashInCkpt.String(), appHashInSealerHeader.String()) + // where a checkpoint with valid BLS multisig but different blockhashes signals a dishonest majority equivocation. + blockHashInCkpt := rawCkpt.BlockHash + blockHashInSealerHeader := checkpointingtypes.BlockHash(epoch.SealerBlockHash) + if !blockHashInCkpt.Equal(blockHashInSealerHeader) { + return fmt.Errorf("BlockHash is not same in rawCkpt (%s) and epoch's SealerHeader (%s)", blockHashInCkpt.String(), blockHashInSealerHeader.String()) } /* - Ensure more than 1/3 (in voting power) validators of this epoch have signed (epoch_num || app_hash) in the raw checkpoint + Ensure more than 2/3 (in voting power) validators of this epoch have signed (epoch_num || block_hash) in the raw checkpoint */ valSet := &checkpointingtypes.ValidatorWithBlsKeySet{ValSet: proof.ValidatorSet} // filter validator set that contributes to the signature @@ -121,9 +122,9 @@ func VerifyEpochSealed(epoch *epochingtypes.Epoch, rawCkpt *checkpointingtypes.R if err != nil { return err } - // ensure the signerSet has > 1/3 voting power - if signerSetPower <= valSet.GetTotalPower()*1/3 { - return fmt.Errorf("the BLS signature involves insufficient voting power") + // ensure the signerSet has > 2/3 voting power + if signerSetPower*3 <= valSet.GetTotalPower()*2 { + return checkpointingtypes.ErrInsufficientVotingPower } // verify BLS multisig signedMsgBytes := rawCkpt.SignedMsg() @@ -170,10 +171,10 @@ func VerifyCZHeaderInEpoch(header *IndexedHeader, epoch *epochingtypes.Epoch, pr return fmt.Errorf("epoch.EpochNumber (%d) is not equal to header.BabylonEpoch (%d)", epoch.EpochNumber, header.BabylonEpoch) } - // get the Merkle root, i.e., the AppHash of the sealer header - root := epoch.SealerHeaderHash + // get the Merkle root, i.e., the BlockHash of the sealer header + root := epoch.SealerAppHash - // Ensure The header is committed to the AppHash of the sealer header + // Ensure The header is committed to the BlockHash of the sealer header headerBytes, err := header.Marshal() if err != nil { return err diff --git a/x/zoneconcierge/types/btc_timestamp_test.go b/x/zoneconcierge/types/btc_timestamp_test.go index a3208407f..2f513329d 100644 --- a/x/zoneconcierge/types/btc_timestamp_test.go +++ b/x/zoneconcierge/types/btc_timestamp_test.go @@ -5,6 +5,11 @@ import ( "math/rand" "testing" + "github.com/boljen/go-bitmap" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" + txformat "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/testutil/datagen" @@ -12,10 +17,6 @@ import ( btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/babylonchain/babylon/x/zoneconcierge/types" - "github.com/boljen/go-bitmap" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" - "github.com/stretchr/testify/require" ) func signBLSWithBitmap(blsSKs []bls12381.PrivateKey, bm bitmap.Bitmap, msg []byte) (bls12381.Signature, error) { @@ -34,10 +35,12 @@ func FuzzBTCTimestamp(f *testing.F) { f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - h := testhelper.NewHelperWithValSet(t) + // generate the validator set with 10 validators as genesis + genesisValSet, privSigner, err := datagen.GenesisValidatorSetWithPrivSigner(10) + require.NoError(t, err) + h := testhelper.NewHelperWithValSet(t, genesisValSet, privSigner) ek := &h.App.EpochingKeeper zck := h.App.ZoneConciergeKeeper - var err error // empty BTC timestamp btcTs := &types.BTCTimestamp{} @@ -50,8 +53,8 @@ func FuzzBTCTimestamp(f *testing.F) { */ // enter block 11, 1st block of epoch 2 epochInterval := ek.GetParams(h.Ctx).EpochInterval - for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + for j := 0; j < int(epochInterval)-2; j++ { + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } @@ -68,11 +71,11 @@ func FuzzBTCTimestamp(f *testing.F) { // enter block 21, 1st block of epoch 3 for j := 0; j < int(epochInterval); j++ { - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) } // seal last epoch - h.Ctx, err = h.GenAndApplyEmptyBlock(r) + h.Ctx, err = h.ApplyEmptyBlockWithVoteExtension(r) h.NoError(err) epochWithHeader, err := ek.GetHistoricalEpoch(h.Ctx, indexedHeader.BabylonEpoch) @@ -92,15 +95,15 @@ func FuzzBTCTimestamp(f *testing.F) { // construct the rawCkpt // Note that the BlsMultiSig will be generated and assigned later bm := datagen.GenFullBitmap() - appHash := checkpointingtypes.AppHash(epochWithHeader.SealerHeaderHash) + blockHash := checkpointingtypes.BlockHash(epochWithHeader.SealerBlockHash) rawCkpt := &checkpointingtypes.RawCheckpoint{ EpochNum: epochWithHeader.EpochNumber, - AppHash: &appHash, + BlockHash: &blockHash, Bitmap: bm, BlsMultiSig: nil, } // let the subset generate a BLS multisig over sealer header's app_hash - multiSig, err := signBLSWithBitmap(h.GenValidators.BlsPrivKeys, bm, rawCkpt.SignedMsg()) + multiSig, err := signBLSWithBitmap(h.GenValidators.GetBLSPrivKeys(), bm, rawCkpt.SignedMsg()) require.NoError(t, err) // assign multiSig to rawCkpt rawCkpt.BlsMultiSig = &multiSig From 531d3db0324b7518d3b7c0b9bb8eab607d7ee508 Mon Sep 17 00:00:00 2001 From: bitcoin-lightning <153181187+AtomicInnovation321@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:23:28 +0800 Subject: [PATCH 138/202] Fix typos (#405) * Update export.go * Update app.go --- app/app.go | 4 ++-- app/export.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/app.go b/app/app.go index c878a001f..287718c5d 100644 --- a/app/app.go +++ b/app/app.go @@ -143,9 +143,9 @@ import ( const ( appName = "BabylonApp" - // Custom prefix for application enviromental variables. + // Custom prefix for application environmental variables. // From cosmos version 0.46 is is possible to have custom prefix for application - // enviromental variables - https://github.com/cosmos/cosmos-sdk/pull/10950 + // environmental variables - https://github.com/cosmos/cosmos-sdk/pull/10950 BabylonAppEnvPrefix = "" // TODO review possible capabilities diff --git a/app/export.go b/app/export.go index ada8411d7..95f7ee5e0 100644 --- a/app/export.go +++ b/app/export.go @@ -111,7 +111,7 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) app.DistrKeeper.SetFeePool(ctx, feePool) - app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) //nolint:errcheck // either we ignore the error here or propogate up the stack + app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) //nolint:errcheck // either we ignore the error here or propagate up the stack return false }) @@ -125,8 +125,8 @@ func (app *BabylonApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddr if err != nil { panic(err) } - app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propogate up the stack - app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propogate up the stack + app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propagate up the stack + app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) //nolint:errcheck // either we ignore the error here or propagate up the stack } // reset context height From a1728750eb2a181d3ad7c972861dd796b2949851 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 3 Jan 2024 16:53:50 +1100 Subject: [PATCH 139/202] chore: some leftover fixes for ABCI++ (#158) --- btctxformatter/formatter.go | 10 +++++----- btctxformatter/formatter_test.go | 6 +++--- proto/babylon/zoneconcierge/v1/zoneconcierge.proto | 2 +- testutil/datagen/btc_transaction.go | 2 +- testutil/datagen/raw_checkpoint.go | 2 +- x/checkpointing/keeper/keeper_test.go | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/btctxformatter/formatter.go b/btctxformatter/formatter.go index 83f19f3b4..6006bf215 100644 --- a/btctxformatter/formatter.go +++ b/btctxformatter/formatter.go @@ -43,7 +43,7 @@ const ( // 4bytes tag + 4 bits version + 4 bits part index headerLength = TagLength + 1 - AppHashLength = 32 + BlockHashLength = 32 BitMapLength = 13 @@ -61,11 +61,11 @@ const ( // 8 bytes are for 64bit unsigned epoch number EpochLength = 8 - firstPartLength = headerLength + AppHashLength + AddressLength + EpochLength + BitMapLength + firstPartLength = headerLength + BlockHashLength + AddressLength + EpochLength + BitMapLength secondPartLength = headerLength + BlsSigLength + firstPartHashLength - RawBTCCheckpointLength = EpochLength + AppHashLength + BitMapLength + BlsSigLength + AddressLength + RawBTCCheckpointLength = EpochLength + BlockHashLength + BitMapLength + BlsSigLength + AddressLength ) func getVerHalf(version FormatVersion, halfNumber uint8) uint8 { @@ -154,7 +154,7 @@ func EncodeCheckpointData( return nil, nil, errors.New("invalid format version") } - if len(rawBTCCheckpoint.BlockHash) != AppHashLength { + if len(rawBTCCheckpoint.BlockHash) != BlockHashLength { return nil, nil, errors.New("appHash should have 32 bytes") } @@ -318,7 +318,7 @@ func DecodeRawCheckpoint(version FormatVersion, btcCkptBytes []byte) (*RawBtcChe var b bytes.Buffer b.Write(btcCkptBytes) epochBytes := b.Next(EpochLength) - appHashBytes := b.Next(AppHashLength) + appHashBytes := b.Next(BlockHashLength) bitmapBytes := b.Next(BitMapLength) addressBytes := b.Next(AddressLength) blsSigBytes := b.Next(BlsSigLength) diff --git a/btctxformatter/formatter_test.go b/btctxformatter/formatter_test.go index 39a375337..1215a81cf 100644 --- a/btctxformatter/formatter_test.go +++ b/btctxformatter/formatter_test.go @@ -14,9 +14,9 @@ func randNBytes(n int) []byte { } func FuzzEncodingDecoding(f *testing.F) { - f.Add(uint64(5), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) - f.Add(uint64(20), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) - f.Add(uint64(2000), randNBytes(TagLength), randNBytes(AppHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(5), randNBytes(TagLength), randNBytes(BlockHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(20), randNBytes(TagLength), randNBytes(BlockHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) + f.Add(uint64(2000), randNBytes(TagLength), randNBytes(BlockHashLength), randNBytes(BitMapLength), randNBytes(BlsSigLength), randNBytes(AddressLength)) f.Fuzz(func(t *testing.T, epoch uint64, tag []byte, appHash []byte, bitMap []byte, blsSig []byte, address []byte) { diff --git a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto index 45093b1b4..7d37d7b30 100644 --- a/proto/babylon/zoneconcierge/v1/zoneconcierge.proto +++ b/proto/babylon/zoneconcierge/v1/zoneconcierge.proto @@ -97,7 +97,7 @@ message FinalizedChainInfo { // - Raw checkpoint of this epoch // The verifier can perform the following verification rules: // - The raw checkpoint's `app_hash` is same as in the sealer header -// - More than 1/3 (in voting power) validators in the validator set of this +// - More than 2/3 (in voting power) validators in the validator set of this // epoch have signed `app_hash` of the sealer header // - The epoch medatata is committed to the `app_hash` of the sealer header // - The validator set is committed to the `app_hash` of the sealer header diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 88731c82e..9bcdad7f9 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -459,7 +459,7 @@ func GenerateMessageWithRandomSubmitter(blockResults []*BlockCreationResult) *bt func getRandomCheckpointDataForEpoch(r *rand.Rand, e uint64) testCheckpointData { return testCheckpointData{ epoch: e, - blockHash: GenRandomByteArray(r, txformat.AppHashLength), + blockHash: GenRandomByteArray(r, txformat.BlockHashLength), bitmap: GenRandomByteArray(r, txformat.BitMapLength), blsSig: GenRandomByteArray(r, txformat.BlsSigLength), submitterAddress: GenRandomByteArray(r, txformat.AddressLength), diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index f852b3bbd..ee22774e7 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -116,7 +116,7 @@ func GenerateLegitimateRawCheckpoint(r *rand.Rand, privKeys []bls12381.PrivateKe // number of validators, at least 4 n := len(privKeys) // ensure sufficient signers - signerNum := n/3 + 1 + signerNum := n*2/3 + 1 epochNum := GenRandomEpochNum(r) blockHash := GenRandomBlockHash(r) msgBytes := types.GetSignBytes(epochNum, blockHash) diff --git a/x/checkpointing/keeper/keeper_test.go b/x/checkpointing/keeper/keeper_test.go index 4b7e89fd5..4dd46f58d 100644 --- a/x/checkpointing/keeper/keeper_test.go +++ b/x/checkpointing/keeper/keeper_test.go @@ -213,7 +213,7 @@ func FuzzKeeperCheckpointEpoch(f *testing.F) { require.ErrorIs(t, err, types.ErrInvalidRawCheckpoint) // 3. check a conflicting checkpoint; signed on a random BlockHash - conflictBlockHash := datagen.GenRandomByteArray(r, btctxformatter.AppHashLength) + conflictBlockHash := datagen.GenRandomByteArray(r, btctxformatter.BlockHashLength) msgBytes = types.GetSignBytes(localCkptWithMeta.Ckpt.EpochNum, conflictBlockHash) rawBtcCheckpoint = makeBtcCkptBytes( r, From 19743980044eb89b2a51d8f79b9f0736d40ee4a4 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Wed, 3 Jan 2024 16:22:04 +0100 Subject: [PATCH 140/202] config for signet params (#161) --- types/btc_config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/types/btc_config.go b/types/btc_config.go index b565740f4..078b2a120 100644 --- a/types/btc_config.go +++ b/types/btc_config.go @@ -19,6 +19,7 @@ const ( BtcTestnet SupportedBtcNetwork = "testnet" BtcSimnet SupportedBtcNetwork = "simnet" BtcRegtest SupportedBtcNetwork = "regtest" + BtcSignet SupportedBtcNetwork = "signet" ) func getParams(opts servertypes.AppOptions) *chaincfg.Params { @@ -42,8 +43,10 @@ func getParams(opts servertypes.AppOptions) *chaincfg.Params { return &chaincfg.SimNetParams } else if network == string(BtcRegtest) { return &chaincfg.RegressionNetParams + } else if network == string(BtcSignet) { + return &chaincfg.SigNetParams } else { - panic("Bitcoin network should be one of [mainet, testnet, simnet, regtest]") + panic("Bitcoin network should be one of [mainet, testnet, simnet, regtest, signet]") } } From 024985553a86d638782db4bfb69f4a1b5c1f8c0c Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Jan 2024 11:56:25 +1100 Subject: [PATCH 141/202] doc: documentation for zoneconcierge module (#156) --- x/zoneconcierge/README.md | 227 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 x/zoneconcierge/README.md diff --git a/x/zoneconcierge/README.md b/x/zoneconcierge/README.md new file mode 100644 index 000000000..e421be470 --- /dev/null +++ b/x/zoneconcierge/README.md @@ -0,0 +1,227 @@ +# ZoneConcierge + +The Zone Concierge module is responsible for generating BTC timestamps of headers from other Cosmos zones. +These BTC timestamps allow Cosmos zones integrating with Babylon to achieve Bitcoin security, i.e., forking a Cosmos zone is as hard as forking Bitcoin. +The Zone Concierge module leverages the IBC light client protocol to receive Cosmos zones' headers. + +There are two phases of integration for a consumer chain: + +- **Phase 1 integration:** Babylon receives consumer chain headers via standard `MsgUpdateClient` messages in IBC light client protocol, timestamps them, and functions as a canonical chain oracle for the consumer chain. [Babylonscan](https://babylonscan.io/) shows Cosmos zones with phase 1 integration. +- **Phase 2 integration:** In addition to phase 1, phase 2 allows a consumer chain to receive BTC timestamps from Babylon via an IBC channel, such that the consumer chain can use BTC timestamps to detect and resolve forks, as well as other use cases such as Bitcoin-assisted fast unbonding. + +## Table of contents + +- [ZoneConcierge](#zoneconcierge) + - [Table of contents](#table-of-contents) + - [State](#state) + - [ChainInfo](#chaininfo) + - [EpochChainInfo](#epochchaininfo) + - [CanonicalChain](#canonicalchain) + - [Fork](#fork) + - [Params](#params) + - [Interaction with consumer chains under phase 1 integration](#interaction-with-consumer-chains-under-phase-1-integration) + - [Interaction with consumer chains under phase 2 integration](#interaction-with-consumer-chains-under-phase-2-integration) + - [Queries](#queries) + +## State + +The Zone Concierge module keeps handling IBC headers of consumer chains, and maintains the following KV stores. + +### ChainInfo + +The chain info storage at `x/zoneconcierge/keeper/chain_info_indexer.go` maintains `ChainInfo` for each consumer chain. +The key is the consumer chain's `ChainID`, and the value is a `ChainInfo` object. +The `ChainInfo` is a structure storing the information of a consumer chain that checkpoints to Babylon. + +```protobuf +// ChainInfo is the information of a CZ +message ChainInfo { + // chain_id is the ID of the chain + string chain_id = 1; + // latest_header is the latest header in CZ's canonical chain + IndexedHeader latest_header = 2; + // latest_forks is the latest forks, formed as a series of IndexedHeader (from + // low to high) + Forks latest_forks = 3; + // timestamped_headers_count is the number of timestamped headers in CZ's + // canonical chain + uint64 timestamped_headers_count = 4; +} +``` + +### EpochChainInfo + +The epoch chain info storage at `x/zoneconcierge/keeper/epoch_chain_info_indexer.go` maintains `ChainInfo` at the end of each Babylon epoch for each consumer chain. +The key is the consumer chain's `ChainID` plus the epoch number, and the value is a `ChainInfo` object. + +### CanonicalChain + +The canonical chain storage at `x/zoneconcierge/keeper/canonical_chain_indexer.go` maintains the metadata of canonical IBC headers of a consumer chain. +The key is the consumer chain's `ChainID` plus the height, and the value is a `IndexedHeader` object. +`IndexedHeader` is a structure storing an IBC header's metadata. + +```protobuf +// IndexedHeader is the metadata of a CZ header +message IndexedHeader { + // chain_id is the unique ID of the chain + string chain_id = 1; + // hash is the hash of this header + bytes hash = 2; + // height is the height of this header on CZ ledger + // (hash, height) jointly provides the position of the header on CZ ledger + uint64 height = 3; + // time is the timestamp of this header on CZ ledger + // it is needed for CZ to unbond all mature validators/delegations + // before this timestamp when this header is BTC-finalised + google.protobuf.Timestamp time = 4 [ (gogoproto.stdtime) = true ]; + // babylon_header_hash is the hash of the babylon block that includes this CZ + // header + bytes babylon_header_hash = 5; + // babylon_header_height is the height of the babylon block that includes this CZ + // header + uint64 babylon_header_height = 6; + // epoch is the epoch number of this header on Babylon ledger + uint64 babylon_epoch = 7; + // babylon_tx_hash is the hash of the tx that includes this header + // (babylon_block_height, babylon_tx_hash) jointly provides the position of + // the header on Babylon ledger + bytes babylon_tx_hash = 8; +} +``` + +### Fork + +The fork storage at `x/zoneconcierge/keeper/fork_indexer.go` maintains the metadata of canonical IBC headers of a consumer chain. +The key is the consumer chain's `ChainID` plus the height, and the value is a list of `IndexedHeader` objects, which represent fork headers at that height. + +### Params + +The parameter storage at `x/zoneconcierge/keeper/params.go` maintains the parameters for the Zone Concierge module. + +```protobuf +// Params defines the parameters for the module. +message Params { + option (gogoproto.equal) = true; + + // ibc_packet_timeout_seconds is the time period after which an unrelayed + // IBC packet becomes timeout, measured in seconds + uint32 ibc_packet_timeout_seconds = 1 + [ (gogoproto.moretags) = "yaml:\"ibc_packet_timeout_seconds\"" ]; +} +``` + +## Interaction with consumer chains under phase 1 integration + + + +In phase 1 integration, Babylon maintains headers for a consumer chain via the IBC light client protocol. +The IBC header chain of the consumer chain is checkpointed by Bitcoin via Babylon, thus achieves Bitcoin security. + +Babylon utilizes IBC light client protocol for the phase 1 integration. +Specifically, the IBC relayer keeps relaying the consumer chain's headers to Babylon via IBC protocol's `MsgUpdateClient` [endpoint](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L20-L21), and the Zone Concierge module uses a `PostHandler` to handle the IBC header timely. +This does not involve any IBC connection or channel between Babylon and a consumer chain. + +The `PostHandler` is defined at `x/zoneconcierge/keeper/header_handler.go`, and works as follows. + +1. If the consumer chain hosting the header is not known to Babylon, initialize `ChainInfo` storage for the consumer chain. +2. If the header is on a fork, insert the header to the fork storage and update `ChainInfo`. +3. If the header is canonical, insert the header to the canonical chain storage and update `ChainInfo`. + +## Interaction with consumer chains under phase 2 integration + + + +In phase 2 integration, Babylon does everything in phase 1, and will send BTC timestamps of headers back to each consumer chain. +Each consumer chain can verify the BTC timestamp and ensure that each header is finalized by Bitcoin, thus obtaining Bitcoin security. +To do phase 2 integration, one needs to deploy a Babylon smart contract on the consumer chain, and start an IBC relayer between Babylon and the Babylon contract on the consumer chain. +It does not require any change to the consumer chain's code. + +The BTC timestamps will allow a consumer chain to make use of BTC timestamps for different use cases, such as BTC-assisted fast unbonding. + +The BTC timestamp is defined in the structure `BTCTimestamp`. +It includes a header and a set of proofs that the header is finalized by Bitcoin. + + + +```protobuf +// BTCTimestamp is a BTC timestamp that carries information of a BTC-finalised epoch +// It includes a number of BTC headers, a raw checkpoint, an epoch metadata, and +// a CZ header if there exists CZ headers checkpointed to this epoch. +// Upon a newly finalised epoch in Babylon, Babylon will send a BTC timestamp to each +// Cosmos zone that has phase-2 integration with Babylon via IBC. +message BTCTimestamp { + // header is the last CZ header in the finalized Babylon epoch + babylon.zoneconcierge.v1.IndexedHeader header = 1; + + /* + Data for BTC light client + */ + // btc_headers is BTC headers between + // - the block AFTER the common ancestor of BTC tip at epoch `lastFinalizedEpoch-1` and BTC tip at epoch `lastFinalizedEpoch` + // - BTC tip at epoch `lastFinalizedEpoch` + // where `lastFinalizedEpoch` is the last finalised epoch in Babylon + repeated babylon.btclightclient.v1.BTCHeaderInfo btc_headers = 2; + + /* + Data for Babylon epoch chain + */ + // epoch_info is the metadata of the sealed epoch + babylon.epoching.v1.Epoch epoch_info = 3; + // raw_checkpoint is the raw checkpoint that seals this epoch + babylon.checkpointing.v1.RawCheckpoint raw_checkpoint = 4; + // btc_submission_key is position of two BTC txs that include the raw checkpoint of this epoch + babylon.btccheckpoint.v1.SubmissionKey btc_submission_key = 5; + + /* + Proofs that the header is finalized + */ + babylon.zoneconcierge.v1.ProofFinalizedChainInfo proof = 6; +} + +// ProofFinalizedChainInfo is a set of proofs that attest a chain info is +// BTC-finalised +message ProofFinalizedChainInfo { + /* + The following fields include proofs that attest the chain info is + BTC-finalised + */ + // proof_cz_header_in_epoch is the proof that the CZ header is timestamped + // within a certain epoch + tendermint.crypto.ProofOps proof_cz_header_in_epoch = 1; + // proof_epoch_sealed is the proof that the epoch is sealed + babylon.zoneconcierge.v1.ProofEpochSealed proof_epoch_sealed = 2; + // proof_epoch_submitted is the proof that the epoch's checkpoint is included + // in BTC ledger It is the two TransactionInfo in the best (i.e., earliest) + // checkpoint submission + repeated babylon.btccheckpoint.v1.TransactionInfo proof_epoch_submitted = 3; +} +``` + +Upon a Babylon epoch is finalized, Babylon will send an IBC packet including a `BTCTimestamp` to each consumer chain doing phase 2/3 integration with Babylon. +The logic upon each finalized epoch is defined at `x/zoneconcierge/keeper/ibc_packet_btc_timestamp.go` and works as follows. + +1. Find all open IBC channels with Babylon's Zone Concierge module. The counterparty at each IBC channel is a consumer chain. +2. Get all canonical BTC headers received during the time of finalizing the last epoch. Specifically, + 2.1. Find the tip `h'` of BTC light client when the second last epoch becomes finalized. + 2.2. Find the tip `h` of BTC light client when the last epoch becomes finalized. + 2.3. If `h'` and `h` are on the same chain, then the canonical BTC headers are headers from `h'` to `h`. + 2.4. If `h'` and `h` are on different forks, then the canonical BTC headers start from their last common ancestor to `h`. +3. For each of these IBC channels: + 3.1. Find the `ChainID` of the counterparty chain (i.e., the consumer chain) in the IBC channel + 3.2. Get the `ChainInfo` of the `ChainID` at the last finalized epoch. + 3.3. Get the metadata of the last finalized epoch and its corresponding raw checkpoint. + 3.4. Generate the proof that the last consumer chain's canonical header is committed to the epoch's metadata. + 3.5. Generate the proof that the epoch is sealed, i.e., receives a BLS multisignature generated by validators with >2/3 total voting power at the last finalized epoch. + 3.6. Generate the proof that the epoch's checkpoint is submitted, i.e., encoded in transactions on Bitcoin. + 3.7. Assemble all the above as `BTCTimestamp`, and send it to the IBC channel in an IBC packet. + +[Babylon contract](https://github.com/babylonchain/babylon-contract) is a CosmWasm smart contract for phase 2 integration. +It can be deployed to a blockchain supporting CosmWasm smart contracts, connects with Babylon's Zone Concierge module via an IBC channel, and receives BTC timestamps from Babylon to help the consumer chain get Bitcoin security. + +Note that Zone Concierge provides 1-to-all connection, where where the Zone Concierge module establishes an IBC channel with each of multiple consumer chains. +Zone Concierge will send an BTC timestamp to each of these consumer chains upon an epoch is finalised. + +## Queries + +The Zone Concierge module only has one message `MsgUpdateParams` for updating the module parameters via a governance proposal. +It provides a set of queries about the status of checkpointed consumer chains, listed at [docs.babylonchain.io](https://docs.babylonchain.io/docs/developer-guides/grpcrestapi#tag/ZoneConcierge). From dd4e4b1c059844e7cd7f0398b64b2e8260a1510e Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 5 Jan 2024 09:36:12 +0100 Subject: [PATCH 142/202] Require lock time in slashing tx change output (#159) * Require lock time in slashing tx change output --- btcstaking/btcstaking_test.go | 67 ++++ btcstaking/staking.go | 97 +++--- btcstaking/staking_test.go | 25 +- btcstaking/types.go | 66 ++++ proto/babylon/btcstaking/v1/btcstaking.proto | 33 +- proto/babylon/btcstaking/v1/query.proto | 8 +- proto/babylon/btcstaking/v1/tx.proto | 15 +- test/e2e/btc_staking_e2e_test.go | 15 +- testutil/datagen/btcstaking.go | 44 ++- testutil/keeper/btcstaking.go | 6 +- x/btcstaking/keeper/grpc_query.go | 2 +- x/btcstaking/keeper/grpc_query_test.go | 22 +- x/btcstaking/keeper/incentive_test.go | 7 +- x/btcstaking/keeper/msg_server.go | 37 ++- x/btcstaking/keeper/msg_server_test.go | 156 ++++++++- .../keeper/voting_power_table_test.go | 23 +- x/btcstaking/types/btc_delegation.go | 2 +- x/btcstaking/types/btc_delegation_test.go | 8 +- x/btcstaking/types/btc_slashing_tx.go | 25 -- x/btcstaking/types/btc_slashing_tx_test.go | 6 +- x/btcstaking/types/btc_undelegation_test.go | 13 +- x/btcstaking/types/btcstaking.pb.go | 305 ++++++++---------- x/btcstaking/types/msg.go | 2 +- x/btcstaking/types/query.pb.go | 210 +++++++----- x/btcstaking/types/tx.pb.go | 150 ++++----- 25 files changed, 819 insertions(+), 525 deletions(-) diff --git a/btcstaking/btcstaking_test.go b/btcstaking/btcstaking_test.go index c4600f092..22f0df961 100644 --- a/btcstaking/btcstaking_test.go +++ b/btcstaking/btcstaking_test.go @@ -568,3 +568,70 @@ func TestSpendingSlashingPathCovenant35MultiSigFinalityProviderRestaking(t *test } btctest.AssertEngineExecution(t, 0, true, newEngine) } + +func TestSpendingRelativeTimeLockScript(t *testing.T) { + stakerPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + stakerPubKey := stakerPrivKey.PubKey() + lockTime := uint16(10) + lockedAmount := btcutil.Amount(2 * 10e8) + + // to spend output with relative timelock transaction need to be version two or higher + spendStakeTx := wire.NewMsgTx(2) + spendStakeTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + spendStakeTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + // spend half of the staking amount + Value: int64(lockedAmount.MulF64(0.5)), + }, + ) + + tls, err := btcstaking.BuildRelativeTimelockTaprootScript( + stakerPubKey, + lockTime, + &chaincfg.MainNetParams, + ) + require.NoError(t, err) + + timeLockOutput := &wire.TxOut{ + PkScript: tls.PkScript, + Value: int64(lockedAmount), + } + + // we need to set sequence number before signing, as signing commits to sequence + // number + spendStakeTx.TxIn[0].Sequence = uint32(tls.LockTime) + + sig, err := btcstaking.SignTxWithOneScriptSpendInputFromTapLeaf( + spendStakeTx, + timeLockOutput, + stakerPrivKey, + tls.SpendInfo.RevealedLeaf, + ) + + require.NoError(t, err) + + witness, err := btcstaking.CreateWitness( + tls.SpendInfo, + [][]byte{sig.Serialize()}, + ) + + require.NoError(t, err) + + spendStakeTx.TxIn[0].Witness = witness + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + timeLockOutput.PkScript, timeLockOutput.Value, + ) + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + timeLockOutput.PkScript, + spendStakeTx, 0, txscript.StandardVerifyFlags, nil, + txscript.NewTxSigHashes(spendStakeTx, prevOutputFetcher), timeLockOutput.Value, + prevOutputFetcher, + ) + } + btctest.AssertEngineExecution(t, 0, true, newEngine) +} diff --git a/btcstaking/staking.go b/btcstaking/staking.go index e464ecf0d..47a224298 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -2,6 +2,7 @@ package btcstaking import ( "bytes" + "encoding/hex" "fmt" sdkmath "cosmossdk.io/math" @@ -16,7 +17,7 @@ import ( asig "github.com/babylonchain/babylon/crypto/schnorr-adaptor-signature" ) -// BuildSlashingTxFromOutpoint builds a valid slashing transaction by creating a new Bitcoin transaction that slashes a portion +// buildSlashingTxFromOutpoint builds a valid slashing transaction by creating a new Bitcoin transaction that slashes a portion // of staked funds and directs them to a specified slashing address. The transaction also includes a change output sent back to // the specified change address. The slashing rate determines the proportion of staked funds to be slashed. // @@ -31,7 +32,7 @@ import ( // Returns: // - *wire.MsgTx: The constructed slashing transaction without a script signature or witness. // - error: An error if any validation or construction step fails. -func BuildSlashingTxFromOutpoint( +func buildSlashingTxFromOutpoint( stakingOutput wire.OutPoint, stakingAmount, fee int64, slashingAddress, changeAddress btcutil.Address, @@ -113,49 +114,6 @@ func getPossibleStakingOutput( return stakingOutput, nil } -// BuildSlashingTxFromStakingTx constructs a valid slashing transaction using information from a staking transaction, -// a specified staking output index, and other parameters such as slashing and change addresses, slashing rate, and transaction fee. -// -// Parameters: -// - stakingTx: The staking transaction from which the staking output is to be used for slashing. -// - stakingOutputIdx: The index of the staking output in the staking transaction. -// - slashingAddress: The Bitcoin address to which the slashed funds will be sent. -// - changeAddress: The Bitcoin address to receive the change from the transaction. -// - slashingRate: The rate at which the staked funds will be slashed, expressed as a decimal. -// - fee: The transaction fee to be paid. -// -// Returns: -// - *wire.MsgTx: The constructed slashing transaction without a script signature or witness. -// - error: An error if any validation or construction step fails. -// -// This function validates that the staking transaction is not nil, has an output at the specified index, -// and that the staking output at the specified index is a valid staking output. -// It then calls BuildSlashingTxFromOutpoint to build the slashing transaction using the validated staking output. -func BuildSlashingTxFromStakingTx( - stakingTx *wire.MsgTx, - stakingOutputIdx uint32, - slashingAddress, changeAddress btcutil.Address, - slashingRate sdkmath.LegacyDec, - fee int64, -) (*wire.MsgTx, error) { - // Get the staking output at the specified index from the staking transaction - stakingOutput, err := getPossibleStakingOutput(stakingTx, stakingOutputIdx) - if err != nil { - return nil, err - } - - // Create an OutPoint for the staking output - stakingTxHash := stakingTx.TxHash() - stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) - - // Build the slashing transaction using the staking output - return BuildSlashingTxFromOutpoint( - *stakingOutpoint, - stakingOutput.Value, fee, - slashingAddress, changeAddress, - slashingRate) -} - // BuildSlashingTxFromStakingTxStrict constructs a valid slashing transaction using information from a staking transaction, // a specified staking output index, and additional parameters such as slashing and change addresses, transaction fee, // staking script, script version, and network. This function performs stricter validation compared to BuildSlashingTxFromStakingTx. @@ -164,7 +122,8 @@ func BuildSlashingTxFromStakingTx( // - stakingTx: The staking transaction from which the staking output is to be used for slashing. // - stakingOutputIdx: The index of the staking output in the staking transaction. // - slashingAddress: The Bitcoin address to which the slashed funds will be sent. -// - changeAddress: The Bitcoin address to receive the change from the transaction. +// - stakerPk: public key of the staker i.e the btc holder who can spend staking output after lock time +// - slashingChangeLockTime: lock time which will be used on slashing transaction change output // - fee: The transaction fee to be paid. // - slashingRate: The rate at which the staked funds will be slashed, expressed as a decimal. // - script: The staking script to which the staking output should commit. @@ -180,7 +139,9 @@ func BuildSlashingTxFromStakingTx( func BuildSlashingTxFromStakingTxStrict( stakingTx *wire.MsgTx, stakingOutputIdx uint32, - slashingAddress, changeAddress btcutil.Address, + slashingAddress btcutil.Address, + stakerPk *btcec.PublicKey, + slashChangeLockTime uint16, fee int64, slashingRate sdkmath.LegacyDec, net *chaincfg.Params, @@ -195,11 +156,22 @@ func BuildSlashingTxFromStakingTxStrict( stakingTxHash := stakingTx.TxHash() stakingOutpoint := wire.NewOutPoint(&stakingTxHash, stakingOutputIdx) + // Create taproot address commiting to timelock script + si, err := BuildRelativeTimelockTaprootScript( + stakerPk, + slashChangeLockTime, + net, + ) + + if err != nil { + return nil, err + } + // Build slashing tx with the staking output information - return BuildSlashingTxFromOutpoint( + return buildSlashingTxFromOutpoint( *stakingOutpoint, stakingOutput.Value, fee, - slashingAddress, changeAddress, + slashingAddress, si.TapAddress, slashingRate) } @@ -258,6 +230,9 @@ func ValidateSlashingTx( slashingAddress btcutil.Address, slashingRate sdkmath.LegacyDec, slashingTxMinFee, stakingOutputValue int64, + stakerPk *btcec.PublicKey, + slashingChangeLockTime uint16, + net *chaincfg.Params, ) error { // Verify that the slashing transaction is not nil. if slashingTx == nil { @@ -303,6 +278,22 @@ func ValidateSlashingTx( return fmt.Errorf("slashing transaction must pay to the provided slashing address") } + // Verify that the second output pays to the taproot address which locks funds for + // slashingChangeLockTime + si, err := BuildRelativeTimelockTaprootScript( + stakerPk, + slashingChangeLockTime, + net, + ) + + if err != nil { + return fmt.Errorf("error creating change timelock script: %w", err) + } + + if !bytes.Equal(slashingTx.TxOut[1].PkScript, si.PkScript) { + return fmt.Errorf("invalid slashing tx change output pkscript, expected: %s, got: %s", hex.EncodeToString(si.PkScript), hex.EncodeToString(slashingTx.TxOut[1].PkScript)) + } + // Verify that the none of the outputs is a dust output. for _, out := range slashingTx.TxOut { if mempool.IsDust(out, mempool.DefaultMinRelayTxFee) { @@ -349,6 +340,8 @@ func CheckTransactions( slashingTxMinFee int64, slashingRate sdkmath.LegacyDec, slashingAddress btcutil.Address, + stakerPk *btcec.PublicKey, + slashingChangeLockTime uint16, net *chaincfg.Params, ) error { // Check if slashing tx min fee is valid @@ -371,7 +364,11 @@ func CheckTransactions( slashingTx, slashingAddress, slashingRate, - slashingTxMinFee, stakingOutput.Value); err != nil { + slashingTxMinFee, + stakingOutput.Value, + stakerPk, + slashingChangeLockTime, + net); err != nil { return err } diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index 554fdce49..8df0c9611 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -68,6 +68,7 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { stakingOutputIdx := r.Intn(5) // always more outputs than stakingOutputIdx stakingTxNumOutputs := r.Intn(5) + 10 + slashingLockTime := uint16(r.Intn(1000) + 1) sd := genValidStakingScriptData(t, r) minStakingValue := 5000 @@ -102,11 +103,11 @@ func FuzzGeneratingValidStakingSlashingTx(f *testing.F) { } // Always check case with min fee - testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, int64(minFee)) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, int64(minFee), sd.StakerKey, slashingLockTime) // Check case with some random fee fee := int64(r.Intn(1000) + minFee) - testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, fee) + testSlashingTx(r, t, stakingTx, stakingOutputIdx, slashingRate, fee, sd.StakerKey, slashingLockTime) }) } @@ -115,21 +116,29 @@ func genRandomBTCAddress(r *rand.Rand) (*btcutil.AddressPubKeyHash, error) { return btcutil.NewAddressPubKeyHash(datagen.GenRandomByteArray(r, 20), &chaincfg.MainNetParams) } -func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOutputIdx int, slashingRate sdkmath.LegacyDec, fee int64) { +func testSlashingTx( + r *rand.Rand, + t *testing.T, + stakingTx *wire.MsgTx, + stakingOutputIdx int, + slashingRate sdkmath.LegacyDec, + fee int64, + stakerPk *btcec.PublicKey, + slashingChangeLockTime uint16, +) { dustThreshold := 546 // in satoshis // Generate random slashing and change addresses slashingAddress, err := genRandomBTCAddress(r) require.NoError(t, err) - changeAddress, err := genRandomBTCAddress(r) - require.NoError(t, err) - // Construct slashing transaction using the provided parameters slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( stakingTx, uint32(stakingOutputIdx), - slashingAddress, changeAddress, + slashingAddress, + stakerPk, + slashingChangeLockTime, fee, slashingRate, &chaincfg.MainNetParams, @@ -165,6 +174,8 @@ func testSlashingTx(r *rand.Rand, t *testing.T, stakingTx *wire.MsgTx, stakingOu fee, slashingRate, slashingAddress, + stakerPk, + slashingChangeLockTime, &chaincfg.MainNetParams, ) require.NoError(t, err) diff --git a/btcstaking/types.go b/btcstaking/types.go index 2d421203f..9ef508069 100644 --- a/btcstaking/types.go +++ b/btcstaking/types.go @@ -489,3 +489,69 @@ func IsSlashingRateValid(slashingRate sdkmath.LegacyDec) bool { // Check if the truncated rate is equal to the original rate return multipliedRate.Equal(truncatedRate) } + +type RelativeTimeLockTapScriptInfo struct { + // data necessary to build witness for given script + SpendInfo *SpendInfo + // lock time in script, required to set proper sequence number when spending output + // with relative time lock + LockTime uint16 + // taproot address of the script + TapAddress btcutil.Address + // pkscript in output wchich commits to the given script/leaf + PkScript []byte +} + +func BuildRelativeTimelockTaprootScript( + pk *btcec.PublicKey, + lockTime uint16, + net *chaincfg.Params, +) (*RelativeTimeLockTapScriptInfo, error) { + unspendableKeyPathKey := unspendableKeyPathInternalPubKey() + + script, err := buildTimeLockScript(pk, lockTime) + + if err != nil { + return nil, err + } + + sh, err := newTaprootScriptHolder( + &unspendableKeyPathKey, + [][]byte{script}, + ) + + if err != nil { + return nil, err + } + + // there is only one script path in tree, so we can use index 0 + proof := sh.scriptTree.LeafMerkleProofs[0] + + spendInfo := &SpendInfo{ + ControlBlock: proof.ToControlBlock(&unspendableKeyPathKey), + RevealedLeaf: proof.TapLeaf, + } + + taprootAddress, err := DeriveTaprootAddress( + sh.scriptTree, + &unspendableKeyPathKey, + net, + ) + + if err != nil { + return nil, err + } + + taprootPkScript, err := txscript.PayToAddrScript(taprootAddress) + + if err != nil { + return nil, err + } + + return &RelativeTimeLockTapScriptInfo{ + SpendInfo: spendInfo, + LockTime: lockTime, + TapAddress: taprootAddress, + PkScript: taprootPkScript, + }, nil +} diff --git a/proto/babylon/btcstaking/v1/btcstaking.proto b/proto/babylon/btcstaking/v1/btcstaking.proto index f2331d600..eff93e884 100644 --- a/proto/babylon/btcstaking/v1/btcstaking.proto +++ b/proto/babylon/btcstaking/v1/btcstaking.proto @@ -93,11 +93,14 @@ message BTCDelegation { // by each covenant member // It will be a part of the witness for the staking tx output. repeated CovenantAdaptorSignatures covenant_sigs = 12; + // unbonding_time describes how long the funds will be locked either in unbonding output + // or slashing change output + uint32 unbonding_time = 13; // if this object is present it means that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object - BTCUndelegation btc_undelegation = 13; + BTCUndelegation btc_undelegation = 14; } // BTCUndelegation signalizes that the delegation is being undelegated @@ -106,30 +109,28 @@ message BTCUndelegation { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. bytes unbonding_tx = 1; - // unbonding_time describes how long the funds will be locked in the unbonding output - uint32 unbonding_time = 2; // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // finality provider or covenant yet. - bytes slashing_tx = 3 [ (gogoproto.customtype) = "BTCSlashingTx" ]; + bytes slashing_tx = 2 [ (gogoproto.customtype) = "BTCSlashingTx" ]; // delegator_unbonding_sig is the signature on the unbonding tx // by the delegator (i.e., SK corresponding to btc_pk). // It effectively proves that the delegator wants to unbond and thus // Babylon will consider this BTC delegation unbonded. Delegator's BTC // on Bitcoin will be unbonded after timelock - bytes delegator_unbonding_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_unbonding_sig = 3 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbonding tx output. - bytes delegator_slashing_sig = 5 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; + bytes delegator_slashing_sig = 4 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx // by each covenant member // It will be a part of the witness for the staking tx output. - repeated CovenantAdaptorSignatures covenant_slashing_sigs = 6; + repeated CovenantAdaptorSignatures covenant_slashing_sigs = 5; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon - repeated SignatureInfo covenant_unbonding_sig_list = 7; + repeated SignatureInfo covenant_unbonding_sig_list = 6; } @@ -139,15 +140,13 @@ message BTCUndelegationInfo { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. bytes unbonding_tx = 1; - // unbonding_time describes how long the funds will be locked in the unbonding output - uint32 unbonding_time = 2; // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - repeated SignatureInfo covenant_unbonding_sig_list = 3; - // covenant_slashing_sigs is a list of adaptor signatures on the + repeated SignatureInfo covenant_unbonding_sig_list = 2; + // covenant_slashing_sigs is a list of adaptor signatures on the // unbonding slashing tx by each covenant member // It will be a part of the witness for the staking tx output. - repeated CovenantAdaptorSignatures covenant_slashing_sigs = 4; + repeated CovenantAdaptorSignatures covenant_slashing_sigs = 3; } // BTCDelegatorDelegations is a collection of BTC delegations from the same delegator. @@ -192,10 +191,10 @@ message CovenantAdaptorSignatures { repeated bytes adaptor_sigs = 2; } -// SelectiveSlashingEvidence is the evidence that the finality provider +// SelectiveSlashingEvidence is the evidence that the finality provider // selectively slashed a BTC delegation -// NOTE: it's possible that a slashed finality provider exploits the -// SelectiveSlashingEvidence endpoint while it is actually slashed due to +// NOTE: it's possible that a slashed finality provider exploits the +// SelectiveSlashingEvidence endpoint while it is actually slashed due to // equivocation. But such behaviour does not affect the system's security // or gives any benefit for the adversary message SelectiveSlashingEvidence { @@ -209,4 +208,4 @@ message SelectiveSlashingEvidence { // the covenant adaptor/Schnorr signature pair. It is the consequence // of selective slashing. bytes recovered_fp_btc_sk = 3; - } \ No newline at end of file + } diff --git a/proto/babylon/btcstaking/v1/query.proto b/proto/babylon/btcstaking/v1/query.proto index e2adcfdda..640258bea 100644 --- a/proto/babylon/btcstaking/v1/query.proto +++ b/proto/babylon/btcstaking/v1/query.proto @@ -242,7 +242,9 @@ message QueryBTCDelegationResponse { repeated CovenantAdaptorSignatures covenant_sigs = 7; // whether this delegation is active bool active = 8; - // undelegation_info is the undelegation info of this delegation. It is nil - // if it is not undelegating - BTCUndelegationInfo undelegation_info = 9; + // unbonding_time used in unbonding output timelock path and in slashing transactions + // change outputs + uint32 unbonding_time = 9; + // undelegation_info is the undelegation info of this delegation. + BTCUndelegationInfo undelegation_info = 10; } diff --git a/proto/babylon/btcstaking/v1/tx.proto b/proto/babylon/btcstaking/v1/tx.proto index c55b37c1a..05bc22218 100644 --- a/proto/babylon/btcstaking/v1/tx.proto +++ b/proto/babylon/btcstaking/v1/tx.proto @@ -84,13 +84,16 @@ message MsgCreateBTCDelegation { // The staking tx output further needs signatures from covenant and finality provider in // order to be spendable. bytes delegator_slashing_sig = 10 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340Signature" ]; - - /// fields related to on-demand unbonding + // unbonding_time is the time lock used when funds are being unbonded. It is be used in: + // - unbonding transaction, time lock spending path + // - staking slashing transaction, change output + // - unbonding slashing transaction, change output + // It must be smaller than math.MaxUInt16 and larger that max(MinUnbondingTime, CheckpointFinalizationTimeout) + uint32 unbonding_time = 11; + // fields related to unbonding transaction // unbonding_tx is a bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output - bytes unbonding_tx = 11; - // unbonding_time is the time lock used in unbonding transaction - uint32 unbonding_time = 12; + bytes unbonding_tx = 12; // unbonding_value is amount of satoshis locked in unbonding output. // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding int64 unbonding_value = 13; @@ -130,7 +133,7 @@ message MsgAddCovenantSigs { message MsgAddCovenantSigsResponse {} // MsgBTCUndelegate is the message for handling signature on unbonding tx -// from its delegator. This signature effectively proves that the delegator +// from its delegator. This signature effectively proves that the delegator // wants to unbond this BTC delegation message MsgBTCUndelegate { option (cosmos.msg.v1.signer) = "signer"; diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index 23fd83d2c..d2c97369a 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -37,8 +37,6 @@ var ( covenantSKs, _, covenantQuorum = bstypes.DefaultCovenantCommittee() stakingValue = int64(2 * 10e8) - - changeAddress, _ = datagen.GenRandomBTCAddress(r, net) ) type BTCStakingTestSuite struct { @@ -97,6 +95,10 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { */ // BTC staking params, BTC delegation key pairs and PoP params := nonValidatorNode.QueryBTCStakingParams() + + // minimal required unbonding time + unbondingTime := uint16(initialization.BabylonBtcFinalizationPeriod) + 1 + // get covenant BTC PKs covenantBTCPKs := []*btcec.PublicKey{} for _, covenantPK := range params.CovenantPks { @@ -118,8 +120,9 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { covenantQuorum, stakingTimeBlocks, stakingValue, - params.SlashingAddress, changeAddress.EncodeAddress(), + params.SlashingAddress, params.SlashingRate, + unbondingTime, ) stakingMsgTx := testStakingInfo.StakingTx @@ -149,7 +152,6 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { // generate BTC undelegation stuff stkTxHash := testStakingInfo.StakingTx.TxHash() - unbondingTime := initialization.BabylonBtcFinalizationPeriod + 1 unbondingValue := stakingValue - datagen.UnbondingTxFee // TODO: parameterise fee testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( r, @@ -160,10 +162,11 @@ func (s *BTCStakingTestSuite) Test1CreateFinalityProviderAndDelegation() { covenantBTCPKs, covenantQuorum, wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), - uint16(unbondingTime), + unbondingTime, unbondingValue, - params.SlashingAddress, changeAddress.EncodeAddress(), + params.SlashingAddress, params.SlashingRate, + unbondingTime, ) delUnbondingSlashingSig, err := testUnbondingInfo.GenDelSlashingTxSig(delBTCSK) s.NoError(err) diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index 4cd1487f8..b70e6b7f2 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -79,9 +79,10 @@ func GenRandomBTCDelegation( delSK *btcec.PrivateKey, covenantSKs []*btcec.PrivateKey, covenantQuorum uint32, - slashingAddress, changeAddress string, + slashingAddress string, startHeight, endHeight, totalSat uint64, slashingRate sdkmath.LegacyDec, + slashingChangeLockTime uint16, ) (*bstypes.BTCDelegation, error) { net := &chaincfg.SimNetParams delPK := delSK.PubKey() @@ -126,8 +127,9 @@ func GenRandomBTCDelegation( covenantQuorum, uint16(endHeight-startHeight), int64(totalSat), - slashingAddress, changeAddress, + slashingAddress, slashingRate, + slashingChangeLockTime, ) slashingPathSpendInfo, err := stakingSlashingInfo.StakingInfo.SlashingPathSpendInfo() @@ -156,7 +158,7 @@ func GenRandomBTCDelegation( serializedStakingTx, err := bbn.SerializeBTCTx(stakingSlashingInfo.StakingTx) require.NoError(t, err) - + w := uint16(100) // TODO: parameterise w del := &bstypes.BTCDelegation{ BabylonPk: secp256k1PK, BtcPk: delBTCPK, @@ -168,6 +170,7 @@ func GenRandomBTCDelegation( StakingOutputIdx: StakingOutIdx, DelegatorSig: delegatorSig, CovenantSigs: covenantSigs, + UnbondingTime: uint32(w + 1), StakingTx: serializedStakingTx, SlashingTx: stakingSlashingInfo.SlashingTx, } @@ -179,7 +182,7 @@ func GenRandomBTCDelegation( // construct unbonding info stkTxHash := stakingSlashingInfo.StakingTx.TxHash() unbondingValue := totalSat - uint64(UnbondingTxFee) - w := uint16(100) // TODO: parameterise w + unbondingSlashingInfo := GenBTCUnbondingSlashingInfo( r, t, @@ -191,8 +194,9 @@ func GenRandomBTCDelegation( wire.NewOutPoint(&stkTxHash, StakingOutIdx), w+1, int64(unbondingValue), - slashingAddress, changeAddress, + slashingAddress, slashingRate, + slashingChangeLockTime, ) unbondingTxBytes, err := bbn.SerializeBTCTx(unbondingSlashingInfo.UnbondingTx) @@ -201,7 +205,6 @@ func GenRandomBTCDelegation( require.NoError(t, err) del.BtcUndelegation = &bstypes.BTCUndelegation{ UnbondingTx: unbondingTxBytes, - UnbondingTime: uint32(w + 1), SlashingTx: unbondingSlashingInfo.SlashingTx, DelegatorSlashingSig: delSlashingTxSig, } @@ -250,8 +253,9 @@ func GenBTCStakingSlashingInfoWithOutPoint( covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress, changeAddress string, + slashingAddress string, slashingRate sdkmath.LegacyDec, + slashingChangeLockTime uint16, ) *TestStakingSlashingInfo { stakingInfo, err := btcstaking.BuildStakingInfo( @@ -281,12 +285,13 @@ func GenBTCStakingSlashingInfoWithOutPoint( // construct slashing tx slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) require.NoError(t, err) - changeAddrBtc, err := btcutil.DecodeAddress(changeAddress, btcNet) - require.NoError(t, err) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, StakingOutIdx, - slashingAddrBtc, changeAddrBtc, + slashingAddrBtc, + stakerSK.PubKey(), + slashingChangeLockTime, 2000, slashingRate, btcNet) @@ -311,8 +316,9 @@ func GenBTCStakingSlashingInfo( covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress, changeAddress string, + slashingAddress string, slashingRate sdkmath.LegacyDec, + slashingChangeLockTime uint16, ) *TestStakingSlashingInfo { // an arbitrary input spend := makeSpendableOutWithRandOutPoint(r, btcutil.Amount(stakingValue+UnbondingTxFee)) @@ -328,8 +334,10 @@ func GenBTCStakingSlashingInfo( covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress, changeAddress, - slashingRate) + slashingAddress, + slashingRate, + slashingChangeLockTime, + ) } func GenBTCUnbondingSlashingInfo( @@ -343,8 +351,9 @@ func GenBTCUnbondingSlashingInfo( stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress, changeAddress string, + slashingAddress string, slashingRate sdkmath.LegacyDec, + slashingChangeLockTime uint16, ) *TestUnbondingSlashingInfo { unbondingInfo, err := btcstaking.BuildUnbondingInfo( @@ -367,12 +376,13 @@ func GenBTCUnbondingSlashingInfo( // construct slashing tx slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) require.NoError(t, err) - changeAddrBtc, err := btcutil.DecodeAddress(changeAddress, btcNet) - require.NoError(t, err) + slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, StakingOutIdx, - slashingAddrBtc, changeAddrBtc, + slashingAddrBtc, + stakerSK.PubKey(), + slashingChangeLockTime, 2000, slashingRate, btcNet) diff --git a/testutil/keeper/btcstaking.go b/testutil/keeper/btcstaking.go index f4f0196b9..6457a1125 100644 --- a/testutil/keeper/btcstaking.go +++ b/testutil/keeper/btcstaking.go @@ -23,7 +23,11 @@ import ( "github.com/stretchr/testify/require" ) -func BTCStakingKeeper(t testing.TB, btclcKeeper types.BTCLightClientKeeper, btccKeeper types.BtcCheckpointKeeper) (*keeper.Keeper, sdk.Context) { +func BTCStakingKeeper( + t testing.TB, + btclcKeeper types.BTCLightClientKeeper, + btccKeeper types.BtcCheckpointKeeper, +) (*keeper.Keeper, sdk.Context) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index f89bdc81f..21251838d 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -277,7 +277,6 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation // get its undelegation info undelegationInfo := &types.BTCUndelegationInfo{ UnbondingTx: btcDel.BtcUndelegation.UnbondingTx, - UnbondingTime: btcDel.BtcUndelegation.UnbondingTime, CovenantUnbondingSigList: btcDel.BtcUndelegation.CovenantUnbondingSigList, CovenantSlashingSigs: btcDel.BtcUndelegation.CovenantSlashingSigs, } @@ -291,6 +290,7 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation StakingTxHex: hex.EncodeToString(btcDel.StakingTx), CovenantSigs: btcDel.CovenantSigs, Active: isActive, + UnbondingTime: btcDel.UnbondingTime, UndelegationInfo: undelegationInfo, }, nil } diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index 97a1641b2..4d9534de4 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -179,8 +179,8 @@ func FuzzPendingBTCDelegations(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -216,9 +216,10 @@ func FuzzPendingBTCDelegations(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) if datagen.RandomInt(r, 2) == 1 { @@ -351,8 +352,9 @@ func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -390,9 +392,10 @@ func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, 10000, slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) @@ -466,8 +469,8 @@ func FuzzFinalityProviderDelegations(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -495,9 +498,10 @@ func FuzzFinalityProviderDelegations(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), startHeight, endHeight, 10000, slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) expectedBtcDelsMap[btcDel.BtcPk.MarshalHex()] = btcDel diff --git a/x/btcstaking/keeper/incentive_test.go b/x/btcstaking/keeper/incentive_test.go index 836cf4162..30b0a4b4d 100644 --- a/x/btcstaking/keeper/incentive_test.go +++ b/x/btcstaking/keeper/incentive_test.go @@ -35,8 +35,8 @@ func FuzzRecordRewardDistCache(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -72,9 +72,10 @@ func FuzzRecordRewardDistCache(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 07bcfa398..cab70c52a 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -106,6 +106,21 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre btccParams := ms.btccKeeper.GetParams(ctx) kValue, wValue := btccParams.BtcConfirmationDepth, btccParams.CheckpointFinalizationTimeout + minUnbondingTime := types.MinimumUnbondingTime(params, btccParams) + + // Check unbonding time (staking time from unbonding tx) is larger than min unbonding time + // which is larger value from: + // - MinUnbondingTime + // - CheckpointFinalizationTimeout + if uint64(req.UnbondingTime) <= minUnbondingTime { + return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than %d", req.UnbondingTime, minUnbondingTime) + } + + // At this point we know that unbonding time in request: + // - is larger than min unbonding time + // - is smaller than math.MaxUint16 (due to check in req.ValidateBasic()) + validatedUnbondingTime := uint16(req.UnbondingTime) + // verify proof of possession if err := req.Pop.Verify(req.BabylonPk, req.BtcPk, ms.btcNet); err != nil { return nil, types.ErrInvalidProofOfPossession.Wrapf("error while validating proof of posession: %v", err) @@ -141,9 +156,10 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre // programming error panic("failed to parse covenant PKs in KVStore") } + stakerPk := req.BtcPk.MustToBTCPK() stakingInfo, err := btcstaking.BuildStakingInfo( - req.BtcPk.MustToBTCPK(), + stakerPk, fpPKs, covenantPKs, params.CovenantQuorum, @@ -206,6 +222,8 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre params.MinSlashingTxFeeSat, params.SlashingRate, slashingAddr, + stakerPk, + validatedUnbondingTime, ms.btcNet, ); err != nil { return nil, types.ErrInvalidStakingTx.Wrap(err.Error()) @@ -221,7 +239,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre stakingInfo.StakingOutput.PkScript, stakingInfo.StakingOutput.Value, slashingSpendInfo.GetPkScriptPath(), - req.BtcPk.MustToBTCPK(), + stakerPk, req.DelegatorSlashingSig, ) if err != nil { @@ -244,6 +262,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre StakingOutputIdx: stakingOutputIdx, SlashingTx: req.SlashingTx, DelegatorSig: req.DelegatorSlashingSig, + UnbondingTime: uint32(validatedUnbondingTime), CovenantSigs: nil, // NOTE: covenant signature will be submitted in a separate msg by covenant BtcUndelegation: nil, // this will be constructed in below code } @@ -270,15 +289,6 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre if unbondingMsgTx.TxIn[0].PreviousOutPoint.Index != stakingOutputIdx { return nil, types.ErrInvalidUnbondingTx.Wrapf("slashing transaction input must spend staking output") } - minUnbondingTime := types.MinimumUnbondingTime(params, btccParams) - - // Check unbonding time (staking time from unbonding tx) is larger than min unbonding time - // which is larger value from: - // - MinUnbondingTime - // - CheckpointFinalizationTimeout - if uint64(req.UnbondingTime) <= minUnbondingTime { - return nil, types.ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be larger than %d", req.UnbondingTime, minUnbondingTime) - } // building unbonding info unbondingInfo, err := btcstaking.BuildUnbondingInfo( @@ -286,7 +296,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre fpPKs, covenantPKs, params.CovenantQuorum, - uint16(req.UnbondingTime), + validatedUnbondingTime, btcutil.Amount(req.UnbondingValue), ms.btcNet, ) @@ -308,6 +318,8 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre params.MinSlashingTxFeeSat, params.SlashingRate, params.MustGetSlashingAddress(ms.btcNet), + stakerPk, + validatedUnbondingTime, ms.btcNet, ) if err != nil { @@ -353,7 +365,6 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre DelegatorUnbondingSig: nil, CovenantSlashingSigs: nil, CovenantUnbondingSigList: nil, - UnbondingTime: req.UnbondingTime, } if err := ms.AddBTCDelegation(ctx, newBTCDel); err != nil { diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 96c9bead6..f739dccd6 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -58,9 +58,21 @@ func (h *Helper) NoError(err error) { } func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec.PublicKey) { + return h.GenAndApplyCustomParams(r, 100, 0) +} + +func (h *Helper) GenAndApplyCustomParams( + r *rand.Rand, + finalizationTimeout uint64, + minUnbondingTime uint32, +) ([]*btcec.PrivateKey, []*btcec.PublicKey) { // mocking stuff for BTC checkpoint keeper h.BTCCheckpointKeeper.EXPECT().GetPowLimit().Return(h.Net.PowLimit).AnyTimes() - h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(btcctypes.DefaultParams()).AnyTimes() + + params := btcctypes.DefaultParams() + params.CheckpointFinalizationTimeout = finalizationTimeout + + h.BTCCheckpointKeeper.EXPECT().GetParams(gomock.Any()).Return(params).AnyTimes() // randomise covenant committee covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) @@ -75,7 +87,7 @@ func (h *Helper) GenAndApplyParams(r *rand.Rand) ([]*btcec.PrivateKey, []*btcec. MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveFinalityProviders: 100, - MinUnbondingTime: 0, + MinUnbondingTime: minUnbondingTime, }) h.NoError(err) return covenantSKs, covenantPKs @@ -99,19 +111,21 @@ func (h *Helper) CreateFinalityProvider(r *rand.Rand) (*btcec.PrivateKey, *btcec return fpSK, fpPK, fp } -func (h *Helper) CreateDelegation( +func (h *Helper) CreateDelegationCustom( r *rand.Rand, fpPK *btcec.PublicKey, changeAddress string, stakingValue int64, stakingTime uint16, -) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { + unbondingTime uint16, +) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation, error) { delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) h.NoError(err) stakingTimeBlocks := stakingTime bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) h.NoError(err) + testStakingInfo := datagen.GenBTCStakingSlashingInfo( r, h.t, @@ -123,8 +137,8 @@ func (h *Helper) CreateDelegation( stakingTimeBlocks, stakingValue, bsParams.SlashingAddress, - changeAddress, bsParams.SlashingRate, + unbondingTime, ) h.NoError(err) stakingTxHash := testStakingInfo.StakingTx.TxHash().String() @@ -170,9 +184,7 @@ func (h *Helper) CreateDelegation( */ stkTxHash := testStakingInfo.StakingTx.TxHash() stkOutputIdx := uint32(0) - defaultParams := btcctypes.DefaultParams() - unbondingTime := uint16(defaultParams.CheckpointFinalizationTimeout) + 1 unbondingValue := stakingValue - 1000 testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( r, @@ -186,8 +198,8 @@ func (h *Helper) CreateDelegation( unbondingTime, unbondingValue, bsParams.SlashingAddress, - changeAddress, bsParams.SlashingRate, + unbondingTime, ) h.NoError(err) @@ -217,7 +229,40 @@ func (h *Helper) CreateDelegation( } _, err = h.MsgServer.CreateBTCDelegation(h.Ctx, msgCreateBTCDel) + + if err != nil { + return "", nil, nil, nil, err + } + + return stakingTxHash, delSK, delPK, msgCreateBTCDel, nil +} + +func (h *Helper) CreateDelegation( + r *rand.Rand, + fpPK *btcec.PublicKey, + changeAddress string, + stakingValue int64, + stakingTime uint16, +) (string, *btcec.PrivateKey, *btcec.PublicKey, *types.MsgCreateBTCDelegation) { + bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + bcParams := h.BTCCheckpointKeeper.GetParams(h.Ctx) + + minUnbondingTime := types.MinimumUnbondingTime( + bsParams, + bcParams, + ) + + stakingTxHash, delSK, delPK, msgCreateBTCDel, err := h.CreateDelegationCustom( + r, + fpPK, + changeAddress, + stakingValue, + stakingTime, + uint16(minUnbondingTime)+1, + ) + h.NoError(err) + return stakingTxHash, delSK, delPK, msgCreateBTCDel } @@ -654,9 +699,14 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { // set covenant PK to params _, covenantPKs := h.GenAndApplyParams(r) bsParams := h.BTCStakingKeeper.GetParams(h.Ctx) + bcParams := h.BTCCheckpointKeeper.GetParams(h.Ctx) - changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) - require.NoError(t, err) + minUnbondingTime := types.MinimumUnbondingTime( + bsParams, + bcParams, + ) + + slashingChangeLockTime := uint16(minUnbondingTime) + 1 // We only generate a finality provider, but not insert it into KVStore. So later // insertion of delegation should fail. @@ -681,8 +731,8 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { stakingTimeBlocks, stakingValue, bsParams.SlashingAddress, - changeAddress.EncodeAddress(), bsParams.SlashingRate, + slashingChangeLockTime, ) // get msgTx stakingMsgTx := testStakingInfo.StakingTx @@ -733,8 +783,8 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { uint16(unbondingTime), unbondingValue, bsParams.SlashingAddress, - changeAddress.EncodeAddress(), bsParams.SlashingRate, + slashingChangeLockTime, ) unbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) h.NoError(err) @@ -763,3 +813,85 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { require.Error(t, err) require.True(t, errors.Is(err, types.ErrFpNotFound)) } + +func TestCorrectUnbondingTimeInDelegation(t *testing.T) { + tests := []struct { + name string + finalizationTimeout uint64 + minUnbondingTime uint32 + unbondingTimeInDelegation uint16 + err error + }{ + { + name: "successful delegation when ubonding time in delegation is larger than finalization timeout when finalization timeout is larger than min unbonding time", + unbondingTimeInDelegation: 101, + minUnbondingTime: 99, + finalizationTimeout: 100, + err: nil, + }, + { + name: "failed delegation when ubonding time in delegation is not larger than finalization time when min unbonding time is lower than finalization timeout", + unbondingTimeInDelegation: 100, + minUnbondingTime: 99, + finalizationTimeout: 100, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "successful delegation when ubonding time ubonding time in delegation is larger than min unbonding time when min unbonding time is larger than finalization timeout", + unbondingTimeInDelegation: 151, + minUnbondingTime: 150, + finalizationTimeout: 100, + err: nil, + }, + { + name: "failed delegation when ubonding time in delegation is not larger than minUnbondingTime when min unbonding time is larger than finalization timeout", + unbondingTimeInDelegation: 150, + minUnbondingTime: 150, + finalizationTimeout: 100, + err: types.ErrInvalidUnbondingTx, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + _, _ = h.GenAndApplyCustomParams(r, tt.finalizationTimeout, tt.minUnbondingTime) + + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new finality provider + _, fpPK, _ := h.CreateFinalityProvider(r) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + stakingTxHash, _, _, _, err := h.CreateDelegationCustom( + r, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + tt.unbondingTimeInDelegation, + ) + if tt.err != nil { + require.Error(t, err) + require.True(t, errors.Is(err, tt.err)) + } else { + require.NoError(t, err) + // Retrieve delegation from keeper + delegation, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + require.NoError(t, err) + require.Equal(t, tt.unbondingTimeInDelegation, uint16(delegation.UnbondingTime)) + } + }) + } +} diff --git a/x/btcstaking/keeper/voting_power_table_test.go b/x/btcstaking/keeper/voting_power_table_test.go index 3edc038eb..c2802f7c8 100644 --- a/x/btcstaking/keeper/voting_power_table_test.go +++ b/x/btcstaking/keeper/voting_power_table_test.go @@ -35,8 +35,7 @@ func FuzzVotingPowerTable(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -69,9 +68,10 @@ func FuzzVotingPowerTable(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, stakingValue, slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) @@ -203,8 +203,8 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -233,9 +233,10 @@ func FuzzVotingPowerTable_ActiveFinalityProviders(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) @@ -306,8 +307,8 @@ func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { covenantSKs, _, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) - require.NoError(t, err) + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -336,9 +337,10 @@ func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) @@ -384,9 +386,10 @@ func FuzzVotingPowerTable_ActiveFinalityProviderRotation(f *testing.F) { delSK, covenantSKs, covenantQuorum, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), 1, 1000, stakingValue, // timelock period: 1-1000 slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) err = keeper.AddBTCDelegation(ctx, btcDel) diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index af330d991..53e54ad79 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -300,7 +300,7 @@ func (d *BTCDelegation) GetUnbondingInfo(bsParams *Params, btcNet *chaincfg.Para fpBtcPkList, covenantBtcPkList, bsParams.CovenantQuorum, - uint16(d.BtcUndelegation.GetUnbondingTime()), + uint16(d.GetUnbondingTime()), btcutil.Amount(unbondingTx.TxOut[0].Value), btcNet, ) diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index ff2c6e0d1..92ee43742 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -91,8 +91,9 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { stakingValue := int64(2 * 10e8) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) + + slashingChangeLockTime := uint16(101) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -109,8 +110,9 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) diff --git a/x/btcstaking/types/btc_slashing_tx.go b/x/btcstaking/types/btc_slashing_tx.go index 922603857..aa3aa730c 100644 --- a/x/btcstaking/types/btc_slashing_tx.go +++ b/x/btcstaking/types/btc_slashing_tx.go @@ -5,11 +5,8 @@ import ( "encoding/hex" "fmt" - sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -97,28 +94,6 @@ func (tx *BTCSlashingTx) MustGetTxHash() *chainhash.Hash { return &txHash } -func (tx *BTCSlashingTx) Validate( - net *chaincfg.Params, - slashingAddress string, - slashingRate sdkmath.LegacyDec, - slashingTxMinFee, stakingOutputValue int64, -) error { - msgTx, err := tx.ToMsgTx() - if err != nil { - return err - } - decodedAddr, err := btcutil.DecodeAddress(slashingAddress, net) - if err != nil { - return err - } - return btcstaking.ValidateSlashingTx( - msgTx, - decodedAddr, - slashingRate, - slashingTxMinFee, stakingOutputValue, - ) -} - // Sign generates a signature on the slashing tx func (tx *BTCSlashingTx) Sign( fundingTx *wire.MsgTx, diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index e5af93185..292613426 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -28,8 +28,6 @@ func FuzzSlashingTxWithWitness(f *testing.F) { // slashing address and key paris slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -47,6 +45,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { covenantSKs, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) require.NoError(t, err) covenantQuorum := uint32(3) + slashingChangeLockTime := uint16(101) // generate staking/slashing tx testStakingInfo := datagen.GenBTCStakingSlashingInfo( @@ -59,8 +58,9 @@ func FuzzSlashingTxWithWitness(f *testing.F) { covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress.EncodeAddress(), changeAddress.EncodeAddress(), + slashingAddress.EncodeAddress(), slashingRate, + slashingChangeLockTime, ) slashingTx := testStakingInfo.SlashingTx diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go index df3063fae..8b62027ed 100644 --- a/x/btcstaking/types/btc_undelegation_test.go +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -38,10 +38,11 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { stakingValue := int64(2 * 10e8) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) - changeAddress, err := datagen.GenRandomBTCAddress(r, net) - require.NoError(t, err) slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) + unbondingTime := uint16(100) + 1 + unbondingValue := stakingValue - 1000 + slashingChangeLockTime := unbondingTime // construct the BTC delegation with everything btcDel, err := datagen.GenRandomBTCDelegation( @@ -52,17 +53,15 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { covenantSKs, covenantQuorum, slashingAddress.EncodeAddress(), - changeAddress.EncodeAddress(), 1000, uint64(1000+stakingTimeBlocks), uint64(stakingValue), slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) stakingTxHash := btcDel.MustGetStakingTxHash() - unbondingTime := uint16(100) + 1 - unbondingValue := stakingValue - 1000 testInfo := datagen.GenBTCUnbondingSlashingInfo( r, @@ -76,8 +75,8 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { unbondingTime, unbondingValue, slashingAddress.EncodeAddress(), - changeAddress.EncodeAddress(), slashingRate, + slashingChangeLockTime, ) require.NoError(t, err) @@ -100,10 +99,8 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { testInfo.SlashingTx, ) require.NoError(t, err) - btcDel.BtcUndelegation = &types.BTCUndelegation{ UnbondingTx: unbondingTxBytes, - UnbondingTime: 100 + 1, SlashingTx: testInfo.SlashingTx, DelegatorUnbondingSig: nil, // not relevant here DelegatorSlashingSig: delSlashingTxSig, diff --git a/x/btcstaking/types/btcstaking.pb.go b/x/btcstaking/types/btcstaking.pb.go index 8f264d9ad..dd9618b47 100644 --- a/x/btcstaking/types/btcstaking.pb.go +++ b/x/btcstaking/types/btcstaking.pb.go @@ -279,11 +279,14 @@ type BTCDelegation struct { // by each covenant member // It will be a part of the witness for the staking tx output. CovenantSigs []*CovenantAdaptorSignatures `protobuf:"bytes,12,rep,name=covenant_sigs,json=covenantSigs,proto3" json:"covenant_sigs,omitempty"` + // unbonding_time describes how long the funds will be locked either in unbonding output + // or slashing change output + UnbondingTime uint32 `protobuf:"varint,13,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // if this object is present it means that staker requested undelegation, and whole // delegation is being undelegated. // TODO: Consider whether it would be better to store it in separate store, and not // directly in delegation object - BtcUndelegation *BTCUndelegation `protobuf:"bytes,13,opt,name=btc_undelegation,json=btcUndelegation,proto3" json:"btc_undelegation,omitempty"` + BtcUndelegation *BTCUndelegation `protobuf:"bytes,14,opt,name=btc_undelegation,json=btcUndelegation,proto3" json:"btc_undelegation,omitempty"` } func (m *BTCDelegation) Reset() { *m = BTCDelegation{} } @@ -375,6 +378,13 @@ func (m *BTCDelegation) GetCovenantSigs() []*CovenantAdaptorSignatures { return nil } +func (m *BTCDelegation) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + func (m *BTCDelegation) GetBtcUndelegation() *BTCUndelegation { if m != nil { return m.BtcUndelegation @@ -388,30 +398,28 @@ type BTCUndelegation struct { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // unbonding_time describes how long the funds will be locked in the unbonding output - UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // slashing_tx is the slashing tx for unbonding transactions // It is partially signed by SK corresponding to btc_pk, but not signed by // finality provider or covenant yet. - SlashingTx *BTCSlashingTx `protobuf:"bytes,3,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` + SlashingTx *BTCSlashingTx `protobuf:"bytes,2,opt,name=slashing_tx,json=slashingTx,proto3,customtype=BTCSlashingTx" json:"slashing_tx,omitempty"` // delegator_unbonding_sig is the signature on the unbonding tx // by the delegator (i.e., SK corresponding to btc_pk). // It effectively proves that the delegator wants to unbond and thus // Babylon will consider this BTC delegation unbonded. Delegator's BTC // on Bitcoin will be unbonded after timelock - DelegatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_unbonding_sig,json=delegatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_unbonding_sig,omitempty"` + DelegatorUnbondingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,3,opt,name=delegator_unbonding_sig,json=delegatorUnbondingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_unbonding_sig,omitempty"` // delegator_slashing_sig is the signature on the slashing tx // by the delegator (i.e., SK corresponding to btc_pk). // It will be a part of the witness for the unbonding tx output. - DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,5,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` + DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,4,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` // covenant_slashing_sigs is a list of adaptor signatures on the slashing tx // by each covenant member // It will be a part of the witness for the staking tx output. - CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,6,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` + CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,5,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members // It must be provided after processing undelagate message by Babylon - CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,7,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,6,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` } func (m *BTCUndelegation) Reset() { *m = BTCUndelegation{} } @@ -454,13 +462,6 @@ func (m *BTCUndelegation) GetUnbondingTx() []byte { return nil } -func (m *BTCUndelegation) GetUnbondingTime() uint32 { - if m != nil { - return m.UnbondingTime - } - return 0 -} - func (m *BTCUndelegation) GetCovenantSlashingSigs() []*CovenantAdaptorSignatures { if m != nil { return m.CovenantSlashingSigs @@ -481,15 +482,13 @@ type BTCUndelegationInfo struct { // output to unbonding output. Unbonding output will usually have lower timelock // than staking output. UnbondingTx []byte `protobuf:"bytes,1,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // unbonding_time describes how long the funds will be locked in the unbonding output - UnbondingTime uint32 `protobuf:"varint,2,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` // covenant_unbonding_sig_list is the list of signatures on the unbonding tx // by covenant members - CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,3,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` + CovenantUnbondingSigList []*SignatureInfo `protobuf:"bytes,2,rep,name=covenant_unbonding_sig_list,json=covenantUnbondingSigList,proto3" json:"covenant_unbonding_sig_list,omitempty"` // covenant_slashing_sigs is a list of adaptor signatures on the // unbonding slashing tx by each covenant member // It will be a part of the witness for the staking tx output. - CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,4,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` + CovenantSlashingSigs []*CovenantAdaptorSignatures `protobuf:"bytes,3,rep,name=covenant_slashing_sigs,json=covenantSlashingSigs,proto3" json:"covenant_slashing_sigs,omitempty"` } func (m *BTCUndelegationInfo) Reset() { *m = BTCUndelegationInfo{} } @@ -532,13 +531,6 @@ func (m *BTCUndelegationInfo) GetUnbondingTx() []byte { return nil } -func (m *BTCUndelegationInfo) GetUnbondingTime() uint32 { - if m != nil { - return m.UnbondingTime - } - return 0 -} - func (m *BTCUndelegationInfo) GetCovenantUnbondingSigList() []*SignatureInfo { if m != nil { return m.CovenantUnbondingSigList @@ -816,83 +808,83 @@ func init() { } var fileDescriptor_3851ae95ccfaf7db = []byte{ - // 1205 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x5d, 0x6f, 0x1b, 0x45, - 0x17, 0xce, 0xda, 0x8e, 0x53, 0x1f, 0xdb, 0xad, 0x3b, 0x4d, 0xd3, 0x6d, 0xa3, 0x37, 0xc9, 0x6b, - 0x4a, 0x65, 0x21, 0xba, 0x6e, 0xd2, 0x0f, 0x01, 0x17, 0x48, 0x75, 0x9c, 0xd2, 0xa8, 0x6d, 0x6a, - 0xd6, 0x09, 0x08, 0x90, 0x58, 0xad, 0x77, 0xc7, 0xeb, 0x95, 0xed, 0x9d, 0x65, 0x67, 0x6c, 0xec, - 0x7f, 0xc0, 0x0d, 0x12, 0x7f, 0x01, 0x89, 0x4b, 0x2e, 0xf9, 0x01, 0x5c, 0x21, 0x2e, 0x2b, 0x6e, - 0x40, 0xb9, 0x88, 0x50, 0xfb, 0x47, 0xd0, 0xcc, 0xce, 0x7e, 0x38, 0x34, 0x2d, 0xad, 0x7d, 0xe7, - 0x99, 0x73, 0xce, 0x73, 0x3e, 0x9e, 0x67, 0x67, 0xc6, 0x70, 0xa3, 0x63, 0x76, 0xa6, 0x03, 0xe2, - 0xd5, 0x3b, 0xcc, 0xa2, 0xcc, 0xec, 0xbb, 0x9e, 0x53, 0x1f, 0x6f, 0xa7, 0x56, 0x9a, 0x1f, 0x10, - 0x46, 0xd0, 0x65, 0xe9, 0xa7, 0xa5, 0x2c, 0xe3, 0xed, 0x6b, 0xab, 0x0e, 0x71, 0x88, 0xf0, 0xa8, - 0xf3, 0x5f, 0xa1, 0xf3, 0xb5, 0xab, 0x16, 0xa1, 0x43, 0x42, 0x8d, 0xd0, 0x10, 0x2e, 0xa4, 0xa9, - 0x1a, 0xae, 0xea, 0x56, 0x30, 0xf5, 0x19, 0xa9, 0x53, 0x6c, 0xf9, 0x3b, 0x77, 0xef, 0xf5, 0xb7, - 0xeb, 0x7d, 0x3c, 0x8d, 0x7c, 0xae, 0x4b, 0x9f, 0xa4, 0x9e, 0x0e, 0x66, 0xe6, 0x76, 0x7d, 0xa6, - 0xa2, 0x6b, 0x9b, 0x2f, 0xaf, 0xdc, 0x27, 0x7e, 0xe8, 0x50, 0xfd, 0x33, 0x0b, 0x95, 0x07, 0xae, - 0x67, 0x0e, 0x5c, 0x36, 0x6d, 0x05, 0x64, 0xec, 0xda, 0x38, 0x40, 0x7b, 0x50, 0xb4, 0x31, 0xb5, - 0x02, 0xd7, 0x67, 0x2e, 0xf1, 0x54, 0x65, 0x4b, 0xa9, 0x15, 0x77, 0xde, 0xd1, 0x64, 0x8d, 0x49, - 0x67, 0x22, 0xa3, 0xd6, 0x4c, 0x5c, 0xf5, 0x74, 0x1c, 0x7a, 0x02, 0x60, 0x91, 0xe1, 0xd0, 0xa5, - 0x94, 0xa3, 0x64, 0xb6, 0x94, 0x5a, 0xa1, 0x71, 0xf3, 0xf8, 0x64, 0x73, 0x3d, 0x04, 0xa2, 0x76, - 0x5f, 0x73, 0x49, 0x7d, 0x68, 0xb2, 0x9e, 0xf6, 0x18, 0x3b, 0xa6, 0x35, 0x6d, 0x62, 0xeb, 0x8f, - 0x5f, 0x6e, 0x82, 0xcc, 0xd3, 0xc4, 0x96, 0x9e, 0x02, 0x40, 0x1f, 0x03, 0xc8, 0x6e, 0x0c, 0xbf, - 0xaf, 0x66, 0x45, 0x51, 0x9b, 0x51, 0x51, 0xe1, 0xa8, 0xb4, 0x78, 0x54, 0x5a, 0x6b, 0xd4, 0x79, - 0x84, 0xa7, 0x7a, 0x41, 0x86, 0xb4, 0xfa, 0xe8, 0x09, 0xe4, 0x3b, 0xcc, 0xe2, 0xb1, 0xb9, 0x2d, - 0xa5, 0x56, 0x6a, 0xdc, 0x3b, 0x3e, 0xd9, 0xdc, 0x71, 0x5c, 0xd6, 0x1b, 0x75, 0x34, 0x8b, 0x0c, - 0xeb, 0xd2, 0xd3, 0xea, 0x99, 0xae, 0x17, 0x2d, 0xea, 0x6c, 0xea, 0x63, 0xaa, 0x35, 0xf6, 0x5b, - 0xb7, 0xef, 0xdc, 0x92, 0x90, 0xcb, 0x1d, 0x66, 0xb5, 0xfa, 0xe8, 0x23, 0xc8, 0xfa, 0xc4, 0x57, - 0x97, 0x45, 0x1d, 0x35, 0xed, 0xa5, 0xd4, 0x6b, 0xad, 0x80, 0x90, 0xee, 0xd3, 0x6e, 0x8b, 0x50, - 0x8a, 0x45, 0x17, 0x3a, 0x0f, 0x42, 0x77, 0x60, 0x8d, 0x0e, 0x4c, 0xda, 0xc3, 0xb6, 0x11, 0xb5, - 0xd4, 0xc3, 0xae, 0xd3, 0x63, 0x6a, 0x7e, 0x4b, 0xa9, 0xe5, 0xf4, 0x55, 0x69, 0x6d, 0x84, 0xc6, - 0x87, 0xc2, 0x86, 0xde, 0x07, 0x14, 0x47, 0x31, 0x2b, 0x8a, 0x58, 0x11, 0x11, 0x95, 0x28, 0x82, - 0x59, 0xa1, 0x77, 0xf5, 0xbb, 0x0c, 0xa8, 0xa7, 0x99, 0xfd, 0xdc, 0x65, 0xbd, 0x27, 0x98, 0x99, - 0xa9, 0x59, 0x28, 0x8b, 0x98, 0xc5, 0x1a, 0xe4, 0x65, 0x35, 0x19, 0x51, 0x8d, 0x5c, 0xa1, 0xff, - 0x43, 0x69, 0x4c, 0x98, 0xeb, 0x39, 0x86, 0x4f, 0xbe, 0xc5, 0x81, 0x20, 0x2d, 0xa7, 0x17, 0xc3, - 0xbd, 0x16, 0xdf, 0x7a, 0xc5, 0x28, 0x72, 0x6f, 0x3c, 0x8a, 0xe5, 0x33, 0x46, 0xf1, 0x63, 0x1e, - 0xca, 0x8d, 0xc3, 0xdd, 0x26, 0x1e, 0x60, 0xc7, 0x64, 0xff, 0xd6, 0x92, 0x32, 0x87, 0x96, 0x32, - 0x0b, 0xd4, 0x52, 0xf6, 0x6d, 0xb4, 0xf4, 0x15, 0x9c, 0xef, 0xfa, 0x46, 0x58, 0x8d, 0x31, 0x70, - 0x29, 0x1f, 0x5c, 0x76, 0x8e, 0x92, 0x8a, 0x5d, 0xbf, 0xc1, 0x8b, 0x7a, 0xec, 0x52, 0x41, 0x20, - 0x65, 0x66, 0xc0, 0x66, 0x27, 0x5c, 0x14, 0x7b, 0x92, 0x8a, 0xff, 0x01, 0x60, 0xcf, 0x9e, 0xd5, - 0x6f, 0x01, 0x7b, 0xb6, 0x34, 0xaf, 0x43, 0x81, 0x11, 0x66, 0x0e, 0x0c, 0x6a, 0x46, 0x5a, 0x3d, - 0x27, 0x36, 0xda, 0xa6, 0x88, 0x95, 0x0d, 0x1a, 0x6c, 0xa2, 0x9e, 0xe3, 0xa3, 0xd4, 0x0b, 0x72, - 0xe7, 0x70, 0x22, 0x58, 0x96, 0x66, 0x32, 0x62, 0xfe, 0x88, 0x19, 0xae, 0x3d, 0x51, 0x0b, 0x5b, - 0x4a, 0xad, 0xac, 0x57, 0xa4, 0xe5, 0xa9, 0x30, 0xec, 0xdb, 0x13, 0xb4, 0x03, 0x45, 0xc1, 0xbc, - 0x44, 0x03, 0x41, 0xcc, 0xc5, 0xe3, 0x93, 0x4d, 0xce, 0x7d, 0x5b, 0x5a, 0x0e, 0x27, 0x3a, 0xd0, - 0xf8, 0x37, 0xfa, 0x1a, 0xca, 0x76, 0xa8, 0x0a, 0x12, 0x18, 0xd4, 0x75, 0xd4, 0xa2, 0x88, 0xfa, - 0xf0, 0xf8, 0x64, 0xf3, 0xee, 0x9b, 0xcc, 0xae, 0xed, 0x3a, 0x9e, 0xc9, 0x46, 0x01, 0xd6, 0x4b, - 0x31, 0x5e, 0xdb, 0x75, 0xd0, 0x11, 0x94, 0x2d, 0x32, 0xc6, 0x9e, 0xe9, 0x31, 0x0e, 0x4f, 0xd5, - 0xd2, 0x56, 0xb6, 0x56, 0xdc, 0xb9, 0x75, 0x06, 0xc5, 0xbb, 0xd2, 0xf7, 0xbe, 0x6d, 0xfa, 0x21, - 0x42, 0x88, 0x4a, 0xf5, 0x52, 0x04, 0xd3, 0x76, 0x1d, 0x8a, 0x3e, 0x85, 0x0a, 0x27, 0x7c, 0xe4, - 0xd9, 0xb1, 0xa4, 0xd5, 0xb2, 0x10, 0xcf, 0x8d, 0x33, 0x90, 0x1b, 0x87, 0xbb, 0x47, 0x29, 0x6f, - 0xfd, 0x42, 0x87, 0x59, 0xe9, 0x8d, 0xea, 0xaf, 0x39, 0xb8, 0x70, 0xca, 0x89, 0xb3, 0x3f, 0xf2, - 0x3a, 0xc4, 0xb3, 0xe5, 0x48, 0xc5, 0x59, 0xa1, 0x17, 0xe3, 0xbd, 0xc3, 0x09, 0x7a, 0x17, 0xce, - 0xa7, 0x5c, 0xdc, 0x21, 0x16, 0x1f, 0x44, 0x59, 0x2f, 0x27, 0x4e, 0xee, 0x10, 0x9f, 0xe6, 0x26, - 0xfb, 0x5f, 0xb8, 0xf9, 0x06, 0xae, 0x24, 0xdc, 0x24, 0x49, 0x38, 0x4b, 0xb9, 0x79, 0x59, 0xba, - 0x1c, 0x23, 0x1f, 0x45, 0xc0, 0x9c, 0x2e, 0x02, 0x6b, 0x29, 0x39, 0x44, 0x05, 0xf3, 0x8c, 0xcb, - 0xf3, 0x66, 0x5c, 0x4d, 0x74, 0x21, 0x71, 0x79, 0xc2, 0x2e, 0xac, 0x25, 0xfa, 0x48, 0xe5, 0xa3, - 0x6a, 0xfe, 0x2d, 0x85, 0xb2, 0x1a, 0x0b, 0x25, 0x49, 0x43, 0x91, 0x05, 0xeb, 0x71, 0x9e, 0x99, - 0x51, 0x86, 0x27, 0xc6, 0x8a, 0x48, 0x76, 0xfd, 0x8c, 0x64, 0x31, 0xfa, 0xbe, 0xd7, 0x25, 0xba, - 0x1a, 0x01, 0xa5, 0x27, 0xc7, 0x0f, 0x8b, 0xea, 0xcf, 0x19, 0xb8, 0x74, 0x4a, 0x42, 0x3c, 0x62, - 0x81, 0x32, 0x7a, 0x4d, 0x1b, 0xd9, 0x45, 0xb4, 0xf1, 0x0a, 0x4e, 0x72, 0x8b, 0xe4, 0xa4, 0xda, - 0x86, 0x2b, 0xc9, 0xa5, 0x44, 0x82, 0xe4, 0x76, 0xa2, 0xe8, 0x03, 0xc8, 0xd9, 0x78, 0x40, 0x55, - 0xe5, 0x95, 0x0d, 0xcd, 0x5c, 0x69, 0xba, 0x88, 0xa8, 0x1e, 0xc0, 0xfa, 0xcb, 0x41, 0xf7, 0x3d, - 0x1b, 0x4f, 0x50, 0x1d, 0x56, 0x93, 0x03, 0xd7, 0xe8, 0x99, 0xb4, 0x17, 0x4e, 0x8e, 0x27, 0x2a, - 0xe9, 0x17, 0xe3, 0xa3, 0xf7, 0xa1, 0x49, 0x7b, 0x82, 0xd3, 0x9f, 0x14, 0x28, 0xcf, 0x0c, 0x0e, - 0x3d, 0x80, 0xcc, 0xdc, 0xcf, 0x86, 0x8c, 0xdf, 0x47, 0x8f, 0x20, 0xcb, 0x3f, 0xac, 0xcc, 0xbc, - 0x1f, 0x16, 0x47, 0xa9, 0x7e, 0xaf, 0xc0, 0xd5, 0x33, 0xe7, 0xcf, 0x6f, 0x6b, 0x8b, 0x8c, 0x17, - 0xf0, 0xda, 0xb1, 0xc8, 0xb8, 0xd5, 0xe7, 0x7a, 0x36, 0xc3, 0x1c, 0xa1, 0x2c, 0x32, 0x62, 0x78, - 0x45, 0x33, 0xce, 0x4b, 0xab, 0xbf, 0x29, 0x70, 0xb5, 0x8d, 0x07, 0xd8, 0x62, 0xee, 0x18, 0x47, - 0xac, 0xef, 0xf1, 0x37, 0x98, 0x67, 0x61, 0x74, 0x03, 0x2e, 0x9c, 0x62, 0x41, 0x14, 0x56, 0xd0, - 0xcb, 0x33, 0x04, 0x20, 0x1d, 0x0a, 0xf1, 0xd5, 0x3e, 0xe7, 0x43, 0x63, 0x45, 0xde, 0xea, 0xe8, - 0x26, 0x5c, 0x0a, 0x30, 0xd7, 0x63, 0x80, 0x6d, 0x43, 0xa2, 0xd3, 0xf0, 0x39, 0x5d, 0xd2, 0x2b, - 0xb1, 0xe9, 0x01, 0x77, 0x6f, 0xf7, 0xdf, 0xdb, 0x13, 0x9f, 0x74, 0x22, 0xa3, 0x36, 0x33, 0xd9, - 0x88, 0xa2, 0x22, 0xac, 0xb4, 0xf6, 0x0e, 0x9a, 0xfb, 0x07, 0x9f, 0x54, 0x96, 0x10, 0x40, 0xfe, - 0xfe, 0xee, 0xe1, 0xfe, 0x67, 0x7b, 0x15, 0x05, 0x95, 0xe0, 0xdc, 0xd1, 0x41, 0xe3, 0xe9, 0x41, - 0x73, 0xaf, 0x59, 0xc9, 0xa0, 0x15, 0xc8, 0xde, 0x3f, 0xf8, 0xa2, 0x92, 0x6d, 0x3c, 0xfe, 0xfd, - 0xf9, 0x86, 0xf2, 0xec, 0xf9, 0x86, 0xf2, 0xf7, 0xf3, 0x0d, 0xe5, 0x87, 0x17, 0x1b, 0x4b, 0xcf, - 0x5e, 0x6c, 0x2c, 0xfd, 0xf5, 0x62, 0x63, 0xe9, 0xcb, 0xd7, 0x36, 0x33, 0x49, 0xff, 0x77, 0x11, - 0x9d, 0x75, 0xf2, 0xe2, 0xbf, 0xcb, 0xed, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x4e, 0xa7, - 0xb4, 0x98, 0x0d, 0x00, 0x00, + // 1203 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcb, 0x6e, 0xdb, 0x46, + 0x1b, 0x35, 0x25, 0x59, 0x8e, 0x3e, 0x49, 0x89, 0x32, 0x71, 0x1c, 0x26, 0xc6, 0x6f, 0xfb, 0x57, + 0xd3, 0x40, 0x28, 0x1a, 0x2a, 0x76, 0x2e, 0x68, 0xbb, 0x28, 0x10, 0x59, 0x4e, 0x63, 0x24, 0x71, + 0x54, 0xca, 0x6e, 0xd1, 0x16, 0x28, 0x41, 0x91, 0x23, 0x8a, 0x90, 0xc4, 0x61, 0x39, 0x23, 0x55, + 0x7a, 0x80, 0x02, 0xdd, 0x14, 0xe8, 0xb6, 0xfb, 0x3e, 0x42, 0x9f, 0xa1, 0xc8, 0x32, 0xe8, 0xa6, + 0x85, 0x17, 0x46, 0x91, 0xbc, 0x48, 0x31, 0xc3, 0xe1, 0x45, 0x4e, 0xec, 0x5c, 0xe4, 0x9d, 0x38, + 0xdf, 0xed, 0xcc, 0x39, 0x87, 0x9c, 0x11, 0xdc, 0xe8, 0x98, 0x9d, 0xe9, 0x80, 0x78, 0xf5, 0x0e, + 0xb3, 0x28, 0x33, 0xfb, 0xae, 0xe7, 0xd4, 0xc7, 0x9b, 0xa9, 0x27, 0xcd, 0x0f, 0x08, 0x23, 0xe8, + 0xb2, 0xcc, 0xd3, 0x52, 0x91, 0xf1, 0xe6, 0xb5, 0x65, 0x87, 0x38, 0x44, 0x64, 0xd4, 0xf9, 0xaf, + 0x30, 0xf9, 0xda, 0x55, 0x8b, 0xd0, 0x21, 0xa1, 0x46, 0x18, 0x08, 0x1f, 0x64, 0xa8, 0x1a, 0x3e, + 0xd5, 0xad, 0x60, 0xea, 0x33, 0x52, 0xa7, 0xd8, 0xf2, 0xb7, 0xee, 0xde, 0xeb, 0x6f, 0xd6, 0xfb, + 0x78, 0x1a, 0xe5, 0x5c, 0x97, 0x39, 0x09, 0x9e, 0x0e, 0x66, 0xe6, 0x66, 0x7d, 0x06, 0xd1, 0xb5, + 0xf5, 0xd7, 0x23, 0xf7, 0x89, 0x1f, 0x26, 0x54, 0xff, 0xce, 0x42, 0xe5, 0x81, 0xeb, 0x99, 0x03, + 0x97, 0x4d, 0x5b, 0x01, 0x19, 0xbb, 0x36, 0x0e, 0xd0, 0x0e, 0x14, 0x6d, 0x4c, 0xad, 0xc0, 0xf5, + 0x99, 0x4b, 0x3c, 0x55, 0xd9, 0x50, 0x6a, 0xc5, 0xad, 0x0f, 0x34, 0x89, 0x31, 0xd9, 0x99, 0x98, + 0xa8, 0x35, 0x93, 0x54, 0x3d, 0x5d, 0x87, 0x9e, 0x00, 0x58, 0x64, 0x38, 0x74, 0x29, 0xe5, 0x5d, + 0x32, 0x1b, 0x4a, 0xad, 0xd0, 0xb8, 0x79, 0x78, 0xb4, 0xbe, 0x1a, 0x36, 0xa2, 0x76, 0x5f, 0x73, + 0x49, 0x7d, 0x68, 0xb2, 0x9e, 0xf6, 0x18, 0x3b, 0xa6, 0x35, 0x6d, 0x62, 0xeb, 0xaf, 0x3f, 0x6e, + 0x82, 0x9c, 0xd3, 0xc4, 0x96, 0x9e, 0x6a, 0x80, 0x3e, 0x07, 0x90, 0xbb, 0x31, 0xfc, 0xbe, 0x9a, + 0x15, 0xa0, 0xd6, 0x23, 0x50, 0x21, 0x55, 0x5a, 0x4c, 0x95, 0xd6, 0x1a, 0x75, 0x1e, 0xe1, 0xa9, + 0x5e, 0x90, 0x25, 0xad, 0x3e, 0x7a, 0x02, 0xf9, 0x0e, 0xb3, 0x78, 0x6d, 0x6e, 0x43, 0xa9, 0x95, + 0x1a, 0xf7, 0x0e, 0x8f, 0xd6, 0xb7, 0x1c, 0x97, 0xf5, 0x46, 0x1d, 0xcd, 0x22, 0xc3, 0xba, 0xcc, + 0xb4, 0x7a, 0xa6, 0xeb, 0x45, 0x0f, 0x75, 0x36, 0xf5, 0x31, 0xd5, 0x1a, 0xbb, 0xad, 0xdb, 0x77, + 0x6e, 0xc9, 0x96, 0x8b, 0x1d, 0x66, 0xb5, 0xfa, 0xe8, 0x33, 0xc8, 0xfa, 0xc4, 0x57, 0x17, 0x05, + 0x8e, 0x9a, 0xf6, 0x5a, 0xe9, 0xb5, 0x56, 0x40, 0x48, 0xf7, 0x69, 0xb7, 0x45, 0x28, 0xc5, 0x62, + 0x17, 0x3a, 0x2f, 0x42, 0x77, 0x60, 0x85, 0x0e, 0x4c, 0xda, 0xc3, 0xb6, 0x11, 0x6d, 0xa9, 0x87, + 0x5d, 0xa7, 0xc7, 0xd4, 0xfc, 0x86, 0x52, 0xcb, 0xe9, 0xcb, 0x32, 0xda, 0x08, 0x83, 0x0f, 0x45, + 0x0c, 0x7d, 0x0c, 0x28, 0xae, 0x62, 0x56, 0x54, 0xb1, 0x24, 0x2a, 0x2a, 0x51, 0x05, 0xb3, 0xc2, + 0xec, 0xea, 0xcf, 0x19, 0x50, 0x8f, 0x2b, 0xfb, 0xb5, 0xcb, 0x7a, 0x4f, 0x30, 0x33, 0x53, 0x5c, + 0x28, 0x67, 0xc1, 0xc5, 0x0a, 0xe4, 0x25, 0x9a, 0x8c, 0x40, 0x23, 0x9f, 0xd0, 0xff, 0xa1, 0x34, + 0x26, 0xcc, 0xf5, 0x1c, 0xc3, 0x27, 0x3f, 0xe2, 0x40, 0x88, 0x96, 0xd3, 0x8b, 0xe1, 0x5a, 0x8b, + 0x2f, 0x9d, 0x42, 0x45, 0xee, 0x9d, 0xa9, 0x58, 0x3c, 0x81, 0x8a, 0x67, 0x79, 0x28, 0x37, 0xf6, + 0xb7, 0x9b, 0x78, 0x80, 0x1d, 0x93, 0xbd, 0xea, 0x25, 0x65, 0x0e, 0x2f, 0x65, 0xce, 0xd0, 0x4b, + 0xd9, 0xf7, 0xf1, 0xd2, 0x77, 0x70, 0xbe, 0xeb, 0x1b, 0x21, 0x1a, 0x63, 0xe0, 0x52, 0x4e, 0x5c, + 0x76, 0x0e, 0x48, 0xc5, 0xae, 0xdf, 0xe0, 0xa0, 0x1e, 0xbb, 0x54, 0x08, 0x48, 0x99, 0x19, 0xb0, + 0x59, 0x86, 0x8b, 0x62, 0x4d, 0x4a, 0xf1, 0x3f, 0x00, 0xec, 0xd9, 0xb3, 0xfe, 0x2d, 0x60, 0xcf, + 0x96, 0xe1, 0x55, 0x28, 0x30, 0xc2, 0xcc, 0x81, 0x41, 0xcd, 0xc8, 0xab, 0xe7, 0xc4, 0x42, 0xdb, + 0x14, 0xb5, 0x72, 0x83, 0x06, 0x9b, 0xa8, 0xe7, 0x38, 0x95, 0x7a, 0x41, 0xae, 0xec, 0x4f, 0x84, + 0xca, 0x32, 0x4c, 0x46, 0xcc, 0x1f, 0x31, 0xc3, 0xb5, 0x27, 0x6a, 0x61, 0x43, 0xa9, 0x95, 0xf5, + 0x8a, 0x8c, 0x3c, 0x15, 0x81, 0x5d, 0x7b, 0x82, 0xb6, 0xa0, 0x28, 0x94, 0x97, 0xdd, 0x40, 0x08, + 0x73, 0xf1, 0xf0, 0x68, 0x9d, 0x6b, 0xdf, 0x96, 0x91, 0xfd, 0x89, 0x0e, 0x34, 0xfe, 0x8d, 0xbe, + 0x87, 0xb2, 0x1d, 0xba, 0x82, 0x04, 0x06, 0x75, 0x1d, 0xb5, 0x28, 0xaa, 0x3e, 0x3d, 0x3c, 0x5a, + 0xbf, 0xfb, 0x2e, 0xdc, 0xb5, 0x5d, 0xc7, 0x33, 0xd9, 0x28, 0xc0, 0x7a, 0x29, 0xee, 0xd7, 0x76, + 0x1d, 0x74, 0x00, 0x65, 0x8b, 0x8c, 0xb1, 0x67, 0x7a, 0x8c, 0xb7, 0xa7, 0x6a, 0x69, 0x23, 0x5b, + 0x2b, 0x6e, 0xdd, 0x3a, 0x41, 0xe2, 0x6d, 0x99, 0x7b, 0xdf, 0x36, 0xfd, 0xb0, 0x43, 0xd8, 0x95, + 0xea, 0xa5, 0xa8, 0x4d, 0xdb, 0x75, 0x28, 0xfa, 0x10, 0xce, 0x8f, 0xbc, 0x0e, 0xf1, 0x6c, 0xb1, + 0x57, 0x77, 0x88, 0xd5, 0xb2, 0x20, 0xa5, 0x1c, 0xaf, 0xee, 0xbb, 0x43, 0x8c, 0xbe, 0x84, 0x0a, + 0xf7, 0xc5, 0xc8, 0xb3, 0x63, 0xe7, 0xab, 0xe7, 0x85, 0xc7, 0x6e, 0x9c, 0x00, 0xa0, 0xb1, 0xbf, + 0x7d, 0x90, 0xca, 0xd6, 0x2f, 0x74, 0x98, 0x95, 0x5e, 0xa8, 0xfe, 0x96, 0x83, 0x0b, 0xc7, 0x92, + 0xb8, 0x49, 0x52, 0x68, 0x26, 0xe1, 0x27, 0x45, 0x2f, 0x26, 0x58, 0x5e, 0xd1, 0x26, 0xf3, 0x36, + 0xda, 0xfc, 0x00, 0x57, 0x12, 0x6d, 0x92, 0x01, 0x5c, 0xa5, 0xec, 0xbc, 0x2a, 0x5d, 0x8e, 0x3b, + 0x1f, 0x44, 0x8d, 0xb9, 0x5c, 0x04, 0x56, 0x52, 0x76, 0x88, 0x00, 0xf3, 0x89, 0xb9, 0x79, 0x27, + 0x2e, 0x27, 0xbe, 0x90, 0x7d, 0xf9, 0xc0, 0x2e, 0xac, 0x24, 0xfe, 0x48, 0xcd, 0xa3, 0xea, 0xe2, + 0x7b, 0x1a, 0x65, 0x39, 0x36, 0x4a, 0x32, 0x86, 0x22, 0x0b, 0x56, 0xe3, 0x39, 0x33, 0x54, 0x86, + 0x5f, 0x8c, 0xbc, 0x18, 0x76, 0xfd, 0x84, 0x61, 0x71, 0xf7, 0x5d, 0xaf, 0x4b, 0x74, 0x35, 0x6a, + 0x94, 0x66, 0x8e, 0x7f, 0x2c, 0xaa, 0x3f, 0x65, 0xe0, 0xd2, 0x31, 0x6f, 0xf0, 0x8a, 0xb7, 0xf1, + 0xc7, 0x1b, 0xf0, 0x65, 0xce, 0x02, 0xdf, 0x29, 0x64, 0x67, 0xcf, 0x92, 0xec, 0x6a, 0x1b, 0xae, + 0x24, 0xa7, 0x0d, 0x09, 0x92, 0x63, 0x87, 0xa2, 0x4f, 0x20, 0x67, 0xe3, 0x01, 0x55, 0x95, 0x53, + 0x37, 0x34, 0x73, 0x56, 0xe9, 0xa2, 0xa2, 0xba, 0x07, 0xab, 0xaf, 0x6f, 0xba, 0xeb, 0xd9, 0x78, + 0x82, 0xea, 0xb0, 0x9c, 0x7c, 0x49, 0x8d, 0x9e, 0x49, 0x7b, 0x21, 0x73, 0x7c, 0x50, 0x49, 0xbf, + 0x18, 0x7f, 0x53, 0x1f, 0x9a, 0xb4, 0x27, 0xc4, 0xfa, 0x5d, 0x81, 0xf2, 0x0c, 0x71, 0xe8, 0x01, + 0x64, 0xe6, 0xbe, 0x0f, 0x64, 0xfc, 0x3e, 0x7a, 0x04, 0x59, 0xfe, 0xc6, 0x64, 0xe6, 0x7d, 0x63, + 0x78, 0x97, 0xea, 0x2f, 0x0a, 0x5c, 0x3d, 0x91, 0x7f, 0x7e, 0x0c, 0x5b, 0x64, 0x7c, 0x06, 0xd7, + 0x18, 0x8b, 0x8c, 0x5b, 0x7d, 0x6e, 0x54, 0x33, 0x9c, 0x11, 0xda, 0x22, 0x23, 0xc8, 0x2b, 0x9a, + 0xf1, 0x5c, 0x5a, 0xfd, 0x53, 0x81, 0xab, 0x6d, 0x3c, 0xc0, 0x16, 0x73, 0xc7, 0x38, 0x52, 0x7d, + 0x87, 0x5f, 0xae, 0x3c, 0x0b, 0xa3, 0x1b, 0x70, 0xe1, 0x98, 0x0a, 0x02, 0x58, 0x41, 0x2f, 0xcf, + 0x08, 0x80, 0x74, 0x28, 0xc4, 0x67, 0xf6, 0x9c, 0x37, 0x88, 0x25, 0x79, 0x5c, 0xa3, 0x9b, 0x70, + 0x29, 0xc0, 0xdc, 0x8f, 0x01, 0xb6, 0x0d, 0xd9, 0x9d, 0x86, 0xf7, 0xe4, 0x92, 0x5e, 0x89, 0x43, + 0x0f, 0x78, 0x7a, 0xbb, 0xff, 0xd1, 0x8e, 0x78, 0x57, 0x13, 0x1b, 0xb5, 0x99, 0xc9, 0x46, 0x14, + 0x15, 0x61, 0xa9, 0xb5, 0xb3, 0xd7, 0xdc, 0xdd, 0xfb, 0xa2, 0xb2, 0x80, 0x00, 0xf2, 0xf7, 0xb7, + 0xf7, 0x77, 0xbf, 0xda, 0xa9, 0x28, 0xa8, 0x04, 0xe7, 0x0e, 0xf6, 0x1a, 0x4f, 0xf7, 0x9a, 0x3b, + 0xcd, 0x4a, 0x06, 0x2d, 0x41, 0xf6, 0xfe, 0xde, 0x37, 0x95, 0x6c, 0xe3, 0xf1, 0xb3, 0x17, 0x6b, + 0xca, 0xf3, 0x17, 0x6b, 0xca, 0xbf, 0x2f, 0xd6, 0x94, 0x5f, 0x5f, 0xae, 0x2d, 0x3c, 0x7f, 0xb9, + 0xb6, 0xf0, 0xcf, 0xcb, 0xb5, 0x85, 0x6f, 0xdf, 0xb8, 0x99, 0x49, 0xfa, 0x4f, 0x89, 0xd8, 0x59, + 0x27, 0x2f, 0xfe, 0x94, 0xdc, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x72, 0x85, 0x4f, 0x14, 0x71, + 0x0d, 0x00, 0x00, } func (m *FinalityProvider) Marshal() (dAtA []byte, err error) { @@ -1073,7 +1065,12 @@ func (m *BTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x6a + dAtA[i] = 0x72 + } + if m.UnbondingTime != 0 { + i = encodeVarintBtcstaking(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x68 } if len(m.CovenantSigs) > 0 { for iNdEx := len(m.CovenantSigs) - 1; iNdEx >= 0; iNdEx-- { @@ -1224,7 +1221,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x3a + dAtA[i] = 0x32 } } if len(m.CovenantSlashingSigs) > 0 { @@ -1238,7 +1235,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 + dAtA[i] = 0x2a } } if m.DelegatorSlashingSig != nil { @@ -1251,7 +1248,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x22 } if m.DelegatorUnbondingSig != nil { { @@ -1263,7 +1260,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x1a } if m.SlashingTx != nil { { @@ -1275,12 +1272,7 @@ func (m *BTCUndelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a - } - if m.UnbondingTime != 0 { - i = encodeVarintBtcstaking(dAtA, i, uint64(m.UnbondingTime)) - i-- - dAtA[i] = 0x10 + dAtA[i] = 0x12 } if len(m.UnbondingTx) > 0 { i -= len(m.UnbondingTx) @@ -1323,7 +1315,7 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x1a } } if len(m.CovenantUnbondingSigList) > 0 { @@ -1337,14 +1329,9 @@ func (m *BTCUndelegationInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintBtcstaking(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x12 } } - if m.UnbondingTime != 0 { - i = encodeVarintBtcstaking(dAtA, i, uint64(m.UnbondingTime)) - i-- - dAtA[i] = 0x10 - } if len(m.UnbondingTx) > 0 { i -= len(m.UnbondingTx) copy(dAtA[i:], m.UnbondingTx) @@ -1689,6 +1676,9 @@ func (m *BTCDelegation) Size() (n int) { n += 1 + l + sovBtcstaking(uint64(l)) } } + if m.UnbondingTime != 0 { + n += 1 + sovBtcstaking(uint64(m.UnbondingTime)) + } if m.BtcUndelegation != nil { l = m.BtcUndelegation.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -1706,9 +1696,6 @@ func (m *BTCUndelegation) Size() (n int) { if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } - if m.UnbondingTime != 0 { - n += 1 + sovBtcstaking(uint64(m.UnbondingTime)) - } if m.SlashingTx != nil { l = m.SlashingTx.Size() n += 1 + l + sovBtcstaking(uint64(l)) @@ -1746,9 +1733,6 @@ func (m *BTCUndelegationInfo) Size() (n int) { if l > 0 { n += 1 + l + sovBtcstaking(uint64(l)) } - if m.UnbondingTime != 0 { - n += 1 + sovBtcstaking(uint64(m.UnbondingTime)) - } if len(m.CovenantUnbondingSigList) > 0 { for _, e := range m.CovenantUnbondingSigList { l = e.Size() @@ -2671,6 +2655,25 @@ func (m *BTCDelegation) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) + } + m.UnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBtcstaking + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 14: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BtcUndelegation", wireType) } @@ -2791,25 +2794,6 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) - } - m.UnbondingTime = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.UnbondingTime |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingTx", wireType) } @@ -2844,7 +2828,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorUnbondingSig", wireType) } @@ -2879,7 +2863,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 5: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DelegatorSlashingSig", wireType) } @@ -2914,7 +2898,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 6: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSigs", wireType) } @@ -2948,7 +2932,7 @@ func (m *BTCUndelegation) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } @@ -3067,25 +3051,6 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) - } - m.UnbondingTime = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtcstaking - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.UnbondingTime |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantUnbondingSigList", wireType) } @@ -3119,7 +3084,7 @@ func (m *BTCUndelegationInfo) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CovenantSlashingSigs", wireType) } diff --git a/x/btcstaking/types/msg.go b/x/btcstaking/types/msg.go index a0ed64f07..4e337efef 100644 --- a/x/btcstaking/types/msg.go +++ b/x/btcstaking/types/msg.go @@ -114,7 +114,7 @@ func (m *MsgCreateBTCDelegation) ValidateBasic() error { } // Check unbonding time is lower than max uint16 - if uint64(m.UnbondingTime) > math.MaxUint16 { + if m.UnbondingTime > math.MaxUint16 { return ErrInvalidUnbondingTx.Wrapf("unbonding time %d must be lower than %d", m.UnbondingTime, math.MaxUint16) } diff --git a/x/btcstaking/types/query.pb.go b/x/btcstaking/types/query.pb.go index 3b5d59719..fb4f4e200 100644 --- a/x/btcstaking/types/query.pb.go +++ b/x/btcstaking/types/query.pb.go @@ -1038,9 +1038,11 @@ type QueryBTCDelegationResponse struct { CovenantSigs []*CovenantAdaptorSignatures `protobuf:"bytes,7,rep,name=covenant_sigs,json=covenantSigs,proto3" json:"covenant_sigs,omitempty"` // whether this delegation is active Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` - // undelegation_info is the undelegation info of this delegation. It is nil - // if it is not undelegating - UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,9,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` + // unbonding_time used in unbonding output timelock path and in slashing transactions + // change outputs + UnbondingTime uint32 `protobuf:"varint,9,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` + // undelegation_info is the undelegation info of this delegation. + UndelegationInfo *BTCUndelegationInfo `protobuf:"bytes,10,opt,name=undelegation_info,json=undelegationInfo,proto3" json:"undelegation_info,omitempty"` } func (m *QueryBTCDelegationResponse) Reset() { *m = QueryBTCDelegationResponse{} } @@ -1118,6 +1120,13 @@ func (m *QueryBTCDelegationResponse) GetActive() bool { return false } +func (m *QueryBTCDelegationResponse) GetUnbondingTime() uint32 { + if m != nil { + return m.UnbondingTime + } + return 0 +} + func (m *QueryBTCDelegationResponse) GetUndelegationInfo() *BTCUndelegationInfo { if m != nil { return m.UndelegationInfo @@ -1151,89 +1160,91 @@ func init() { func init() { proto.RegisterFile("babylon/btcstaking/v1/query.proto", fileDescriptor_74d49d26f7429697) } var fileDescriptor_74d49d26f7429697 = []byte{ - // 1305 bytes of a gzipped FileDescriptorProto + // 1333 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcb, 0x6f, 0x1b, 0x45, - 0x1c, 0xce, 0xe4, 0x61, 0x92, 0xc9, 0x7b, 0x5a, 0xc0, 0x75, 0x62, 0x27, 0x59, 0x95, 0xbc, 0x0a, - 0xbb, 0xb1, 0x13, 0x7a, 0x00, 0xd4, 0xd4, 0x4e, 0x69, 0xd2, 0x47, 0x84, 0xd9, 0xa4, 0x54, 0x02, - 0x09, 0x6b, 0xec, 0x8c, 0xd7, 0x4b, 0x9c, 0x9d, 0xed, 0xee, 0xd8, 0x38, 0xaa, 0x72, 0xe1, 0xc0, - 0x0d, 0x09, 0x09, 0xf8, 0x1b, 0x40, 0xe2, 0x88, 0xb8, 0x70, 0xe3, 0xd6, 0x63, 0x05, 0x07, 0x10, - 0x87, 0x82, 0x12, 0xc4, 0x01, 0x09, 0x71, 0xe3, 0x8c, 0x3c, 0x3b, 0x8b, 0xd7, 0xf6, 0xae, 0xb3, - 0x4e, 0xc2, 0xad, 0xde, 0xf9, 0xbd, 0xbe, 0x6f, 0xbe, 0xfd, 0xf6, 0xd7, 0xc0, 0xb9, 0x3c, 0xce, - 0x1f, 0x96, 0xa9, 0xa1, 0xe4, 0x59, 0xc1, 0x66, 0x78, 0x5f, 0x37, 0x34, 0xa5, 0x9a, 0x54, 0x1e, - 0x55, 0x88, 0x75, 0x28, 0x9b, 0x16, 0x65, 0x14, 0x3d, 0x2f, 0x42, 0xe4, 0x46, 0x88, 0x5c, 0x4d, - 0xc6, 0x2e, 0x6b, 0x54, 0xa3, 0x3c, 0x42, 0xa9, 0xff, 0xcb, 0x09, 0x8e, 0x4d, 0x6b, 0x94, 0x6a, - 0x65, 0xa2, 0x60, 0x53, 0x57, 0xb0, 0x61, 0x50, 0x86, 0x99, 0x4e, 0x0d, 0x5b, 0x9c, 0x2e, 0x17, - 0xa8, 0x7d, 0x40, 0x6d, 0x25, 0x8f, 0x6d, 0xe2, 0xf4, 0x50, 0xaa, 0xc9, 0x3c, 0x61, 0x38, 0xa9, - 0x98, 0x58, 0xd3, 0x0d, 0x1e, 0x2c, 0x62, 0x25, 0xff, 0xc9, 0x4c, 0x6c, 0xe1, 0x03, 0xb7, 0xde, - 0xbc, 0x7f, 0x8c, 0x67, 0x50, 0x1e, 0x27, 0x5d, 0x86, 0xe8, 0xed, 0x7a, 0xb7, 0x2c, 0x4f, 0x56, - 0xc9, 0xa3, 0x0a, 0xb1, 0x99, 0xa4, 0xc2, 0x4b, 0x4d, 0x4f, 0x6d, 0x93, 0x1a, 0x36, 0x41, 0xaf, - 0xc3, 0x88, 0xd3, 0x24, 0x0a, 0x66, 0xc1, 0xe2, 0x70, 0x2a, 0x2e, 0xfb, 0x12, 0x20, 0x3b, 0x69, - 0x99, 0xfe, 0x27, 0xcf, 0x66, 0x7a, 0x54, 0x91, 0x22, 0x69, 0x30, 0xce, 0x6b, 0xde, 0xd6, 0x0d, - 0x5c, 0xd6, 0xd9, 0x61, 0xd6, 0xa2, 0x55, 0x7d, 0x8f, 0x58, 0x6e, 0x53, 0x74, 0x1b, 0xc2, 0x06, - 0x54, 0xd1, 0x61, 0x5e, 0x76, 0x78, 0x91, 0xeb, 0xbc, 0xc8, 0x0e, 0xf7, 0x82, 0x17, 0x39, 0x8b, - 0x35, 0x22, 0x72, 0x55, 0x4f, 0xa6, 0xf4, 0x3d, 0x80, 0x89, 0xa0, 0x4e, 0x02, 0xc8, 0x3b, 0x10, - 0x15, 0xc5, 0x61, 0xce, 0x74, 0x4f, 0xa3, 0x60, 0xb6, 0x6f, 0x71, 0x38, 0xb5, 0x10, 0x00, 0xaa, - 0xb5, 0x9a, 0x3a, 0x59, 0x6c, 0xad, 0x8f, 0x36, 0x9b, 0x20, 0xf4, 0x72, 0x08, 0x0b, 0xa7, 0x42, - 0x70, 0x86, 0x6a, 0xc2, 0x90, 0x86, 0xd3, 0xbe, 0x10, 0x5c, 0xae, 0xe6, 0xe0, 0x68, 0xd1, 0xcc, - 0xe5, 0x59, 0x21, 0x67, 0xee, 0xe7, 0x4a, 0xa4, 0xc6, 0xe9, 0x1a, 0x52, 0x61, 0xd1, 0xcc, 0xb0, - 0x42, 0x76, 0x7f, 0x8b, 0xd4, 0xa4, 0x4a, 0x00, 0xdf, 0xff, 0x91, 0xb0, 0x0b, 0x27, 0xdb, 0x48, - 0x10, 0xb4, 0x87, 0xe6, 0x60, 0xa2, 0x95, 0x03, 0xe9, 0x2b, 0x00, 0x63, 0xbc, 0x6f, 0x66, 0x77, - 0xe3, 0x16, 0x29, 0x13, 0xcd, 0x91, 0xb9, 0x3b, 0x78, 0x06, 0x46, 0x6c, 0x86, 0x59, 0xc5, 0x91, - 0xd0, 0x58, 0x6a, 0x39, 0xa0, 0x53, 0x53, 0xf6, 0x0e, 0xcf, 0x50, 0x45, 0x66, 0x8b, 0x50, 0x7a, - 0xcf, 0x2c, 0x94, 0x6f, 0x01, 0x9c, 0xf2, 0x1d, 0x55, 0x10, 0xb4, 0x0d, 0xc7, 0xeb, 0x0c, 0xef, - 0x35, 0x8e, 0x84, 0x44, 0xae, 0x86, 0x19, 0x5a, 0x1d, 0xcb, 0xb3, 0x82, 0xa7, 0xec, 0xc5, 0x89, - 0xa3, 0x08, 0x97, 0x7c, 0x6f, 0x36, 0x4b, 0x3f, 0x24, 0x56, 0x9a, 0x6d, 0x11, 0x5d, 0x2b, 0xb1, - 0xf0, 0x4a, 0x41, 0x2f, 0xc0, 0x48, 0x89, 0xe7, 0xf0, 0xa1, 0xfa, 0x55, 0xf1, 0x4b, 0x7a, 0x0b, - 0x2e, 0x87, 0xe9, 0x23, 0xd8, 0x9a, 0x83, 0x23, 0x55, 0xca, 0x74, 0x43, 0xcb, 0x99, 0xf5, 0x73, - 0xde, 0xa7, 0x5f, 0x1d, 0x76, 0x9e, 0xf1, 0x14, 0x69, 0x1b, 0x2e, 0xfa, 0x16, 0xdc, 0xa8, 0x58, - 0x16, 0x31, 0x18, 0x0f, 0xea, 0x42, 0xe1, 0x41, 0x3c, 0x34, 0x97, 0x13, 0xe3, 0x35, 0x40, 0x02, - 0x2f, 0xc8, 0xb6, 0xb1, 0x7b, 0xdb, 0xc7, 0xfe, 0x04, 0xc0, 0x6b, 0xbc, 0x51, 0xba, 0xc0, 0xf4, - 0x2a, 0x69, 0xb3, 0x95, 0x56, 0xca, 0x83, 0x5a, 0x5d, 0x94, 0x6e, 0x7f, 0x02, 0xf0, 0xe5, 0x70, - 0xf3, 0x08, 0xec, 0xef, 0x77, 0xb0, 0x3b, 0x25, 0xe4, 0xab, 0xfe, 0x50, 0x67, 0xa5, 0x6d, 0xc2, - 0xf0, 0xff, 0x6a, 0x7b, 0x71, 0xf1, 0x42, 0x72, 0x60, 0x98, 0x91, 0xbd, 0x26, 0x62, 0xa5, 0xeb, - 0xc2, 0x15, 0xdb, 0x8e, 0x3b, 0xdf, 0xb1, 0xf4, 0x39, 0x80, 0x0b, 0xbe, 0x4a, 0xf1, 0x31, 0xa8, - 0x10, 0xef, 0xcb, 0x45, 0xdd, 0xe3, 0xaf, 0x20, 0xe0, 0x7d, 0xf0, 0x33, 0xa3, 0x0f, 0xe0, 0x15, - 0x8f, 0x19, 0x51, 0xcb, 0xc7, 0x96, 0xe4, 0x53, 0x6d, 0x89, 0x36, 0x95, 0x7e, 0xb1, 0x61, 0x50, - 0x4d, 0x07, 0x17, 0x77, 0x9f, 0x77, 0xe1, 0x95, 0x76, 0x83, 0x75, 0x99, 0x7e, 0x05, 0x5e, 0x12, - 0x43, 0xe6, 0x58, 0x2d, 0x57, 0xc2, 0x76, 0xc9, 0xc3, 0xf7, 0x84, 0x38, 0xda, 0xad, 0x6d, 0x61, - 0xbb, 0x54, 0x7f, 0xdb, 0xbf, 0xe8, 0xf7, 0xfb, 0xb0, 0x78, 0xcc, 0x3a, 0xe2, 0x5c, 0x1a, 0x2f, - 0x30, 0x92, 0xb9, 0xfe, 0xcb, 0xb3, 0x99, 0x94, 0xa6, 0xb3, 0x52, 0x25, 0x2f, 0x17, 0xe8, 0x81, - 0x22, 0xa8, 0x29, 0x94, 0xb0, 0x6e, 0xb8, 0x3f, 0x14, 0x76, 0x68, 0x12, 0x5b, 0xce, 0xdc, 0xc9, - 0xae, 0xae, 0xad, 0x64, 0x2b, 0xf9, 0x7b, 0xe4, 0x50, 0x1d, 0xc8, 0xd7, 0xaf, 0x19, 0xbd, 0x07, - 0xc7, 0x1a, 0x32, 0x28, 0xeb, 0x76, 0xdd, 0x1b, 0xfb, 0xce, 0x51, 0x76, 0x58, 0xe8, 0xe7, 0xbe, - 0xce, 0x35, 0x36, 0x62, 0x33, 0x6c, 0xb1, 0x9c, 0x50, 0x6b, 0x9f, 0xe3, 0x39, 0xfc, 0x99, 0x23, - 0x69, 0x14, 0x87, 0x90, 0x18, 0x7b, 0x6e, 0x40, 0x3f, 0x0f, 0x18, 0x22, 0x86, 0x50, 0x3c, 0x9a, - 0x82, 0x43, 0x8c, 0x32, 0x5c, 0xce, 0xd9, 0x98, 0x45, 0x07, 0xf8, 0xe9, 0x20, 0x7f, 0xb0, 0x83, - 0x19, 0xba, 0x0a, 0xc7, 0xbc, 0xc4, 0x92, 0x5a, 0x34, 0xc2, 0x39, 0x1d, 0x69, 0x70, 0x4a, 0x6a, - 0xe8, 0x01, 0x1c, 0x2d, 0xd0, 0x2a, 0x31, 0xb0, 0xc1, 0x72, 0xb6, 0xae, 0xd9, 0xd1, 0xe7, 0xb8, - 0x88, 0x56, 0x02, 0x44, 0xb4, 0x21, 0x62, 0xd3, 0x7b, 0xd8, 0x64, 0xd4, 0xda, 0xd1, 0x35, 0x03, - 0xb3, 0x8a, 0x45, 0x6c, 0x75, 0xc4, 0x2d, 0xb3, 0xa3, 0x6b, 0x76, 0xfd, 0x1d, 0xc4, 0xdc, 0x96, - 0xa2, 0x83, 0xb3, 0x60, 0x71, 0x50, 0x15, 0xbf, 0xd0, 0x43, 0x38, 0x59, 0x31, 0x1a, 0x9a, 0xcd, - 0xe9, 0x46, 0x91, 0x46, 0x87, 0xb8, 0xb4, 0x3a, 0xec, 0x00, 0x0f, 0x3c, 0x29, 0x77, 0x8c, 0x22, - 0x55, 0x27, 0x2a, 0x2d, 0x4f, 0x52, 0x7f, 0x8f, 0xc3, 0x01, 0xae, 0x0b, 0xf4, 0x31, 0x80, 0x11, - 0x67, 0xf5, 0x44, 0x4b, 0x01, 0x25, 0xdb, 0x77, 0xdd, 0xd8, 0x72, 0x98, 0x50, 0x47, 0x64, 0xd2, - 0x4b, 0x1f, 0xfd, 0xf8, 0xfb, 0x67, 0xbd, 0x33, 0x28, 0xae, 0x74, 0x5a, 0xc1, 0xd1, 0x37, 0x00, - 0x4e, 0xb6, 0xb9, 0x32, 0x5a, 0xeb, 0xd4, 0x28, 0x68, 0x2b, 0x8e, 0xbd, 0xda, 0x65, 0x96, 0x98, - 0x34, 0xc9, 0x27, 0xbd, 0x86, 0x96, 0x02, 0x26, 0x6d, 0xff, 0x1e, 0xa0, 0x1f, 0x00, 0x9c, 0x68, - 0x2d, 0x88, 0x56, 0xbb, 0x69, 0xef, 0xce, 0xbc, 0xd6, 0x5d, 0x92, 0x18, 0x79, 0x87, 0x8f, 0xbc, - 0x8d, 0xee, 0x85, 0x1e, 0x59, 0x79, 0xdc, 0x64, 0xd5, 0x47, 0xed, 0x21, 0xe8, 0x4b, 0x00, 0xc7, - 0x9a, 0xd7, 0x3b, 0x94, 0xec, 0x34, 0x9d, 0xef, 0xd6, 0x1a, 0x4b, 0x75, 0x93, 0x22, 0xe0, 0xc8, - 0x1c, 0xce, 0x22, 0x9a, 0x57, 0x02, 0xff, 0x2b, 0xe6, 0xf5, 0x70, 0xf4, 0x07, 0x80, 0x33, 0xa7, - 0x7c, 0xd0, 0x51, 0xa6, 0xd3, 0x1c, 0xe1, 0xb6, 0x93, 0xd8, 0xc6, 0xb9, 0x6a, 0x08, 0x70, 0xaf, - 0x71, 0x70, 0x6b, 0x28, 0xd5, 0xc5, 0x5d, 0x39, 0x5e, 0x76, 0x84, 0xfe, 0x01, 0x30, 0xde, 0x71, - 0xa5, 0x44, 0x37, 0xbb, 0xd1, 0x8f, 0xdf, 0xd6, 0x1b, 0x4b, 0x9f, 0xa3, 0x82, 0x80, 0x98, 0xe5, - 0x10, 0xef, 0xa2, 0xad, 0xb3, 0xcb, 0x91, 0x6f, 0x94, 0x0d, 0xe0, 0x7f, 0x02, 0x38, 0xdd, 0x69, - 0x57, 0x45, 0xeb, 0xdd, 0x4c, 0xed, 0xb3, 0x34, 0xc7, 0x6e, 0x9e, 0xbd, 0x80, 0x40, 0xbd, 0xc9, - 0x51, 0xa7, 0xd1, 0xfa, 0x39, 0x51, 0xa3, 0xaf, 0x01, 0x1c, 0x6f, 0xd9, 0xd3, 0x50, 0xea, 0x54, - 0xe9, 0xb5, 0xed, 0x7c, 0xb1, 0xd5, 0xae, 0x72, 0x04, 0x0a, 0x85, 0xa3, 0x58, 0x42, 0x0b, 0x01, - 0x28, 0xb0, 0x9b, 0x27, 0x3e, 0xb0, 0xe8, 0x2f, 0x00, 0xa7, 0x3a, 0x6c, 0x61, 0xe8, 0x46, 0x37, - 0xc4, 0xfa, 0x18, 0xc8, 0xfa, 0x99, 0xf3, 0x05, 0xa2, 0x6d, 0x8e, 0x68, 0x13, 0xbd, 0x79, 0xf6, - 0x7b, 0xf1, 0x9a, 0xcd, 0x77, 0x00, 0x8e, 0x36, 0xf9, 0x16, 0x5a, 0x09, 0x6d, 0x71, 0x2e, 0xa6, - 0x64, 0x17, 0x19, 0x02, 0xc5, 0x2d, 0x8e, 0xe2, 0x06, 0x7a, 0x23, 0x9c, 0x27, 0x2a, 0x8f, 0x7d, - 0x16, 0xc4, 0xa3, 0xcc, 0xfd, 0x27, 0xc7, 0x09, 0xf0, 0xf4, 0x38, 0x01, 0x7e, 0x3b, 0x4e, 0x80, - 0x4f, 0x4f, 0x12, 0x3d, 0x4f, 0x4f, 0x12, 0x3d, 0x3f, 0x9f, 0x24, 0x7a, 0xde, 0x3d, 0x75, 0x33, - 0xab, 0x79, 0x1b, 0xf2, 0x35, 0x2d, 0x1f, 0xe1, 0x7f, 0x08, 0x5b, 0xfd, 0x37, 0x00, 0x00, 0xff, - 0xff, 0x51, 0xd9, 0xf0, 0x54, 0xf0, 0x13, 0x00, 0x00, + 0x1c, 0xce, 0xa4, 0xa9, 0x69, 0x7e, 0x79, 0x34, 0x99, 0x16, 0x70, 0xdd, 0xc6, 0x49, 0x57, 0x6d, + 0xf3, 0x28, 0xec, 0xd6, 0x4e, 0xe8, 0x01, 0x50, 0xdb, 0x38, 0xa5, 0x4d, 0x1f, 0x11, 0x66, 0x93, + 0x52, 0x09, 0x24, 0xac, 0x59, 0x67, 0xbc, 0x5e, 0x9a, 0xec, 0x6c, 0x77, 0xc7, 0xc6, 0x51, 0xd5, + 0x0b, 0x07, 0x6e, 0x48, 0x48, 0xf0, 0x3f, 0x80, 0xc4, 0x11, 0x71, 0x41, 0x5c, 0xb8, 0xf5, 0x58, + 0xc1, 0x01, 0xc4, 0xa1, 0xa0, 0x16, 0x71, 0x40, 0x42, 0xdc, 0x38, 0x23, 0xcf, 0xce, 0xd6, 0x6b, + 0x7b, 0xd7, 0x59, 0x27, 0xe1, 0x56, 0xef, 0xfc, 0x5e, 0xdf, 0x37, 0xdf, 0x7e, 0xfb, 0x6b, 0xe0, + 0xb4, 0x41, 0x8c, 0x9d, 0x2d, 0x66, 0x6b, 0x06, 0x2f, 0x7b, 0x9c, 0xdc, 0xb3, 0x6c, 0x53, 0xab, + 0xe7, 0xb4, 0xfb, 0x35, 0xea, 0xee, 0xa8, 0x8e, 0xcb, 0x38, 0xc3, 0x2f, 0xca, 0x10, 0xb5, 0x15, + 0xa2, 0xd6, 0x73, 0x99, 0xe3, 0x26, 0x33, 0x99, 0x88, 0xd0, 0x9a, 0xff, 0xf2, 0x83, 0x33, 0xa7, + 0x4c, 0xc6, 0xcc, 0x2d, 0xaa, 0x11, 0xc7, 0xd2, 0x88, 0x6d, 0x33, 0x4e, 0xb8, 0xc5, 0x6c, 0x4f, + 0x9e, 0x2e, 0x94, 0x99, 0xb7, 0xcd, 0x3c, 0xcd, 0x20, 0x1e, 0xf5, 0x7b, 0x68, 0xf5, 0x9c, 0x41, + 0x39, 0xc9, 0x69, 0x0e, 0x31, 0x2d, 0x5b, 0x04, 0xcb, 0x58, 0x25, 0x7a, 0x32, 0x87, 0xb8, 0x64, + 0x3b, 0xa8, 0x77, 0x2e, 0x3a, 0x26, 0x34, 0xa8, 0x88, 0x53, 0x8e, 0x03, 0x7e, 0xa7, 0xd9, 0xad, + 0x28, 0x92, 0x75, 0x7a, 0xbf, 0x46, 0x3d, 0xae, 0xe8, 0x70, 0xac, 0xed, 0xa9, 0xe7, 0x30, 0xdb, + 0xa3, 0xf8, 0x0d, 0x48, 0xf9, 0x4d, 0xd2, 0x68, 0x06, 0xcd, 0x8d, 0xe4, 0xa7, 0xd4, 0x48, 0x02, + 0x54, 0x3f, 0xad, 0x30, 0xf4, 0xe8, 0xc9, 0xf4, 0x80, 0x2e, 0x53, 0x14, 0x13, 0xa6, 0x44, 0xcd, + 0x6b, 0x96, 0x4d, 0xb6, 0x2c, 0xbe, 0x53, 0x74, 0x59, 0xdd, 0xda, 0xa4, 0x6e, 0xd0, 0x14, 0x5f, + 0x03, 0x68, 0x41, 0x95, 0x1d, 0xce, 0xa9, 0x3e, 0x2f, 0x6a, 0x93, 0x17, 0xd5, 0xe7, 0x5e, 0xf2, + 0xa2, 0x16, 0x89, 0x49, 0x65, 0xae, 0x1e, 0xca, 0x54, 0x7e, 0x40, 0x90, 0x8d, 0xeb, 0x24, 0x81, + 0xbc, 0x0b, 0xb8, 0x22, 0x0f, 0x4b, 0x4e, 0x70, 0x9a, 0x46, 0x33, 0x87, 0xe6, 0x46, 0xf2, 0xb3, + 0x31, 0xa0, 0x3a, 0xab, 0xe9, 0x93, 0x95, 0xce, 0xfa, 0xf8, 0x7a, 0x1b, 0x84, 0x41, 0x01, 0x61, + 0x76, 0x57, 0x08, 0xfe, 0x50, 0x6d, 0x18, 0x96, 0xe1, 0x54, 0x24, 0x84, 0x80, 0xab, 0xd3, 0x30, + 0x56, 0x71, 0x4a, 0x06, 0x2f, 0x97, 0x9c, 0x7b, 0xa5, 0x2a, 0x6d, 0x08, 0xba, 0x86, 0x75, 0xa8, + 0x38, 0x05, 0x5e, 0x2e, 0xde, 0x5b, 0xa5, 0x0d, 0xa5, 0x16, 0xc3, 0xf7, 0x73, 0x12, 0x36, 0x60, + 0xb2, 0x8b, 0x04, 0x49, 0x7b, 0x62, 0x0e, 0x26, 0x3a, 0x39, 0x50, 0xbe, 0x42, 0x90, 0x11, 0x7d, + 0x0b, 0x1b, 0x2b, 0x57, 0xe9, 0x16, 0x35, 0x7d, 0x99, 0x07, 0x83, 0x17, 0x20, 0xe5, 0x71, 0xc2, + 0x6b, 0xbe, 0x84, 0xc6, 0xf3, 0x0b, 0x31, 0x9d, 0xda, 0xb2, 0xd7, 0x45, 0x86, 0x2e, 0x33, 0x3b, + 0x84, 0x32, 0xb8, 0x67, 0xa1, 0x7c, 0x8b, 0xe0, 0x64, 0xe4, 0xa8, 0x92, 0xa0, 0x35, 0x38, 0xda, + 0x64, 0x78, 0xb3, 0x75, 0x24, 0x25, 0x72, 0x26, 0xc9, 0xd0, 0xfa, 0xb8, 0xc1, 0xcb, 0xa1, 0xb2, + 0x07, 0x27, 0x8e, 0x0a, 0xcc, 0x47, 0xde, 0x6c, 0x91, 0x7d, 0x44, 0xdd, 0x65, 0xbe, 0x4a, 0x2d, + 0xb3, 0xca, 0x93, 0x2b, 0x05, 0xbf, 0x04, 0xa9, 0xaa, 0xc8, 0x11, 0x43, 0x0d, 0xe9, 0xf2, 0x97, + 0xf2, 0x36, 0x2c, 0x24, 0xe9, 0x23, 0xd9, 0x3a, 0x0d, 0xa3, 0x75, 0xc6, 0x2d, 0xdb, 0x2c, 0x39, + 0xcd, 0x73, 0xd1, 0x67, 0x48, 0x1f, 0xf1, 0x9f, 0x89, 0x14, 0x65, 0x0d, 0xe6, 0x22, 0x0b, 0xae, + 0xd4, 0x5c, 0x97, 0xda, 0x5c, 0x04, 0xf5, 0xa1, 0xf0, 0x38, 0x1e, 0xda, 0xcb, 0xc9, 0xf1, 0x5a, + 0x20, 0x51, 0x18, 0x64, 0xd7, 0xd8, 0x83, 0xdd, 0x63, 0x7f, 0x8a, 0xe0, 0xbc, 0x68, 0xb4, 0x5c, + 0xe6, 0x56, 0x9d, 0x76, 0xd9, 0x4a, 0x27, 0xe5, 0x71, 0xad, 0x0e, 0x4a, 0xb7, 0x3f, 0x23, 0x78, + 0x25, 0xd9, 0x3c, 0x12, 0xfb, 0x07, 0x3d, 0xec, 0x4e, 0x4b, 0xf8, 0xaa, 0xdf, 0xb5, 0x78, 0x75, + 0x8d, 0x72, 0xf2, 0xbf, 0xda, 0xde, 0x94, 0x7c, 0x21, 0x05, 0x30, 0xc2, 0xe9, 0x66, 0x1b, 0xb1, + 0xca, 0x45, 0xe9, 0x8a, 0x5d, 0xc7, 0xbd, 0xef, 0x58, 0xf9, 0x02, 0xc1, 0x6c, 0xa4, 0x52, 0x22, + 0x0c, 0x2a, 0xc1, 0xfb, 0x72, 0x50, 0xf7, 0xf8, 0x1b, 0x8a, 0x79, 0x1f, 0xa2, 0xcc, 0xe8, 0x43, + 0x38, 0x11, 0x32, 0x23, 0xe6, 0x46, 0xd8, 0x92, 0xba, 0xab, 0x2d, 0xb1, 0xb6, 0xd2, 0x2f, 0xb7, + 0x0c, 0xaa, 0xed, 0xe0, 0xe0, 0xee, 0xf3, 0x26, 0x9c, 0xe8, 0x36, 0xd8, 0x80, 0xe9, 0x57, 0xe1, + 0x98, 0x1c, 0xb2, 0xc4, 0x1b, 0xa5, 0x2a, 0xf1, 0xaa, 0x21, 0xbe, 0x27, 0xe4, 0xd1, 0x46, 0x63, + 0x95, 0x78, 0xd5, 0xe6, 0xdb, 0xfe, 0xfd, 0x50, 0xd4, 0x87, 0x25, 0x64, 0xd6, 0x29, 0xff, 0xd2, + 0x44, 0x81, 0xd1, 0xc2, 0xc5, 0x5f, 0x9f, 0x4c, 0xe7, 0x4d, 0x8b, 0x57, 0x6b, 0x86, 0x5a, 0x66, + 0xdb, 0x9a, 0xa4, 0xa6, 0x5c, 0x25, 0x96, 0x1d, 0xfc, 0xd0, 0xf8, 0x8e, 0x43, 0x3d, 0xb5, 0x70, + 0xa3, 0xb8, 0xb8, 0x74, 0xa1, 0x58, 0x33, 0x6e, 0xd1, 0x1d, 0xfd, 0xb0, 0xd1, 0xbc, 0x66, 0xfc, + 0x3e, 0x8c, 0xb7, 0x64, 0xb0, 0x65, 0x79, 0x4d, 0x6f, 0x3c, 0xb4, 0x8f, 0xb2, 0x23, 0x52, 0x3f, + 0xb7, 0x2d, 0xa1, 0xb1, 0x51, 0x8f, 0x13, 0x97, 0x97, 0xa4, 0x5a, 0x0f, 0xf9, 0x9e, 0x23, 0x9e, + 0xf9, 0x92, 0xc6, 0x53, 0x00, 0xd4, 0xde, 0x0c, 0x02, 0x86, 0x44, 0xc0, 0x30, 0xb5, 0xa5, 0xe2, + 0xf1, 0x49, 0x18, 0xe6, 0x8c, 0x93, 0xad, 0x92, 0x47, 0x78, 0xfa, 0xb0, 0x38, 0x3d, 0x22, 0x1e, + 0xac, 0x13, 0x8e, 0xcf, 0xc0, 0x78, 0x98, 0x58, 0xda, 0x48, 0xa7, 0x04, 0xa7, 0xa3, 0x2d, 0x4e, + 0x69, 0x03, 0xdf, 0x81, 0xb1, 0x32, 0xab, 0x53, 0x9b, 0xd8, 0xbc, 0xe4, 0x59, 0xa6, 0x97, 0x7e, + 0x41, 0x88, 0xe8, 0x42, 0x8c, 0x88, 0x56, 0x64, 0xec, 0xf2, 0x26, 0x71, 0x38, 0x73, 0xd7, 0x2d, + 0xd3, 0x26, 0xbc, 0xe6, 0x52, 0x4f, 0x1f, 0x0d, 0xca, 0xac, 0x5b, 0xa6, 0xd7, 0x7c, 0x07, 0x89, + 0xb0, 0xa5, 0xf4, 0x91, 0x19, 0x34, 0x77, 0x44, 0x97, 0xbf, 0xf0, 0x59, 0x18, 0xaf, 0xd9, 0x06, + 0xb3, 0x37, 0xc5, 0x58, 0xd6, 0x36, 0x4d, 0x0f, 0xcf, 0xa0, 0xb9, 0x31, 0x7d, 0xec, 0xf9, 0xd3, + 0x0d, 0x6b, 0x9b, 0xe2, 0xbb, 0x30, 0x59, 0xb3, 0x5b, 0xd2, 0x2e, 0x59, 0x76, 0x85, 0xa5, 0x41, + 0x28, 0xb0, 0xc7, 0xaa, 0x70, 0x27, 0x94, 0x72, 0xc3, 0xae, 0x30, 0x7d, 0xa2, 0xd6, 0xf1, 0x24, + 0xff, 0xcf, 0x51, 0x38, 0x2c, 0xe4, 0x83, 0x3f, 0x41, 0x90, 0xf2, 0x37, 0x54, 0x3c, 0x1f, 0x53, + 0xb2, 0x7b, 0x25, 0xce, 0x2c, 0x24, 0x09, 0xf5, 0xb5, 0xa8, 0x9c, 0xfd, 0xf8, 0xa7, 0x3f, 0x3e, + 0x1f, 0x9c, 0xc6, 0x53, 0x5a, 0xaf, 0x4d, 0x1d, 0x7f, 0x83, 0x60, 0xb2, 0xcb, 0xbc, 0xf1, 0x52, + 0xaf, 0x46, 0x71, 0xcb, 0x73, 0xe6, 0xb5, 0x3e, 0xb3, 0xe4, 0xa4, 0x39, 0x31, 0xe9, 0x79, 0x3c, + 0x1f, 0x33, 0x69, 0xf7, 0x67, 0x03, 0xff, 0x88, 0x60, 0xa2, 0xb3, 0x20, 0x5e, 0xec, 0xa7, 0x7d, + 0x30, 0xf3, 0x52, 0x7f, 0x49, 0x72, 0xe4, 0x75, 0x31, 0xf2, 0x1a, 0xbe, 0x95, 0x78, 0x64, 0xed, + 0x41, 0x9b, 0xa3, 0x3f, 0xec, 0x0e, 0xc1, 0x5f, 0x22, 0x18, 0x6f, 0xdf, 0x02, 0x71, 0xae, 0xd7, + 0x74, 0x91, 0xcb, 0x6d, 0x26, 0xdf, 0x4f, 0x8a, 0x84, 0xa3, 0x0a, 0x38, 0x73, 0xf8, 0x9c, 0x16, + 0xfb, 0x3f, 0xb6, 0xb0, 0xd5, 0xe3, 0x3f, 0x11, 0x4c, 0xef, 0xf2, 0xdd, 0xc7, 0x85, 0x5e, 0x73, + 0x24, 0x5b, 0x62, 0x32, 0x2b, 0xfb, 0xaa, 0x21, 0xc1, 0xbd, 0x2e, 0xc0, 0x2d, 0xe1, 0x7c, 0x1f, + 0x77, 0xe5, 0x5b, 0xde, 0x43, 0xfc, 0x2f, 0x82, 0xa9, 0x9e, 0x9b, 0x27, 0xbe, 0xd2, 0x8f, 0x7e, + 0xa2, 0x96, 0xe3, 0xcc, 0xf2, 0x3e, 0x2a, 0x48, 0x88, 0x45, 0x01, 0xf1, 0x26, 0x5e, 0xdd, 0xbb, + 0x1c, 0xc5, 0xe2, 0xd9, 0x02, 0xfe, 0x17, 0x82, 0x53, 0xbd, 0x56, 0x5a, 0x7c, 0xb9, 0x9f, 0xa9, + 0x23, 0x76, 0xeb, 0xcc, 0x95, 0xbd, 0x17, 0x90, 0xa8, 0xaf, 0x0b, 0xd4, 0xcb, 0xf8, 0xf2, 0x3e, + 0x51, 0xe3, 0xaf, 0x11, 0x1c, 0xed, 0x58, 0xe7, 0x70, 0x7e, 0x57, 0xe9, 0x75, 0xad, 0x86, 0x99, + 0xc5, 0xbe, 0x72, 0x24, 0x0a, 0x4d, 0xa0, 0x98, 0xc7, 0xb3, 0x31, 0x28, 0x48, 0x90, 0x27, 0xbf, + 0xc3, 0xf8, 0x6f, 0x04, 0x27, 0x7b, 0x2c, 0x6b, 0xf8, 0x52, 0x3f, 0xc4, 0x46, 0x18, 0xc8, 0xe5, + 0x3d, 0xe7, 0x4b, 0x44, 0x6b, 0x02, 0xd1, 0x75, 0xfc, 0xd6, 0xde, 0xef, 0x25, 0x6c, 0x36, 0xdf, + 0x21, 0x18, 0x6b, 0xf3, 0x2d, 0x7c, 0x21, 0xb1, 0xc5, 0x05, 0x98, 0x72, 0x7d, 0x64, 0x48, 0x14, + 0x57, 0x05, 0x8a, 0x4b, 0xf8, 0xcd, 0x64, 0x9e, 0xa8, 0x3d, 0x88, 0xd8, 0x23, 0x1f, 0x16, 0x6e, + 0x3f, 0x7a, 0x9a, 0x45, 0x8f, 0x9f, 0x66, 0xd1, 0xef, 0x4f, 0xb3, 0xe8, 0xb3, 0x67, 0xd9, 0x81, + 0xc7, 0xcf, 0xb2, 0x03, 0xbf, 0x3c, 0xcb, 0x0e, 0xbc, 0xb7, 0xeb, 0x02, 0xd7, 0x08, 0x37, 0x14, + 0xdb, 0x9c, 0x91, 0x12, 0x7f, 0x2f, 0x5b, 0xfc, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x11, 0x52, + 0x4c, 0x17, 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2373,7 +2384,12 @@ func (m *QueryBTCDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, err i = encodeVarintQuery(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x4a + dAtA[i] = 0x52 + } + if m.UnbondingTime != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x48 } if m.Active { i-- @@ -2773,6 +2789,9 @@ func (m *QueryBTCDelegationResponse) Size() (n int) { if m.Active { n += 2 } + if m.UnbondingTime != 0 { + n += 1 + sovQuery(uint64(m.UnbondingTime)) + } if m.UndelegationInfo != nil { l = m.UndelegationInfo.Size() n += 1 + l + sovQuery(uint64(l)) @@ -4765,6 +4784,25 @@ func (m *QueryBTCDelegationResponse) Unmarshal(dAtA []byte) error { } m.Active = bool(v != 0) case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) + } + m.UnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UndelegationInfo", wireType) } diff --git a/x/btcstaking/types/tx.pb.go b/x/btcstaking/types/tx.pb.go index 8f28ec3c5..be9cf29ba 100644 --- a/x/btcstaking/types/tx.pb.go +++ b/x/btcstaking/types/tx.pb.go @@ -175,12 +175,16 @@ type MsgCreateBTCDelegation struct { // The staking tx output further needs signatures from covenant and finality provider in // order to be spendable. DelegatorSlashingSig *github_com_babylonchain_babylon_types.BIP340Signature `protobuf:"bytes,10,opt,name=delegator_slashing_sig,json=delegatorSlashingSig,proto3,customtype=github.com/babylonchain/babylon/types.BIP340Signature" json:"delegator_slashing_sig,omitempty"` - /// fields related to on-demand unbonding + // unbonding_time is the time lock used when funds are being unbonded. It is be used in: + // - unbonding transaction, time lock spending path + // - staking slashing transaction, change output + // - unbonding slashing transaction, change output + // It must be smaller than math.MaxUInt16 and larger that max(MinUnbondingTime, CheckpointFinalizationTimeout) + UnbondingTime uint32 `protobuf:"varint,11,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` + // fields related to unbonding transaction // unbonding_tx is a bitcoin unbonding transaction i.e transaction that spends // staking output and sends it to the unbonding output - UnbondingTx []byte `protobuf:"bytes,11,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` - // unbonding_time is the time lock used in unbonding transaction - UnbondingTime uint32 `protobuf:"varint,12,opt,name=unbonding_time,json=unbondingTime,proto3" json:"unbonding_time,omitempty"` + UnbondingTx []byte `protobuf:"bytes,12,opt,name=unbonding_tx,json=unbondingTx,proto3" json:"unbonding_tx,omitempty"` // unbonding_value is amount of satoshis locked in unbonding output. // NOTE: staking_value and unbonding_value could be different because of the difference between the fee for staking tx and that for unbonding UnbondingValue int64 `protobuf:"varint,13,opt,name=unbonding_value,json=unbondingValue,proto3" json:"unbonding_value,omitempty"` @@ -266,18 +270,18 @@ func (m *MsgCreateBTCDelegation) GetStakingTx() *types1.TransactionInfo { return nil } -func (m *MsgCreateBTCDelegation) GetUnbondingTx() []byte { +func (m *MsgCreateBTCDelegation) GetUnbondingTime() uint32 { if m != nil { - return m.UnbondingTx + return m.UnbondingTime } - return nil + return 0 } -func (m *MsgCreateBTCDelegation) GetUnbondingTime() uint32 { +func (m *MsgCreateBTCDelegation) GetUnbondingTx() []byte { if m != nil { - return m.UnbondingTime + return m.UnbondingTx } - return 0 + return nil } func (m *MsgCreateBTCDelegation) GetUnbondingValue() int64 { @@ -804,41 +808,41 @@ var fileDescriptor_4baddb53e97f38f2 = []byte{ 0xff, 0x7f, 0x78, 0x54, 0xc8, 0xd6, 0x5a, 0x7b, 0x4d, 0xbe, 0xd3, 0x9a, 0xaa, 0x40, 0x83, 0xdf, 0x22, 0x81, 0xad, 0xae, 0xd7, 0x13, 0xc4, 0x6e, 0x07, 0xd6, 0x14, 0x1b, 0x12, 0xb8, 0xe6, 0x9f, 0x1f, 0x1e, 0x15, 0x6e, 0x1d, 0x47, 0xaa, 0x26, 0x36, 0x4c, 0x8d, 0x8d, 0x6d, 0xa4, 0x9e, 0x09, - 0x88, 0x7d, 0xdf, 0x4d, 0x6c, 0x38, 0xb2, 0x8d, 0xcd, 0x0e, 0x31, 0xbb, 0x3c, 0xca, 0x8c, 0xe3, - 0x46, 0xcd, 0x04, 0x6b, 0xad, 0xa9, 0xf8, 0x31, 0xe4, 0x22, 0x10, 0x47, 0xdb, 0x75, 0x57, 0xdb, - 0x6c, 0x08, 0x72, 0xd4, 0xbd, 0x0a, 0x1b, 0x21, 0xcc, 0xd3, 0x37, 0xeb, 0xea, 0x1b, 0x5a, 0x7b, - 0x0a, 0xdf, 0x87, 0xb3, 0x21, 0x30, 0xaa, 0x50, 0x2e, 0x4e, 0xa1, 0xd3, 0x01, 0x3e, 0x5c, 0x14, - 0x9f, 0x08, 0x50, 0x0c, 0xb5, 0x5a, 0xc2, 0xe8, 0xa8, 0xb6, 0x71, 0x52, 0xd5, 0x2e, 0x04, 0x2e, - 0x0e, 0x16, 0x63, 0x68, 0x62, 0x63, 0xfe, 0x08, 0x28, 0x42, 0x7e, 0xf9, 0x70, 0x07, 0xf3, 0xff, - 0x77, 0x02, 0xc4, 0x3a, 0x35, 0xee, 0x75, 0xbb, 0x7b, 0x64, 0x82, 0x4c, 0xcd, 0x64, 0x4d, 0x6c, - 0xd0, 0xd8, 0xd9, 0x7f, 0x00, 0x09, 0x3e, 0xf3, 0xef, 0x3f, 0x24, 0x09, 0x6b, 0x20, 0x5e, 0x81, - 0x8d, 0xb0, 0xa7, 0xdb, 0x7d, 0x8d, 0xf6, 0xbd, 0x73, 0x5c, 0xcd, 0x06, 0xdd, 0xfa, 0x50, 0xa3, - 0x7d, 0xb1, 0x0c, 0x9b, 0x91, 0x7a, 0x38, 0x02, 0x52, 0x69, 0xd5, 0x19, 0x51, 0x35, 0x17, 0xf6, - 0xa8, 0x1b, 0xb1, 0x0e, 0x9b, 0xd1, 0xb6, 0x71, 0xb5, 0x4e, 0x9d, 0x54, 0xeb, 0x5c, 0xa4, 0xeb, - 0x9c, 0xde, 0xbc, 0x0b, 0x72, 0x10, 0xce, 0xa2, 0x37, 0x2a, 0xa5, 0xdd, 0xc0, 0xce, 0xf9, 0x88, - 0x83, 0x39, 0x5b, 0x3a, 0x5f, 0x99, 0xf3, 0x20, 0xff, 0x5b, 0xf6, 0xa0, 0x2a, 0xbf, 0x09, 0xb0, - 0x59, 0xa7, 0x46, 0xad, 0xb5, 0x77, 0x60, 0xf2, 0x72, 0xa3, 0xd8, 0x9a, 0x2c, 0xd1, 0x32, 0xb1, - 0x4c, 0xcb, 0x65, 0x0a, 0x25, 0x3f, 0xb0, 0x42, 0xf3, 0x49, 0xca, 0x20, 0x2d, 0x66, 0x11, 0xa4, - 0xf8, 0x8b, 0x00, 0xe7, 0xeb, 0xd4, 0x68, 0xa2, 0x21, 0xd2, 0x19, 0x9e, 0x20, 0xbf, 0x87, 0xef, - 0x3b, 0xf7, 0x93, 0xa9, 0x9f, 0x3c, 0xdd, 0x1d, 0x38, 0x6d, 0x23, 0x9d, 0x4c, 0x90, 0x8d, 0xba, - 0x6d, 0x7e, 0xca, 0xd3, 0x81, 0x97, 0xb1, 0xba, 0x19, 0x6c, 0x3d, 0x70, 0x4e, 0xec, 0xe6, 0x60, - 0x3e, 0xf0, 0x2b, 0x70, 0xf9, 0xbf, 0x62, 0x0b, 0x92, 0xf8, 0x59, 0x80, 0x8d, 0x3a, 0x35, 0x0e, - 0xac, 0xae, 0xc6, 0x50, 0xc3, 0x7d, 0x95, 0x89, 0xb7, 0x61, 0x4d, 0x1b, 0xb3, 0x3e, 0xb1, 0x31, - 0x9b, 0x79, 0xa1, 0xd7, 0xa4, 0x97, 0xcf, 0x76, 0xce, 0xf0, 0x0b, 0xf2, 0x5e, 0xb7, 0x6b, 0x23, - 0x4a, 0x9b, 0xcc, 0xc6, 0xa6, 0xa1, 0x86, 0x50, 0xf1, 0x2e, 0xa4, 0xbd, 0x77, 0x1d, 0xbf, 0x52, - 0x2f, 0xc4, 0xdd, 0x8c, 0x2e, 0xa8, 0xb6, 0xfa, 0xfc, 0xa8, 0xb0, 0xa2, 0x72, 0x93, 0x3b, 0x39, - 0x27, 0xfa, 0x90, 0xac, 0xb4, 0x0d, 0xe7, 0x16, 0xe2, 0xf2, 0x63, 0xde, 0xfd, 0x2b, 0x05, 0xc9, - 0x3a, 0x35, 0xc4, 0x1f, 0x04, 0xd8, 0x8a, 0x79, 0xbf, 0xdd, 0x88, 0x71, 0x1d, 0xfb, 0x9c, 0x90, - 0x3f, 0x3b, 0xae, 0x85, 0x1f, 0x8e, 0xf8, 0x1d, 0x9c, 0x5e, 0xf6, 0xf8, 0xd8, 0x79, 0x1b, 0xe1, - 0x1c, 0x5c, 0xbe, 0x75, 0x2c, 0x78, 0xe0, 0x9c, 0xc0, 0xc6, 0xe2, 0xc9, 0x77, 0x2d, 0x9e, 0x69, - 0x01, 0x2a, 0x57, 0xdf, 0x19, 0x1a, 0x38, 0xc4, 0x90, 0x9d, 0x1f, 0xea, 0xab, 0xf1, 0x1c, 0x73, - 0x40, 0xb9, 0xf2, 0x8e, 0xc0, 0xc0, 0xd5, 0x8f, 0x02, 0x6c, 0xc7, 0x4f, 0xd7, 0xcd, 0x78, 0xba, - 0x58, 0x23, 0xf9, 0xee, 0x7b, 0x18, 0x05, 0xf1, 0xf4, 0x60, 0x7d, 0x6e, 0x4e, 0xae, 0xc4, 0x93, - 0x45, 0x71, 0xb2, 0xf2, 0x6e, 0x38, 0xdf, 0x8f, 0x9c, 0xfa, 0xfe, 0xcd, 0xd3, 0xeb, 0x42, 0xed, - 0xf1, 0xf3, 0x57, 0x79, 0xe1, 0xc5, 0xab, 0xbc, 0xf0, 0xe7, 0xab, 0xbc, 0xf0, 0xd3, 0xeb, 0xfc, - 0xca, 0x8b, 0xd7, 0xf9, 0x95, 0xdf, 0x5f, 0xe7, 0x57, 0xbe, 0x79, 0xeb, 0x9d, 0x35, 0x8d, 0xfe, - 0xed, 0x71, 0x8f, 0xbd, 0x4e, 0xda, 0xfd, 0xdb, 0x73, 0xf3, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xb2, 0xee, 0xb9, 0x19, 0x36, 0x0e, 0x00, 0x00, + 0x88, 0x7d, 0xdf, 0x4d, 0x6c, 0x88, 0x1f, 0x43, 0x6e, 0x6c, 0x76, 0x88, 0xd9, 0x0d, 0x84, 0xcb, + 0xb8, 0xc2, 0x65, 0x83, 0x55, 0x57, 0xba, 0x8b, 0xb0, 0x1e, 0x81, 0x4d, 0xa5, 0x75, 0x27, 0x1a, + 0x35, 0x13, 0x82, 0xa6, 0xe2, 0x55, 0xd8, 0x08, 0x21, 0x9e, 0xbe, 0x59, 0x57, 0xdf, 0xd0, 0x81, + 0xa7, 0xf0, 0x7d, 0x38, 0x1b, 0x02, 0xa3, 0x0a, 0xe5, 0xe2, 0x14, 0x3a, 0x1d, 0xe0, 0xc3, 0x45, + 0xf1, 0x89, 0x00, 0xc5, 0x50, 0xab, 0x25, 0x8c, 0x8e, 0x6a, 0x1b, 0x27, 0x55, 0xed, 0x42, 0xe0, + 0xe2, 0x60, 0x31, 0x86, 0x26, 0x36, 0xe6, 0x8f, 0x80, 0x22, 0xe4, 0x97, 0x0f, 0x77, 0x30, 0xff, + 0x7f, 0x27, 0x40, 0xac, 0x53, 0xe3, 0x5e, 0xb7, 0xbb, 0x47, 0x26, 0xc8, 0xd4, 0x4c, 0xd6, 0xc4, + 0x06, 0x8d, 0x9d, 0xfd, 0x07, 0x90, 0xe0, 0x33, 0xff, 0xfe, 0x43, 0x92, 0xb0, 0x06, 0xe2, 0x15, + 0xd8, 0x08, 0x7b, 0xba, 0xdd, 0xd7, 0x68, 0xdf, 0x3b, 0xc7, 0xd5, 0x6c, 0xd0, 0xad, 0x0f, 0x35, + 0xda, 0x17, 0xcb, 0xb0, 0x19, 0xa9, 0x87, 0x23, 0x20, 0x95, 0x56, 0x9d, 0x11, 0x55, 0x73, 0x61, + 0x8f, 0xba, 0x11, 0xeb, 0xb0, 0x19, 0xed, 0x07, 0x57, 0xeb, 0xd4, 0x49, 0xb5, 0xce, 0x45, 0xda, + 0xc9, 0xe9, 0xcd, 0xbb, 0x20, 0x07, 0xe1, 0x2c, 0x7a, 0xa3, 0x52, 0xda, 0x0d, 0xec, 0x9c, 0x8f, + 0x38, 0x98, 0xb3, 0xa5, 0xf3, 0x95, 0x39, 0x0f, 0xf2, 0xbf, 0x65, 0x0f, 0xaa, 0xf2, 0x9b, 0x00, + 0x9b, 0x75, 0x6a, 0xd4, 0x5a, 0x7b, 0x07, 0x26, 0x2f, 0x37, 0x8a, 0xad, 0xc9, 0x12, 0x2d, 0x13, + 0xcb, 0xb4, 0x5c, 0xa6, 0x50, 0xf2, 0x03, 0x2b, 0x34, 0x9f, 0xa4, 0x0c, 0xd2, 0x62, 0x16, 0x41, + 0x8a, 0xbf, 0x08, 0x70, 0xbe, 0x4e, 0x8d, 0x26, 0x1a, 0x22, 0x9d, 0xe1, 0x09, 0xf2, 0x7b, 0xf8, + 0xbe, 0x73, 0x3f, 0x99, 0xfa, 0xc9, 0xd3, 0xdd, 0x81, 0xd3, 0x36, 0xd2, 0xc9, 0x04, 0xd9, 0xa8, + 0xdb, 0xe6, 0xa7, 0x3c, 0x1d, 0x78, 0x19, 0xab, 0x9b, 0xc1, 0xd6, 0x03, 0xe7, 0xc4, 0x6e, 0x0e, + 0xe6, 0x03, 0xbf, 0x02, 0x97, 0xff, 0x2b, 0xb6, 0x20, 0x89, 0x9f, 0x05, 0xd8, 0xa8, 0x53, 0xe3, + 0xc0, 0xea, 0x6a, 0x0c, 0x35, 0xdc, 0x57, 0x99, 0x78, 0x1b, 0xd6, 0xb4, 0x31, 0xeb, 0x13, 0x1b, + 0xb3, 0x99, 0x17, 0x7a, 0x4d, 0x7a, 0xf9, 0x6c, 0xe7, 0x0c, 0xbf, 0x20, 0xef, 0x75, 0xbb, 0x36, + 0xa2, 0xb4, 0xc9, 0x6c, 0x6c, 0x1a, 0x6a, 0x08, 0x15, 0xef, 0x42, 0xda, 0x7b, 0xd7, 0xf1, 0x2b, + 0xf5, 0x42, 0xdc, 0xcd, 0xe8, 0x82, 0x6a, 0xab, 0xcf, 0x8f, 0x0a, 0x2b, 0x2a, 0x37, 0xb9, 0x93, + 0x73, 0xa2, 0x0f, 0xc9, 0x4a, 0xdb, 0x70, 0x6e, 0x21, 0x2e, 0x3f, 0xe6, 0xdd, 0xbf, 0x52, 0x90, + 0xac, 0x53, 0x43, 0xfc, 0x41, 0x80, 0xad, 0x98, 0xf7, 0xdb, 0x8d, 0x18, 0xd7, 0xb1, 0xcf, 0x09, + 0xf9, 0xb3, 0xe3, 0x5a, 0xf8, 0xe1, 0x88, 0xdf, 0xc1, 0xe9, 0x65, 0x8f, 0x8f, 0x9d, 0xb7, 0x11, + 0xce, 0xc1, 0xe5, 0x5b, 0xc7, 0x82, 0x07, 0xce, 0x09, 0x6c, 0x2c, 0x9e, 0x7c, 0xd7, 0xe2, 0x99, + 0x16, 0xa0, 0x72, 0xf5, 0x9d, 0xa1, 0x81, 0x43, 0x0c, 0xd9, 0xf9, 0xa1, 0xbe, 0x1a, 0xcf, 0x31, + 0x07, 0x94, 0x2b, 0xef, 0x08, 0x0c, 0x5c, 0xfd, 0x28, 0xc0, 0x76, 0xfc, 0x74, 0xdd, 0x8c, 0xa7, + 0x8b, 0x35, 0x92, 0xef, 0xbe, 0x87, 0x51, 0x10, 0x4f, 0x0f, 0xd6, 0xe7, 0xe6, 0xe4, 0x4a, 0x3c, + 0x59, 0x14, 0x27, 0x2b, 0xef, 0x86, 0xf3, 0xfd, 0xc8, 0xa9, 0xef, 0xdf, 0x3c, 0xbd, 0x2e, 0xd4, + 0x1e, 0x3f, 0x7f, 0x95, 0x17, 0x5e, 0xbc, 0xca, 0x0b, 0x7f, 0xbe, 0xca, 0x0b, 0x3f, 0xbd, 0xce, + 0xaf, 0xbc, 0x78, 0x9d, 0x5f, 0xf9, 0xfd, 0x75, 0x7e, 0xe5, 0x9b, 0xb7, 0xde, 0x59, 0xd3, 0xe8, + 0xdf, 0x1e, 0xf7, 0xd8, 0xeb, 0xa4, 0xdd, 0xbf, 0x3d, 0x37, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, + 0x14, 0xbd, 0x7a, 0x11, 0x36, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1277,17 +1281,17 @@ func (m *MsgCreateBTCDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x68 } - if m.UnbondingTime != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.UnbondingTime)) - i-- - dAtA[i] = 0x60 - } if len(m.UnbondingTx) > 0 { i -= len(m.UnbondingTx) copy(dAtA[i:], m.UnbondingTx) i = encodeVarintTx(dAtA, i, uint64(len(m.UnbondingTx))) i-- - dAtA[i] = 0x5a + dAtA[i] = 0x62 + } + if m.UnbondingTime != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.UnbondingTime)) + i-- + dAtA[i] = 0x58 } if m.DelegatorSlashingSig != nil { { @@ -1821,13 +1825,13 @@ func (m *MsgCreateBTCDelegation) Size() (n int) { l = m.DelegatorSlashingSig.Size() n += 1 + l + sovTx(uint64(l)) } + if m.UnbondingTime != 0 { + n += 1 + sovTx(uint64(m.UnbondingTime)) + } l = len(m.UnbondingTx) if l > 0 { n += 1 + l + sovTx(uint64(l)) } - if m.UnbondingTime != 0 { - n += 1 + sovTx(uint64(m.UnbondingTime)) - } if m.UnbondingValue != 0 { n += 1 + sovTx(uint64(m.UnbondingValue)) } @@ -2646,6 +2650,25 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) + } + m.UnbondingTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnbondingTime |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 12: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTx", wireType) } @@ -2679,25 +2702,6 @@ func (m *MsgCreateBTCDelegation) Unmarshal(dAtA []byte) error { m.UnbondingTx = []byte{} } iNdEx = postIndex - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UnbondingTime", wireType) - } - m.UnbondingTime = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.UnbondingTime |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } case 13: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field UnbondingValue", wireType) From 2490313f96274db8e16d031eafab290a58a72000 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 5 Jan 2024 12:51:02 +0100 Subject: [PATCH 143/202] Update to latest wasmd 0.50.0 (#164) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b840cdc10..ed5eabf65 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ go 1.21 module github.com/babylonchain/babylon require ( - github.com/CosmWasm/wasmd v0.50.0-rc.2 + github.com/CosmWasm/wasmd v0.50.0 github.com/btcsuite/btcd v0.23.5-0.20230711222809-7faa9b266231 github.com/cometbft/cometbft v0.38.2 github.com/cometbft/cometbft-db v0.8.0 // indirect diff --git a/go.sum b/go.sum index 1e8c2750f..1b1bde36a 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CosmWasm/wasmd v0.50.0-rc.2 h1:CK7bRrAxYo8xooGhAwCRWyJqog+/M/7G1/p1BQVDNNU= -github.com/CosmWasm/wasmd v0.50.0-rc.2/go.mod h1:KtrZmXmh/V1ZmQ2/3dU6VRuZ09moRDpRqTL3L/mnPIE= +github.com/CosmWasm/wasmd v0.50.0 h1:NVaGqCSTRfb9UTDHJwT6nQIWcb6VjlQl88iI+u1+qjE= +github.com/CosmWasm/wasmd v0.50.0/go.mod h1:UjmShW4l9YxaMytwJZ7IB7MWzHiynSZP3DdWrG0FRtk= github.com/CosmWasm/wasmvm v1.5.0 h1:3hKeT9SfwfLhxTGKH3vXaKFzBz1yuvP8SlfwfQXbQfw= github.com/CosmWasm/wasmvm v1.5.0/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= From 9a9be21c01e565ec46fd152b7720ef8ca551d084 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 9 Jan 2024 14:46:44 +1100 Subject: [PATCH 144/202] chore: fix overflow height in FinalityProviderCurrentPower (#168) --- x/btcstaking/keeper/grpc_query.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 21251838d..2680cc20f 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -147,6 +147,12 @@ func (k Keeper) FinalityProviderCurrentPower(ctx context.Context, req *types.Que // NOTE: it's possible that the voting power is not recorded at the current height, // e.g., `EndBlock` is not reached yet // in this case, we use the last height + + // ensure curHeight > 0 thus won't over flow + if curHeight == 0 { + return &types.QueryFinalityProviderCurrentPowerResponse{Height: 0, VotingPower: 0}, nil + } + curHeight -= 1 power = k.GetVotingPower(sdkCtx, fpBTCPK.MustMarshal(), curHeight) } From 7282d2dcb4ecd0b21bad954d3c1264782451185d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 9 Jan 2024 08:19:44 +0100 Subject: [PATCH 145/202] Upgrade keyring / go-keychain dependency (#170) --- go.mod | 4 +--- go.sum | 12 ++++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ed5eabf65..63849cc4a 100644 --- a/go.mod +++ b/go.mod @@ -96,7 +96,6 @@ require ( github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect - github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect @@ -137,6 +136,7 @@ require ( cloud.google.com/go/storage v1.30.1 // indirect cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/x/nft v0.1.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect @@ -237,8 +237,6 @@ require ( ) replace ( - github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 - // slay the dragonberry github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 diff --git a/go.sum b/go.sum index 1b1bde36a..535e26e4e 100644 --- a/go.sum +++ b/go.sum @@ -222,6 +222,10 @@ cosmossdk.io/x/upgrade v0.1.0/go.mod h1:/6jjNGbiPCNtmA1N+rBtP601sr0g4ZXuj3yC6ClP dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -401,8 +405,6 @@ github.com/cosmos/ibc-go/v8 v8.0.0 h1:QKipnr/NGwc+9L7NZipURvmSIu+nw9jOIWTJuDBqOh github.com/cosmos/ibc-go/v8 v8.0.0/go.mod h1:C6IiJom0F3cIQCD5fKwVPDrDK9j/xTu563AWuOmXois= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= -github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU= -github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -417,7 +419,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -458,7 +459,6 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -794,8 +794,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1068,7 +1066,6 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -1345,7 +1342,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From bfddd3b2c925e54341307d834aadc0a32cef7dac Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Tue, 9 Jan 2024 10:20:36 +0100 Subject: [PATCH 146/202] Add allow list for btc light client - list of addresses of reporters allowed to report headers (#167) * Params support in btclightclient module * Add core logic handling allowlist * Add config for allow list --- app/app.go | 1 + cmd/babylond/cmd/flags.go | 11 + cmd/babylond/cmd/genesis.go | 14 +- cmd/babylond/cmd/testnet.go | 4 +- proto/babylon/btclightclient/v1/genesis.proto | 4 +- proto/babylon/btclightclient/v1/params.proto | 16 + proto/babylon/btclightclient/v1/query.proto | 15 + proto/babylon/btclightclient/v1/tx.proto | 23 + testutil/keeper/btclightclient.go | 11 + x/btclightclient/genesis.go | 6 + x/btclightclient/keeper/grpc_query.go | 9 + x/btclightclient/keeper/keeper.go | 3 + x/btclightclient/keeper/msg_server.go | 47 +- x/btclightclient/keeper/msg_server_test.go | 94 +++- x/btclightclient/keeper/params.go | 31 ++ x/btclightclient/keeper/params_test.go | 21 + x/btclightclient/types/errors.go | 2 + x/btclightclient/types/genesis.go | 5 + x/btclightclient/types/genesis.pb.go | 81 ++- x/btclightclient/types/keys.go | 6 +- x/btclightclient/types/msgs.go | 22 + x/btclightclient/types/params.go | 58 +++ x/btclightclient/types/params.pb.go | 357 ++++++++++++++ x/btclightclient/types/query.pb.go | 462 +++++++++++++++--- x/btclightclient/types/query.pb.gw.go | 65 +++ x/btclightclient/types/tx.pb.go | 429 +++++++++++++++- x/zoneconcierge/types/zoneconcierge.pb.go | 2 +- 27 files changed, 1696 insertions(+), 103 deletions(-) create mode 100644 proto/babylon/btclightclient/v1/params.proto create mode 100644 x/btclightclient/keeper/params.go create mode 100644 x/btclightclient/keeper/params_test.go create mode 100644 x/btclightclient/types/params.go create mode 100644 x/btclightclient/types/params.pb.go diff --git a/app/app.go b/app/app.go index 608ffc745..246b87809 100644 --- a/app/app.go +++ b/app/app.go @@ -601,6 +601,7 @@ func NewBabylonApp( appCodec, runtime.NewKVStoreService(keys[btclightclienttypes.StoreKey]), btcConfig, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) btcCheckpointKeeper := btccheckpointkeeper.NewKeeper( diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 243d36c22..ee7c61488 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -12,6 +12,7 @@ import ( babylonApp "github.com/babylonchain/babylon/app" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" + btcltypes "github.com/babylonchain/babylon/x/btclightclient/types" btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" ) @@ -23,6 +24,7 @@ const ( flagCheckpointTag = "checkpoint-tag" flagBaseBtcHeaderHex = "btc-base-header" flagBaseBtcHeaderHeight = "btc-base-header-height" + flagAllowedReporterAddresses = "allowed-reporter-addresses" flagInflationRateChange = "inflation-rate-change" flagInflationMax = "inflation-max" flagInflationMin = "inflation-min" @@ -51,6 +53,7 @@ type GenesisCLIArgs struct { EpochInterval uint64 BaseBtcHeaderHex string BaseBtcHeaderHeight uint64 + AllowedReporterAddresses []string InflationRateChange float64 InflationMax float64 InflationMin float64 @@ -83,6 +86,7 @@ func addGenesisFlags(cmd *cobra.Command) { // btclightclient args // Genesis header for the simnet cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") + cmd.Flags().String(flagAllowedReporterAddresses, strings.Join(btcltypes.DefaultParams().InsertHeadersAllowList, ","), "addresses of reporters allowed to submit Bitcoin headers to babylon") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") // btcstaking args cmd.Flags().String(flagCovenantPks, strings.Join(btcstypes.DefaultParams().CovenantPksHex(), ","), "Bitcoin staking covenant public keys, comma separated") @@ -117,6 +121,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { epochInterval, _ := cmd.Flags().GetUint64(flagEpochInterval) baseBtcHeaderHex, _ := cmd.Flags().GetString(flagBaseBtcHeaderHex) baseBtcHeaderHeight, _ := cmd.Flags().GetUint64(flagBaseBtcHeaderHeight) + reporterAddresses, _ := cmd.Flags().GetString(flagAllowedReporterAddresses) covenantPks, _ := cmd.Flags().GetString(flagCovenantPks) covenantQuorum, _ := cmd.Flags().GetUint32(flagCovenantQuorum) slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) @@ -139,6 +144,11 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { chainID = "chain-" + tmrand.NewRand().Str(6) } + var allowedReporterAddresses []string = make([]string, 0) + if reporterAddresses != "" { + allowedReporterAddresses = strings.Split(reporterAddresses, ",") + } + genesisTime := time.Unix(genesisTimeUnix, 0) return &GenesisCLIArgs{ @@ -150,6 +160,7 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { EpochInterval: epochInterval, BaseBtcHeaderHeight: baseBtcHeaderHeight, BaseBtcHeaderHex: baseBtcHeaderHex, + AllowedReporterAddresses: allowedReporterAddresses, CovenantPKs: strings.Split(covenantPks, ","), CovenantQuorum: covenantQuorum, SlashingAddress: slashingAddress, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 4d0750de0..08aee3a81 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -71,7 +71,8 @@ Example: genesisParams = TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, + genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.AllowedReporterAddresses, + genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, @@ -230,13 +231,14 @@ type GenesisParams struct { BtcstakingParams btcstakingtypes.Params FinalityParams finalitytypes.Params BtclightclientBaseBtcHeader btclightclienttypes.BTCHeaderInfo + BtclightclientParams btclightclienttypes.Params BlockGasLimit int64 VoteExtensionsEnableHeight int64 } func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, - baseBtcHeaderHeight uint64, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, + baseBtcHeaderHeight uint64, allowedReporters []string, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, minUnbondingTime uint16, minPubRand uint64, inflationRateChange float64, inflationMin float64, inflationMax float64, goalBonded float64, @@ -309,7 +311,15 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 } work := btclightclienttypes.CalcWork(&baseBtcHeader) baseBtcHeaderInfo := btclightclienttypes.NewBTCHeaderInfo(&baseBtcHeader, baseBtcHeader.Hash(), baseBtcHeaderHeight, &work) + + params, err := btclightclienttypes.NewParamsValidate(allowedReporters) + + if err != nil { + panic(err) + } + genParams.BtclightclientBaseBtcHeader = *baseBtcHeaderInfo + genParams.BtclightclientParams = params genParams.BtcstakingParams = btcstakingtypes.DefaultParams() covenantPKsBIP340 := make([]bbn.BIP340PubKey, 0, len(covenantPKs)) diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 10d18b210..bf18f8849 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -102,8 +102,8 @@ Example: genesisParams := TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, - genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, genesisCliArgs.SlashingAddress, - genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, + genesisCliArgs.AllowedReporterAddresses, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, + genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinPubRand, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, diff --git a/proto/babylon/btclightclient/v1/genesis.proto b/proto/babylon/btclightclient/v1/genesis.proto index 4b15f0714..4de7fce3d 100644 --- a/proto/babylon/btclightclient/v1/genesis.proto +++ b/proto/babylon/btclightclient/v1/genesis.proto @@ -3,10 +3,12 @@ package babylon.btclightclient.v1; import "gogoproto/gogo.proto"; import "babylon/btclightclient/v1/btclightclient.proto"; +import "babylon/btclightclient/v1/params.proto"; option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; // GenesisState defines the btclightclient module's genesis state. message GenesisState { - BTCHeaderInfo base_btc_header = 1 [ (gogoproto.nullable) = false ]; + Params params = 1 [(gogoproto.nullable) = false]; + BTCHeaderInfo base_btc_header = 2 [ (gogoproto.nullable) = false ]; } diff --git a/proto/babylon/btclightclient/v1/params.proto b/proto/babylon/btclightclient/v1/params.proto new file mode 100644 index 000000000..ea0f0c709 --- /dev/null +++ b/proto/babylon/btclightclient/v1/params.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package babylon.btclightclient.v1; + +import "gogoproto/gogo.proto"; + + +option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; + +// Params defines the parameters for the module. +message Params { + option (gogoproto.equal) = true; + + // List of addresses which are allowed to insert headers to btc light client + // if the list is empty, any address can insert headers + repeated string insert_headers_allow_list = 1; +} diff --git a/proto/babylon/btclightclient/v1/query.proto b/proto/babylon/btclightclient/v1/query.proto index a4934911a..146cbf601 100644 --- a/proto/babylon/btclightclient/v1/query.proto +++ b/proto/babylon/btclightclient/v1/query.proto @@ -5,11 +5,17 @@ import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "cosmos/base/query/v1beta1/pagination.proto"; import "babylon/btclightclient/v1/btclightclient.proto"; +import "babylon/btclightclient/v1/params.proto"; option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; // Query defines the gRPC querier service. service Query { + // Params queries the parameters of the module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/babylon/btclightclient/v1/params"; + } + // Hashes retrieves the hashes maintained by the module. rpc Hashes(QueryHashesRequest) returns (QueryHashesResponse) { option (google.api.http).get = "/babylon/btclightclient/v1/hashes"; @@ -52,6 +58,15 @@ service Query { } } +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params holds all the parameters of this module. + babylon.btclightclient.v1.Params params = 1 [ (gogoproto.nullable) = false ]; +} + // QueryHashesRequest is request type for the Query/Hashes RPC method. // It involves retrieving all hashes that are maintained by the module. message QueryHashesRequest { diff --git a/proto/babylon/btclightclient/v1/tx.proto b/proto/babylon/btclightclient/v1/tx.proto index 6a10a7c64..1c884ba87 100644 --- a/proto/babylon/btclightclient/v1/tx.proto +++ b/proto/babylon/btclightclient/v1/tx.proto @@ -3,6 +3,8 @@ package babylon.btclightclient.v1; import "gogoproto/gogo.proto"; import "cosmos/msg/v1/msg.proto"; +import "babylon/btclightclient/v1/params.proto"; +import "cosmos_proto/cosmos.proto"; option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; @@ -12,6 +14,9 @@ service Msg { // InsertHeaders adds a batch of headers to the BTC light client chain rpc InsertHeaders(MsgInsertHeaders) returns (MsgInsertHeadersResponse) {}; + + // UpdateParams defines a method for updating btc light client module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } // MsgInsertHeaders defines the message for multiple incoming header bytes @@ -26,3 +31,21 @@ message MsgInsertHeaders { // MsgInsertHeadersResponse defines the response for the InsertHeaders transaction message MsgInsertHeadersResponse {} +// MsgUpdateParams defines a message for updating btc light client module parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params defines the btc light client parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +message MsgUpdateParamsResponse {} diff --git a/testutil/keeper/btclightclient.go b/testutil/keeper/btclightclient.go index 060fd1600..a612e0654 100644 --- a/testutil/keeper/btclightclient.go +++ b/testutil/keeper/btclightclient.go @@ -7,6 +7,8 @@ import ( storemetrics "cosmossdk.io/store/metrics" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "cosmossdk.io/log" "cosmossdk.io/store" @@ -23,6 +25,10 @@ import ( ) func BTCLightClientKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { + return BTCLightClientKeeperWithCustomParams(t, types.DefaultParams()) +} + +func BTCLightClientKeeperWithCustomParams(t testing.TB, p types.Params) (*keeper.Keeper, sdk.Context) { storeKey := storetypes.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() @@ -39,10 +45,15 @@ func BTCLightClientKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { cdc, runtime.NewKVStoreService(storeKey), testCfg, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) ctx = ctx.WithHeaderInfo(header.Info{}) + if err := k.SetParams(ctx, p); err != nil { + panic(err) + } + return &k, ctx } diff --git a/x/btclightclient/genesis.go b/x/btclightclient/genesis.go index a71067089..d19d9dfee 100644 --- a/x/btclightclient/genesis.go +++ b/x/btclightclient/genesis.go @@ -2,6 +2,7 @@ package btclightclient import ( "context" + "github.com/babylonchain/babylon/x/btclightclient/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" ) @@ -14,6 +15,9 @@ func InitGenesis(ctx context.Context, k keeper.Keeper, genState types.GenesisSta } k.SetBaseBTCHeader(ctx, genState.BaseBtcHeader) + if err := k.SetParams(ctx, genState.Params); err != nil { + panic(err) + } } // ExportGenesis returns the capability module's exported genesis. @@ -23,7 +27,9 @@ func ExportGenesis(ctx context.Context, k keeper.Keeper) *types.GenesisState { if baseBTCHeader == nil { panic("A base BTC Header has not been set") } + genesis.BaseBtcHeader = *baseBTCHeader + genesis.Params = k.GetParams(ctx) return genesis } diff --git a/x/btclightclient/keeper/grpc_query.go b/x/btclightclient/keeper/grpc_query.go index c8796c8c6..c3230435e 100644 --- a/x/btclightclient/keeper/grpc_query.go +++ b/x/btclightclient/keeper/grpc_query.go @@ -13,6 +13,15 @@ import ( var _ types.QueryServer = Keeper{} +func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(c) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} + func (k Keeper) Hashes(ctx context.Context, req *types.QueryHashesRequest) (*types.QueryHashesResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index 44ef1dee5..a19563b03 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -23,6 +23,7 @@ type ( hooks types.BTCLightClientHooks btcConfig bbn.BtcConfig bl *types.BtcLightClient + authority string } ) @@ -32,6 +33,7 @@ func NewKeeper( cdc codec.BinaryCodec, storeService corestoretypes.KVStoreService, btcConfig bbn.BtcConfig, + authority string, ) Keeper { bl := types.NewBtcLightClientFromParams(btcConfig.NetParams()) @@ -41,6 +43,7 @@ func NewKeeper( hooks: nil, btcConfig: btcConfig, bl: bl, + authority: authority, } } diff --git a/x/btclightclient/keeper/msg_server.go b/x/btclightclient/keeper/msg_server.go index 6349ecee0..cd8fff632 100644 --- a/x/btclightclient/keeper/msg_server.go +++ b/x/btclightclient/keeper/msg_server.go @@ -3,8 +3,10 @@ package keeper import ( "context" + errorsmod "cosmossdk.io/errors" "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) type msgServer struct { @@ -12,13 +14,39 @@ type msgServer struct { k Keeper } +func (m msgServer) canInsertHeaders(sdkCtx sdk.Context, reporterAddress sdk.AccAddress) bool { + params := m.k.GetParams(sdkCtx) + + if params.AllowAllReporters() { + return true + } + + var allowInsertHeaders bool = false + for _, addr := range params.InsertHeadersAllowList { + if sdk.MustAccAddressFromBech32(addr).Equals(reporterAddress) { + allowInsertHeaders = true + } + } + + return allowInsertHeaders +} + func (m msgServer) InsertHeaders(ctx context.Context, msg *types.MsgInsertHeaders) (*types.MsgInsertHeadersResponse, error) { if msg == nil { return nil, types.ErrEmptyMessage.Wrapf("message is nil") } - sdkCtx := sdk.UnwrapSDKContext(ctx) + if err := msg.ValidateStateless(); err != nil { + return nil, types.ErrInvalidMessageFormat.Wrapf("invalid insert header message: %v", err) + } + + reporterAddress := msg.ReporterAddress() + + if !m.canInsertHeaders(sdkCtx, reporterAddress) { + return nil, types.ErrUnauthorizedReporter.Wrapf("reporter %s is not authorized to insert headers", reporterAddress) + } + err := m.k.InsertHeaders(sdkCtx, msg.Headers) if err != nil { @@ -27,6 +55,23 @@ func (m msgServer) InsertHeaders(ctx context.Context, msg *types.MsgInsertHeader return &types.MsgInsertHeadersResponse{}, nil } +func (ms msgServer) UpdateParams(ctx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if ms.k.authority != req.Authority { + return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.k.authority, req.Authority) + } + if err := req.Params.Validate(); err != nil { + return nil, govtypes.ErrInvalidProposalMsg.Wrapf("invalid parameter: %v", err) + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + if err := ms.k.SetParams(sdkCtx, req.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} + // NewMsgServerImpl returns an implementation of the MsgServer interface // for the provided Keeper. func NewMsgServerImpl(keeper Keeper) types.MsgServer { diff --git a/x/btclightclient/keeper/msg_server_test.go b/x/btclightclient/keeper/msg_server_test.go index 338a80ee9..a3dacb251 100644 --- a/x/btclightclient/keeper/msg_server_test.go +++ b/x/btclightclient/keeper/msg_server_test.go @@ -4,6 +4,7 @@ import ( "context" "math/rand" "testing" + "time" "github.com/babylonchain/babylon/testutil/datagen" "github.com/stretchr/testify/require" @@ -11,6 +12,7 @@ import ( keepertest "github.com/babylonchain/babylon/testutil/keeper" "github.com/babylonchain/babylon/x/btclightclient/keeper" "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -19,10 +21,19 @@ func setupMsgServer(t testing.TB) (types.MsgServer, *keeper.Keeper, context.Cont return keeper.NewMsgServerImpl(*k), k, ctx } +func setupMsgServerWithCustomParams(t testing.TB, p types.Params) (types.MsgServer, *keeper.Keeper, context.Context) { + k, ctx := keepertest.BTCLightClientKeeperWithCustomParams(t, p) + return keeper.NewMsgServerImpl(*k), k, ctx +} + // Property: Inserting valid chain which has current tip as parent, should always update the chain // and tip func FuzzMsgServerInsertNewTip(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 5) + senderPrivKey := secp256k1.GenPrivKey() + address, err := sdk.AccAddressFromHexUnsafe(senderPrivKey.PubKey().Address().String()) + require.NoError(f, err) + f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) srv, blcKeeper, sdkCtx := setupMsgServer(t) @@ -56,7 +67,7 @@ func FuzzMsgServerInsertNewTip(f *testing.F) { ) chainExtensionWork := chainWork(chainExtension) - msg := &types.MsgInsertHeaders{Headers: chainToChainBytes(chainExtension)} + msg := &types.MsgInsertHeaders{Signer: address.String(), Headers: chainToChainBytes(chainExtension)} _, err := srv.InsertHeaders(sdkCtx, msg) require.NoError(t, err) @@ -78,6 +89,10 @@ func FuzzMsgServerInsertNewTip(f *testing.F) { // Property: Inserting valid better chain should always update the chain and tip func FuzzMsgServerReorgChain(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 5) + senderPrivKey := secp256k1.GenPrivKey() + address, err := sdk.AccAddressFromHexUnsafe(senderPrivKey.PubKey().Address().String()) + require.NoError(f, err) + f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) srv, blcKeeper, sdkCtx := setupMsgServer(t) @@ -119,7 +134,7 @@ func FuzzMsgServerReorgChain(f *testing.F) { uint32(forkChainLen), ) chainExtensionWork := chainWork(chainExtension) - msg := &types.MsgInsertHeaders{Headers: chainToChainBytes(chainExtension)} + msg := &types.MsgInsertHeaders{Signer: address.String(), Headers: chainToChainBytes(chainExtension)} _, err := srv.InsertHeaders(sdkCtx, msg) require.NoError(t, err) @@ -137,3 +152,78 @@ func FuzzMsgServerReorgChain(f *testing.F) { ) }) } + +func TestAllowUpdatesOnlyFromReportesInTheList(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + sender1 := secp256k1.GenPrivKey() + address1, err := sdk.AccAddressFromHexUnsafe(sender1.PubKey().Address().String()) + require.NoError(t, err) + sender2 := secp256k1.GenPrivKey() + address2, err := sdk.AccAddressFromHexUnsafe(sender2.PubKey().Address().String()) + require.NoError(t, err) + sender3 := secp256k1.GenPrivKey() + address3, err := sdk.AccAddressFromHexUnsafe(sender3.PubKey().Address().String()) + require.NoError(t, err) + + params := types.NewParams( + // only sender1 and sender2 are allowed to update + []string{address1.String(), address2.String()}, + ) + + srv, blcKeeper, sdkCtx := setupMsgServerWithCustomParams(t, params) + ctx := sdk.UnwrapSDKContext(sdkCtx) + + _, chain := genRandomChain( + t, + r, + blcKeeper, + ctx, + 0, + 10, + ) + initTip := chain.GetTipInfo() + + checkTip( + t, + ctx, + blcKeeper, + *initTip.Work, + initTip.Height, + initTip.Header.ToBlockHeader(), + ) + + chainExtension := datagen.GenRandomValidChainStartingFrom( + r, + initTip.Height, + initTip.Header.ToBlockHeader(), + nil, + 10, + ) + + // sender 1 is allowed to update, it should succeed + msg := &types.MsgInsertHeaders{Signer: address1.String(), Headers: chainToChainBytes(chainExtension)} + _, err = srv.InsertHeaders(sdkCtx, msg) + require.NoError(t, err) + + newTip := blcKeeper.GetTipInfo(ctx) + require.NotNil(t, newTip) + + newChainExt := datagen.GenRandomValidChainStartingFrom( + r, + newTip.Height, + newTip.Header.ToBlockHeader(), + nil, + 10, + ) + + // sender 3 is not allowed to update, it should fail + msg1 := &types.MsgInsertHeaders{Signer: address3.String(), Headers: chainToChainBytes(newChainExt)} + _, err = srv.InsertHeaders(sdkCtx, msg1) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrUnauthorizedReporter) + + // sender 2 is allowed to update, it should succeed + msg1 = &types.MsgInsertHeaders{Signer: address2.String(), Headers: chainToChainBytes(newChainExt)} + _, err = srv.InsertHeaders(sdkCtx, msg1) + require.NoError(t, err) +} diff --git a/x/btclightclient/keeper/params.go b/x/btclightclient/keeper/params.go new file mode 100644 index 000000000..bd68dd5d6 --- /dev/null +++ b/x/btclightclient/keeper/params.go @@ -0,0 +1,31 @@ +package keeper + +import ( + "context" + + "github.com/babylonchain/babylon/x/btclightclient/types" +) + +// SetParams sets the x/btclightclient module parameters. +func (k Keeper) SetParams(ctx context.Context, p types.Params) error { + if err := p.Validate(); err != nil { + return err + } + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(&p) + return store.Set(types.ParamsKey, bz) +} + +// GetParams returns the current x/btclightclient module parameters. +func (k Keeper) GetParams(ctx context.Context) (p types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil { + panic(err) + } + if bz == nil { + return p + } + k.cdc.MustUnmarshal(bz, &p) + return p +} diff --git a/x/btclightclient/keeper/params_test.go b/x/btclightclient/keeper/params_test.go new file mode 100644 index 000000000..4dc68b70f --- /dev/null +++ b/x/btclightclient/keeper/params_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/babylonchain/babylon/testutil/keeper" + "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/stretchr/testify/require" +) + +func TestGetParams(t *testing.T) { + k, ctx := testkeeper.BTCLightClientKeeper(t) + // using nil as empty params list as, default proto decoder deserializes empty list as nil + params := types.NewParams(nil) + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + retrievedParams := k.GetParams(ctx) + require.EqualValues(t, params, retrievedParams) +} diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index e5c648207..f9c694c38 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -14,4 +14,6 @@ var ( ErrInvalidProofOfWOrk = errorsmod.Register(ModuleName, 1103, "provided header has invalid proof of work") ErrInvalidHeader = errorsmod.Register(ModuleName, 1104, "provided header does not satisfy header validation rules") ErrChainWithNotEnoughWork = errorsmod.Register(ModuleName, 1105, "provided chain has not enough work") + ErrUnauthorizedReporter = errorsmod.Register(ModuleName, 1106, "unauthorized reporter") + ErrInvalidMessageFormat = errorsmod.Register(ModuleName, 1107, "invalid message format") ) diff --git a/x/btclightclient/types/genesis.go b/x/btclightclient/types/genesis.go index 278e2af70..2f5300632 100644 --- a/x/btclightclient/types/genesis.go +++ b/x/btclightclient/types/genesis.go @@ -30,6 +30,7 @@ func DefaultGenesis() *GenesisState { return &GenesisState{ BaseBtcHeader: defaultBaseHeader, + Params: DefaultParams(), } } @@ -47,5 +48,9 @@ func (gs GenesisState) Validate() error { return fmt.Errorf("genesis block must be a difficulty adjustment block") } + if err := gs.Params.Validate(); err != nil { + return fmt.Errorf("invalid params in genesis: %w", err) + } + return nil } diff --git a/x/btclightclient/types/genesis.pb.go b/x/btclightclient/types/genesis.pb.go index 096396119..d3cc8819a 100644 --- a/x/btclightclient/types/genesis.pb.go +++ b/x/btclightclient/types/genesis.pb.go @@ -25,7 +25,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the btclightclient module's genesis state. type GenesisState struct { - BaseBtcHeader BTCHeaderInfo `protobuf:"bytes,1,opt,name=base_btc_header,json=baseBtcHeader,proto3" json:"base_btc_header"` + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + BaseBtcHeader BTCHeaderInfo `protobuf:"bytes,2,opt,name=base_btc_header,json=baseBtcHeader,proto3" json:"base_btc_header"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -61,6 +62,13 @@ func (m *GenesisState) XXX_DiscardUnknown() { var xxx_messageInfo_GenesisState proto.InternalMessageInfo +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + func (m *GenesisState) GetBaseBtcHeader() BTCHeaderInfo { if m != nil { return m.BaseBtcHeader @@ -77,22 +85,24 @@ func init() { } var fileDescriptor_4f95902e4096217a = []byte{ - // 227 bytes of a gzipped FileDescriptorProto + // 260 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4f, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0x65, 0x86, 0xfa, 0xe9, 0xa9, 0x79, 0xa9, 0xc5, 0x99, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0x85, 0x7a, 0xa8, 0x0a, 0xf5, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, - 0xf3, 0xc1, 0xaa, 0xf4, 0x41, 0x2c, 0x88, 0x06, 0x29, 0x3d, 0xdc, 0x26, 0xa3, 0x19, 0x01, 0x56, - 0xaf, 0x94, 0xc6, 0xc5, 0xe3, 0x0e, 0xb1, 0x31, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0x28, 0x8c, 0x8b, - 0x3f, 0x29, 0xb1, 0x38, 0x35, 0x3e, 0xa9, 0x24, 0x39, 0x3e, 0x23, 0x35, 0x31, 0x25, 0xb5, 0x48, - 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x43, 0x0f, 0xa7, 0x53, 0xf4, 0x9c, 0x42, 0x9c, 0x3d, - 0xc0, 0x6a, 0x3d, 0xf3, 0xd2, 0xf2, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0xe2, 0x05, 0x19, - 0xe3, 0x54, 0x92, 0x0c, 0x91, 0x70, 0x0a, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, - 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, - 0x86, 0x28, 0xb3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0x15, - 0xc9, 0x19, 0x89, 0x99, 0x79, 0x30, 0x8e, 0x7e, 0x05, 0xba, 0x5f, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, - 0x93, 0xd8, 0xc0, 0x1e, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x13, 0xa2, 0xce, 0xd4, 0x4c, - 0x01, 0x00, 0x00, + 0xf3, 0xc1, 0xaa, 0xf4, 0x41, 0x2c, 0x88, 0x06, 0x29, 0x3d, 0xdc, 0x26, 0xa3, 0x19, 0x01, 0x51, + 0xaf, 0x86, 0x5b, 0x7d, 0x41, 0x62, 0x51, 0x62, 0x2e, 0xd4, 0x21, 0x4a, 0xcb, 0x19, 0xb9, 0x78, + 0xdc, 0x21, 0x4e, 0x0b, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xb2, 0xe7, 0x62, 0x83, 0x28, 0x90, 0x60, + 0x54, 0x60, 0xd4, 0xe0, 0x36, 0x52, 0xd4, 0xc3, 0xe9, 0x54, 0xbd, 0x00, 0xb0, 0x42, 0x27, 0x96, + 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xa0, 0xda, 0x84, 0xc2, 0xb8, 0xf8, 0x93, 0x12, 0x8b, 0x53, 0xe3, + 0x93, 0x4a, 0x92, 0xe3, 0x33, 0x52, 0x13, 0x53, 0x52, 0x8b, 0x24, 0x98, 0xc0, 0x26, 0x69, 0xe0, + 0x31, 0xc9, 0x29, 0xc4, 0xd9, 0x03, 0xac, 0xd6, 0x33, 0x2f, 0x2d, 0x1f, 0x6a, 0x20, 0x2f, 0xc8, + 0x18, 0xa7, 0x92, 0x64, 0x88, 0x84, 0x53, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, + 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, + 0x31, 0x44, 0x99, 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0xad, + 0x48, 0xce, 0x48, 0xcc, 0xcc, 0x83, 0x71, 0xf4, 0x2b, 0xd0, 0x43, 0xa1, 0xa4, 0xb2, 0x20, 0xb5, + 0x38, 0x89, 0x0d, 0x1c, 0x04, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa8, 0x0c, 0x15, 0x34, + 0xb6, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -124,6 +134,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenesis(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x12 + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -145,6 +165,8 @@ func (m *GenesisState) Size() (n int) { } var l int _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) l = m.BaseBtcHeader.Size() n += 1 + l + sovGenesis(uint64(l)) return n @@ -186,6 +208,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field BaseBtcHeader", wireType) } diff --git a/x/btclightclient/types/keys.go b/x/btclightclient/types/keys.go index 363b2719f..b0844f440 100644 --- a/x/btclightclient/types/keys.go +++ b/x/btclightclient/types/keys.go @@ -23,9 +23,9 @@ const ( ) var ( - HeadersPrefix = []byte{0x0} // reserve this namespace for headers - HeadersObjectPrefix = append(HeadersPrefix, 0x0) // reserve this namespace mapping: Height -> BTCHeaderInfo - HashToHeightPrefix = append(HeadersPrefix, 0x1) // reserve this namespace mapping: Hash -> Height + HeadersObjectPrefix = []byte{0x01} // reserve this namespace mapping: Height -> BTCHeaderInfo + HashToHeightPrefix = []byte{0x02} // reserve this namespace mapping: Hash -> Height + ParamsKey = []byte{0x03} // key for params ) func HeadersObjectKey(height uint64) []byte { diff --git a/x/btclightclient/types/msgs.go b/x/btclightclient/types/msgs.go index 74d5e8fbb..038b9ac8c 100644 --- a/x/btclightclient/types/msgs.go +++ b/x/btclightclient/types/msgs.go @@ -50,3 +50,25 @@ func (msg *MsgInsertHeaders) ValidateHeaders(powLimit *big.Int) error { return nil } + +func (msg *MsgInsertHeaders) ReporterAddress() sdk.AccAddress { + sender, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + panic(err) + } + return sender +} + +func (msg *MsgInsertHeaders) ValidateStateless() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + + if err != nil { + return err + } + + if len(msg.Headers) == 0 { + return fmt.Errorf("empty headers list") + } + + return nil +} diff --git a/x/btclightclient/types/params.go b/x/btclightclient/types/params.go new file mode 100644 index 000000000..8b07a206a --- /dev/null +++ b/x/btclightclient/types/params.go @@ -0,0 +1,58 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewParams creates a new Params instance +func NewParams(allowedAddresses []string) Params { + return Params{ + InsertHeadersAllowList: allowedAddresses, + } +} + +func NewParamsValidate(allowedAddresses []string) (Params, error) { + p := NewParams(allowedAddresses) + if err := p.Validate(); err != nil { + return Params{}, err + } + return p, nil +} + +// DefaultParams returns a default set of parameters +func DefaultParams() Params { + return NewParams( + []string{}, + ) +} + +func ValidateAddressList(i interface{}) error { + allowList, ok := i.([]string) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + for _, a := range allowList { + if _, err := sdk.AccAddressFromBech32(a); err != nil { + return fmt.Errorf("invalid address") + } + } + + return nil +} + +// Validate validates the set of params +func (p Params) Validate() error { + if err := ValidateAddressList(p.InsertHeadersAllowList); err != nil { + return err + } + + return nil +} + +func (p *Params) AllowAllReporters() bool { + return len(p.InsertHeadersAllowList) == 0 +} diff --git a/x/btclightclient/types/params.pb.go b/x/btclightclient/types/params.pb.go new file mode 100644 index 000000000..bdd917254 --- /dev/null +++ b/x/btclightclient/types/params.pb.go @@ -0,0 +1,357 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: babylon/btclightclient/v1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the module. +type Params struct { + // List of addresses which are allowed to insert headers to btc light client + // if the list is empty, any address can insert headers + InsertHeadersAllowList []string `protobuf:"bytes,1,rep,name=insert_headers_allow_list,json=insertHeadersAllowList,proto3" json:"insert_headers_allow_list,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_1e4c5f7a17079e1f, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetInsertHeadersAllowList() []string { + if m != nil { + return m.InsertHeadersAllowList + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "babylon.btclightclient.v1.Params") +} + +func init() { + proto.RegisterFile("babylon/btclightclient/v1/params.proto", fileDescriptor_1e4c5f7a17079e1f) +} + +var fileDescriptor_1e4c5f7a17079e1f = []byte{ + // 211 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4b, 0x4a, 0x4c, 0xaa, + 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, + 0xfa, 0x65, 0x86, 0xfa, 0x05, 0x89, 0x45, 0x89, 0xb9, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, + 0x42, 0x92, 0x50, 0x75, 0x7a, 0xa8, 0xea, 0xf4, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, + 0xc1, 0xaa, 0xf4, 0x41, 0x2c, 0x88, 0x06, 0x25, 0x4f, 0x2e, 0xb6, 0x00, 0xb0, 0x01, 0x42, 0x96, + 0x5c, 0x92, 0x99, 0x79, 0xc5, 0xa9, 0x45, 0x25, 0xf1, 0x19, 0xa9, 0x89, 0x29, 0xa9, 0x45, 0xc5, + 0xf1, 0x89, 0x39, 0x39, 0xf9, 0xe5, 0xf1, 0x39, 0x99, 0xc5, 0x25, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, + 0x9c, 0x41, 0x62, 0x10, 0x05, 0x1e, 0x10, 0x79, 0x47, 0x90, 0xb4, 0x4f, 0x66, 0x71, 0x89, 0x15, + 0xcb, 0x8b, 0x05, 0xf2, 0x8c, 0x4e, 0x01, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, + 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, + 0x10, 0x65, 0x96, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x75, 0x60, + 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x81, 0xee, 0xaf, 0x92, 0xca, 0x82, 0xd4, 0xe2, + 0x24, 0x36, 0xb0, 0x1b, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x30, 0xe4, 0x07, 0x29, 0xfe, + 0x00, 0x00, 0x00, +} + +func (this *Params) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Params) + if !ok { + that2, ok := that.(Params) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.InsertHeadersAllowList) != len(that1.InsertHeadersAllowList) { + return false + } + for i := range this.InsertHeadersAllowList { + if this.InsertHeadersAllowList[i] != that1.InsertHeadersAllowList[i] { + return false + } + } + return true +} +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.InsertHeadersAllowList) > 0 { + for iNdEx := len(m.InsertHeadersAllowList) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.InsertHeadersAllowList[iNdEx]) + copy(dAtA[i:], m.InsertHeadersAllowList[iNdEx]) + i = encodeVarintParams(dAtA, i, uint64(len(m.InsertHeadersAllowList[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.InsertHeadersAllowList) > 0 { + for _, s := range m.InsertHeadersAllowList { + l = len(s) + n += 1 + l + sovParams(uint64(l)) + } + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InsertHeadersAllowList", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InsertHeadersAllowList = append(m.InsertHeadersAllowList, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/btclightclient/types/query.pb.go b/x/btclightclient/types/query.pb.go index cf79b0c7c..65d640b56 100644 --- a/x/btclightclient/types/query.pb.go +++ b/x/btclightclient/types/query.pb.go @@ -31,6 +31,89 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_3961270631e52721, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params holds all the parameters of this module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_3961270631e52721, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + // QueryHashesRequest is request type for the Query/Hashes RPC method. // It involves retrieving all hashes that are maintained by the module. type QueryHashesRequest struct { @@ -41,7 +124,7 @@ func (m *QueryHashesRequest) Reset() { *m = QueryHashesRequest{} } func (m *QueryHashesRequest) String() string { return proto.CompactTextString(m) } func (*QueryHashesRequest) ProtoMessage() {} func (*QueryHashesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{0} + return fileDescriptor_3961270631e52721, []int{2} } func (m *QueryHashesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -87,7 +170,7 @@ func (m *QueryHashesResponse) Reset() { *m = QueryHashesResponse{} } func (m *QueryHashesResponse) String() string { return proto.CompactTextString(m) } func (*QueryHashesResponse) ProtoMessage() {} func (*QueryHashesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{1} + return fileDescriptor_3961270631e52721, []int{3} } func (m *QueryHashesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -133,7 +216,7 @@ func (m *QueryContainsRequest) Reset() { *m = QueryContainsRequest{} } func (m *QueryContainsRequest) String() string { return proto.CompactTextString(m) } func (*QueryContainsRequest) ProtoMessage() {} func (*QueryContainsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{2} + return fileDescriptor_3961270631e52721, []int{4} } func (m *QueryContainsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -171,7 +254,7 @@ func (m *QueryContainsResponse) Reset() { *m = QueryContainsResponse{} } func (m *QueryContainsResponse) String() string { return proto.CompactTextString(m) } func (*QueryContainsResponse) ProtoMessage() {} func (*QueryContainsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{3} + return fileDescriptor_3961270631e52721, []int{5} } func (m *QueryContainsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -217,7 +300,7 @@ func (m *QueryContainsBytesRequest) Reset() { *m = QueryContainsBytesReq func (m *QueryContainsBytesRequest) String() string { return proto.CompactTextString(m) } func (*QueryContainsBytesRequest) ProtoMessage() {} func (*QueryContainsBytesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{4} + return fileDescriptor_3961270631e52721, []int{6} } func (m *QueryContainsBytesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -263,7 +346,7 @@ func (m *QueryContainsBytesResponse) Reset() { *m = QueryContainsBytesRe func (m *QueryContainsBytesResponse) String() string { return proto.CompactTextString(m) } func (*QueryContainsBytesResponse) ProtoMessage() {} func (*QueryContainsBytesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{5} + return fileDescriptor_3961270631e52721, []int{7} } func (m *QueryContainsBytesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -309,7 +392,7 @@ func (m *QueryMainChainRequest) Reset() { *m = QueryMainChainRequest{} } func (m *QueryMainChainRequest) String() string { return proto.CompactTextString(m) } func (*QueryMainChainRequest) ProtoMessage() {} func (*QueryMainChainRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{6} + return fileDescriptor_3961270631e52721, []int{8} } func (m *QueryMainChainRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -355,7 +438,7 @@ func (m *QueryMainChainResponse) Reset() { *m = QueryMainChainResponse{} func (m *QueryMainChainResponse) String() string { return proto.CompactTextString(m) } func (*QueryMainChainResponse) ProtoMessage() {} func (*QueryMainChainResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{7} + return fileDescriptor_3961270631e52721, []int{9} } func (m *QueryMainChainResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -406,7 +489,7 @@ func (m *QueryTipRequest) Reset() { *m = QueryTipRequest{} } func (m *QueryTipRequest) String() string { return proto.CompactTextString(m) } func (*QueryTipRequest) ProtoMessage() {} func (*QueryTipRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{8} + return fileDescriptor_3961270631e52721, []int{10} } func (m *QueryTipRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -444,7 +527,7 @@ func (m *QueryTipResponse) Reset() { *m = QueryTipResponse{} } func (m *QueryTipResponse) String() string { return proto.CompactTextString(m) } func (*QueryTipResponse) ProtoMessage() {} func (*QueryTipResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{9} + return fileDescriptor_3961270631e52721, []int{11} } func (m *QueryTipResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -489,7 +572,7 @@ func (m *QueryBaseHeaderRequest) Reset() { *m = QueryBaseHeaderRequest{} func (m *QueryBaseHeaderRequest) String() string { return proto.CompactTextString(m) } func (*QueryBaseHeaderRequest) ProtoMessage() {} func (*QueryBaseHeaderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{10} + return fileDescriptor_3961270631e52721, []int{12} } func (m *QueryBaseHeaderRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -528,7 +611,7 @@ func (m *QueryBaseHeaderResponse) Reset() { *m = QueryBaseHeaderResponse func (m *QueryBaseHeaderResponse) String() string { return proto.CompactTextString(m) } func (*QueryBaseHeaderResponse) ProtoMessage() {} func (*QueryBaseHeaderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{11} + return fileDescriptor_3961270631e52721, []int{13} } func (m *QueryBaseHeaderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -574,7 +657,7 @@ func (m *QueryHeaderDepthRequest) Reset() { *m = QueryHeaderDepthRequest func (m *QueryHeaderDepthRequest) String() string { return proto.CompactTextString(m) } func (*QueryHeaderDepthRequest) ProtoMessage() {} func (*QueryHeaderDepthRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{12} + return fileDescriptor_3961270631e52721, []int{14} } func (m *QueryHeaderDepthRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -620,7 +703,7 @@ func (m *QueryHeaderDepthResponse) Reset() { *m = QueryHeaderDepthRespon func (m *QueryHeaderDepthResponse) String() string { return proto.CompactTextString(m) } func (*QueryHeaderDepthResponse) ProtoMessage() {} func (*QueryHeaderDepthResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3961270631e52721, []int{13} + return fileDescriptor_3961270631e52721, []int{15} } func (m *QueryHeaderDepthResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -657,6 +740,8 @@ func (m *QueryHeaderDepthResponse) GetDepth() uint64 { } func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "babylon.btclightclient.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "babylon.btclightclient.v1.QueryParamsResponse") proto.RegisterType((*QueryHashesRequest)(nil), "babylon.btclightclient.v1.QueryHashesRequest") proto.RegisterType((*QueryHashesResponse)(nil), "babylon.btclightclient.v1.QueryHashesResponse") proto.RegisterType((*QueryContainsRequest)(nil), "babylon.btclightclient.v1.QueryContainsRequest") @@ -678,55 +763,59 @@ func init() { } var fileDescriptor_3961270631e52721 = []byte{ - // 760 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x4f, 0x4f, 0x13, 0x4f, - 0x18, 0xc7, 0x19, 0xfe, 0xf4, 0x57, 0x1e, 0x7e, 0x46, 0x1d, 0x51, 0xcb, 0xc6, 0x54, 0x2c, 0x02, - 0x05, 0x64, 0x87, 0x16, 0x35, 0x1c, 0x3c, 0x98, 0x62, 0x14, 0x0f, 0x26, 0xd8, 0xf4, 0xa4, 0x26, - 0x66, 0xb6, 0x8c, 0xbb, 0x9b, 0xc0, 0xce, 0xc2, 0x2e, 0xc4, 0xc6, 0x78, 0xf1, 0xe0, 0xd9, 0xe8, - 0xcd, 0x83, 0x07, 0x13, 0xe3, 0xcd, 0x93, 0x2f, 0xc2, 0x23, 0x89, 0x17, 0xe3, 0xc1, 0x18, 0xea, - 0x0b, 0x31, 0x3b, 0xf3, 0x94, 0xfe, 0x83, 0xee, 0x36, 0x72, 0x21, 0xcc, 0xcc, 0xf3, 0x7d, 0xbe, - 0x9f, 0x79, 0x32, 0xcf, 0xb3, 0x85, 0x69, 0x8b, 0x5b, 0xb5, 0x4d, 0xe9, 0x31, 0x2b, 0xac, 0x6e, - 0xba, 0xb6, 0x13, 0xfd, 0x15, 0x5e, 0xc8, 0xf6, 0x0a, 0x6c, 0x7b, 0x57, 0xec, 0xd4, 0x4c, 0x7f, - 0x47, 0x86, 0x92, 0x4e, 0x60, 0x98, 0xd9, 0x1e, 0x66, 0xee, 0x15, 0x8c, 0x71, 0x5b, 0xda, 0x52, - 0x45, 0xb1, 0xe8, 0x3f, 0x2d, 0x30, 0x2e, 0xd9, 0x52, 0xda, 0x9b, 0x82, 0x71, 0xdf, 0x65, 0xdc, - 0xf3, 0x64, 0xc8, 0x43, 0x57, 0x7a, 0x01, 0x9e, 0xce, 0x57, 0x65, 0xb0, 0x25, 0x03, 0x66, 0xf1, - 0x40, 0x68, 0x1f, 0xb6, 0x57, 0xb0, 0x44, 0xc8, 0x0b, 0xcc, 0xe7, 0xb6, 0xeb, 0xa9, 0x60, 0x8c, - 0x35, 0x8f, 0x27, 0xec, 0x80, 0x51, 0xf1, 0xb9, 0x27, 0x40, 0x1f, 0x46, 0x19, 0xd7, 0x78, 0xe0, - 0x88, 0xa0, 0x2c, 0xb6, 0x77, 0x45, 0x10, 0xd2, 0xbb, 0x00, 0xcd, 0xcc, 0x19, 0x32, 0x49, 0xf2, - 0x63, 0xc5, 0x19, 0x53, 0x63, 0x98, 0x11, 0x86, 0xa9, 0xaf, 0x8b, 0x18, 0xe6, 0x3a, 0xb7, 0x05, - 0x6a, 0xcb, 0x2d, 0xca, 0xdc, 0x57, 0x02, 0xe7, 0xda, 0xd2, 0x07, 0xbe, 0xf4, 0x02, 0x41, 0x2b, - 0x90, 0x72, 0xd4, 0x4e, 0x86, 0x4c, 0x0e, 0xe5, 0xff, 0x2f, 0xdd, 0xfa, 0xf9, 0xeb, 0xf2, 0x8a, - 0xed, 0x86, 0xce, 0xae, 0x65, 0x56, 0xe5, 0x16, 0xc3, 0x4b, 0x54, 0x1d, 0xee, 0x7a, 0x8d, 0x05, - 0x0b, 0x6b, 0xbe, 0x08, 0xcc, 0x52, 0x65, 0x75, 0x4d, 0xf0, 0x0d, 0xb1, 0x13, 0xa5, 0x2c, 0xd5, - 0x42, 0x11, 0x94, 0x31, 0x17, 0xbd, 0xd7, 0x46, 0x3d, 0xa8, 0xa8, 0x67, 0x63, 0xa9, 0x35, 0x52, - 0x1b, 0xb6, 0x03, 0xe3, 0x8a, 0x7a, 0x55, 0x7a, 0x21, 0x77, 0xbd, 0xc3, 0xb2, 0xac, 0xc3, 0x70, - 0x64, 0xa5, 0x0a, 0xf2, 0xaf, 0xd0, 0x2a, 0x53, 0x6e, 0x19, 0xce, 0x77, 0x38, 0x61, 0x85, 0x0c, - 0x48, 0x57, 0x71, 0x4f, 0xd9, 0xa5, 0xcb, 0x87, 0xeb, 0x1c, 0x83, 0x89, 0x36, 0x91, 0x4e, 0x88, - 0x8c, 0xb4, 0x95, 0x11, 0x5d, 0x56, 0xc0, 0x38, 0x4a, 0x90, 0xc0, 0xea, 0x29, 0xf2, 0x3d, 0xe0, - 0xae, 0xb7, 0x1a, 0x5d, 0xec, 0xa4, 0x5f, 0xc8, 0x27, 0x02, 0x17, 0x3a, 0x1d, 0x90, 0xab, 0x04, - 0xff, 0x39, 0xaa, 0x68, 0xfa, 0x95, 0x8c, 0x15, 0xf3, 0xe6, 0xb1, 0x7d, 0xd5, 0xac, 0xf0, 0x7d, - 0xef, 0x99, 0x2c, 0x37, 0x84, 0x27, 0xf7, 0x24, 0xce, 0xc2, 0x69, 0x85, 0x59, 0x71, 0x7d, 0xbc, - 0x46, 0xae, 0x02, 0x67, 0x9a, 0x5b, 0xc8, 0x7c, 0x1b, 0x52, 0xda, 0x1a, 0x4b, 0x92, 0x1c, 0x19, - 0x75, 0xb9, 0x0c, 0xd6, 0xa3, 0xc4, 0x03, 0xa1, 0x8f, 0x1b, 0x7e, 0x8f, 0xe1, 0x62, 0xd7, 0xc9, - 0x89, 0xd9, 0x2e, 0x62, 0x72, 0x7d, 0x74, 0x47, 0xf8, 0xa1, 0x73, 0xd4, 0x8b, 0x1a, 0xc5, 0x17, - 0xb5, 0x04, 0x99, 0xee, 0x70, 0x84, 0x19, 0x87, 0x91, 0x8d, 0x68, 0x43, 0x09, 0x86, 0xcb, 0x7a, - 0x51, 0xac, 0xa7, 0x61, 0x44, 0x49, 0xe8, 0x5b, 0x02, 0x29, 0x3d, 0x0f, 0xe8, 0x62, 0x0f, 0xce, - 0xee, 0xb1, 0x64, 0x98, 0x49, 0xc3, 0x35, 0x49, 0x6e, 0xee, 0xd5, 0xf7, 0x3f, 0xef, 0x06, 0xa7, - 0xe8, 0x15, 0x76, 0xfc, 0x54, 0xc4, 0xd9, 0xf1, 0x9e, 0x40, 0xba, 0xd1, 0x1e, 0x94, 0xc5, 0xf9, - 0x74, 0x0c, 0x06, 0x63, 0x29, 0xb9, 0x00, 0xd1, 0x16, 0x14, 0xda, 0x34, 0x9d, 0xea, 0x81, 0xd6, - 0xe8, 0x42, 0xfa, 0x85, 0xc0, 0xa9, 0xb6, 0xde, 0xa5, 0xd7, 0x93, 0x1a, 0xb6, 0xce, 0x06, 0xe3, - 0x46, 0x9f, 0x2a, 0x64, 0x5d, 0x52, 0xac, 0xf3, 0x34, 0x9f, 0x80, 0x55, 0xe3, 0x7d, 0x20, 0x30, - 0x7a, 0xd8, 0xd0, 0x34, 0xb6, 0x3a, 0x9d, 0xd3, 0xc5, 0x28, 0xf4, 0xa1, 0x40, 0xc8, 0x6b, 0x0a, - 0x72, 0x86, 0x5e, 0xed, 0x01, 0xb9, 0xc5, 0x5d, 0x3d, 0x9e, 0xe9, 0x6b, 0x02, 0x43, 0x15, 0xd7, - 0xa7, 0xf3, 0x71, 0x46, 0xcd, 0x7e, 0x37, 0x16, 0x12, 0xc5, 0x22, 0xce, 0x8c, 0xc2, 0x99, 0xa4, - 0xd9, 0x1e, 0x38, 0xa1, 0xeb, 0xd3, 0x8f, 0x04, 0xa0, 0xd9, 0xd0, 0x34, 0xf6, 0xe2, 0x5d, 0x63, - 0xc1, 0x28, 0xf6, 0x23, 0x41, 0xba, 0x45, 0x45, 0x37, 0x4b, 0xa7, 0x7b, 0xd0, 0x45, 0xd3, 0x51, - 0x0f, 0x07, 0xfa, 0x99, 0xc0, 0x58, 0x4b, 0xa7, 0xd3, 0x58, 0xcb, 0xee, 0x29, 0x62, 0x2c, 0xf7, - 0xa5, 0x41, 0x4e, 0xa6, 0x38, 0xe7, 0xe8, 0x6c, 0x0f, 0x4e, 0x35, 0x5e, 0xd8, 0x8b, 0xa8, 0x8f, - 0x5f, 0x96, 0xd6, 0xbf, 0x1d, 0x64, 0xc9, 0xfe, 0x41, 0x96, 0xfc, 0x3e, 0xc8, 0x92, 0x37, 0xf5, - 0xec, 0xc0, 0x7e, 0x3d, 0x3b, 0xf0, 0xa3, 0x9e, 0x1d, 0x78, 0x74, 0x33, 0xee, 0x4b, 0xfd, 0xbc, - 0x33, 0xb7, 0xfa, 0x74, 0x5b, 0x29, 0xf5, 0x3b, 0x69, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xf7, 0x92, 0x0d, 0xa9, 0xfb, 0x09, 0x00, 0x00, + // 823 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xcf, 0x4f, 0x13, 0x4d, + 0x18, 0xc7, 0xbb, 0xfc, 0xe8, 0x0b, 0xc3, 0xfb, 0xe6, 0xd5, 0xb1, 0x6a, 0xd9, 0x98, 0x02, 0x8b, + 0x94, 0x02, 0xb2, 0x43, 0x8b, 0x1a, 0x0e, 0x26, 0x9a, 0x62, 0x14, 0x0f, 0x26, 0xb5, 0x69, 0x3c, + 0xa8, 0x89, 0x99, 0x96, 0x71, 0x77, 0x13, 0xba, 0xb3, 0x74, 0x17, 0x62, 0x63, 0xbc, 0x78, 0xf0, + 0x6c, 0xf4, 0xe6, 0xc1, 0x83, 0x89, 0xf1, 0xe6, 0xc9, 0x3f, 0x82, 0x23, 0x89, 0x17, 0xe3, 0x81, + 0x18, 0xf0, 0x8f, 0xf0, 0x68, 0x76, 0xe6, 0xd9, 0xb6, 0xdb, 0x42, 0x77, 0x1b, 0xb9, 0x10, 0x76, + 0xe6, 0xf9, 0x3e, 0xdf, 0xcf, 0x3c, 0xcc, 0x7e, 0x59, 0x34, 0x57, 0xa5, 0xd5, 0xe6, 0x16, 0xb7, + 0x49, 0xd5, 0xab, 0x6d, 0x59, 0x86, 0xe9, 0xff, 0x64, 0xb6, 0x47, 0x76, 0xf3, 0x64, 0x7b, 0x87, + 0x35, 0x9a, 0xba, 0xd3, 0xe0, 0x1e, 0xc7, 0x93, 0x50, 0xa6, 0x87, 0xcb, 0xf4, 0xdd, 0xbc, 0x9a, + 0x32, 0xb8, 0xc1, 0x45, 0x15, 0xf1, 0x7f, 0x93, 0x02, 0xf5, 0x92, 0xc1, 0xb9, 0xb1, 0xc5, 0x08, + 0x75, 0x2c, 0x42, 0x6d, 0x9b, 0x7b, 0xd4, 0xb3, 0xb8, 0xed, 0xc2, 0xee, 0x62, 0x8d, 0xbb, 0x75, + 0xee, 0x92, 0x2a, 0x75, 0x99, 0xf4, 0x21, 0xbb, 0xf9, 0x2a, 0xf3, 0x68, 0x9e, 0x38, 0xd4, 0xb0, + 0x6c, 0x51, 0x0c, 0xb5, 0xfa, 0xc9, 0x84, 0x5d, 0x30, 0xb2, 0x3e, 0x7b, 0x72, 0xbd, 0x43, 0x1b, + 0xb4, 0x0e, 0x0c, 0x5a, 0x0a, 0xe1, 0x07, 0xbe, 0x73, 0x49, 0x2c, 0x96, 0xd9, 0xf6, 0x0e, 0x73, + 0x3d, 0xed, 0x21, 0x3a, 0x17, 0x5a, 0x75, 0x1d, 0x6e, 0xbb, 0x0c, 0xdf, 0x44, 0x49, 0x29, 0x4e, + 0x2b, 0xd3, 0x4a, 0x6e, 0xa2, 0x30, 0xa3, 0x9f, 0x38, 0x10, 0x5d, 0x4a, 0x8b, 0x23, 0x7b, 0x07, + 0x53, 0x89, 0x32, 0xc8, 0xb4, 0x27, 0xe0, 0xb6, 0x41, 0x5d, 0x93, 0x05, 0x6e, 0xf8, 0x0e, 0x42, + 0xed, 0xf3, 0x42, 0xeb, 0xac, 0x2e, 0x87, 0xa3, 0xfb, 0xc3, 0xd1, 0xe5, 0x1f, 0x01, 0x86, 0xa3, + 0x97, 0xa8, 0xc1, 0x40, 0x5b, 0xee, 0x50, 0x6a, 0x5f, 0x15, 0xc0, 0x0e, 0xda, 0x03, 0x76, 0x05, + 0x25, 0x4d, 0xb1, 0x92, 0x56, 0xa6, 0x87, 0x73, 0xff, 0x16, 0x6f, 0xfc, 0x38, 0x98, 0x5a, 0x33, + 0x2c, 0xcf, 0xdc, 0xa9, 0xea, 0x35, 0x5e, 0x27, 0x70, 0x88, 0x9a, 0x49, 0x2d, 0x3b, 0x78, 0x20, + 0x5e, 0xd3, 0x61, 0xae, 0x5e, 0xac, 0xac, 0x6f, 0x30, 0xba, 0xc9, 0x1a, 0x7e, 0xcb, 0x62, 0xd3, + 0x63, 0x6e, 0x19, 0x7a, 0xe1, 0xbb, 0x21, 0xea, 0x21, 0x41, 0x3d, 0x1f, 0x49, 0x2d, 0x91, 0x42, + 0xd8, 0x26, 0x4a, 0x09, 0xea, 0x75, 0x6e, 0x7b, 0xd4, 0xb2, 0x5b, 0x63, 0x29, 0xa1, 0x11, 0xdf, + 0x4a, 0x0c, 0xe4, 0x6f, 0xa1, 0x45, 0x27, 0x6d, 0x15, 0x9d, 0xef, 0x72, 0x82, 0x09, 0xa9, 0x68, + 0xac, 0x06, 0x6b, 0xc2, 0x6e, 0xac, 0xdc, 0x7a, 0xd6, 0x08, 0x9a, 0x0c, 0x89, 0x64, 0x43, 0x60, + 0xc4, 0x9d, 0x8c, 0xe0, 0xb2, 0x86, 0xd4, 0xe3, 0x04, 0x31, 0xac, 0x9e, 0x02, 0xdf, 0x7d, 0x6a, + 0xd9, 0xeb, 0xfe, 0xc1, 0x4e, 0xfb, 0x86, 0x7c, 0x52, 0xd0, 0x85, 0x6e, 0x07, 0xe0, 0x2a, 0xa2, + 0x7f, 0x4c, 0x31, 0x34, 0x79, 0x4b, 0x26, 0x0a, 0xb9, 0x3e, 0x97, 0xbb, 0x35, 0xe1, 0x7b, 0xf6, + 0x33, 0x5e, 0x0e, 0x84, 0xa7, 0x77, 0x25, 0xce, 0xa2, 0xff, 0x05, 0x66, 0xc5, 0x72, 0x82, 0x57, + 0xb2, 0x82, 0xce, 0xb4, 0x97, 0x80, 0xf9, 0x16, 0x4a, 0x4a, 0x6b, 0x18, 0x49, 0x7c, 0x64, 0xd0, + 0x69, 0x69, 0x98, 0x47, 0x91, 0xba, 0x4c, 0x6e, 0x07, 0x7e, 0x8f, 0xd1, 0xc5, 0x9e, 0x9d, 0x53, + 0xb3, 0x5d, 0x86, 0xe6, 0x72, 0xeb, 0x36, 0x73, 0x3c, 0xf3, 0xb8, 0x1b, 0x35, 0x0e, 0x37, 0x6a, + 0x05, 0xa5, 0x7b, 0xcb, 0x01, 0x26, 0x85, 0x46, 0x37, 0xfd, 0x05, 0x21, 0x18, 0x29, 0xcb, 0x87, + 0xc2, 0xef, 0x71, 0x34, 0x2a, 0x24, 0xf8, 0xad, 0x82, 0x92, 0x32, 0x8b, 0xf0, 0x72, 0x1f, 0xce, + 0xde, 0x10, 0x54, 0xf5, 0xb8, 0xe5, 0x92, 0x44, 0x5b, 0x78, 0xf5, 0xed, 0xd7, 0xbb, 0xa1, 0x59, + 0x3c, 0x43, 0xa2, 0xb2, 0x57, 0x40, 0xc9, 0x90, 0x8a, 0x86, 0x0a, 0x65, 0x65, 0x34, 0x54, 0x38, + 0xfb, 0x62, 0x41, 0x41, 0xa0, 0xbd, 0x57, 0xd0, 0x58, 0xf0, 0xce, 0x62, 0x12, 0xe5, 0xd3, 0x95, + 0x56, 0xea, 0x4a, 0x7c, 0x01, 0xa0, 0x2d, 0x09, 0xb4, 0x39, 0x3c, 0xdb, 0x07, 0x2d, 0x88, 0x06, + 0xfc, 0x45, 0x41, 0xff, 0x85, 0x02, 0x05, 0x5f, 0x8d, 0x6b, 0xd8, 0x19, 0x58, 0xea, 0xb5, 0x01, + 0x55, 0xc0, 0xba, 0x22, 0x58, 0x17, 0x71, 0x2e, 0x06, 0xab, 0xc4, 0xfb, 0xa0, 0xa0, 0xf1, 0x56, + 0xca, 0xe0, 0xc8, 0xe9, 0x74, 0x47, 0x9e, 0x9a, 0x1f, 0x40, 0x01, 0x90, 0x57, 0x04, 0x64, 0x16, + 0x5f, 0xee, 0x03, 0x59, 0xa7, 0x96, 0xfc, 0x9f, 0x81, 0x5f, 0x2b, 0x68, 0xb8, 0x62, 0x39, 0x78, + 0x31, 0xca, 0xa8, 0x1d, 0x42, 0xea, 0x52, 0xac, 0x5a, 0xc0, 0xc9, 0x0a, 0x9c, 0x69, 0x9c, 0xe9, + 0x83, 0xe3, 0x59, 0x0e, 0xfe, 0xa8, 0x20, 0xd4, 0x4e, 0x19, 0x1c, 0x79, 0xf0, 0x9e, 0xac, 0x52, + 0x0b, 0x83, 0x48, 0x80, 0x6e, 0x59, 0xd0, 0xcd, 0xe3, 0xb9, 0x3e, 0x74, 0x7e, 0x64, 0xcb, 0xc4, + 0xc2, 0x9f, 0x15, 0x34, 0xd1, 0x11, 0x3f, 0x38, 0xd2, 0xb2, 0x37, 0xda, 0xd4, 0xd5, 0x81, 0x34, + 0xc0, 0x49, 0x04, 0xe7, 0x02, 0x9e, 0xef, 0xc3, 0x29, 0x32, 0x8f, 0xbc, 0xf0, 0xdf, 0xe3, 0x97, + 0xc5, 0xd2, 0xde, 0x61, 0x46, 0xd9, 0x3f, 0xcc, 0x28, 0x3f, 0x0f, 0x33, 0xca, 0x9b, 0xa3, 0x4c, + 0x62, 0xff, 0x28, 0x93, 0xf8, 0x7e, 0x94, 0x49, 0x3c, 0xba, 0x1e, 0xf5, 0xf9, 0xf0, 0xbc, 0xbb, + 0xb7, 0xf8, 0x9e, 0xa8, 0x26, 0xc5, 0xa7, 0xe2, 0xea, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x25, + 0x1e, 0x8e, 0x7d, 0x26, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -741,6 +830,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type QueryClient interface { + // Params queries the parameters of the module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) // Hashes retrieves the hashes maintained by the module. Hashes(ctx context.Context, in *QueryHashesRequest, opts ...grpc.CallOption) (*QueryHashesResponse, error) // Contains checks whether a hash is maintained by the module. @@ -770,6 +861,15 @@ func NewQueryClient(cc grpc1.ClientConn) QueryClient { return &queryClient{cc} } +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *queryClient) Hashes(ctx context.Context, in *QueryHashesRequest, opts ...grpc.CallOption) (*QueryHashesResponse, error) { out := new(QueryHashesResponse) err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Query/Hashes", in, out, opts...) @@ -835,6 +935,8 @@ func (c *queryClient) HeaderDepth(ctx context.Context, in *QueryHeaderDepthReque // QueryServer is the server API for Query service. type QueryServer interface { + // Params queries the parameters of the module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) // Hashes retrieves the hashes maintained by the module. Hashes(context.Context, *QueryHashesRequest) (*QueryHashesResponse, error) // Contains checks whether a hash is maintained by the module. @@ -860,6 +962,9 @@ type QueryServer interface { type UnimplementedQueryServer struct { } +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} func (*UnimplementedQueryServer) Hashes(ctx context.Context, req *QueryHashesRequest) (*QueryHashesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Hashes not implemented") } @@ -886,6 +991,24 @@ func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) } +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btclightclient.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Query_Hashes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryHashesRequest) if err := dec(in); err != nil { @@ -1016,6 +1139,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btclightclient.v1.Query", HandlerType: (*QueryServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, { MethodName: "Hashes", Handler: _Query_Hashes_Handler, @@ -1049,6 +1176,62 @@ var _Query_serviceDesc = grpc.ServiceDesc{ Metadata: "babylon/btclightclient/v1/query.proto", } +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *QueryHashesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1533,6 +1716,26 @@ func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + func (m *QueryHashesRequest) Size() (n int) { if m == nil { return 0 @@ -1722,6 +1925,139 @@ func sovQuery(x uint64) (n int) { func sozQuery(x uint64) (n int) { return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryHashesRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/btclightclient/types/query.pb.gw.go b/x/btclightclient/types/query.pb.gw.go index bcb180f44..026611cb2 100644 --- a/x/btclightclient/types/query.pb.gw.go +++ b/x/btclightclient/types/query.pb.gw.go @@ -33,6 +33,24 @@ var _ = utilities.NewDoubleArray var _ = descriptor.ForMessage var _ = metadata.Join +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_Query_Hashes_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -273,6 +291,29 @@ func local_request_Query_HeaderDepth_0(ctx context.Context, marshaler runtime.Ma // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_Hashes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -475,6 +516,26 @@ func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc // "QueryClient" to call the correct interceptors. func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_Query_Hashes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -619,6 +680,8 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btclightclient", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Hashes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btclightclient", "v1", "hashes"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Contains_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"babylon", "btclightclient", "v1", "contains"}, "", runtime.AssumeColonVerbOpt(false))) @@ -635,6 +698,8 @@ var ( ) var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + forward_Query_Hashes_0 = runtime.ForwardResponseMessage forward_Query_Contains_0 = runtime.ForwardResponseMessage diff --git a/x/btclightclient/types/tx.pb.go b/x/btclightclient/types/tx.pb.go index c642f99a5..3f22f09f1 100644 --- a/x/btclightclient/types/tx.pb.go +++ b/x/btclightclient/types/tx.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + _ "github.com/cosmos/cosmos-proto" _ "github.com/cosmos/cosmos-sdk/types/msgservice" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" @@ -113,9 +114,108 @@ func (m *MsgInsertHeadersResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgInsertHeadersResponse proto.InternalMessageInfo +// MsgUpdateParams defines a message for updating btc light client module parameters. +type MsgUpdateParams struct { + // authority is the address of the governance account. + // just FYI: cosmos.AddressString marks that this field should use type alias + // for AddressString instead of string, but the functionality is not yet implemented + // in cosmos-proto + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params defines the btc light client parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_5f638eee60234021, []int{2} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse is the response to the MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5f638eee60234021, []int{3} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgInsertHeaders)(nil), "babylon.btclightclient.v1.MsgInsertHeaders") proto.RegisterType((*MsgInsertHeadersResponse)(nil), "babylon.btclightclient.v1.MsgInsertHeadersResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "babylon.btclightclient.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "babylon.btclightclient.v1.MsgUpdateParamsResponse") } func init() { @@ -123,26 +223,35 @@ func init() { } var fileDescriptor_5f638eee60234021 = []byte{ - // 303 bytes of a gzipped FileDescriptorProto + // 443 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0x65, 0x86, 0xfa, 0x25, 0x15, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, 0x50, 0x35, 0x7a, 0xa8, 0x6a, 0xf4, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xaa, 0xf4, 0x41, 0x2c, 0x88, 0x06, 0x29, 0xf1, 0xe4, 0xfc, 0xe2, 0xdc, 0xfc, 0x62, 0xfd, 0xdc, 0xe2, 0x74, 0x90, - 0x41, 0xb9, 0xc5, 0xe9, 0x10, 0x09, 0xa5, 0x6e, 0x46, 0x2e, 0x01, 0xdf, 0xe2, 0x74, 0xcf, 0xbc, - 0xe2, 0xd4, 0xa2, 0x12, 0x8f, 0xd4, 0xc4, 0x94, 0xd4, 0xa2, 0x62, 0x21, 0x31, 0x2e, 0xb6, 0xe2, - 0xcc, 0xf4, 0xbc, 0xd4, 0x22, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x28, 0x4f, 0x28, 0x88, - 0x8b, 0x3d, 0x03, 0xa2, 0x44, 0x82, 0x49, 0x81, 0x59, 0x83, 0xc7, 0xc9, 0xe2, 0xd6, 0x3d, 0x79, - 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0xa8, 0xb3, 0x92, 0x33, - 0x12, 0x33, 0xf3, 0x60, 0x1c, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x3d, 0xa7, 0x10, 0x67, 0x88, - 0xf1, 0x4e, 0x95, 0x25, 0xa9, 0xc5, 0x41, 0x30, 0x83, 0xac, 0xb8, 0x9b, 0x9e, 0x6f, 0xd0, 0x82, - 0x5a, 0xa0, 0x24, 0xc5, 0x25, 0x81, 0xee, 0x98, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, - 0xa3, 0x46, 0x46, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0xa1, 0x62, 0x2e, 0x5e, 0x54, 0xd7, 0x6a, 0xeb, - 0xe1, 0x0c, 0x0d, 0x3d, 0x74, 0xd3, 0xa4, 0x8c, 0x49, 0x50, 0x0c, 0xb3, 0x5a, 0x89, 0x41, 0x8a, - 0xb5, 0xe1, 0xf9, 0x06, 0x2d, 0x46, 0xa7, 0x80, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, - 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, - 0x63, 0x88, 0x32, 0x23, 0x14, 0x0a, 0x15, 0xe8, 0xf1, 0x09, 0x0e, 0x96, 0x24, 0x36, 0x70, 0x34, - 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x5f, 0xe3, 0xd7, 0xf6, 0x01, 0x00, 0x00, + 0x41, 0xb9, 0xc5, 0xe9, 0x50, 0x09, 0x35, 0xdc, 0xb6, 0x15, 0x24, 0x16, 0x25, 0xe6, 0x16, 0x43, + 0xd5, 0x49, 0x42, 0x0c, 0x88, 0x87, 0x98, 0x0c, 0xe1, 0x40, 0xa4, 0x94, 0xba, 0x19, 0xb9, 0x04, + 0x7c, 0x8b, 0xd3, 0x3d, 0xf3, 0x8a, 0x53, 0x8b, 0x4a, 0x3c, 0x52, 0x13, 0x53, 0x52, 0x8b, 0x8a, + 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x33, 0xd3, 0xf3, 0x52, 0x8b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, + 0x83, 0xa0, 0x3c, 0xa1, 0x20, 0x2e, 0xf6, 0x0c, 0x88, 0x12, 0x09, 0x26, 0x05, 0x66, 0x0d, 0x1e, + 0x27, 0x8b, 0x5b, 0xf7, 0xe4, 0x4d, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, + 0xf5, 0xa1, 0xee, 0x49, 0xce, 0x48, 0xcc, 0xcc, 0x83, 0x71, 0xf4, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, + 0xf5, 0x9c, 0x42, 0x9c, 0x21, 0xc6, 0x3b, 0x55, 0x96, 0xa4, 0x16, 0x07, 0xc1, 0x0c, 0xb2, 0xe2, + 0x6e, 0x7a, 0xbe, 0x41, 0x0b, 0x6a, 0x81, 0x92, 0x14, 0x97, 0x04, 0xba, 0x63, 0x82, 0x52, 0x8b, + 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x95, 0x66, 0x31, 0x72, 0xf1, 0xfb, 0x16, 0xa7, 0x87, 0x16, 0xa4, + 0x24, 0x96, 0xa4, 0x06, 0x80, 0xbd, 0x27, 0x64, 0xc6, 0xc5, 0x99, 0x58, 0x5a, 0x92, 0x91, 0x5f, + 0x94, 0x59, 0x52, 0x09, 0x71, 0xab, 0x93, 0xc4, 0xa5, 0x2d, 0xba, 0x22, 0x50, 0x2f, 0x3a, 0xa6, + 0xa4, 0x14, 0xa5, 0x16, 0x17, 0x07, 0x97, 0x14, 0x65, 0xe6, 0xa5, 0x07, 0x21, 0x94, 0x0a, 0xd9, + 0x73, 0xb1, 0x41, 0x02, 0x48, 0x82, 0x49, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x51, 0x0f, 0x67, 0x9c, + 0xe8, 0x41, 0xac, 0x72, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x08, 0xaa, 0xcd, 0x8a, 0x0f, 0xe4, + 0x6a, 0x84, 0x81, 0x4a, 0x92, 0x5c, 0xe2, 0x68, 0x6e, 0x83, 0xb9, 0xdb, 0xe8, 0x23, 0x23, 0x17, + 0xb3, 0x6f, 0x71, 0xba, 0x50, 0x31, 0x17, 0x2f, 0x6a, 0x28, 0x6b, 0xe3, 0xb1, 0x14, 0x3d, 0x14, + 0xa4, 0x8c, 0x49, 0x50, 0x0c, 0x0f, 0x32, 0x06, 0xa1, 0x3c, 0x2e, 0x1e, 0x94, 0x00, 0xd3, 0xc2, + 0x6f, 0x0c, 0xb2, 0x5a, 0x29, 0x23, 0xe2, 0xd5, 0xc2, 0x6c, 0x94, 0x62, 0x6d, 0x78, 0xbe, 0x41, + 0x8b, 0xd1, 0x29, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, + 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xcc, 0x08, + 0xa5, 0x96, 0x0a, 0xf4, 0xc4, 0x0c, 0x4e, 0x3e, 0x49, 0x6c, 0xe0, 0xe4, 0x6a, 0x0c, 0x08, 0x00, + 0x00, 0xff, 0xff, 0x64, 0x60, 0x2a, 0x32, 0x61, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -159,6 +268,8 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { // InsertHeaders adds a batch of headers to the BTC light client chain InsertHeaders(ctx context.Context, in *MsgInsertHeaders, opts ...grpc.CallOption) (*MsgInsertHeadersResponse, error) + // UpdateParams defines a method for updating btc light client module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -178,10 +289,21 @@ func (c *msgClient) InsertHeaders(ctx context.Context, in *MsgInsertHeaders, opt return out, nil } +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/babylon.btclightclient.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // InsertHeaders adds a batch of headers to the BTC light client chain InsertHeaders(context.Context, *MsgInsertHeaders) (*MsgInsertHeadersResponse, error) + // UpdateParams defines a method for updating btc light client module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -191,6 +313,9 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) InsertHeaders(ctx context.Context, req *MsgInsertHeaders) (*MsgInsertHeadersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method InsertHeaders not implemented") } +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -214,6 +339,24 @@ func _Msg_InsertHeaders_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/babylon.btclightclient.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "babylon.btclightclient.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -222,6 +365,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "InsertHeaders", Handler: _Msg_InsertHeaders_Handler, }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "babylon/btclightclient/v1/tx.proto", @@ -294,6 +441,69 @@ func (m *MsgInsertHeadersResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -333,6 +543,30 @@ func (m *MsgInsertHeadersResponse) Size() (n int) { return n } +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -506,6 +740,171 @@ func (m *MsgInsertHeadersResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/zoneconcierge/types/zoneconcierge.pb.go b/x/zoneconcierge/types/zoneconcierge.pb.go index 5018b27c9..a6b9473e3 100644 --- a/x/zoneconcierge/types/zoneconcierge.pb.go +++ b/x/zoneconcierge/types/zoneconcierge.pb.go @@ -382,7 +382,7 @@ func (m *FinalizedChainInfo) GetProof() *ProofFinalizedChainInfo { // - Raw checkpoint of this epoch // The verifier can perform the following verification rules: // - The raw checkpoint's `app_hash` is same as in the sealer header -// - More than 1/3 (in voting power) validators in the validator set of this +// - More than 2/3 (in voting power) validators in the validator set of this // epoch have signed `app_hash` of the sealer header // - The epoch medatata is committed to the `app_hash` of the sealer header // - The validator set is committed to the `app_hash` of the sealer header From 2892424e2f741ae6caa12ed01bfb44f925e470f2 Mon Sep 17 00:00:00 2001 From: Vitalis Salis Date: Wed, 10 Jan 2024 14:23:41 +0200 Subject: [PATCH 147/202] docs: Babylon Architecture (#165) --- docs/architecture.md | 227 + docs/static/arch.excalidraw | 12630 ++++++++++++++++++++++++++++++++++ docs/static/arch.png | Bin 0 -> 4302382 bytes 3 files changed, 12857 insertions(+) create mode 100644 docs/architecture.md create mode 100644 docs/static/arch.excalidraw create mode 100644 docs/static/arch.png diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..3cf3af816 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,227 @@ +# Babylon Architecture + +The Babylon system is composed of a Babylon node +built using the Cosmos SDK as well as peripheral programs +that facilitate BTC staking, finality round participation, and +communication with Bitcoin and other Consumer Zones. +![Babylon Architecture](./static/arch.png) + +## Babylon Node Modules + +### [Epoching](../x/epoching) + +The Babylon blockchain is divided into epochs +that consist of a parameterized number of blocks. +Within each epoch, the validator set does not change. +This way, Babylon needs a checkpoint per epoch rather than per block, +which reduces the checkpointing costs. +The epoching module achieves this by delaying the execution +of transactions that affect the validator set to the last block +of each epoch. + +### [BTC Light Client](../x/btclightclient) + +The BTC Light Client module receives Bitcoin headers +reported by the Vigilante Reporter and +maintains a BTC header chain based on the PoW rules of Bitcoin. +It exposes information about the canonical Bitcoin chain, +the depth of headers, and +whether the inclusion evidence for a Bitcoin transaction is valid. + +### [BTC Checkpoint](../x/btccheckpoint) + +The BTC Checkpoint module verifies Babylon’s BTC checkpoints +reported by the Vigilante Reporter, and +provides the confirmation status of these checkpoints to the Checkpointing +module based on their depth according to the BTC Light Client module. + +### [Checkpointing](../x/checkpointing) + +The checkpointing module is responsible for creating Babylon checkpoints +to be submitted to Bitcoin and maintaining their confirmation status. +It collects the validator's +[BLS signatures](https://en.wikipedia.org/wiki/BLS_digital_signature) +for each block to be checkpointed and aggregates them +into a BLS multisignature to include in the Bitcoin checkpoint. +The confirmation status of each checkpoint is determined by +Bitcoin checkpoint inclusion information retrieved from the +BTC checkpoint module. + +### [ZoneConcierge](../x/zoneconcierge) + +The Zone Concierge module +extracts verified Consumer Zone headers from +connected [IBC light clients](https://github.com/cosmos/ibc-go) and +maintains their Bitcoin confirmation status based on the +Bitcoin confirmation status of the +Babylon transactions that carry them. +It communicates the Bitcoin confirmation status to the Consumer Zone +using verifiable proofs through an +[IBC](https://github.com/cosmos/ibc-go) connection. + +### [BTC Staking](../x/btcstaking) + +The BTC Staking module +is the bookkeeper for the BTC staking protocol. +It is responsible for verifying and activating +BTC staking requests and +maintaining the active finality provider set. +It communicates with the BTC Light Client module +to extract the confirmation status of staking requests and +receives notifications about on-demand unlocked stake from the +BTC Staking Monitor. + +### [Finality](../x/finality) + +The Finality module is responsible for finalizing blocks +produced by the CometBFT consensus. +It receives and verifies finality round votes +from finality providers and +a block is considered finalized if sufficient +voting power is cast on it. +The voting power of each finality provider is based on +its Bitcoin stake retrieved from the BTC Staking module. +Finality votes are performed using +[Extractable-One-Time-Signatures (EOTS)](https://docs.babylonchain.io/assets/files/btc_staking_litepaper-32bfea0c243773f0bfac63e148387aef.pdf) +and verified using +the finality providers' committed public randomness. + +### [Incentive](../x/incentive) + +The incentive module consumes a percentage +of the rewards intended for Babylon stakers and +distributes it as rewards to Bitcoin stakers and +vigilantes. + +## Vigilantes + +The vigilante suite of programs acts as a +relayer of data between Babylon and Bitcoin. +Babylon's secure operation requires +that at least one honest +operator of each of the programs exist. +Otherwise, +an alarm will be raised by the monitor program. + +### [Vigilante Submitter](https://github.com/babylonchain/vigilante) + +A standalone program that submits +Babylon checkpoints to Bitcoin as +Bitcoin transactions embedding data +utilising the `OP_RETURN` Bitcoin script code. + +### [Vigilante Reporter](https://github.com/babylonchain/vigilante) + +A standalone program that scans +the Bitcoin ledger for Bitcoin headers and Babylon checkpoints, +and reports them back to Babylon using Babylon transactions. + +## Monitors + +The monitor programs suite is responsible for +monitoring the consistency between Babylon's state and +Bitcoin. + +### [Checkpointing Monitor](https://github.com/babylonchain/vigilante) + +A standalone program that monitors: + +- The consistency between the Bitcoin canonical chain and + the Bitcoin header chain maintained by + Babylon's BTC Light client module. +- The timely inclusion of Babylon's Bitcoin checkpoints + information in the Babylon ledger. + +### [BTC Staking Monitor](https://github.com/babylonchain/vigilante) + +A standalone program that monitors: + +- The execution of BTC Staking on-demand unbonding transactions + on the Bitcoin ledger to inform Babylon about them. +- The execution of BTC Staking slashing transactions in the case + of a finality provider double voting. + In the case of non-execution the monitor extracts the finality provider's + private key and executes the slashing. +- The execution of a selective slashing attack launched + by a finality provider. In this case, + the monitor extracts the finality provider's private key + and slashes them. + +## BTC Staking Programs + +The BTC Staking programs suite +involves components that enable the function +Bitcoin Stakers and Finality Providers +while also ensuring their adherence to the protocol. + +### BTC Staker + +Bitcoin holders can stake their Bitcoin +by creating a set of Bitcoin transactions, +including them to the Bitcoin ledger, and +then informing Babylon about their staking. +Later, they can also on-demand unlock or +withdraw their funds when their stake expires. +The following set of standalone programs +has been developed to enable these functionalities: +- [BTC Staker Daemon](https://github.com/babylonchain/btc-staker): + Daemon program connecting to a Bitcoin wallet and Babylon. +- [BTC Staker Dashboard](https://github.com/babylonchain/btc-staking-dashboard): + Web application connecting to a Bitcoin wallet extension and the Babylon API. + Should only be used for testing purposes. +- Wallet Integrations (TBD) + +### [Finality Provider](https://github.com/babylonchain/finality-provider) + +A standalone program that allows for the registration and +maintenance of a finality provider. +It monitors for a finality provider's inclusion in the active set, commits +[Extractable One Time Signature (EOTS)](https://docs.babylonchain.io/assets/files/btc_staking_litepaper-32bfea0c243773f0bfac63e148387aef.pdf) +public randomness, and +submits finality votes for blocks. +Finality votes are created through a connection to a standalone +[EOTS manager daemon](https://github.com/babylonchain/finality-provider) +responsible for securely maintaining the +finality provider's private keys. + +### [Covenant Emulator](https://github.com/babylonchain/covenant-emulator) + +A standalone program utilised by the covenant emulation committee members. +It emulates [covenant](https://covenants.info) functionality by monitoring +for pending staking requests, +verifying their contents, and +submitting necessary signatures. + +## Consumer Zones + +### IBC Relayer + +The IBC Relayer maintains the +[IBC protocol](https://cosmos.network/ibc/) connection +between Babylon and other Consumer Zones (CZs). +It is responsible for updating the CZ's light client +inside the Babylon ledger to enable checkpointing and +propagating checkpoint information to the Babylon smart contract +deployed within the CZ. + +There are different IBC relayer implementations that can achieve +this function. Most notably: +- [Cosmos Relayer](https://github.com/cosmos/relayer): + A fully functional relayer written in Go. +- [Babylon Relayer](https://github.com/babylonchain/babylon-relayer/): + A wrapper of the Cosmos Relayer that can maintain a one-way IBC connection. + It is recommended to be used when the Consumer Zone does not deploy the + Babylon smart contract. +- [Hermes Relayer](https://github.com/informalsystems/hermes): + A fully functional relayer written in Rust. + +### [Babylon Contract](https://github.com/babylonchain/babylon-contract) + +A [CosmWasm](https://cosmwasm.com/) smart contract intended for +deployment in a Consumer Zone. +It enables Bitcoin Checkpointing functionality without introducing +invasive changes in the codebase of the Consumer Zone. +Based on the Bitcoin Checkpointing functionality, +the Consumer Zone can make decisions based on the inclusion +of its checkpoints in the Bitcoin ledger +(e.g. execute BTC-assisted unbonding requests). diff --git a/docs/static/arch.excalidraw b/docs/static/arch.excalidraw new file mode 100644 index 000000000..bbd73cd87 --- /dev/null +++ b/docs/static/arch.excalidraw @@ -0,0 +1,12630 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 340, + "versionNonce": 1771279469, + "isDeleted": false, + "id": "mcq3uHl3gntSG5245aRvO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10581.623910435768, + "y": 1514.456137017838, + "strokeColor": "#1e1e1e", + "backgroundColor": "#eebefa", + "width": 3474.2757184114666, + "height": 1162.4597560067823, + "seed": 1952395142, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "PIo2X8_qG0ipchEme9mOU", + "type": "arrow" + }, + { + "id": "OMRiiKcWvoZZ9P2ZIgA2p", + "type": "arrow" + }, + { + "id": "7hrt9Q8ALp2hgi-CH7RhI", + "type": "arrow" + }, + { + "id": "BWjQ8wJWdXaye_ajaXVvj", + "type": "arrow" + }, + { + "id": "kpT676I1UQJTahGMjR2KY", + "type": "arrow" + }, + { + "id": "8E8bIA1TLsDkEkmLGooFY", + "type": "arrow" + }, + { + "id": "GFH7lVRLTqVlZnVWPiljy", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 407, + "versionNonce": 1517286627, + "isDeleted": false, + "id": "EK_xusAsckpkIcS-MXJEd", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10598.136996979245, + "y": 1553.7018200231457, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 3434.6746007716406, + "height": 529.3673460007709, + "seed": 576342406, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "YJq6laSN-yx57jGCvqtHl", + "type": "arrow" + }, + { + "id": "i8rUSngD92Yf-8G5kqyGx", + "type": "arrow" + }, + { + "id": "Th72X_O0Jcw9QslRrN6dG", + "type": "arrow" + }, + { + "id": "ubD9PQiYw8EIHbYNeDuZg", + "type": "arrow" + }, + { + "id": "u56fJvEUflV3M4Wf2lfxj", + "type": "arrow" + }, + { + "id": "XfoF5gyizY9lr0o_lqvKd", + "type": "arrow" + }, + { + "id": "4C_kRR9PuZj7jJ5ntwVfz", + "type": "arrow" + }, + { + "id": "ByxqHImFIZUnMJs-EiM9L", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 375, + "versionNonce": 1012956877, + "isDeleted": false, + "id": "jI5SxjH5n3FRdYSSMZzjk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 935.254278524366, + "y": 810.9461934446426, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 709.0507674670008, + "height": 1438.7687038603308, + "seed": 214421556, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "K1_BQ-eNxQoevuNfEo-FQ", + "type": "arrow" + }, + { + "id": "BNsbfOJxpu3SiumhnV4Pp", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 245, + "versionNonce": 386228355, + "isDeleted": false, + "id": "igFd0InbawEl8svohKYrJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1063.448970961376, + "y": 838.3187252660047, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.39999389648438, + "height": 67.76958097087783, + "seed": 1231820212, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 54.21566477670226, + "fontFamily": 1, + "text": "Babylon", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon", + "lineHeight": 1.25, + "baseline": 48 + }, + { + "type": "line", + "version": 1581, + "versionNonce": 1323375917, + "isDeleted": false, + "id": "9eBByjO2HfoGPqFBH7hwX", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1131.836454370302, + "y": 2565.185456674697, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1046945744, + "groupIds": [ + "bQLU23_-t1X52UFFmjbDe", + "kaUp0V4XDOFsPbtazd_zf" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1260, + "versionNonce": 1630296099, + "isDeleted": false, + "id": "zpoa8O-C_49AKFWWUkuEO", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1138.5140601484704, + "y": 2529.42657239686, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 437429712, + "groupIds": [ + "bQLU23_-t1X52UFFmjbDe", + "kaUp0V4XDOFsPbtazd_zf" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1558, + "versionNonce": 1484713869, + "isDeleted": false, + "id": "6YnAv5R0wSRRQHrxVkGVA", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1203.6893504013765, + "y": 2561.763890197028, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1097861584, + "groupIds": [ + "L841L7GshHE7nhetcYpu9", + "79DD2RffIvBe48C8kMKLV" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1237, + "versionNonce": 651385795, + "isDeleted": false, + "id": "bDrow_QQ2uoMWyAkhmLt9", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1210.366956179545, + "y": 2526.005005919191, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1617842128, + "groupIds": [ + "L841L7GshHE7nhetcYpu9", + "79DD2RffIvBe48C8kMKLV" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1563, + "versionNonce": 1735620077, + "isDeleted": false, + "id": "p_UMN7oDs17jo84NCppRZ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1272.120679954781, + "y": 2563.474673435862, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1073423152, + "groupIds": [ + "FVJgsTSbWaBLiHWr0J8rR", + "buYNEMfFx4pfCg3Jwxhoo" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1242, + "versionNonce": 337177443, + "isDeleted": false, + "id": "ZND1YX1Z3a9r3CApWkd_K", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1278.7982857329496, + "y": 2527.715789158025, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 84884784, + "groupIds": [ + "FVJgsTSbWaBLiHWr0J8rR", + "buYNEMfFx4pfCg3Jwxhoo" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 472, + "versionNonce": 113782861, + "isDeleted": false, + "id": "X9v7_TtCdmuXMD42T1sms", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 989.9993421670897, + "y": 2439.6118368156713, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 475.5977403961617, + "height": 155.681274733995, + "seed": 1568945104, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "9-neyMzbXyIAzVdCZgE37", + "type": "arrow" + }, + { + "id": "hIGhfc7N5UnvINvkJx3X-", + "type": "arrow" + }, + { + "id": "hqjhvGv_tzZo3qWQSfyYg", + "type": "arrow" + }, + { + "id": "3EYM65srfkkFAkRf4Nf0R", + "type": "arrow" + }, + { + "id": "h-FxVGkngS5rHxj52BuW6", + "type": "arrow" + }, + { + "id": "I7ZDOOFXqDv-baF94yeo3", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 166, + "versionNonce": 1296795395, + "isDeleted": false, + "id": "QbINWPdkTxErSSSZcflzt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1087.5139867806913, + "y": 2448.430452442857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 276.0333251953125, + "height": 45, + "seed": 1255878960, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC Validators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Validators", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 1614, + "versionNonce": 609656493, + "isDeleted": false, + "id": "9-neyMzbXyIAzVdCZgE37", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 985.737144751741, + "y": 2439.7865955042416, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 580.3638685513667, + "height": 820.1585715411788, + "seed": 1266209232, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "tiAtyPBHjgSuF9M_WHV7m" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": -0.5053529283640728, + "gap": 4.2621974153486235 + }, + "endBinding": { + "elementId": "oWuiUx2ktjtNyWW0RbdKk", + "focus": 0.24670147912627394, + "gap": 9.85634884674755 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -529.5021731012084, + -508.27738062259823 + ], + [ + 50.86169545015832, + -820.1585715411788 + ] + ] + }, + { + "type": "text", + "version": 104, + "versionNonce": 1760145059, + "isDeleted": false, + "id": "tiAtyPBHjgSuF9M_WHV7m", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 974.5056998931386, + "y": 2018.234642734079, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 80.5999984741211, + "height": 25, + "seed": 186621904, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Register", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9-neyMzbXyIAzVdCZgE37", + "originalText": "Register", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 485, + "versionNonce": 919093517, + "isDeleted": false, + "id": "l-VxcloF1H5GLPDnWvCWJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 298.8429136777026, + "y": 1418.2742432311086, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 234.37730372041068, + "height": 278.85766793012385, + "seed": 1239093200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "K1_BQ-eNxQoevuNfEo-FQ", + "type": "arrow" + }, + { + "id": "BNsbfOJxpu3SiumhnV4Pp", + "type": "arrow" + }, + { + "id": "EowCRh9q6NgH7YHsP82Ls", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 244, + "versionNonce": 698475075, + "isDeleted": false, + "id": "5f26CvXqEUV7O-SBwo0Cv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 319.3723125437237, + "y": 1439.06834153014, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 194.23333740234375, + "height": 45, + "seed": 2015116592, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Delegators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Delegators", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "line", + "version": 1568, + "versionNonce": 597071725, + "isDeleted": false, + "id": "omrSW1p9HQfSxC3CFXXky", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 396.1996616712015, + "y": 1562.6664787173202, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 253368272, + "groupIds": [ + "73fwTXN88iALHhbYutpmU", + "CDyz2Gb40BrRst5hBlNBj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1248, + "versionNonce": 2018681315, + "isDeleted": false, + "id": "Hm2H_LnAkLQPiDu-55X5B", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 402.87726744937004, + "y": 1526.9075944394833, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1235656144, + "groupIds": [ + "73fwTXN88iALHhbYutpmU", + "CDyz2Gb40BrRst5hBlNBj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1518, + "versionNonce": 1695228365, + "isDeleted": false, + "id": "7hi9JyWZNGZ9tZ-V9gDzY", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 392.77809519353127, + "y": 1624.254675315385, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 57334576, + "groupIds": [ + "pGByMNfvcC_QP1hHr0pvw", + "9faYOwl283k8XB1RgmZxT" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1197, + "versionNonce": 503999875, + "isDeleted": false, + "id": "h-RqYNgfETQnnNArxRuQk", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 399.4557009716998, + "y": 1588.4957910375476, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 835188016, + "groupIds": [ + "pGByMNfvcC_QP1hHr0pvw", + "9faYOwl283k8XB1RgmZxT" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1520, + "versionNonce": 1883201581, + "isDeleted": false, + "id": "yTfBBenj07FGW3lNyp2Gr", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 394.4888784323665, + "y": 1680.7105221969434, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1604977104, + "groupIds": [ + "xf8IqsZHP5ZflMZVLMDIB", + "GVI2jR5M9qW-gM-aZdViG" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1200, + "versionNonce": 1040696611, + "isDeleted": false, + "id": "E8ta0We_g-r_JrmBOAf8l", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 401.16648421053503, + "y": 1644.9516379191061, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 346638288, + "groupIds": [ + "xf8IqsZHP5ZflMZVLMDIB", + "GVI2jR5M9qW-gM-aZdViG" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 2053, + "versionNonce": 1528615565, + "isDeleted": false, + "id": "K1_BQ-eNxQoevuNfEo-FQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 536.6417838757835, + "y": 1522.760565187596, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 499.54870573985306, + "height": 72.19291385590486, + "seed": 352565200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "nAJhEtoz2L8Nz6Uo4K3IL" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "l-VxcloF1H5GLPDnWvCWJ", + "focus": -0.07803258904145931, + "gap": 3.421566477670268 + }, + "endBinding": { + "elementId": "oWuiUx2ktjtNyWW0RbdKk", + "focus": -0.25279303227658806, + "gap": 10.264699433010264 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 249.7743528699259, + -46.31969183609317 + ], + [ + 499.54870573985306, + 25.873222019811692 + ] + ] + }, + { + "type": "text", + "version": 82, + "versionNonce": 263792835, + "isDeleted": false, + "id": "nAJhEtoz2L8Nz6Uo4K3IL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 652.6318355255535, + "y": 1569.1540425398616, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 219.6666717529297, + "height": 25, + "seed": 1108101936, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Delegate to Validator", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "K1_BQ-eNxQoevuNfEo-FQ", + "originalText": "Delegate to Validator", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 1966, + "versionNonce": 1113389293, + "isDeleted": false, + "id": "BNsbfOJxpu3SiumhnV4Pp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1034.4797063768012, + "y": 1578.5723840967194, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 492.70557278451236, + "height": 47.04080725299514, + "seed": 1920390448, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "c0unLHK01D1YMERb5apNM" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "oWuiUx2ktjtNyWW0RbdKk", + "focus": -0.007235549823675875, + "gap": 11.975482671845612 + }, + "endBinding": { + "elementId": "l-VxcloF1H5GLPDnWvCWJ", + "focus": -0.210158554590088, + "gap": 8.553916194175628 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -242.93121991458645, + 15.91253273440634 + ], + [ + -492.70557278451236, + -31.128274518588796 + ] + ] + }, + { + "type": "text", + "version": 127, + "versionNonce": 1488787555, + "isDeleted": false, + "id": "c0unLHK01D1YMERb5apNM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 698.2592905173183, + "y": 1610.874588854431, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 131.8333282470703, + "height": 75, + "seed": 827577296, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Validators\nand chains\nthey validate", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BNsbfOJxpu3SiumhnV4Pp", + "originalText": "Validators\nand chains\nthey validate", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "rectangle", + "version": 334, + "versionNonce": 1871270733, + "isDeleted": false, + "id": "3Kg-UpYyQzQOkeMXdE-kQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1046.4551890486468, + "y": 2040.99934216709, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 396.90171140974655, + "height": 201.87242218254346, + "seed": 1167215056, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "ve_qswif0oRQu7buEaV8r", + "type": "arrow" + }, + { + "id": "hqjhvGv_tzZo3qWQSfyYg", + "type": "arrow" + }, + { + "id": "3EYM65srfkkFAkRf4Nf0R", + "type": "arrow" + }, + { + "id": "CLmpSDPqzoyNVdM8bskrx", + "type": "arrow" + }, + { + "id": "fHa7mU34mFfK6rFMd8b8W", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 518, + "versionNonce": 652350467, + "isDeleted": false, + "id": "Dy0E3pNJ_DML2Soyo5PZK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1958.3026553477625, + "y": 812.6569766834787, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 656.9407637126835, + "height": 1455.8765362486815, + "seed": 96971056, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 150, + "versionNonce": 1056661933, + "isDeleted": false, + "id": "fhfk9Qc8nhOCy-NKkk7Os", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2103.719230648747, + "y": 835.1618582213459, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 265.5666809082031, + "height": 45, + "seed": 251421488, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Consumer Chain", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Consumer Chain", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 342, + "versionNonce": 1244044195, + "isDeleted": false, + "id": "XRsX4CQXeYyrF6iFbcwkf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1990.8075368856294, + "y": 927.2794536854315, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 586.7986509204438, + "height": 1086.347356660297, + "seed": 1025845712, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "RjZMbOE6EaUXBfBevc8RA", + "type": "arrow" + }, + { + "id": "k5XmRkToqNRcQ9CVHvwF2", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 156, + "versionNonce": 1172087821, + "isDeleted": false, + "id": "jp53mA0XDBTCeudMNmCdu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2019.8908519458262, + "y": 937.4118034019367, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 195.8000030517578, + "height": 35, + "seed": 1684464592, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BBN Contract", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "BBN Contract", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 167, + "versionNonce": 247017283, + "isDeleted": false, + "id": "-fU10jT3GNlbPP5PigmFn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1025.9257901826256, + "y": 992.2892167611656, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 434.53894266411885, + "height": 102.64699433010647, + "seed": 1793259824, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "aPy186FNWzxJXzWqaVRUa", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 107, + "versionNonce": 942053997, + "isDeleted": false, + "id": "8oUpk-NBK6jhtFsVmT0l-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1116.5973018408865, + "y": 1011.2401821048572, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 226.46665954589844, + "height": 45, + "seed": 551498544, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Timestamping", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Timestamping", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 206, + "versionNonce": 922242787, + "isDeleted": false, + "id": "IE6raU1vN7ZaQgjKbsP_I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2040.420250811848, + "y": 1000.8431329553414, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 506.3918386951934, + "height": 94.09307813593159, + "seed": 2124307248, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "sJTA7xjvulEX_PgGCpiTw", + "type": "arrow" + }, + { + "id": "aPy186FNWzxJXzWqaVRUa", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 167, + "versionNonce": 245548237, + "isDeleted": false, + "id": "S3TVaWOLg26PaZPRq9iFV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 6.278007089795098, + "x": 2149.910378097295, + "y": 1019.9264480155383, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 226.46665954589844, + "height": 45, + "seed": 1514290640, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Timestamping", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Timestamping", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 245, + "versionNonce": 896694915, + "isDeleted": false, + "id": "oXlZjJ6fHpM0JEGCEuxaN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2045.5526005283534, + "y": 1242.0635696310922, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 496.1271392621827, + "height": 179.6322400776869, + "seed": 1366099248, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "sJTA7xjvulEX_PgGCpiTw", + "type": "arrow" + }, + { + "id": "bdiHkTDzy9zV8N9HxCSSw", + "type": "arrow" + }, + { + "id": "tzgh7GO8Oh0n7_ZVppRoK", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 193, + "versionNonce": 1827316525, + "isDeleted": false, + "id": "LQevBNiNcVk_XVP9ywkkY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2177.282909918658, + "y": 1267.7253182136192, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 180.8333282470703, + "height": 45, + "seed": 1205069104, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "btcstaking", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "btcstaking", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 347, + "versionNonce": 652620323, + "isDeleted": false, + "id": "uzA8ziWTtK0AsmUy3zLry", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2093.454531215736, + "y": 1342.7350812893528, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 415.6333312988281, + "height": 50, + "seed": 1095966672, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- Active BTC validators and delegations\n- Verifies delegations based on BTC view ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- Active BTC validators and delegations\n- Verifies delegations based on BTC view ", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 155, + "versionNonce": 1197979021, + "isDeleted": false, + "id": "w2dXDneqDY3sHfMyjPTXG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2332.9641846526524, + "y": 2135.0924203030218, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 222.40182104856444, + "height": 99.2254278524365, + "seed": 1662100944, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "9nV-VJmIHd59YQ3cj9FmB" + }, + { + "id": "RjZMbOE6EaUXBfBevc8RA", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 126, + "versionNonce": 205649347, + "isDeleted": false, + "id": "9nV-VJmIHd59YQ3cj9FmB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2356.4317654039855, + "y": 2162.2051342292402, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 175.46665954589844, + "height": 45, + "seed": 106403280, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Consensus", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "w2dXDneqDY3sHfMyjPTXG", + "originalText": "Consensus", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 910, + "versionNonce": 1230207981, + "isDeleted": false, + "id": "RjZMbOE6EaUXBfBevc8RA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2473.6172046407873, + "y": 2126.5385041088457, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 182.5520033377179, + "height": 464.4821305286473, + "seed": 2018388432, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "oraHyp86WSPqJCcz8KU3X" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "w2dXDneqDY3sHfMyjPTXG", + "focus": -0.04958680170408755, + "gap": 8.553916194175827 + }, + "endBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "focus": -0.8187827599837586, + "gap": 8.553916194174917 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 182.5520033377179, + -290.83315060196924 + ], + [ + 66.35175191091275, + -464.4821305286473 + ] + ] + }, + { + "type": "text", + "version": 67, + "versionNonce": 1151616355, + "isDeleted": false, + "id": "oraHyp86WSPqJCcz8KU3X", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2067.8368809322415, + "y": 1853.9994518059088, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 61.5, + "height": 25, + "seed": 749724624, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Blocks", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "RjZMbOE6EaUXBfBevc8RA", + "originalText": "Blocks", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 237, + "versionNonce": 2058843725, + "isDeleted": false, + "id": "MpZJWSpMDFY-n7qtelImN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2011.3369357516508, + "y": 2136.803203541857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 234.37730372041096, + "height": 100, + "seed": 175513392, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "TiYDbxHw6Jvhwd-po9zCn" + }, + { + "id": "oxbKMbcxmmyRC0Uszo0za", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 202, + "versionNonce": 1197187331, + "isDeleted": false, + "id": "TiYDbxHw6Jvhwd-po9zCn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2035.1589189106844, + "y": 2141.803203541857, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 186.73333740234375, + "height": 90, + "seed": 1164512560, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Rewards \nMiddleware", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "MpZJWSpMDFY-n7qtelImN", + "originalText": "Rewards Middleware", + "lineHeight": 1.25, + "baseline": 77 + }, + { + "type": "arrow", + "version": 1013, + "versionNonce": 644687021, + "isDeleted": false, + "id": "sJTA7xjvulEX_PgGCpiTw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2278.504308338577, + "y": 1103.4901272854481, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.2717844870212502, + "height": 133.4410926291382, + "seed": 204340016, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "-Ezb4MpSw2xaKA8RhVKO0" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "IE6raU1vN7ZaQgjKbsP_I", + "focus": 0.054054055149846666, + "gap": 8.553916194175144 + }, + "endBinding": { + "elementId": "oXlZjJ6fHpM0JEGCEuxaN", + "focus": -0.08275862068965532, + "gap": 5.132349716505701 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.2717844870212502, + 133.4410926291382 + ] + ] + }, + { + "type": "text", + "version": 84, + "versionNonce": 501647523, + "isDeleted": false, + "id": "-Ezb4MpSw2xaKA8RhVKO0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2183.7850802186017, + "y": 1135.2106736000173, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 186.1666717529297, + "height": 70, + "seed": 1800789808, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BTC chain + \nCheckpoints", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "sJTA7xjvulEX_PgGCpiTw", + "originalText": "BTC chain + Checkpoints", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 233, + "versionNonce": 1967885, + "isDeleted": false, + "id": "bapEN-Oda9yJXFVTnxLpI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2060.9496496778693, + "y": 1570.5339514874343, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 470.46539067965614, + "height": 165.94597416700594, + "seed": 1044380624, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "RjZMbOE6EaUXBfBevc8RA", + "type": "arrow" + }, + { + "id": "bdiHkTDzy9zV8N9HxCSSw", + "type": "arrow" + }, + { + "id": "h-FxVGkngS5rHxj52BuW6", + "type": "arrow" + }, + { + "id": "I7ZDOOFXqDv-baF94yeo3", + "type": "arrow" + }, + { + "id": "1mR6xqwOtINXSyrvyV3k2", + "type": "arrow" + }, + { + "id": "UW2vwnBaiRHvonL7pl21d", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 151, + "versionNonce": 502496323, + "isDeleted": false, + "id": "GEPC9a12TjZGnRUzicvlN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2220.052490889535, + "y": 1589.4849168311252, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 120.80000305175781, + "height": 45, + "seed": 1396162352, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "bdiHkTDzy9zV8N9HxCSSw", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "finality", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "finality", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 223, + "versionNonce": 172203373, + "isDeleted": false, + "id": "hw6VSdw7cEbOR96Bujjjr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2102.0084474099117, + "y": 1645.5437145631681, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 393.8333435058594, + "height": 75, + "seed": 1417770960, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- Collects consensus blocks\n- processes signatures for those blocks\n- manages validators' randomness", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- Collects consensus blocks\n- processes signatures for those blocks\n- manages validators' randomness", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "arrow", + "version": 528, + "versionNonce": 1667300323, + "isDeleted": false, + "id": "bdiHkTDzy9zV8N9HxCSSw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2264.4835799034536, + "y": 1431.96050914179, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.6321014821578501, + "height": 130.01952615146865, + "seed": 2123662800, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "7Ft2TboD60hs2SxZu3iKG" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "oXlZjJ6fHpM0JEGCEuxaN", + "gap": 10.26469943301106, + "focus": 0.12196884172500101 + }, + "endBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "gap": 8.553916194175486, + "focus": -0.12239062053188139 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.6321014821578501, + 130.01952615146865 + ] + ] + }, + { + "type": "text", + "version": 114, + "versionNonce": 1009215437, + "isDeleted": false, + "id": "7Ft2TboD60hs2SxZu3iKG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2169.6103070392546, + "y": 1530.136902337918, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 193.26666259765625, + "height": 50, + "seed": 113373648, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Active Delegations \nfor block", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bdiHkTDzy9zV8N9HxCSSw", + "originalText": "Active Delegations for block", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 123, + "versionNonce": 171804547, + "isDeleted": false, + "id": "XLV0apwvUf-95Ikn5tQNT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2069.5035658720453, + "y": 1838.4537760189055, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 460.2006912466453, + "height": 143.7057920621496, + "seed": 92739376, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "oxbKMbcxmmyRC0Uszo0za", + "type": "arrow" + }, + { + "id": "FcQpoNogq2Nm9AsrkForr", + "type": "arrow" + }, + { + "id": "UW2vwnBaiRHvonL7pl21d", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 71, + "versionNonce": 1742923309, + "isDeleted": false, + "id": "vsG0L_Oy2c9JHJXMoITGK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2184.1260428739974, + "y": 1834.2592697010527, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 146.03334045410156, + "height": 45, + "seed": 251469104, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "incentive", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "incentive", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 245, + "versionNonce": 684171043, + "isDeleted": false, + "id": "MLLQQJX_gOT9E6PWE0j0b", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2125.9594127536034, + "y": 1893.60728419426, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 351.9333190917969, + "height": 50, + "seed": 1006141232, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- collects rewards for each block\n- distributes rewards to validators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- collects rewards for each block\n- distributes rewards to validators", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "arrow", + "version": 167, + "versionNonce": 1616877709, + "isDeleted": false, + "id": "oxbKMbcxmmyRC0Uszo0za", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2129.3809792312736, + "y": 2123.1169376311764, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 134.11423659478078, + "seed": 122708272, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "pqenW44iuTnPJ2rbsdpy-" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "MpZJWSpMDFY-n7qtelImN", + "focus": 0.007299270072990775, + "gap": 13.686265910680504 + }, + "endBinding": { + "elementId": "XLV0apwvUf-95Ikn5tQNT", + "focus": 0.7397769516728665, + "gap": 6.843132955340479 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -134.11423659478078 + ] + ] + }, + { + "type": "text", + "version": 85, + "versionNonce": 158188227, + "isDeleted": false, + "id": "pqenW44iuTnPJ2rbsdpy-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2034.0643135818596, + "y": 2000.6022930175748, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 190.63333129882812, + "height": 50, + "seed": 1395974960, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Token transfer of \nrewards", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oxbKMbcxmmyRC0Uszo0za", + "originalText": "Token transfer of rewards", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 179, + "versionNonce": 1599536877, + "isDeleted": false, + "id": "ofMiK1KHYfIynAOsVqat2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1034.5346034578095, + "y": 1126.7733539294657, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 420.8526767534381, + "height": 155.19617473249173, + "seed": 587171120, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "aPy186FNWzxJXzWqaVRUa", + "type": "arrow" + }, + { + "id": "tzgh7GO8Oh0n7_ZVppRoK", + "type": "arrow" + }, + { + "id": "8hxi0sEZD-C2JdYmCn6cp", + "type": "arrow" + }, + { + "id": "FcQpoNogq2Nm9AsrkForr", + "type": "arrow" + }, + { + "id": "z9FxhoUfh_p_HuDlfW_jQ", + "type": "arrow" + }, + { + "id": "13tbfOPspPTrQ4jvUBqS8", + "type": "arrow" + }, + { + "id": "1mR6xqwOtINXSyrvyV3k2", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 173, + "versionNonce": 617509475, + "isDeleted": false, + "id": "Gilx9LAuX4U_RNPl-sB2W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1163.6343090996388, + "y": 1179.9326947637362, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 159.3000030517578, + "height": 45, + "seed": 1564107568, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "restaking", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "restaking", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 479, + "versionNonce": 726833485, + "isDeleted": false, + "id": "aPy186FNWzxJXzWqaVRUa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1461.4916988730813, + "y": 1039.0216170971448, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 572.1403160644345, + "height": 78.14832800509294, + "seed": 551999440, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "P_2FRlE44FXHr9d948KkW" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "-fU10jT3GNlbPP5PigmFn", + "focus": 0.398490965135845, + "gap": 1.0269660263367086 + }, + "endBinding": { + "elementId": "IE6raU1vN7ZaQgjKbsP_I", + "focus": -0.7305730201150965, + "gap": 6.788235874332145 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 294.7520049795039, + -56.04071882745848 + ], + [ + 572.1403160644345, + 22.10760917763446 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 1538797059, + "isDeleted": false, + "id": "P_2FRlE44FXHr9d948KkW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1666.4420003426449, + "y": 1129.4827501592385, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 166.39999389648438, + "height": 25, + "seed": 190769456, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "BTC Timestamps", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "aPy186FNWzxJXzWqaVRUa", + "originalText": "BTC Timestamps", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 403, + "versionNonce": 2108417965, + "isDeleted": false, + "id": "k5XmRkToqNRcQ9CVHvwF2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1982.2536206914542, + "y": 967.9428668123143, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 532.4898851115133, + "height": 163.54145853838384, + "seed": 165561808, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "6BWf0-an_-SUp_llydlU8" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "XRsX4CQXeYyrF6iFbcwkf", + "focus": 0.9422646641437531, + "gap": 8.553916194175144 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -221.4459472165404, + 80.81038345601917 + ], + [ + -532.4898851115133, + 163.54145853838384 + ] + ] + }, + { + "type": "text", + "version": 73, + "versionNonce": 1898542499, + "isDeleted": false, + "id": "6BWf0-an_-SUp_llydlU8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1650.8817233032134, + "y": 1061.0514206058338, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 137.53334045410156, + "height": 25, + "seed": 1941780784, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Register chain", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "k5XmRkToqNRcQ9CVHvwF2", + "originalText": "Register chain", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 419, + "versionNonce": 680290829, + "isDeleted": false, + "id": "tzgh7GO8Oh0n7_ZVppRoK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1467.3627628830934, + "y": 1193.531092469634, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 562.7927884957437, + "height": 95.25602118528127, + "seed": 123328464, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "XQrFDC1Wtt82SkVbiUZhk" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "focus": -0.29308730123075766, + "gap": 11.975482671845839 + }, + "endBinding": { + "elementId": "oXlZjJ6fHpM0JEGCEuxaN", + "focus": -0.19340385431013918, + "gap": 15.397049149516192 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 303.3802275387802, + 22.4666597391913 + ], + [ + 562.7927884957437, + 95.25602118528127 + ] + ] + }, + { + "type": "text", + "version": 95, + "versionNonce": 1146145091, + "isDeleted": false, + "id": "XQrFDC1Wtt82SkVbiUZhk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1637.3704330345856, + "y": 1244.2715682210107, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 224.43333435058594, + "height": 25, + "seed": 1601658160, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Validator Set Update ", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tzgh7GO8Oh0n7_ZVppRoK", + "originalText": "Validator Set Update ", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 141, + "versionNonce": 2095947885, + "isDeleted": false, + "id": "oWuiUx2ktjtNyWW0RbdKk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1046.4551890486468, + "y": 1440.5144253359658, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 400.3232778874167, + "height": 246.35278639225638, + "seed": 2043765552, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "8hxi0sEZD-C2JdYmCn6cp", + "type": "arrow" + }, + { + "id": "K1_BQ-eNxQoevuNfEo-FQ", + "type": "arrow" + }, + { + "id": "BNsbfOJxpu3SiumhnV4Pp", + "type": "arrow" + }, + { + "id": "9-neyMzbXyIAzVdCZgE37", + "type": "arrow" + }, + { + "id": "hIGhfc7N5UnvINvkJx3X-", + "type": "arrow" + }, + { + "id": "13tbfOPspPTrQ4jvUBqS8", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 120, + "versionNonce": 1250732259, + "isDeleted": false, + "id": "KkM2jbot4Uh37CwxneSPN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1147.3914001399187, + "y": 1444.2006912466468, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 180.8333282470703, + "height": 45, + "seed": 1759719728, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "8hxi0sEZD-C2JdYmCn6cp", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "btcstaking", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "btcstaking", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 282, + "versionNonce": 1889077965, + "isDeleted": false, + "id": "J2YNwz75b65WxH5u9WeIc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1099.4894694525353, + "y": 1522.3673213670406, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 322.8666687011719, + "height": 150, + "seed": 1205748176, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "ve_qswif0oRQu7buEaV8r", + "type": "arrow" + }, + { + "id": "fHa7mU34mFfK6rFMd8b8W", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- Keeps track of validators\n and the chains they intend\n to validate\n- Keeps track of validator\n delegations and the delegation\n status", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- Keeps track of validators\n and the chains they intend\n to validate\n- Keeps track of validator\n delegations and the delegation\n status", + "lineHeight": 1.25, + "baseline": 143 + }, + { + "type": "arrow", + "version": 420, + "versionNonce": 1231787139, + "isDeleted": false, + "id": "8hxi0sEZD-C2JdYmCn6cp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1318.2779315261082, + "y": 1431.9605091417902, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.719910124743592, + "height": 141.43706428565724, + "seed": 1251876656, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "nxZiS14IwWClAol9VPStB" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "KkM2jbot4Uh37CwxneSPN", + "focus": 0.8632990176278252, + "gap": 12.240182104856558 + }, + "endBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "gap": 8.5539161941756, + "focus": -0.34215644960803865 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.719910124743592, + -83.49186430683312 + ], + [ + 2.0091487127742766, + -141.43706428565724 + ] + ] + }, + { + "type": "text", + "version": 156, + "versionNonce": 69740845, + "isDeleted": false, + "id": "nxZiS14IwWClAol9VPStB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1209.5724047754566, + "y": 1312.0120393864413, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 217.76666259765625, + "height": 50, + "seed": 1396410320, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Validator delegations \nupdates", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8hxi0sEZD-C2JdYmCn6cp", + "originalText": "Validator delegations updates", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "arrow", + "version": 592, + "versionNonce": 1353452579, + "isDeleted": false, + "id": "hIGhfc7N5UnvINvkJx3X-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1079.5571254671095, + "y": 2436.190270338002, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 513.8320265311296, + "height": 777.7919193599225, + "seed": 379098576, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "DF9hwRGA6A-StP9JFI7aX" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": -0.20660851527352694, + "gap": 3.4215664776695576 + }, + "endBinding": { + "elementId": "oWuiUx2ktjtNyWW0RbdKk", + "focus": 0.10310326856195558, + "gap": 6.843132955340707 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -513.8320265311296, + -504.681055456359 + ], + [ + -39.945069373803335, + -777.7919193599225 + ] + ] + }, + { + "type": "text", + "version": 95, + "versionNonce": 1761901453, + "isDeleted": false, + "id": "DF9hwRGA6A-StP9JFI7aX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 593.9475744120535, + "y": 1933.8817467030049, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 172.8000030517578, + "height": 50, + "seed": 464463312, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Update validated\nchains", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hIGhfc7N5UnvINvkJx3X-", + "originalText": "Update validated\nchains", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "text", + "version": 227, + "versionNonce": 1950126019, + "isDeleted": false, + "id": "zXtTvKHeYbl7wvN-HfmHE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1166.2100157671043, + "y": 2046.3963913166065, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 120.80000305175781, + "height": 45, + "seed": 1582267696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "finality", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "finality", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 294, + "versionNonce": 1734836717, + "isDeleted": false, + "id": "bDN6LTGscJ4-rx6W10tOa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1087.5139867806893, + "y": 2116.273804675835, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 332.3999938964844, + "height": 100, + "seed": 395664176, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- collects consensus blocks\n- processes signatures for those\n blocks\n- manages validators' randomness", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- collects consensus blocks\n- processes signatures for those\n blocks\n- manages validators' randomness", + "lineHeight": 1.25, + "baseline": 93 + }, + { + "type": "arrow", + "version": 858, + "versionNonce": 1178312547, + "isDeleted": false, + "id": "ve_qswif0oRQu7buEaV8r", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1222.8871583970135, + "y": 1700.5534776389031, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 429.53360373344105, + "height": 393.56723401888803, + "seed": 1663580112, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "3OiTc64HIVc5Xc_a4K3uX" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "J2YNwz75b65WxH5u9WeIc", + "focus": -0.7714634996564614, + "gap": 28.1861562718625 + }, + "endBinding": { + "elementId": "3Kg-UpYyQzQOkeMXdE-kQ", + "focus": -0.6192360789399399, + "gap": 18.826009837054926 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -429.53360373344105, + 119.75482671845793 + ], + [ + -195.25797918542162, + 393.56723401888803 + ] + ] + }, + { + "type": "text", + "version": 107, + "versionNonce": 73055309, + "isDeleted": false, + "id": "3OiTc64HIVc5Xc_a4K3uX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 742.1535160827945, + "y": 1879.661200388436, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 218.73333740234375, + "height": 25, + "seed": 58452272, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Active Delegations. ", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ve_qswif0oRQu7buEaV8r", + "originalText": "Active Delegations. ", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 634, + "versionNonce": 284670723, + "isDeleted": false, + "id": "hqjhvGv_tzZo3qWQSfyYg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1154.5266748083152, + "y": 2425.925570904992, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.7224116137363126, + "height": 167.65675740584174, + "seed": 921937200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "eXyYOog56hrQydgVhh81L" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": -0.30935251798561925, + "gap": 13.686265910679367 + }, + "endBinding": { + "elementId": "3Kg-UpYyQzQOkeMXdE-kQ", + "focus": 0.4482758620689669, + "gap": 15.397049149516874 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.7224116137363126, + -167.65675740584174 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 1740090029, + "isDeleted": false, + "id": "eXyYOog56hrQydgVhh81L", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1061.8119850352657, + "y": 2221.817848155459, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 188.26666259765625, + "height": 25, + "seed": 1865183696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Commit Randomness", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hqjhvGv_tzZo3qWQSfyYg", + "originalText": "Commit Randomness", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 623, + "versionNonce": 1357865635, + "isDeleted": false, + "id": "3EYM65srfkkFAkRf4Nf0R", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1300.3578469906981, + "y": 2429.3471373826624, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.152347117853651, + "height": 167.65675740584174, + "seed": 228175824, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "U-q3fKx5KPNHy_pOInWwj" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": 0.2990766760337138, + "gap": 10.264699433009127 + }, + "endBinding": { + "elementId": "3Kg-UpYyQzQOkeMXdE-kQ", + "focus": -0.29608275394268135, + "gap": 18.818615627187114 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.152347117853651, + -167.65675740584174 + ] + ] + }, + { + "type": "text", + "version": 63, + "versionNonce": 36162829, + "isDeleted": false, + "id": "U-q3fKx5KPNHy_pOInWwj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1280.4006156977334, + "y": 2225.2394146331294, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 43.63333511352539, + "height": 25, + "seed": 1266543408, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Vote", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3EYM65srfkkFAkRf4Nf0R", + "originalText": "Vote", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 376, + "versionNonce": 1859196483, + "isDeleted": false, + "id": "h-FxVGkngS5rHxj52BuW6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1407.3048698649611, + "y": 2432.7687038603326, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 636.5369474245567, + "height": 773.2740239534719, + "seed": 1532766512, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "qEq5dpdfjX320ooAKeKu4" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": 0.4066956388810014, + "gap": 6.843132955338888 + }, + "endBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "focus": 0.7656817003628804, + "gap": 17.1078323883512 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 362.8116292109387, + -509.81340517286435 + ], + [ + 636.5369474245567, + -773.2740239534719 + ] + ] + }, + { + "type": "text", + "version": 63, + "versionNonce": 1931780973, + "isDeleted": false, + "id": "qEq5dpdfjX320ooAKeKu4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1708.9518170259294, + "y": 2002.8375935845645, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 43.63333511352539, + "height": 25, + "seed": 36369872, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Vote", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "h-FxVGkngS5rHxj52BuW6", + "originalText": "Vote", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 330, + "versionNonce": 964384227, + "isDeleted": false, + "id": "I7ZDOOFXqDv-baF94yeo3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1474.150998757425, + "y": 2442.237573703209, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 579.786270501495, + "height": 700.859076510359, + "seed": 1652303312, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "glu1IuiXYQo3aBygB2zmT" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "X9v7_TtCdmuXMD42T1sms", + "focus": 0.6093173465436234, + "gap": 8.553916194173667 + }, + "endBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "focus": 0.557975591517287, + "gap": 8.553916194175372 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 579.786270501495, + -700.859076510359 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 787983821, + "isDeleted": false, + "id": "glu1IuiXYQo3aBygB2zmT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1669.9954264411488, + "y": 2049.0287410331125, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 188.26666259765625, + "height": 25, + "seed": 619977168, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Commit Randomness", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "I7ZDOOFXqDv-baF94yeo3", + "originalText": "Commit Randomness", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 549, + "versionNonce": 536650115, + "isDeleted": false, + "id": "FcQpoNogq2Nm9AsrkForr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2062.6604329167044, + "y": 1873.2272975900787, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 600.4300197501159, + "height": 629.7139202663616, + "seed": 1239850960, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "eSK86We9_NvTofYu3o94Q" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "XLV0apwvUf-95Ikn5tQNT", + "focus": -0.7855089749192722, + "gap": 6.8431329553411615 + }, + "endBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "focus": -0.32714528964879064, + "gap": 6.843132955340934 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -299.89011425279364, + -499.05811272919595 + ], + [ + -600.4300197501159, + -629.7139202663616 + ] + ] + }, + { + "type": "text", + "version": 165, + "versionNonce": 1689938989, + "isDeleted": false, + "id": "eSK86We9_NvTofYu3o94Q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1647.6703201897897, + "y": 1336.6691848608828, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 230.1999969482422, + "height": 75, + "seed": 1924245296, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Rewards for validators\n+\nIBC Token Transfer", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "FcQpoNogq2Nm9AsrkForr", + "originalText": "Rewards for validators\n+\nIBC Token Transfer", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "rectangle", + "version": 164, + "versionNonce": 731085091, + "isDeleted": false, + "id": "b6KcYnozKEq-fYIxBcM6W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1055.0091052428222, + "y": 1787.803422819494, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 381.50466226023036, + "height": 135.15187586797447, + "seed": 1487472080, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "CLmpSDPqzoyNVdM8bskrx", + "type": "arrow" + }, + { + "id": "z9FxhoUfh_p_HuDlfW_jQ", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 132, + "versionNonce": 609640077, + "isDeleted": false, + "id": "zrxYY_x_rHMydclL1MoYB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1164.4992325282697, + "y": 1788.0681222525045, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 146.03334045410156, + "height": 45, + "seed": 1799196112, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "incentive", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "incentive", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 189, + "versionNonce": 967728323, + "isDeleted": false, + "id": "6fpmEHIkMrtnzYIcUTeCL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1085.8032035418541, + "y": 1852.548486462218, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 320.29998779296875, + "height": 50, + "seed": 1832829392, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "- responsible of keeping track of\n rewards and distributing them", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- responsible of keeping track of\n rewards and distributing them", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "arrow", + "version": 83, + "versionNonce": 1149568237, + "isDeleted": false, + "id": "CLmpSDPqzoyNVdM8bskrx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1232.9305620816738, + "y": 2025.6022930175754, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 97.5146446136016, + "seed": 921810896, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "E6JaMRl5d55kxyucKUuJW" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3Kg-UpYyQzQOkeMXdE-kQ", + "focus": -0.06034482758620939, + "gap": 15.3970491495146 + }, + "endBinding": { + "elementId": "b6KcYnozKEq-fYIxBcM6W", + "focus": 0.06726457399103282, + "gap": 5.1323497165053595 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -97.5146446136016 + ] + ] + }, + { + "type": "text", + "version": 87, + "versionNonce": 1978387555, + "isDeleted": false, + "id": "E6JaMRl5d55kxyucKUuJW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1131.880559029916, + "y": 1964.3449707107748, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 202.10000610351562, + "height": 25, + "seed": 248690480, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Votes for BBN block", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CLmpSDPqzoyNVdM8bskrx", + "originalText": "Votes for BBN block", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 629, + "versionNonce": 147840845, + "isDeleted": false, + "id": "z9FxhoUfh_p_HuDlfW_jQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1358.8593134638436, + "y": 1290.523444856133, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 184.6486846829648, + "height": 544.9712940542827, + "seed": 836724528, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "YoXfOV_j-a0jUPeXhx2MO" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "focus": -0.30836404398432243, + "gap": 8.5539161941756 + }, + "endBinding": { + "elementId": "b6KcYnozKEq-fYIxBcM6W", + "focus": 0.7400351664342889, + "gap": 5.095400090642215 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 184.6486846829648, + 414.73217957878023 + ], + [ + 82.7498541298512, + 544.9712940542827 + ] + ] + }, + { + "type": "text", + "version": 239, + "versionNonce": 337266691, + "isDeleted": false, + "id": "YoXfOV_j-a0jUPeXhx2MO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1444.8746668479803, + "y": 1667.7556244349132, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.26666259765625, + "height": 75, + "seed": 309293360, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Tranfer IBC token \nrewards for \nvalidators ", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z9FxhoUfh_p_HuDlfW_jQ", + "originalText": "Tranfer IBC token rewards for validators ", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "arrow", + "version": 364, + "versionNonce": 126905773, + "isDeleted": false, + "id": "13tbfOPspPTrQ4jvUBqS8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1099.9374015473259, + "y": 1288.8567431531974, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.7554594203365923, + "height": 140.75032341526526, + "seed": 511240144, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "lkhN2mqZyaYAd_7BxfLGI" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "gap": 6.887214491240002, + "focus": 0.6810510191094379 + }, + "endBinding": { + "elementId": "oWuiUx2ktjtNyWW0RbdKk", + "gap": 10.907358767503183, + "focus": -0.7442179053198268 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.7554594203365923, + 140.75032341526526 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 1245204387, + "isDeleted": false, + "id": "lkhN2mqZyaYAd_7BxfLGI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1017.0424308526667, + "y": 1329.3451002036502, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 166.13333129882812, + "height": 25, + "seed": 1827579696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Slashing Evidence", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "13tbfOPspPTrQ4jvUBqS8", + "originalText": "Slashing Evidence", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 138, + "versionNonce": 1341141005, + "isDeleted": false, + "id": "1mR6xqwOtINXSyrvyV3k2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2052.2436372524044, + "y": 1613.410430087221, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 601.086675291074, + "height": 326.20957309184996, + "seed": 26137904, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yJOfaLWucAHV8j0_f7duZ" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false, + "startBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "focus": -0.19395149162936365, + "gap": 8.706012425464678 + }, + "endBinding": { + "elementId": "ofMiK1KHYfIynAOsVqat2", + "focus": -0.3327234872122553, + "gap": 5.231328333413558 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -298.0595084087972, + -84.45019404915911 + ], + [ + -601.086675291074, + -326.20957309184996 + ] + ] + }, + { + "type": "text", + "version": 118, + "versionNonce": 175399747, + "isDeleted": false, + "id": "yJOfaLWucAHV8j0_f7duZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1671.117463194193, + "y": 1516.4602360380618, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 166.13333129882812, + "height": 25, + "seed": 1320932304, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Slashing Evidence", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "1mR6xqwOtINXSyrvyV3k2", + "originalText": "Slashing Evidence", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 459, + "versionNonce": 1340647021, + "isDeleted": false, + "id": "AMHVs2xnShhWLbgiqPQ1i", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 133.9477782536867, + "y": 1058.7706948312298, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 743.9666748046875, + "height": 315, + "seed": 1844066608, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770351, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegators:\n- Select a validator to delegate to\nbased on their trustworthiness and chains\nthey validate.\n- Create a staking transaction to this validator\nand send it to Babylon.\n- No changes to the btc-staker program apart\nfrom displaying some extra information to stakers\nrelated to the chains that validators are validating.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Delegators:\n- Select a validator to delegate to\nbased on their trustworthiness and chains\nthey validate.\n- Create a staking transaction to this validator\nand send it to Babylon.\n- No changes to the btc-staker program apart\nfrom displaying some extra information to stakers\nrelated to the chains that validators are validating.", + "lineHeight": 1.25, + "baseline": 305 + }, + { + "type": "rectangle", + "version": 115, + "versionNonce": 2100101859, + "isDeleted": false, + "id": "WuOw12xQRyeRBIfbt2RUi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -251.6434945541322, + "y": 1412.2437130774852, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 337.95940969626497, + "height": 229.08657972699848, + "seed": 608342832, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "EowCRh9q6NgH7YHsP82Ls", + "type": "arrow" + } + ], + "updated": 1704783770351, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 63, + "versionNonce": 2051231949, + "isDeleted": false, + "id": "lzkJTxBLC5Q1dbND9uCr4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -140.50248062717253, + "y": 1477.093783681356, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 78.30000305175781, + "height": 45, + "seed": 1349112112, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 83, + "versionNonce": 988851843, + "isDeleted": false, + "id": "EowCRh9q6NgH7YHsP82Ls", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 283.6479194614283, + "y": 1527.921094919831, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 188.25926848852328, + "height": 2.2681839576930543, + "seed": 329796048, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Av0BFVBhVSkowWlgxcSqg" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "l-VxcloF1H5GLPDnWvCWJ", + "focus": 0.2227832994456643, + "gap": 15.194994216274296 + }, + "endBinding": { + "elementId": "WuOw12xQRyeRBIfbt2RUi", + "focus": 0.0475855602437881, + "gap": 9.072735830772217 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -188.25926848852328, + 2.2681839576930543 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 748269357, + "isDeleted": false, + "id": "Av0BFVBhVSkowWlgxcSqg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 132.68495315539906, + "y": 1516.5551868986774, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 113.66666412353516, + "height": 25, + "seed": 41875760, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Staking txs", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "EowCRh9q6NgH7YHsP82Ls", + "originalText": "Staking txs", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 607, + "versionNonce": 1182656035, + "isDeleted": false, + "id": "ko-eqD_IxIdEoWROr-Qrd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1059.3668329924526, + "y": 2616.5494416817437, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 686.2666625976562, + "height": 350, + "seed": 1055652816, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Validators:\n- Register to Babylon as BTC Validators\n- Signal intent to validate chains\n (don't necessarily need to validate Babylon)\n- Commit public randomness and vote for blocks\n in each of the chains they validate.\n- Changes needed to btc-validator program\n - Modular validator implementation\n - Implementation of interface for communicating\n with the BBN smart contract", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Validators:\n- Register to Babylon as BTC Validators\n- Signal intent to validate chains\n (don't necessarily need to validate Babylon)\n- Commit public randomness and vote for blocks\n in each of the chains they validate.\n- Changes needed to btc-validator program\n - Modular validator implementation\n - Implementation of interface for communicating\n with the BBN smart contract", + "lineHeight": 1.25, + "baseline": 340 + }, + { + "type": "text", + "version": 2966, + "versionNonce": 1431650701, + "isDeleted": false, + "id": "FPcO8ulMV9FksOzCpt5rg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2721.945673981461, + "y": 889.6341386388548, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 990.7333374023438, + "height": 1575, + "seed": 591997744, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "A consumer chain deploys the BBN smart contract.\nThis is an extension of the smart contract developed\nfor receiving BTC timestamps.\n\nThe contract communicates with Babylon via the\nzoneconcierge module.\n\nThe btcstaking component maintains the active BTC\nvalidators and their delegations. It accomplishes this\nthrough the `ValidatorSetUpdate` message, which is\nsent from Babylon to the chain every time there is\nan update on the BTC validator set that has signalled\nintent to validate this chain. It contains:\n- Validators that are affected (either new or old)\n- For each validator affected, the validator's delegations\n that have changed state. A state change might be\n a delegation expiring, unbonding, or a new delegation.\n- This message also lets a chain know if a validator\n has been slashed.\n- Through the connection to the timestamping components,\n the module can verify proofs of inclusion of delegations.\n\nThe finality component acts in exactly the same way\nas the finality module of Babylon. It receives public\nrandomness and votes from validators. If a validator\ndouble votes, then the finality component constructs\na `SlashingEvidence` message which contains a proof\nthat the validator misbehaved. It knows whether a block\nhas been finalized by calculating the voting power table\nthrough a connection to the btcstaking component.\n\nThe incentive component is responsible for receiving tokens\nfrom the underlying consumer chain and sending them to\nBabylon to be distributed to validators + delegators\nthrough IBC token transfer. This module has several unknowns\n1. How can we transfer rewards from the underlying chain to\n the smart contract in the minimal invasive way?\n2. Do we need to provide any proofs that the validators for\n which we say that they should get rewards have actually voted?\n3. Should the message contain precise validator/delegator distributions\n or can we only specify the validators and Babylon does the further\n partitioning based on commissions? The size of the message will be\n significantly affected by our solution. \n\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "A consumer chain deploys the BBN smart contract.\nThis is an extension of the smart contract developed\nfor receiving BTC timestamps.\n\nThe contract communicates with Babylon via the\nzoneconcierge module.\n\nThe btcstaking component maintains the active BTC\nvalidators and their delegations. It accomplishes this\nthrough the `ValidatorSetUpdate` message, which is\nsent from Babylon to the chain every time there is\nan update on the BTC validator set that has signalled\nintent to validate this chain. It contains:\n- Validators that are affected (either new or old)\n- For each validator affected, the validator's delegations\n that have changed state. A state change might be\n a delegation expiring, unbonding, or a new delegation.\n- This message also lets a chain know if a validator\n has been slashed.\n- Through the connection to the timestamping components,\n the module can verify proofs of inclusion of delegations.\n\nThe finality component acts in exactly the same way\nas the finality module of Babylon. It receives public\nrandomness and votes from validators. If a validator\ndouble votes, then the finality component constructs\na `SlashingEvidence` message which contains a proof\nthat the validator misbehaved. It knows whether a block\nhas been finalized by calculating the voting power table\nthrough a connection to the btcstaking component.\n\nThe incentive component is responsible for receiving tokens\nfrom the underlying consumer chain and sending them to\nBabylon to be distributed to validators + delegators\nthrough IBC token transfer. This module has several unknowns\n1. How can we transfer rewards from the underlying chain to\n the smart contract in the minimal invasive way?\n2. Do we need to provide any proofs that the validators for\n which we say that they should get rewards have actually voted?\n3. Should the message contain precise validator/delegator distributions\n or can we only specify the validators and Babylon does the further\n partitioning based on commissions? The size of the message will be\n significantly affected by our solution. \n\n", + "lineHeight": 1.25, + "baseline": 1565 + }, + { + "type": "arrow", + "version": 106, + "versionNonce": 1187195331, + "isDeleted": false, + "id": "fHa7mU34mFfK6rFMd8b8W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1113.8032479770864, + "y": 2031.4579335276883, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 149.70014120774158, + "height": 347.0321455270371, + "seed": 748618192, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "s5gU5UWgV4kGKDi61s7wd" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3Kg-UpYyQzQOkeMXdE-kQ", + "focus": -0.16985167163782902, + "gap": 9.541408639401766 + }, + "endBinding": { + "elementId": "J2YNwz75b65WxH5u9WeIc", + "focus": 0.24079731712523425, + "gap": 12.058466633610578 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -131.55466954619715, + -172.38198078467212 + ], + [ + 18.145471661544434, + -347.0321455270371 + ] + ] + }, + { + "type": "text", + "version": 75, + "versionNonce": 1504675821, + "isDeleted": false, + "id": "s5gU5UWgV4kGKDi61s7wd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 925.5985769050103, + "y": 1824.0759527430162, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 113.30000305175781, + "height": 70, + "seed": 2054009808, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Slashing\nEvidence", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fHa7mU34mFfK6rFMd8b8W", + "originalText": "Slashing\nEvidence", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 127, + "versionNonce": 14612835, + "isDeleted": false, + "id": "UW2vwnBaiRHvonL7pl21d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2281.917986189009, + "y": 1743.3985709006706, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 88.45917435002889, + "seed": 218065200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yGbujroL94mA1fR0swcei" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "bapEN-Oda9yJXFVTnxLpI", + "focus": 0.06063935461046908, + "gap": 6.91864524623054 + }, + "endBinding": { + "elementId": "XLV0apwvUf-95Ikn5tQNT", + "focus": -0.07686179374676609, + "gap": 6.596030768205992 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + 88.45917435002889 + ] + ] + }, + { + "type": "text", + "version": 65, + "versionNonce": 1001521741, + "isDeleted": false, + "id": "yGbujroL94mA1fR0swcei", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2254.667986189009, + "y": 1775.128158075685, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 54.5, + "height": 25, + "seed": 74131920, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Votes", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UW2vwnBaiRHvonL7pl21d", + "originalText": "Votes", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 812, + "versionNonce": 304076035, + "isDeleted": false, + "id": "kt56wdKguh6DZf9tP4qAn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 902.8621399116323, + "y": 186.96074383862174, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 834.8333129882812, + "height": 595, + "seed": 377777104, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "In the Babylon node we have to perform\nthe following updates:\n\nbtcstaking:\n- Track the chains a validator has opted to delegate to\n- Handle slashing evidence from zoneconcierge module\n- At the EndBlocker, send updates happened to validators\n and the chains they validate to the zoneconcierge module,\n as well as necessary proofs.\n\nincentive:\n- Handle IBC token transfer rewards and distribute them\n\nzoneconcierge:\n- Define message interfaces for the messages in between\n the consumer chain and Babylon.\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "In the Babylon node we have to perform\nthe following updates:\n\nbtcstaking:\n- Track the chains a validator has opted to delegate to\n- Handle slashing evidence from zoneconcierge module\n- At the EndBlocker, send updates happened to validators\n and the chains they validate to the zoneconcierge module,\n as well as necessary proofs.\n\nincentive:\n- Handle IBC token transfer rewards and distribute them\n\nzoneconcierge:\n- Define message interfaces for the messages in between\n the consumer chain and Babylon.\n", + "lineHeight": 1.25, + "baseline": 585 + }, + { + "type": "rectangle", + "version": 514, + "versionNonce": 3551405, + "isDeleted": false, + "id": "5f7uu4kjIEUFzrjUpX9s9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7007.774178879419, + "y": 945.8884991612201, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 709.0507674670008, + "height": 676.0071462436719, + "seed": 260055696, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "gFiD_drGpBD-W-UZK0NwH", + "type": "arrow" + }, + { + "id": "qQPd6opx3debj8-1YUcb5", + "type": "arrow" + }, + { + "id": "Giojol2wXs96hmIzH19d5", + "type": "arrow" + }, + { + "id": "JA8TyLpYrRi8MLw2LeRyM", + "type": "arrow" + }, + { + "id": "Py60shrNSM5iaxY-OKjWA", + "type": "arrow" + }, + { + "id": "BC0vFMLOBUKZMe0II0jva", + "type": "arrow" + }, + { + "id": "TV7k2DcOhFbsd7McAHI4P", + "type": "arrow" + }, + { + "id": "J9v9y3Ffg_2wkgPNTj4bZ", + "type": "arrow" + }, + { + "id": "UIkSG6S2UtMt2LBbzOoZ-", + "type": "arrow" + }, + { + "id": "yiNt60KG3azTY3duMRMfA", + "type": "arrow" + }, + { + "id": "p8LtEmaFkk4DaHFUquc3E", + "type": "arrow" + }, + { + "id": "IAu1_oifIihVnusXFf1rq", + "type": "arrow" + }, + { + "id": "41lDBkHDzYp0h0NaioZ7H", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 347, + "versionNonce": 1778662563, + "isDeleted": false, + "id": "h7nbiMGIF04_TjXN9QLka", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7245.357156711066, + "y": 1183.1682813344537, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.39999389648438, + "height": 67.76958097087783, + "seed": 1316835472, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 54.21566477670226, + "fontFamily": 1, + "text": "Babylon", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon", + "lineHeight": 1.25, + "baseline": 48 + }, + { + "type": "line", + "version": 1664, + "versionNonce": 1424331533, + "isDeleted": false, + "id": "rSPnMAmMv2vPjlPHFXx6y", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7269.398037932977, + "y": 1984.669247107432, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1369751184, + "groupIds": [ + "Q6ZejYyHbpZtFHQfOP-l1", + "PpL-5dWT6RL3rLrqil-Ta" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1343, + "versionNonce": 70955075, + "isDeleted": false, + "id": "LLJ7_mQxgISTTRF2bad6s", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7276.075643711146, + "y": 1948.9103628295952, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 430861456, + "groupIds": [ + "Q6ZejYyHbpZtFHQfOP-l1", + "PpL-5dWT6RL3rLrqil-Ta" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1641, + "versionNonce": 1983490413, + "isDeleted": false, + "id": "JYU01rQ4H1EnPGQoT1LLQ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7341.250933964052, + "y": 1981.2476806297627, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1347574416, + "groupIds": [ + "IOGzXgpb4fYBRMN1eprja", + "9TXhTcAvCdtvf49g7cdRk" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1320, + "versionNonce": 1654763491, + "isDeleted": false, + "id": "O014FmQmJ71K_GoUzpWHW", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7347.92853974222, + "y": 1945.4887963519259, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 675842192, + "groupIds": [ + "IOGzXgpb4fYBRMN1eprja", + "9TXhTcAvCdtvf49g7cdRk" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1646, + "versionNonce": 1434186701, + "isDeleted": false, + "id": "LZ3Rfy3tszlQSel9s_t9U", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7409.682263517456, + "y": 1982.9584638685965, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1028191888, + "groupIds": [ + "FcHkQqYEqoetsNL_xBe7r", + "rlxBycV5nP5gXrhTMBlpS" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1325, + "versionNonce": 1195883395, + "isDeleted": false, + "id": "fDQA_hdnLDrWFMjyHSEK8", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 7416.359869295625, + "y": 1947.1995795907596, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1946920080, + "groupIds": [ + "FcHkQqYEqoetsNL_xBe7r", + "rlxBycV5nP5gXrhTMBlpS" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 605, + "versionNonce": 2126901805, + "isDeleted": false, + "id": "Z2GPJMdSMH7oDinaJuQrP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7127.560925729765, + "y": 1859.0956272484063, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 475.5977403961617, + "height": 155.681274733995, + "seed": 1514705552, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "Giojol2wXs96hmIzH19d5", + "type": "arrow" + }, + { + "id": "JA8TyLpYrRi8MLw2LeRyM", + "type": "arrow" + }, + { + "id": "Py60shrNSM5iaxY-OKjWA", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 273, + "versionNonce": 778785571, + "isDeleted": false, + "id": "xxHevteGCHHMo1nEdj1Po", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7154.121006844141, + "y": 1867.9142428755922, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 425.6000061035156, + "height": 45, + "seed": 112705680, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Babylon BTC Validators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon BTC Validators", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 487, + "versionNonce": 1057039501, + "isDeleted": false, + "id": "Ofx5yKSduYv665xdEAj8Y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6247.192327909113, + "y": 1086.0990059111277, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 234.37730372041068, + "height": 278.85766793012385, + "seed": 2070071952, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "gFiD_drGpBD-W-UZK0NwH", + "type": "arrow" + }, + { + "id": "qQPd6opx3debj8-1YUcb5", + "type": "arrow" + }, + { + "id": "9Wp2uIUthDigdWp8INFhj", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 246, + "versionNonce": 142276291, + "isDeleted": false, + "id": "FHjwRM3_Zj23khl_WU5hU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6267.721726775135, + "y": 1106.8931042101592, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 194.23333740234375, + "height": 45, + "seed": 2009790608, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Delegators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Delegators", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "line", + "version": 1570, + "versionNonce": 166136557, + "isDeleted": false, + "id": "yuYjkBvz_V4k9nRYVQC3d", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6344.5490759026125, + "y": 1230.4912413973393, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1392338576, + "groupIds": [ + "8gtCuOni2QgOJPFtlJCjX", + "hBU-bZ9kdrBuNBG9mLj7t" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1250, + "versionNonce": 873801315, + "isDeleted": false, + "id": "WFuv3lHbFfjQU6Q9Tj2SX", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6351.226681680781, + "y": 1194.7323571195025, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 967971984, + "groupIds": [ + "8gtCuOni2QgOJPFtlJCjX", + "hBU-bZ9kdrBuNBG9mLj7t" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1520, + "versionNonce": 536945997, + "isDeleted": false, + "id": "HVgMQwm8lMt2nedrobX5K", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6341.127509424942, + "y": 1292.079437995404, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 27329168, + "groupIds": [ + "YPaDmuQObNsJZibRZAkyi", + "TO0_QepGN2HHhmF2gQr6W" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1199, + "versionNonce": 940073475, + "isDeleted": false, + "id": "_dSBYYCtV9AagDJo_srC0", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6347.805115203111, + "y": 1256.3205537175668, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 159527056, + "groupIds": [ + "YPaDmuQObNsJZibRZAkyi", + "TO0_QepGN2HHhmF2gQr6W" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1522, + "versionNonce": 1957229485, + "isDeleted": false, + "id": "zlevQdrH8nwV-yCPKT3Eo", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6342.838292663778, + "y": 1348.5352848769626, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 420322960, + "groupIds": [ + "pFgvakPz64Rvvprivmbns", + "6KL8W5Otw2Lqa8wvd7RcF" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1202, + "versionNonce": 1028860323, + "isDeleted": false, + "id": "ytrPTr01YU5fMLw0pm4iw", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 6349.515898441946, + "y": 1312.7764005991253, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1090210960, + "groupIds": [ + "pFgvakPz64Rvvprivmbns", + "6KL8W5Otw2Lqa8wvd7RcF" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 2165, + "versionNonce": 1409807885, + "isDeleted": false, + "id": "gFiD_drGpBD-W-UZK0NwH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6484.991198107195, + "y": 1190.5853278676152, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 511.3744663230573, + "height": 59.97751480944885, + "seed": 990067344, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "wKERMThinXI5vuKcpVLXn" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Ofx5yKSduYv665xdEAj8Y", + "focus": -0.07803258904145931, + "gap": 3.421566477670268 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "gap": 11.408514449167342, + "focus": -0.010134800773305403 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 249.7743528699259, + -46.31969183609317 + ], + [ + 511.3744663230573, + 13.657822973355678 + ] + ] + }, + { + "type": "text", + "version": 107, + "versionNonce": 1227095363, + "isDeleted": false, + "id": "wKERMThinXI5vuKcpVLXn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6619.4988807500695, + "y": 1131.765636031522, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 230.53334045410156, + "height": 25, + "seed": 1239246992, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Delegate to Validators", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "gFiD_drGpBD-W-UZK0NwH", + "originalText": "Delegate to Validators", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 2077, + "versionNonce": 1226288237, + "isDeleted": false, + "id": "qQPd6opx3debj8-1YUcb5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6994.654881191416, + "y": 1242.6758727340282, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 504.53133336771646, + "height": 47.04080725299514, + "seed": 423745168, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "xdxtSuzMCbQQP3Yrg2sWJ" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "gap": 13.119297688002916, + "focus": 0.19037803160455372 + }, + "endBinding": { + "elementId": "Ofx5yKSduYv665xdEAj8Y", + "focus": -0.210158554590088, + "gap": 8.553916194175628 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -254.75698049779055, + 19.63380677711666 + ], + [ + -504.53133336771646, + -27.407000475878476 + ] + ] + }, + { + "type": "text", + "version": 130, + "versionNonce": 920214755, + "isDeleted": false, + "id": "xdxtSuzMCbQQP3Yrg2sWJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6673.981236570091, + "y": 1224.8096795111449, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 131.8333282470703, + "height": 75, + "seed": 501777552, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Validators\nand chains\nthey validate", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qQPd6opx3debj8-1YUcb5", + "originalText": "Validators\nand chains\nthey validate", + "lineHeight": 1.25, + "baseline": 68 + }, + { + "type": "rectangle", + "version": 478, + "versionNonce": 1134663373, + "isDeleted": false, + "id": "r4v0FbTF5HqPDSOjESQ79", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6991.626703538119, + "y": 148.7898298301879, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1825.0488030341673, + "height": 229.08657972699848, + "seed": 857896080, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "9Wp2uIUthDigdWp8INFhj", + "type": "arrow" + }, + { + "id": "2tbxDclyApG1yvPHvk2aZ", + "type": "arrow" + }, + { + "id": "NwIiDKmNYbPb8ZDJEKuaO", + "type": "arrow" + }, + { + "id": "d7CFBP2RyYRBwJ9VuOR81", + "type": "arrow" + }, + { + "id": "HR75PFP27DrbOlqk-Yx1H", + "type": "arrow" + }, + { + "id": "PuvSeOD6rWEj-1r-IYOSX", + "type": "arrow" + }, + { + "id": "_s7MHtQZYSEdXqgtUKkZg", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 200, + "versionNonce": 1912719491, + "isDeleted": false, + "id": "etZDKrUaw8P-mfQio3nl2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7877.355035664942, + "y": 240.24786174626792, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 78.30000305175781, + "height": 45, + "seed": 772104848, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 1393, + "versionNonce": 2118071597, + "isDeleted": false, + "id": "9Wp2uIUthDigdWp8INFhj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6357.811938993152, + "y": 1077.2516868183918, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 609.315590833362, + "height": 783.5586668509086, + "seed": 1784320144, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "nhnXz7aHpuv2QL1Bp9b3k" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Ofx5yKSduYv665xdEAj8Y", + "focus": -0.5096817711054381, + "gap": 8.84731909273603 + }, + "endBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": 0.9031989905637419, + "gap": 24.49917371160518 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 228.65442261700719, + -332.10804930115273 + ], + [ + 609.315590833362, + -783.5586668509086 + ] + ] + }, + { + "type": "text", + "version": 89, + "versionNonce": 1885061155, + "isDeleted": false, + "id": "nhnXz7aHpuv2QL1Bp9b3k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6084.084366623871, + "y": 1187.3363897244976, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 107.56666564941406, + "height": 25, + "seed": 2144992912, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Staking Tx", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9Wp2uIUthDigdWp8INFhj", + "originalText": "Staking Tx", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 89, + "versionNonce": 609879949, + "isDeleted": false, + "id": "Giojol2wXs96hmIzH19d5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7349.227919226818, + "y": 1839.0264914636102, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 141.9091269984483, + "height": 206.95081020607017, + "seed": 226287679, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "upmIldamSUC3mOwQOMLeC" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Z2GPJMdSMH7oDinaJuQrP", + "focus": 0.35975658828674184, + "gap": 20.069135784796117 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": -0.5531108222723243, + "gap": 10.180035852647848 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -141.9091269984483, + -97.56252481143315 + ], + [ + 0, + -206.95081020607017 + ] + ] + }, + { + "type": "text", + "version": 54, + "versionNonce": 1809171395, + "isDeleted": false, + "id": "upmIldamSUC3mOwQOMLeC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7292.8112512885855, + "y": 1718.051086360575, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 112.83333587646484, + "height": 35, + "seed": 376267697, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Register", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Giojol2wXs96hmIzH19d5", + "originalText": "Register", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 71, + "versionNonce": 942378477, + "isDeleted": false, + "id": "JA8TyLpYrRi8MLw2LeRyM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7352.184359372619, + "y": 1839.0264914636102, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 198.08148976866732, + "seed": 2145670015, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "NnsXI67fP2pBCEULJQEHA" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Z2GPJMdSMH7oDinaJuQrP", + "focus": -0.05540579963332548, + "gap": 20.069135784796117 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.0285316755990152, + "gap": 19.049356290050696 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -198.08148976866732 + ] + ] + }, + { + "type": "text", + "version": 56, + "versionNonce": 1552831331, + "isDeleted": false, + "id": "NnsXI67fP2pBCEULJQEHA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7312.817694486144, + "y": 1722.4857465792766, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 78.73332977294922, + "height": 35, + "seed": 1793711281, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "EOTS", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "JA8TyLpYrRi8MLw2LeRyM", + "originalText": "EOTS", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 129, + "versionNonce": 951928909, + "isDeleted": false, + "id": "Py60shrNSM5iaxY-OKjWA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7361.053679810022, + "y": 1839.0264914636102, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 144.86556714424933, + "height": 209.9072503518712, + "seed": 1231758065, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "MnF3U1mH0nAv9_UwrXYKH" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Z2GPJMdSMH7oDinaJuQrP", + "focus": -0.4595977125459293, + "gap": 20.069135784796117 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.5394991804414715, + "gap": 7.223595706846822 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 144.86556714424933, + -85.73676422822905 + ], + [ + 0, + -209.9072503518712 + ] + ] + }, + { + "type": "text", + "version": 50, + "versionNonce": 852750083, + "isDeleted": false, + "id": "MnF3U1mH0nAv9_UwrXYKH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7475.369247717211, + "y": 1735.7897272353812, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 61.099998474121094, + "height": 35, + "seed": 603054673, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Vote", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Py60shrNSM5iaxY-OKjWA", + "originalText": "Vote", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 572, + "versionNonce": 132691629, + "isDeleted": false, + "id": "7AAf0l0osVIkrHWsFIYH3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8088.58538943969, + "y": 940.9401521194957, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 709.0507674670008, + "height": 676.0071462436719, + "seed": 300997585, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "BC0vFMLOBUKZMe0II0jva", + "type": "arrow" + }, + { + "id": "AiSrsmg9fiSgRXLkdnfge", + "type": "arrow" + }, + { + "id": "dLxA2jEbQoL0B_g3dEmVM", + "type": "arrow" + }, + { + "id": "TV7k2DcOhFbsd7McAHI4P", + "type": "arrow" + }, + { + "id": "J9v9y3Ffg_2wkgPNTj4bZ", + "type": "arrow" + }, + { + "id": "UIkSG6S2UtMt2LBbzOoZ-", + "type": "arrow" + }, + { + "id": "yiNt60KG3azTY3duMRMfA", + "type": "arrow" + }, + { + "id": "kA6i6mSq8vH4WFRsFxf24", + "type": "arrow" + }, + { + "id": "FdZGZ0PzL8Xkr4hEgdBVK", + "type": "arrow" + }, + { + "id": "6TzS7ktzcWBuV0JdftQZ6", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 477, + "versionNonce": 1361385123, + "isDeleted": false, + "id": "nwyH63OrI1Fz9XZ4fZbU4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8237.475162897306, + "y": 980.1384445240622, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 398.3666687011719, + "height": 67.76958097087783, + "seed": 1474771377, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 54.21566477670226, + "fontFamily": 1, + "text": "Consumer Chain", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Consumer Chain", + "lineHeight": 1.25, + "baseline": 48 + }, + { + "type": "line", + "version": 1725, + "versionNonce": 274159885, + "isDeleted": false, + "id": "cdVtPe6bhwbE4xeB3JxhT", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8350.209248493247, + "y": 1979.720900065708, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 790027153, + "groupIds": [ + "olJS_vJcDIcz6B9rC7Zja", + "XOrc6_SIRthg-5yHqE9rX" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1404, + "versionNonce": 1177569859, + "isDeleted": false, + "id": "udXIZ2TbZfA-27huUiDCG", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8356.886854271415, + "y": 1943.9620157878712, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 666321265, + "groupIds": [ + "olJS_vJcDIcz6B9rC7Zja", + "XOrc6_SIRthg-5yHqE9rX" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1702, + "versionNonce": 729984877, + "isDeleted": false, + "id": "Zf0Dnk4DhRIMaJcxv8xMh", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8422.062144524323, + "y": 1976.2993335880387, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 1594878801, + "groupIds": [ + "UHwt3EfekmoX7sR5x-Vab", + "XO3DPqNmrMDh35ID9Y8U_" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1381, + "versionNonce": 1188301283, + "isDeleted": false, + "id": "xXiewZJ4Oj8fGFs-Du3Ft", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8428.739750302491, + "y": 1940.540449310202, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1568168241, + "groupIds": [ + "UHwt3EfekmoX7sR5x-Vab", + "XO3DPqNmrMDh35ID9Y8U_" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1707, + "versionNonce": 1202993613, + "isDeleted": false, + "id": "zcMc61rmVYIFwrQAqWgYC", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8490.493474077726, + "y": 1978.0101168268725, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 26.41763133716407, + "height": 23.524142033662113, + "seed": 878185233, + "groupIds": [ + "_dj15OYIXruJL2SEfg8Ct", + "FCIxn0rNF-LloFcJmEutw" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.958774709762374, + -15.5882868897761 + ], + [ + 12.680463041838754, + -23.524142033662113 + ], + [ + 22.40215137391513, + -17.28882727775165 + ], + [ + 26.41763133716407, + -1.6186460582798874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1386, + "versionNonce": 1746851203, + "isDeleted": false, + "id": "OuRr0ekwr6xcLmQrTqKD2", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 8497.171079855896, + "y": 1942.2512325490356, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 13.525827244628017, + "height": 11.835098839049556, + "seed": 1546027249, + "groupIds": [ + "_dj15OYIXruJL2SEfg8Ct", + "FCIxn0rNF-LloFcJmEutw" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 674, + "versionNonce": 1308981293, + "isDeleted": false, + "id": "cb13-7-UUFl8kPeinKC98", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8208.372136290036, + "y": 1854.1472802066824, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 525.8572228747772, + "height": 155.681274733995, + "seed": 1382759121, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "BC0vFMLOBUKZMe0II0jva", + "type": "arrow" + }, + { + "id": "AiSrsmg9fiSgRXLkdnfge", + "type": "arrow" + }, + { + "id": "dLxA2jEbQoL0B_g3dEmVM", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 344, + "versionNonce": 424619299, + "isDeleted": false, + "id": "9ZX4NT61ejU3WaC0qd2SS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8234.932217404412, + "y": 1862.9658958338682, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 451.8999938964844, + "height": 45, + "seed": 2008922289, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Consumer BTC Validators", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Consumer BTC Validators", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 499, + "versionNonce": 1214245517, + "isDeleted": false, + "id": "BC0vFMLOBUKZMe0II0jva", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8461.862716463207, + "y": 1834.078144421886, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 738.4127815225593, + "height": 206.95081020607017, + "seed": 767916689, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "G9WuYHob5xRbJC3APFZYF" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "cb13-7-UUFl8kPeinKC98", + "gap": 20.069135784796345, + "focus": 0.8142024780010457 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.2515879784907131, + "gap": 6.624988594227943 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -516.6797705874833, + -79.82388393662723 + ], + [ + -738.4127815225593, + -206.95081020607017 + ] + ] + }, + { + "type": "text", + "version": 116, + "versionNonce": 1574146243, + "isDeleted": false, + "id": "G9WuYHob5xRbJC3APFZYF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8231.713334850407, + "y": 1719.015619610453, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 112.83333587646484, + "height": 35, + "seed": 892993649, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Register", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BC0vFMLOBUKZMe0II0jva", + "originalText": "Register", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 370, + "versionNonce": 1199378669, + "isDeleted": false, + "id": "AiSrsmg9fiSgRXLkdnfge", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8453.029023881314, + "y": 1834.078144421886, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 7.2746557395512355, + "height": 198.08148976866732, + "seed": 1273154129, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "W2VLqI3SMCRUpK182Gf_p" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "cb13-7-UUFl8kPeinKC98", + "gap": 20.069135784796345, + "focus": -0.05540579963333314 + }, + "endBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "gap": 19.049356290050923, + "focus": 0.028531675599015205 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -7.2746557395512355, + -198.08148976866732 + ] + ] + }, + { + "type": "text", + "version": 117, + "versionNonce": 1196064867, + "isDeleted": false, + "id": "W2VLqI3SMCRUpK182Gf_p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8393.628905046415, + "y": 1717.5373995375526, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 78.73332977294922, + "height": 35, + "seed": 177690673, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "EOTS", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AiSrsmg9fiSgRXLkdnfge", + "originalText": "EOTS", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 443, + "versionNonce": 149318477, + "isDeleted": false, + "id": "dLxA2jEbQoL0B_g3dEmVM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8449.738770680353, + "y": 1834.078144421886, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 141.9091269984483, + "height": 212.86369049767222, + "seed": 54471185, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "O_dY1q5qXRXf8iWEHc-PU" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "cb13-7-UUFl8kPeinKC98", + "focus": -0.45959771254593923, + "gap": 20.069135784796345 + }, + "endBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "focus": 0.5197357457499742, + "gap": 4.267155561046025 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 136.99168683418884, + -85.73676422822905 + ], + [ + -4.91744016425946, + -212.86369049767222 + ] + ] + }, + { + "type": "text", + "version": 112, + "versionNonce": 797080579, + "isDeleted": false, + "id": "O_dY1q5qXRXf8iWEHc-PU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8556.180458277482, + "y": 1730.8413801936572, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 61.099998474121094, + "height": 35, + "seed": 517062641, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Vote", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dLxA2jEbQoL0B_g3dEmVM", + "originalText": "Vote", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 66, + "versionNonce": 1559591341, + "isDeleted": false, + "id": "TV7k2DcOhFbsd7McAHI4P", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8079.468635239666, + "y": 1040.7876520973393, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 337.03417662131415, + "height": 0, + "seed": 1067290783, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "bqvlj9zX8ymsU2YaGX_ga" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "focus": 0.7045963181523743, + "gap": 9.116754200023934 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": -0.7192362433934629, + "gap": 25.609512271931635 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -337.03417662131415, + 0 + ] + ] + }, + { + "type": "text", + "version": 54, + "versionNonce": 846857123, + "isDeleted": false, + "id": "bqvlj9zX8ymsU2YaGX_ga", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7854.534878990777, + "y": 1023.2876520973393, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 112.83333587646484, + "height": 35, + "seed": 840853553, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Register", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TV7k2DcOhFbsd7McAHI4P", + "originalText": "Register", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 112, + "versionNonce": 1288944653, + "isDeleted": false, + "id": "J9v9y3Ffg_2wkgPNTj4bZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7727.652257889346, + "y": 1170.8710185125835, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 407.98874012053875, + "height": 0, + "seed": 348964529, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "w2mNuWfGcUnCCs8jhNv1x" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": -0.33437828105365447, + "gap": 10.827311542926509 + }, + "endBinding": { + "elementId": "8h8jUw2Xae2g79QIr6Ynx", + "focus": 0.6144578313253011, + "gap": 26.607961312209227 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 407.98874012053875, + 0 + ] + ] + }, + { + "type": "text", + "version": 88, + "versionNonce": 1751095107, + "isDeleted": false, + "id": "w2mNuWfGcUnCCs8jhNv1x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7814.015560842192, + "y": 1153.3710185125835, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 176.13333129882812, + "height": 35, + "seed": 262956607, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Timestamping", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "J9v9y3Ffg_2wkgPNTj4bZ", + "originalText": "Timestamping", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 111, + "versionNonce": 8041069, + "isDeleted": false, + "id": "UIkSG6S2UtMt2LBbzOoZ-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7724.695817743545, + "y": 1292.0850644904247, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 416.8580605579418, + "height": 5.91288029160205, + "seed": 727475839, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "MD5nN7_r_vjq-nEM-CaEg" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.03886912265705265, + "gap": 7.870871397125484 + }, + "endBinding": { + "elementId": "8h8jUw2Xae2g79QIr6Ynx", + "focus": 0.15963349024882342, + "gap": 20.695081020607176 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 416.8580605579418, + -5.91288029160205 + ] + ] + }, + { + "type": "text", + "version": 63, + "versionNonce": 1603385059, + "isDeleted": false, + "id": "MD5nN7_r_vjq-nEM-CaEg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7778.372002513872, + "y": 1274.5850644904247, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 253.3333282470703, + "height": 35, + "seed": 18241425, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Validator Updates", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UIkSG6S2UtMt2LBbzOoZ-", + "originalText": "Validator Updates", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 89, + "versionNonce": 1689509069, + "isDeleted": false, + "id": "yiNt60KG3azTY3duMRMfA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7733.565138180948, + "y": 1404.429790030863, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 405.0322999747377, + "height": 0, + "seed": 2020080369, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "ECoxw8OQHmONjgMggZSdS" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.3566166967837289, + "gap": 16.74019183452856 + }, + "endBinding": { + "elementId": "8h8jUw2Xae2g79QIr6Ynx", + "focus": -0.3373493975903621, + "gap": 23.6515211664082 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 405.0322999747377, + 0 + ] + ] + }, + { + "type": "text", + "version": 57, + "versionNonce": 1453253251, + "isDeleted": false, + "id": "ECoxw8OQHmONjgMggZSdS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7828.878438082036, + "y": 1386.929790030863, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 158.23333740234375, + "height": 35, + "seed": 453654623, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegations", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "yiNt60KG3azTY3duMRMfA", + "originalText": "Delegations", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 601, + "versionNonce": 286833453, + "isDeleted": false, + "id": "op4Vo_dXXuuyI3LWXuHY7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7014.687566213829, + "y": 544.8635834255102, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 692.7322271923852, + "height": 229.08657972699848, + "seed": 398641169, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "2tbxDclyApG1yvPHvk2aZ", + "type": "arrow" + }, + { + "id": "NwIiDKmNYbPb8ZDJEKuaO", + "type": "arrow" + }, + { + "id": "p8LtEmaFkk4DaHFUquc3E", + "type": "arrow" + }, + { + "id": "IAu1_oifIihVnusXFf1rq", + "type": "arrow" + }, + { + "id": "41lDBkHDzYp0h0NaioZ7H", + "type": "arrow" + }, + { + "id": "HR75PFP27DrbOlqk-Yx1H", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 508, + "versionNonce": 910531107, + "isDeleted": false, + "id": "KJXyNQSnKtsj6cwHFNgLk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7282.519907868243, + "y": 642.2344956331922, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 127.93333435058594, + "height": 45, + "seed": 1563532785, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Monitor", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Monitor", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 140, + "versionNonce": 1846726029, + "isDeleted": false, + "id": "8h8jUw2Xae2g79QIr6Ynx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8162.248959322094, + "y": 1076.2649338469514, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 570.5929481395942, + "height": 490.76906420296655, + "seed": 1298913361, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "J9v9y3Ffg_2wkgPNTj4bZ", + "type": "arrow" + }, + { + "id": "UIkSG6S2UtMt2LBbzOoZ-", + "type": "arrow" + }, + { + "id": "yiNt60KG3azTY3duMRMfA", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 125, + "versionNonce": 1265917379, + "isDeleted": false, + "id": "Nxnq5HES_Hq-nOd_N-Np7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8230.247082675516, + "y": 1281.1721841988226, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 430.20001220703125, + "height": 45, + "seed": 1844676977, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Babylon Smart Contract", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon Smart Contract", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 197, + "versionNonce": 450597869, + "isDeleted": false, + "id": "2tbxDclyApG1yvPHvk2aZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7047.671024355115, + "y": 393.3272601669196, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5.91288029160205, + "height": 135.99624670684625, + "seed": 1026158673, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Z7iJryo9X1kyGxnperih-" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": 0.9396487515705894, + "gap": 15.450850609733209 + }, + "endBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": -0.859021466502033, + "gap": 15.540076551744392 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 5.91288029160205, + 135.99624670684625 + ] + ] + }, + { + "type": "text", + "version": 107, + "versionNonce": 1095347555, + "isDeleted": false, + "id": "Z7iJryo9X1kyGxnperih-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6935.127464500916, + "y": 443.8253835203427, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 231, + "height": 35, + "seed": 905388031, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BBN Delegations", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2tbxDclyApG1yvPHvk2aZ", + "originalText": "BBN Delegations", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 176, + "versionNonce": 326929997, + "isDeleted": false, + "id": "NwIiDKmNYbPb8ZDJEKuaO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7310.794197331405, + "y": 399.24014045852164, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 115.30116568623907, + "seed": 1257324351, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "LGGjoxdozaSpmTTGkBIm5" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": 0.6502367572169409, + "gap": 21.36373090133526 + }, + "endBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": -0.14510507958411872, + "gap": 30.322277280749518 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + 115.30116568623907 + ] + ] + }, + { + "type": "text", + "version": 143, + "versionNonce": 1528039683, + "isDeleted": false, + "id": "LGGjoxdozaSpmTTGkBIm5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7195.894195805526, + "y": 421.8907233016412, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 229.8000030517578, + "height": 70, + "seed": 8036031, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BBN Delegation \nSlashings", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "NwIiDKmNYbPb8ZDJEKuaO", + "originalText": "BBN Delegation Slashings", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 107, + "versionNonce": 382680237, + "isDeleted": false, + "id": "p8LtEmaFkk4DaHFUquc3E", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7059.496784938319, + "y": 780.6209192668512, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 156.6913277274532, + "seed": 426436785, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "gupdfS_pUDrd25gse7Vr3" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": 0.870630477504707, + "gap": 6.670756114342453 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": -0.8541074675268376, + "gap": 8.576252166915879 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + 156.6913277274532 + ] + ] + }, + { + "type": "text", + "version": 110, + "versionNonce": 1850863779, + "isDeleted": false, + "id": "gupdfS_pUDrd25gse7Vr3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 6911.930119288905, + "y": 823.9665831305778, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 295.1333312988281, + "height": 70, + "seed": 820496287, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegation Censorship\nAlarm + Submission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "p8LtEmaFkk4DaHFUquc3E", + "originalText": "Delegation Censorship Alarm + Submission", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 78, + "versionNonce": 1283268365, + "isDeleted": false, + "id": "IAu1_oifIihVnusXFf1rq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7369.923000247425, + "y": 795.4031199958561, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 141.9091269984483, + "seed": 1732152223, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "66mim4O2W71gOBFm2TRgB" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": -0.025606778750138594, + "gap": 21.45295684334735 + }, + "endBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.021503220881459183, + "gap": 8.576252166915879 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + 141.9091269984483 + ] + ] + }, + { + "type": "text", + "version": 77, + "versionNonce": 481420355, + "isDeleted": false, + "id": "66mim4O2W71gOBFm2TRgB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7239.739665896839, + "y": 848.8576834950802, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 260.3666687011719, + "height": 35, + "seed": 1751089041, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegation Slashed", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "IAu1_oifIihVnusXFf1rq", + "originalText": "Delegation Slashed", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 651, + "versionNonce": 721401197, + "isDeleted": false, + "id": "YmCLaNHb2U4lTQau2fVIa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8084.918898993796, + "y": 541.9071432797097, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 716.3837483587934, + "height": 229.08657972699848, + "seed": 412685503, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "d7CFBP2RyYRBwJ9VuOR81", + "type": "arrow" + }, + { + "id": "kA6i6mSq8vH4WFRsFxf24", + "type": "arrow" + }, + { + "id": "PuvSeOD6rWEj-1r-IYOSX", + "type": "arrow" + }, + { + "id": "FdZGZ0PzL8Xkr4hEgdBVK", + "type": "arrow" + }, + { + "id": "6TzS7ktzcWBuV0JdftQZ6", + "type": "arrow" + }, + { + "id": "_s7MHtQZYSEdXqgtUKkZg", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 137, + "versionNonce": 1309828067, + "isDeleted": false, + "id": "5CJsonXtdPq7fglvCZSzD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8378.069089965567, + "y": 642.5811127058057, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 127.93333435058594, + "height": 45, + "seed": 1182783185, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Monitor", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Monitor", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 87, + "versionNonce": 497057741, + "isDeleted": false, + "id": "d7CFBP2RyYRBwJ9VuOR81", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8142.153113829583, + "y": 396.2837003127205, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.4654610321886139, + "height": 124.17048612364215, + "seed": 905994143, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "wecg1dr72sJv2S98tJLJq" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "gap": 18.407290755534063, + "focus": -0.2601604656835446 + }, + "endBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "gap": 21.45295684334701, + "focus": -0.8364881058378715 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.4654610321886139, + 124.17048612364215 + ] + ] + }, + { + "type": "text", + "version": 69, + "versionNonce": 1786900355, + "isDeleted": false, + "id": "wecg1dr72sJv2S98tJLJq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8035.270545476778, + "y": 440.8689433745416, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 212.56666564941406, + "height": 35, + "seed": 500132401, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "CZ Delegations", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d7CFBP2RyYRBwJ9VuOR81", + "originalText": "CZ Delegations", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 88, + "versionNonce": 1786314285, + "isDeleted": false, + "id": "41lDBkHDzYp0h0NaioZ7H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7647.82837395272, + "y": 934.3558068485036, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 138.95268685264728, + "seed": 73728447, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "KACAVfr7omqA9QMVFA92_" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5f7uu4kjIEUFzrjUpX9s9", + "focus": 0.8053832657422193, + "gap": 11.532692312716676 + }, + "endBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": -0.8279525129211429, + "gap": 21.45295684334758 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -138.95268685264728 + ] + ] + }, + { + "type": "text", + "version": 70, + "versionNonce": 1413416739, + "isDeleted": false, + "id": "KACAVfr7omqA9QMVFA92_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7554.011708303306, + "y": 847.3794634221799, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 187.63333129882812, + "height": 35, + "seed": 1468914641, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Double Signing", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "41lDBkHDzYp0h0NaioZ7H", + "originalText": "Double Signing", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 77, + "versionNonce": 1483911309, + "isDeleted": false, + "id": "HR75PFP27DrbOlqk-Yx1H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7570.960930161893, + "y": 529.3235068737656, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0, + "height": 133.03980656104505, + "seed": 104998385, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "6EBYbdqdsvxFlygFaIrYS" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "op4Vo_dXXuuyI3LWXuHY7", + "focus": 0.6060270970866081, + "gap": 15.540076551744619 + }, + "endBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": 0.3651301536039766, + "gap": 18.407290755534177 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -133.03980656104505 + ] + ] + }, + { + "type": "text", + "version": 76, + "versionNonce": 565717699, + "isDeleted": false, + "id": "6EBYbdqdsvxFlygFaIrYS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 7428.927589707791, + "y": 445.3036035932431, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 284.0666809082031, + "height": 35, + "seed": 361256415, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Slashing Transaction", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "HR75PFP27DrbOlqk-Yx1H", + "originalText": "Slashing Transaction", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 79, + "versionNonce": 356980461, + "isDeleted": false, + "id": "kA6i6mSq8vH4WFRsFxf24", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8166.566947436617, + "y": 792.4466798500551, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.7812910939501307, + "height": 127.12692626944306, + "seed": 527872607, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "kqKNzuVAYSeDj0SbJn-k_" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "gap": 21.452956843346897, + "focus": 0.7682033625041633 + }, + "endBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "gap": 21.366545999997584, + "focus": -0.7838800448607602 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.7812910939501307, + 127.12692626944306 + ] + ] + }, + { + "type": "text", + "version": 87, + "versionNonce": 395041379, + "isDeleted": false, + "id": "kqKNzuVAYSeDj0SbJn-k_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8017.638733818481, + "y": 821.0101429847766, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 295.1333312988281, + "height": 70, + "seed": 999466257, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegation Censorship\nAlarm + Submission", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "kA6i6mSq8vH4WFRsFxf24", + "originalText": "Delegation Censorship Alarm + Submission", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 122, + "versionNonce": 1535095117, + "isDeleted": false, + "id": "PuvSeOD6rWEj-1r-IYOSX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8447.019308617064, + "y": 396.2837003127206, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.7477778498869156, + "height": 133.03980656104522, + "seed": 678169745, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "zv8udvWwe2ibUCsZwNt_K" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": -0.5903684338962878, + "gap": 18.407290755534234 + }, + "endBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "focus": 0.025743367901733382, + "gap": 12.583636405943821 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.7477778498869156, + 133.03980656104522 + ] + ] + }, + { + "type": "text", + "version": 96, + "versionNonce": 1653118467, + "isDeleted": false, + "id": "zv8udvWwe2ibUCsZwNt_K", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8344.427438822604, + "y": 427.8036035932432, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 197.36666870117188, + "height": 70, + "seed": 1890635135, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "CZ Delegation\nSlashings", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PuvSeOD6rWEj-1r-IYOSX", + "originalText": "CZ Delegation\nSlashings", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 119, + "versionNonce": 1484128173, + "isDeleted": false, + "id": "FdZGZ0PzL8Xkr4hEgdBVK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8460.702959526669, + "y": 777.6644791210501, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.851548081915098, + "height": 153.73488758165217, + "seed": 41207537, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "v7NamQiGRSsEzbIvAK6i3" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "focus": -0.05506415488509982, + "gap": 6.670756114341998 + }, + "endBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "focus": 0.022988742610528204, + "gap": 9.540785416793483 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.851548081915098, + 153.73488758165217 + ] + ] + }, + { + "type": "text", + "version": 89, + "versionNonce": 296541603, + "isDeleted": false, + "id": "v7NamQiGRSsEzbIvAK6i3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8380.463423383542, + "y": 819.5319229118762, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 143.03334045410156, + "height": 70, + "seed": 315946879, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Delegation\nSlashed", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "FdZGZ0PzL8Xkr4hEgdBVK", + "originalText": "Delegation\nSlashed", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 110, + "versionNonce": 1381204493, + "isDeleted": false, + "id": "6TzS7ktzcWBuV0JdftQZ6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8706.157512064185, + "y": 925.4864864111003, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.391290840734655, + "height": 138.95268685264716, + "seed": 420177631, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "RTHhfCqiV8xCLXbpUHzHE" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "7AAf0l0osVIkrHWsFIYH3", + "focus": 0.7013174102681039, + "gap": 15.453665708395533 + }, + "endBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "focus": -0.7468760262840102, + "gap": 15.54007655174496 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.391290840734655, + -138.95268685264716 + ] + ] + }, + { + "type": "text", + "version": 75, + "versionNonce": 1344745795, + "isDeleted": false, + "id": "RTHhfCqiV8xCLXbpUHzHE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8649.34151991686, + "y": 821.0101429847766, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 90.13333129882812, + "height": 70, + "seed": 1915108593, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Double\nSigning", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "6TzS7ktzcWBuV0JdftQZ6", + "originalText": "Double\nSigning", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 113, + "versionNonce": 236042349, + "isDeleted": false, + "id": "_s7MHtQZYSEdXqgtUKkZg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8702.051447998578, + "y": 532.2799470195666, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 5.267820324528657, + "height": 135.99624670684602, + "seed": 1904669841, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "DDpUDxUaQ4qyIK36KonaP" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "YmCLaNHb2U4lTQau2fVIa", + "focus": 0.7273291224032434, + "gap": 9.627196260143023 + }, + "endBinding": { + "elementId": "r4v0FbTF5HqPDSOjESQ79", + "focus": -0.8587960334431806, + "gap": 18.407290755534234 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -5.267820324528657, + -135.99624670684602 + ] + ] + }, + { + "type": "text", + "version": 87, + "versionNonce": 1599475939, + "isDeleted": false, + "id": "DDpUDxUaQ4qyIK36KonaP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 8606.128636573501, + "y": 429.2818236661436, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 164.73333740234375, + "height": 70, + "seed": 991005695, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Slashing\nTransaction", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_s7MHtQZYSEdXqgtUKkZg", + "originalText": "Slashing\nTransaction", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 338, + "versionNonce": 1970938573, + "isDeleted": false, + "id": "cm5FyddK0vz7subeXAVIp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 9028.48592204179, + "y": 642.5811127058059, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 668.566650390625, + "height": 540, + "seed": 2146321215, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Design Choices\n1. Monitor per CZ running:\n a. slasher routine\n b. monitor for liveness\n c. monitor for slashed delegations\n2. Both finality votes and EOTS are\n submitted to the PoS chain.\n\n\nDoes not include rewards.\n\n ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Design Choices\n1. Monitor per CZ running:\n a. slasher routine\n b. monitor for liveness\n c. monitor for slashed delegations\n2. Both finality votes and EOTS are\n submitted to the PoS chain.\n\n\nDoes not include rewards.\n\n ", + "lineHeight": 1.25, + "baseline": 527 + }, + { + "type": "rectangle", + "version": 516, + "versionNonce": 1355283587, + "isDeleted": false, + "id": "Sm8uhB6wbwpfqUcz7JMxV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10604.268519753767, + "y": 1830.157171026204, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 366.5985780793235, + "height": 200.34111593401462, + "seed": 267247302, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 546, + "versionNonce": 2099659053, + "isDeleted": false, + "id": "90pwTHY-XyKJ9o-VPGi4H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10715.140484298008, + "y": 1831.945562176414, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 140.53334045410156, + "height": 45, + "seed": 1571678534, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Epoching", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Epoching", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 430, + "versionNonce": 3896355, + "isDeleted": false, + "id": "s8RKBb4aWHmHLWMzJ8QCY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10995.996839072399, + "y": 1827.2007308804025, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 413.9016204121399, + "height": 200.34111593401505, + "seed": 2097260954, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 393, + "versionNonce": 1249874829, + "isDeleted": false, + "id": "fM9WsqwhZEesCWmOe_Z29", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11095.758832962998, + "y": 1831.8761888612244, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 221.73333740234375, + "height": 45, + "seed": 1555399898, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Checkpointing", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Checkpointing", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 550, + "versionNonce": 1302263747, + "isDeleted": false, + "id": "bnKi6MvGeOa6rXfF9wqLu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11430.593540505144, + "y": 1830.1571710262035, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 410.94518026633705, + "height": 195.9346530859773, + "seed": 2111804358, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "anB8X_sbi4sqniV1s2nox", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 567, + "versionNonce": 431987181, + "isDeleted": false, + "id": "U1sOemPDPOre1B9lDBySD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11491.592796450943, + "y": 1836.7373588952162, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 275.29998779296875, + "height": 45, + "seed": 1545921670, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC Checkpoint", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Checkpoint", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 564, + "versionNonce": 386991971, + "isDeleted": false, + "id": "MSM2z9sn1FcicEuDUqxc7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11865.190241937895, + "y": 1827.8975448608571, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 354.7728174961211, + "height": 195.93465308597774, + "seed": 940223002, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 724, + "versionNonce": 1676661837, + "isDeleted": false, + "id": "wUowYeaLX6Lc7qVoXSxkI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11894.04759369749, + "y": 1833.8373134907445, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 301.4666748046875, + "height": 45.383541253697636, + "seed": 1264550810, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36.30683300295811, + "fontFamily": 1, + "text": "BTC Light Client", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Light Client", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 564, + "versionNonce": 1927291651, + "isDeleted": false, + "id": "ozU2lapefevVSfaW8NkgQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12246.571020746223, + "y": 1827.1443361390739, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 473.0304233281604, + "height": 193.7314216619587, + "seed": 1829384006, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1051, + "versionNonce": 1156170413, + "isDeleted": false, + "id": "y4rme2MnApWx44nmHyZ73", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12342.11288161698, + "y": 1826.6037278087472, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 261.9333190917969, + "height": 45, + "seed": 1730406746, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "anB8X_sbi4sqniV1s2nox", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Zone Concierge", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Zone Concierge", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 423, + "versionNonce": 1616690659, + "isDeleted": false, + "id": "BnWROnQbk7tSdMVPxlq_t", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12778.680615824873, + "y": 1818.1323281609975, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 369.55501822512633, + "height": 198.08148976866732, + "seed": 729734682, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 246, + "versionNonce": 1866515917, + "isDeleted": false, + "id": "1SlzFWp6uSd7a3FudUT2_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12854.306548605986, + "y": 1826.497625763036, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 222.8000030517578, + "height": 45, + "seed": 16703258, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC Staking", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Staking", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 443, + "versionNonce": 913377667, + "isDeleted": false, + "id": "zKbCKEPGeNiS66zSHK-7X", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13196.257990599379, + "y": 1817.8469834376199, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 369.55501822512633, + "height": 198.08148976866732, + "seed": 1719904602, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 275, + "versionNonce": 1814070317, + "isDeleted": false, + "id": "FpSCSFCEsIa3mIplF9fw6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13315.260684867038, + "y": 1823.1476418607629, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 123.96666717529297, + "height": 45, + "seed": 772233670, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Finality", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Finality", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 504, + "versionNonce": 1195041059, + "isDeleted": false, + "id": "lnzn99DGfieaS4acsCNvd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13620.121937783335, + "y": 1818.7969640376396, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 369.55501822512633, + "height": 198.08148976866732, + "seed": 41929158, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 212, + "versionNonce": 121206413, + "isDeleted": false, + "id": "l2EsbdDM-dCi_MiBbLtc3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13715.048095089276, + "y": 1824.8508311825635, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 157.76666259765625, + "height": 45, + "seed": 33119174, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Incentive", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Incentive", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 417, + "versionNonce": 1009304771, + "isDeleted": false, + "id": "gMWY7fP5rBveUymQJJJE_", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10602.708019729667, + "y": 2412.167299041643, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 3434.3828279992267, + "height": 227.64589122667712, + "seed": 1232370778, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "YJq6laSN-yx57jGCvqtHl", + "type": "arrow" + }, + { + "id": "i8rUSngD92Yf-8G5kqyGx", + "type": "arrow" + }, + { + "id": "Th72X_O0Jcw9QslRrN6dG", + "type": "arrow" + }, + { + "id": "ubD9PQiYw8EIHbYNeDuZg", + "type": "arrow" + }, + { + "id": "u56fJvEUflV3M4Wf2lfxj", + "type": "arrow" + }, + { + "id": "XfoF5gyizY9lr0o_lqvKd", + "type": "arrow" + }, + { + "id": "4C_kRR9PuZj7jJ5ntwVfz", + "type": "arrow" + }, + { + "id": "ByxqHImFIZUnMJs-EiM9L", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 214, + "versionNonce": 189078765, + "isDeleted": false, + "id": "MEVGXPLS6yVNDWw7VK_Lb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12372.182313369209, + "y": 2492.7559572209757, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 181.73333740234375, + "height": 45, + "seed": 347616794, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "CometBFT", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CometBFT", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 174, + "versionNonce": 215561315, + "isDeleted": false, + "id": "lxEY1ksynkZ0IDgBeWJc5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12348.568205776357, + "y": 1581.9021913139006, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 232.89999389648438, + "height": 45, + "seed": 772791238, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Babylon Node", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon Node", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 1426, + "versionNonce": 801961805, + "isDeleted": false, + "id": "YJq6laSN-yx57jGCvqtHl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10807.583263430915, + "y": 2398.590060457221, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.4923277655107086, + "height": 306.1434258317727, + "seed": 434382022, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "NHtt-cTZNhJN_NH4m_mjE" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": -0.8800456644337932, + "gap": 13.577238584422048 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": 0.8790265931258232, + "gap": 9.37746860153186 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.4923277655107086, + -306.1434258317727 + ] + ] + }, + { + "type": "text", + "version": 81, + "versionNonce": 958050307, + "isDeleted": false, + "id": "NHtt-cTZNhJN_NH4m_mjE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10664.325332689881, + "y": 2251.0360013445916, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 164.63333129882812, + "height": 105, + "seed": 155073754, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Staking and\nundelegating\nrequests", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "YJq6laSN-yx57jGCvqtHl", + "originalText": "Staking and\nundelegating\nrequests", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "arrow", + "version": 1390, + "versionNonce": 2133493165, + "isDeleted": false, + "id": "i8rUSngD92Yf-8G5kqyGx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11213.221570340298, + "y": 2397.6301588702913, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.6194059013741935, + "height": 297.2177106530394, + "seed": 1435581446, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "qNSCgzBcfz6bo5w7RWl8p" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": -0.6446441799007094, + "gap": 14.537140171351894 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": 0.6394634793652543, + "gap": 17.343282193335313 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.6194059013741935, + -297.2177106530394 + ] + ] + }, + { + "type": "text", + "version": 56, + "versionNonce": 1122744227, + "isDeleted": false, + "id": "qNSCgzBcfz6bo5w7RWl8p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11073.563291535085, + "y": 2283.135955940119, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 211.36666870117188, + "height": 35, + "seed": 481772166, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BLS Signatures", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "i8rUSngD92Yf-8G5kqyGx", + "originalText": "BLS Signatures", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 1374, + "versionNonce": 2089240589, + "isDeleted": false, + "id": "Th72X_O0Jcw9QslRrN6dG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11637.041572624663, + "y": 2392.9222407232724, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.3639037471111806, + "height": 295.76768795080443, + "seed": 689566106, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "JsaRAOieRYHl47aT4c6QQ" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": -0.39789544538102267, + "gap": 19.245058318370866 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": 0.39322697199292445, + "gap": 14.085386748551286 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.3639037471111806, + -295.76768795080443 + ] + ] + }, + { + "type": "text", + "version": 57, + "versionNonce": 1752877891, + "isDeleted": false, + "id": "JsaRAOieRYHl47aT4c6QQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11489.010037405751, + "y": 2282.3545498476724, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 229.3000030517578, + "height": 35, + "seed": 1951528454, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BTC Checkpoints", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Th72X_O0Jcw9QslRrN6dG", + "originalText": "BTC Checkpoints", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 1378, + "versionNonce": 165461613, + "isDeleted": false, + "id": "ubD9PQiYw8EIHbYNeDuZg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12041.417675743578, + "y": 2390.775404040582, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.6802226912986953, + "height": 292.92403728766067, + "seed": 800448454, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "fAvRjCaIV6ZyczltkXT8Z" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": -0.16196502740821794, + "gap": 21.39189500106113 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": 0.16029890457536092, + "gap": 14.782200729004785 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.6802226912986953, + -292.92403728766067 + ] + ] + }, + { + "type": "text", + "version": 53, + "versionNonce": 505834211, + "isDeleted": false, + "id": "fAvRjCaIV6ZyczltkXT8Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11931.91047245705, + "y": 2281.6295384965542, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 182.76666259765625, + "height": 35, + "seed": 622777882, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BTC Headers", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ubD9PQiYw8EIHbYNeDuZg", + "originalText": "BTC Headers", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 1092, + "versionNonce": 655414893, + "isDeleted": false, + "id": "u56fJvEUflV3M4Wf2lfxj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12489.26862768361, + "y": 2404.354343312355, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.5464064750576654, + "height": 303.83867173444605, + "seed": 914038106, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "tRRUUx5HaZwZi_YntT6KA" + } + ], + "updated": 1704783776106, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": 0.09797406006495779, + "gap": 7.8129557292882055 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": -0.09566311441357997, + "gap": 17.446505553992324 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.3081885034589504, + -153.8657274751763 + ], + [ + -2.238217971598715, + -303.83867173444605 + ] + ] + }, + { + "type": "text", + "version": 52, + "versionNonce": 1216157315, + "isDeleted": false, + "id": "tRRUUx5HaZwZi_YntT6KA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12875.91637864943, + "y": 2279.273867832769, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 162.1999969482422, + "height": 35, + "seed": 1271585222, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "CZ Headers", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "u56fJvEUflV3M4Wf2lfxj", + "originalText": "CZ Headers", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 1426, + "versionNonce": 1516750637, + "isDeleted": false, + "id": "XfoF5gyizY9lr0o_lqvKd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12984.056454420994, + "y": 2398.6383491366378, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 6.927570128478692, + "height": 304.7497978708657, + "seed": 5515078, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "W6axddyxGkRZm0Lv23Kzs" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": 0.3878707941570865, + "gap": 13.528949905005447 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": -0.3803001705554596, + "gap": 10.819385241855457 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -6.927570128478692, + -304.7497978708657 + ] + ] + }, + { + "type": "text", + "version": 146, + "versionNonce": 909987363, + "isDeleted": false, + "id": "W6axddyxGkRZm0Lv23Kzs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13249.808627964663, + "y": 2250.2827926228083, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 220.46665954589844, + "height": 105, + "seed": 1002467354, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "BTC Stake and\nFinality Provider\nregistration", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "XfoF5gyizY9lr0o_lqvKd", + "originalText": "BTC Stake and\nFinality Provider\nregistration", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "arrow", + "version": 1393, + "versionNonce": 1369463181, + "isDeleted": false, + "id": "4C_kRR9PuZj7jJ5ntwVfz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13402.066426973137, + "y": 2393.55128435257, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.6082158574135974, + "height": 285.42871524912425, + "seed": 594403235, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "YBAXEJLMBkbdsj-9oEQKJ" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": 0.6306424420964465, + "gap": 18.61601468907338 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": -0.6272631651623789, + "gap": 25.053403079528948 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.6082158574135974, + -285.42871524912425 + ] + ] + }, + { + "type": "text", + "version": 92, + "versionNonce": 1515557315, + "isDeleted": false, + "id": "YBAXEJLMBkbdsj-9oEQKJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13620.269060954219, + "y": 2269.154768446157, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 280.1000061035156, + "height": 70, + "seed": 1056068877, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Finality Round Votes\nand EOTS commits", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4C_kRR9PuZj7jJ5ntwVfz", + "originalText": "Finality Round Votes\nand EOTS commits", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 1369, + "versionNonce": 1289199597, + "isDeleted": false, + "id": "ByxqHImFIZUnMJs-EiM9L", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13812.275096950993, + "y": 2390.014760399173, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.2359759580067475, + "height": 288.15101085679635, + "seed": 1075003501, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "fLgfxPQD7EMPAdFRAQAei" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "gMWY7fP5rBveUymQJJJE_", + "focus": 0.8693215366473958, + "gap": 22.152538642470063 + }, + "endBinding": { + "elementId": "EK_xusAsckpkIcS-MXJEd", + "focus": -0.8663447593994864, + "gap": 18.794583518460172 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.2359759580067475, + -288.15101085679635 + ] + ] + }, + { + "type": "text", + "version": 57, + "versionNonce": 1172595043, + "isDeleted": false, + "id": "fLgfxPQD7EMPAdFRAQAei", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 14063.484224706346, + "y": 2279.849029426974, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 232.39999389648438, + "height": 35, + "seed": 994674275, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Withdraw Reward", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ByxqHImFIZUnMJs-EiM9L", + "originalText": "Withdraw Reward", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 357, + "versionNonce": 1108810317, + "isDeleted": false, + "id": "a1_3jaimGqTutIIO3YJT5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11220.22142839115, + "y": 742.0761538254686, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 2572.5693492510527, + "height": 187.83839692944207, + "seed": 649066307, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "mfQpANsypEbrVN4CpqxpA", + "type": "arrow" + }, + { + "id": "yqd26byPLQs14kelxJ1-G", + "type": "arrow" + }, + { + "id": "a-2O3cQmmsLXw3Bxai_qN", + "type": "arrow" + }, + { + "id": "IyuMVWi6LT-PazE_jpikJ", + "type": "arrow" + }, + { + "id": "7wvuIjAIlJ-I_bJox-PJW", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 46, + "versionNonce": 1647852803, + "isDeleted": false, + "id": "1EJ_sCjHoobEzX1BnOrBx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12295.528193422015, + "y": 829.1896132710069, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 117.0999984741211, + "height": 45, + "seed": 926608621, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Bitcoin", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Bitcoin", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 496, + "versionNonce": 708331693, + "isDeleted": false, + "id": "fmxh-Xmm15Jabfus_Ij8h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11203.88765474511, + "y": 1150.4204949764294, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 337.5646553514599, + "height": 196.00528375246134, + "seed": 1426440227, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "mfQpANsypEbrVN4CpqxpA", + "type": "arrow" + }, + { + "id": "XB9cK1H8fLx9OrleB1iOc", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1377, + "versionNonce": 1791992995, + "isDeleted": false, + "id": "mfQpANsypEbrVN4CpqxpA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11400.145272730319, + "y": 943.526028793276, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.4503503342602926, + "height": 182.3938057140956, + "seed": 1171976963, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "a1_3jaimGqTutIIO3YJT5", + "focus": 0.8589578232837415, + "gap": 13.611478038365362 + }, + "endBinding": { + "elementId": "fmxh-Xmm15Jabfus_Ij8h", + "focus": 0.147738763176863, + "gap": 24.50066046905772 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.4503503342602926, + 182.3938057140956 + ] + ] + }, + { + "type": "arrow", + "version": 1512, + "versionNonce": 652520205, + "isDeleted": false, + "id": "XB9cK1H8fLx9OrleB1iOc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11390.84266061509, + "y": 1357.3149611595832, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.216259688962964, + "height": 123.69450834866484, + "seed": 631603661, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "fmxh-Xmm15Jabfus_Ij8h", + "focus": -0.10868694285339095, + "gap": 10.889182430692472 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.216259688962964, + 123.69450834866484 + ] + ] + }, + { + "type": "rectangle", + "version": 561, + "versionNonce": 660150339, + "isDeleted": false, + "id": "673a9lNedA3G2dhKONQ6_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11756.652547696174, + "y": 1153.1427905841026, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 348.45383778215194, + "height": 185.9986790618848, + "seed": 1752415309, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "yqd26byPLQs14kelxJ1-G", + "type": "arrow" + }, + { + "id": "PIo2X8_qG0ipchEme9mOU", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 761, + "versionNonce": 1754644845, + "isDeleted": false, + "id": "yqd26byPLQs14kelxJ1-G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11906.783749884315, + "y": 1139.5313125457374, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.3638562037322117, + "height": 190.5606925371153, + "seed": 1426207373, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "673a9lNedA3G2dhKONQ6_", + "focus": -0.126308964431348, + "gap": 13.611478038365135 + }, + "endBinding": { + "elementId": "a1_3jaimGqTutIIO3YJT5", + "focus": 0.4698039393782825, + "gap": 19.05606925371137 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.3638562037322117, + -190.5606925371153 + ] + ] + }, + { + "type": "arrow", + "version": 1034, + "versionNonce": 2075812291, + "isDeleted": false, + "id": "PIo2X8_qG0ipchEme9mOU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11915.562640100825, + "y": 1489.5379580336917, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.36581632484558213, + "height": 143.66011054117143, + "seed": 999483395, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783800188, + "link": null, + "locked": false, + "startBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": -0.23279561597819842, + "gap": 24.91817898414638 + }, + "endBinding": { + "elementId": "673a9lNedA3G2dhKONQ6_", + "focus": 0.08424125039419571, + "gap": 6.736377846532946 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.36581632484558213, + -143.66011054117143 + ] + ] + }, + { + "type": "arrow", + "version": 1114, + "versionNonce": 1419054029, + "isDeleted": false, + "id": "a-2O3cQmmsLXw3Bxai_qN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12451.504013980817, + "y": 938.8605577954424, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.5967360645390727, + "height": 198.6444867575285, + "seed": 214669677, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "a1_3jaimGqTutIIO3YJT5", + "focus": 0.0429912382060799, + "gap": 8.946007040531754 + }, + "endBinding": { + "elementId": "1W8DJ9txG4KCNn-8JrThS", + "focus": -0.08309477489031791, + "gap": 14.950029373862549 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.5967360645390727, + 198.6444867575285 + ] + ] + }, + { + "type": "arrow", + "version": 1115, + "versionNonce": 1348174403, + "isDeleted": false, + "id": "OMRiiKcWvoZZ9P2ZIgA2p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12460.376285434088, + "y": 1359.7181546722893, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.386867845279994, + "height": 138.94510037598502, + "seed": 745408451, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783805326, + "link": null, + "locked": false, + "startBinding": { + "elementId": "ZdXGL4Hg2PSzAEO98aq3F", + "focus": -0.052520323556520976, + "gap": 23.499022867740905 + }, + "endBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": 0.08546511481810001, + "gap": 15.792881969563723 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.386867845279994, + 138.94510037598502 + ] + ] + }, + { + "type": "rectangle", + "version": 937, + "versionNonce": 1514322723, + "isDeleted": false, + "id": "RPd3Ts2qYHajodT4DqAiv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12846.853117088702, + "y": 1146.761544911934, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 402.1646011736996, + "height": 184.51025311618514, + "seed": 1539936397, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "7hrt9Q8ALp2hgi-CH7RhI", + "type": "arrow" + }, + { + "id": "IyuMVWi6LT-PazE_jpikJ", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1301, + "versionNonce": 630366947, + "isDeleted": false, + "id": "7hrt9Q8ALp2hgi-CH7RhI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13049.96504755975, + "y": 1498.6632550482743, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.19527532206120668, + "height": 153.88616803142645, + "seed": 13301133, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783815330, + "link": null, + "locked": false, + "startBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": 0.42118144300698207, + "gap": 15.792881969563723 + }, + "endBinding": { + "elementId": "7YBNHHpVyngxousOJEXUu", + "focus": -0.00745054484076321, + "gap": 22.37247485594844 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.19527532206120668, + -153.88616803142645 + ] + ] + }, + { + "type": "arrow", + "version": 1221, + "versionNonce": 254971491, + "isDeleted": false, + "id": "IyuMVWi6LT-PazE_jpikJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13038.762181968208, + "y": 1130.9031564859067, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 3.1934308415275154, + "height": 181.70134749614238, + "seed": 1287719491, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "RPd3Ts2qYHajodT4DqAiv", + "focus": -0.03588056385821184, + "gap": 15.858388426027204 + }, + "endBinding": { + "elementId": "a1_3jaimGqTutIIO3YJT5", + "focus": -0.4092387389518107, + "gap": 19.287258234853653 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.1934308415275154, + -181.70134749614238 + ] + ] + }, + { + "type": "arrow", + "version": 1580, + "versionNonce": 500055373, + "isDeleted": false, + "id": "7wvuIjAIlJ-I_bJox-PJW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13618.769431129636, + "y": 947.027444618464, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.6850431259772449, + "height": 170.84265905543248, + "seed": 1588284077, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "a1_3jaimGqTutIIO3YJT5", + "gap": 17.112893863553268, + "focus": -0.8641123108830687 + }, + "endBinding": { + "elementId": "DjV5UB4NwXHIkAXHRfeJA", + "gap": 25.677323001460536, + "focus": -0.014279542779406101 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.6850431259772449, + 170.84265905543248 + ] + ] + }, + { + "type": "arrow", + "version": 1685, + "versionNonce": 857872493, + "isDeleted": false, + "id": "BWjQ8wJWdXaye_ajaXVvj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13623.465766647503, + "y": 1346.4259722215186, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.3896433100544527, + "height": 139.7898599610403, + "seed": 1682973635, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783829179, + "link": null, + "locked": false, + "startBinding": { + "elementId": "DjV5UB4NwXHIkAXHRfeJA", + "focus": 0.004749821888850901, + "gap": 16.72451984765621 + }, + "endBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": 0.7541251637875088, + "gap": 28.240304835279176 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.3896433100544527, + 139.7898599610403 + ] + ] + }, + { + "type": "rectangle", + "version": 1085, + "versionNonce": 1762855853, + "isDeleted": false, + "id": "4Yrr-euaC9DdSkW5y8oyN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11238.362441030637, + "y": 2911.4268564867584, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 348.45383778215194, + "height": 235, + "seed": 1053963971, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "88f60GDXQNihrbx7Rd4tO", + "type": "arrow" + }, + { + "id": "kpT676I1UQJTahGMjR2KY", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1025, + "versionNonce": 1668222477, + "isDeleted": false, + "id": "qpE4b-DBvZhuDy_uFDRU-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10602.608259175538, + "y": 2909.8906508026707, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 348.45383778215194, + "height": 235, + "seed": 552851363, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "88f60GDXQNihrbx7Rd4tO", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1411, + "versionNonce": 198952045, + "isDeleted": false, + "id": "88f60GDXQNihrbx7Rd4tO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10958.787694910654, + "y": 3020.2730198147474, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 260.95996573632874, + "height": 0.3117425560913034, + "seed": 1959568003, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "mWWsvhW90Myb-JBP-3vCi" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "qpE4b-DBvZhuDy_uFDRU-", + "focus": -0.04687499999998858, + "gap": 7.725597952964108 + }, + "endBinding": { + "elementId": "4Yrr-euaC9DdSkW5y8oyN", + "focus": 0.07812499999995659, + "gap": 18.614780383652487 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 260.95996573632874, + -0.3117425560913034 + ] + ] + }, + { + "type": "text", + "version": 54, + "versionNonce": 67290339, + "isDeleted": false, + "id": "mWWsvhW90Myb-JBP-3vCi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 10953.944506448326, + "y": 3470.4558653791087, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 184.10000610351562, + "height": 45, + "seed": 222508877, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Signatures", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "88f60GDXQNihrbx7Rd4tO", + "originalText": "Signatures", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "arrow", + "version": 1549, + "versionNonce": 1176678093, + "isDeleted": false, + "id": "kpT676I1UQJTahGMjR2KY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11396.227121735248, + "y": 2892.2100867640706, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.682493292615618, + "height": 197.62170939324778, + "seed": 33091277, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "4Yrr-euaC9DdSkW5y8oyN", + "focus": -0.09639886068542863, + "gap": 19.216769722687786 + }, + "endBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": 0.5288712224987059, + "gap": 17.67248434620251 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.682493292615618, + -197.62170939324778 + ] + ] + }, + { + "type": "rectangle", + "version": 960, + "versionNonce": 633221251, + "isDeleted": false, + "id": "PNGdxsolpFZkdFBDEJXv3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11751.04844200363, + "y": 2904.6389201247093, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 348.45383778215194, + "height": 235, + "seed": 1375695139, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "8E8bIA1TLsDkEkmLGooFY", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 881, + "versionNonce": 1078667299, + "isDeleted": false, + "id": "8E8bIA1TLsDkEkmLGooFY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11913.304049881848, + "y": 2884.465899306031, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1.4217447706050734, + "height": 182.87469028483883, + "seed": 838428973, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "PNGdxsolpFZkdFBDEJXv3", + "focus": -0.07446390437993745, + "gap": 20.173020818678197 + }, + "endBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": 0.22927907638047093, + "gap": 24.675315996571953 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.4217447706050734, + -182.87469028483883 + ] + ] + }, + { + "type": "rectangle", + "version": 246, + "versionNonce": 460660621, + "isDeleted": false, + "id": "Dl-HTanqJ1Hckc2GzBlri", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12488.663318115425, + "y": 3184.6804316677885, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "width": 1505.4294710432077, + "height": 434.88068008259864, + "seed": 568905955, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "7lTqxzE2G6qkRBd-0W_JC", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 224, + "versionNonce": 1934408077, + "isDeleted": false, + "id": "-240uTfhSQUOLibdAW3ww", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13032.334069125285, + "y": 2851.396312359026, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "width": 334.8423597437868, + "height": 145, + "seed": 856193773, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "GFH7lVRLTqVlZnVWPiljy", + "type": "arrow" + }, + { + "id": "7lTqxzE2G6qkRBd-0W_JC", + "type": "arrow" + } + ], + "updated": 1704783845793, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 768, + "versionNonce": 902019939, + "isDeleted": false, + "id": "GFH7lVRLTqVlZnVWPiljy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13200.29273144336, + "y": 2826.895651889968, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2.0423116889724042, + "height": 132.99212459601404, + "seed": 865456141, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "-240uTfhSQUOLibdAW3ww", + "focus": 0.012027720599590914, + "gap": 24.500660469057948 + }, + "endBinding": { + "elementId": "mcq3uHl3gntSG5245aRvO", + "focus": -0.49843662270444483, + "gap": 16.98763426933374 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.0423116889724042, + -132.99212459601404 + ] + ] + }, + { + "type": "arrow", + "version": 520, + "versionNonce": 1173512269, + "isDeleted": false, + "id": "7lTqxzE2G6qkRBd-0W_JC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13203.710310763878, + "y": 3009.2894576040644, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 0.3847324452253815, + "height": 150.8903135946657, + "seed": 409933379, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "startBinding": { + "elementId": "-240uTfhSQUOLibdAW3ww", + "focus": -0.022298305855725533, + "gap": 12.89314524503834 + }, + "endBinding": { + "elementId": "Dl-HTanqJ1Hckc2GzBlri", + "focus": -0.04867598969964186, + "gap": 24.500660469058403 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.3847324452253815, + 150.8903135946657 + ] + ] + }, + { + "type": "text", + "version": 128, + "versionNonce": 287069955, + "isDeleted": false, + "id": "_8y98_xJch7NLILMKEhCn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 10664.552144282003, + "y": 1903.5825172373818, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 246.06666564941406, + "height": 70, + "seed": 2075553933, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Epoched validator\nset change.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Epoched validator\nset change.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 94, + "versionNonce": 1589300909, + "isDeleted": false, + "id": "wYHEguLvgU82KMiyAfZJJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 11067.583960803446, + "y": 1893.5825172373818, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 280.1333312988281, + "height": 105, + "seed": 1490476131, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Create and maintain\nBLS multisig as \ncheckpoints.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Create and maintain\nBLS multisig as \ncheckpoints.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 88, + "versionNonce": 438337187, + "isDeleted": false, + "id": "JvfCEyLFHS1pKMGz1meyP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 11451.50762258726, + "y": 1903.693627334623, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 362.73333740234375, + "height": 70, + "seed": 1934013901, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Verify and maintain\nreported BTC checkpoints.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Verify and maintain\nreported BTC checkpoints.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 134, + "versionNonce": 1965453581, + "isDeleted": false, + "id": "4NzZ9SDYkbCZv9tjhYup0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 11930.188771907147, + "y": 1907.1950431598086, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 220.56666564941406, + "height": 70, + "seed": 698171149, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Maintain a BTC\nheader chain.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Maintain a BTC\nheader chain.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 94, + "versionNonce": 729723459, + "isDeleted": false, + "id": "5I-dCP4iyKE0NnGgrwMXP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 12319.448498565636, + "y": 1891.3792858133634, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 318.9333190917969, + "height": 105, + "seed": 683909869, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Maintain the consumer\nzone headers and their\nBTC Timestamps.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Maintain the consumer\nzone headers and their\nBTC Timestamps.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 221, + "versionNonce": 1175810531, + "isDeleted": false, + "id": "gtGLLlyfysHcg3f6taCDq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 90, + "angle": 0, + "x": 12836.048160600903, + "y": 1891.8793279155798, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 259.6333312988281, + "height": 105, + "seed": 1947919235, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Verify and maintain\nBTC stake and\nFinality Providers.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Verify and maintain\nBTC stake and\nFinality Providers.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 225, + "versionNonce": 1704808909, + "isDeleted": false, + "id": "vBcHGfi5-K9nKkqyhznI0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 90, + "angle": 0, + "x": 13228.70469666267, + "y": 1885.0665706283746, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 313.76666259765625, + "height": 105, + "seed": 1759651971, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Verify and maintain\nfinality votes\nand EOTS randomness.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Verify and maintain\nfinality votes\nand EOTS randomness.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 132, + "versionNonce": 1257454979, + "isDeleted": false, + "id": "d7jrXxnu9PHk_MGUeW8xK", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 90, + "angle": 0, + "x": 13679.341546413047, + "y": 1884.566528526157, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 218.5, + "height": 105, + "seed": 669907437, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Distribution of\nnetwork support\nrewards.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Distribution of\nnetwork support\nrewards.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 66, + "versionNonce": 2131464237, + "isDeleted": false, + "id": "fdIyxAx6eQhS7u-aK6ckn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11211.77038288783, + "y": 1167.7503926873162, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 313.4666748046875, + "height": 45, + "seed": 730865635, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Vigilante Reporter", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Vigilante Reporter", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 81, + "versionNonce": 1968608547, + "isDeleted": false, + "id": "T3pfRIYcYPL6bFv5zCqZS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 11228.841464847414, + "y": 1249.8753662004258, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 286.6333312988281, + "height": 70, + "seed": 1488152515, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Report BTC headers\nand checkpoints.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Report BTC headers\nand checkpoints.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 109, + "versionNonce": 551748237, + "isDeleted": false, + "id": "vrYH93HbDN6yP8Fy_dGmB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 11766.58320148878, + "y": 1171.4048028939699, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 327.8999938964844, + "height": 45, + "seed": 1306485613, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Vigilante Submitter", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Vigilante Submitter", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 99, + "versionNonce": 346945731, + "isDeleted": false, + "id": "3ub_r5XSJBHCTWXlFecrP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 11797.574376497836, + "y": 1245.838596820386, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 251.3000030517578, + "height": 70, + "seed": 907125251, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Submit checkpoints\nto BTC.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Submit checkpoints\nto BTC.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 602, + "versionNonce": 1339893997, + "isDeleted": false, + "id": "1W8DJ9txG4KCNn-8JrThS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12276.206992082845, + "y": 1152.4550739268336, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 384.4263330666291, + "height": 185.9986790618848, + "seed": 2027432301, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "a-2O3cQmmsLXw3Bxai_qN", + "type": "arrow" + }, + { + "id": "OMRiiKcWvoZZ9P2ZIgA2p", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 151, + "versionNonce": 110618723, + "isDeleted": false, + "id": "lZCmgY7bzBcYE7Xa_wM8O", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12283.33590030968, + "y": 1168.7117991180526, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 367.6666564941406, + "height": 45, + "seed": 497840493, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Checkpointing Monitor", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Checkpointing Monitor", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 155, + "versionNonce": 1032029005, + "isDeleted": false, + "id": "ZdXGL4Hg2PSzAEO98aq3F", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 12318.534925132479, + "y": 1231.2191318045484, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 268.0333251953125, + "height": 105, + "seed": 1145697667, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "OMRiiKcWvoZZ9P2ZIgA2p", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Monitor Consistency\nbetween Babylon\nand BTC.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Monitor Consistency\nbetween Babylon\nand BTC.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "text", + "version": 157, + "versionNonce": 1893917699, + "isDeleted": false, + "id": "_AjAQ5ECzKaDvH9mnCcJY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12866.792928280334, + "y": 1161.362933380697, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 368.73333740234375, + "height": 45, + "seed": 2105201475, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC Staking Monitor", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Staking Monitor", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 253, + "versionNonce": 774869421, + "isDeleted": false, + "id": "7YBNHHpVyngxousOJEXUu", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 12880.959888335641, + "y": 1217.4046121608994, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 334.9333190917969, + "height": 105, + "seed": 1865156237, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "7hrt9Q8ALp2hgi-CH7RhI", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Slash Finality Round\noffenders and\nreport stake unbondings.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Slash Finality Round\noffenders and\nreport stake unbondings.", + "lineHeight": 1.25, + "baseline": 95 + }, + { + "type": "rectangle", + "version": 819, + "versionNonce": 1254489923, + "isDeleted": false, + "id": "DjV5UB4NwXHIkAXHRfeJA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13448.196945413038, + "y": 1143.547426675357, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 348.45383778215194, + "height": 186.15402569850542, + "seed": 1062002499, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "BWjQ8wJWdXaye_ajaXVvj", + "type": "arrow" + }, + { + "id": "7wvuIjAIlJ-I_bJox-PJW", + "type": "arrow" + } + ], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 53, + "versionNonce": 739166829, + "isDeleted": false, + "id": "X7yUfzjIr-3NOFjM0jc08", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13507.80436395257, + "y": 1175.793060282003, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 215.23333740234375, + "height": 45, + "seed": 1141714765, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "BTC Staker", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "BTC Staker", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 89, + "versionNonce": 2028674787, + "isDeleted": false, + "id": "hsSqnNpDaD9_WfjllVJRX", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 13495.305779777755, + "y": 1238.3142976597876, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd8a8", + "width": 247.23333740234375, + "height": 70, + "seed": 1974030253, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Stake Bitcoin and\ncollect rewards.", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Stake Bitcoin and\ncollect rewards.", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 114, + "versionNonce": 1245242413, + "isDeleted": false, + "id": "xRSksN_qbcyeHx1gN6uiH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13103.642533820894, + "y": 3490.2289207297235, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "width": 261, + "height": 45, + "seed": 1929764173, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783837066, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Consumer Zone", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Consumer Zone", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 123, + "versionNonce": 1351305859, + "isDeleted": false, + "id": "caDZYXP-6Bzkxb4Eb4_EA", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "angle": 0, + "x": 12568.87352703562, + "y": 3225.619902190428, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "width": 1376.0564192979637, + "height": 154.06229630816915, + "seed": 1415937955, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 96, + "versionNonce": 1216795437, + "isDeleted": false, + "id": "pWxJ8IrgHfJGgeLDhzpEB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 13087.295359367748, + "y": 3278.1411395682126, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 307.70001220703125, + "height": 45, + "seed": 772957283, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Babylon Contract", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Babylon Contract", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "image", + "version": 227, + "versionNonce": 107282979, + "isDeleted": false, + "id": "W1XRgLLBnKZdGnv-V4FVQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12446.625125189941, + "y": 803.3171293101589, + "strokeColor": "transparent", + "backgroundColor": "#e7f5ff", + "width": 79.1786076828552, + "height": 79.1786076828552, + "seed": 1432549997, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "status": "saved", + "fileId": "fa53842a29ce0961cab2326a60a37b9ba18bfb84", + "scale": [ + 1, + 1 + ] + }, + { + "type": "image", + "version": 89, + "versionNonce": 1073603981, + "isDeleted": false, + "id": "_iRFSxYHXSKt6--dzrpBm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12239.371692481705, + "y": 1561.9544643531679, + "strokeColor": "transparent", + "backgroundColor": "#e7f5ff", + "width": 79.25400556831883, + "height": 81.51840572741365, + "seed": 847206925, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1704783770352, + "link": null, + "locked": false, + "status": "saved", + "fileId": "8ec1dc6981e6ee38b87d2c4cc93ce1a5a287cc35", + "scale": [ + 1, + 1 + ] + }, + { + "id": "_evFNTW7bn-AoKjXNN_NZ", + "type": "text", + "x": 10645.896165817512, + "y": 2921.7534438548937, + "width": 264.79998779296875, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2013581901, + "version": 53, + "versionNonce": 799255533, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "EOTS Manager", + "fontSize": 36, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 32, + "containerId": null, + "originalText": "EOTS Manager", + "lineHeight": 1.25 + }, + { + "id": "Hch1PSwsJWUE_P6X4aCQ5", + "type": "text", + "x": 10648.920491369734, + "y": 3014.992462144717, + "width": 220.3333282470703, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1400250115, + "version": 47, + "versionNonce": 1320097123, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "Generate EOTS\nrandomness.", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 60, + "containerId": null, + "originalText": "Generate EOTS\nrandomness.", + "lineHeight": 1.25 + }, + { + "id": "z7jqVmJirhSNfddWdOTE_", + "type": "text", + "x": 11270.409958543934, + "y": 2923.1564452618036, + "width": 283.5666809082031, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1849965293, + "version": 57, + "versionNonce": 125937229, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "Finality Provider", + "fontSize": 36, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 32, + "containerId": null, + "originalText": "Finality Provider", + "lineHeight": 1.25 + }, + { + "id": "oRk1lQcXcsd7oCGeUnYLW", + "type": "text", + "x": 11244.376633348624, + "y": 3013.1939628481723, + "width": 335.6333312988281, + "height": 105, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 567029187, + "version": 49, + "versionNonce": 1737257219, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "Manage Finality Provider\nand Vote in\nfinality round.", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 95, + "containerId": null, + "originalText": "Manage Finality Provider\nand Vote in\nfinality round.", + "lineHeight": 1.25 + }, + { + "id": "iehFyWIhnoh5DujUNT9sS", + "type": "text", + "x": 11783.98191084734, + "y": 3014.5969642550817, + "width": 274.5, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1364488867, + "version": 38, + "versionNonce": 2054247597, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "Enforce transaction\nformatting.", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 60, + "containerId": null, + "originalText": "Enforce transaction\nformatting.", + "lineHeight": 1.25 + }, + { + "id": "wlTg6sWMPMpIH0PwhrKJC", + "type": "text", + "x": 11760.484918357764, + "y": 2916.7534438548937, + "width": 334.29998779296875, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 137649197, + "version": 58, + "versionNonce": 528308387, + "isDeleted": false, + "boundElements": null, + "updated": 1704783770352, + "link": null, + "locked": false, + "text": "Covenant Emulator", + "fontSize": 36, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 32, + "containerId": null, + "originalText": "Covenant Emulator", + "lineHeight": 1.25 + }, + { + "id": "9BopJYEF94m4kH0sJ-y9i", + "type": "text", + "x": 13096.717027283268, + "y": 2867.3279318961627, + "width": 216.63333129882812, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1814788685, + "version": 46, + "versionNonce": 104337901, + "isDeleted": false, + "boundElements": null, + "updated": 1704783858059, + "link": null, + "locked": false, + "text": "IBC Relayer", + "fontSize": 36, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 32, + "containerId": null, + "originalText": "IBC Relayer", + "lineHeight": 1.25 + }, + { + "id": "CmwsicIXUm_k2K1UjBN4A", + "type": "text", + "x": 13079.300363159731, + "y": 2936.3579459652583, + "width": 251.46665954589844, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1470380717, + "version": 28, + "versionNonce": 783533357, + "isDeleted": false, + "boundElements": null, + "updated": 1704783874822, + "link": null, + "locked": false, + "text": "Share chain state", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "baseline": 25, + "containerId": null, + "originalText": "Share chain state", + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": { + "fa53842a29ce0961cab2326a60a37b9ba18bfb84": { + "mimeType": "image/png", + "id": "fa53842a29ce0961cab2326a60a37b9ba18bfb84", + "dataURL": "data:image/png;base64,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", + "created": 1704443366413, + "lastRetrieved": 1704700745150 + }, + "8ec1dc6981e6ee38b87d2c4cc93ce1a5a287cc35": { + "mimeType": "image/svg+xml", + "id": "8ec1dc6981e6ee38b87d2c4cc93ce1a5a287cc35", + "dataURL": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzVweCIgaGVpZ2h0PSIzNnB4IiB2aWV3Qm94PSIwIDAgMzUgMzYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8dGl0bGU+bG9nby1mdWxsLWJsYWNrPC90aXRsZT4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJsb2dvLWZ1bGwtYmxhY2siIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMzMzNjIwLCAwLjUyNzgzMCkiPgogICAgICAgICAgICA8cGF0aCBkPSJNMy4zNzcxLDEzLjE2MjM3IEM1LjE5NDU4LDcuNDIxMDYgMTAuNTQ2NzgsMy4yNjE0NyAxNi44NjYzOCwzLjI2MTQ3IEMyNC42ODIwOCwzLjI2MTQ3IDMxLjAxNzk4LDkuNjIzOTcgMzEuMDE3OTgsMTcuNDcyMjcgQzMxLjAxNzk4LDI0Ljc2MzE3IDI1LjU1MDI4LDMwLjc3MTQ3IDE4LjUwNzc4LDMxLjU4ODI3IEwxOC41MDc3OCwyNy4xMTMxNyBDMjEuNTk1MDgsMjYuNTg3OTcgMjQuMTg4MTgsMjQuNjA1NjcgMjUuNTYwMzgsMjEuODk2MDcgTDIxLjYzOTM4LDIxLjg5NjA3IEMyMC40NTI2OCwyMy4xODQ2NyAxOC43NTQxOCwyMy45OTE1NyAxNi44Njc3OCwyMy45OTE1NyBDMTMuMjc5OTgsMjMuOTkxNTcgMTAuMzcxNDgsMjEuMDcyNjcgMTAuMzcxNDgsMTcuNDcyMTcgQzEwLjM3MTQ4LDEzLjg3MTY3IDEzLjI3OTk4LDEwLjk1MzA3IDE2Ljg2Nzc4LDEwLjk1MzA3IEMxOC44MTAxOCwxMC45NTMwNyAyMC41NTM2OCwxMS44MDg2NyAyMS43NDQyOCwxMy4xNjQ4NyBMMjUuNjE4MjgsMTMuMTY0ODcgQzI0LjAzMTA4LDkuOTIzOTcgMjAuNzA5MTgsNy42OTMzNyAxNi44Njc3OCw3LjY5MzM3IEMxMS40ODYwOCw3LjY5MzM3IDcuMTIzNDgsMTIuMDcxNTcgNy4xMjM0OCwxNy40NzIxNyBDNy4xMjM0OCwyMi4zMTgzNyAxMC42MzYyOCwyNi4zNDEyNyAxNS4yNDU5OCwyNy4xMTYxNyBMMTUuMjQ1OTgsMzEuNTkwNjcgQzkuNjg2NzgsMzAuOTU0MzcgNS4xMDU0OCwyNy4wODM1NyAzLjQxNDM5LDIxLjg5ODQ3IEwwLjAyOTg0LDIxLjg5ODQ3IEMxLjk4MTU2LDI5LjQwNDc3IDguNzc5NzgsMzQuOTQ0MjcgMTYuODY2MzgsMzQuOTQ0MjcgQzI2LjQ3NTk4LDM0Ljk0NDI3IDM0LjI2NjE4LDI3LjEyMTk3IDM0LjI2NjE4LDE3LjQ3MjI3IEMzNC4yNjYxOCw3LjgyMjU3IDI2LjQ3NTc4LDAgMTYuODY2MzgsMCBDOC43Mzc4OCwwIDEuOTExMjQsNS41OTcgMCwxMy4xNjIzNyBMMy4zNzcxLDEzLjE2MjM3IFoiIGlkPSJTaGFwZSIgZmlsbD0iIzBBMTQxOCIvPgogICAgICAgICAgICA8cGF0aCBkPSJNMjAuNDI5MjgsMTcuNDY0NzcgQzIwLjQyOTI4LDE5LjQ0NDI3IDE4LjgzMDk4LDIxLjA0ODg3IDE2Ljg1OTQ4LDIxLjA0ODg3IEMxNC44ODgwOCwyMS4wNDg4NyAxMy4yODk3OCwxOS40NDQyNyAxMy4yODk3OCwxNy40NjQ3NyBDMTMuMjg5NzgsMTUuNDg1NDcgMTQuODg4MDgsMTMuODgwODcgMTYuODU5NDgsMTMuODgwODcgQzE4LjgzMDk4LDEzLjg4MDg3IDIwLjQyOTI4LDE1LjQ4NTQ3IDIwLjQyOTI4LDE3LjQ2NDc3IFoiIGlkPSJQYXRoIiBmaWxsPSIjRkY3QzJCIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==", + "created": 1704445033257, + "lastRetrieved": 1704700745150 + } + } +} \ No newline at end of file diff --git a/docs/static/arch.png b/docs/static/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..307060f476291ffadb00501df2bcd096b8f17d01 GIT binary patch literal 4302382 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⪻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
5#42z=GajA@`aIZbh?Lfp7Q%H!$H4 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 z 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%0AN005yB;8to* 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 z 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
kfofE4JXil`vRad1 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

VSN;?oXx?-rjY=#u?G6 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

J^0g4J5hkKLP_nu?$xH80+bs^sx!%H6z_I0{XE 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:w@$LWY0S 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

b+ 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%0Aa)`|O~u*P|-f=oWIa%4%G>Gshe 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

Znbbn@)LEu+6lryEvt8RU_eiN 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�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 iKRC|1hj5aSaaP6axS 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%0D_TIHd{^@U=*$jUoO()Ep*#@tti(A)fW5o(p9@tH z9RA=J-)*ma`K#Kg_rFiO&++#kihbEQXOJif#Cx1{k9O77i=^1S?|tv~u=_r$9eLal zZOyi|?aH71sD15=Uuzd%@`?8GPkel=ySG_4vAf^>?(MptY-&H>aCO_|hV|_hcig+} zxY~dbJ33L~f{&a()V^Nv(pR;IJmNtURTeI$BDmV(cAx;sm!M|J+C04}bVWefKVR zxl4mgcGXo^DJgl@S!Y!{u=IC2p}qAV&S`fz`S+Fz76hdadeDO=$y5@Zk9zc@+LNC2 zq_*$A`wrzK=7F{KlOJ5w{^^4M^}F5MlAe&#ZoU6)zQ*uAPM zC3`oz2X>-OR|4z@?L-NBW>ty6X6sujj@S#-M{Qq+vI+l=!=DdwjVp(|6*PJNl~l^Q z-R}h?<}#eQtZtkSU6Fk3yvQ9fq?m;Yg4@md3Yq*84h7%k9@r#-<|&TbXO0{Z*MsPQ zv%mZA_cMn-Glwe3_|cZvZ&jsYegyDCB_k`M0|*QCiv$o*5KO@C4zozxW@m?EY0sb_ z3C4WcBUQy2QUmjYt)CP(S%YTFc#*E`k`}f*^i`d1fTSS(qOfIZlbkU@2JRr`W%XOe zOlORpbtG5WzXG%Vmd+c!nAU-ttkvn7Py*1$mZf z+H398U;I=%@1y6mo8N5T_UK1Gy50Ux-c{6tkCAM1$A5I4N`=&1oWVIK@yys%`w%u% zjydbu6P`Q|fjxdf_$*=DNj_B7rVe^sbY`8{KnD@aS0X`aK-NfT&lTTu0k-acbtYr) z>@qC|p|c)`=7c37t*c(f{^wS88$zwf+zf^NVxi|ucTlm5}*-qw5K?) zj5ypZaf<3uEo{Yn0{AB>udc^#TjG3+v$UQI6*P=(isFTUVdZScc5H!IW_D~2h!&k# z0Y=W9LjqQMus^`y_PkN3t@Eoj0D#d70Gz`D-4g3qVv2J}WM%uM827rz12D_`vj!AY zr$hrbnP*b>%@iBNxXE|%Jv@kS1u&|DAw`3YkU4D+6>vj+C(d#92nsJNk=KII#V9I< zbNk1&qvP<1Ch%V+x7cO?3pEY8pL`|M#aoW%FSF7X@z;@~^{d{Tf2 zLT~_JQTVh(y*i?sd8sr~BOU$u9>^PM85-EeJM=V`A< z83@11{zqX0a8izId2V$ir$v4Gu!s3xbPoy@F?@PDkwrdZ*m9Ex-=$X9eIwh1ia@9Umq0eJv{K79f`7FLREPgMq*oz%k`k?0H6<=mI1v zc2kg~V3Yj35WmRb@ z!nP1MN;T0s8mPdK_E#295eNqWpS>HM7Uz^jV{cHb7C4Y|FR2ap+)hu9N)<2?{Pr-h z-Y1H6%z-^xkehNIBof8!kI}(%a|i9LJ8TL@nCxdygiCRnyq5b1>K189YKq$2FUDaI zMKSs003eDc1-Uq{DJrfp;2!pDNl7&M$^qOJxW*OshTm&H8=!4lJF=gUNjw)3qzs}ime`MfADk)sb zrua(`)9k-lS`gxz~qu2O_`ITq}_R{>IzUQaUALA1K zgyJF_7rkG~AMm}r=SCMO3U+{F+-_!nmgfyp*QBGffyCG^yPB<{P=y%J#3qZ_)$I-I zD--!D$VTJfWD^nCJQspx%;Wo(cfkbXgZZ$YM7Y2$$y@fMBEB zFr@ETXB6g$>s$pjx{)mrC9y-0iy&Ji@LODe0K(j6Gd8kywl*YEVLrAl0zfJu^ad&H7lmrnc%(HVy{ySt__jU97 zp3SG=PfNvl3SJ-fnenE30t76JX*XNvgxyS~J^(L@W2yB=Uen%RK(HwEqt=XYj2A_7 zjy`e(fNzy+wAehzDlz|+n5T7Xb|HMzajwe$?M3C@k+IvlTr`%wrTc@O5xd^fI+=G} z!STre4}k?CHm(qJsfK6(9(5-2xEP=gv0QXVkWB_;Vr!{_;JB$`Pwf*2wq=50XaE2p z07*naRILM?)P0VvTer5;AO5)Zt4+UbPkho78g(y-X>(!{WZu z$cuHKc4|*@UP?ZeJqLLWe6`!+@lD$9^2pt^1cus zB{Ew4$@ zOr0ey4ji#Y$z2ocp`1P^1j}5>zWDhOdm+e;Is*`)@^#3ZQb!SKSNWC2?qS}@C3XNK zIm`u7T2gG3~{8n zTrl?_7a`xwV)0Dg)N><(~(#f3%dxanB|Alh1otgcR^E*7XRbKdE+65j!Z<#)>0 z)%tNDXZXzO9IVb|{hP`Q63_eDQUy5pPWZeV?Oxc9Y9qBCZ4I(dq9!ANFMAl_EAKv= zpDnMq_&EZZ0WxM8@YucWB?9~o9;1=zZpZjF{ z#3wth<9W|~K|ABjCkRl+{<>uNpyhEQ=jtG_JI%fE^P03-MiZHryeu; zHTiqQyO8Ex+~vEMUeW&hOP<|6|HaRb?;do}LG5Wzds=(wLm#Txm-Rv2e4T;aHgDeC zp8BMxjWOYu)M;#Y!*QgX;qM|yeCxF)YH7X(ekSvJ;YA;9XPj|{RMP+HwQuV2t?hvg zTd!_!e9b?%3orgi`~DBUKY6D^4?VP&J;aR( zz?vVW3djjxL9Q752OWz&Ultuul$)@<16eLU#QN0o)rBWvlO`hG_(%@e)jS&DnzOco z`0JU>Jz~AFKSWHoTT>qmkaavq&a<&^z=ZN0)iIJFDDf1r3iYHc7#alox`tU#B6-_= z*Lh4?1xPY}jpw=UlI_+F*~GRoZkD_yV*PXvWXJM{gMYI>T7j8*gJ$Lv^)qjBe517Az`LC>9exq$yjwji)Y^7%OysjVbneY{&1 zxFOaI0iEdF6$EE6%j9q(CyIR{KAIznk2UOTM5HwH3#idi-;^Mfjg_ut=OZ>lx!Ba{ zdM5a%|_W?1ab1Ikr{JoTed(JR`CZSUVSmu4^NAkmeoK zDV6im{x`9^YZ#4h_^#QhR^OtnlAYaoV}Zov7O84Kbyum@`Rk;%>e zMU5XKXNHgB6X%@-jLdd`3CnAAE+*>DVEP<}XjXoXiXqB4kBD#a%X4g_{GZK5AJ=I| zq`0;)3gn7xEoyz`4vLmvQGEbBM#e}mzAo&=b?`}rrLgbEyj6@!_FF^DfH|t9J&A!h zR$un8@%>_XH!uetnJ?E@EI(B5*k5z~%7rakM~ z&ushbvriYHgxuvhnZTHcunF$k)`D;ch!Y@Gk@kJJ?Nc0aS!u z4Up2vOB7o;-m!Ec6i$nCB;lHU_Jnk4WHZ zSTfz?9cR^RLs^sunO`CQmW3D)tiJikQtFcBm2sZYXyUkr0<7@-} zmzymqkDLTF!oDeQR(u|E_4wDhE1a6E{K%fuiIOAVW%$xG@>4M~Auh9U? zeC`Qn#CZ+)Fj+KF>ydSmy7f}S#@6l*f6G6;W@htDx5I=P6DP}RyUIxqYpauavI|FQBN|F09%KJ+2$m}c$lDP~A5&yqMr+q-17Jmx{U0zVG1p<%9qWl?5tLrEge)@ok|82u+J0 zg>F+nWWxd7jmwVJ0!EpoyS}#ozZ=XDTakytogCD#4A45)ca= zfX#tC#ukN301K|h6D^`As)nVANx=HuOMcLv{^BRMZ++`q(*gev{`S82y|3Nj&?9w! z4l&+e#Y(_NbbT?SaOIU(D)GqYsH2W*=e_Q|IuN1*paN$LepEbxQEY27$b2#9sP~gJ zV(n%!9?5fBAin5DFZw@vZaSsC{`Ifd?=Y`dZ~I9*|AP0lZ+-1M?b|>2X8Xl2exbwv zX6yHA`|r13JLcG9+NsB#*7iE=CNhe09@9b3bvAtKy7un(pVxl2;i^XWwv!)l;z;5K z%RD;j@pp)fT;m6q{kWa~;eTo0{lVo@(Bj@V-*ca~*Is+I{r29!9edI-?ST(`U|Vk$ zUMX}M7?|-LO8njxQN@;2Ov^>COf;DclY8*)@k+u$ZU;~O1kkp(+l)FO3wbV6| zf`_-Cov^W`f=ouTKW8rZ&H)DFnq7^>3n+AWGCO(wkInwuL0i>e>!MUEc~7<~8G(8& z3KtZd?Es`5vktu(l~&YF**9~=_hHLC=|BN#D@qS$59A=*o?pjUx{oIE1chFaY`cw- z8mWy1K!Qp-i0jLSnN!DpjpQNrNF+j2;OF_I?llt}=CR=X;eg-c=Np2zwgmV+90E0u3Ao6tD=60jt>aRR9{-1e{5Lo%eQK3)m}-r=3r=C@u1v z?M;5as;5-|@&qKsdEuny0B6N97ze-0FNXLa2lg45dTKEKG_Po#Cc&vAUqNRc1?lDf+R?H1F%=NK5D@<_6as- zkbR6;R7rJxVcP$&akfqbWI|DLrqf*?4_Cx;FnU(K6Zp{fA4OI<#ijTn6)9VTd_H@b zbxeELIc>jv_G^!R+@l2k*ZU`hbp74Ifdat+hV}$*UV9yo)eGyP#uN^8*igm*N0tIa z6yRBltjPeGWZ>PBLVx%`MV*Rg6i9$p+;pr%T?G{+Crnmy7~ij;w!CjyLv(>4i2w<# zl4q+O!bV6T-E2ngV3hwH&lrjvB2w5#g`Naxlv(kthUB>STLug3kfa{W}?87 z0TJgdZoWe$?Xv%P-<4D>1-bxOX6F}VgP6Mtu#EeO#AZbal&#fu zhTzZsOn1tqZolkD$WtU~ut%1#iNGyLNt_QE6rblK2fiYYQdo8og1LPrRd2h;oGQ2q zPRtoeS`q4|ethpt-HAF;!~(cb$xUM%=(J@;Gl4mXn`{mldy%YcKd|PMNcSRC-Qy9H zNZs1(7l6uKfYO?!JCy2DYkL6Y zFH1UPg{!=-!`=;%2)kSdcXWW92rEv&2(k(97-Y6Mhcw2VOST{8gbYZ=boIt?PI@s- z#KA;TALJmLhbE$wXDYVYB|t?UN_fZ}Y?T(CY;H}6P}O9UaU6NyIH9-$MRo;%1&5Qe=F zJ+bp_d7F3EiQcDnV|MjX{KWeEu~w8Ex0nDTLoK!{k7EDJq@XCaQsm7(gF36Aa*67q z#gbvCM5wZ{S8>+v!JJoBxw>x0dJ2%UY+wiMcHnAP+RdbN6@k2{8Q@>$e2bBm1Z*cy zA{*KLqzabIq9Wba_r1V+j5wda1BlAGftYjof3}`PUURTVRYYgq@tl051na(1zBhZ1 z1SWeS?06>l>zBQ#tFy>K(R$|)A#$;es$iuqa&+}fK*s1Wq|V1?Q&@94r){hhU^DT0+HVTvaKKqUi-Vl{goy4}Gk_eu} zi^D-i!p9qmvaVLC4nKv6ttE&mJw-zEuUSJBTh#pwU!G2YrCV-RqQ%ExUMTKh>qduX z2SCJ`5#1PBYXTBL>Pkv?8AuRQ<2U>o~KV?2A3*cL1A)?cjP__t@AT`mjH2Yu2o3&-&9pYj?fn32nE7 zcUkyvzR$x?U~DOvbS3Tum?-Xt&y0dE0C(QEc7x=5xd^TNv#m4Mu=2U&NXX~X^~gyx zQ5dK``VxTd?E~s?QVl1=DWGWzsa0Gk00t)d9R~y+jI_-79 zHS?JY%!q0s*yZ&W)(>Y>27AqQ!i;gCfQTI!isR}m)9oJbEP#TGobWpnFpQt8>a6FW zq{*3-NLe}#$?+l@W$TqSnR6IX7>^=2K5!Ja*=yA|75NZigy-+8*ibHR=l7Qwm%xpl zyX86#9AXbv5f;V<1x(rH62mB;LEg(gj1l%_=bHc|>N(gr6YxuPPC`Pr{6pT?tijsp zw%Ub3wm|H|^Xk~U-r=~f%M4E8_vciGrE1Q40YFY%T)N4~rrDmurc&6Y7Py|yrwI9= zc1o=$i6jQeJBqCt6dLak`L+N6vsVH*pYxDGyw4qE!lrVj3q)h%9V+;x<9@n>ZyqABtc95DW6|a_hQWu$JdxkL=nrYuQGM!$WgP;49PyaQin}e+>sQj z$9}M{^StbF3c}PSM6&ln^R@rJr9I`jPi(*3_zS7sQ_Ed{qZu)B^yS&}9ur^)1z@{Y z_b0p1TwectKd)<@W&8{>VYNfnt6Q08A`yO2RnLtxD2Rvj|THpTf`YbQA` zB_E5qw7pKwu9BJZ+=$#wJ(72E|8J?DlVXIt9|pk8{ykz<&5aGH{K*ol*JqYsl7Zyt zR)mz#)zf+* zaSHK|+jh+*B=d;%Z0~api0rF(A|KGlIs@hhFpc^jIRZNu=DJSE%-R`A9u!g<*In|3 zL$CuNCE$NZRLqsxD&;R{I!o$#0Nz37^LmBE0&Py|1RgPFbP(~UoBpa1;l+l3$e zSo`W_myGXz{Y&3yd+gg8;^E5+Yr=Hdf1mxge^00>mBTUe({~_+Vwa6 zjX+fSbN<=yZ?AscE868h{P(4KuRnPF;~(Fi|C|@7RxA-%#G#xtgUI)jpZw&>uiWh} z$F@h`|Md2t$KQW)p8forzizL6$3L`nZTI%<7ykL=ccjE`op~UBklT?Up!q(E?d{)w z_Oq)+$UEqc2edu+T(laJ5QOE<)pdsO#YF_acKKJ@_rCsvcG77lw0&>3pXx>#7j*5{ zuWyg|^Z(K=`{ts%8{)`xfJ2b@rdw~Wm=?Q}MY6BH`f79CcWLW3-9$D+L^CJSQfv6} zSFUM0ZfU`tefHmff8j_nK)mJ&`5ya4#LK8d6r9iYZQI)>i#yitxu(}MyjyD65^4~t z3l%&@)cMsV+rhnbajx}Ku9Ly!%rmw_{z&>%!tRm?0QCZDePCp$;TUF)`PuEr<$K2d zmak~vS&NtsM17{)&j9vdV9W9u+x^S_jNrwxUzq*)IbTC(uh|I+B0^Z%=x3)Mn*i$?-9fHy6fVZgdZ`Vd`UFU z#;JE{?_`kt!EQ$$691JsiTYzD9&pz0z)!>Gl@1kLGyFO75!g-cMVB4rFM`NI&6Ju- zA~?($7T%RJL%C1#P^lXO=L2)GBXxAC;zk0yN3Fu%n`a~66uBaS@6CS_fOQ3t&k2Eu z_2z(Cb>2d(!+p<)NBMab!V4l1znd{}*(azz5?w~gv((&X&6P23ft%snmk6aj1NjW) z9nw_i-(^e@SreGr?lY;c+4KU^CbyQcrjeToqJECSwa3&2WOa|l+~h*+WEdtzeZ`QEtxWu|ve-H7Vx%G?BxXEmq&92#sMNE3Z z1sAkWfBMsH<8L;$$36IrcE{TtwKO-(vqTA8FeG9y-7^tMLKte)@{+JmUK8wB`H^O~ zz~@Q)%Srk1>``9`0Kf7H)Y2?B+H>wnq@rxH{=M$MM@-uZDGL%T=Mj5^JTC-Vof{rE z&vosq_0YH_=N@)N^%w-Dyes?!V{5g+9a|P1n)0=>fJAivVjOe*o4;>Ed>;`6%MJ3Z zxfT=8qUW~UpL}k!pW*LjakBVs)cKXuXMb6JWwq7ta~-Dom)JhY)A-2|OYv+H$-+Oi z@s4~iI0|skbiw*>Hj3e=XJ)_s_LVu%NbVUpK+M$OwM=IA zCjwYzpEhf5r}#+DSk5d7B-z#pzgc_4?3B)D>IHu0YfU6yInEE(88#$~a*1nU&n&Ww z?fWc%mgh)VQi~y@8y^3bZPvg);yb!fr|Q=B+`@=^Edcv*-SyYD_kQ5L?X7P+s~vau z;~D}J`|P=|@k2UBN;TD}^&~;&Adq))g2mG*pM`DD`8!_|{4Daz+bvJ221ZGUBQRub z`;=%lY0F$Vd<@+U0uD_+MnEE}DTgXN zgXOqlk7M`1t}lt|fupj$i;tZ}wZyAQxSl+h8~@Go@tkk0IdXW!uL)PyF&R?=m~4~f zoY|LAJCY!g#iq5fh=hCSmX6T3Z}+)wTa&fywREjt>TPD@>2?-24I8u5+J3HtUnYSq zd;cm#PQw$`T0Hg}7&h;09C$^Ms*>=D&WjmN5Ih)r=ETGJefFYoyD1m1 zJbK||LCVJN2VN@H&RkbH;pZUm*|;M1L$2k3{SRm-pK@}0)0^JZ4mAOql|DP!g6hyO3E8&6 z7QW?J!Z>M@0+e(Wg}A?f7==J4$3la;m}s0VsmQ>X&n#pLYyb$o{T|m9xdB6hlaW9m zO7hamI2FEY!dz;b1mGl(?7tD16ZnjzSeXMDwNml$Ly?16M%Im#z$vheote(B>o6fH z4A8kGUGjmt(G*WH;=E%8*AXbguq9B-pp+>5skq`qBdKaw0FxD%F_dc2Sd{qHYS4a{ zjVEwFs^b7_l0u1wfMh3*iWDq5fo-T>`hmu?%Q1nBVuq6OTvA2j03x_>W-E!Ljkeiv zem8F1*k1Osm#L-TZ~LIu^#_0O2krFJPj5%v?oOll#`~*ijKVgKq8IisCMTV8V*BBb zf2hm8{xxrC_qAjypC|yRa7*iC)?)4&Xx|YC8AUh>SxE^!KO=$R-~P|PZC_6b!nt=i z@{UKf2b^~QMv|0d-;#2DQazGkci@T&hN$%d=AA28kl3_$2e^%swbVuzryU~?`3{HB zlh4)3j`fNKB47yvilLxOk5_aAbU^6QR3W%68jFyi-><5yS*l#7bN~esW=twdmse5L?z3dePH^$S? z#cZe5-r3eE`%sDdwAU&~DT7i~xC78^_IfBEg1-QS6~(l&Z`BRMk`S^twuU%oe_Q7b z>;%O|C6G8fZTMzYz(fis@klnm7ihwXmf{midA8^DZ1&s`%K6|-XH|_18q0~JSO+Si znA51Djh%hi!>r;w){6&BJ6kXkgns!PrPCer$6P63On{QHAN`CZAxILX`T<)1Bs@tv z+BtObXD@2!zVqC6z`?grkqKg24Zeer9_uCUm4jR<1t7fT;{X6407*naRN0=0?zWRc zf~)weaBgFX(;54QA{iv)(N@`kcS;sun^NMRE_>Uo<8B5q$M^-H3Y)_|Q=$dKQ9I5-O;rT4Ac*kM+%5gj!43OcAFFXd&!9wv9ckLYAEmktoq#vFDX4 zcLEp=`%G4Oy zb4Z}Xo}((==k<+!gQ(k+UmYMLQXENpvmv7ZLQ;LwecQW6dq^2zFXp8Z);UKY?d+Di z7AmH*tuJ+E_gMmTA|*alqKr&0(2f&>gMe4b=~Cjexf9tT6Or6?QL&k5@05x7*sKn;@qOlr z!#H2~IURFDtf9vE+SmAX$=8|`SiRUIK%UR3zVE&d`%+0eNHV@I%^bEZmJ3iPOyb9M zzSe^CTvs2FwC#3bn*eL$@8yK+0AB$%V~ta2Cn;CRYdkalmbxd~cO-BVF*+RzDFArB zsj6yaf`80|6kYB8)qMqE90D`0_Ln;(#`l8^pgoX)j1o+MRBdOH_69iw)b{D*2T;cS z@gTS|#;i*fL|vUT+pQ1_VC`fTK)xdZ4f72`L?P#=axP#H;B0MU@8-Je#@>)^g0O8d z?W8jtq@ma~**JhbvPt>-6v70Y=`A?`Be8jWq*E&74RGCFQkb15$UC zSo7p}w02zybQGK*Hzi_Vj$;CJkX4h%+&Kv@=Qu(|GVG!X9_I5@ux5^5yVCC>R~pyb zV#qy&kJhbS*Pi}}XSTcF*NAERE-G0WfW*0kf5v^u-BeO|&l@v2J@+d+=r%IeX9iG+ zghQtrfTR%45!SIak9{M?kM*ImsT52D4W=9Y5l|B$8pa3%iuX+_qb6M*bHYB7gvd{@jh;V_eoOK9B6F$7pp8aIX-g zz$Qe81PS|SFLfU}NGlT(w1e>2F8~-OogBhXC027HUcA>txi$f1@;M+2p-wy9R?Mji zc3ZH>pXNAFqH896q4>fJK`LB38I*aM5OXOcyEp{bm5QeMyvo7a0#ujTl*<+H12B^d zCrO0K)|tSu5ZV31+{*WKe_r5#nGW4q^p4-*`pN-%KIFHl#&b^cZb634~cJAxOnHSlExe?MV|%s4+|@JviwX(29cxi5WyxaKb_4=ZLL2 ze{(*bHOP6$zN{iHLDbS7a=<|3)fvOE`4S0nkVCAUDz>Bf0wiHM6M;(*Z7Sz!)Fp1(LA+GAnRLd||)O*(Y$6&0*L^-gWb}TiVlJc!tCPA*P*j{Jq-l z#@wi4Hp4*JA4Eix60eMvUr)7S<_g!H^SP+b#+>GJ z6#K#lJHUO^Wf8F!e8vRQ)$dw-9fBX3%#?itktY$Gq2^L^&54wuPTE!%s&xo~HBclc z51j}l6QYrWf5=%YuVi*N`*oAwLk-`?m^BzamQ->r_C_#O_0rUpIL0&b&Wd|on5P2a zwP!fT=*DXIiF`~3e#);wWD`MCI+idm;UlNX%g>A*m%xo2_8rcqhO`okX-) z7(=v!@Hg8*_ah*Ktl5_sGsW!=KvDOG5Gleex=pNtLUXlAYyi)T4XE8|Vp?Lo1ZlGi z+SzkD9wElFcIzfLfV#QXY^g8sJKKMKgPlt~r<<`3HU6n$g{m##tGM4KBCem?6Ck74 zDCDDfTog+4OY_1RX>}TEm%AX8V`H1uvmvFCEKM#ma*md%>H*PgM<^piog-WuEc#ZdrI4?hY z*jkZUl>6-6ZiK0z)1d8#oVSa2tpsGv6@*_p-+Dec=YRwvrUq>GKs8HZGXSLtyu`i? z;hQCbF~_ObPDT2rmWeqI%uj~SnzpA!pWGgt3-W4q|f7q%~a z;S25C-~M*{-uJ%OcG+c@w&zXuYJ1&$uXe;84j1A3&j0Do0;K`W6DU5_XV^nu{_2<8 zr@wr0+wF#%ifp(4>RWZZL&}v$4GzC=E_Y4MY?XoWuy;BK2?sbTv7Jt;s$j*A)$(}3 zyJB=aPpV0Bwv+eHd43l-x_;fpcHVjCwJX2#({}91$Fvhqx_if#Ae?Jw6>GnCW>Fr_ zY&!V_btrHW6{07b9AKFDd@z2R|jzS8=0@o)LcWb1lP)Qe>%4e z+$|Du5ltAtK^G}vKXYzY5|$PUwjbOQB4f74*(d8Elci4u_*a{+HBZgdo|AkASS*o` ztez%s~vBsyfwZAaWy$#@Cq(S5hN7;J?CrkT!l!Q@-ym8W#=(#4ciIX zI0?MYnEe8QfI!`#Ju?m36*R@(Q#PA_(=%N<7xA|-mxU(~0 zCGvJ9h6d)3n3?Mbb}9A`^O*DNyibt9Bodsu+h?Dk`mUV$h>@*x;CGBmJ(FdhZT?0l zCtpK!u;AJ9{>?QB)#EHyV*RMQpN+%F4cj##%JB058%yAYyo=?N!q#Mr9`i17T6}Ht z@x*^lM%EaVIvE%@aF!VZ82%OW5&5jhS5&-0#=~*_fn_z4PX%L-oGbng!X-((XKHEs z-^iojqj2VD|L=nz{9wEI;)~mcpZu~t`M*A;-SwV#o|68+*dlmsHVnI#W3b#6uiEt- zkK}xkd~%VHdyYavZz)F8zV>_<=gcZP1lu_yf)DvL&H&qcxvR@$uPPqxwU->zPWGjr zU&2RP-3~0DL|e@!R1&D3r#xTWPq-vR+|XEaf2!7WA_fbrUl!AubO&-<7Cv39Gp^4% z?LJxWbXU9W@I1;3QEQ-%WoL5WsdEi8j}zme{SN7oba}<)_?aGdm|75ag)xenO4JM_ zkcb}-A;XDz0?QlGU?p)`o(0>xlO>KMg(VUxCi0MERUs}zTKO+ zF7mrWIE`+%wwENPiSStwqQL(34r|>8_AWfcg5Cce_^jgl2bOcLbN$#mjHCHI!dTgQ ztsOySU(F8Si)9fP#V0l&!rxItNWS2lKgPb6e?<-7#J^ZGIbT=1UFJCoe34=tU)Qn+ z#8Jw1+jx`nH~*~Wt%Olf!-nN9(prZ$_dLIRr>K*feZ!8gJ-DAU;Wvk^i+Ri49CO|} z-e0jD4SYNXmgqJZL--0JL3XjQ+B`;ME)pLk@@A1)&V2frZMSRo?zMhu7$JU>><#apy z>LC6|E-?AZyeD=;MCVj1*BNcOjPQ#hhgTbmkh`E;8<^pUb+I+zG}f6N9yRR1fO3u% z{4Qbz>k(-1vaR9|i%;}h7EMWY$b)&S~uSp z7Jf!}O@eAh5(a}AA2r~x7-d3gBL|D{e_H$MQfKcvaJqg0i8rXLTFQTtJHH+RZqKV6 zfBf<79dACj?X%;6cE3M4rJZ%wS?!QR4w>xB{FnbDV%oDF_w06```ml7AOTXTO=luj zaDzFeR26V-p%AFWN8nr>m7>Fo;Z(Abv*LMg zx!RqB`l(|jrltUg{d!^>8=H|sFAT&;3IqDDN|j0Vl>4$l_KAtM<`Pez<+-vdh~KfARyhX+ChjTeaKV{?_fT$KAEv z_O`bb3pxT&45w5h02bK+p{_)3pab0Tr7J$$9{uP?k13^v;gJX3SsNX~1dct1Bdh)1rsDjsesqb%^YBG*gFrdVb%x8nd=C|YqoWcfRk+{zeh(v z2n3;=8vxc#6f|iWnw06s-nTIWSS?bh>c^3EmcPW8y6M6T=ik8 z>mN>70BEI@MWrEixT{42ci?yq1wRyN3B)L%y0TDC>PxAc7So&&saR6iLMU0Yuei>n zkWMnkh$tjJuvHoCRFm8Jx;@F{?P4v!jG`*%2=-=I3pke)?EKv5-3B^9qN=)OV&5pH zp^!JD+LcU#0m?cloblqh!>P?}*8?EWe5nPuCkzHjFoDK$;wbJTbqRt-3;v_CRZdLj z=MjLw)+D4&6!YeYyed3;$A+q65kP6KgP$U$ui^)2{fjifIm8-h?ZWqcqP_dVceVo# zI6%a-gKv7+L?J!y7waVwxB`W{5=)SKf-E#CB%tWPnxpu_dPY?wK#}$+xutcSdr~hc zUAtIIBn{RTGB>{yfGY06y*LX3P_m>_Xy2DoUjk3(c~HU{@&F_|bIe!jsK^;EfU&7t ziJ)!%N=|mv#772Pi=YZ143UL2M-oqIO9krbMFopnj4R|#nJFI9!LaaJig*^`fa zbbY`;w%=j77zU)(o z4Ul|yzh-n!GJi4R*9;7y!w=`L#5)qWO6wGIjm=pA`?^9s=WiJtd`4I@#LZB0e5USw84-rbIz8V#>Y=7r+QW zJqUw9OndXo&u&MZd_-HdsuzT!YZ3Fno@KuZM4So?N{-ul#6IP{P^`>TqmC8_vI+l#c7pJzp-chOOXdj8mvBs<>gd-SnO9mUCI%_yphqDe6|J)}R>1%;8 zfW+9O3}{q`7JKg-dO>}(m*bLBHkP#y0=_J~I5ajq} z7O27;A@*Zyq$m8lTAqL@%v9{0101*JOyjBchFs^e)CfW_xx!bS+>+0m*?Pzp+sGuEOEbBSIX-ExrnW>u?R?b&Nl1@?<)Vc3ZkW4%A5+a&M1ytyCy^;KDk49wo92< zI9cEL{Rr`i?BV;L7{_deYyzsy{v4Vk2L=WRQ*(m2ovlxVpZZuXgiR`$M@3=C!cKgM zXOtjTQd!5>ztKo9yIPSuK1-l%t6=~@U3Xg(S@NP_1;XVqkSGL-v*w3n#qm(+#im2{ z;T*+(oe(8?Z-iIutl4g2gtZtLXKY@7@)_*+1X%G7s@yloaC!z`3$e#kln)hGe|RZQ z+nNUmA6@ezw=&icM0G^bD3CkhfE?s@2rn+?z;)Dd#^zaZ&5Q`kN~Ff{C!@HkoV$%V zH3F{BnVyNV0>E32X;Q?d4udo5`Byv>XRZh0i6xv+bds4%%u!|1;W#4IA3yPk%x?>CX3T z`~03I(O39L{o7uU{E<40p|b1#_yp{d)^+67GFfV@8^A>&rnv&Sql%iT9tmO47M1c$ zcGg~RfPBWh0;DHEkmNIosOz~z)kwWNV$BC+fidO3QIRg)?KCDvkR$J%Yalt-7ygmz zUk*^JKJ5UuxmvOWMk5~JzV@E-w-cEuPg4Ls1FX}#W-NT5u`awGM|>QB;M!GGBAYh1 zisdY>seoFpHL3Sdqq15mwig>A0V40{z;`n_cZqDC&RD)%JEY1lGT)QyhRvTN{6~FG z=er+S5|1M%mjEI;r^fi8ybYLWiELn(Q7mb`*#wLcezDi~Co0DXsZcM4aN$B*yi=Ts zL4@TQGl4!6|6^>)*JZIa&Jh&t0olzGBe3C~10wG0fbTByqdn`kuKI+^(>sxoIj^7} zoelQ9D0<_+WguLQf9e^`{*g_l`7?uz5q*OAL0xPU~%M17)!oi#n-}zP;S8i{^2KU@0stb8g0VjRRODbH|1%m zTbM1aAaW5R0Gb*eLftVS>sa1!(z)Jji{eGPBb4q#G1l_6EzXr_76Jn{?yG6u8l{-+B@9KKT5Ux=q$$RpeOTQk$qVNOtkI0)ZWwc=Et$@NhA@@8*2 z6L&pu+68`e7ed^nc4@V@7oiDvmOMBmH3%wne5dAVwQHT#MSQNtpt&BEMb9chlx{UU z8NqsVJtWpmf^!o1vi%F`9-C0>`haKE>A^)l!_N|N1VM%@T;lJhj!wh?1$(iT?v$R} z9A{i16RPu-x&-tZbFT9Xm@DiOBlV6ub6JlQY8Do;WdCNSzP=Ex}3#N*9C$2?c6Z*`GmIanVS&3&H3_5gsMg@Y#6yH{LM_RQC$VmZaZ&0Fk?bhI|? z+~E0hek;zcO0*edF0&(16AS+|k6Co*V_)-L)L-$P)Ts!+MR|a}H}D1NI2~YJolmv~ zA*GDPtYNQ2y?y1;|M@0vkZS(QFuG#=sj^L7N<_Xnotf zY_a;$Ds@Ym0;j^qrp~}QEz+)GaOXO{#=ca%um|;yX{RN+eSZA?7qtz)+|ZtO=2P3z zcl-UZS`=GRUnVvqrE2y&I$uwSBqavOM6~KB(zQe)+lEt#Zt5Ag5%mT3Ld06wQuc4} z_`aNr$m^1`m;Dp(t+il3%R)tTDgw8lxX;d%SYva!uR1Sd{%vj}N7>0swiWDp+15)O z*gF?7ev@wBPQ29l?%ktneTg+7ki6y800SN;6}rRt7GAe_)0v7#A)pL;;d5s z)@n}ppW*j(V$^ac=Bq1pUP)Iwe9rsA2S3nAUwLTn?I!bWTYzSXyX#t%&9 z)AQKOX>WAk*mTH!5|98p*mG*p%`mU^IpU>qg7)s@K(UX)zuH>F_hP*GJGs#4N}MtL z8EaY3K>n_diTELO8skh(1iARW8sS*YA$P}QY{7AmBj?$2es9hn6RDHDBp5@p_h4!= zMsitXe1Y=_yQSwzu?>4=D>VzlO7)IJ(~e$xKQNB=tO)H%5Yo=1EP%zc;7=faGjaj8 z9UxBc3L(VtW20V~=B*O=!bcO11{|Qow}!~0o2sD69EtMW9pzLKf>0K@UQ7v)f51{Vd{!J$G_@7BVNw{CGS#~I;}A_ z>MDm1k+A%Vr|c|-YZIRnjz6Kj>2KcNe(&A~x06mf>HiKf?Tv4IV>|cl?`+RFh0u05ofGM5G7YZ!;H^6d$tVwPHHt5qlTd+_9($+j(WFSeZkecjd6oYvN z-i^agj*hE_XM-F8CC`-;uPBg0F2GhqVF8CJD+Y%%&vQl*qT3w~gVnMV`4dG|CE%0F zFF_K41H?)>=N_13b?-S94Gw5IE+{HFqc|r$xHoDzK}yXEbMahC5<3_pD>9%WyVmBD zIqJp24$hXEEc0jqGKWk42<&4Y5}Y+?WPL~lgG2=2Y~Po2YP509aF|f66adrKJStRx zR(dfd0DjYP=5I>s+5QyZ0U)>oL54F*XTDt*N$$yen9~GUMovoVAT*gvIk~EKuC(2 zRgGBZhC001x<+*pl^p=-NpY(xi&K~*ajZml0XyaS8LGE--+V5!CrO}_qz>Yh0KzEB zSm2n6#fAz@()Q3Y7Wa;C#iknPxKIG-fes(RcQ54t`krQw{o=Q;$z z0EDuyrM(H@3SR)X$kqr!2z%r+m;Y-!=bUrefd?Mg{^)*>Xuo&6+YUvEI;S~-8*8yA zBvCf&zD!n%=kJ`0>;cv%{#`z+X&p_Vqyz%Liz=ljaWV)QN137$3f6NU1u`TJAt~5; zj)Zvyz4E(UODg6GOfdF>txG9R+pAP1zKI>8kObghTR$u#z>WmnT;gN+2asqfB6^GT zVS7lHSrJBL$!Ow$?*YNg1HT|Aa}Ed)l7fthEya&Qv|ul5dw=u(FSE%QIv7&eGus}C zg1Pa{b1HQh3Z@kCD1b&+Ixl>%e`@y@k$vd~lmsj)*f;hv^TQYj+#ogkUMv(a!sb@w z>KT$z6nzCMv}cPzZzTmYl7R%|>IRCxZXfQ4k5K)|I1?oB;O{|1v7)j+_m@}@2-N9tpnDpl%fEf^FAaJ%s!58%VxvnYdV== zqW)~(k8?@g)~cGBU64c;AebO=OVN)aK1oOiJZ5r`5?FMB!lqR*f&y07y0>ewM=CH? zik6n3;9SFBW$g-lw!*kA2wMvqq(l>f8i2Oaxl=nu#q*0$WWKgKI)+kKRtzR~vSrVD zz_(=HGf6J{Ry%g=XeXb1a{JA-8{64$e``DZ@Wa~;*YG+d~wg8?(sSJ{u+luf*xOR}R=8|$T zCnTkb542_zd14|yVkCF2m%;dhQ0;MI%qM4>3TlYaBodeigQ;5$_h&6sKsP?KK%92n z(ODD#jjOw6UrjzDfNhlFQP`SM5e}Q6f&y_`QVhn2psOsZSP-t%`O0l^CUD4(+Ze1d z{{Wwd#PaYvVj$TYb`}xOvmV$l69NICWV3e?jj%N_ie@|(g-$1*)V;!9#gD+gWkOy) zCnf#@;KCGv`OaLfjO+GdNYBkxQfco<$r$uv`N-3eL$fghXi$Zzbe^G(i+BbdTuKL{ ze0JouC^cO=XuIeFV?oT#Jy?SQJQiu!&(aM1$Z^(WyrSa;DrmM(P$3)V2P(2j*dSjo zfXd7{=Tbb^l1Px-Kh_%0k8PdIv7LSFgYjORjkZ_GaqRA0-L;mSq$)qs3HaIPoKsEC zb0>T2WF!F!ArISLlDZ#-B=!ryB4V@j$+-;fEl~r{_0)XWmMvS_srSBLyUFf1X=nb~ z)7yzBo+x5k?x-x#iV7gT6V6h%G7*V7c#XY3r!+q4Jm3H!)`x(I*gNuZ2F%62WrCD) z6o^UKnWgan+{CeyfoK#uy^}`xhO-@v9KgWZQ&-6StW%o#P}N@?HqUO!y6@fRyGQ`@ zo_&hR8D9Wi8!zp#>taH zNX0(lxhR%}4gIacRwyPT>|k>hYd6RZ#2FGUqob4OscKXPJu?VeSkG3@iCld8>+ z)KRS%DZmIM9#`8G*~+e^;yyOc3*p$5Ov>WA>}j42!7>RelsHFr&WqbS06cqVx~eA0 z&+&g%v~%AxpN(8cxo#tJ&*g`sPP4Pw0hBM5>qaaB1z+<;v$zo=EH|2ep2YxzD1(?p z6(R|uR@CR>`LtdjIY5%MSV3Jru~R9>FsCw19*o?KVgnPaihM)thcVvv1LcpS6O4D& z0u|I?GYR8Cx@(|=xt}s+ZZ`|yFT*QBp zf2B2`;`?$}hRJyYSWrsG*t%tsZa*`7PK>iv1YEQIn=B``$6Y<{_p@1f#X_)+_lfud zo5}sNkH*+*4_Pi?^Ont>n0C#E_Q!wt#CHEj+^6lahoR?ZKFZfX4l>}l01yZ%dRK-T zSH!v=;*DPS$@MMl8$g=1bQ73So#hdjQvgxwF2UL7LNox<$o~THRorIR$cnf6U8^N? z-2gs^kVgQ7dl!)oFjE(l0)ngE)l{3IE6Jjnsz@Iq^IB~(=fkRv$7bvK0Q#8SnfSEH zo@BM;5Mv>qxN7(Avu85XWQw95nZ$o85ME>AxpH+5U`|kIPNEdyGmuLJC=2M2+Sx?( zkTr_BK3#w8nPj){jR0Fyb19@}@;KXm-i4#)ayxUHGCNhRO*2{Yl-&@3d1NQMQ^+Ok8gaRg=uzc?UCR2y21RPO4+hTClXObTg z;Bdfc{hXYF*sGwB{8fwR*BUV;YLkFhz$>u#YArLKo02shZxVWgd5|>5ex4IswVSgCl#919{r>mA-#&8Q$J&p6c}4r_RX=U()~s)b9CTqcJuZ7O=4EngYcb*r>LDUFE(aB`_6`T%)L99{`5b5e0$-`|5|@usq3%s zK#(+T&tx!OJo}uDqqfMHk-yFRD|Z)^FONV8Vt+|MPv;&0KwC@fxe8L%vtlz42{WHT zc@M(_C|^%b1pv4GdhGx#k_z1f9r(<3V*lovWW~)y7of0lOZ8W3D7Kb}8I`9h!a)IW z#I7PZ8=!tLUTZ8KrBgxve;kigT9%UB}OHb!j)Bu99|H z{}8`~&2oYP-(!p-jvU>q=*Cn9ZTw+lOe1$5weBE?0Ragc6|WyGU_ z?Tfrr)zaj3`Qm-KYqq~2703GRm-Q*GBxS?(Yv ztSi@Y0mR9(+qG-mE!GPEt+~DMu{j5OO`+F#D>*afmv!0w%H9E_c5(mUyFS`3`s63t z&wq7Qd-^k;-i|){X#Fa_B(((P^S#y=dsT5=5>(0dDaRw#PZSQr#s?on{vWjl@_t^= ziTZ->N32CX+;IY0m-f3W5#|+-+8US$x7r@#?45|$(e=h+2i3Odo(NIeY>vcEJogi0 zSLb>B+_6qPKjSG3o}DpyeX2tdXSkC{C-EpbU}`+RzVJiiY|3>_ktrb*0aUkVoa-_j zxk(8g+OrVj#z(ndKOe?=U@efd_8M!(iSeC!w()oQKW3BG-g*IC2!it}(hVH3PGnP! z-p<+(gP5~}!l*j2I)sf73(y=>52Q1Y`8TTNrSm|0)X$}iC&b?9T|EylR}&5KC2G^u z;@Jxl{BaSdETl%9iLWL+j{85t+8LfvIBsy6W$p58abNzdMjK!dINPgu3q%7jX(s3# zBFdwY0YiXDo%ubgd3e0WGqW$2{4sKc5T_h>fDg!+X|F;mhLmZsec*1ix0C4GoS^r} zH4-DZu&CzM-aF1MVZg{4SZ;naa!?1LPLx^c!?^Lge29lKnK^z(giQ0p`1ioKN>Iwp zF#-`;#COgg!^hbI?%w7ZdsQ)#)z8o5|A_g?kL1`U8sE^sM7SG!jw~Lk{bOs2+yi7% zY+dal&9iW}vo3O-PxTx~f1a1@xVesr4_sTts02TNJcrISa$q(_>Ne%|Q^oOMc)Xq) zF=wrnh^Jv|jGAmbS6$;hkIn*G2$_Ztr1j+@X1Phtw!JsD^UptD#Fb<2a9n%xAOA_a z#eN4Ycn(F7S$T51N48_a#oKx&*OT{2CNwHXM}7vJgFQ>E+wf`0o6q5@m^0!*bz-%e zxbmyy1*o04yQ2L_-Hfhajw6sQ0*`E;z_vwQYeLM%*4ub-K4pQZLA<2iYM+(3HxVy{ zSWNig_!nCYyFpH=Hh++aExvJJn?$7SzGe7vvHm&tvhUCv)hud9BIH?|uRQF+{LAK# zPH_o~GbhZaIs{!w4TGHVM#7$$FE9ihU5JZ07y_5kRgC*5vmy~W;ZsuV=(*VK@JQ%t zjIfiv3pKuIYM`<9I<)LdnonXEa&@e;{OM=f0)MD@5v&2C|4B4ph@0AcXDlj&V&Zqk zKsEyGtHsOI=Q3_+7AIrR(_^WrSZxS`PH@AXN67O$=C5=qMyQXvXKq3=cTIdH>RMG$ z;`n1uXy?A`+;*#j50udOTi^QDcJRRmuW0vwr;ceadEwu+k9_RI?Z3a^x$R!}x>p}8 zTgw7J+D6Ij%g$cy%E|H3qM#U?oFFCoYSEF!3#sj}X>v^BFW(i2**0`mn$PNzC~1nMSClF z0LCz6SsydS@HmVF2xopsm{MplQk8y&I@F9}VrFGe3iftE75)I&0FXtc!PbD<__}ga zwi;r`b6`*~BLGOCLB6LVkUbXHV=jVlHm8ms017!_=8Q+dOUZpm7pP%4$O+cISoscu z$<9_TjEM1V2`)%&`J=HlklwDhSb**n3}XXO^R>e_in%0DG9a{q-1MG+LTn92FwK<}b;iN5WU2ID$Pp(BdP2 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 z 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%0D2bC05k9 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

rAlFg$C}9Z;vp-|6B)0Q(jB&_TEce{g?(W2I8)k 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%0Dp*4U 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�>G#w3cIiJ`!8~-nv=w0S5#)CN}Xhl)oOsw66 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 z%0D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